Story: Rethink synthetic reference-data generation across entities
Table of Contents
This page documents a story in Sprint 22. It captures the goal, current status, acceptance criteria, and the tasks that compose it.
Goal
Design and implement a proper, cross-entity mechanism for generating synthetic test data from the Qt UI — covering both a single-record fill (detail dialog "Generate" button, pre-fills the form with plausible data the user can tweak before saving) and batch generation (list-window "Generate N" button, creates N records) — that actually round-trips through the real save path, for any entity that opts in, not just currency. This supersedes and replaces currency's existing ad-hoc implementation, which is being removed as part of this story (see Analysis below) rather than carried forward.
Analysis: currency's previous implementation (removed)
Origin
Introduced in commit ef1326ee9 ("[qt] Add synthetic data generation
for currencies", 2026-01-07), well before the current org-model/codegen
system existed for the Qt layer. Added, in one shot:
system.synthetic_data_generationfeature flag (SQL-seeded).ClientCurrencyModelextended to track astd::set<std::string> synthetic_iso_codes_, consulted by theQt::ForegroundRoleoverride indata()to paint matching rows cornflower-blue.CurrencyMdiWindow: a "Generate" toolbar action, gated by the feature flag, opening a count-input dialog (QInputDialog::getInt, 1-50), calling a faker-based generator N times, and pushing the results straight into the client-side model viaadd_synthetic_currencies()— no server round-trip at all.CurrencyDetailDialog: a second, independent "Generate" toolbar action that fills the open form's fields from one generated record (does not touch the model or the server either).
Why it's half-baked — not actually save-able
ClientCurrencyModel also defines is_synthetic(), mark_as_saved(),
and clear_synthetic_markers() — clearly intended to let the blue
highlight clear once a generated row is actually persisted. None of
the three is ever called anywhere in the codebase (confirmed by
project-wide grep, 2026-07-08) — dead code from day one. The practical
consequence: a row inserted by the list-level Generate button exists
only in the client-side model, was never sent to the server, and has
no working path to becoming real:
- Double-clicking it to edit opens the detail dialog in edit mode (an existing-record assumption) for a currency the server has never heard of; saving would issue an UPDATE against a non-existent row.
- There is no "this row is new, POST it" affordance anywhere in the flow — the batch Generate button was, at best, a way to preview what fake data would look like in the grid, never a way to actually create records.
- The detail-dialog's own independent Generate button is the only one that ever produces a genuinely saveable record — because it just fills the currently open create-mode form; the user still has to click the real Save button themselves. The list-level batch version never got this right.
What was reused this session (2026-07-08), now also removed
While landing the "Sync Qt codegen for currency" task, the detail
dialog's Generate button (single-record fill into an already-open
form, click Save yourself) was carried forward and even generalised
into the qt-profile codegen template (gated by a generateAction row
in each entity's Setting-gated actions table, calling the generic
generate_synthetic_<entity>() function every ores.cpp.generator
facet already produces) — this part genuinely worked correctly, since
it never bypassed the save button. It is being removed here too,
pending this story's redesign, so the whole feature is designed once,
properly, rather than half-migrated.
Design questions for the redo
- Single-record fill: keep the working pattern (fill the open
create-mode form, user reviews/edits and clicks Save themselves) —
cheap, safe, no server-side concept of "unsaved synthetic row"
needed. Candidate to keep close to as-is, generalised via the
generator facet's
generate_synthetic_<entity>(). - Batch generation: needs an actual decision on the save path. Options: (a) generate + immediately POST each record to the server in a loop, surfacing real per-row success/failure, so the grid only ever shows genuinely-persisted rows (no synthetic/unsaved state to track at all); (b) keep an explicit "pending" concept but make it real — a dedicated pending-rows area, a bulk "Save all" action, and correct is_synthetic-driven routing to POST instead of PUT/PATCH on save; (c) drop batch grid-generation entirely and offer batch creation only via a script/CLI path, keeping the Qt UI to single-record fills.
- Whichever shape wins should be a generic mechanism (template +
org-model flag, same pattern as
Setting-gated actions=/=Related entity shortcuts), available to any entity, not hand-wired per-entity. - Decide whether "N fake rows previewed before committing" is a real user need at all, or whether it was solving a problem nobody asked for — worth confirming with whoever originally wanted the feature before rebuilding it.
Status
| Field | Value |
|---|---|
| State | BACKLOG |
| Parent sprint | Sprint 22 |
| Now | Not yet started. |
| Waiting on | Nothing. |
| Next | Break the story into tasks. |
| Last touched | 2026-07-08 |
Acceptance
- A design decision is recorded (in
* Decisionsbelow) on the batch generation save path (see the three options in Analysis), covering every entity that opts in, not just currency. - Single-record "Generate" (fill an open create-mode form) is
reimplemented as a generic template capability, gated by an
org-model flag, using the existing
generate_synthetic_<entity>()generator function — no per-entity hand-wiring. - If batch generation is kept, generated rows are either immediately persisted (no client-only "synthetic" state) or have a fully-working save path with no dead tracking methods.
- Currency's
ClientCurrencyModelregenerates for real with zero hand-maintained drift, picking up the 5 list-view columns (spot_days,deliverable,day_basis,base_precedence,holiday_calendar) that were blocked behind this rewrite. system.synthetic_data_generationsetting and any UI it gates reflect the new design, not the removed one.
Tasks
| Task | State | Start | End | Description |
|---|---|---|---|---|