paperclipai/paperclip · error · ValueError

Report refresh has no reliable measurement date

Error message

Report refresh has no reliable measurement date

What it means

When a campaign carries a reportRevision (a presentation-only refresh of an earlier report), the page must still show the original measurement date. summarize() therefore requires revision.sourceGeneratedAt to be a string; a revision without it would silently drift the displayed date to the refresh time, so it raises ValueError.

Solutions

  1. Fix the report-refresh publisher to always include sourceGeneratedAt (ISO string of the original campaign's generatedAt).
  2. Backfill existing history entries: set reportRevision.sourceGeneratedAt from the source campaign's generatedAt.
  3. If a revision is genuinely unreconstructable, drop the reportRevision block so the script falls back to campaign['generatedAt'].
  4. In fixtures, serialize the timestamp as an ISO string, not an epoch number.

Example fix

# before
reportRevision = {"renderedAt": "2026-09-18T10:00:00Z", "sourceCampaignId": "c-1"}
# after
reportRevision = {"renderedAt": "2026-09-18T10:00:00Z", "sourceCampaignId": "c-1", "sourceGeneratedAt": "2026-09-10T08:00:00Z"}
Defensive patterns

Strategy: validation

Validate before calling

def revision_has_source_date(campaign):
    rev = campaign.get("reportRevision") or {}
    return (not rev) or isinstance(rev.get("sourceGeneratedAt"), str)

Type guard

def refresh_is_traceable(campaign):
    rev = campaign.get("reportRevision")
    return not rev or isinstance(rev.get("sourceGeneratedAt"), str)

Try / catch

try:
    values = summarize(history, kind)
except ValueError as e:
    if str(e) == "Report refresh has no reliable measurement date":
        log.error("reportRevision without sourceGeneratedAt; backfill before building")
        raise
    raise

Prevention

When it happens

Trigger: A campaign entry with a non-empty reportRevision object whose sourceGeneratedAt is missing, null, or a non-string (e.g. a number timestamp) — typically a report-refresh writer that copied the revision block but omitted the original generation timestamp.

Common situations: Report refresh tooling upgraded without backfilling sourceGeneratedAt; hand-edited fixtures adding reportRevision with only renderedAt/sourceCampaignId; timestamp serialized as epoch int instead of ISO string.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/a1db7f208941ad18. Report an issue: GitHub.

Appendix: source

Thrown at scripts/evals-hub/build.py:61

def display_date(value):
    date = datetime.fromisoformat(value.replace("Z", "+00:00"))
    if date.tzinfo is None:
        raise ValueError("History date must include a timezone")
    return date.astimezone(timezone.utc).strftime("%d %b %Y · %H:%M UTC")


def summarize(history, kind):
    prefix, schema = SYSTEMS[kind]
    campaign = latest(history, schema)
    totals = campaign["totals"] if kind == "runner" else campaign
    passed, selected = totals["passed"], totals["selected"]
    if any(type(n) is not int for n in (passed, selected)) or not 0 <= passed <= selected:
        raise ValueError("Invalid campaign counts")
    if type(campaign.get("complete")) is not bool:
        raise ValueError("Missing campaign coverage flag")
    revision = campaign.get("reportRevision", {})
    if revision and not isinstance(revision.get("sourceGeneratedAt"), str):
        raise ValueError("Report refresh has no reliable measurement date")
    measured = revision.get("sourceGeneratedAt", campaign["generatedAt"])
    return {
        "url": public_report(campaign["publicUrl"], prefix),
        "date": html.escape(display_date(measured)),
        "counts": f"{passed} / {selected} selected cases passed",
        "coverage": "Complete campaign" if campaign["complete"] else "Partial campaign",
        "id": html.escape(revision.get("sourceCampaignId", campaign["campaignId"])),
    }


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--output", type=Path, required=True)
    parser.add_argument("--history-dir", type=Path, help="Use saved public history JSON files for offline verification")
    parser.add_argument("--docs-ref", default="master", help="Git ref containing doc/evals.md")
    args = parser.parse_args()
    values = {}
    for kind, (prefix, _) in SYSTEMS.items():

View on GitHub (pinned to 3f1d897a7c)