> For the complete documentation index, see [llms.txt](https://docs.digit.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.digit.org/complaints-management/design/architecture/complaints-classification.md).

# Complaints Classification

**What changed, in one line:** Complaint types used to be a fixed 2-level hierarchy (Type → Sub‑type) implicit in `ServiceDefs.menuPath`. They are now a **configurable N‑level hierarchy** (e.g. Authority → Main Category → Sector → Sub‑type) held in **two MDMS masters**, and the leaf complaint types live **inside the hierarchy** itself. This is a **breaking, lockstep** change — `ServiceDefs` is removed, `pgr-services` reads the new master, and there is **no flat fallback**: every tenant must be migrated.

<table data-header-hidden><thead><tr><th width="190.97265625"></th><th width="220.83984375"></th><th></th></tr></thead><tbody><tr><td></td><td>Before (<code>develop</code>)</td><td>After (this change)</td></tr><tr><td>Levels</td><td>Fixed <strong>2</strong></td><td>Configurable <strong>N</strong> (≥2)</td></tr><tr><td>Where the tree lives</td><td><em>implicit</em> in <code>ServiceDefs.menuPath</code></td><td><em>explicit</em> <code>ComplaintHierarchyDefinition</code> + <code>ComplaintHierarchy</code></td></tr><tr><td>Where the <strong>leaf</strong> lives</td><td>the <code>ServiceDefs</code> record</td><td>a <code>ComplaintHierarchy</code> <strong>leaf row</strong> (at the <code>isLeafServiceCode</code> level)</td></tr><tr><td>Picker UI</td><td>2 dropdowns</td><td>N cascading dropdowns</td></tr><tr><td>Backend (<code>pgr-services</code>, analytics MV)</td><td>—</td><td><strong>changed</strong> — validates + sources SLA + builds the grain MV from <code>ComplaintHierarchy</code></td></tr><tr><td>Per‑tenant opt‑in</td><td>—</td><td><strong>no</strong> — breaking; every tenant migrated before cutover; <strong>no flat fallback</strong></td></tr></tbody></table>

### 1. Before — the old 2‑level model

**MDMS:** a single schema `RAINMAKER-PGR.ServiceDefs`. One record per complaint **sub‑type**:

<table><thead><tr><th width="309.109375">Field</th><th>Meaning</th></tr></thead><tbody><tr><td><code>serviceCode</code></td><td>the sub‑type (the leaf; stored on every complaint)</td></tr><tr><td><code>menuPath</code></td><td>the <strong>category</strong> code (level 1)</td></tr><tr><td><code>menuPathName</code></td><td>category display label</td></tr><tr><td><code>department</code>, <code>slaHours</code>, <code>keywords</code>, <code>active</code>, <code>order</code></td><td>the rest</td></tr></tbody></table>

The 2 levels were **implicit** — the UI built them at render time by grouping records on `menuPath`:

<figure><img src="https://2437503360-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxNs29HvQfLFAPZCcDd7D%2Fuploads%2FHtaHezBfRG7wMks8MSda%2Fimage.png?alt=media&amp;token=dd4bbba6-19d0-4966-bca6-23f9132b63c3" alt=""><figcaption></figcaption></figure>

**Screens (old):** citizen *File Complaint* and employee *Create Complaint* each show **2 dropdowns** (Type → Sub‑type); the details page shows **2 rows** (Complaint Type / Sub‑Type).

### 2. After — the configurable N‑level model

The tree becomes **explicit data** in **two** masters, and the leaf complaint types live **inside the tree** (not in a separate master):

<figure><img src="https://2437503360-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxNs29HvQfLFAPZCcDd7D%2Fuploads%2FnWyClsFqs0FQYsxMWtMu%2Fimage.png?alt=media&amp;token=9f3896bd-fab3-46b2-9abf-6c36573f7647" alt=""><figcaption></figcaption></figure>

* **`ComplaintHierarchyDefinition`** declares *the number of* levels and their order (the shape). Exactly one level has `isLeafServiceCode: true`.
* **`ComplaintHierarchy`** is a single adjacency list holding **every** node — interior level nodes **and** leaf complaint sub‑types — each pointing at its `parentCode`. Leaf rows (at the `isLeafServiceCode` level) additionally carry `department` / `departments[]` / `slaHours` / `keywords`. **A leaf row's `code` IS the `serviceCode` stored on a complaint.**

{% hint style="info" %}
There is no `ServiceDefs` master and no separate node master anymore. A row is a **leaf** iff it carries `department` or `slaHours` (interior nodes omit them).
{% endhint %}

**Concrete example — `ke.bomet` (2 levels: CATEGORY → SUB\_TYPE):**

<figure><img src="https://2437503360-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxNs29HvQfLFAPZCcDd7D%2Fuploads%2F4CgtoSgwXpWENLyQGGOM%2Fimage.png?alt=media&amp;token=8365ef26-29f2-46aa-a174-30a466813fd6" alt=""><figcaption></figcaption></figure>

**Screens (new):** the pickers render **one dropdown per level** (cascading), and the details pages show **one row per level** (Category → … → Sub‑Type) instead of the flat pair. Grouping labels (the old `menuPathName`) are now derived from the leaf's parent node `name`.

### 3. The MDMS Masters

<table><thead><tr><th width="165.49609375">Schema</th><th width="164.38671875">Status</th><th width="256.625">Key fields</th><th>Role</th></tr></thead><tbody><tr><td><code>RAINMAKER-PGR.ComplaintHierarchyDefinition</code></td><td><strong>kept (unchanged)</strong></td><td><code>hierarchyType</code>, <code>active</code>, <code>levels[] {levelCode, order, parentLevel, isFreeText, isLeafServiceCode, label}</code></td><td>the level shape (one per tenant)</td></tr><tr><td><code>RAINMAKER-PGR.ComplaintHierarchy</code></td><td><strong>the merged master</strong></td><td><code>hierarchyType</code>, <code>levelCode</code>, <code>code</code>, <code>parentCode</code>, <code>name</code>, <code>order</code>, <code>active</code>, <code>path</code> — <strong>plus leaf‑only</strong> <code>department</code>, <code>departments[]</code>, <code>slaHours</code>, <code>keywords</code></td><td>the whole tree: interior nodes AND leaf complaint types in one adjacency list</td></tr><tr><td><del><code>RAINMAKER-PGR.ServiceDefs</code></del></td><td><strong>removed</strong></td><td>—</td><td>folded into <code>ComplaintHierarchy</code> leaf rows (<code>serviceCode → code</code>, verbatim)</td></tr><tr><td><del><code>RAINMAKER-PGR.ClassificationNode</code></del></td><td><strong>removed</strong></td><td>—</td><td>renamed/merged into <code>ComplaintHierarchy</code> (interior rows)</td></tr><tr><td><del><code>RAINMAKER-PGR.HierarchySchema</code></del></td><td><strong>removed</strong></td><td>—</td><td>per‑module window — dropped; derived from <code>levels[]</code> order</td></tr><tr><td><del><code>RAINMAKER-PGR.ComplaintTypeDepartments</code></del></td><td><strong>removed</strong></td><td>—</td><td>multi‑dept folded inline into the leaf row's <code>departments[]</code></td></tr></tbody></table>

{% hint style="info" %}
**`menuPath` / `menuPathName` are gone from the masters.** They were UI‑only derived values. The leaf→parent link is now the explicit `parentCode` field; the group label is the parent node's `name`. Code that needs the legacy `menuPath`/`menuPathName` shape reconstructs it at the data‑access layer (`menuPath = leaf.parentCode`, `menuPathName = parent node name`).
{% endhint %}

**Leaf-only fields & detection.** `department`, `departments[]`, `slaHours`, `keywords` appear **only** on leaf rows; interior nodes omit them. The schema allows this mixed shape because those four are optional `properties` (not `required`) under `additionalProperties:false`. A row is a **leaf** iff `department` or `slaHours` is present — this is exactly the predicate pgr-services and the analytics MV use.

### 4. What changed (by area)

<table><thead><tr><th width="160.08984375">Area</th><th>Files</th><th>Nature</th></tr></thead><tbody><tr><td><strong>MDMS schemas</strong></td><td><code>schema/RAINMAKER-PGR.json</code> + data‑handler config + Helm <code>values.yaml</code></td><td><strong>breaking</strong> — rename <code>ClassificationNode</code>→<code>ComplaintHierarchy</code> + leaf fields; delete <code>ServiceDefs</code>/<code>HierarchySchema</code>/<code>ComplaintTypeDepartments</code></td></tr><tr><td><strong>Backend</strong> (<code>pgr-services</code>)</td><td><code>PGRConstants</code>, <code>MDMSUtils</code>, <code>ServiceRequestValidator</code>, <code>PGRService</code>, <code>NotificationService</code>, <code>DashboardQueryBuilder</code>, <code>PGRQueryBuilder</code>, <code>MigrationUtils</code>, <code>V20260608000000__create_v2_grain_mvs.sql</code></td><td><strong>changed</strong> — validate / SLA‑map / dept‑name / grain MV all read <code>ComplaintHierarchy</code> leaf rows</td></tr><tr><td><strong>Citizen / employee UI</strong></td><td><code>digit-ui-esbuild/.../pgr</code>, <code>frontend/micro-ui/web/.../pgr</code>, <code>digit-ui-v2</code> — cascade picker + create flows + details breakdown</td><td>repointed to <code>ComplaintHierarchy</code>; <strong>no flat fallback</strong></td></tr><tr><td><strong>Configurator</strong></td><td>hierarchy resources, Phase‑3 Excel setup, one‑click migrate button</td><td>rewritten to write leaf rows into <code>ComplaintHierarchy</code>; breaking/one‑way copy</td></tr></tbody></table>

`pgr-services` now validates `serviceCode` against **`ComplaintHierarchy` leaf rows** (JSONPath `$.MdmsRes.RAINMAKER-PGR.ComplaintHierarchy[?(@.code=='X')]`, leaf‑only), sources the SLA map from their `slaHours`, and the V2‑grain materialised view builds from them (`WHERE schemacode = 'RAINMAKER-PGR.ComplaintHierarchy' AND data->>'department' IS NOT NULL`, `service_group = parentCode`). This is **not** backend‑untouched.

### 5. Rollout — why this is breaking (and how nothing-breaks-once-migrated)

This is a **BREAKING, mandatory, lockstep** change. There is **no per‑tenant opt‑in and no flat fallback**. Once `ServiceDefs` is removed and `pgr-services` reads `ComplaintHierarchy`, an un‑migrated tenant has a **hard outage**:

<div align="left"><figure><img src="https://2437503360-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxNs29HvQfLFAPZCcDd7D%2Fuploads%2Fiu8ZLhJuMYOpiCV8xWv8%2Fimage.png?alt=media&amp;token=c0939fe5-dd44-4691-9789-1b8bb76d011e" alt="" width="375"><figcaption></figcaption></figure></div>

<table><thead><tr><th width="283.96875">Surface</th><th>After cutover, un‑migrated tenant →</th></tr></thead><tbody><tr><td>Citizen <em>File Complaint</em></td><td><code>CS_NO_COMPLAINT_HIERARCHY</code> — cannot file</td></tr><tr><td>Employee <em>Create Complaint</em></td><td>no options — cannot create</td></tr><tr><td>pgr-services create/update</td><td><code>INVALID_SERVICECODE</code> on every request</td></tr><tr><td>Citizen / Employee <strong>details</strong></td><td>path resolver returns null (no crash, but no breakdown)</td></tr></tbody></table>

**Why it is safe once every tenant is migrated, in order:**

1. **Leaf `code` == old `serviceCode`, verbatim** → every already‑filed complaint still resolves; no complaint data is rewritten.
2. **Lockstep release.** Masters migration (every tenant, city **and** state level) → pgr-services cutover + restart + V2‑MV → all frontend bundles + indexer/chatbot/MCP → only **then** delete the old masters.
3. **Preflight gate.** The read‑only dry‑run asserts schemas are installed, `(hierarchyType, code)` globally unique, every old serviceCode is present as a leaf, and leaf fields are carried — before any write.
4. **Rollback is via snapshot, not a delete.** Because leaves were *moved* (not added alongside `ServiceDefs`), reverting means restoring the old masters from the mandatory MDMS snapshot, redeploying the old pgr-services images, and reverting the V2‑MV migration — **not** "delete the definition + nodes".

### 6. Migrating a tenant (2 → N)

Migration **moves** the leaf data: each `ServiceDefs` record becomes a `ComplaintHierarchy` leaf row (`code = serviceCode` verbatim, plus `parentCode`, `department`/`departments[]`/`slaHours`/`keywords`), and each `ClassificationNode` interior row is copied 1:1. It is idempotent on `(hierarchyType, code)` , but it is **not** additive and **not** reversible by deletion.

<table><thead><tr><th width="178.94921875">Path</th><th>How</th></tr></thead><tbody><tr><td><strong>One click</strong></td><td>Configurator → <em>Manage → Complaint Hierarchies</em> → <strong>Migrate from 2‑level</strong> (writes leaf rows into <code>ComplaintHierarchy</code>; reads the old masters as a read‑only source)</td></tr><tr><td><strong>Headless</strong></td><td>masters‑migration script in the <a href="/complaints-management/release-notes/migration-guide.md">migration guide</a></td></tr><tr><td><strong>Pre‑flight (gate)</strong></td><td><a href="https://github.com/egovernments/Citizen-Complaint-Resolution-System/blob/master/docs/migration/preflight-dryrun.cjs"><code>preflight-dryrun.cjs</code></a> — read‑only; asserts uniqueness + verbatim‑code preservation + backend‑validation readiness</td></tr><tr><td><strong>Rollback</strong></td><td>restore <code>ServiceDefs</code>/<code>ClassificationNode</code> from the MDMS snapshot + redeploy old pgr-services images + revert the V2‑MV migration</td></tr></tbody></table>

Mandatory production order: **snapshot → install schemas → migrate every tenant (city + state) → pre‑flight green → deploy pgr-services (restart) + V2‑MV → deploy all frontends → only then delete old masters**. Filed complaints are never rewritten — correctness hinges on the verbatim leaf `code` = old `serviceCode`.

### 7. At a glance — the whole shift

<figure><img src="https://2437503360-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxNs29HvQfLFAPZCcDd7D%2Fuploads%2FC4VutQARHBN7l6FBiYAh%2Fimage.png?alt=media&amp;token=9beae3ba-7de6-465d-87e3-0de31ac1d275" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.digit.org/complaints-management/design/architecture/complaints-classification.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
