Skip to content

Commit e13f6de

Browse files
committed
data(brand): require source urls for brand records
1 parent 61e8a04 commit e13f6de

131 files changed

Lines changed: 434 additions & 29 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,20 @@ static-dump generator live in
1212

1313
## Layout
1414

15-
```
16-
data/brand/<slug>.json # e.g. data/brand/samsung.json
17-
data/soc/<manufacturer>/<slug>.json # data/soc/qualcomm/snapdragon-8-elite.json
18-
data/smartphone/<brand>/<slug>.json # data/smartphone/samsung/galaxy-s25.json
19-
data/gpu/<manufacturer>/<year>/<segment>/<slug>.json # data/gpu/nvidia/2025/consumer/geforce-rtx-5090.json
20-
data/cpu/<manufacturer>/<year>/<segment>/<slug>.json # data/cpu/intel/2023/consumer/core-i9-14900k.json
15+
```text
16+
data/brand/<country>/<slug>.json # e.g. data/brand/kr/samsung.json
17+
data/soc/<manufacturer>/<year>/<slug>.json # data/soc/qualcomm/2024/snapdragon-8-elite.json
18+
data/smartphone/<brand>/<year>/<slug>.json # data/smartphone/samsung/2025/galaxy-s25.json
19+
data/gpu/<manufacturer>/<year>/<segment>/<slug>.json # data/gpu/nvidia/2025/consumer/geforce-rtx-5090.json
20+
data/cpu/<manufacturer>/<year>/<segment>/<slug>.json # data/cpu/intel/2023/consumer/core-i9-14900k.json
2121
```
2222

2323
All paths use singular folder names. Slugs are kebab-case and unique within each category.
2424

25-
The Astro site lives under `site/` and is the deploy target for GitHub Pages
26-
it consumes the static JSON dump produced by TechEngine's `weekly-refresh` workflow.
25+
The Astro site lives under `site/` and is the deploy target for GitHub Pages. It
26+
consumes the static JSON dump produced by TechEngine's `weekly-refresh` workflow.
2727

28-
## Self-check
28+
## Self-Check
2929

3030
A lightweight bundled validator lives at `app/validate.py`. It runs on every PR via
3131
[`validate-data.yml`](.github/workflows/validate-data.yml) and is also chained into
@@ -35,15 +35,16 @@ the heavier TechEngine validation workflow as a downstream job.
3535
python -m app.validate
3636
```
3737

38-
The validator uses only the Python standard library no install step required.
38+
The validator uses only the Python standard library; no install step required.
3939

4040
## Contributing
4141

4242
Open a PR with the new/updated JSON file. The PR template walks through what to
43-
include. The validator must pass and `source_urls` must cite at least one canonical
43+
include. The validator must pass. All records (`brand`, `soc`, `smartphone`,
44+
`gpu`, and `cpu`) must include `source_urls` with at least one canonical
4445
reference (vendor product page, Wikipedia infobox, datasheet).
4546

4647
## License
4748

48-
Data is licensed **CC-BY-SA 4.0** attribute "Data from TechAPI" and share alike.
49+
Data is licensed **CC-BY-SA 4.0**; attribute "Data from TechAPI" and share alike.
4950
The bundled validator code is [MIT](LICENSE).

app/validate.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
SLUG_RE = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
1919

20-
BRAND_REQUIRED = {"slug", "name", "country", "categories"}
20+
BRAND_REQUIRED = {"slug", "name", "country", "categories", "source_urls"}
2121
BRAND_CATEGORIES = {
2222
"smartphone-oem",
2323
"soc-designer",
@@ -78,7 +78,7 @@ def _load(subdir: str) -> list[tuple[str, dict[str, Any]]]:
7878
if not path.exists():
7979
return []
8080
return [
81-
(str(f.relative_to(DATA_DIR)), json.loads(f.read_text(encoding="utf-8")))
81+
(str(f.relative_to(DATA_DIR)), json.loads(f.read_text(encoding="utf-8-sig")))
8282
for f in sorted(path.rglob("*.json")) # recurse into brand subfolders
8383
]
8484

@@ -127,6 +127,14 @@ def _check_unique_slugs(
127127
seen[slug] = fname
128128

129129

130+
def _check_source_urls(name: str, record: dict[str, Any], errors: list[str]) -> None:
131+
urls = record.get("source_urls")
132+
if not isinstance(urls, list) or not urls or not all(
133+
isinstance(url, str) and url.startswith(("http://", "https://")) for url in urls
134+
):
135+
errors.append(f"{name}: source_urls must be a non-empty list of http(s) URL strings")
136+
137+
130138
def validate() -> list[str]:
131139
errors: list[str] = []
132140

@@ -150,6 +158,7 @@ def validate() -> list[str]:
150158

151159
for fname, rec in brands:
152160
_check_required(fname, rec, BRAND_REQUIRED, errors)
161+
_check_source_urls(fname, rec, errors)
153162
_check_slug(fname, rec.get("slug"), errors)
154163
if "founded_year" in rec:
155164
_check_range(fname, "founded_year", rec["founded_year"], 1800, 2100, errors)
@@ -182,6 +191,7 @@ def validate() -> list[str]:
182191

183192
for fname, rec in socs:
184193
_check_required(fname, rec, SOC_REQUIRED, errors)
194+
_check_source_urls(fname, rec, errors)
185195
_check_slug(fname, rec.get("slug"), errors)
186196
if "release_date" in rec:
187197
_check_date(fname, rec["release_date"], errors)
@@ -191,6 +201,7 @@ def validate() -> list[str]:
191201

192202
for fname, rec in phones:
193203
_check_required(fname, rec, PHONE_REQUIRED, errors)
204+
_check_source_urls(fname, rec, errors)
194205
_check_slug(fname, rec.get("slug"), errors)
195206
if "release_date" in rec:
196207
_check_date(fname, rec["release_date"], errors)
@@ -206,6 +217,7 @@ def validate() -> list[str]:
206217

207218
for fname, rec in gpus:
208219
_check_required(fname, rec, GPU_REQUIRED, errors)
220+
_check_source_urls(fname, rec, errors)
209221
_check_slug(fname, rec.get("slug"), errors)
210222
if "release_date" in rec:
211223
_check_date(fname, rec["release_date"], errors)
@@ -219,6 +231,7 @@ def validate() -> list[str]:
219231
valid_segments = {"desktop", "laptop", "hedt", "server"}
220232
for fname, rec in cpus:
221233
_check_required(fname, rec, CPU_REQUIRED, errors)
234+
_check_source_urls(fname, rec, errors)
222235
_check_slug(fname, rec.get("slug"), errors)
223236
if "release_date" in rec:
224237
_check_date(fname, rec["release_date"], errors)
@@ -236,6 +249,10 @@ def validate() -> list[str]:
236249

237250

238251
def run() -> int:
252+
try:
253+
sys.stdout.reconfigure(encoding="utf-8") # type: ignore[union-attr]
254+
except Exception:
255+
pass
239256
errors = validate()
240257
if errors:
241258
print(f"❌ Data validation failed ({len(errors)} issue(s)):")

data/brand/at/emporia.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
"name": "Emporia Telecom",
44
"country": "AT",
55
"founded_year": 1991,
6-
"logo_url": null,
6+
"logo_url": "https://commons.wikimedia.org/wiki/Special:FilePath/Emporia.jpg",
77
"website": "https://www.emporia.eu",
8+
"source_urls": [
9+
"https://commons.wikimedia.org/wiki/Special:FilePath/Emporia.jpg"
10+
],
811
"categories": [
912
"smartphone-oem"
1013
],

data/brand/ca/ati.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
"founded_year": 1985,
66
"logo_url": "https://commons.wikimedia.org/wiki/Special:FilePath/ATI_Technologies_%28logo%29.svg",
77
"website": null,
8+
"source_urls": [
9+
"https://commons.wikimedia.org/wiki/Special:FilePath/ATI_Technologies_%28logo%29.svg"
10+
],
811
"categories": [
912
"gpu-designer",
1013
"defunct"

data/brand/ca/blackberry.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
"founded_year": 1984,
66
"logo_url": "https://commons.wikimedia.org/wiki/Special:FilePath/Blackberry_Logo.svg",
77
"website": "https://www.blackberry.com",
8+
"source_urls": [
9+
"https://www.blackberry.com"
10+
],
811
"categories": [
912
"smartphone-oem",
1013
"defunct"

data/brand/ca/matrox.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
"founded_year": 1976,
66
"logo_url": "https://commons.wikimedia.org/wiki/Special:FilePath/Matrox_Electronic_Systems_logo.svg",
77
"website": "https://www.matrox.com",
8+
"source_urls": [
9+
"https://www.matrox.com"
10+
],
811
"categories": [
912
"gpu-designer"
1013
],

data/brand/ch/stmicroelectronics.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
"founded_year": 1987,
66
"logo_url": "https://commons.wikimedia.org/wiki/Special:FilePath/ST_logo_2020_blue_V.svg",
77
"website": "https://www.st.com",
8+
"source_urls": [
9+
"https://www.st.com"
10+
],
811
"categories": [
912
"chipset-maker",
1013
"soc-designer"

data/brand/cn/alcatel.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
"founded_year": 2004,
66
"logo_url": "https://commons.wikimedia.org/wiki/Special:FilePath/Alcatel_logo_2016.svg",
77
"website": "https://www.alcatelmobile.com",
8+
"source_urls": [
9+
"https://www.alcatelmobile.com"
10+
],
811
"categories": [
912
"smartphone-oem",
1013
"sub-brand"

data/brand/cn/allwinner.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
"founded_year": 2007,
66
"logo_url": "https://commons.wikimedia.org/wiki/Special:FilePath/Allwinner_logo.png",
77
"website": "https://www.allwinnertech.com",
8+
"source_urls": [
9+
"https://www.allwinnertech.com"
10+
],
811
"categories": [
912
"soc-designer"
1013
],

data/brand/cn/amlogic.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
"founded_year": 1995,
66
"logo_url": "https://commons.wikimedia.org/wiki/Special:FilePath/Amlogic_logo.svg",
77
"website": "https://www.amlogic.com",
8+
"source_urls": [
9+
"https://www.amlogic.com"
10+
],
811
"categories": [
912
"soc-designer"
1013
],

0 commit comments

Comments
 (0)