File size: 5,623 Bytes
58e6885
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
"""
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()