diff --git a/src/casecrawler/cli.py b/src/casecrawler/cli.py index d7a5967..d7d326e 100644 --- a/src/casecrawler/cli.py +++ b/src/casecrawler/cli.py @@ -2076,7 +2076,7 @@ def export_blueprints( blueprints = store.list_blueprints( dataset_id=dataset_id, cohort_plan_id=cohort_plan_id, - limit=1_000_000, + limit=None, ) if dataset_id and not blueprints: raise click.ClickException(f"No blueprints found for dataset {dataset_id}.") @@ -2113,7 +2113,7 @@ def validate_blueprints( blueprints = store.list_blueprints( dataset_id=dataset_id, cohort_plan_id=cohort_plan_id, - limit=1_000_000, + limit=None, ) if dataset_id and not blueprints: raise click.ClickException(f"No blueprints found for dataset {dataset_id}.") @@ -2133,6 +2133,68 @@ def validate_blueprints( click.echo(f"Clinically plausible: {clinically_plausible}") +@cli.command("materialize-blueprints") +@click.option("--dataset-id", required=True, help="Dataset id filter") +@click.option("--cohort-plan-id", default=None, help="Cohort plan id filter") +@click.option( + "--require-release-ready", + is_flag=True, + help="Only materialize blueprints with research-release-ready validation reports.", +) +def materialize_blueprints( + dataset_id: str, + cohort_plan_id: str | None, + require_release_ready: bool, +) -> None: + """Materialize persisted clinical blueprints into synthetic record scaffolds.""" + from casecrawler.generation.blueprint_materializer import BlueprintMaterializer + from casecrawler.storage.dataset_store import DatasetStore + + store = DatasetStore() + blueprints = store.list_blueprints( + dataset_id=dataset_id, + cohort_plan_id=cohort_plan_id, + limit=None, + ) + if not blueprints: + raise click.ClickException(f"No blueprints found for dataset {dataset_id}.") + + materializer = BlueprintMaterializer() + materialized_count = 0 + for blueprint in blueprints: + validation_report = store.get_blueprint_validation_report( + blueprint.blueprint_id + ) + try: + materializer.materialize( + blueprint, + validation_report=validation_report, + store=store, + require_release_ready=require_release_ready, + ) + except ValueError as exc: + raise click.ClickException(str(exc)) from exc + materialized_count += 1 + + click.echo(f"Materialized {materialized_count} blueprint record(s)") + + +@cli.command("export-blueprint-release-summary") +@click.option("--dataset-id", required=True, help="Dataset id to summarize") +@click.option("--output", required=True, help="Output JSON path") +def export_blueprint_release_summary(dataset_id: str, output: str) -> None: + """Export blueprint dataset release readiness and audit summary JSON.""" + from casecrawler.export.blueprint_release import write_blueprint_release_summary + from casecrawler.storage.dataset_store import DatasetStore + + store = DatasetStore() + summary = write_blueprint_release_summary(store, dataset_id, output) + click.echo( + "Wrote blueprint release summary " + f"for {summary['blueprint_count']} blueprint artifact(s) to {output}" + ) + + @cli.command("verify-fhir-export") @click.argument("path", type=click.Path(exists=True, dir_okay=False)) def verify_fhir_export(path: str) -> None: diff --git a/tests/test_cli_synthetic.py b/tests/test_cli_synthetic.py index 107427d..57bdff2 100644 --- a/tests/test_cli_synthetic.py +++ b/tests/test_cli_synthetic.py @@ -11,6 +11,7 @@ CohortArchetype, CohortPlan, GenerationRole, + JudgeReport, ) from casecrawler.models.synthetic import Modality from casecrawler.storage.dataset_store import DatasetStore @@ -46,6 +47,7 @@ def _blueprint_cli_result(dataset_id: str) -> BlueprintPipelineResult: "supporting_findings": ["ECG confirms AF"], } ], + clinical_reasoning_targets=["Review renal dosing and bleeding risk."], evidence=BlueprintEvidence( supported_claims=["AF anticoagulation requires renal-dose review."], citations=[{"source": "dailymed", "claim": "renal-dose review"}], @@ -177,3 +179,65 @@ def test_validate_blueprints_command_persists_validation_reports(tmp_path, monke assert report is not None assert report.blueprint_id == "bp-1" assert report.clinically_plausible is True + + +def test_blueprint_cli_materializes_and_exports_release_summary(tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + store = DatasetStore() + result = _blueprint_cli_result("ds-blueprint") + store.save_cohort_plan(result.plan) + for blueprint in result.blueprints: + store.save_blueprint(blueprint) + store.save_judge_report( + JudgeReport( + report_id="judge-1", + dataset_id="ds-blueprint", + artifact_id=blueprint.blueprint_id, + role=GenerationRole.JUDGE, + score=0.93, + passed=True, + rubric="blueprint_plausibility", + ) + ) + runner = CliRunner() + + validated = runner.invoke( + cli, + [ + "validate-blueprints", + "--dataset-id", + "ds-blueprint", + ], + ) + materialized = runner.invoke( + cli, + [ + "materialize-blueprints", + "--dataset-id", + "ds-blueprint", + "--require-release-ready", + ], + ) + exported = runner.invoke( + cli, + [ + "export-blueprint-release-summary", + "--dataset-id", + "ds-blueprint", + "--output", + "blueprint-release-summary.json", + ], + ) + + summary = json.loads((tmp_path / "blueprint-release-summary.json").read_text()) + records = DatasetStore().list_records(dataset_id="ds-blueprint") + assert validated.exit_code == 0 + assert materialized.exit_code == 0 + assert "Materialized 1 blueprint record(s)" in materialized.output + assert exported.exit_code == 0 + assert "Wrote blueprint release summary" in exported.output + assert len(records) == 1 + assert records[0].metadata["blueprint_id"] == "bp-1" + assert summary["blueprint_count"] == 1 + assert summary["research_release_ready_count"] == 1 + assert summary["materialized_record_count"] == 1