PDO::ERRMODE_EXCEPTION, PDO::ATTR_TIMEOUT => 3]); $db->exec('CREATE TABLE IF NOT EXISTS admin.user_feedback (id SERIAL PRIMARY KEY, ts TIMESTAMPTZ DEFAULT NOW(), session_id TEXT, trace_id INTEGER, intent TEXT, score INTEGER, correction TEXT, user_context TEXT)'); return $db; } catch (Exception $e) { return null; } } if ($action === 'submit') { $db = get_db(); if (!$db) { // Fallback to SQLite $sf = '/opt/weval-ops/rlhf-feedback.sqlite'; $s = new SQLite3($sf); $s->exec('CREATE TABLE IF NOT EXISTS feedback(id INTEGER PRIMARY KEY AUTOINCREMENT, ts TEXT, session_id TEXT, intent TEXT, score INTEGER, correction TEXT)'); $s->prepare('INSERT INTO feedback(ts, session_id, intent, score, correction) VALUES(?,?,?,?,?)') ->bindValue(1, date('c')) ->bindValue(2, $_POST['session_id'] ?? 'anon') ->bindValue(3, $_POST['intent'] ?? '') ->bindValue(4, (int)($_POST['score'] ?? 0)) ->bindValue(5, $_POST['correction'] ?? '') ->execute(); echo json_encode(['ok'=>true, 'storage'=>'sqlite_fallback']); exit; } $stmt = $db->prepare('INSERT INTO admin.user_feedback(session_id, trace_id, intent, score, correction, user_context) VALUES(?,?,?,?,?,?)'); $stmt->execute([ $_POST['session_id'] ?? 'anon', (int)($_POST['trace_id'] ?? 0), $_POST['intent'] ?? '', (int)($_POST['score'] ?? 0), $_POST['correction'] ?? '', $_POST['context'] ?? '', ]); echo json_encode(['ok'=>true, 'storage'=>'postgres']); exit; } if ($action === 'stats') { $db = get_db(); if ($db) { $r = $db->query('SELECT intent, AVG(score) as avg_score, COUNT(*) as n FROM admin.user_feedback GROUP BY intent ORDER BY n DESC LIMIT 20'); echo json_encode(['ok'=>true, 'stats'=>$r->fetchAll(PDO::FETCH_ASSOC)]); } else { echo json_encode(['ok'=>false, 'error'=>'db unreachable']); } exit; } echo json_encode(['ok'=>false, 'actions'=>['submit','stats']]);