Skip to content

Commit 998a09b

Browse files
committed
fix(coverage): drop footnote and decimal artifacts from upstream slugs
The auto-generated coverage report listed non-SKU garbage such as '1200-4' (from a '[4]' reference marker) and '1-25' (from a '1.25' clock cell). Strip <sup> reference nodes and leftover '[...]' text in wikitable_first_cells, and reject decimal-derived '<digits>-<digits>' slugs via a shared is_probable_model_slug filter now used by both the CPU and GPU sources.
1 parent a99d349 commit 998a09b

5 files changed

Lines changed: 65 additions & 7 deletions

File tree

app/coverage/normalize.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,22 @@
3838
_SEPARATOR_RE = re.compile(r"[^a-z0-9]+")
3939
_COLLAPSE_RE = re.compile(r"-+")
4040

41+
# Decimal-derived artifacts like "1-25" (from a "1.25" clock cell) — two pure
42+
# numeric groups joined by a hyphen. Real CPU/GPU SKUs are a single numeric run
43+
# ("6276") or carry letters ("core-i9-14900k"), never bare "<digits>-<digits>".
44+
_DECIMAL_ARTIFACT_RE = re.compile(r"^\d+-\d+$")
45+
46+
47+
def is_probable_model_slug(slug: str, *, min_len: int = 4) -> bool:
48+
"""Heuristic: does ``slug`` look like a real CPU/GPU model (vs a stray cell)?
49+
50+
Rejects too-short slugs, slugs with no digit, and decimal-derived artifacts
51+
such as ``"1-25"`` that come from non-model cells (clock speeds, footnotes).
52+
"""
53+
if len(slug) < min_len or not any(c.isdigit() for c in slug):
54+
return False
55+
return not _DECIMAL_ARTIFACT_RE.match(slug)
56+
4157

4258
def slugify(name: str, manufacturer: str | None = None) -> str:
4359
"""Normalize a vendor-style name to a kebab-case slug.

app/coverage/sources/wikipedia.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from __future__ import annotations
88

9+
import re
910
from collections.abc import Iterator
1011

1112
import httpx
@@ -14,6 +15,10 @@
1415
WIKI_REST_HTML = "https://en.wikipedia.org/api/rest_v1/page/html/{title}"
1516
USER_AGENT = "TechEngine-Coverage/0.1 (+https://github.com/GetTechAPI/TechEngine)"
1617

18+
# Leftover footnote/reference markers (e.g. "[ 4 ]", "[c]") that survive as plain
19+
# text after <sup> nodes are stripped. They otherwise leak into slugs as "-4".
20+
_FOOTNOTE_RE = re.compile(r"\[[^\]]*\]")
21+
1722

1823
def fetch_wikipedia_html(page_title: str, *, timeout: float = 30.0) -> str:
1924
"""Download the parsed HTML for a Wikipedia page."""
@@ -38,6 +43,10 @@ def wikitable_first_cells(html: str) -> Iterator[str]:
3843
cell = row.find(["th", "td"])
3944
if not cell:
4045
continue
41-
text = cell.get_text(" ", strip=True)
46+
# Drop Wikipedia reference/footnote markers (rendered as <sup>), which
47+
# otherwise leak into the model name as "[4]" and pollute the slug.
48+
for sup in cell.find_all("sup"):
49+
sup.decompose()
50+
text = _FOOTNOTE_RE.sub("", cell.get_text(" ", strip=True)).strip()
4251
if text:
4352
yield text

app/coverage/sources/wikipedia_cpu.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from collections.abc import Iterator
66

7-
from ..normalize import slugify
7+
from ..normalize import is_probable_model_slug, slugify
88
from .base import CoveragePoint
99
from .wikipedia import fetch_wikipedia_html, wikitable_first_cells
1010

@@ -48,9 +48,8 @@ def fetch(self) -> Iterator[CoveragePoint]:
4848
def _extract(html: str, manufacturer: str, page: str) -> Iterator[CoveragePoint]:
4949
for raw_name in wikitable_first_cells(html):
5050
slug = slugify(raw_name, manufacturer=manufacturer)
51-
# Filter obvious non-models. Real CPU slugs always contain a digit
52-
# and are at least a few characters long.
53-
if len(slug) < 4 or not any(c.isdigit() for c in slug):
51+
# Filter obvious non-models (too short, no digit, decimal artifacts).
52+
if not is_probable_model_slug(slug):
5453
continue
5554
yield CoveragePoint(
5655
category="cpu",

app/coverage/sources/wikipedia_gpu.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from collections.abc import Iterator
66

7-
from ..normalize import slugify
7+
from ..normalize import is_probable_model_slug, slugify
88
from .base import CoveragePoint
99
from .wikipedia import fetch_wikipedia_html, wikitable_first_cells
1010

@@ -36,7 +36,7 @@ def fetch(self) -> Iterator[CoveragePoint]:
3636
def _extract(html: str, manufacturer: str, page: str) -> Iterator[CoveragePoint]:
3737
for raw_name in wikitable_first_cells(html):
3838
slug = slugify(raw_name, manufacturer=manufacturer)
39-
if len(slug) < 4 or not any(c.isdigit() for c in slug):
39+
if not is_probable_model_slug(slug):
4040
continue
4141
yield CoveragePoint(
4242
category="gpu",

tests/unit/test_coverage_wikipedia_parser.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,37 @@ def test_wikipedia_cpu_extract_filters_obvious_non_models() -> None:
3535
assert "core-i7-14700k" in slugs
3636
# "Codename: Raptor Lake" has no digit and should be filtered.
3737
assert all("raptor" not in s for s in slugs)
38+
39+
40+
# Mirrors the AMD list pages that produced garbage slugs in the auto report:
41+
# footnote markers (<sup> + leftover "[ c ]") and a decimal clock cell.
42+
_HTML_NOISY = """
43+
<html><body>
44+
<table class="wikitable">
45+
<tr><th>Model</th></tr>
46+
<tr><td>1200<sup class="reference">[4]</sup></td></tr>
47+
<tr><td>1200 (AF) [ 16 ] [ c ]</td></tr>
48+
<tr><td>1.25</td></tr>
49+
<tr><td>1210</td></tr>
50+
</table>
51+
</body></html>
52+
"""
53+
54+
55+
def test_wikitable_first_cells_strips_footnote_markers() -> None:
56+
cells = list(wikitable_first_cells(_HTML_NOISY))
57+
# <sup> reference and bracketed leftover markers are gone.
58+
assert "1200" in cells
59+
assert all("[" not in c and "]" not in c for c in cells)
60+
61+
62+
def test_wikipedia_cpu_extract_drops_decimal_and_footnote_artifacts() -> None:
63+
points = list(WikipediaCpu._extract(_HTML_NOISY, "amd", "List_of_AMD_Ryzen_processors"))
64+
slugs = {p.slug for p in points}
65+
# Footnote suffixes no longer appear.
66+
assert "1200-4" not in slugs
67+
assert "1200-af-16-c" not in slugs
68+
# Decimal clock cell "1.25" -> "1-25" is rejected as a non-model artifact.
69+
assert "1-25" not in slugs
70+
# A genuine bare-numeric SKU still survives.
71+
assert "1210" in slugs

0 commit comments

Comments
 (0)