From c6befae3304668a25f982dfe37fdacacc090d0bf Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 20 Jul 2026 07:02:39 +0000 Subject: [PATCH] feat(ogar-emitter): add OptionFlagToOptionBool + TruncateDate FieldSource variants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two additive projection-codegen transforms, driven by the medcare-rs read-arm W4 hand-off (docs/handoffs/W4-ogar-fieldsource-variants.md in medcare-rs). The FieldSource vocabulary is documented to "grow deliberately when a new consumer needs a transform not here" — these are exactly that: - OptionFlagToOptionBool { column } -> `r.col.map(|v| v != 0)` (`Option` flag -> `Option`; the nullable sibling of IntFlagToBool). Unblocks the `dms` projection (6 `Option` -> `Option` fields). - TruncateDate { column } -> `r.col.date()` (`NaiveDateTime` -> `NaiveDate`, non-optional; the non-Option sibling of MapNaiveDate). Unblocks the `lab_order` projection (`measured_on`). Both reproduce a spelling already present in the hand-written medcare projections byte-for-byte, so the value oracle replays green. No existing variant, golden, or emitter behaviour changes. New unit test asserts both arms render the exact hand-written RHS. Generated by Claude Code --- crates/ogar-emitter/src/projection_adapter.rs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/crates/ogar-emitter/src/projection_adapter.rs b/crates/ogar-emitter/src/projection_adapter.rs index e6cb274..c8db27a 100644 --- a/crates/ogar-emitter/src/projection_adapter.rs +++ b/crates/ogar-emitter/src/projection_adapter.rs @@ -103,6 +103,21 @@ pub enum FieldSource { /// Source column on the row binding. column: String, }, + /// `field: r.col.map(|v| v != 0)` — `Option` flag → `Option` + /// (the nullable sibling of [`FieldSource::IntFlagToBool`], which + /// unwraps the flag to a bare `bool`; here the `Option` is preserved). + OptionFlagToOptionBool { + /// Source column on the row binding. + column: String, + }, + /// `field: r.col.date()` — `NaiveDateTime` → `NaiveDate`, non-optional + /// (the non-`Option` sibling of [`FieldSource::MapNaiveDate`]; drops the + /// time component from a `NOT NULL DATETIME` column read as a full + /// timestamp). + TruncateDate { + /// Source column on the row binding. + column: String, + }, /// `field: ` — no backing column (e.g. `None` for a domain /// field absent from the live schema). Const { @@ -120,6 +135,10 @@ impl FieldSource { FieldSource::UnwrapOrDefault { column } => format!("r.{column}.unwrap_or_default()"), FieldSource::MapNaiveDate { column } => format!("r.{column}.map(|dt| dt.date())"), FieldSource::IntFlagToBool { column } => format!("r.{column}.unwrap_or(0) != 0"), + FieldSource::OptionFlagToOptionBool { column } => { + format!("r.{column}.map(|v| v != 0)") + } + FieldSource::TruncateDate { column } => format!("r.{column}.date()"), FieldSource::Const { literal } => literal.clone(), } } @@ -402,4 +421,40 @@ impl From for Patient { assert!(out.contains("// [concept: UNMINTED")); assert!(out.contains("unminted=1")); } + + /// The two variants added for the medcare-rs read-arm W4 hand-off + /// (`dms` needs `Option` → `Option`; `lab_order` needs a + /// non-optional `NaiveDateTime` → `NaiveDate` truncation). Each must + /// render the exact hand-written spelling those projections already use. + #[test] + fn option_flag_and_truncate_date_render_the_handwritten_spelling() { + let c = ProjectionClass { + row_type: "Row".to_string(), + domain_type: "Dom".to_string(), + concept: String::new(), + fields: vec![ + FieldProjection { + domain_field: "transfercomplete".to_string(), + source: FieldSource::OptionFlagToOptionBool { + column: "transfercomplete".to_string(), + }, + }, + FieldProjection { + domain_field: "measured_on".to_string(), + source: FieldSource::TruncateDate { + column: "file_date".to_string(), + }, + }, + ], + }; + let out = emit_projection_adapters(&[c]); + assert!( + out.contains("transfercomplete: r.transfercomplete.map(|v| v != 0),"), + "OptionFlagToOptionBool must render `.map(|v| v != 0)`" + ); + assert!( + out.contains("measured_on: r.file_date.date(),"), + "TruncateDate must render `.date()` (non-optional)" + ); + } }