diff --git a/.github/workflows/pr-metadata.yml b/.github/workflows/pr-metadata.yml index 21f4fcd7412..1c31e62b27e 100644 --- a/.github/workflows/pr-metadata.yml +++ b/.github/workflows/pr-metadata.yml @@ -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 }], ]); diff --git a/app/validate.py b/app/validate.py index 3c59d169454..d5a0509dee3 100644 --- a/app/validate.py +++ b/app/validate.py @@ -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", @@ -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}////.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") @@ -151,6 +201,9 @@ def validate() -> list[str]: ("brand", brands), ("soc", socs), ("smartphone", phones), + ("tablet", tablets), + ("watch", watches), + ("pda", pdas), ("gpu", gpus), ("cpu", cpus), ): @@ -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) diff --git a/data/pda/hp/2000/ipaq-h3600/ipaq-h3600-base.json b/data/pda/hp/2000/ipaq-h3600/ipaq-h3600-base.json new file mode 100644 index 00000000000..f398cc3164e --- /dev/null +++ b/data/pda/hp/2000/ipaq-h3600/ipaq-h3600-base.json @@ -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" + ] +} diff --git a/data/tablet/apple/2024/ipad-pro-11-m4/ipad-pro-11-m4-wifi-8gb-256gb.json b/data/tablet/apple/2024/ipad-pro-11-m4/ipad-pro-11-m4-wifi-8gb-256gb.json new file mode 100644 index 00000000000..16f92152296 --- /dev/null +++ b/data/tablet/apple/2024/ipad-pro-11-m4/ipad-pro-11-m4-wifi-8gb-256gb.json @@ -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" + ] +} diff --git a/data/watch/samsung/2018/galaxy-watch/galaxy-watch-global-bluetooth-42mm.json b/data/watch/samsung/2018/galaxy-watch/galaxy-watch-global-bluetooth-42mm.json new file mode 100644 index 00000000000..a4ef3244c83 --- /dev/null +++ b/data/watch/samsung/2018/galaxy-watch/galaxy-watch-global-bluetooth-42mm.json @@ -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/" + ] +} diff --git a/site/public/openapi.json b/site/public/openapi.json index 86b7c52af31..a4520c91453 100644 --- a/site/public/openapi.json +++ b/site/public/openapi.json @@ -580,6 +580,492 @@ } } }, + "/v1/tablets": { + "get": { + "tags": [ + "tablets" + ], + "summary": "List tablets", + "operationId": "list_devices_v1_tablets_get", + "parameters": [ + { + "name": "brand", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Brand" + } + }, + { + "name": "soc", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Soc" + } + }, + { + "name": "base_model_slug", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Base Model Slug" + } + }, + { + "name": "sort", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Sort" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 20, + "title": "Limit" + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "default": 0, + "title": "Offset" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Page_ResourceRef_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/tablets/{slug}": { + "get": { + "tags": [ + "tablets" + ], + "summary": "Get a tablet", + "operationId": "get_device_v1_tablets__slug__get", + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Slug" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MobileDeviceRead" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/watches": { + "get": { + "tags": [ + "watches" + ], + "summary": "List watches", + "operationId": "list_devices_v1_watches_get", + "parameters": [ + { + "name": "brand", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Brand" + } + }, + { + "name": "soc", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Soc" + } + }, + { + "name": "base_model_slug", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Base Model Slug" + } + }, + { + "name": "sort", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Sort" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 20, + "title": "Limit" + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "default": 0, + "title": "Offset" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Page_ResourceRef_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/watches/{slug}": { + "get": { + "tags": [ + "watches" + ], + "summary": "Get a watche", + "operationId": "get_device_v1_watches__slug__get", + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Slug" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MobileDeviceRead" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/pdas": { + "get": { + "tags": [ + "pdas" + ], + "summary": "List pdas", + "operationId": "list_devices_v1_pdas_get", + "parameters": [ + { + "name": "brand", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Brand" + } + }, + { + "name": "soc", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Soc" + } + }, + { + "name": "base_model_slug", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Base Model Slug" + } + }, + { + "name": "sort", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Sort" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 20, + "title": "Limit" + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "default": 0, + "title": "Offset" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Page_ResourceRef_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v1/pdas/{slug}": { + "get": { + "tags": [ + "pdas" + ], + "summary": "Get a pda", + "operationId": "get_device_v1_pdas__slug__get", + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Slug" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MobileDeviceRead" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, "/v1/gpus": { "get": { "tags": [ @@ -1371,6 +1857,225 @@ "title": "ManufacturerRef", "description": "A manufacturer (brand) reference embedded in a component detail." }, + "MobileDeviceRead": { + "properties": { + "id": { + "type": "integer", + "title": "Id" + }, + "slug": { + "type": "string", + "title": "Slug" + }, + "base_model_slug": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Base Model Slug" + }, + "name": { + "type": "string", + "title": "Name" + }, + "brand": { + "$ref": "#/components/schemas/BrandSummary" + }, + "soc": { + "anyOf": [ + { + "$ref": "#/components/schemas/SoCSummary" + }, + { + "type": "null" + } + ] + }, + "release_date": { + "type": "string", + "format": "date", + "title": "Release Date" + }, + "msrp_usd": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Msrp Usd" + }, + "ram_gb": { + "type": "number", + "title": "Ram Gb" + }, + "storage_options_gb": { + "items": { + "type": "integer" + }, + "type": "array", + "title": "Storage Options Gb" + }, + "variant": { + "additionalProperties": true, + "type": "object", + "title": "Variant" + }, + "display": { + "additionalProperties": true, + "type": "object", + "title": "Display" + }, + "cameras": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "type": "array", + "title": "Cameras" + }, + "battery_mah": { + "type": "integer", + "title": "Battery Mah" + }, + "charging_wired_w": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Charging Wired W" + }, + "charging_wireless_w": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Charging Wireless W" + }, + "weight_g": { + "type": "number", + "title": "Weight G" + }, + "dimensions": { + "additionalProperties": true, + "type": "object", + "title": "Dimensions" + }, + "ip_rating": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Ip Rating" + }, + "os": { + "type": "string", + "title": "Os" + }, + "os_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Os Version" + }, + "connectivity": { + "additionalProperties": true, + "type": "object", + "title": "Connectivity" + }, + "image_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Image Url" + }, + "images": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Images" + }, + "verified": { + "type": "boolean", + "title": "Verified" + }, + "source_urls": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Source Urls" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "title": "Updated At" + }, + "url": { + "type": "string", + "title": "Url" + } + }, + "type": "object", + "required": [ + "id", + "slug", + "name", + "brand", + "release_date", + "ram_gb", + "storage_options_gb", + "variant", + "display", + "cameras", + "battery_mah", + "weight_g", + "dimensions", + "os", + "connectivity", + "verified", + "source_urls", + "created_at", + "updated_at", + "url" + ], + "title": "MobileDeviceRead", + "description": "Full mobile device detail response." + }, "Page_ResourceRef_": { "properties": { "count": { @@ -1528,6 +2233,17 @@ "type": "string", "title": "Slug" }, + "base_model_slug": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Base Model Slug" + }, "name": { "type": "string", "title": "Name" @@ -1565,6 +2281,11 @@ "type": "array", "title": "Storage Options Gb" }, + "variant": { + "additionalProperties": true, + "type": "object", + "title": "Variant" + }, "display": { "additionalProperties": true, "type": "object", @@ -1698,6 +2419,7 @@ "release_date", "ram_gb", "storage_options_gb", + "variant", "display", "cameras", "battery_mah", diff --git a/site/public/v1/index.json b/site/public/v1/index.json index a16f07dd608..0591695af83 100644 --- a/site/public/v1/index.json +++ b/site/public/v1/index.json @@ -13,6 +13,18 @@ "count": 31671, "url": "/v1/smartphones/index.json" }, + "tablets": { + "count": 1, + "url": "/v1/tablets/index.json" + }, + "watches": { + "count": 1, + "url": "/v1/watches/index.json" + }, + "pdas": { + "count": 1, + "url": "/v1/pdas/index.json" + }, "gpus": { "count": 2030, "url": "/v1/gpus/index.json" diff --git a/site/public/v1/pdas/index.json b/site/public/v1/pdas/index.json new file mode 100644 index 00000000000..f8b7baa1c93 --- /dev/null +++ b/site/public/v1/pdas/index.json @@ -0,0 +1,10 @@ +{ + "count": 1, + "results": [ + { + "slug": "ipaq-h3600-base", + "name": "HP iPAQ H3600", + "url": "/v1/pdas/ipaq-h3600-base" + } + ] +} diff --git a/site/public/v1/pdas/ipaq-h3600-base/index.json b/site/public/v1/pdas/ipaq-h3600-base/index.json new file mode 100644 index 00000000000..4392a5c1737 --- /dev/null +++ b/site/public/v1/pdas/ipaq-h3600-base/index.json @@ -0,0 +1,59 @@ +{ + "id": 1, + "slug": "ipaq-h3600-base", + "base_model_slug": "ipaq-h3600", + "name": "HP iPAQ H3600", + "brand": { + "id": 160, + "slug": "hp", + "name": "HP", + "country": "US", + "url": "/v1/brands/hp" + }, + "soc": null, + "release_date": "2000-04-01", + "msrp_usd": null, + "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, + "charging_wired_w": null, + "charging_wireless_w": null, + "weight_g": 170.0, + "dimensions": { + "height_mm": 130, + "width_mm": 84, + "depth_mm": 16 + }, + "ip_rating": null, + "os": "Pocket PC", + "os_version": null, + "connectivity": {}, + "image_url": null, + "images": [], + "verified": false, + "source_urls": [ + "https://en.wikipedia.org/wiki/IPAQ" + ], + "created_at": "2026-06-20T06:11:29.264530", + "updated_at": "2026-06-20T06:11:29.264530", + "url": "/v1/pdas/ipaq-h3600-base" +} diff --git a/site/public/v1/tablets/index.json b/site/public/v1/tablets/index.json new file mode 100644 index 00000000000..c161b6ac30c --- /dev/null +++ b/site/public/v1/tablets/index.json @@ -0,0 +1,10 @@ +{ + "count": 1, + "results": [ + { + "slug": "ipad-pro-11-m4-wifi-8gb-256gb", + "name": "iPad Pro 11-inch (M4, Wi-Fi, 8GB/256GB)", + "url": "/v1/tablets/ipad-pro-11-m4-wifi-8gb-256gb" + } + ] +} diff --git a/site/public/v1/tablets/ipad-pro-11-m4-wifi-8gb-256gb/index.json b/site/public/v1/tablets/ipad-pro-11-m4-wifi-8gb-256gb/index.json new file mode 100644 index 00000000000..1059f26a814 --- /dev/null +++ b/site/public/v1/tablets/ipad-pro-11-m4-wifi-8gb-256gb/index.json @@ -0,0 +1,77 @@ +{ + "id": 1, + "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": { + "id": 149, + "slug": "apple", + "name": "Apple", + "country": "US", + "url": "/v1/brands/apple" + }, + "soc": null, + "release_date": "2024-05-15", + "msrp_usd": 999, + "ram_gb": 8.0, + "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, + "charging_wired_w": null, + "charging_wireless_w": null, + "weight_g": 444.0, + "dimensions": { + "height_mm": 249.7, + "width_mm": 177.5, + "depth_mm": 5.3 + }, + "ip_rating": null, + "os": "iPadOS", + "os_version": "17.5", + "connectivity": { + "wifi": "Wi-Fi 6E", + "bluetooth": "5.3", + "usb": "USB-C" + }, + "image_url": null, + "images": [], + "verified": false, + "source_urls": [ + "https://support.apple.com/en-us/119891" + ], + "created_at": "2026-06-20T06:11:29.239602", + "updated_at": "2026-06-20T06:11:29.239602", + "url": "/v1/tablets/ipad-pro-11-m4-wifi-8gb-256gb" +} diff --git a/site/public/v1/watches/galaxy-watch-global-bluetooth-42mm/index.json b/site/public/v1/watches/galaxy-watch-global-bluetooth-42mm/index.json new file mode 100644 index 00000000000..659ec6493c0 --- /dev/null +++ b/site/public/v1/watches/galaxy-watch-global-bluetooth-42mm/index.json @@ -0,0 +1,79 @@ +{ + "id": 1, + "slug": "galaxy-watch-global-bluetooth-42mm", + "base_model_slug": "galaxy-watch", + "name": "Galaxy Watch (Bluetooth, 42mm)", + "brand": { + "id": 113, + "slug": "samsung", + "name": "Samsung", + "country": "KR", + "url": "/v1/brands/samsung" + }, + "soc": { + "id": 1902, + "slug": "exynos-9110", + "name": "Exynos 9110", + "manufacturer": { + "slug": "samsung", + "name": "Samsung", + "url": "/v1/brands/samsung" + }, + "process_nm": 10.0, + "gpu_name": "Mali-T720", + "url": "/v1/socs/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, + "charging_wired_w": null, + "charging_wireless_w": null, + "weight_g": 49.0, + "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 + }, + "image_url": null, + "images": [], + "verified": false, + "source_urls": [ + "https://www.samsung.com/us/support/answer/ANS00078020/" + ], + "created_at": "2026-06-20T06:11:29.252062", + "updated_at": "2026-06-20T06:11:29.252062", + "url": "/v1/watches/galaxy-watch-global-bluetooth-42mm" +} diff --git a/site/public/v1/watches/index.json b/site/public/v1/watches/index.json new file mode 100644 index 00000000000..0ff90ab1096 --- /dev/null +++ b/site/public/v1/watches/index.json @@ -0,0 +1,10 @@ +{ + "count": 1, + "results": [ + { + "slug": "galaxy-watch-global-bluetooth-42mm", + "name": "Galaxy Watch (Bluetooth, 42mm)", + "url": "/v1/watches/galaxy-watch-global-bluetooth-42mm" + } + ] +} diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index 45b6e90444f..55dafee5f25 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -9,6 +9,9 @@ const endpoints = [ { label: "/v1/smartphones", desc: "List all phones", href: `${base}v1/smartphones/index.json` }, { label: "/v1/smartphones/{slug}", desc: "Phone detail + scores", href: `${base}v1/smartphones/galaxy-s25-ultra/index.json` }, { label: "/v1/smartphones/{slug}/score", desc: "Computed scores", href: `${base}v1/smartphones/galaxy-s25-ultra/score/index.json` }, + { label: "/v1/tablets", desc: "List all tablets", href: `${base}v1/tablets/index.json` }, + { label: "/v1/watches", desc: "List all watches", href: `${base}v1/watches/index.json` }, + { label: "/v1/pdas", desc: "List all PDAs", href: `${base}v1/pdas/index.json` }, { label: "/v1/socs/{slug}", desc: "System-on-chip detail", href: `${base}v1/socs/snapdragon-8-elite/index.json` }, { label: "/v1/gpus/{slug}", desc: "Discrete GPU detail", href: `${base}v1/gpus/geforce-rtx-5090/index.json` }, { label: "/v1/cpus/{slug}", desc: "Desktop / laptop CPU", href: `${base}v1/cpus/core-i9-14900k/index.json` }, @@ -155,6 +158,9 @@ const endpoints = [