Task: Implement classified entities in batches
Table of Contents
This page documents a task in the Classify and roll out badge/image/plain-text rendering across domain entities story. It captures the goal, current status, acceptance, and any notes or results.
Goal
Every entity the audit/classification task classified as badge or image either has that rendering wired and regenerated, or has an explicit, documented reason it doesn't (e.g. too many distinct values for a badge to be useful). Worked through in the audit's batches, not all 120 entities in one pass – each batch should be its own, independently reviewable unit of work.
Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Classify and roll out badge/image/plain-text rendering across domain entities |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-08-10 |
Acceptance
- Every 'ready-to-annotate' badge entry from the audit gets badge_key added to its model and is regenerated.
- Every 'ready-to-annotate' image entry gets its image/flag wiring added and is regenerated.
- Every 'needs new data' entry gets its badge_definition/badge_mapping rows or image assets authored first, following the established colour-semantic convention (grey=disabled/no, green=good, red=bad, yellow=warning) where applicable.
- Any audit entry deliberately skipped has a one-line reason recorded in this task's Result.
- Each batch is its own commit (not a separate PR – explicit instruction to minimise PR count, all batches land in the same PR as the audit/classification analysis); zero diff on entities not touched by that batch; build clean; existing badge/image-related tests still pass after each batch.
Plan
Picked up Batch 1. Checking the DB directly showed only 3 of the
audit's 11 listed entities (calendar_type, currency_market_tier,
monetary_nature) actually have code_domain=/=badge_definition=/
=badge_mapping rows today; the other 8 need that data authored
first (same as Batch 2) – scoped this pass down to the 3 genuinely
ready ones and corrected the audit task's Batch 1 description.
Self-badging pattern (per precedent book_status, task 5BAE2E27):
each of the 3 entities' own code column gets is_badge:
true=/=badge_key: <itself> in its "Columns (Qt model)" table.
Consumer wiring: calendar.org's calendar_type field and
currency.org's monetary_nature=/=market_tier fields get their
badge_key cell set to point at the corresponding domain
(currency.org already had this from earlier session work;
calendar.org didn't even have a badge_key column in its Detail
fields table header, so that column was added).
Regenerating calendar's Qt facet exposed unrelated drift: a
"Browse Holidays" toolbar action had been hand-added directly to the
generated CalendarDetailDialog=/=CalendarController files in a
past commit, never modelled in calendar.org, and codegen had no
paste seam to declare a custom dialog signal or relay it through the
controller – regenerating would have silently deleted a working
feature. Root-caused and fixed properly rather than working around
it:
- Added two new paste seams to the shared
ores.cpp.qttemplates: one in the detail dialog'ssignals:block (EC19A4EE-363D-4D41-A33A-D5F85A139C80), one at the end of a newwireDetailDialogCommon()controller helper (DF72DFCF-FD65-4FE5-98BA-CB8168CE0FDF). Documented both in paste_blocks_in_codegen and theores.cpp.qtmeta-model. wireDetailDialogCommon()is itself new: extracted the cache-wiring/status/error-connect boilerplate that was duplicated identically across all four detail-dialog construction sites (add/edit/history-version/revert) into one generated helper, so future custom-connect seams (like the one above) only need pasting once instead of four times, and the four sites can't drift from each other. Verified equivalent behaviour (two of the four sites moved from lambda-wrappedemit self->statusMessage(...)to a direct signal-to-signalconnect, which Qt treats identically).- Modelled "Browse Holidays" properly in
calendar.orgusing the new seams instead of leaving it as hand-edited generated code. - Regenerated
calendar_type,currency_market_tier,monetary_nature,calendar,currencyviacompass codegen entity generate <entity> --address ores.cpp.qt, diffing each first – every diff was exactly the expected badge_key/refactor change, no unrelated drift once the seam fix was in place. - Fixed two call sites in
RefdataPlugin.cpp(MonetaryNatureController,CurrencyMarketTierController) that needed the newbadgeCache_constructor parameter wired fromctx_.badge_cache. - Not regenerated: the ~100+ other entities with detail dialogs now
differ mechanically from the refactored template (still using the
old duplicated-per-site pattern) – functionally fine, but a future
--diffon any of them will show this mechanical change until they're next touched. Not done here to keep this PR's diff scoped; flagged as optional follow-up cleanup, not a defect.
Build clean (ores.qt.exe, ores.refdata.core.lib);
ctest -R "refdata|badge" 4/4 passed.
Batch 1, remaining 8 entities
Authored the code_domain=/=badge_definition=/=badge_mapping data
the audit assumed already existed (book_purpose_type,
business_unit_type, contact_type, ledger_feed_type, leg_type,
purpose_type, rounding_type, tenor_anchor; 46 distinct values
total) in both places that must stay in sync:
dq_badge_system_populate.sql (the actual runtime seed, via
PERFORM upsert calls) and the three artefact mirror scripts
(dq_code_domains_artefact_populate.sql,
dq_badge_definitions_artefact_populate.sql,
dq_badge_mappings_artefact_populate.sql) that feed the
publish-from-dq pipeline.
Colour choices follow ux_language.org's documented rule: green/red/amber
("RAG") and gray are reserved for genuine status/lifecycle semantics,
never a plain category. None of these 8 domains are lifecycle
states (they're pure classifications), so all use the
Blue/Teal/Violet/Purple/Fuchsia/Pink/Indigo/Sky/Slate "classification"
family, cycled per domain; gray is used only for the two values that
genuinely mean "none" (ledger_feed_type's None,
tenor_anchor's NONE) and book_purpose_type's Test (not a
production purpose). Hit one real bug this surfaced: badge_definitions
has a (tenant_id, name) unique constraint, and both None values
initially shared that display name – renamed to "No Feed"/"No
Anchor" (caught by a full db recreate, not by inspection).
Self-badged all 8 entities' own code columns
(is_badge=/=badge_key) the same way as the first 3; none had a
badge_key column in their "Columns (Qt model)" table yet, so it was
added throughout. Regenerated all 8 via compass codegen entity
generate <entity> --address ores.cpp.qt, diffed first – each diff
was the expected badge_key wiring plus the wireDetailDialogCommon()
refactor (all 8 already had detail dialogs, unlike calendar_type
which turned out to be dead-code-wired). Fixed 8 more
RefdataPlugin.cpp construction sites needing the new badgeCache_
parameter (all 8 controllers ARE instantiated there, confirmed by the
build failing with exactly 8 "no matching constructor" errors, then
passing clean once fixed).
Build clean (ores.qt.exe); ctest -R "refdata|badge" (ores.dq +
ores.refdata) all passed.
Notes
Test Scenarios
Manual QA scenarios (scaffolded via compass add test_scenario, run
through the QA Validation Runner panel) that verify this task. Link
new ones here as they're created; the scenario doc itself links back
via its "Verifies task" field.
| Scenario | State | Notes |
|---|---|---|
PRs
Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 | drift check failure: templates hand-edited instead of their org source | cpp_qt_controller.{hpp,cpp}.mustache, cpp_qt_detail_dialog.hpp.mustache | Fixed | CI check, not a review comment – moved the same edits into the tangle sources (ores.cpp.qt.controller_header/impl.org, ores.cpp.qt.detail_dialog_header.org) and re-tangled; verified byte-identical output. |
| 2 | Stale link display text for retitled story 6AC16489 | fk-lookup-entity-badge-image-policy/story.org, badge_adoption_audit_rollout/task_*.org, badge_colour_scheme_snags/story.org | Fixed | Updated all 6 occurrences (including the two pre-existing, lower-priority ones the bot flagged as out of scope). |
| 3 | currency_pair classified as both Image and Badge | task_badge-adoption-coverage-audit.org | Fixed | Not a duplicate – it genuinely has flag icons (base/quote combos) and a badge (classification field). Added a "Both" note clarifying the policy is per-field, not per-entity. |
| 4 | 108 vs 120 entity-count mismatch | story.org, task_annotate-audited-entities-with-badges.org | Fixed | Updated acceptance/description text to the corrected 120 count. |
| 5 | Cosmetic: badge_key column not padded to table width | ores.refdata.{monetary_nature,currency_market_tier}.org | Fixed | Re-aligned; regenerated diff confirmed no functional change. |
| 6 | One more stale-link occurrence (sprint_23, out of scope, optional) | sprint_23/badge-colour-scheme-support/story.org | Fixed | Bot confirmed rounds 1-5 fully resolved; fixed this low-priority extra find anyway since it was trivial. |
| 7 (PR #1784) | purpose_type badge data documents wrong consumer (report_definition instead of portfolio) | dq_badge_system_populate.sql + 3 artefact mirrors | Fixed | purpose_type actually classifies portfolio's Purpose column, not report_definition (which has no purpose field at all); confirmed by two independent review passes. Reworded the comment, code_domain description, and all 4 badge_definition descriptions to reference portfolio instead. |
| 8 (PR #1950) | party_role_type messaging stack never registered | core/src/messaging/registrar.cpp | Fixed | register_party_role_type_handlers appended to the NATS fan-out after the family handlers, with its header included. |
| 9 (PR #1950) | party_role_type/trade_type event mappings never invoked | service/src/app/application.cpp | Fixed | both register_X_event_mapping calls added after the six instrument mappings, before event_source.start(), so the Postgres LISTEN/NOTIFY -> NATS pipeline now emits change events for both entities. |
| 10 (PR #1950) | trade_type handler/registrar dead code | core/src/messaging/registrar_trades.cpp | Fixed | the four legacy instrument_ref_handler trade-type subscription blocks replaced with register_trade_type_handlers. |
| 11 (PR #1950) | Silent reply drop on decode failure in instrument_ref_handler | instrument_ref_handler.hpp (deleted) | Fixed | moot by retirement: the header-only handler (only caller was the trade-type blocks) is deleted; trade_type now routes through the entity-shaped handler, which calls error_reply() on decode failure. |
| 12 (PR #1950) | Generators double-increment the uniqueness counter | party_role_type/trade_type_generator.cpp + both orgs | Fixed | dropped the leftover ++counter in both generators and both org generator expressions; codes now match the refdata party_type pattern (noun_role-<idx>), one atomic increment per record, no double suffix. |
| 13 (PR #1950) | trade_type table dump loses the Product column | trade_type_table.cpp + ores.trading.trade_type.org | Fixed | product_type column added to converter and Table display section; rendered via to_string() – the codegen table template streams enum columns raw and cannot compile (first enum column in a table display; template gap captured separately). |
| 14 (PR #1950) | product_type validation dropped in trade_type_mapper (minor) | trade_type_mapper.cpp | Not changed | intentional: trade_type_entity::product_type is a typed domain::product_type column and sqlgen handles the enum directly; the string-parse logic_error failure mode no longer applies. |
| 15 (PR #1950) | <unordered_set> included but unused (minor) | both generator .cpp files | Not changed | pre-existing codegen template pattern elsewhere in the codebase, not introduced by this PR. |
Result
Batch 1 shipped (PR #1784): all 11 refdata entities
(calendar_type, currency_market_tier, monetary_nature,
book_purpose_type, business_unit_type, contact_type,
ledger_feed_type, leg_type, purpose_type, rounding_type,
tenor_anchor) self-badged, with the first 3's consumers
(calendar, currency) wired too. The other 8 needed new
code_domain=/=badge_definition=/=badge_mapping data authored first
(46 values, colour-mapped per ux_language.org's RAG/gray rules) –
authored in both the runtime seed and its artefact-catalogue mirror.
Regenerated, built, tested throughout.
Batch 2 shipped as the trading slice. The cross-component rollout
(feature/badge-batch2-cross-component) decomposed at close: the
other components' content is superseded on main or deleted surface,
leaving trading as the substantive remainder. party_role_type and
trade_type were migrated to the full entity shape (primary key
folded into Columns, Table display section) and regenerated –
generators now use tenant-scoped generation
(generation_keys::tenant_id, thread-safe counter) and emit
version = 0 per the current codegen template; the generated table,
eventing, messaging, presentation, repository, and service files
absent on main were ported. Their generator tests were updated to the
current codegen's version = 0 convention – the same
stale-expectation fix already applied to the refdata generators
(main's trading generators predate the template change and were
never regenerated).
Batch 3 (plain-text documentation) is tracked as capture Batch 3 of the badge/image/plain-text rendering rollout.
Side effect, fixed during batch 1: a standing codegen gap – there
was no way to add a custom signal to a generated Qt detail dialog (or
relay it through its controller) without hand-editing generated code,
and the four per-window call sites that wire a detail dialog's caches
were duplicated verbatim. Both fixed with two new paste seams and a
shared wireDetailDialogCommon() helper in the ores.cpp.qt
templates – see Plan for detail. Only the 5 touched entities were
regenerated against the refactored templates; the rest of the
codebase's detail dialogs still use the old (functionally
equivalent) generated pattern until next touched.