Story: Calendar entity follow-ups: date picker, list-pagination fix, QuantLib materialization

Table of Contents

This page documents a story in Sprint 24. It captures the goal, current status, acceptance criteria, and the tasks that compose it.

1. Goal

Pick up three follow-ups beyond Model calendars as proper ORE Studio reference data (Sprint 23, in progress) that aren't required by its own acceptance: a holiday-aware date picker widget, a list-truncation/pagination bug fix, and QuantLib calendar-holiday materialization.

2. Status

Field Value
State DONE
Parent sprint Sprint 24
Now All three original acceptance items DONE (date picker, pagination fix, QuantLib materialisation). Closed at sprint 24 health review: the three remaining pagination-scope follow-up tasks are not required by this story's acceptance and were split into Calendar follow-ups: remaining hand-written list pagination cleanup in the product backlog.
Waiting on Nothing.
Next Nothing – see the split-off story for the remaining follow-ups.
Last touched 2026-08-03

3. Acceptance

  • A holiday-aware date picker widget exists.
  • The silent 100-row list truncation on entities missing has_pagination is fixed.
  • Real QuantLib calendar holidays are materialised, with calendar-adjustment support.

4. Tasks

Task State Start End Description
Analyse implementation approach for calendar entity follow-ups DONE 2026-07-29 2026-07-29 Analyse how to implement the three calendar-followup items (holiday-aware date picker widget, list-pagination fix, QuantLib calendar materialisation/adjustments): scope each, identify design decisions and risks, and produce a plan before implementation starts.
Holiday-aware date picker widget DONE 2026-07-30 2026-07-30 A reusable Qt date-picker widget that takes one or more calendars as input, highlights their holidays (with a tooltip naming the source calendar), and is used everywhere a date sensitive to holidays needs to be picked.
Fix silent 100-row list truncation on entities missing has_pagination DONE 2026-07-29 2026-07-30 30 Qt entities (29 ores.refdata/ores.marketdata + calendar_type) show working-looking pagination controls (Next/Load All/page size) but their Client*Model.cpp never sends offset/limit to the server and reports total_available_count as just the fetched page size – silently capping every list at the server's default limit (100) with no visible indication rows are missing.
Materialise QuantLib calendar holidays and support calendar adjustments DONE 2026-07-24 2026-07-30 Materialise QuantLib-computed holiday dates into a real, DQ-published, read-only refdata table, clearly attributed as QuantLib-sourced, and model calendar_adjustment as a proper refdata entity so users can layer institution-specific overrides on top of a QuantLib base calendar instead of editing it directly.
Wire ORE export path for user-authored calendar templates DONE     Assemble a CalendarAdjustments XML file for source='user' calendars referenced by an ORE export/run, using the existing calendar_adjustment_mapper::reverse() – part of the quantlib-calendar-transparency-and-adjustments task's step 5, split out because no ORE-run export mechanism exists yet in the codebase to hang it off.
Add codegen Qt facet for paginated read-only junction-scoped lists DONE   2026-07-25 Extend ores.codegen's Qt facet with a new shape for junction-backed, read-only, server-paginated lists scoped to a parent key – a client model + list dialog (no detail dialog, nothing to edit), reusable for calendar_dates and future large materialised/derived datasets. Closer to the existing History dialog shape (read-only, parent-scoped) than to the relationship-assignment-widget pattern (CalendarAssignmentWidget) used by every existing small many-to-many junction today.
Calendar Qt UI: source/editability display, regenerate button, browse-holidays view DONE 2026-07-26 2026-07-27 Remaining step 6 pieces: Calendar detail dialog shows source/is_editable/base_calendar_code and locks the form for source=quantlib rows; a Regenerate-up-to-year button wired to the calendar_dates regenerate NATS command; a Browse Holidays view backed by the paginated read-only junction-list codegen facet (blocked on that facet existing).
Codegen: extend junction repository/service templates with paginated list_by filter DONE 2026-07-27 2026-07-27 Add the same list_by/pagination knob that domain_entity foreign_keys already have (read_latest_by_COLUMN with offset/limit through repository and service) to the junction repository/service mustache templates, so a junction like calendar_dates can expose a paginated, filtered by-left-or-right-column list method.
Codegen: generate calendar_date service/protocol/nats-handler layers using extra_list_requests DONE 2026-07-27 2026-07-28 First real consumer of the existing but never-exercised extra_list_requests knob: generate calendar_date's service, protocol, and nats-handler layers with a paginated list-by-calendar_code request/response, once the junction list_by pagination knob exists. Validates the extra_list_requests mechanism end to end for the first time.
Codegen: design and implement Qt parent-scoping for has_readonly_paginated_list DONE 2026-07-28 2026-07-28 The Qt cpp.qt facet has a has_readonly_paginated_list knob but no way to scope a generated list window's get-request to an owning parent key (e.g. a calendar's calendar_code). Design and implement a parent_key_field/parent_key_param-style knob in the client_model/controller/mdi_window templates so a paginated read-only list can be opened for a specific parent and pass that key on every request.
Generate calendar_dates Qt facet and wire Browse Holidays action DONE 2026-07-29 2026-07-29 Once the junction pagination knob, calendar_date messaging layer, and Qt parent-scoping knob all exist: generate calendar_date's Qt facet (has_readonly_paginated_list, scoped by calendar_code) and add a Browse Holidays action to Calendar's detail dialog that opens it for the selected calendar. Closes the deferred acceptance item from task B2888D7D.
Codegen: teach the ores.cpp.qt facet to render junction models DONE   2026-07-29 The ores.cpp.qt facet's mustache templates and ~1300 lines of enrichment in core.py/org_loader.py are hard-wired to the domain_entity model shape (entity_pascal/entity_singular derivation, ** Qt drawer parsing including detail_fields/combo-widget machinery). Junction models load into a structurally different {"junction": {…}} dict (name_singular instead of entity_singular, no Qt-section parsing in load_org_junction_model at all, no entity_pascal derivation anywhere in the junction path). Flipping ores.cpp.qt.org's #+model_types: to include junction routes generation correctly (verified: dry-run produces the right 6 files, has_readonly_paginated_list detail-dialog suppression works, zero drift on unrelated domain entities) but the actual render is broken: empty class names (class Controller), empty includes (#include ""), mangled macro guards (ORES_QT__CONTROLLER_HPP) – because entity_pascal/entity_singular and the Qt-drawer context are simply never populated for a junction model.
Fix calendar_rule/calendar_exception soft-FK check: wrong property name silently no-ops the code override DONE 2026-07-30 2026-07-30 calendar_rule.org and calendar_exception.org set :referenced_column: code on their calendar_code soft-FK, but sql_schema_domain_entity_create.mustache only ever reads :target_column:. The override is a complete no-op, so both generated insert triggers check id = NEW.calendar_code against ores_refdata_calendars_tbl, which has no id column at all – every calendar_rule/calendar_exception INSERT fails. Discovered independently by prime_origin (fresh db recreate reproduction) while this session's own local DB was stale enough to mask it.

5. Decisions

  • Promoted from the product backlog into Sprint 24 rather than starting straight into the three pre-scaffolded implementation tasks: added an analysis task first to scope each item, surface design decisions and risks, and produce an implementation plan before committing to any of the three.
  • Closed the analysis task (Analyse implementation approach for calendar entity follow-ups) without producing fresh analysis: by the time it was picked up, each of the three follow-ups had already accumulated its own implementation plan organically through iterative work on the story's other tasks (the QuantLib materialisation chain alone ran to 6+ sub-tasks, all DONE). Re-deriving that analysis fresh would have duplicated, not added to, what already existed. Status per follow-up: materialisation + browse-holidays UI DONE (calendar_adjustment's own CRUD/UI still open under Materialise QuantLib calendar holidays and support calendar adjustments); pagination fix (Fix silent 100-row list truncation on entities missing has_pagination) fully scoped and ready to pick up directly (61 entities, one knob, regenerate – no design decisions left); date picker (Holiday-aware date picker widget) scoped but genuinely blocked on materialisation reaching DONE.
  • extra_list_requests turned out to be domain_entity-only (every facet using it declares #+model_types: domain_entity schema, and the mustache sections are wrapped in \{\{#domain_entity\}\}...\{\{/domain_entity\}\}), so calendar_date's messaging layer instead got a parallel, deliberately minimal \{\{#junction\}\} section added to the service/protocol/ nats-handler/nats-registrar templates – covering only the paginated read-only list a browse view needs, not full CRUD. Verified via --diff against another junction (no list_by set) that this adds zero regression risk to existing entities.
  • Fixed a latent codegen bug surfaced by this task: primary-key/ junction-key validation in the service templates only branched on is_uuid vs. a string-only .empty() fallback, with no case for the is_date flag that already existed in the data model. Any future entity with a non-string, non-UUID key column (e.g. a date) would hit the same compile error calendar_date did. Fixed at the template, not the generated file, and regenerated for real.
  • Added a new, distinctly-named :has_parent_scoped_list:=/ =:parent_key_field:=/:parent_key_param:= knob for scoping a :has_readonly_paginated_list: list window's get-request to a parent key, rather than reusing the pre-existing parent_entity_ singular=/=has_parent_relationship mechanism – that one solves a different problem (pre-filling a foreign key on create for a still-full-CRUD entity), and conflating the two would have been confusing. Mirrored its openAddWithParent pattern instead: a new public openForParent(...) entry point delegates to a private overload, while the required zero-arg EntityController:: showListWindow() override still satisfies the base class contract. Verified via --diff against book=/=currency (knob unset) plus a temporary, reverted smoke test on currency.org with the knob set, confirming valid generated C++ with zero regression to existing entities.
  • The pagination-truncation fix's original 61-entity list needed a 3-way scope split before any regeneration: 29 had a clean, real codegen model with a ** Qt drawer and current-convention primary key and were fixed directly; 16 hit a pre-existing, unrelated bug where the model still declares its primary key with a legacy * Primary key heading org_loader.py never parses (a repo-wide grep found 70+ files with this, not just these 16), split into its own follow-up; 11 have no codegen model at all (hand-written Qt predating the entity_org migration) and, having no PaginationWidget at all, present no misleading affordance – split into a lower-priority follow-up; 5 of those 45 have a model but no ** Qt drawer in it, so flipping the knob would be a no-op – split into a Qt-drawer-backfill follow-up. Rebasing onto main mid-investigation picked up a separately-merged PR that had already absorbed unrelated template-modernisation drift that was contaminating the first round of --diff output – confirms the value of syncing before large mechanical regen work rather than hand-reconciling drift that's already fixed elsewhere.
  • Started migrating badge_definition=/=badge_severity's legacy primary-key heading as part of this task, then reverted: their committed generated Qt files carry hand-patched logic (an __unmapped__ reserved-code delete guard, placeholder-text tweaks) the model has no way to express, so a real regen would have silently deleted a safety check. Treated the legacy-heading migration as its own reviewed task instead of folding it into whichever entity happens to touch it next – each of the 70+ files needs individual verification, not a blind batch fix.
  • Dropped QuantLib from the runtime path entirely during the materialisation task (see that task's own * Revision section): its global/static singleton state isn't safe under concurrent server request handlers, and its calendar source decomposes cleanly into a small set of timeless rules (fixed dates, nth-weekday-of-month, Easter offsets, weekend-observance shifts) plus flat one-off exceptions – a shape several independent open-source calendar libraries converge on independently. Replaced with a pure, dependency-light rule engine in ores.analytics.quant and two new refdata entities (calendar_rule, calendar_exception) with full Qt CRUD, superseding the original plan to wire up calendar_adjustment as the persisted entity – it stayed a transient export-time DTO instead, assembled only when building ORE's CalendarAdjustments file.
  • Closed the materialisation task without a DQ-publish artefact for calendar_dates, confirmed with the user: the DQ-publish pattern is DQ→refdata (external data synced in), the reverse of what a table refdata computes and owns itself needs.
  • Found and captured a pre-existing, unrelated defect while closing the materialisation task: ores.iam.api.tests=/ =ores.iam.core.tests fail (an assertion, FK violations, and a SIGABRT/memory-corruption crash) on unmodified main, reproduced on a branch touching zero ores.iam files. Not fixed under this story – filed as a product-backlog capture instead.
  • Shipped the holiday-aware date picker (HolidayAwareDatePicker, ores.qt.api) as a drop-in OreDateEdit replacement rather than a standalone new-widget-from-scratch: highlights non-business dates via QCalendarWidget::setDateTextFormat() (no custom paint code needed), sourced from paged reads of the existing materialised calendar_dates endpoint rather than a new bulk API, windowed lazily (1y back/5y forward around the popup's shown month, expanding on navigation) rather than eagerly fetching the full multi-decade materialisation horizon per calendar. The proof-point wiring (SwapInstrumentForm's FRA Fixing Date) uses a static TARGET default rather than deriving the calendar from the form's selected currency, since no client-facing currency-to-calendar lookup exists yet (currency_calendar has no NATS service/protocol layer – deriving that mapping is separate, future scope). All three of the story's original acceptance items (date picker, pagination fix, QuantLib materialisation) are now DONE; the three pagination-scope follow-up tasks remain BACKLOG for future pickup.
  • Found and fixed a real, currently-broken bug that had shipped undetected: calendar_rule=/=calendar_exception's soft-FK check against calendars.code actually checked a nonexistent id column, because their .org models set :referenced_column:, a property name the codegen template never reads (only :target_column: is consumed) – a dead-key trap in org_loader.py itself, now removed so no other entity can fall into it. Reported independently by another environment (prime_origin) via a from-clean compass db recreate; this session's own earlier "verified, tests passing" claims for the same trigger had been against a stale local Postgres schema that never picked up the (also-broken) earlier attempt at this fix – every subsequent verification in this story used an explicit db recreate first, not just a green ctest, to avoid repeating that mistake.

6. Out of scope

Emacs 29.3 (Org mode 9.6.15)