From 293b16442521de3f5c370b95d1c49d0675d41ff8 Mon Sep 17 00:00:00 2001 From: Michael Feng Date: Sat, 30 May 2026 21:49:41 +1000 Subject: [PATCH] =?UTF-8?q?fix:=20/analyze/compare=20500=20=E2=80=94=20def?= =?UTF-8?q?ault=20created=5Fat=5Fiso=20when=20response=20has=20no=20meta?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit insert_run derived the NOT NULL created_at_iso from response.meta.timestamp_iso, which analyze() sets but compare() does not — so every /analyze/compare hit failed with 'NOT NULL constraint failed: runs.created_at_iso' (HTTP 500). Fall back to the current UTC time so persistence never depends on response shape. Verified locally: /analyze/compare -> 200 and the run persists to /history. Co-Authored-By: Claude Opus 4.8 (1M context) --- services/ml_api/db.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/services/ml_api/db.py b/services/ml_api/db.py index bc64dfe..63edd5d 100644 --- a/services/ml_api/db.py +++ b/services/ml_api/db.py @@ -2,6 +2,7 @@ import json import sqlite3 +from datetime import datetime, timezone from pathlib import Path from typing import Any, Dict, List, Optional @@ -37,13 +38,22 @@ def insert_run( variant_text: Optional[str] = None, ) -> None: conn = CONN + # created_at_iso is NOT NULL. analyze() responses carry meta.timestamp_iso, + # but compare() (and any future mode) may not — fall back to the current UTC + # time so persistence never depends on the response shape. + meta = response.get("meta", {}) + created_at_iso = ( + meta.get("timestamp_iso") + or meta.get("timestamp") + or datetime.now(timezone.utc).isoformat() + ) conn.execute( """ INSERT INTO runs (created_at_iso, mode, baseline_text, variant_text, response_json) VALUES (?, ?, ?, ?, ?) """, ( - response.get("meta", {}).get("timestamp_iso") or response.get("meta", {}).get("timestamp"), + created_at_iso, mode, baseline_text, variant_text,