Database-Driven Badge System Design

Overview

Badge rendering in ORE Studio is currently fully hardcoded in C++. Colours live in ColorConstants.hpp, resolver functions live in BadgeColors.hpp, and some delegates (e.g. AccountItemDelegate) hardcode colours inline. There is no shared language for what a badge means — no severity, no tooltip, no label. Qt can use this, but Wt cannot, and every new badge type requires a C++ change.

Goals

  • Introduce a database-driven badge system where a badge definition (label, colours, severity, description, CSS class hint) lives in the dq schema as reference data.
  • A universal mapping table links any (code_domain, entity_code) pair to a badge definition.
  • Both Qt and Wt resolve badges at runtime via the standard service/protocol/client pipeline — no C++ changes needed for new badge types.
  • ores.codegen gains awareness of badge columns so the delegate wiring is generated automatically.
  • All traces of the legacy hardcoded approach are removed; migration is done in clean phases with no backwards-compatibility shims.

Success Criteria

  • A single DB table is the source of truth for all badge visual metadata.
  • Adding a new badge type requires only a DB population entry and a codegen model annotation — no new C++ resolver functions.
  • Qt and Wt use identical badge definitions loaded at startup.
  • All existing badges (accounts, parties, books, DQ dimensions, FSM states, compute tasks) are migrated to the new system.
  • BadgeColors.hpp and the badge_colors struct in ColorConstants.hpp are deleted.

Architecture

The badge system is built entirely on existing infrastructure patterns:

  • Four new domain entities in the dq namespace, generated by ores.codegen in the normal way.
  • Badge definitions are reference data — loaded at client startup via the standard service/protocol/client pipeline, exactly like currencies or party statuses.
  • A new BadgeCache class replaces all hardcoded resolver callbacks. It is injected into delegates at construction.
  • ores.codegen gains a badge annotation for Qt column metadata, driving delegate wiring generation.
  • DelegatePaintUtils (pill rendering) and EntityItemDelegate structure are unchanged.

Domain Model

Four new entities in the dq namespace.

badge_severity

Standard reference data entity. Codes align with Bootstrap 5 contextual classes so Wt rendering requires no translation layer.

Column Type Notes
code text PK: secondary, info, success, warning, danger, primary
name text Display label
description text Tooltip / explanatory text
display_order int  

Full codegen treatment: domain type, JSON I/O, table I/O, service, protocol, Qt management UI.

code_domain

A reusable classification registry — a named namespace that disambiguates enum codes across entity types (e.g. ACTIVE in party_status vs ACTIVE in book_status). Useful beyond badges for any future system needing to namespace code values.

Column Type Notes
code text PK: e.g. party_status, fsm_state, dq_nature
name text e.g. Party Status
description text  
display_order int  

Full codegen treatment with management UI.

badge_definition

The badge catalogue. One row per named badge.

Column Type Notes
code text PK: e.g. active, locked, fsm_draft
name text Display label e.g. Active
description text Tooltip text
background_colour text Hex e.g. #22c55e
text_colour text Hex e.g. #ffffff
severity_code text Soft FK → badge_severity
css_class text Nullable; Bootstrap hint for Wt e.g. badge bg-success
display_order int  

Full codegen treatment with management UI. css_class is nullable — unused by Qt, consumed by Wt when present, extensible for future CSS frameworks.

badge_mapping

Universal link table. Maps any (code_domain, entity_code) pair to a badge.

Column Type Notes
code_domain_code text Soft FK → code_domain
entity_code text e.g. ACTIVE, DRAFT
badge_code text Soft FK → badge_definition

PK: (tenant_id, code_domain_code, entity_code, valid_from). Temporal, tenant-scoped. Junction-style codegen: SQL + C++ domain type + repository. No management UI — populated via seed scripts.

Data Flow

Badge definitions follow the identical path as every other reference data type.

Startup Sequence

  1. Qt/Wt client connects and authenticates.
  2. Client requests reference data from the service layer. Badge severities, code domains, badge definitions, and badge mappings are loaded as part of this initial fetch.
  3. A new BadgeCache class holds the loaded data and exposes a single lookup:
std::optional<badge_definition>
resolve(std::string_view code_domain, std::string_view entity_code) const;
  1. All Qt delegates that currently use badge_color_resolver callbacks are replaced with BadgeCache::resolve(). The cache is injected at construction.

Render Time — Qt

  1. Delegate calls cache.resolve("party_status", "ACTIVE").
  2. Gets back a badge_definition with background_colour, text_colour, name, severity_code.
  3. Passes colours to the existing draw_centered_badge() / draw_inline_badge() in DelegatePaintUtils — those functions do not change.
  4. If no mapping is found, falls back to the existing default grey.

Render Time — Wt

  1. Same cache.resolve() call.
  2. Uses css_class from the definition if present (e.g. badge bg-success), otherwise constructs an inline style from background_colour / text_colour.

Codegen Changes

New Models

Four new JSON model files in models/dq/:

  • badge_severity_domain_entity.json
  • code_domain_domain_entity.json
  • badge_definition_domain_entity.json
  • badge_mapping_junction.json

Plus a population seed file (badge_definitions_populate.json) encoding all current hardcoded colours from BadgeColors.hpp and ColorConstants.hpp as initial data.

Badge Column Annotation

The Qt column metadata in domain entity JSON gains an optional badge object:

{
  "field": "status_code",
  "header": "Status",
  "width": 100,
  "is_string": true,
  "badge": {
    "code_domain": "party_status"
  }
}

When codegen sees badge on a column it:

  • Sets the column style to badge_centered (already exists).
  • Generates delegate wiring that calls BadgeCache::resolve("party_status", value) instead of a hardcoded resolver callback.
  • Emits no resolver function.

Adding a badge column to a new entity type in the future is fully declarative: annotate the model, run codegen, done.

Migration

Migration is done in three clean phases. Each phase produces a PR that leaves the codebase strictly cleaner than before. No backwards-compatibility shims — legacy code is removed as each phase completes.

Phase 1 — Infrastructure

  • Add the four new DB tables via codegen-generated SQL.
  • Populate badge_severities, code_domains, badge_definitions, and badge_mappings from the current hardcoded values in BadgeColors.hpp and ColorConstants.hpp.
  • Implement BadgeCache and wire it into the service/protocol/client startup sequence.
  • Existing delegates still use the old resolver callbacks during this phase.

Phase 2 — Delegate Migration

Migrate delegates one by one, annotating each codegen model and regenerating:

  1. Simple status delegates sharing resolve_status_badge_color: parties, books, business units, counterparties.
  2. Specialised delegates: portfolios, compute tasks, report definitions.
  3. AccountItemDelegate — inline hardcoded colours, worst offender.
  4. DatasetItemDelegate — multi-badge cells (Origin + Nature + Treatment), most structurally different.

Phase 3 — Cleanup

  • Delete BadgeColors.hpp.
  • Remove badge_colors struct from ColorConstants.hpp.
  • Remove badge_color_resolver callback from EntityItemDelegate.
  • Remove all individual resolver functions.

Testing Strategy

Unit Tests (Catch2)

  • BadgeCache: table-driven tests for resolve() with known mappings, missing mapping returns empty optional, case handling.
  • badge_definition domain type: standard JSON round-trip and table I/O tests, same pattern as every other domain entity.

Integration Tests

  • Badge definitions and mappings load correctly via the service layer on startup.
  • Seed data completeness: every (code_domain, entity_code) pair previously hardcoded in BadgeColors.hpp has a corresponding mapping entry — asserted by comparing the seed file against the old resolver logic during migration.

Open Questions

  • Should BadgeCache be a standalone component or merged into the existing reference data cache? To be decided during Phase 1 implementation.
  • Wt badge rendering will need a dedicated widget/utility equivalent to DelegatePaintUtils. Scope to be defined when Wt work begins.