-
Notifications
You must be signed in to change notification settings - Fork 0
Add blueprint workflow CLI commands #385
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
Comment on lines
+2164
to
+2176
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid partial materialization on a later release-readiness failure. Because records are persisted inside the same loop that can still raise on a later blueprint, this command can exit non-zero after already saving a partial dataset. Preflight the selected blueprints first, then materialize once the whole batch is known to be eligible. 💡 One way to make the command fail before any writes materializer = BlueprintMaterializer()
+ validation_reports = {
+ blueprint.blueprint_id: store.get_blueprint_validation_report(blueprint.blueprint_id)
+ for blueprint in blueprints
+ }
+
+ if require_release_ready:
+ for blueprint in blueprints:
+ report = validation_reports[blueprint.blueprint_id]
+ if report is None or not report.research_release_ready:
+ raise click.ClickException(
+ "Blueprint must be research release ready before materialization."
+ )
+
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,
+ validation_report=validation_reports[blueprint.blueprint_id],
store=store,
require_release_ready=require_release_ready,
)🤖 Prompt for AI Agents |
||
| 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: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unbounded scans still eagerly load every blueprint into memory.
DatasetStore.list_blueprints()returns a full Python list, solimit=Noneturns these CLI paths into all-at-once loads. On large datasets,export-blueprints,validate-blueprints, andmaterialize-blueprintscan become OOM-prone or stall before doing useful work. If the intent is truly unbounded processing, this needs a paged/streaming store API rather than removing the cap at the call site.🤖 Prompt for AI Agents