Arag / scripts /backtest_book_urls.py
AuthorBot
Improve multi-platform book URL import with ranked catalog matching.
e61653c
Raw
History Blame Contribute Delete
4.67 kB
"""Backtest book URL metadata fetch across all supported platforms."""
from __future__ import annotations
import asyncio
import json
import sys
from dataclasses import asdict
# Core fields expected for a "complete" import preview
CORE_FIELDS = ("title", "author", "description", "genre", "cover_url")
EXTRA_FIELDS = ("about_author", "isbn", "publisher", "publish_year", "page_count", "language")
TEST_URLS: dict[str, str] = {
"amazon": "https://www.amazon.com/dp/0593128257",
"barnes_noble": "https://www.barnesandnoble.com/w/james-percival-everett/1143678734?ean=9780385550369",
"goodreads": "https://www.goodreads.com/book/show/4671.The_Great_Gatsby",
"google_books": "https://books.google.com/books/about/Project_Hail_Mary.html?id=GrYsEAAAQBAJ",
"apple_books": "https://books.apple.com/us/book/fang-fiction/id6475274221",
"kobo": "https://www.kobo.com/ca/en/ebook/project-hail-mary-9780593135204",
"thriftbooks": "https://www.thriftbooks.com/w/1q84_haruki-murakami/252010/",
"abebooks": "https://www.abebooks.com/products/isbn/9780593135204",
"worldcat": "https://search.worldcat.org/search?q=bn:9780593135204",
"open_library": "https://openlibrary.org/works/OL268236W/The_Cat_Who_Went_to_Heaven",
"bookshop": "https://bookshop.org/p/books/1q84-haruki-murakami/525d944c7585381e?ean=9780307476463",
"lulu": "https://www.lulu.com/shop/brett-arquette/operation-hail-storm/paperback/product-22895695.html",
"walmart": "https://www.walmart.com/ip/Winternight-Trilogy-The-Bear-and-the-Nightingale-Book-1-Hardcover-9781101885932/53596259",
"waterstones": "https://www.waterstones.com/book/9780575077331",
"booksamillion": "https://www.booksamillion.com/p/Three-Body-Problem/Cixin-Liu/9780765382030",
"powells": "https://www.powells.com/book/project-hail-mary-9780593135204",
"indigo": "https://www.indigo.ca/en-ca/project-hail-mary-a-novel/9780593135204.html",
}
def _field_ok(name: str, value) -> bool:
if name == "description":
return bool(value) and len(str(value)) >= 40
if name == "about_author":
return bool(value) and len(str(value)) >= 50
if name == "page_count":
return int(value or 0) > 0
return bool(value)
async def main() -> int:
from app.services.book_url_scraper import fetch_book_metadata, detect_platform
results = []
for platform, url in TEST_URLS.items():
detected = detect_platform(url)
meta = await fetch_book_metadata(url, force_refresh=True)
row = {
"platform": platform,
"detected": detected,
"url": url,
"confidence": meta.confidence,
"error": meta.error,
"warnings": meta.warnings,
"fields": {},
"missing_core": [],
"missing_extra": [],
}
d = asdict(meta)
for f in CORE_FIELDS + EXTRA_FIELDS:
val = d.get(f, "")
if f == "page_count":
display = val if val else ""
else:
display = (str(val)[:60] + "...") if len(str(val)) > 60 else val
row["fields"][f] = {"ok": _field_ok(f, val), "value": display}
row["missing_core"] = [f for f in CORE_FIELDS if not row["fields"][f]["ok"]]
row["missing_extra"] = [f for f in EXTRA_FIELDS if not row["fields"][f]["ok"]]
row["perfect"] = not row["missing_core"] and not row["error"]
results.append(row)
# Print summary table
print("\n" + "=" * 100)
print("BOOK URL METADATA BACKTEST")
print("=" * 100)
perfect = sum(1 for r in results if r["perfect"])
print(f"\nPerfect (all core fields): {perfect}/{len(results)}\n")
for r in results:
status = "PASS" if r["perfect"] else "FAIL"
print(f"[{status}] {r['platform']:15} conf={r['confidence']:.2f} detected={r['detected']}")
print(f" title: {ascii(r['fields']['title']['value'])}")
print(f" author: {ascii(r['fields']['author']['value'])}")
if r["missing_core"]:
print(f" MISSING CORE: {', '.join(r['missing_core'])}")
if r["missing_extra"]:
print(f" missing extra: {', '.join(r['missing_extra'])}")
if r["error"]:
print(f" ERROR: {r['error']}")
print()
out_path = "scripts/backtest_book_urls_results.json"
with open(out_path, "w", encoding="utf-8") as f:
json.dump(results, f, indent=2)
print(f"Full results written to {out_path}")
return 0 if perfect == len(results) else 1
if __name__ == "__main__":
sys.path.insert(0, ".")
raise SystemExit(asyncio.run(main()))