Skip to content

Commit ee035e9

Browse files
committed
perf(verify): load only the changed records for score --changed
`app.verify score --changed` parsed all ~190k seed files and then filtered down to the diff. `_load`/`load_all` now take an optional set of changed paths; brand and soc stay fully loaded because foreign-key checks and SoC release dates depend on them. `pr` keeps the full load until its baseline moves to status.json. Refs GetTechAPI/TechAPI#350
1 parent 1d5ca01 commit ee035e9

4 files changed

Lines changed: 66 additions & 10 deletions

File tree

‎app/validate.py‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,25 @@
126126
DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
127127

128128

129-
def _load(subdir: str) -> list[tuple[str, dict[str, Any]]]:
129+
def _load(subdir: str, only: set[str] | None = None) -> list[tuple[str, dict[str, Any]]]:
130+
"""Load every ``*.json`` under ``subdir``; with ``only``, just those paths.
131+
132+
``only`` holds DATA_DIR-relative posix paths (as ``git diff`` prints them
133+
minus the ``data/`` prefix), so a scoped run never walks the whole tree.
134+
"""
130135
path = DATA_DIR / subdir
131136
if not path.exists():
132137
return []
138+
if only is not None:
139+
files = sorted(
140+
f for rel in only
141+
if rel.startswith(f"{subdir}/") and (f := DATA_DIR / rel).is_file()
142+
)
143+
else:
144+
files = sorted(path.rglob("*.json")) # recurse into brand subfolders
133145
return [
134146
(str(f.relative_to(DATA_DIR)), json.loads(f.read_text(encoding="utf-8-sig")))
135-
for f in sorted(path.rglob("*.json")) # recurse into brand subfolders
147+
for f in files
136148
]
137149

138150

‎app/verify/cli.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,13 @@ def _iter_selected(
7878

7979

8080
def cmd_score(args: argparse.Namespace) -> int:
81-
records = load_all()
81+
changed = _changed_data_slugs(args.base) if args.changed else None
82+
records = load_all(only=changed)
8283
_, _, soc_release = foreign_key_sets(records)
8384
now_year = offline.now_year_today()
8485
ts = _now_iso()
8586

8687
categories = tuple(args.category) if args.category else CATEGORIES
87-
changed = _changed_data_slugs(args.base) if args.changed else None
8888

8989
# The scores cache is a full-dataset snapshot; only rewrite it on a full run.
9090
full_scope = args.category is None and args.max is None and not args.changed
@@ -622,7 +622,7 @@ def cmd_pr(args: argparse.Namespace) -> int:
622622
cross-reference) + Tier 3 (promotion decision, DRY-RUN — never writes). Network
623623
tiers run only over the records changed vs the PR merge base, capped by --max.
624624
"""
625-
records = load_all()
625+
records = load_all() # full: the baseline below scores everything (see status.json)
626626
_, _, soc_release = foreign_key_sets(records)
627627
now_year = offline.now_year_today()
628628

‎app/verify/common.py‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,28 @@ def __repr__(self) -> str: # pragma: no cover - debug aid
7070
return f"Record({self.category}, {self.slug!r})"
7171

7272

73-
def load_category(category: str) -> list[Record]:
73+
# Categories other records point at (brand/soc FKs, SoC release dates). A scoped
74+
# load keeps these whole (~2.3k files) so foreign-key checks stay exact.
75+
FK_CATEGORIES = ("brand", "soc")
76+
77+
78+
def load_category(category: str, only: set[str] | None = None) -> list[Record]:
7479
"""Load one category's records as :class:`Record` objects."""
75-
return [Record(category, path, data) for path, data in _load(category)]
80+
return [Record(category, path, data) for path, data in _load(category, only)]
7681

7782

78-
def load_all(categories: Iterable[str] = CATEGORIES) -> dict[str, list[Record]]:
79-
"""Load every category into ``{category: [Record, ...]}``."""
80-
return {cat: load_category(cat) for cat in categories}
83+
def load_all(
84+
categories: Iterable[str] = CATEGORIES, only: set[str] | None = None
85+
) -> dict[str, list[Record]]:
86+
"""Load every category into ``{category: [Record, ...]}``.
87+
88+
``only`` (data-relative paths) restricts non-FK categories to those files so
89+
a PR touching a handful of records does not parse all ~190k.
90+
"""
91+
return {
92+
cat: load_category(cat, None if cat in FK_CATEGORIES else only)
93+
for cat in categories
94+
}
8195

8296

8397
def foreign_key_sets(

‎tests/verify/test_scoped_load.py‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Scoped loading reads only the requested files (plus whole FK categories)."""
2+
3+
import json
4+
5+
from app import validate
6+
from app.verify import common
7+
8+
9+
def _write(root, rel, slug):
10+
f = root / rel
11+
f.parent.mkdir(parents=True, exist_ok=True)
12+
f.write_text(json.dumps({"slug": slug}), encoding="utf-8")
13+
14+
15+
def test_only_restricts_non_fk_categories(tmp_path, monkeypatch):
16+
monkeypatch.setattr(validate, "DATA_DIR", tmp_path)
17+
monkeypatch.setattr(common, "_load", validate._load)
18+
_write(tmp_path, "cpu/a/1.json", "a1")
19+
_write(tmp_path, "cpu/a/2.json", "a2")
20+
_write(tmp_path, "soc/x/1.json", "s1")
21+
_write(tmp_path, "soc/x/2.json", "s2")
22+
23+
full = common.load_all(("cpu", "soc"))
24+
assert [len(full[c]) for c in ("cpu", "soc")] == [2, 2]
25+
26+
scoped = common.load_all(("cpu", "soc"), only={"cpu/a/2.json", "cpu/missing.json"})
27+
assert [r.slug for r in scoped["cpu"]] == ["a2"]
28+
assert len(scoped["soc"]) == 2 # FK category stays whole
29+
30+
assert common.load_all(("cpu",), only=set())["cpu"] == []

0 commit comments

Comments
 (0)