File size: 5,310 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
"""
Dry-run: for every template, count how many JSON files in one or more
data directories are compatible (per the same check used by
infographics_generator).

Usage:
    # default (read pools from config.data_resource_dirs)
    python scripts/match_templates_to_data.py --out scripts/_template_match.csv

    # explicit pools
    python scripts/match_templates_to_data.py \
        --data /data/liduan/resources/claude_data_v2 /data/liduan/resources/claude_new \
        --out scripts/_template_match.csv
"""
import argparse
import csv
import json
import os
import sys
from pathlib import Path
from collections import defaultdict

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 (
    analyze_templates,
    check_template_compatibility,
)


def parse_args():
    p = argparse.ArgumentParser()
    p.add_argument("--data", nargs="+", default=None,
                   help="One or more directories of input JSONs "
                        "(default: config.data_resource_dirs)")
    p.add_argument("--out", default="scripts/_template_match.csv", help="CSV report path")
    p.add_argument("--engines", nargs="+",
                   default=["d3-js", "echarts-js", "echarts_py"],
                   help="Which engines to consider")
    p.add_argument("--sample", type=int, default=0,
                   help="If >0, randomly subsample this many files (per --seed)")
    p.add_argument("--seed", type=int, default=42)
    return p.parse_args()


def main():
    args = parse_args()
    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)")

    if args.sample and args.sample < len(json_files):
        import random
        random.Random(args.seed).shuffle(json_files)
        json_files = json_files[:args.sample]
        print(f"Sub-sampled to {len(json_files)} files (seed={args.seed})")

    datas = []
    for f in json_files:
        with open(f, "r") as fh:
            d = json.load(fh)
        d["name"] = str(f)
        # Disambiguate filenames when the same basename exists in both pools:
        # use "<dir-name>/<filename>" so picker / driver can map back uniquely.
        rel = f"{f.parent.name}/{f.name}"
        datas.append((rel, d))

    templates = scan_templates()
    _, template_requirements = analyze_templates(templates)

    # Enumerate ALL templates (engine/chart_type/chart_name) keys
    all_template_keys = []
    for engine, t_dict in templates.items():
        if engine not in args.engines:
            continue
        for chart_type, c_dict in t_dict.items():
            for chart_name in c_dict:
                if "base" in chart_name:
                    continue
                all_template_keys.append((engine, chart_type, chart_name))
    print(f"Total templates (engines={args.engines}): {len(all_template_keys)}")

    # For each data, compute compatible templates ONCE (it returns the full list)
    # then we tally per template.
    counter = defaultdict(int)
    matched_files = defaultdict(list)
    for name, data in datas:
        compat = check_template_compatibility(data, templates, None)
        for tpl_key, _ordered_fields in compat:
            counter[tpl_key] += 1
            matched_files[tpl_key].append(name)

    os.makedirs(os.path.dirname(args.out) or ".", exist_ok=True)
    with open(args.out, "w", newline="") as fh:
        w = csv.writer(fh)
        w.writerow(["engine", "chart_type", "chart_name", "num_compatible_data"])
        for engine, chart_type, chart_name in all_template_keys:
            tpl_key = f"{engine}/{chart_type}/{chart_name}"
            w.writerow([engine, chart_type, chart_name, counter.get(tpl_key, 0)])
    print(f"Wrote {args.out}")

    # Distribution histogram
    buckets = [0, 1, 5, 10, 20, 30, 50, 100, 10**9]
    bucket_labels = ["0", "1-4", "5-9", "10-19", "20-29", "30-49", "50-99", "100+"]
    bucket_counts = [0] * len(bucket_labels)
    for engine, chart_type, chart_name in all_template_keys:
        n = counter.get(f"{engine}/{chart_type}/{chart_name}", 0)
        for i in range(len(bucket_labels)):
            if buckets[i] <= n < buckets[i + 1]:
                bucket_counts[i] += 1
                break

    print()
    print("Templates by number of compatible data files in",
          ", ".join(str(d) for d in data_dirs))
    for lab, c in zip(bucket_labels, bucket_counts):
        print(f"  {lab:>8s}: {c}")

    n_geq_20 = sum(1 for k in all_template_keys
                   if counter.get("/".join(k), 0) >= 20)
    n_geq_5 = sum(1 for k in all_template_keys
                  if counter.get("/".join(k), 0) >= 5)
    print()
    print(f"Templates with >= 20 compatible data: {n_geq_20}")
    print(f"Templates with >=  5 compatible data: {n_geq_5}")


if __name__ == "__main__":
    main()