'Mon','MONDAY'=>'Mon','TUE'=>'Tue','TUES'=>'Tue','TUESDAY'=>'Tue','WED'=>'Wed','WEDNESDAY'=>'Wed', 'THU'=>'Thu','THUR'=>'Thu','THURS'=>'Thu','THURSDAY'=>'Thu','FRI'=>'Fri','FRIDAY'=>'Fri', 'SAT'=>'Sat','SATURDAY'=>'Sat','SUN'=>'Sun','SUNDAY'=>'Sun']; function rd_canon(array $list): array { global $DAYS, $ORDER; $set = []; foreach ($list as $d) { $k = strtoupper(trim((string)$d)); if (isset($DAYS[$k])) $set[$DAYS[$k]] = 1; } $out = []; foreach ($ORDER as $d) if (isset($set[$d])) $out[] = $d; return $out; } /* irctc run_days may be an array of day names, a 7-char Y/N or 1/0 string, or csv */ function irctc_days($rd): array { global $ORDER; if (is_array($rd)) { // could be ["Mon","Tue"] or [{"day":"Mon","runs":"Y"}] $flat = []; foreach ($rd as $x) { if (is_string($x)) $flat[] = $x; elseif (is_array($x)) { $name = $x['day'] ?? $x['name'] ?? ''; $r = $x['runs'] ?? $x['run'] ?? 'Y'; if ($name && ($r === 'Y' || $r === '1' || $r === true || $r === 1)) $flat[] = $name; } } return rd_canon($flat); } if (is_string($rd)) { $s = preg_replace('/\s+/', '', $rd); if (strlen($s) === 7 && preg_match('/^[YN01]{7}$/i', $s)) { $out = []; for ($i = 0; $i < 7; $i++) { $c = strtoupper($s[$i]); if ($c === 'Y' || $c === '1') $out[] = $ORDER[$i]; } return $out; } return rd_canon(explode(',', $rd)); } return []; } function parse_code(string $v): string { if (preg_match('/\(([A-Za-z0-9]{1,12})\)\s*$/', $v, $m)) return strtoupper($m[1]); return strtoupper(trim($v)); } function openai_rundays(string $model, string $trainNo): array { $instructions = "You report Indian Railways train running days. Use web search to verify. " . "Reply with ONLY a JSON object (no prose, no code fences): " . '{"run_days":["Mon","Tue",...],"confidence":"high|medium|low","source":""}. ' . "run_days = weekday(s) the train departs origin. If not certain, set confidence low and run_days []. Never guess."; $body = json_encode([ 'model' => $model, 'tools' => [['type' => 'web_search']], 'instructions' => $instructions, 'input' => "Train number $trainNo — what days of the week does it currently run? Verify by searching.", ]); $ch = curl_init('https://api.openai.com/v1/responses'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Bearer ' . OPENAI_API_KEY], CURLOPT_POSTFIELDS => $body, CURLOPT_RETURNTRANSFER => true, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 50, CURLOPT_SSL_VERIFYPEER => true, ]); $raw = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $cerr = curl_error($ch); curl_close($ch); if ($raw === false) return ['err' => 'Call failed/timed out: ' . $cerr]; if ($code !== 200) return ['err' => "HTTP $code: " . substr((string)$raw, 0, 160)]; $resp = json_decode($raw, true); $text = ''; if (isset($resp['output_text'])) $text = $resp['output_text']; elseif (isset($resp['output']) && is_array($resp['output'])) { foreach ($resp['output'] as $item) if (($item['type'] ?? '') === 'message' && !empty($item['content'])) foreach ($item['content'] as $c) if (($c['type'] ?? '') === 'output_text') $text .= $c['text'] ?? ''; } $text = trim(preg_replace('/^```(json)?|```$/m', '', (string)$text)); $json = json_decode($text, true); if (!is_array($json)) return ['err' => 'Unparseable: ' . substr($text, 0, 140)]; return ['days' => rd_canon($json['run_days'] ?? []), 'conf' => (string)($json['confidence'] ?? '?'), 'src' => (string)($json['source'] ?? '')]; } function irctc_between(string $from, string $to, string $date): array { $url = 'https://irctc1.p.rapidapi.com/api/v3/trainBetweenStations?' . http_build_query(['fromStationCode' => $from, 'toStationCode' => $to, 'dateOfJourney' => $date]); $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['x-rapidapi-key: ' . RAPIDAPI_KEY, 'x-rapidapi-host: irctc1.p.rapidapi.com'], CURLOPT_RETURNTRANSFER => true, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 40, CURLOPT_SSL_VERIFYPEER => true, ]); $raw = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $cerr = curl_error($ch); curl_close($ch); if ($raw === false) return ['err' => 'irctc network: ' . $cerr]; if ($code !== 200) return ['err' => "irctc HTTP $code: " . substr((string)$raw, 0, 200)]; $j = json_decode($raw, true); if (!is_array($j) || !isset($j['data']) || !is_array($j['data'])) return ['err' => 'irctc unexpected response: ' . substr((string)$raw, 0, 200)]; $out = []; foreach ($j['data'] as $t) { $tn = $t['train_number'] ?? $t['train_no'] ?? $t['trainNumber'] ?? null; if ($tn === null) continue; $out[] = [ 'tn' => (string)$tn, 'name' => (string)($t['train_name'] ?? $t['trainName'] ?? ''), 'days' => irctc_days($t['run_days'] ?? $t['runDays'] ?? $t['running_days'] ?? null), ]; } return ['trains' => $out]; } $haveRapid = defined('RAPIDAPI_KEY') && RAPIDAPI_KEY; $haveOpenAI = defined('OPENAI_API_KEY') && OPENAI_API_KEY; /* ---- AJAX 1: irctc truth for a route ---- */ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'irctc') { csrf_check(); header('Content-Type: application/json'); if (!$haveRapid) { echo json_encode(['err' => 'RAPIDAPI_KEY not set in app/secrets.php']); exit; } $from = parse_code((string)($_POST['from'] ?? '')); $to = parse_code((string)($_POST['to'] ?? '')); $date = (string)($_POST['date'] ?? ''); if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) $date = date('Y-m-d', strtotime('+2 days')); if ($from === '' || $to === '') { echo json_encode(['err' => 'from/to required']); exit; } $res = irctc_between($from, $to, $date); if (isset($res['trains'])) { // attach each train's day_offset at the from-station (from our timetable) $off = db()->prepare('SELECT day_offset FROM train_stops WHERE train_no = ? AND station_code = ? ORDER BY seq LIMIT 1'); foreach ($res['trains'] as &$t) { $off->execute([$t['tn'], $from]); $row = $off->fetch(); $t['offset'] = $row ? (int)$row['day_offset'] : null; // null = train not in our data } unset($t); } echo json_encode($res); exit; } /* ---- AJAX 2: OpenAI for one train ---- */ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'one') { csrf_check(); header('Content-Type: application/json'); if (!$haveOpenAI) { echo json_encode(['err' => 'OPENAI_API_KEY not set in app/secrets.php']); exit; } $tn = preg_replace('/\D/', '', (string)($_POST['train'] ?? '')); $model = trim((string)($_POST['model'] ?? '')) ?: $MODEL_DEFAULT; if ($tn === '') { echo json_encode(['err' => 'no train']); exit; } $r = openai_rundays($model, $tn); echo json_encode(isset($r['err']) ? ['err' => $r['err']] : ['got' => $r['days'], 'conf' => $r['conf'], 'src' => $r['src']]); exit; } $page_title = 'irctc1 vs OpenAI'; require APP_DIR . '/header.php'; ?> ← Admin

irctc1 vs OpenAI — running days

One irctc1 call fetches the true running days for a whole route (the answer key), then OpenAI is tested per train against it. No hand-typed values.

Add define('RAPIDAPI_KEY', '...'); to app/secrets.php.
Add define('OPENAI_API_KEY', 'sk-...'); to app/secrets.php.
Pick a route with a mix of trains (some weekly ones make it a real test). Each train on the route = one paid OpenAI call, so cap it. irctc1 is treated as ground truth.