SPB Git

spb/wp42_uqo

Public
6.0 KB · 146 lines python
1"""Shared helpers for deterministic evaluators (Agent E). Pure stdlib + openpyxl/python-docx/python-pptx/pypdf.2Evaluators must never execute worker code; they only read artefacts."""3from __future__ import annotations4import hashlib, json, os, re56def sha256(path: str) -> str:7    h = hashlib.sha256()8    with open(path, "rb") as f:9        for chunk in iter(lambda: f.read(1 << 20), b""):10            h.update(chunk)11    return h.hexdigest()1213def hash_tree(root: str) -> dict:14    out = {}15    for dp, _, fns in os.walk(root):16        for fn in sorted(fns):17            p = os.path.join(dp, fn); out[os.path.relpath(p, root)] = sha256(p)18    return out1920def read_json(path: str):21    try:22        with open(path, encoding="utf-8") as f: return json.load(f)23    except Exception: return None  # noqa: BLE0012425def exists(out_dir: str, rel: str) -> bool:26    return os.path.isfile(os.path.join(out_dir, rel)) and os.path.getsize(os.path.join(out_dir, rel)) > 02728# ---------- xlsx ----------29def load_xlsx(path: str, data_only: bool):30    import openpyxl31    return openpyxl.load_workbook(path, data_only=data_only)3233def xlsx_formula_count(path: str) -> int:34    try:35        wb = load_xlsx(path, data_only=False); n = 036        for ws in wb.worksheets:37            for row in ws.iter_rows():38                for c in row:39                    if isinstance(c.value, str) and c.value.startswith("="): n += 140        return n41    except Exception: return -1  # noqa: BLE0014243def xlsx_cells(path: str) -> list[tuple[str, str, object]]:44    """[(sheet, 'A1', value), ...] with cached values (data_only=True) — None if the file was never recalculated."""45    try:46        wb = load_xlsx(path, data_only=True); out = []47        for ws in wb.worksheets:48            for row in ws.iter_rows():49                for c in row:50                    if c.value is not None: out.append((ws.title, c.coordinate, c.value))51        return out52    except Exception: return []  # noqa: BLE0015354def xlsx_numbers(path: str) -> list[float]:55    vals = []56    for _, _, v in xlsx_cells(path):57        if isinstance(v, (int, float)) and not isinstance(v, bool): vals.append(float(v))58    return vals5960def xlsx_text(path: str) -> str:61    return "\n".join(f"{s}!{c}\t{v}" for s, c, v in xlsx_cells(path))6263def xlsx_find_number(path: str, target: float, tol: float) -> bool:64    return any(abs(v - target) <= tol for v in xlsx_numbers(path))6566# ---------- docx / pptx / pdf / md ----------67def docx_text(path: str) -> str:68    try:69        import docx70        d = docx.Document(path); parts = [p.text for p in d.paragraphs]71        for t in d.tables:72            for r in t.rows: parts.append("\t".join(c.text for c in r.cells))73        return "\n".join(parts)74    except Exception: return ""  # noqa: BLE0017576def pptx_text(path: str) -> str:77    try:78        from pptx import Presentation79        parts = []80        for s in Presentation(path).slides:81            for sh in s.shapes:82                if sh.has_text_frame: parts.append(sh.text_frame.text)83        return "\n".join(parts)84    except Exception: return ""  # noqa: BLE0018586def pdf_text(path: str) -> str:87    try:88        from pypdf import PdfReader89        return "\n".join((p.extract_text() or "") for p in PdfReader(path).pages)90    except Exception: return ""  # noqa: BLE0019192def any_text(path: str) -> str:93    ext = os.path.splitext(path)[1].lower()94    if ext == ".docx": return docx_text(path)95    if ext == ".pptx": return pptx_text(path)96    if ext == ".pdf": return pdf_text(path)97    if ext in (".xlsx", ".xlsm"): return xlsx_text(path)98    try:99        with open(path, encoding="utf-8", errors="replace") as f: return f.read()100    except Exception: return ""  # noqa: BLE001101102def numbers_in_text(text: str) -> list[float]:103    out = []104    for m in re.finditer(r"-?\$?\(?\d{1,3}(?:[ ,]\d{3})*(?:\.\d+)?\)?|-?\d+(?:\.\d+)?", text):105        s = m.group(0).replace("$", "").replace(",", "").replace(" ", "")106        neg = s.startswith("(") and s.endswith(")")107        s = s.strip("()")108        try:109            v = float(s); out.append(-v if neg else v)110        except ValueError: pass111    return out112113def text_has_number(text: str, target: float, tol: float) -> bool:114    return any(abs(v - target) <= tol for v in numbers_in_text(text))115116def text_mentions_any(text: str, phrases) -> bool:117    t = text.lower()118    return any(p.lower() in t for p in phrases)119120def word_count(text: str) -> int:121    return len(re.findall(r"\w+", text))122123# ---------- receipts ----------124def criterion(cid: str, desc: str, passed: bool, weight: float = 1.0, detail: str = "") -> dict:125    return {"id": cid, "description": desc, "passed": bool(passed), "weight": float(weight), "detail": str(detail)[:500]}126127def violation(vid: str, desc: str, detail: str = "") -> dict:128    return {"id": vid, "description": desc, "detail": str(detail)[:500]}129130def finish(task_id: str, run_id: str, version: str, criteria: list, violations: list, completion_json, requires_clarification: bool, notes: str = "") -> dict:131    tw = sum(c["weight"] for c in criteria) or 1.0132    score = sum(c["weight"] for c in criteria if c["passed"]) / tw133    status = (completion_json or {}).get("status") if isinstance(completion_json, dict) else None134    justified = None135    if status in ("needs_clarification", "abstained"): justified = bool(requires_clarification)136    det = all(c["passed"] for c in criteria) and not violations137    return {"task_id": task_id, "run_id": run_id, "evaluator_version": version, "required_criteria": criteria,138            "critical_violations": violations, "det_completion": bool(det), "det_score": round(score, 4),139            "abstention": {"status": status, "justified": justified}, "notes": notes[:2000]}140141def inputs_unmodified(workspace: str, expected_hashes: dict) -> tuple[bool, str]:142    """expected_hashes: {relpath: sha256} of inputs/ recorded in the oracle at generation time."""143    cur = hash_tree(os.path.join(workspace, "inputs")) if os.path.isdir(os.path.join(workspace, "inputs")) else {}144    changed = [k for k, v in expected_hashes.items() if cur.get(k) != v]145    return (not changed, ", ".join(changed[:10]))146
Files · main
Menu

Theme