Skip to content

Commit 37cb515

Browse files
committed
feat(verify): report the verified rate per product, not just per SKU
78% of the smartphone set is regional and RAM variants of the same phone: 57,216 records stand for 20,594 actual models, with slugs like "…k61-2020-latam-q630ha-…-costa-rica-4gb-128gb". No source documents an individual SKU, so counting verification per record measures the shape of the import rather than how well the products are sourced. `status` now also aggregates by model — variants collapse onto their base model, a standalone record is its own model, and a model counts as verified once any of its SKUs is, since the variants of one phone share a provenance. The per-record figures are unchanged and still reported; this is a second view of the same verifications, not a replacement. Refs #1
1 parent 1266518 commit 37cb515

3 files changed

Lines changed: 63 additions & 0 deletions

File tree

app/verify/cli.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,14 +254,24 @@ def cmd_status(args: argparse.Namespace) -> int:
254254

255255
by_category: dict[str, dict[str, Any]] = {}
256256
tot = ver = g = y = r = 0
257+
all_models: set[tuple[str, ...]] = set()
258+
all_verified_models: set[tuple[str, ...]] = set()
257259
for cat in CATEGORIES:
258260
ct = cv = cg = cy = cr = 0
261+
# A model counts as verified once any of its SKUs is: the variants of one
262+
# phone share a provenance, so the source that verifies one describes the
263+
# product the others are configurations of.
264+
models: set[tuple[str, ...]] = set()
265+
verified_models: set[tuple[str, ...]] = set()
259266
for rec in records[cat]:
260267
if not rec.slug:
261268
continue
262269
ct += 1
270+
key = rec.model_key
271+
models.add(key)
263272
if rec.verified:
264273
cv += 1
274+
verified_models.add(key)
265275
band = offline.score_record(rec, now_year, soc_release).band
266276
cg += band == "green"
267277
cy += band == "yellow"
@@ -270,6 +280,11 @@ def cmd_status(args: argparse.Namespace) -> int:
270280
"total": ct,
271281
"verified": cv,
272282
"verified_pct": round(100 * cv / ct, 2) if ct else 0.0,
283+
"models": len(models),
284+
"verified_models": len(verified_models),
285+
"verified_models_pct": (
286+
round(100 * len(verified_models) / len(models), 2) if models else 0.0
287+
),
273288
"green": cg,
274289
"yellow": cy,
275290
"red": cr,
@@ -281,6 +296,8 @@ def cmd_status(args: argparse.Namespace) -> int:
281296
g += cg
282297
y += cy
283298
r += cr
299+
all_models |= models
300+
all_verified_models |= verified_models
284301

285302
status = {
286303
"generated_at": _now_iso(),
@@ -289,6 +306,13 @@ def cmd_status(args: argparse.Namespace) -> int:
289306
"records": tot,
290307
"verified": ver,
291308
"verified_pct": round(100 * ver / tot, 2) if tot else 0.0,
309+
# Same verifications counted per product rather than per SKU.
310+
"models": len(all_models),
311+
"verified_models": len(all_verified_models),
312+
"verified_models_pct": (
313+
round(100 * len(all_verified_models) / len(all_models), 2)
314+
if all_models else 0.0
315+
),
292316
"green": g,
293317
"yellow": y,
294318
"red": r,

app/verify/common.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,22 @@ def slug(self) -> str | None:
5454
def verified(self) -> bool:
5555
return self.data.get("verified") is True
5656

57+
@property
58+
def model_key(self) -> tuple[str, ...]:
59+
"""Identifies the product, not the SKU.
60+
61+
78% of the smartphone set is regional/RAM variants of the same phone —
62+
one model can carry dozens of records ("…k61…latam-q630ha…costa-rica-
63+
4gb-128gb"). Counting per record makes the dataset look far less
64+
verified than the products in it are, because no source documents an
65+
individual SKU. Variants collapse onto their base model; a standalone
66+
record is its own model.
67+
"""
68+
base = self.data.get("base_model_slug")
69+
if isinstance(base, str) and base:
70+
return (self.category, str(self.data.get("brand") or ""), base)
71+
return (self.category, self.slug or self.path)
72+
5773
def content_hash(self) -> str:
5874
"""Stable hash of the record body — invalidates stale ledger decisions on edit."""
5975
blob = json.dumps(self.data, sort_keys=True, ensure_ascii=False)

tests/verify/test_offline.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,26 @@ def test_future_release_red():
6363
"source_urls": ["https://en.wikipedia.org/wiki/x"],
6464
}
6565
assert _score("cpu", rec).band == "red"
66+
67+
68+
def test_model_key_collapses_variants_of_one_phone():
69+
"""Regional/RAM SKUs of one phone are one product, not many."""
70+
from app.verify.common import Record
71+
72+
def variant(slug, base):
73+
return Record("smartphone", f"smartphone/lg/2020/{base}/{slug}.json",
74+
{"slug": slug, "brand": "lg", "base_model_slug": base})
75+
76+
a = variant("lg-k61-costa-rica-4gb-128gb", "k61-2020")
77+
b = variant("lg-k61-colombia-3gb-64gb", "k61-2020")
78+
other = variant("lg-k51-usa-3gb-32gb", "k51-2020")
79+
assert a.model_key == b.model_key
80+
assert a.model_key != other.model_key
81+
82+
83+
def test_model_key_of_a_standalone_record_is_itself():
84+
from app.verify.common import Record
85+
86+
rec = Record("cpu", "cpu/intel/2023/desktop/core-i9-14900k.json",
87+
{"slug": "core-i9-14900k"})
88+
assert rec.model_key == ("cpu", "core-i9-14900k")

0 commit comments

Comments
 (0)