Skills › Scientific Agent Skills › treatment-plans › generate_template.py
generate_template.py
133 lines · skills/treatment-plans/scripts/generate_template.py at 5f2bc03
1#!/usr/bin/env python3
2"""Generate a fail-closed local JSON documentation package."""
4from __future__ import annotations
6import argparse
7import sys
8from pathlib import Path
10from _common import (
11 IDENTIFIER_RE,
12 TEMPLATE_FILES,
13 ValidationError,
14 atomic_write_json,
15 copy_template_document,
16 error_report,
17 print_report,
18 report_payload,
19 safe_new_directory,
20)
23def generate_package(
24 output_directory: str,
25 *,
26 subject_ref: str,
27 classification: str,
28 acknowledge_real_patient_local_processing: bool = False,
29) -> dict:
30 """Copy all templates without generating any clinical content."""
32 if not IDENTIFIER_RE.fullmatch(subject_ref):
33 raise ValidationError("SUBJECT_REFERENCE_FORMAT")
34 allowed = {
35 "synthetic",
36 "deidentified_qualified_review",
37 "real_patient_minimum_necessary",
38 }
39 if classification not in allowed:
40 raise ValidationError("DATA_CLASSIFICATION_INVALID")
41 if (
42 classification == "real_patient_minimum_necessary"
43 and not acknowledge_real_patient_local_processing
44 ):
45 raise ValidationError("REAL_PATIENT_LOCAL_ACK_REQUIRED")
47 assets = Path(__file__).resolve().parents[1] / "assets"
48 prepared: list[tuple[str, dict]] = []
49 for _, (template_name, output_name) in TEMPLATE_FILES.items():
50 document = copy_template_document(
51 assets / template_name,
52 subject_ref=subject_ref,
53 classification=classification,
54 )
55 prepared.append((output_name, document))
57 destination = safe_new_directory(output_directory)
58 for output_name, document in prepared:
59 atomic_write_json(destination / output_name, document)
61 return report_payload(
62 "generate_template",
63 [],
64 counts={"documents_generated": len(prepared)},
65 extra={
66 "generated_filenames": sorted(name for name, _ in prepared),
67 "release_status": "blocked",
68 "clinical_content_generated": False,
69 "external_processing_authorized": False,
70 },
71 )
74def build_parser() -> argparse.ArgumentParser:
75 parser = argparse.ArgumentParser(
76 description=(
77 "Create six local, generic JSON treatment-plan documentation "
78 "templates. No clinical content is generated."
79 )
80 )
81 parser.add_argument(
82 "--output-dir",
83 required=True,
84 help="New local directory; existing paths are rejected",
85 )
86 parser.add_argument(
87 "--subject-ref",
88 default="SYNTHETIC-CASE-001",
89 help=(
90 "Local pseudonymous identifier; never use a name, MRN, or other "
91 "direct identifier"
92 ),
93 )
94 parser.add_argument(
95 "--classification",
96 choices=[
97 "synthetic",
98 "deidentified_qualified_review",
99 "real_patient_minimum_necessary",
100 ],
101 default="synthetic",
102 )
103 parser.add_argument(
104 "--acknowledge-real-patient-local-processing",
105 action="store_true",
106 help=(
107 "Required only for real_patient_minimum_necessary; confirms that "
108 "the user will apply the documented local-only data gate"
109 ),
110 )
111 return parser
114def main() -> int:
115 args = build_parser().parse_args()
116 try:
117 report = generate_package(
118 args.output_dir,
119 subject_ref=args.subject_ref,
120 classification=args.classification,
121 acknowledge_real_patient_local_processing=(
122 args.acknowledge_real_patient_local_processing
123 ),
124 )
125 except ValidationError as exc:
126 print_report(error_report("generate_template", exc))
127 return 1
128 print_report(report)
129 return 0
132if __name__ == "__main__":
133 sys.exit(main())