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.

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.

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

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.

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.

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.

Out of scope

Emacs 29.3 (Org mode 9.6.15)