"""Generate the Scribe and Interpreter term artifacts from shared source data.""" from __future__ import annotations import argparse import csv import io import json from pathlib import Path ROOT = Path(__file__).resolve().parents[1] SOURCE = ROOT / "shared" / "carepath_shared" / "terms" / "medical_terms.json" SCRIBE_TARGET = ROOT / "data" / "medical_lexicon.json" INTERPRETER_TARGET = ROOT / "interpreter" / "app" / "glossary" / "data" / "seed_glossary.csv" def _terms() -> list[dict[str, object]]: payload = json.loads(SOURCE.read_text(encoding="utf-8")) terms = payload.get("terms") if not isinstance(terms, list): raise ValueError(f"{SOURCE} must contain a terms list") return terms def _target(term: dict[str, object], name: str) -> dict[str, object] | None: targets = term.get("targets") if not isinstance(targets, dict): raise ValueError(f"{term.get('term_en')!r} has no target mapping") target = targets.get(name) if target is None: return None if not isinstance(target, dict): raise ValueError(f"{term.get('term_en')!r} has an invalid {name} mapping") return target def _ordered_terms(terms: list[dict[str, object]], name: str) -> list[tuple[dict[str, object], dict[str, object]]]: selected = [(term, target) for term in terms if (target := _target(term, name)) is not None] orders = [target.get("order") for _, target in selected] if sorted(orders) != list(range(len(selected))): raise ValueError(f"{name} target orders must be contiguous and unique") return sorted(selected, key=lambda item: int(item[1]["order"])) def _scribe_artifact(terms: list[dict[str, object]]) -> str: lines = ["{", ' "terms": ['] rows = [] for term, target in _ordered_terms(terms, "scribe"): aliases = term.get("aliases") if ( not isinstance(term.get("term_en"), str) or not isinstance(target.get("category"), str) or not isinstance(target.get("term_vi"), str) or not isinstance(aliases, list) or not all(isinstance(alias, str) for alias in aliases) ): raise ValueError(f"{term.get('term_en')!r} has invalid Scribe fields") rows.append((term["term_en"], target["category"], target["term_vi"], aliases)) for index, (term_en, category, term_vi, aliases) in enumerate(rows): comma = "," if index + 1 < len(rows) else "" lines.extend( ( " {", f' "term": {json.dumps(term_en, ensure_ascii=False)},', f' "category": {json.dumps(category, ensure_ascii=False)},', f' "vietnamese": {json.dumps(term_vi, ensure_ascii=False)},', f' "aliases": {json.dumps(aliases, ensure_ascii=False)}', f" }}{comma}", ) ) return "\n".join([*lines, " ]", "}", "", ""]) def _interpreter_artifact(terms: list[dict[str, object]]) -> str: output = io.StringIO(newline="") writer = csv.DictWriter( output, fieldnames=["term_vi", "term_en", "kind", "lasa_group"], lineterminator="\n", ) writer.writeheader() for term, _target_data in _ordered_terms(terms, "interpreter"): flags = term.get("risk_flags") if ( not all(isinstance(term.get(field), str) for field in ("term_vi", "term_en", "kind")) or not isinstance(flags, dict) or not isinstance(flags.get("lasa_group", ""), str) ): raise ValueError(f"{term.get('term_en')!r} has invalid risk flags") writer.writerow( { "term_vi": term["term_vi"], "term_en": term["term_en"], "kind": term["kind"], "lasa_group": flags.get("lasa_group", ""), } ) return output.getvalue() def artifacts() -> dict[Path, str]: terms = _terms() return {SCRIBE_TARGET: _scribe_artifact(terms), INTERPRETER_TARGET: _interpreter_artifact(terms)} def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--check", action="store_true", help="fail if generated artifacts differ") args = parser.parse_args() generated = artifacts() changed = [path for path, content in generated.items() if path.read_text(encoding="utf-8") != content] if args.check: if changed: raise SystemExit("Generated term artifacts differ: " + ", ".join(map(str, changed))) print("Generated term artifacts are current.") return for path, content in generated.items(): path.write_text(content, encoding="utf-8") print(f"Wrote {path.relative_to(ROOT)}") if __name__ == "__main__": main()