Publish first RF4 fish quality upgrades
This commit is contained in:
@@ -8,7 +8,7 @@ import urllib.error
|
||||
import urllib.request
|
||||
|
||||
from .community_cli import MIN_FETCH_INTERVAL_SECONDS, USER_AGENT, _StrictRedirectHandler, _read_state, _validate_url_before_io, check_and_reserve, fetch_html, fetch_site_key
|
||||
from .media_assets import approve_stored_assets, audit_media_catalog, extract_media_candidates, media_coverage, media_quality_report, merge_manifest, reconcile_queued_duplicates, reclassify_manifest, review_asset, store_asset
|
||||
from .media_assets import approve_stored_assets, audit_media_catalog, compare_quality_upgrades, extract_media_candidates, media_coverage, media_quality_report, merge_manifest, publish_quality_upgrades, queue_quality_upgrades, reconcile_queued_duplicates, reclassify_manifest, review_asset, store_asset
|
||||
|
||||
|
||||
DEFAULT_ROOT = Path("data/media")
|
||||
@@ -33,7 +33,7 @@ def media_queue_plan(root: Path, state_file: Path, *, now: float | None = None)
|
||||
current = datetime.now(timezone.utc).timestamp() if now is None else now
|
||||
grouped: dict[str, list[dict]] = {}
|
||||
for item in manifest.get("assets", []):
|
||||
if item.get("status") == "queued":
|
||||
if item.get("status") in {"queued", "upgrade_queued"}:
|
||||
grouped.setdefault(fetch_site_key(item["asset_url"]), []).append(item)
|
||||
|
||||
domains: dict[str, dict] = {}
|
||||
@@ -79,6 +79,7 @@ def _save_manifest(path: Path, manifest: dict) -> None:
|
||||
|
||||
def _attempt_asset(root: Path, manifest_path: Path, manifest: dict, queued: dict) -> tuple[str, Exception | None]:
|
||||
url = queued["asset_url"]
|
||||
is_upgrade = queued.get("status") == "upgrade_queued"
|
||||
attempted_at = datetime.now(timezone.utc).isoformat()
|
||||
try:
|
||||
request = urllib.request.Request(url, headers={"User-Agent": USER_AGENT, "Accept": "image/*"})
|
||||
@@ -93,11 +94,12 @@ def _attempt_asset(root: Path, manifest_path: Path, manifest: dict, queued: dict
|
||||
digest, relative, width, height, detected_mime = store_asset(root, body, content_type=content_type, source_url=url)
|
||||
except Exception as exc:
|
||||
code = exc.code if isinstance(exc, urllib.error.HTTPError) else None
|
||||
status = "missing" if code in {404, 410} else "blocked" if code in {401, 403, 429} else "invalid" if isinstance(exc, ValueError) else "queued"
|
||||
status = "missing" if code in {404, 410} else "blocked" if code in {401, 403, 429} else "invalid" if isinstance(exc, ValueError) else "upgrade_queued" if is_upgrade else "queued"
|
||||
queued.update({"status": status, "last_attempt_at": attempted_at, "last_error": str(exc)[:500]})
|
||||
_save_manifest(manifest_path, manifest)
|
||||
return f"{status} {url}: {str(exc)[:200]}", exc
|
||||
queued.update({"status": "stored", "sha256": digest, "local_path": relative, "content_type": detected_mime, "bytes": len(body), "width": width, "height": height, "fetched_at": attempted_at})
|
||||
stored_status = "upgrade_stored" if is_upgrade else "stored"
|
||||
queued.update({"status": stored_status, "sha256": digest, "local_path": relative, "content_type": detected_mime, "bytes": len(body), "width": width, "height": height, "fetched_at": attempted_at})
|
||||
_save_manifest(manifest_path, manifest)
|
||||
return f"stored {url} as {relative}", None
|
||||
|
||||
@@ -105,7 +107,7 @@ def _attempt_asset(root: Path, manifest_path: Path, manifest: dict, queued: dict
|
||||
def _download_one(root: Path, state_file: Path, asset_url: str | None = None) -> str:
|
||||
manifest_path = root / "manifest.json"
|
||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
queued = next((item for item in manifest["assets"] if item.get("status") == "queued" and (asset_url is None or item["asset_url"] == asset_url)), None)
|
||||
queued = next((item for item in manifest["assets"] if item.get("status") in {"queued", "upgrade_queued"} and (asset_url is None or item["asset_url"] == asset_url)), None)
|
||||
if queued is None:
|
||||
return "queue is empty" if asset_url is None else "asset is not queued"
|
||||
url = queued["asset_url"]
|
||||
@@ -125,7 +127,7 @@ def _download_batch(root: Path, state_file: Path, *, limit: int = MAX_BATCH_ASSE
|
||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
grouped: dict[str, list[dict]] = {}
|
||||
for item in manifest.get("assets", []):
|
||||
if item.get("status") != "queued":
|
||||
if item.get("status") not in {"queued", "upgrade_queued"}:
|
||||
continue
|
||||
_validate_url_before_io(item["asset_url"])
|
||||
grouped.setdefault(fetch_site_key(item["asset_url"]), []).append(item)
|
||||
@@ -182,7 +184,9 @@ def main(argv: list[str] | None = None) -> int:
|
||||
parser.add_argument("--asset-url", help="Download this exact queued asset instead of the first queue item")
|
||||
parser.add_argument("--reclassify", action="store_true", help="Reapply current conservative classifier without network access")
|
||||
parser.add_argument("--dedupe-queue", action="store_true", help="Mark queued label matches to stored assets as duplicates without network access")
|
||||
parser.add_argument("--queue-quality-upgrades", action="store_true", help="Queue alternatives to published fish below the minimum resolution")
|
||||
parser.add_argument("--approve-stored", action="store_true", help="Publish all stored assets after explicit owner approval")
|
||||
parser.add_argument("--publish-upgrades", action="store_true", help="Atomically publish all stored quality upgrades and retain fallbacks")
|
||||
parser.add_argument("--review-url", help="Review an asset already present in the manifest")
|
||||
parser.add_argument("--decision", choices=("approved", "rejected"))
|
||||
parser.add_argument("--entity-type", choices=("fish", "waterbody", "tackle", "reference"))
|
||||
@@ -191,6 +195,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
parser.add_argument("--audit", action="store_true", help="Verify manifest metadata, hashes and local files without network access")
|
||||
parser.add_argument("--coverage", action="store_true", help="Compare candidates and approvals with the catalog baseline")
|
||||
parser.add_argument("--quality-report", action="store_true", help="Report low-resolution published fish and known alternatives without network access")
|
||||
parser.add_argument("--compare-quality-upgrades", action="store_true", help="Compare stored quality candidates with their published fallbacks")
|
||||
parser.add_argument("--queue-plan", action="store_true", help="Show the next useful queued asset per domain without network access")
|
||||
parser.add_argument("--root", type=Path, default=DEFAULT_ROOT)
|
||||
parser.add_argument("--state-file", type=Path, default=Path(".cache/community-fetch-state.json"))
|
||||
@@ -206,6 +211,10 @@ def main(argv: list[str] | None = None) -> int:
|
||||
if args.quality_report:
|
||||
print(json.dumps(media_quality_report(args.root), ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
if args.compare_quality_upgrades:
|
||||
report = compare_quality_upgrades(args.root)
|
||||
print(json.dumps(report, ensure_ascii=False, indent=2))
|
||||
return 1 if report["issues"] else 0
|
||||
if args.queue_plan:
|
||||
print(json.dumps(media_queue_plan(args.root, args.state_file), ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
@@ -222,11 +231,19 @@ def main(argv: list[str] | None = None) -> int:
|
||||
if args.dedupe_queue:
|
||||
print(json.dumps(reconcile_queued_duplicates(args.root / "manifest.json"), ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
if args.queue_quality_upgrades:
|
||||
print(json.dumps(queue_quality_upgrades(args.root / "manifest.json"), ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
if args.approve_stored:
|
||||
if not args.note:
|
||||
parser.error("--note is required with --approve-stored")
|
||||
print(json.dumps(approve_stored_assets(args.root / "manifest.json", note=args.note), ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
if args.publish_upgrades:
|
||||
if not args.note:
|
||||
parser.error("--note is required with --publish-upgrades")
|
||||
print(json.dumps(publish_quality_upgrades(args.root / "manifest.json", note=args.note), ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
if args.download_one:
|
||||
print(_download_one(args.root, args.state_file, args.asset_url))
|
||||
return 0
|
||||
|
||||
Reference in New Issue
Block a user