diff --git a/backend/app/modules/results/service.py b/backend/app/modules/results/service.py
index f52ffb6..86637f5 100644
--- a/backend/app/modules/results/service.py
+++ b/backend/app/modules/results/service.py
@@ -183,7 +183,7 @@ def parse_training_curve(path: Path, max_points: int = 300) -> dict | None:
y = _as_float(row.get(name))
if y is not None:
points.append({"x": x, "y": y})
- if len(points) >= 2:
+ if points:
values = [point["y"] for point in points]
series.append({"name": name, "points": points, "last": values[-1], "min": min(values), "max": max(values)})
if not series:
diff --git a/backend/tests/test_results_service.py b/backend/tests/test_results_service.py
index 4f5ad7a..56290e3 100644
--- a/backend/tests/test_results_service.py
+++ b/backend/tests/test_results_service.py
@@ -28,6 +28,26 @@ def test_parse_yolo_results_curve(tmp_path: Path):
assert all("lr/" not in name for name in names)
+def test_parse_single_epoch_yolo_results_curve(tmp_path: Path):
+ csv_path = tmp_path / "results.csv"
+ csv_path.write_text(
+ "\n".join(
+ [
+ "epoch,time,train/box_loss,train/seg_loss,metrics/mAP50(M),lr/pg0",
+ "1,1.0,1.5,3.0,0.12,0.001",
+ ]
+ ),
+ encoding="utf-8",
+ )
+
+ curve = parse_training_curve(csv_path)
+
+ assert curve is not None
+ assert curve["row_count"] == 1
+ seg_loss = next(item for item in curve["series"] if item["name"] == "train/seg_loss")
+ assert seg_loss["points"] == [{"x": 1.0, "y": 3.0}]
+
+
def test_results_api_honors_limit(monkeypatch):
calls = {}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index 034e15f..1e58846 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -1764,17 +1764,24 @@ function MiniCurvePlot({ series }: { series: CurveSeries[] }) {
{[0.25, 0.5, 0.75].map((tick) => (
))}
- {series.map((item, index) => (
- `${scaleX(point.x).toFixed(2)},${scaleY(point.y).toFixed(2)}`).join(" ")}
- fill="none"
- stroke={curveColor(index)}
- strokeWidth="2.4"
- strokeLinejoin="round"
- strokeLinecap="round"
- />
- ))}
+ {series.map((item, index) => {
+ const color = curveColor(index);
+ const scaled = item.points.map((point) => ({ x: scaleX(point.x), y: scaleY(point.y) }));
+ if (scaled.length === 1) {
+ return ;
+ }
+ return (
+ `${point.x.toFixed(2)},${point.y.toFixed(2)}`).join(" ")}
+ fill="none"
+ stroke={color}
+ strokeWidth="2.4"
+ strokeLinejoin="round"
+ strokeLinecap="round"
+ />
+ );
+ })}
);
}