From 6f3ce169608b31da43cbcb3e2a504aecf7683a37 Mon Sep 17 00:00:00 2001 From: Kisa Date: Wed, 24 Jun 2026 15:53:10 -0400 Subject: [PATCH] verify+fix: Pi's dedup + Convex-spike designs build-ready [CC] - dedup: separate client-mapped plan_type (not payer) so CoverageLine never guesses plan type; fix merge_quantities to sum across distinct component configs (blanket-max dropped qty in mixed dup+split groups) - convex-spike: shipments orderNumber/hcpcs -> arrays (post-dedup multi-value); de-stale dedup note to LOCKED - ledger: Design Verification section added Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/convex-signal-spike-design-2026-06-24.md | 9 +++-- docs/dedup-design-2026-06-24.md | 37 ++++++++++++------- docs/ship-status-ledger.md | 8 ++++ 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/docs/convex-signal-spike-design-2026-06-24.md b/docs/convex-signal-spike-design-2026-06-24.md index 5d8a861..6f369b1 100644 --- a/docs/convex-signal-spike-design-2026-06-24.md +++ b/docs/convex-signal-spike-design-2026-06-24.md @@ -6,6 +6,7 @@ **Approves:** Kisa **Source:** docs/convex-signal-instance-brief-2026-06-24.md **Status:** Design input. This is a SEPARATE, parallel spike in repo `signal-convex`. The current Signal build keeps running. +**Verification fixes (Claude, 2026-06-24):** `shipments.orderNumber`/`hcpcs` changed to arrays to match the deduped multi-value model; §11 dedup note updated (the key is now LOCKED). Carry the dedup doc's plan_type fix: grade on the client-mapped `planType`, never the payer name. --- @@ -214,10 +215,10 @@ export default defineSchema({ shipments: defineTable({ lineId: v.id("coverageLines"), dateOfService: v.string(), // YYYY-MM-DD - quantity: v.number(), + quantity: v.number(), // total after dedup (sum across distinct component configs) components: v.array(v.string()), - orderNumber: v.string(), - hcpcs: v.string(), + orderNumbers: v.array(v.string()), // post-dedup: collected, display-only + hcpcs: v.array(v.string()), // post-dedup: collected, display-only isCurrent: v.boolean(), }).index("by_line", ["lineId"]) .index("by_line_dos", ["lineId", "dateOfService"]), @@ -513,7 +514,7 @@ The priority is proving the core thesis: CSV-in → grade → worklist-display o ### Dedup key (carried from readiness-model decision) -**Pending Kisa's decision** — same patient + device + DOS under two order numbers → one worklist line or two? The spike design supports both. The `shipments` table has `orderNumber` as a displayed field (not a dedup key), and the dedup logic lives in `readiness/engine.ts`. When Kisa decides, one line changes in the engine. +**LOCKED 2026-06-24** — key = `(patient_id, device_type, date_of_service)`; order numbers are display-only (stored as `orderNumbers: v.array`), never in the dedup key. Full logic (tiebreakers, quantity-merge guard, test cases) in `docs/dedup-design-2026-06-24.md`. Dedup runs inside the CSV import mutation, before grading. ### Data-access interface boundary diff --git a/docs/dedup-design-2026-06-24.md b/docs/dedup-design-2026-06-24.md index 55e1b44..cf14e08 100644 --- a/docs/dedup-design-2026-06-24.md +++ b/docs/dedup-design-2026-06-24.md @@ -4,6 +4,7 @@ **Design by:** Pi (decision locked by Kisa) **Builds by:** Claude (Phase: patient/line/shipment grouping + required-vs-input verdict) **Source:** docs/readiness-model-brief-2026-06-23.md, Kisa dedup decision 2026-06-24 +**Verification fixes (Claude, 2026-06-24):** (1) added a separate client-mapped `plan_type` field, distinct from display `payer`, so CoverageLine keys on the mapped plan type and never the payer name (locked 2026-06-23); (2) fixed `merge_quantities` to sum across distinct component configs instead of taking a blanket max. --- @@ -58,7 +59,8 @@ class MergedShipment: components: list[str] # combined set, deduped order_numbers: list[str] # collected, displayed only hcpcs_codes: list[str] # collected, displayed only - payer: str # taken from the LATEST row in the group + payer: str # raw payer NAME from the LATEST row — DISPLAY ONLY + plan_type: str # REQUIRED client-mapped plan type (medicare|medicare_advantage|medicaid|commercial|unknown); NEVER derived from payer name; used for CoverageLine keying + grading # CSV doc fields — merged from all rows, latest non-null wins csv_visit_date: date | None csv_swo_status: str | None @@ -91,7 +93,7 @@ When multiple CSV rows have the same `(patient_id, device_type, DOS)`: | Normal split | Row A qty=3, Row B qty=1 | total_qty = 4 | Sensor + transmitter = 4 components | | Duplicate | Row A qty=3, Row B qty=3 (same components) | total_qty = 6 | ⚠️ This IS the risk of summing: a data-entry duplicate doubles the quantity. Mitigation: flag rows where components are identical across rows in the same group. If all fields except order_number match exactly, it is a likely duplicate → take max, not sum. | | Zero row | Row A qty=3, Row B qty=0 | total_qty = 3 | Zero-quantity rows are informational (e.g., a cancelation) and do not add to quantity | -| Wildly different | Row A qty=3, Row B qty=100 | total_qty = 103 | This is almost certainly an error. Flag for review. For the pilot, take max (100) and log a warning. | +| Wildly different | Row A qty=3, Row B qty=100 (same component) | total_qty = 100 | Almost certainly an error. Same component config, so take max (100, not sum); flag for review and log a warning. | ### Recommended default for quantity ambiguity @@ -99,18 +101,25 @@ When multiple CSV rows have the same `(patient_id, device_type, DOS)`: ```python def merge_quantities(rows_in_group: list[CSVRow]) -> int: - """Sum quantities within a dedup group. - If two rows have identical components, take max instead of sum - (likely a data-entry duplicate, not a real split).""" - component_sets = [set(row.components.split(",")) for row in rows_in_group] - unique_component_configs = set(frozenset(cs) for cs in component_sets) + """Sum quantities across DISTINCT component configs within a dedup group, + while collapsing exact-duplicate rows. - if len(unique_component_configs) < len(rows_in_group): - # At least one component config appears more than once - # Take the maximum quantity, not the sum (defense against dupes) - return max(row.quantity for row in rows_in_group) + Group rows by their component set. Within one config, identical rows are + likely data-entry duplicates, so take the MAX for that config (not the sum). + Then SUM across distinct configs (a real component split, e.g. sensor + transmitter). - return sum(row.quantity for row in rows_in_group) + Fixes the mixed case: two duplicate [sensor] rows + one [transmitter] row + -> max(sensor)=3 + max(transmitter)=1 = 4, not a blanket global max of 3. + """ + from collections import defaultdict + + by_config: dict[frozenset, list[int]] = defaultdict(list) + for row in rows_in_group: + config = frozenset(row.components.split(",")) + by_config[config].append(row.quantity) + + # Per distinct config: max (defend against duplicate rows). Across configs: sum. + return sum(max(qtys) for qtys in by_config.values()) ``` --- @@ -125,11 +134,11 @@ def assign_to_coverage_lines(merged_shipments: list[MergedShipment]) -> dict[Cov lines: dict[CoverageLineKey, list[MergedShipment]] = {} for shipment in merged_shipments: - # Capline: plan_type comes from client's CSV mapping, not from guessing + # plan_type is the client-mapped REQUIRED field, never the payer name (locked 2026-06-23) key = CoverageLineKey( patient_id=shipment.patient_id, device_type=shipment.device_type, - plan_type_id=shipment.payer, # The client-mapped plan type, not the raw payer name + plan_type_id=shipment.plan_type, # mapped plan type; payer name is display-only ) lines.setdefault(key, []).append(shipment) diff --git a/docs/ship-status-ledger.md b/docs/ship-status-ledger.md index 957c101..ac34cf9 100644 --- a/docs/ship-status-ledger.md +++ b/docs/ship-status-ledger.md @@ -51,3 +51,11 @@ Canonical, git-tracked record of **what shipped, what didn't, and the reason or ## Maintenance Update this whenever an item ships or a blocker changes. Move shipped items to a `## Shipped (archive)` section when the table grows. Both agents reason from this; Kisa validates and redirects. + +## Design Verification (2026-06-24, Claude) + +Pi's dedup design (item 9) and Convex-spike design (item 6) were verified line-by-line. Both are build-ready; fixes applied directly to the design docs: + +- **dedup-design-2026-06-24.md**: (1) added a separate client-mapped `plan_type` field, distinct from display `payer`, so CoverageLine keys on the mapped plan type, never the payer name — the original draft reintroduced the killed plan-type-guessing bug. (2) Fixed `merge_quantities` to sum across distinct component configs; the old blanket-max dropped quantity in mixed duplicate+split groups (e.g. two [sensor] + one [transmitter] returned 3 instead of 4). +- **convex-signal-spike-design-2026-06-24.md**: `shipments.orderNumber`/`hcpcs` changed to arrays (post-dedup multi-value); §11 dedup note de-staled to LOCKED; carry-forward note to grade on the mapped `planType`. +- Noted, not a blocker: "Convex auth -> Clerk = config-only swap" is optimistic; treat as low-but-nonzero effort (Convex Auth still maturing).