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
- Fix the report-refresh publisher to always include sourceGeneratedAt (ISO string of the original campaign's generatedAt).
- Backfill existing history entries: set reportRevision.sourceGeneratedAt from the source campaign's generatedAt.
- If a revision is genuinely unreconstructable, drop the reportRevision block so the script falls back to campaign['generatedAt'].
- 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
- Always copy sourceGeneratedAt (ISO string of the original generatedAt) into reportRevision when refreshing a report.
- Serialize timestamps as ISO strings, never epoch numbers.
- Backfill existing refreshed entries from their source campaign's generatedAt.
- Add a publish-time check: reportRevision present implies sourceGeneratedAt present.
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
- History has no latest campaign
- Missing campaign coverage flag
- CreateOS requires a shape from its shape catalog.
- History date must include a timezone
- Invalid campaign counts
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)