""" Pick a set of templates to use for quality-check. Reads scripts/_template_match.csv (produced by match_templates_to_data.py), keeps templates that match >= --min-data data files, filters by engine (default d3-js), then picks --n templates (or all of them when --n=-1) with a deterministic seed and writes them (and per-template list of compatible data files) to the output plan JSON. Two common modes: # smoke: 30 random templates with at least 20 data files each python scripts/pick_smoke_templates.py \\ --engine d3-js --n 30 --min-data 20 --per-template-n 20 \\ --out scripts/_smoke_templates.json # full d3-js: every template with at least 5 data files, up to 20 each python scripts/pick_smoke_templates.py \\ --engine d3-js --n -1 --min-data 5 --per-template-n 20 \\ --out scripts/_full_d3_plan.json """ import argparse import csv import json import os import random import sys from collections import defaultdict from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) from config import data_resource_dirs as CFG_DATA_DIRS from modules.chart_engine.template.template_registry import scan_templates from modules.infographics_generator.template_utils import check_template_compatibility def parse_args(): p = argparse.ArgumentParser() p.add_argument("--match-csv", default="scripts/_template_match.csv") p.add_argument("--data", nargs="+", default=None, help="One or more directories of input JSONs " "(default: config.data_resource_dirs)") p.add_argument("--engine", default="d3-js", help="Engine to sample from") p.add_argument("--n", type=int, default=30, help="Number of templates to sample (-1 for all qualified)") p.add_argument("--min-data", type=int, default=20, help="Only sample templates that match at least this many data files") p.add_argument("--per-template-n", type=int, default=20, help="Number of data files to use per template") p.add_argument("--seed", type=int, default=42) p.add_argument("--out", default="scripts/_smoke_templates.json") return p.parse_args() def main(): args = parse_args() rng = random.Random(args.seed) rows = list(csv.DictReader(open(args.match_csv))) qualified = [r for r in rows if r["engine"] == args.engine and int(r["num_compatible_data"]) >= args.min_data] print(f"Qualified ({args.engine}, >= {args.min_data} matches): {len(qualified)}") if args.n < 0 or len(qualified) <= args.n: if args.n < 0: print(f"Selecting ALL {len(qualified)} qualified templates") else: print(f"WARNING: only {len(qualified)} qualified, sampling all of them") sample = sorted(qualified, key=lambda r: (r["chart_type"], r["chart_name"])) else: sample = rng.sample(qualified, args.n) sample.sort(key=lambda r: (r["chart_type"], r["chart_name"])) # For each chosen template, enumerate the actual data files it can use. print("Re-running compatibility check to enumerate per-template data files...") data_dirs = [Path(p) for p in (args.data or CFG_DATA_DIRS)] for d in data_dirs: if not d.is_dir(): raise SystemExit(f"--data path is not a directory: {d}") json_files = [] for d in data_dirs: files = sorted(d.glob("*.json")) json_files.extend(files) print(f" {d}: {len(files)} json files") print(f"Total: {len(json_files)} json files across {len(data_dirs)} dir(s)") datas = [] for f in json_files: with open(f) as fh: d = json.load(fh) d["name"] = str(f) rel = f"{f.parent.name}/{f.name}" datas.append((rel, d)) templates = scan_templates() matched_files = defaultdict(list) for name, data in datas: compat = check_template_compatibility(data, templates, None) for tpl_key, _ in compat: matched_files[tpl_key].append(name) out_records = [] chart_type_counter = defaultdict(int) for r in sample: tpl_key = f"{r['engine']}/{r['chart_type']}/{r['chart_name']}" compatible = matched_files.get(tpl_key, []) if len(compatible) >= args.per_template_n: picked = rng.sample(compatible, args.per_template_n) else: picked = list(compatible) out_records.append({ "engine": r["engine"], "chart_type": r["chart_type"], "chart_name": r["chart_name"], "total_compatible": len(compatible), "picked_data_files": picked, }) chart_type_counter[r["chart_type"]] += 1 os.makedirs(os.path.dirname(args.out) or ".", exist_ok=True) with open(args.out, "w") as fh: json.dump({ "data_dirs": [str(d) for d in data_dirs], "engine": args.engine, "n_templates": len(out_records), "per_template_n": args.per_template_n, "templates": out_records, }, fh, indent=2) print(f"Wrote {args.out}") print() print("=== Sampled templates by chart_type ===") for ct, n in sorted(chart_type_counter.items(), key=lambda x: -x[1]): print(f" {n:>2d} {ct}") print() print("=== Templates and how many data files each will use ===") for rec in out_records: print(f" {len(rec['picked_data_files']):>2d}/{rec['total_compatible']:>2d} " f"{rec['chart_type']:35s} {rec['chart_name']}") if __name__ == "__main__": main()