{"record":{"id":"c745f1c6e34b13bd","repo":"666ghj/MiroFish","slug":"chart-coordinates-must-be-ordered","errorCode":null,"errorMessage":"chart coordinates must be ordered","messagePattern":"chart coordinates must be ordered","errorType":"exception","errorClass":"StarHistoryError","httpStatus":null,"severity":"error","filePath":"scripts/star_history.py","lineNumber":788,"sourceCode":"    if value > 0:\n        return 1\n    return 0\n\n\ndef _monotone_x_path(points: Sequence[tuple[float, float]]) -> str:\n    \"\"\"Return a D3 curveMonotoneX-equivalent SVG path.\n\n    D3 uses Steffen monotonic interpolation: interior tangents are limited so a\n    smooth cubic cannot overshoot a monotonic run. This small implementation\n    keeps the Star History curve shape without adding a JavaScript dependency.\n    \"\"\"\n\n    normalized: list[tuple[float, float]] = []\n    for x, y in points:\n        if not math.isfinite(x) or not math.isfinite(y):\n            raise StarHistoryError(\"chart coordinates must be finite\")\n        if normalized and x < normalized[-1][0]:\n            raise StarHistoryError(\"chart coordinates must be ordered\")\n        if normalized and x == normalized[-1][0]:\n            normalized[-1] = (x, y)\n        else:\n            normalized.append((x, y))\n\n    if not normalized:\n        return \"\"\n\n    start_x, start_y = normalized[0]\n    commands = [f\"M{_format_float(start_x)},{_format_float(start_y)}\"]\n    if len(normalized) == 1:\n        return \"\".join(commands)\n    if len(normalized) == 2:\n        end_x, end_y = normalized[1]\n        commands.append(f\"L{_format_float(end_x)},{_format_float(end_y)}\")\n        return \"\".join(commands)\n\n    secants = [","sourceCodeStart":770,"sourceCodeEnd":806,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/scripts/star_history.py#L770-L806","documentation":"Raised in `_monotone_x_path` (scripts/star_history.py:788) when a chart point's x is strictly smaller than the previous point's x. Monotone-X interpolation requires non-decreasing x; equal x values are tolerated (the later point replaces the earlier one), but a regression means the input sequence zig-zags and the cubic tangents would be undefined.","triggerScenarios":"`render_svg` building points whose x (a monotonic function of the point's datetime) decreases — e.g. state where a reconstruction `daily` entry is dated after a later snapshot, or direct callers passing unsorted tuples. Duplicate x is fine (`x == normalized[-1][0]` replaces the last point); only `x < normalized[-1][0]` raises.","commonSituations":"State files whose `reconstruction.daily` dates and `snapshots[].at` overlap out of order (usually from hand-merging two state files); forks that reorder `_chart_points`; direct use of the helper with unsorted data.","solutions":["Sort the points before calling the path builder: `pts = sorted(pts, key=lambda p: p[0])` — duplicate-x entries are then handled by the function itself.","If `render_svg` raises it, inspect the state's `reconstruction.daily` dates and `snapshots[].at` for out-of-order entries and regenerate the state from a clean backfill.","Avoid hand-merging state files from different runs; use the library's update path which keeps snapshots monotonic.","Add a quick assertion when preparing state: `dates = [p['date'] for p in state['reconstruction']['daily']]; assert dates == sorted(dates)`."],"exampleFix":"# before: unsorted points\npath = _monotone_x_path(points)\n\n# after: order guaranteed (duplicate x kept, last wins)\npoints = sorted(points, key=lambda p: p[0])\npath = _monotone_x_path(points)","handlingStrategy":"validation","validationCode":"def non_decreasing_x(points) -> bool:\n    return all(points[i][0] <= points[i + 1][0] for i in range(len(points) - 1))\n\nif not non_decreasing_x(points):\n    points = sorted(points, key=lambda p: p[0])\npath = _monotone_x_path(points)","typeGuard":null,"tryCatchPattern":"try:\n    svg = render_svg(state, theme)\nexcept StarHistoryError as exc:\n    if \"must be ordered\" in str(exc):\n        raise ValueError(\"state has out-of-order chart points; regenerate state\") from exc\n    raise","preventionTips":["Keep daily dates and snapshot timestamps ordered: use the library's update functions instead of merging state files by hand.","When calling the path helper directly, sort by x first — equal x values are handled (last wins), decreasing x is not.","After any state manipulation, assert daily dates are sorted: `d == sorted(d)`."],"tags":["svg","chart","rendering","ordering","monotonicity"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}