4444 "os" ,
4545}
4646
47+ MOBILE_DEVICE_REQUIRED = {
48+ "slug" ,
49+ "name" ,
50+ "brand" ,
51+ "release_date" ,
52+ "ram_gb" ,
53+ "battery_mah" ,
54+ "weight_g" ,
55+ "os" ,
56+ "source_urls" ,
57+ "verified" ,
58+ }
59+
4760GPU_REQUIRED = {
4861 "slug" ,
4962 "name" ,
@@ -135,12 +148,49 @@ def _check_source_urls(name: str, record: dict[str, Any], errors: list[str]) ->
135148 errors .append (f"{ name } : source_urls must be a non-empty list of http(s) URL strings" )
136149
137150
151+ def _check_variant_path (
152+ fname : str ,
153+ rec : dict [str , Any ],
154+ category : str ,
155+ errors : list [str ],
156+ * ,
157+ allow_flat : bool = False ,
158+ ) -> None :
159+ parts = Path (fname ).parts
160+ if allow_flat and len (parts ) == 4 :
161+ return
162+ if len (parts ) != 5 :
163+ errors .append (
164+ f"{ fname } : { category } variants must live at "
165+ f"'{ category } /<brand>/<year>/<base_model_slug>/<slug>.json'"
166+ )
167+ return
168+ _ , brand , year , base_model_slug , filename = parts
169+ if rec .get ("brand" ) != brand :
170+ errors .append (f"{ fname } : lives in brand '{ brand } ' but brand='{ rec .get ('brand' )} '" )
171+ release_year = str (rec .get ("release_date" , "" ))[:4 ]
172+ if release_year and year != release_year :
173+ errors .append (
174+ f"{ fname } : lives in year '{ year } ' but release_date starts with '{ release_year } '"
175+ )
176+ if rec .get ("base_model_slug" ) and rec .get ("base_model_slug" ) != base_model_slug :
177+ errors .append (
178+ f"{ fname } : lives under base '{ base_model_slug } ' but "
179+ f"base_model_slug='{ rec .get ('base_model_slug' )} '"
180+ )
181+ if filename != f"{ rec .get ('slug' )} .json" :
182+ errors .append (f"{ fname } : filename must match slug '{ rec .get ('slug' )} '" )
183+
184+
138185def validate () -> list [str ]:
139186 errors : list [str ] = []
140187
141188 brands = _load ("brand" )
142189 socs = _load ("soc" )
143190 phones = _load ("smartphone" )
191+ tablets = _load ("tablet" )
192+ watches = _load ("watch" )
193+ pdas = _load ("pda" )
144194 gpus = _load ("gpu" )
145195 cpus = _load ("cpu" )
146196
@@ -151,6 +201,9 @@ def validate() -> list[str]:
151201 ("brand" , brands ),
152202 ("soc" , socs ),
153203 ("smartphone" , phones ),
204+ ("tablet" , tablets ),
205+ ("watch" , watches ),
206+ ("pda" , pdas ),
154207 ("gpu" , gpus ),
155208 ("cpu" , cpus ),
156209 ):
@@ -214,6 +267,25 @@ def validate() -> list[str]:
214267 errors .append (f"{ fname } : brand '{ rec .get ('brand' )} ' not a known brand" )
215268 if rec .get ("soc" ) not in soc_slugs :
216269 errors .append (f"{ fname } : soc '{ rec .get ('soc' )} ' not a known SoC" )
270+ _check_variant_path (fname , rec , "smartphone" , errors , allow_flat = True )
271+
272+ for category , records in (("tablet" , tablets ), ("watch" , watches ), ("pda" , pdas )):
273+ for fname , rec in records :
274+ _check_required (fname , rec , MOBILE_DEVICE_REQUIRED , errors )
275+ _check_source_urls (fname , rec , errors )
276+ _check_slug (fname , rec .get ("slug" ), errors )
277+ if "release_date" in rec :
278+ _check_date (fname , rec ["release_date" ], errors )
279+ _check_range (fname , "ram_gb" , rec .get ("ram_gb" ), 0.016 , 64 , errors )
280+ _check_range (fname , "battery_mah" , rec .get ("battery_mah" ), 50 , 20000 , errors )
281+ _check_range (fname , "weight_g" , rec .get ("weight_g" ), 10 , 2000 , errors )
282+ if "msrp_usd" in rec :
283+ _check_range (fname , "msrp_usd" , rec ["msrp_usd" ], 10 , 10000 , errors )
284+ if rec .get ("brand" ) not in brand_slugs :
285+ errors .append (f"{ fname } : brand '{ rec .get ('brand' )} ' not a known brand" )
286+ if rec .get ("soc" ) is not None and rec .get ("soc" ) not in soc_slugs :
287+ errors .append (f"{ fname } : soc '{ rec .get ('soc' )} ' not a known SoC" )
288+ _check_variant_path (fname , rec , category , errors )
217289
218290 for fname , rec in gpus :
219291 _check_required (fname , rec , GPU_REQUIRED , errors )
0 commit comments