"""Keep every published Covenant Index figure inside the 24 h refresh window we promise, independently of the accuracy check that measures it. Resolves accuracy-opened tickets publicly with the re-pulled values.""" import asyncio import logging from datetime import datetime, timedelta, timezone import scheduler as scheduler_mod from deps import db logger = logging.getLogger("scorerefresh") JOB = "score_refresh" EVERY_HOURS = 12 OLDER_THAN_HOURS = 12 MAX_PER_RUN = 150 async def run(db_=None, payload: dict | None = None) -> dict: import corrections import covenantscore db_ = db if db_ is None else db_ cutoff = (datetime.now(timezone.utc) - timedelta(hours=OLDER_THAN_HOURS)).isoformat() docs = await db_.covenant_scores.find({"cached_at": {"$lt": cutoff}}, {"_id": 0, "company": 1, "domain": 1, "key": 1, "cached_at": 1}).sort("cached_at", 1).to_list(MAX_PER_RUN) ticketed = {t["figure"].split(" — ")[1] for t in await db_.corrections.find({"opened_by": "accuracy_audit", "resolved_at": None}, {"_id": 0, "figure": 1}).to_list(200) if t.get("figure", "").count(" — ") >= 2} if ticketed: first = await db_.covenant_scores.find({"company": {"$in": list(ticketed)}}, {"_id": 0, "company": 1, "domain": 1, "key": 1, "cached_at": 1}).to_list(50) seen = {d["key"] for d in first} docs = first + [d for d in docs if d["key"] not in seen] refreshed, failed, resolved = 0, 0, 0 for d in docs: try: q_company, _, q_domain = d["key"].partition("|") await db_.covenant_scores.update_one({"key": d["key"]}, {"$set": {"cached_at": "1970-01-01T00:00:00+00:00"}}) try: new = await covenantscore.compute_score(q_company, q_domain) except Exception: await asyncio.sleep(5) new = await covenantscore.compute_score(q_company, q_domain) if new.get("key") != d["key"]: raise RuntimeError(f"refresh wrote key {new.get('key')!r}, expected {d['key']!r}") refreshed += 1 step = (new.get("reproduce") or {}).get("steps", [{}])[0] open_tickets = await db_.corrections.find({"opened_by": "accuracy_audit", "resolved_at": None, "figure": {"$regex": f"— {__import__('re').escape(d['company'])} —"}}).to_list(20) for t in open_tickets: upd = {"status": "resolved", "outcome": "corrected", "resolution": f"Re-pulled from the source by the scheduled refresh; the published figure is now {step.get('value')!r} as of {new.get('cached_at')}. Cause: the cached copy had aged past the 24 h window.", "resolution_source_url": step.get("url") or "", "resolved_at": datetime.now(timezone.utc).isoformat(), "resolved_by": "score_refresh"} await db_.corrections.update_one({"ticket_id": t["ticket_id"]}, {"$set": upd}) await corrections._log("correction_resolved", t["ticket_id"], {"outcome": "corrected", "meaning": corrections.OUTCOMES["corrected"], "note": upd["resolution"], "source_url": upd["resolution_source_url"] or None, "resolved_by": "score_refresh"}) resolved += 1 await asyncio.sleep(1.0) except Exception as e: failed += 1 logger.warning(f"score_refresh {d['company']}: {e!r}") out = {"candidates": len(docs), "refreshed": refreshed, "failed": failed, "tickets_resolved": resolved, "at": datetime.now(timezone.utc).isoformat()} await db_.score_refresh_runs.insert_one(dict(out)) logger.info(f"score_refresh: {out}") if not await db_.jobs.find_one({"type": JOB, "status": "pending"}): await scheduler_mod.enqueue(db_, JOB, {}, run_at=datetime.now(timezone.utc) + timedelta(hours=EVERY_HOURS)) return out