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.qt templates: one in the detail dialog's signals: block (EC19A4EE-363D-4D41-A33A-D5F85A139C80), one at the end of a new wireDetailDialogCommon() controller helper (DF72DFCF-FD65-4FE5-98BA-CB8168CE0FDF). Documented both in paste_blocks_in_codegen and the ores.cpp.qt meta-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-wrapped emit self->statusMessage(...) to a direct signal-to-signal connect, which Qt treats identically).
  • Modelled "Browse Holidays" properly in calendar.org using the new seams instead of leaving it as hand-edited generated code.
  • Regenerated calendar_type, currency_market_tier, monetary_nature, calendar, currency via compass 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 new badgeCache_ constructor parameter wired from ctx_.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 --diff on 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

PR Title
#1950 [ores.trading] Port Batch 2 trading entity-convention rollout
#1784 [ores.refdata] Finish Batch 1: badge_key for remaining 8 refdata entities

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.

Emacs 29.3 (Org mode 9.6.15)