From 1a4fa4fbd3c9e1d5a56a11569b86df79e04fe501 Mon Sep 17 00:00:00 2001 From: sudoshi Date: Wed, 5 Aug 2026 19:28:04 -0400 Subject: [PATCH] fix(nightingale): bind demo cohort to catalog evidence --- .../Demo/NightingaleDemoCohort.php | 32 ++++++- .../Demo/NightingaleDemoCohortProvisioner.php | 31 +++++- .../README.md | 17 +++- ...investor-demo-patient-cohort-2026-07-27.md | 96 +++++++++++++------ .../NightingaleDemoCohortProvisionerTest.php | 96 +++++++++++++++---- 5 files changed, 219 insertions(+), 53 deletions(-) diff --git a/app/Nightingale/Demo/NightingaleDemoCohort.php b/app/Nightingale/Demo/NightingaleDemoCohort.php index 26c73e83..e3918c67 100644 --- a/app/Nightingale/Demo/NightingaleDemoCohort.php +++ b/app/Nightingale/Demo/NightingaleDemoCohort.php @@ -24,14 +24,42 @@ final class NightingaleDemoCohort public const DEMO_NOTICE = 'DEMO — NOT FOR CLINICAL USE'; - public const CATALOG_RELEASE_UUID = '019f95de-b9a4-726a-91d3-41d6d911d4c4'; - public const CATALOG_DATASET_KEY = 'drg-care-pathways-verification-package-v43.1-20260721'; + public const CATALOG_SOURCE_CSV_SHA256 = '2e3ac28238cdb8d7e1002117de6ad824'.'d71882dae54df77fe4abd214b268a6ae'; + + public const CATALOG_VERIFICATION_WORKBOOK_SHA256 = '42cadf84dce297c5a839784148ebd2c5'.'375320350394c0d143411008ed5bd171'; + + public const CATALOG_DECLARED_BASELINE_SHA256 = '6819c1e111985da1fc62f38cdd85dd2'.'a34b69308f4cf9be9f8941fbce62bf8fd'; + public const CATALOG_GROUPER_VERSION = '43.1'; public const CATALOG_PATHWAY_COUNT = 250; + public const CATALOG_PATHWAY_DRG_ASSOCIATION_COUNT = 802; + + public const CATALOG_UNIQUE_DRG_CODE_COUNT = 770; + + public const CATALOG_CLAIM_COUNT = 10123; + + public const CATALOG_SOURCE_COUNT = 811; + + public const CATALOG_CHANGE_COUNT = 324; + + public const CATALOG_EVIDENCE_VERIFIED_COUNT = 96; + + public const CATALOG_EVIDENCE_LIMITATIONS_COUNT = 154; + + public const CATALOG_SIGNOFF_QUEUE_COUNT = 96; + + public const CATALOG_SPECIALIST_REVIEW_COUNT = 148; + + public const CATALOG_REDESIGN_COUNT = 6; + + public const CATALOG_VOLUME_CONTROL_TOTAL = 32967000; + + public const CATALOG_COVERAGE_CONTROL_PERCENT = '99.000'; + public const RELEASE_POLICY_VERSION = 'nightingale-investor-demo-disclosure-v1'; public const SOURCE_SYSTEM_KEY = 'nightingale-synthetic-demo'; diff --git a/app/Nightingale/Demo/NightingaleDemoCohortProvisioner.php b/app/Nightingale/Demo/NightingaleDemoCohortProvisioner.php index e04028d9..76b518ac 100644 --- a/app/Nightingale/Demo/NightingaleDemoCohortProvisioner.php +++ b/app/Nightingale/Demo/NightingaleDemoCohortProvisioner.php @@ -420,8 +420,16 @@ private function assertPristineReferencePrincipal(PatientPrincipal $principal): private function catalogBindings(bool $forUpdate): array { $releaseQuery = DB::table('care_pathways.catalog_releases') - ->where('catalog_release_uuid', NightingaleDemoCohort::CATALOG_RELEASE_UUID) - ->where('dataset_key', NightingaleDemoCohort::CATALOG_DATASET_KEY); + ->where('dataset_key', NightingaleDemoCohort::CATALOG_DATASET_KEY) + ->where('source_csv_sha256', NightingaleDemoCohort::CATALOG_SOURCE_CSV_SHA256) + ->where( + 'verification_workbook_sha256', + NightingaleDemoCohort::CATALOG_VERIFICATION_WORKBOOK_SHA256, + ) + ->where( + 'declared_baseline_sha256', + NightingaleDemoCohort::CATALOG_DECLARED_BASELINE_SHA256, + ); if ($forUpdate) { $releaseQuery->lockForUpdate(); } @@ -432,6 +440,18 @@ private function catalogBindings(bool $forUpdate): array $release = $releases->sole(); if ($release->grouper_version !== NightingaleDemoCohort::CATALOG_GROUPER_VERSION || (int) $release->pathway_count !== NightingaleDemoCohort::CATALOG_PATHWAY_COUNT + || (int) $release->pathway_drg_association_count !== NightingaleDemoCohort::CATALOG_PATHWAY_DRG_ASSOCIATION_COUNT + || (int) $release->unique_drg_code_count !== NightingaleDemoCohort::CATALOG_UNIQUE_DRG_CODE_COUNT + || (int) $release->claim_count !== NightingaleDemoCohort::CATALOG_CLAIM_COUNT + || (int) $release->source_count !== NightingaleDemoCohort::CATALOG_SOURCE_COUNT + || (int) $release->change_count !== NightingaleDemoCohort::CATALOG_CHANGE_COUNT + || (int) $release->evidence_verified_count !== NightingaleDemoCohort::CATALOG_EVIDENCE_VERIFIED_COUNT + || (int) $release->evidence_limitations_count !== NightingaleDemoCohort::CATALOG_EVIDENCE_LIMITATIONS_COUNT + || (int) $release->signoff_queue_count !== NightingaleDemoCohort::CATALOG_SIGNOFF_QUEUE_COUNT + || (int) $release->specialist_review_count !== NightingaleDemoCohort::CATALOG_SPECIALIST_REVIEW_COUNT + || (int) $release->redesign_count !== NightingaleDemoCohort::CATALOG_REDESIGN_COUNT + || (int) $release->volume_control_total !== NightingaleDemoCohort::CATALOG_VOLUME_CONTROL_TOTAL + || number_format((float) $release->coverage_control_percent, 3, '.', '') !== NightingaleDemoCohort::CATALOG_COVERAGE_CONTROL_PERCENT || $release->state !== 'inactive' || (bool) $release->clinical_signoff_complete || (int) $release->clinical_signoff_count !== 0 @@ -495,6 +515,7 @@ private function catalogBindings(bool $forUpdate): array } $members[$alias] = [ + 'catalog_release_uuid' => (string) $release->catalog_release_uuid, 'pathway_version_id' => (int) $row->pathway_version_id, 'pathway_version_uuid' => (string) $row->pathway_version_uuid, 'pathway_key' => (string) $row->pathway_key, @@ -697,7 +718,7 @@ private function principalPreferences(string $alias, array $binding): array 'clinical_use_permitted' => false, 'automatic_enrollment' => false, 'catalog_binding' => [ - 'catalog_release_uuid' => NightingaleDemoCohort::CATALOG_RELEASE_UUID, + 'catalog_release_uuid' => $binding['catalog_release_uuid'], 'pathway_version_uuid' => $binding['pathway_version_uuid'], 'pathway_key' => $binding['pathway_key'], 'ms_drg' => $binding['ms_drg'], @@ -1241,6 +1262,10 @@ private function verifyWithinTransaction(array $catalog): array } $unitIds[] = (int) $encounter->unit_id; $binding = $catalog['members'][$alias]; + if ($this->canonicalJson((array) $principal->preferences) + !== $this->canonicalJson($this->principalPreferences($alias, $binding))) { + throw new RuntimeException("nightingale_demo_{$alias}_principal_not_ready"); + } $projections = PatientEncounterProjection::query() ->where('access_grant_id', $grant->getKey()) ->get(); diff --git a/docs/evidence/nightingale/investor-demo-cohort-planning-2026-07-27/README.md b/docs/evidence/nightingale/investor-demo-cohort-planning-2026-07-27/README.md index 9415aa5b..74bc0e48 100644 --- a/docs/evidence/nightingale/investor-demo-cohort-planning-2026-07-27/README.md +++ b/docs/evidence/nightingale/investor-demo-cohort-planning-2026-07-27/README.md @@ -35,6 +35,14 @@ The sole release was `inactive`, institutionally `not_reviewed`, and not clinica signed off. Every observed stage and milestone definition was `draft`. No canonical catalog row was changed. +The 2026-08-05 pre-provisioning reconciliation confirmed the production release UUID is +`019f8702-a824-7172-b1dc-ab3612d2f1e8`. That UUID is environment-generated provenance, +not the portable content identity. The exact cross-environment identity is the dataset +key plus source CSV, verification workbook, and declared-baseline SHA-256 fingerprints; +the provisioner separately rechecks grouper, all aggregate control counts, inactive +state, and zero clinical signoff before accepting the row. The production evidence values +are recorded in the paired implementation plan. + ## Exact selected scenario facts | Demo handle | Pathway key | MS-DRG | Exact codebook title | Stages | Milestones | @@ -110,5 +118,10 @@ authorization to provision. - App identity: the merged PR #111 uniqueness guard reports seven unique iOS identifiers and two unique Android identifiers in the reconciled checkout. -No production mutation, account activation, deploy, or live credential verification has -occurred at this checkpoint. +PR #118 passed all 19 required checks, merged through protected `main` as +`06e3d0b363f2ef553f857aa426f220ccff7fc8a4`, and is present in the canonically deployed +`main` release `a38044ca9f0df564b7de08cf915bdcc189a4d860`. The first production cohort preview +failed closed before any account write because the implementation had pinned a generated +fixture release UUID. No cohort principal, credential, grant, encounter, projection, +session, or token was created. A semantic catalog-identity correction is now the only +code gate before the production preview/apply/verify/suspend-and-reapply sequence. diff --git a/docs/plans/nightingale-investor-demo-patient-cohort-2026-07-27.md b/docs/plans/nightingale-investor-demo-patient-cohort-2026-07-27.md index 9422bdcf..c0f44d2b 100644 --- a/docs/plans/nightingale-investor-demo-patient-cohort-2026-07-27.md +++ b/docs/plans/nightingale-investor-demo-patient-cohort-2026-07-27.md @@ -2,7 +2,8 @@ **Date:** 2026-07-27 -**Status:** implementation active; production provisioning incomplete +**Status:** implementation released; production provisioning gated on catalog-identity +correction and final operational proof **Scope:** five synthetic Nightingale demo accounts, five isolated inpatient scenarios, five exact MS-DRG bindings, safe in-place adoption of the existing reference sample, and @@ -64,8 +65,12 @@ The five selected versions are in the first group and have complete stage/milest inventories. That makes them useful reconciliation inputs, not approved patient content. The demo implementation therefore has two deliberately separate layers: -1. **Catalog binding evidence** pins the exact pathway key, version UUID, release UUID, - MS-DRG code/title, catalog fingerprints, stage count, and milestone count. +1. **Catalog binding evidence** resolves the one release with the exact dataset key, three + immutable SHA-256 evidence fingerprints, grouper version, and aggregate controls, then + records that environment's release UUID together with the exact pathway key, version + UUID, MS-DRG code/title, stage count, and milestone count. The UUID is provenance, not + a portable content identity: production and generated verification fixtures assign + different UUIDs to the same evidence-defined release. 2. **Patient-safe synthetic demo projections** contain purpose-written, allowlisted, conspicuously labeled demonstration copy. @@ -423,9 +428,11 @@ This cohort item may be checked only when: ## 11. Current implementation checkpoint and live checklist -The cohort stream has been replayed as one isolated commit onto current `main`; the -superseded #92 foundation history is not part of the branch. Current implemented -elements are: +The cohort stream was replayed as one isolated commit onto current `main`; the superseded +#92 foundation history is not part of the branch. PR #118 merged through protected +`main` as `06e3d0b363f2ef553f857aa426f220ccff7fc8a4`, and the same implementation remains +present in the subsequently deployed `main` commit +`a38044ca9f0df564b7de08cf915bdcc189a4d860`. Current implemented elements are: - exact code-owned `demo1` through `demo5` aliases and five DRG/catalog bindings; - PostgreSQL-only preview, atomic apply, read-only verify, and reversible suspend; @@ -443,28 +450,63 @@ elements are: in addition to email-shaped identifiers; and - backend, iOS, and Android tests for alias boundaries and token-route compatibility. -| Work section | Status | Current evidence | -| ---------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -| Code-owned five-member scenario and exact DRG manifest | Complete | Five exact pathway keys, codebook titles, stage counts, and milestone counts are pinned in code and tests. | -| Patient-safe projection generation and catalog-content separation | Complete | Six synthetic projections per account; persistent notice and raw catalog-prose exclusion are asserted. | -| Exact alias authentication and account-isolation boundary | Complete | Email compatibility plus exact aliases; all 20 directed cross-account substitutions return generic denial. | -| Atomic PostgreSQL preview/apply/verify/suspend implementation | Complete | Serializable/advisory-locked apply, read-only preview/verify, rotation, suspend, and re-apply pass within 208 Patient tests/3,569 assertions. | -| Runtime-secret and command safety | Complete | Hidden prompt only, no password/secret/credential CLI option, exact distinct confirmations, one-way hashing, and redacted results. | -| Existing reference sample adopted as `demo1` | Complete | Primary identity and encounter are preserved; changed lifecycle, contact data, or pre-existing linkage fails before cohort writes. | -| Canonical iOS exact-alias implementation and simulator regression | Complete | 67 unit tests, nine UI journeys, Release build, artifact boundary, and transport check pass in `hummingbird/iosPatientApp`. | -| Canonical Android exact-alias implementation and JVM regression | Complete | Debug/release unit, lint, build, artifact-boundary, and transport checks pass in `hummingbird/androidPatientApp`. | -| Canonical Android API 35 connected-emulator regression | Complete | All 16 tests pass at 1080×2400/420 dpi; tab changes reset shared content scroll to keep revision and urgent context visible. | -| PR #111 reconciliation without duplicate salvaged artifacts | Complete | Cohort is rebased directly on the protected-main #111 merge; all live/foundation config keys remain and only the default-off demo gate is added. | -| Successor PR, exact-SHA CI, protected-main merge, and canonical deployment | Pending | No production deployment or provisioning claim is made yet. | -| Production preview, atomic apply to unit 85, five live logins, read-after-write, and kill test | Pending | Production remains unchanged beyond the pre-existing inactive reference sample. | +| Work section | Status | Current evidence | +| ---------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| Code-owned five-member scenario and exact DRG manifest | Complete | Five exact pathway keys, codebook titles, stage counts, and milestone counts are pinned in code and tests. | +| Patient-safe projection generation and catalog-content separation | Complete | Six synthetic projections per account; persistent notice and raw catalog-prose exclusion are asserted. | +| Exact alias authentication and account-isolation boundary | Complete | Email compatibility plus exact aliases; all 20 directed cross-account substitutions return generic denial. | +| Atomic PostgreSQL preview/apply/verify/suspend implementation | Complete | Serializable/advisory-locked apply, read-only preview/verify, rotation, suspend, and re-apply pass within 208 Patient tests/3,569 assertions. | +| Runtime-secret and command safety | Complete | Hidden prompt only, no password/secret/credential CLI option, exact distinct confirmations, one-way hashing, and redacted results. | +| Existing reference sample adopted as `demo1` | Complete | Primary identity and encounter are preserved; changed lifecycle, contact data, or pre-existing linkage fails before cohort writes. | +| Canonical iOS exact-alias implementation and simulator regression | Complete | 67 unit tests, nine UI journeys, Release build, artifact boundary, and transport check pass in `hummingbird/iosPatientApp`. | +| Canonical Android exact-alias implementation and JVM regression | Complete | Debug/release unit, lint, build, artifact-boundary, and transport checks pass in `hummingbird/androidPatientApp`. | +| Canonical Android API 35 connected-emulator regression | Complete | All 16 tests pass at 1080×2400/420 dpi; tab changes reset shared content scroll to keep revision and urgent context visible. | +| PR #111 reconciliation without duplicate salvaged artifacts | Complete | Cohort is rebased directly on the protected-main #111 merge; all live/foundation config keys remain and only the default-off demo gate is added. | +| Successor PR, exact-SHA CI, protected-main merge, and canonical deployment | Complete | PR #118 passed all 19 required jobs, merged as `06e3d0b3`, and remains present in canonically deployed `main` `a38044ca`. | +| Portable, fail-closed catalog release identity | Active | First production preview made no writes and exposed a generated-fixture UUID pin; correction now requires dataset key, three hashes, and controls. | +| Production preview, atomic apply to unit 85, five live logins, read-after-write, and kill test | Pending | Production remains unchanged beyond the pre-existing inactive reference sample; the failed preview was read-only. | Remaining gates before production apply are: -1. obtain exact-SHA CI on the successor PR; -2. merge the successor PR through protected `main`; -3. deploy through `./deploy.sh --check` then `./deploy.sh`; -4. run production preview against the explicit synthetic-demo unit 85; +1. prove the semantic catalog-identity correction locally and in exact-SHA CI; +2. merge the correction through protected `main`; +3. deploy it through `./deploy.sh --check` and the confirmed canonical apply; +4. rerun production preview against the explicit synthetic-demo unit 85 and require the + observed production release UUID plus zero cohort counts; 5. perform one confirmed production apply using the non-echoing runtime prompt; -6. execute read-after-write verification and all five live logins; -7. retain only redacted cardinality, isolation, and rollback evidence; and -8. keep the suspend command immediately available as the cohort kill switch. +6. execute read-after-write verification and all five live logins without retaining + tokens or credential material; +7. rehearse suspension, prove all five credentials and grants are disabled, re-apply the + same runtime-only credential, and repeat final verification and live-login checks; +8. retain only redacted cardinality, isolation, lineage, and rollback evidence; and +9. keep the suspend command immediately available as the cohort kill switch. + +### 11.1 Production catalog-identity reconciliation (2026-08-05) + +The first deployed `preview` correctly failed with +`nightingale_demo_catalog_release_not_exact` before opening a cohort write transaction. +Read-only inspection then established that production holds the same governed dataset and +evidence as the implementation fixture but with an environment-generated release UUID: + +- production release UUID: `019f8702-a824-7172-b1dc-ab3612d2f1e8`; +- dataset key: `drg-care-pathways-verification-package-v43.1-20260721`; +- source CSV SHA-256: + `2e3ac28238cdb8d7e1002117de6ad824d71882dae54df77fe4abd214b268a6ae`; +- verification workbook SHA-256: + `42cadf84dce297c5a839784148ebd2c5375320350394c0d143411008ed5bd171`; +- declared baseline SHA-256: + `6819c1e111985da1fc62f38cdd85dd2a34b69308f4cf9be9f8941fbce62bf8fd`; +- grouper `43.1`, 250 pathways, 802 pathway/DRG associations, 770 unique DRGs, + 10,123 claims, 811 sources, and 324 changes; +- evidence partition 96 verified plus 154 with limitations; +- disposition partition 96 signoff queue plus 148 specialist review plus 6 redesign; +- volume control 32,967,000 and coverage control 99.000%; and +- inactive state, zero clinical signoffs, no activation timestamp, and no withdrawal + timestamp. + +The correction deliberately does not pin the production UUID. It requires exactly one +row matching the dataset key and all three evidence hashes, independently validates every +aggregate and governance control above, and then propagates the observed UUID into each +account's immutable binding digest and product-owned preferences. Any fingerprint, +control, state, binding, stage count, or milestone count mismatch still fails before the +first cohort write. This reconciliation does not approve or activate the catalog. diff --git a/tests/Feature/Patient/NightingaleDemoCohortProvisionerTest.php b/tests/Feature/Patient/NightingaleDemoCohortProvisionerTest.php index 5dc2ef92..43d07035 100644 --- a/tests/Feature/Patient/NightingaleDemoCohortProvisionerTest.php +++ b/tests/Feature/Patient/NightingaleDemoCohortProvisionerTest.php @@ -31,6 +31,8 @@ class NightingaleDemoCohortProvisionerTest extends TestCase private PatientPrincipal $referencePrincipal; + private string $catalogReleaseUuid; + protected function setUp(): void { parent::setUp(); @@ -47,7 +49,7 @@ protected function setUp(): void $this->seedExactCatalog(); } - public function test_preview_reconciles_exact_catalog_and_makes_no_cohort_write(): void + public function test_preview_reconciles_exact_semantic_catalog_identity_and_returns_its_environment_uuid_without_writes(): void { $before = $this->cohortCounts(); $result = $this->provisioner()->preview($this->unit->getKey()); @@ -55,7 +57,7 @@ public function test_preview_reconciles_exact_catalog_and_makes_no_cohort_write( $this->assertFalse($result['committed']); $this->assertSame('preview', $result['action']); $this->assertSame(NightingaleDemoCohort::loginAliases(), $result['login_handles']); - $this->assertSame(NightingaleDemoCohort::CATALOG_RELEASE_UUID, $result['catalog_release_uuid']); + $this->assertSame($this->catalogReleaseUuid, $result['catalog_release_uuid']); $this->assertSame('inactive', $result['catalog_state']); $this->assertFalse($result['catalog_clinical_signoff_complete']); $this->assertFalse($result['credential_material_emitted']); @@ -126,6 +128,10 @@ public function test_apply_atomically_creates_five_isolated_accounts_and_six_pro foreach (NightingaleDemoCohort::MEMBERS as $alias => $scenario) { $principal = $this->principal($alias); $this->assertTrue(Hash::check($password, (string) $principal->password)); + $this->assertSame( + $this->catalogReleaseUuid, + $principal->preferences['provisioning']['catalog_binding']['catalog_release_uuid'], + ); $this->assertSame($scenario['pathway_key'], $principal->preferences['provisioning']['catalog_binding']['pathway_key']); $this->assertSame($scenario['ms_drg'], $principal->preferences['provisioning']['catalog_binding']['ms_drg']); $this->assertFalse($principal->preferences['provisioning']['clinical_use_permitted']); @@ -405,6 +411,57 @@ public function test_catalog_drift_fails_before_any_cohort_write(): void } } + public function test_catalog_release_state_drift_fails_before_any_cohort_write(): void + { + DB::table('care_pathways.catalog_releases') + ->where('catalog_release_uuid', $this->catalogReleaseUuid) + ->update(['state' => 'under_review']); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('nightingale_demo_catalog_release_state_changed'); + try { + $this->provisioner()->apply( + $this->unit->getKey(), + 'Synthetic-Catalog-State-Guard-Test-83!', + ); + } finally { + $this->assertSame([ + 'principals' => 0, + 'identity_links' => 0, + 'grants' => 0, + 'encounters' => 0, + 'projections' => 0, + 'sessions' => 0, + 'tokens' => 0, + ], $this->cohortCounts()); + } + } + + public function test_catalog_evidence_fingerprint_mismatch_fails_before_any_cohort_write(): void + { + DB::statement('TRUNCATE TABLE care_pathways.definitions, care_pathways.catalog_releases CASCADE'); + $this->seedExactCatalog(hash('sha256', 'intentionally-wrong-source-fingerprint')); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('nightingale_demo_catalog_release_not_exact'); + try { + $this->provisioner()->apply( + $this->unit->getKey(), + 'Synthetic-Catalog-Fingerprint-Guard-61!', + ); + } finally { + $this->assertSame([ + 'principals' => 0, + 'identity_links' => 0, + 'grants' => 0, + 'encounters' => 0, + 'projections' => 0, + 'sessions' => 0, + 'tokens' => 0, + ], $this->cohortCounts()); + } + } + public function test_foreign_owned_collision_rolls_back_all_five_members(): void { Encounter::query()->create([ @@ -665,31 +722,32 @@ private function cohortCounts(): array ]; } - private function seedExactCatalog(): void + private function seedExactCatalog(?string $sourceCsvSha256 = null): void { + $this->catalogReleaseUuid = (string) Str::uuid7(); $releaseId = DB::table('care_pathways.catalog_releases')->insertGetId([ - 'catalog_release_uuid' => NightingaleDemoCohort::CATALOG_RELEASE_UUID, + 'catalog_release_uuid' => $this->catalogReleaseUuid, 'dataset_key' => NightingaleDemoCohort::CATALOG_DATASET_KEY, - 'source_csv_sha256' => str_repeat('a', 64), - 'verification_workbook_sha256' => str_repeat('b', 64), - 'declared_baseline_sha256' => str_repeat('c', 64), + 'source_csv_sha256' => $sourceCsvSha256 ?? NightingaleDemoCohort::CATALOG_SOURCE_CSV_SHA256, + 'verification_workbook_sha256' => NightingaleDemoCohort::CATALOG_VERIFICATION_WORKBOOK_SHA256, + 'declared_baseline_sha256' => NightingaleDemoCohort::CATALOG_DECLARED_BASELINE_SHA256, 'grouper_version' => NightingaleDemoCohort::CATALOG_GROUPER_VERSION, 'grouper_effective_start' => '2026-04-01', 'grouper_effective_end' => '2026-09-30', 'pathway_count' => NightingaleDemoCohort::CATALOG_PATHWAY_COUNT, - 'pathway_drg_association_count' => 802, - 'unique_drg_code_count' => 770, - 'claim_count' => 10123, - 'source_count' => 811, - 'change_count' => 324, - 'evidence_verified_count' => 96, - 'evidence_limitations_count' => 154, - 'signoff_queue_count' => 96, - 'specialist_review_count' => 148, - 'redesign_count' => 6, + 'pathway_drg_association_count' => NightingaleDemoCohort::CATALOG_PATHWAY_DRG_ASSOCIATION_COUNT, + 'unique_drg_code_count' => NightingaleDemoCohort::CATALOG_UNIQUE_DRG_CODE_COUNT, + 'claim_count' => NightingaleDemoCohort::CATALOG_CLAIM_COUNT, + 'source_count' => NightingaleDemoCohort::CATALOG_SOURCE_COUNT, + 'change_count' => NightingaleDemoCohort::CATALOG_CHANGE_COUNT, + 'evidence_verified_count' => NightingaleDemoCohort::CATALOG_EVIDENCE_VERIFIED_COUNT, + 'evidence_limitations_count' => NightingaleDemoCohort::CATALOG_EVIDENCE_LIMITATIONS_COUNT, + 'signoff_queue_count' => NightingaleDemoCohort::CATALOG_SIGNOFF_QUEUE_COUNT, + 'specialist_review_count' => NightingaleDemoCohort::CATALOG_SPECIALIST_REVIEW_COUNT, + 'redesign_count' => NightingaleDemoCohort::CATALOG_REDESIGN_COUNT, 'clinical_signoff_count' => 0, - 'volume_control_total' => 1000, - 'coverage_control_percent' => 100, + 'volume_control_total' => NightingaleDemoCohort::CATALOG_VOLUME_CONTROL_TOTAL, + 'coverage_control_percent' => NightingaleDemoCohort::CATALOG_COVERAGE_CONTROL_PERCENT, 'state' => 'inactive', 'clinical_signoff_complete' => false, 'source_controls' => '{}',