|
22 | 22 |
|
23 | 23 | from app.validate import DATA_DIR |
24 | 24 |
|
25 | | -from . import crossref, http_check, ledger, offline, promote |
| 25 | +from . import crossref, http_check, ledger, offline, promote, wikidata |
26 | 26 | from .common import ( |
27 | 27 | CATEGORIES, |
28 | 28 | SCORES_PATH, |
@@ -198,8 +198,6 @@ def _print_markdown(hist: dict[str, Counter[str]], scored: int, hard_flags: Coun |
198 | 198 | ) |
199 | 199 | gtot = sum(totals.values()) or 1 |
200 | 200 | print(f"**{scored} record(s) assessed.**\n") |
201 | | - print("Software and website assess required fields and sources only; " |
202 | | - "domain consistency rules are unavailable and these categories cannot earn green.\n") |
203 | 201 |
|
204 | 202 | # Overall distribution as a Mermaid pie (rendered by GitHub). Mermaid colors |
205 | 203 | # slices pie1/pie2/pie3 in declaration order, so pin them to green/amber/red |
@@ -384,6 +382,34 @@ def _ranked_unverified( |
384 | 382 | return [rec for _score, rec in scored] |
385 | 383 |
|
386 | 384 |
|
| 385 | +def cmd_check_wikidata(args: argparse.Namespace) -> int: |
| 386 | + records = load_all(args.category or ("software", "website")) |
| 387 | + cache = http_check.load_cache() |
| 388 | + now = datetime.now(UTC) |
| 389 | + grouped: dict[str, list[str]] = {} |
| 390 | + for rows in records.values(): |
| 391 | + for rec in rows: |
| 392 | + for url in rec.data.get("source_urls", []): |
| 393 | + qid = wikidata.qid_of(url) |
| 394 | + if qid and (args.recheck or url not in cache |
| 395 | + or not str(cache[url].get("reason", "")).startswith("wikidata-") |
| 396 | + or not http_check.is_fresh(cache[url], now, args.ttl_days)): |
| 397 | + grouped.setdefault(qid, []).append(url) |
| 398 | + selected = list(grouped)[:args.max] |
| 399 | + urls = list(dict.fromkeys(u for qid in selected for u in grouped[qid])) |
| 400 | + checked = alive = 0 |
| 401 | + for results in wikidata.check_batches(urls): |
| 402 | + for result in results: |
| 403 | + cache[result.url] = http_check.result_to_entry(result, _now_iso()) |
| 404 | + checked += 1 |
| 405 | + alive += result.alive |
| 406 | + if results: |
| 407 | + http_check.save_cache(cache) |
| 408 | + print(f"check-wikidata: {len(selected)} QIDs; {checked} URLs checked, {alive} alive; " |
| 409 | + "indeterminate results remain uncached") |
| 410 | + return 0 |
| 411 | + |
| 412 | + |
387 | 413 | def cmd_check_urls(args: argparse.Namespace) -> int: |
388 | 414 | records = load_all() |
389 | 415 | _, _, soc_release = foreign_key_sets(records) |
@@ -418,10 +444,13 @@ def cmd_check_urls(args: argparse.Namespace) -> int: |
418 | 444 |
|
419 | 445 | ts = _now_iso() |
420 | 446 | results = http_check.check_urls( |
421 | | - todo, |
| 447 | + [u for u in todo if not wikidata.qid_of(u)], |
422 | 448 | max_workers=args.workers, |
423 | 449 | min_interval=args.min_interval, |
424 | 450 | ) |
| 451 | + # A redirect entity must not become alive again through a generic HTTP 200. |
| 452 | + for batch in wikidata.check_batches([u for u in todo if wikidata.qid_of(u)]): |
| 453 | + results.extend(batch) |
425 | 454 | # A rate-limited answer is not a verdict — leave it out so the next run asks |
426 | 455 | # again instead of parking the URL as dead for the whole TTL. |
427 | 456 | throttled = sum(1 for r in results if r.transient) |
@@ -740,6 +769,13 @@ def build_parser() -> argparse.ArgumentParser: |
740 | 769 | cu.add_argument("--recheck", action="store_true", help="ignore cache freshness") |
741 | 770 | cu.set_defaults(func=cmd_check_urls) |
742 | 771 |
|
| 772 | + wd = sub.add_parser("check-wikidata", help="Batched Wikidata source liveness") |
| 773 | + wd.add_argument("--category", nargs="*", choices=CATEGORIES) |
| 774 | + wd.add_argument("--max", type=int, default=20000, help="maximum uncached QIDs") |
| 775 | + wd.add_argument("--ttl-days", type=int, default=http_check.DEFAULT_TTL_DAYS) |
| 776 | + wd.add_argument("--recheck", action="store_true") |
| 777 | + wd.set_defaults(func=cmd_check_wikidata) |
| 778 | + |
743 | 779 | cr = sub.add_parser("crossref", help="Tier 2: external cross-reference (exact heading)") |
744 | 780 | cr.add_argument("--category", nargs="*", choices=CATEGORIES, help="limit to categories") |
745 | 781 | cr.add_argument("--max", type=int, default=200, help="number of yellow/red records to escalate") |
|
0 commit comments