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_requeststurned 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\}\}), socalendar_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--diffagainst another junction (nolist_byset) 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_uuidvs. a string-only.empty()fallback, with no case for theis_dateflag 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 errorcalendar_datedid. 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-existingparent_entity_ singular=/=has_parent_relationshipmechanism – 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 itsopenAddWithParentpattern instead: a new publicopenForParent(...)entry point delegates to a private overload, while the required zero-argEntityController:: showListWindow()override still satisfies the base class contract. Verified via--diffagainstbook=/=currency(knob unset) plus a temporary, reverted smoke test oncurrency.orgwith 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
** Qtdrawer 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 keyheadingorg_loader.pynever 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 noPaginationWidgetat all, present no misleading affordance – split into a lower-priority follow-up; 5 of those 45 have a model but no** Qtdrawer 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--diffoutput – 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
* Revisionsection): 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 inores.analytics.quantand two new refdata entities (calendar_rule,calendar_exception) with full Qt CRUD, superseding the original plan to wire upcalendar_adjustmentas the persisted entity – it stayed a transient export-time DTO instead, assembled only when building ORE'sCalendarAdjustmentsfile. - 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.testsfail (an assertion, FK violations, and a SIGABRT/memory-corruption crash) on unmodifiedmain, reproduced on a branch touching zeroores.iamfiles. 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-inOreDateEditreplacement rather than a standalone new-widget-from-scratch: highlights non-business dates viaQCalendarWidget::setDateTextFormat()(no custom paint code needed), sourced from paged reads of the existing materialisedcalendar_datesendpoint 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 staticTARGETdefault rather than deriving the calendar from the form's selected currency, since no client-facing currency-to-calendar lookup exists yet (currency_calendarhas 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 againstcalendars.codeactually checked a nonexistentidcolumn, because their.orgmodels set:referenced_column:, a property name the codegen template never reads (only:target_column:is consumed) – a dead-key trap inorg_loader.pyitself, now removed so no other entity can fall into it. Reported independently by another environment (prime_origin) via a from-cleancompass 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 explicitdb recreatefirst, not just a greenctest, to avoid repeating that mistake.