Part 1 β Ontology & Taxonomy Admin
One of eight part-DRs decomposing the Notify-Me feature (CU-86c1p963g / Track B). Siblings: tagging, resident prefs, engine/selector, rollout/flags. Supersedes the taxonomy sections of the earlier single-file v1 DR (now removed).
TL;DRβ
A single controlled vocabulary β Gali's "City Events Ontology" v2 (city-events-ontology.json) β is the source of truth for how every event is classified and what a resident can subscribe to. Ten categories, each with a 3-letter code and a colour, each holding a two-level tree (concept group β applied tag). It is greenfield: no migration from the four legacy category systems (system-categories, communities-categories, community_tags, tag_groups), which are flat/no-alias and superseded. Super-admins own the canonical L1/L2; org/community admins add aliases (display-only, multi-language). L3 (user-created) is a later phase.
Data model ()β
Mongo collection notify-categories (apps/notifications/src/notify-categories/schemas/notify-category.schema.ts). Flat rows + parentKey form the tree (read = one find, build tree in memory, cache β no $graphLookup):
| field | meaning |
|---|---|
key | stable, language-independent, dotted path e.g. mai.sports_and_fitness.team_sports |
type | one of the 10 category codes (NotifyCategoryTypeEnum) |
code, color, multi | denormalised category facet (code MAIβ¦GEN, hex colour, cardinality) |
level | 0 = category, 1 = concept group, 2 = applied tag |
parentKey | parent row's key (undefined at level 0) |
labels | a lang->text map (English default) for display |
scope | system | org | community |
source | canonical | requested (the request flow) |
status | active | pending | rejected |
overrides[] | per-scope aliases { scope, scopeId, labels{he,en} } |
The ontology JSON lives in-repo at apps/notifications/src/notify-categories/ontology/city-events-ontology.ts; the seed (notify-categories.seed.ts) flattens it to the tree on module init (idempotent $setOnInsert bulkWrite). Seeded shape: 282 rows (10 categories, 60 groups, 212 tags).
Aliases (org/community relabel)β
An alias is a display label override only β the key never changes, so matching/analytics are unaffected. Stored as overrides[] embedded in each row (cache-resolved, no runtime lookup β the argument that satisfied the no-lookup requirement). Precedence at read time: community > org > canonical. Aliases are a multi-language lang->text map (English default; add/remove any supported language) β fixing the single-language gap.
API ( β internal-gateway, @SuperGuard)β
apps/internal-gateway/src/controllers/notify-digest/notify-digest.controller.ts, mounted under /api/v2/notify:
GET /categoriesβ full taxonomy (282 rows).POST /categoriesβ create a node (CreateNotifyCategoryDto, now carriescode/color/multi).PUT /categories/:keyβ update label/status/emoji.DELETE /categories/:keyβ delete, cascades to descendant keys.PUT /categories/:key/overrideβ upsert a per-scope multi-language alias.
RPC patterns on the notifications service: NotifyDigestEndpointsEnum (GET/CREATE/UPDATE/DELETE_NOTIFY_CATEGORY, UPSERT_NOTIFY_CATEGORY_OVERRIDE).
Surfacesβ
- Super-admin taxonomy admin (support-tool
/notify-digest): categoryβgroupβtag tree with colours/codes, collapse + filter, per-node alias (multi-language modal) + delete (cascade). From@bewith-dev/design-system(Button/TextField/Typography/Search) + MUI layout primitives. - Org-dashboard "Notifications Categories" (Settings subsection, planned): view the canonical taxonomy read-only, manage org/community aliases only. Mirror
batch-settings.tsx+CommunitiesConfigurationStore(MobX + DS).
Decisions lockedβ
- Greenfield, no migration; the 4 legacy systems are superseded.
- L1+L2 = system canonical (super-admin); org/community give aliases; L3 (user-created) = next phase.
- Aliases are display-only, multi-language, precedence community > org > canonical, cache-resolved.
- Key is UUID-like stable dotted path, language-independent; labels are separate/deferrable.
Open questionsβ
- OQ1 (Gali): the district/department values in the JSON are placeholders β replace with each city's real taxonomy before launch; does it map 1:1 onto existing Bewith data?
- Request flow (
status: pending+ a resident/admin "request a category" action) β UX undefined; a later phase. - Versioning of the ontology (
catalogVersion) + an admin approval workflow (FR-A6/A7) β P1/P2.
Design-system gapβ
The DS has no tree/accordion/card/table, and its Chip is option-typed (ObjectOptionType, not a plain label). The admin tree therefore uses MUI layout primitives + DS-token values. Follow-up: add a plain label-Chip variant and a tree/accordion to the DS (via /add-component) so future taxonomy UIs are fully DS-native.
Testingβ
- Seed idempotency: re-run leaves 282 rows, no dupes.
- Cascade delete: deleting a category removes its groups + tags.
- Alias upsert: replaces the same (scope, scopeId), persists the label map.