Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/pr-metadata.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ jobs:
['brand', { added: 0, modified: 0, deleted: 0 }],
['soc', { added: 0, modified: 0, deleted: 0 }],
['smartphone', { added: 0, modified: 0, deleted: 0 }],
['tablet', { added: 0, modified: 0, deleted: 0 }],
['watch', { added: 0, modified: 0, deleted: 0 }],
['pda', { added: 0, modified: 0, deleted: 0 }],
['gpu', { added: 0, modified: 0, deleted: 0 }],
['cpu', { added: 0, modified: 0, deleted: 0 }],
]);
Expand Down
72 changes: 72 additions & 0 deletions app/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@
"os",
}

MOBILE_DEVICE_REQUIRED = {
"slug",
"name",
"brand",
"release_date",
"ram_gb",
"battery_mah",
"weight_g",
"os",
"source_urls",
"verified",
}

GPU_REQUIRED = {
"slug",
"name",
Expand Down Expand Up @@ -135,12 +148,49 @@ def _check_source_urls(name: str, record: dict[str, Any], errors: list[str]) ->
errors.append(f"{name}: source_urls must be a non-empty list of http(s) URL strings")


def _check_variant_path(
fname: str,
rec: dict[str, Any],
category: str,
errors: list[str],
*,
allow_flat: bool = False,
) -> None:
parts = Path(fname).parts
if allow_flat and len(parts) == 4:
return
if len(parts) != 5:
errors.append(
f"{fname}: {category} variants must live at "
f"'{category}/<brand>/<year>/<base_model_slug>/<slug>.json'"
)
return
_, brand, year, base_model_slug, filename = parts
if rec.get("brand") != brand:
errors.append(f"{fname}: lives in brand '{brand}' but brand='{rec.get('brand')}'")
release_year = str(rec.get("release_date", ""))[:4]
if release_year and year != release_year:
errors.append(
f"{fname}: lives in year '{year}' but release_date starts with '{release_year}'"
)
if rec.get("base_model_slug") and rec.get("base_model_slug") != base_model_slug:
errors.append(
f"{fname}: lives under base '{base_model_slug}' but "
f"base_model_slug='{rec.get('base_model_slug')}'"
)
if filename != f"{rec.get('slug')}.json":
errors.append(f"{fname}: filename must match slug '{rec.get('slug')}'")


def validate() -> list[str]:
errors: list[str] = []

brands = _load("brand")
socs = _load("soc")
phones = _load("smartphone")
tablets = _load("tablet")
watches = _load("watch")
pdas = _load("pda")
gpus = _load("gpu")
cpus = _load("cpu")

Expand All @@ -151,6 +201,9 @@ def validate() -> list[str]:
("brand", brands),
("soc", socs),
("smartphone", phones),
("tablet", tablets),
("watch", watches),
("pda", pdas),
("gpu", gpus),
("cpu", cpus),
):
Expand Down Expand Up @@ -214,6 +267,25 @@ def validate() -> list[str]:
errors.append(f"{fname}: brand '{rec.get('brand')}' not a known brand")
if rec.get("soc") not in soc_slugs:
errors.append(f"{fname}: soc '{rec.get('soc')}' not a known SoC")
_check_variant_path(fname, rec, "smartphone", errors, allow_flat=True)

for category, records in (("tablet", tablets), ("watch", watches), ("pda", pdas)):
for fname, rec in records:
_check_required(fname, rec, MOBILE_DEVICE_REQUIRED, errors)
_check_source_urls(fname, rec, errors)
_check_slug(fname, rec.get("slug"), errors)
if "release_date" in rec:
_check_date(fname, rec["release_date"], errors)
_check_range(fname, "ram_gb", rec.get("ram_gb"), 0.016, 64, errors)
_check_range(fname, "battery_mah", rec.get("battery_mah"), 50, 20000, errors)
_check_range(fname, "weight_g", rec.get("weight_g"), 10, 2000, errors)
if "msrp_usd" in rec:
_check_range(fname, "msrp_usd", rec["msrp_usd"], 10, 10000, errors)
if rec.get("brand") not in brand_slugs:
errors.append(f"{fname}: brand '{rec.get('brand')}' not a known brand")
if rec.get("soc") is not None and rec.get("soc") not in soc_slugs:
errors.append(f"{fname}: soc '{rec.get('soc')}' not a known SoC")
_check_variant_path(fname, rec, category, errors)

for fname, rec in gpus:
_check_required(fname, rec, GPU_REQUIRED, errors)
Expand Down
31 changes: 31 additions & 0 deletions data/pda/hp/2000/ipaq-h3600/ipaq-h3600-base.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"slug": "ipaq-h3600-base",
"base_model_slug": "ipaq-h3600",
"name": "HP iPAQ H3600",
"brand": "hp",
"release_date": "2000-04-01",
"ram_gb": 0.032,
"storage_options_gb": [],
"variant": {
"region": "global",
"model_numbers": ["H3600"],
"memory": {"ram_gb": 0.032},
"network": {"cellular": "none"},
"sim": "none"
},
"display": {
"size_inch": 3.8,
"resolution": "240x320",
"type": "TFT LCD"
},
"cameras": [],
"battery_mah": 950,
"weight_g": 170,
"dimensions": {"height_mm": 130, "width_mm": 84, "depth_mm": 16},
"os": "Pocket PC",
"connectivity": {},
"verified": false,
"source_urls": [
"https://en.wikipedia.org/wiki/IPAQ"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"slug": "ipad-pro-11-m4-wifi-8gb-256gb",
"base_model_slug": "ipad-pro-11-m4",
"name": "iPad Pro 11-inch (M4, Wi-Fi, 8GB/256GB)",
"brand": "apple",
"release_date": "2024-05-15",
"msrp_usd": 999,
"ram_gb": 8,
"storage_options_gb": [256],
"variant": {
"region": "global",
"model_numbers": ["A2836"],
"memory": {"ram_gb": 8, "storage_gb": 256},
"network": {"cellular": "none", "carrier": "wifi-only"},
"sim": "none"
},
"display": {
"size_inch": 11.0,
"resolution": "2420x1668",
"refresh_hz": 120,
"type": "Ultra Retina XDR OLED"
},
"cameras": [{"type": "main", "mp": 12}, {"type": "selfie", "mp": 12}],
"battery_mah": 8160,
"weight_g": 444,
"dimensions": {"height_mm": 249.7, "width_mm": 177.5, "depth_mm": 5.3},
"os": "iPadOS",
"os_version": "17.5",
"connectivity": {"wifi": "Wi-Fi 6E", "bluetooth": "5.3", "usb": "USB-C"},
"verified": false,
"source_urls": [
"https://support.apple.com/en-us/119891"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"slug": "galaxy-watch-global-bluetooth-42mm",
"base_model_slug": "galaxy-watch",
"name": "Galaxy Watch (Bluetooth, 42mm)",
"brand": "samsung",
"soc": "exynos-9110",
"release_date": "2018-08-24",
"msrp_usd": 329,
"ram_gb": 0.75,
"storage_options_gb": [4],
"variant": {
"region": "global",
"model_numbers": ["SM-R810"],
"memory": {"ram_gb": 0.75, "storage_gb": 4},
"network": {"cellular": "none", "carrier": "bluetooth-only"},
"sim": "none"
},
"display": {
"size_inch": 1.2,
"resolution": "360x360",
"type": "Super AMOLED"
},
"cameras": [],
"battery_mah": 270,
"weight_g": 49,
"dimensions": {"height_mm": 45.7, "width_mm": 41.9, "depth_mm": 12.7},
"ip_rating": "IP68",
"os": "Tizen",
"os_version": "4.0",
"connectivity": {"wifi": "802.11 b/g/n", "bluetooth": "4.2", "nfc": true},
"verified": false,
"source_urls": [
"https://www.samsung.com/us/support/answer/ANS00078020/"
]
}
Loading
Loading