{"record":{"id":"2aa5795eb9dcae05","repo":"666ghj/MiroFish","slug":"reconstruction-must-contain-only-completed-utc-dat","errorCode":null,"errorMessage":"reconstruction must contain only completed UTC dates","messagePattern":"reconstruction must contain only completed UTC dates","errorType":"exception","errorClass":"StarHistoryError","httpStatus":null,"severity":"error","filePath":"scripts/star_history.py","lineNumber":419,"sourceCode":"        _expect_keys(raw_point, {\"date\", \"stars\"}, \"reconstruction point\")\n        raw_date = raw_point[\"date\"]\n        if not isinstance(raw_date, str):\n            raise StarHistoryError(\"reconstruction date must be a string\")\n        try:\n            point_day = date.fromisoformat(raw_date)\n        except ValueError as exc:\n            raise StarHistoryError(\"reconstruction date is invalid\") from exc\n        if point_day.isoformat() != raw_date:\n            raise StarHistoryError(\"reconstruction date is not canonical\")\n        stars = _strict_non_negative_int(raw_point[\"stars\"], \"reconstruction stars\")\n        if index == 0 and stars <= 0:\n            raise StarHistoryError(\"first reconstruction point must have stars\")\n        if previous_day is not None and point_day <= previous_day:\n            raise StarHistoryError(\"reconstruction dates must be strictly increasing\")\n        if index > 0 and stars <= previous_stars:\n            raise StarHistoryError(\"reconstruction stars must be strictly increasing\")\n        if point_day >= generated_at.date():\n            raise StarHistoryError(\"reconstruction must contain only completed UTC dates\")\n        previous_day = point_day\n        previous_stars = stars\n\n    snapshots = state[\"snapshots\"]\n    if not isinstance(snapshots, list):\n        raise StarHistoryError(\"snapshots must be a list\")\n    previous_snapshot: datetime | None = None\n    first_snapshot: datetime | None = None\n    for raw_snapshot in snapshots:\n        if not isinstance(raw_snapshot, dict):\n            raise StarHistoryError(\"snapshot must be an object\")\n        _expect_keys(raw_snapshot, {\"at\", \"stars\"}, \"snapshot\")\n        snapshot_at = _parse_state_timestamp(raw_snapshot[\"at\"], \"snapshot.at\")\n        _strict_non_negative_int(raw_snapshot[\"stars\"], \"snapshot stars\")\n        if previous_snapshot is not None and snapshot_at <= previous_snapshot:\n            raise StarHistoryError(\"snapshot timestamps must be strictly increasing\")\n        if first_snapshot is None:\n            first_snapshot = snapshot_at","sourceCodeStart":401,"sourceCodeEnd":437,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/scripts/star_history.py#L401-L437","documentation":"Raised by validate_state() when a daily point's date is >= the generated_at timestamp's date. Reconstruction may only cover fully completed UTC days; the day of generation is still in progress, so any point on or after that day would be a partial count.","triggerScenarios":"validate_state(state) where a daily date equals or exceeds the calendar date of reconstruction.generated_at, e.g. generated_at '2024-01-02T00:00:00Z' with a daily point dated '2024-01-02'.","commonSituations":"Timezone bugs where generated_at was recorded in local time ahead of UTC; writing today's partial count into daily; backfilling dates incorrectly after downtime.","solutions":["Remove daily points dated on/after generated_at's UTC date","Ensure generated_at is written as the UTC time reconstruction ran, not a future or local time","Re-run reconstruction so the in-progress day is naturally excluded"],"exampleFix":"// before\n\"generated_at\": \"2024-01-02T00:00:00Z\",\n\"daily\": [..., {\"date\": \"2024-01-02\", \"stars\": 300}]\n// after\n\"generated_at\": \"2024-01-02T00:00:00Z\",\n\"daily\": [..., {\"date\": \"2024-01-01\", \"stars\": 295}]  // last completed UTC day only","handlingStrategy":"validation","validationCode":"from datetime import datetime, UTC\ngen_date = datetime.strptime(\n    state[\"reconstruction\"][\"generated_at\"], \"%Y-%m-%dT%H:%M:%SZ\"\n).replace(tzinfo=UTC).date()\ndaily = [p for p in daily if p[\"date\"] < gen_date.isoformat()]","typeGuard":"def only_completed_dates(daily: list, generated_at: str) -> bool:\n    gen = datetime.strptime(generated_at, \"%Y-%m-%dT%H:%M:%SZ\").date()\n    return all(p[\"date\"] < gen.isoformat() for p in daily)","tryCatchPattern":"try:\n    validate_state(state)\nexcept StarHistoryError as exc:\n    if \"only completed UTC dates\" in str(exc):\n        # drop points dated on/after generated_at's UTC date, re-validate\n        ...","preventionTips":["Always write generated_at in UTC","Exclude the in-progress day when aggregating daily counts","Treat local-time generated_at values as a bug, not a workaround"],"tags":["validation","utc","data-integrity"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}