/* ai_vs_irctc.js — irctc1 truth vs OpenAI, with origin->boarding day-offset correction.
irctc1 reports running days AT the from-station. OpenAI reports ORIGIN days.
We shift OpenAI's answer by the train's stored day_offset at the from-station,
then compare. Trains not in our timetable (offset unknown) are shown but not scored. */
(function () {
var ORDER = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
var IDX = { Mon: 0, Tue: 1, Wed: 2, Thu: 3, Fri: 4, Sat: 5, Sun: 6 };
function esc(s) {
return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) {
return { '&': '&', '<': '<', '>': '>', '"': '"' }[c];
});
}
function shiftDays(days, off) {
off = ((off % 7) + 7) % 7;
var set = {};
days.forEach(function (d) { if (d in IDX) set[ORDER[(IDX[d] + off) % 7]] = 1; });
return ORDER.filter(function (d) { return set[d]; });
}
document.addEventListener('DOMContentLoaded', function () {
var form = document.getElementById('cmp-form');
if (!form) return;
form.addEventListener('submit', function (ev) {
ev.preventDefault();
var btn = document.getElementById('cmp-run');
var orig = btn.textContent;
var csrf = form.querySelector('input[name=csrf]').value;
var model = (document.getElementById('model').value || '').trim() || 'gpt-5.5';
var max = parseInt(document.getElementById('max').value, 10) || 6;
var tbody = document.getElementById('cmp-rows');
var summary = document.getElementById('cmp-summary');
var box = document.getElementById('cmp-results');
tbody.innerHTML = ''; summary.innerHTML = ''; box.style.display = 'block';
btn.disabled = true; btn.textContent = 'Fetching irctc1 truth…';
var p = new URLSearchParams({
action: 'irctc', csrf: csrf,
from: document.getElementById('from').value,
to: document.getElementById('to').value,
date: document.getElementById('date').value
});
fetch(window.location.pathname, { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' }, body: p })
.then(function (r) { if (!r.ok) throw new Error('irctc HTTP ' + r.status); return r.json(); })
.then(function (data) {
if (data.err) throw new Error(data.err);
var trains = (data.trains || []).slice(0, max);
if (!trains.length) { summary.innerHTML = errBox('irctc1 returned no trains for this route/date.'); reset(); return; }
var rows = [];
trains.forEach(function (t) {
var tr = document.createElement('tr');
tr.style.borderBottom = '1px solid #eee'; tr.style.verticalAlign = 'top';
tr.innerHTML = '
' + esc(t.tn) + ' | '
+ '' + esc(t.name) + ' | '
+ '' + esc((t.days || []).join(',') || '(none)') + ' | '
+ '…querying OpenAI… | ';
tbody.appendChild(tr);
rows.push({ t: t, tr: tr });
});
var i = 0, pass = 0, fail = 0, errc = 0, judged = 0;
function next() {
if (i >= rows.length) {
var cls = (fail > 0 || errc > 0) ? 'flash-error' : 'flash-info';
var m = 'PASS ' + pass + ' \u00b7 FAIL ' + fail + ' \u00b7 ERROR ' + errc + '. ';
if (judged === 0) m += 'Nothing scored (no truth or no offset data).';
else if (fail > 0) m += 'After offset correction, OpenAI still disagreed with irctc1 on ' + fail + ' of ' + judged + ' scored train(s) — those are real differences. Per your rule, irctc1 wins.';
else if (errc > 0) m += 'OpenAI matched on all ' + judged + ' scored, but some calls errored/timed out — rerun those (try gpt-5.4-mini) before concluding.';
else m += 'OpenAI matched irctc1 on all ' + judged + ' scored (offset-corrected). Widen across routes and include obscure/weekly trains before trusting it.';
summary.innerHTML = '' + m + '
';
reset(); return;
}
var row = rows[i++]; var t = row.t; var tr = row.tr;
var truth = (t.days || []).join(',');
btn.textContent = 'OpenAI: ' + t.tn + ' (' + i + '/' + rows.length + ')…';
var b = new URLSearchParams({ action: 'one', csrf: csrf, train: t.tn, model: model });
fetch(window.location.pathname, { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' }, body: b })
.then(function (r) {
if (!r.ok) { var x = r.status === 504 ? ' (gateway timeout — try gpt-5.4-mini)' : ''; throw new Error('HTTP ' + r.status + x); }
return r.json();
})
.then(function (d) {
if (d.err) { errc++; fillErr(tr, d.err); return; }
var originDays = d.got || [];
var off = t.offset;
var oCell, match;
if (off === null || off === undefined) {
oCell = esc(originDays.join(',') || '(none)') + ' (origin; no offset data — not scored)';
match = null;
} else {
var shifted = off === 0 ? originDays : shiftDays(originDays, off);
oCell = (off === 0)
? esc(originDays.join(',') || '(none)')
: esc(originDays.join(',') || '(none)') + ' \u2192 ' + esc(shifted.join(',') || '(none)') + ' (off +' + off + ')';
match = truth === '' ? null : (shifted.join(',') === truth);
}
if (match === true) pass++; else if (match === false) fail++;
if (match !== null) judged++;
fillResult(tr, oCell, match, d.conf, d.src);
})
.catch(function (e) { errc++; fillErr(tr, e.message); })
.then(next);
}
next();
})
.catch(function (e) { summary.innerHTML = errBox(e.message); reset(); });
function reset() { btn.disabled = false; btn.textContent = orig; }
function errBox(msg) { return '' + esc(msg) + '
'; }
function fillResult(tr, openaiHtml, match, conf, src) {
var color = match === true ? '#197b3a' : (match === false ? '#c0392b' : '#888');
var label = match === true ? 'PASS' : (match === false ? 'FAIL' : '—');
while (tr.children.length > 3) tr.removeChild(tr.children[3]);
tr.insertAdjacentHTML('beforeend',
'' + openaiHtml + ' | '
+ '' + label + ' | '
+ '' + esc(conf) + ' | '
+ '' + esc(src) + ' | ');
}
function fillErr(tr, msg) {
while (tr.children.length > 3) tr.removeChild(tr.children[3]);
tr.insertAdjacentHTML('beforeend', '\u26a0 ' + esc(msg) + ' | ');
}
});
});
})();