-------------------- END OF FILE --------------------
FILE: disclaimer.php
TYPE: PHP
SIZE: 2.58 KB
------------------------------------------------------------
← Home
Disclaimer
Last updated: = e($EFFECTIVE) ?>
= e($OPERATOR) ?> is a private, independent service and is NOT affiliated with, endorsed by, or connected to IRCTC, the Indian Railways, the Ministry of Railways, or any government body.
Information is indicative — verify before you travel
The train numbers, timings, running days, routes, and connecting journeys shown on = e($SITE) ?> are compiled from a reference timetable and third-party data for general planning convenience. This information may be incomplete, outdated, or inaccurate, and the running days of some trains may not be verified.
Always confirm train details with official and reliable railway sources — such as IRCTC (irctc.co.in), the National Train Enquiry System (NTES, enquiry.indianrail.gov.in), or your railway station — before booking tickets or travelling. Do not rely solely on this Service for travel decisions.
No ticketing
We do not sell tickets, make reservations, or process any payments for rail travel. The Service is purely an informational route planner.
No liability
We accept no responsibility or liability for any loss, inconvenience, or damage — including missed trains or connections — arising from the use of, or reliance on, information provided by the Service. Use of the Service is entirely at your own risk.
Trademarks
All railway, train, and station names and trademarks are the property of their respective owners and are used here for identification and informational purposes only.
Advertising
This Service displays advertisements, including from third-party networks. Advertiser content and offers are the responsibility of the respective advertisers; we do not endorse them.
-------------------- END OF FILE --------------------
FILE: index.php
TYPE: PHP
SIZE: 4.19 KB
------------------------------------------------------------
= e(setting('company_name','TrainChain')) ?>
= e(setting('tagline','')) ?>
How it works
Enter two stations and a date. We show every train running that day — direct
trains and connecting journeys with up to three changes, where a transfer can
be at any station two trains share, not only the obvious junctions. Sort and
filter the results by travel time, number of trains, departure or arrival, and
reserved vs unreserved.
🎫 To book or manage this ticket, use Indian Railways' official IRCTC / RailOne app. Rails and Routes shows status only.
-------------------- END OF FILE --------------------
FILE: privacy.php
TYPE: PHP
SIZE: 6.25 KB
------------------------------------------------------------
← Home
Privacy Policy
Last updated: = e($EFFECTIVE) ?>
This Privacy Policy explains how = e($OPERATOR) ?> ("we", "us", "our"), operator of = e($SITE) ?> (the "Service"), collects, uses, and protects information when you use the Service. By using the Service you agree to this Policy.
= e($OPERATOR) ?> is a private, independent service. It is not affiliated with, endorsed by, or connected to IRCTC, the Indian Railways, or any government body.
1. Information we collect
Account information (if you register): such as your name and email address, and a securely stored (hashed) password.
Usage information: the searches you run (origin, destination, date), pages viewed, and actions taken on the Service.
Technical/log data: IP address, browser type, device and operating system, referring pages, and timestamps, collected automatically by our servers.
Cookies and similar technologies: small files used to keep you signed in, remember preferences, measure usage, and serve advertising (see sections 4 and 5).
We do not sell train tickets and do not collect payment-card details for ticketing.
2. How we use information
To provide and operate the journey-planning Service and your account.
To improve search quality, performance, and reliability.
To understand usage trends and maintain the security and integrity of the Service.
To display advertising and measure its performance.
To comply with legal obligations and respond to lawful requests.
3. Legal basis
We process personal data on the basis of your consent (where required), the performance of our service to you, our legitimate interests in operating and improving the Service, and compliance with applicable law, including India's Digital Personal Data Protection Act, 2023.
4. Cookies
We use cookies and similar technologies to operate the Service, remember your session and preferences, gather analytics, and deliver advertising. You can control or disable cookies in your browser settings; however, some features may not function correctly without them.
5. Advertising
The Service displays advertisements, which may be provided by third-party advertising networks (for example, Google AdSense). These third parties may use cookies, device identifiers, and similar technologies to show ads based on your visits to this and other websites, and to measure ad performance. We do not control the data practices of these advertisers and networks.
You can manage personalised advertising through:
Google Ads Settings: adssettings.google.com
Digital Advertising Alliance / "About Ads": optout.aboutads.info
Your Online Choices (EU): youronlinechoices.eu
Please also review the privacy policies of any advertising partners we use.
6. How we share information
We do not sell your personal information. We may share information with: service providers who host and operate the Service on our behalf; advertising and analytics partners (as described above); and authorities or others where required by law or to protect rights, safety, and the integrity of the Service.
7. Data retention
We retain personal data only as long as necessary for the purposes described in this Policy, to comply with legal obligations, resolve disputes, and enforce our agreements. Account data is retained while your account is active and for a reasonable period afterward.
8. Data security
We apply reasonable technical and organisational measures to protect information, including encrypted connections (HTTPS) for data in transit, hashing of account passwords, restricted administrative access, and routine maintenance of our systems. No method of transmission or storage is completely secure, and we cannot guarantee absolute security. You are responsible for keeping your account credentials confidential.
9. Your rights
Subject to applicable law, you may have the right to access, correct, update, or delete your personal data, to withdraw consent, and to raise a grievance. To exercise these rights, contact us at = e($CONTACT) ?>. We will respond within the timeframes required by law.
10. Children
The Service is not directed to children, and we do not knowingly collect personal data from children. If you believe a child has provided us personal data, please contact us so we can remove it.
11. Third-party links
The Service may link to third-party sites and resources (including official railway sources). We are not responsible for the content or privacy practices of those sites.
12. Changes to this Policy
We may update this Policy from time to time. Material changes will be posted on this page with a revised "Last updated" date. Continued use of the Service after changes take effect constitutes acceptance.
13. Contact
For questions or grievances regarding this Policy or your data, contact us at = e($CONTACT) ?>. Operator location: = e($JURIS) ?>.
Our planner also finds connecting journeys — transfers at any shared station, not just major junctions. Indicative times; verify on official sources before travel.
-------------------- END OF FILE --------------------
FILE: search.php
TYPE: PHP
SIZE: 30.33 KB
------------------------------------------------------------
every train that runs that day
$base = rt_plan($tt, $from, $to, 0, ['date' => $date, 'horizon' => 1440]);
foreach ($base['direct'] as $l) $journeys[] = [$l];
// connecting options sampled across the day, de-duplicated
$seen = [];
foreach ([0, 360, 720, 1080] as $qd) {
$r = ($qd === 0) ? $base : rt_plan($tt, $from, $to, $qd, ['date' => $date, 'horizon' => 1440]);
foreach ($r['hops'] as $journey) {
$sig = '';
foreach ($journey as $l) $sig .= $l['train'] . ':' . $l['board'] . '>';
if (isset($seen[$sig])) continue;
$seen[$sig] = 1;
$journeys[] = $journey;
}
}
} catch (Throwable $ex) {
error_log('router: ' . $ex->getMessage());
$err = 'The journey planner is warming up. Please try again in a moment.';
}
}
}
// ---- Hide trains that have already left the origin (only for today's date, IST) ----
$departedAll = false;
if ($journeys) {
$nowIst = new DateTime('now', new DateTimeZone('Asia/Kolkata'));
if ($date === $nowIst->format('Y-m-d')) {
$nowMin = ((int) $nowIst->format('G')) * 60 + (int) $nowIst->format('i');
$before = count($journeys);
$journeys = array_values(array_filter($journeys, function ($j) use ($nowMin) {
return isset($j[0]['board']) && (int) $j[0]['board'] >= $nowMin;
}));
$departedAll = ($before > 0 && count($journeys) === 0);
}
}
// ---- analytics: log the search (only when an actual A->B search ran) ----
if ($from !== '' && $to !== '') {
$__an = APP_DIR . '/analytics.php';
if (is_file($__an)) {
require_once $__an;
rr_track('search', ['from_code' => $from, 'to_code' => $to, 'jdate' => $date,
'n' => count($journeys), 'note' => $departedAll ? 'departed' : '']);
}
}
// ---- Names + types ----
$trainNos = []; $codes = [$from, $to];
foreach ($journeys as $j) foreach ($j as $l) { $trainNos[$l['train']] = 1; $codes[] = $l['from']; $codes[] = $l['to']; }
$trainNames = []; $trainTypes = []; $stationNames = []; $trainClasses = [];
if ($trainNos) {
$in = implode(',', array_fill(0, count($trainNos), '?'));
$hasClassesCol = true;
try {
$st = db()->prepare("SELECT train_no, train_name, train_type, classes FROM trains WHERE train_no IN ($in)");
$st->execute(array_keys($trainNos));
} catch (Throwable $e) {
$hasClassesCol = false;
$st = db()->prepare("SELECT train_no, train_name, train_type FROM trains WHERE train_no IN ($in)");
$st->execute(array_keys($trainNos));
}
foreach ($st as $r) {
$trainNames[$r['train_no']] = $r['train_name'];
$trainTypes[$r['train_no']] = $r['train_type'];
if ($hasClassesCol && isset($r['classes']) && $r['classes'] !== '' && $r['classes'] !== '-') {
$vals = array_values(array_filter(array_map('trim', explode(',', (string) $r['classes']))));
if ($vals) $trainClasses[$r['train_no']] = ['valid' => $vals, 'invalid' => []];
}
}
try {
$tcq = db()->prepare("SELECT train_no, class, valid FROM train_classes WHERE train_no IN ($in)");
$tcq->execute(array_keys($trainNos));
foreach ($tcq as $r) {
$tn = $r['train_no'];
if (!isset($trainClasses[$tn])) $trainClasses[$tn] = ['valid' => [], 'invalid' => []];
if ((int) $r['valid'] === 1) $trainClasses[$tn]['valid'][] = $r['class'];
else $trainClasses[$tn]['invalid'][] = $r['class'];
}
} catch (Throwable $e) { /* learning table not created yet */ }
}
$codes = array_values(array_unique(array_filter($codes)));
if ($codes) {
$in = implode(',', array_fill(0, count($codes), '?'));
$st = db()->prepare("SELECT code, name FROM stations WHERE code IN ($in)");
$st->execute($codes);
foreach ($st as $r) $stationNames[$r['code']] = $r['name'];
}
// (class lists now come from trains.classes, populated by tools/backfill_classes.php — no per-search API call)
function sname(string $c): string { global $stationNames; return $stationNames[$c] ?? $c; }
function tname(string $t): string { global $trainNames; return $trainNames[$t] ?? ''; }
function ttype(string $t): ?string { global $trainTypes; return $trainTypes[$t] ?? null; }
function train_class(string $no, ?string $t): array {
$u = strtoupper(trim((string) $t));
$isMEMU = strpos($u, 'ELECTRICAL MULTIPLE') !== false || strpos($u, 'MEMU') !== false;
$isDEMU = (strpos($u, 'DIESEL') !== false && strpos($u, 'MULTIPLE') !== false) || strpos($u, 'DEMU') !== false;
$tokenUnres = $isMEMU || $isDEMU
|| strpos($u, 'SUBURBAN') !== false || strpos($u, 'PASSENGER') !== false || strpos($u, 'EMU') !== false
|| in_array($u, ['SUB','PASS','MEX'], true);
$reservedType = false;
if ($u !== '') {
foreach (['RAJDHANI','SHATABDI','DURONTO','SUPERFAST','SUF','GARIB RATH','HUMSAFAR','HMSR',
'VANDE BHARAT','VBEX','TEJAS','DOUBLE DECKER','MAIL EXPRESS','INTERCITY','SAMPARK KRANTI',
'SUVIDHA','RAJ','DRNT'] as $rk) {
if (strpos($u, $rk) !== false) { $reservedType = true; break; }
}
}
$d = ($no !== '') ? $no[0] : '';
$noUnres = in_array($d, ['3','4','5','6','7','9'], true);
if ($reservedType) $unreserved = false;
elseif ($tokenUnres) $unreserved = true;
else $unreserved = $noUnres;
$map = [
'MAIL EXPRESS'=>'Mail/Express','SUPERFAST'=>'Superfast','SUF'=>'Superfast',
'INTERCITY'=>'Intercity','INT'=>'Intercity','GARIB RATH'=>'Garib Rath',
'SAMPARK KRANTI'=>'Sampark Kranti','SHATABDI'=>'Shatabdi','JAN SHATABDI'=>'Jan Shatabdi',
'RAJDHANI'=>'Rajdhani','RAJ'=>'Rajdhani','DURONTO'=>'Duronto','DRNT'=>'Duronto',
'HUMSAFAR'=>'Humsafar','HMSR'=>'Humsafar','VANDE BHARAT EXPRESS'=>'Vande Bharat','VBEX'=>'Vande Bharat',
'TEJAS EXPRESS'=>'Tejas','DOUBLE DECKER'=>'Double Decker','DD'=>'Double Decker',
'SUBURBAN'=>'Suburban','SUB'=>'Suburban','PASSENGER'=>'Passenger','PASS'=>'Passenger','MEX'=>'MEMU',
];
if ($isMEMU) $label = 'MEMU';
elseif ($isDEMU) $label = 'DEMU';
elseif (isset($map[$u])) $label = $map[$u];
elseif ($u !== '') $label = ucwords(strtolower($u));
else $label = ['3'=>'Suburban','4'=>'Suburban','5'=>'Passenger','6'=>'MEMU','7'=>'DEMU','9'=>'Suburban'][$d] ?? '';
return [$label, $unreserved];
}
function rd_tag(array $l): string {
if (!empty($l['harvested'])) {
$days = $l['days'] ?? '—';
if (!empty($l['stale'])) return ' · Runs: ' . e($days) . ' · re-checking';
return ' · Runs: ' . e($days) . '';
}
return ' · days not verified — assumed daily';
}
$travelTs = strtotime($date . ' 00:00');
function whenstr(int $absMin): string {
global $travelTs;
$ts = $travelTs + $absMin * 60;
return date('H:i', $ts) . ' · ' . date('D j M', $ts);
}
function station_node(string $code, string $kind, ?int $layover): void {
?>
Times are indicative (based on a reference timetable). All trains running on the selected date are shown. Always confirm on the official source before booking.
Reserved train (bookable online) Unreserved train (book on the RailOne app)
🎫
Travelling unreserved (general class)? Book your ticket online on Indian Railways’ official RailOne app.
Google PlayApp Store
= e($err) ?>
Enter an origin and destination to see journeys.
All trains from = e($from) ?> → = e($to) ?> for today have already departed. Try tomorrow's date.No trains found for = e($from) ?> → = e($to) ?> on = e($date) ?>. Try a nearby date or a different pair of stations.
No journeys match your filters.
-------------------- END OF FILE --------------------
FILE: sitemap.php
TYPE: PHP
SIZE: 1.46 KB
------------------------------------------------------------
' . "\n";
$out .= '' . "\n";
$add = function (string $loc) use (&$out) {
$out .= '' . htmlspecialchars($loc, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' . "\n";
};
$add($base . '/');
$tr = $pdo->query("SELECT train_no FROM trains WHERE run_days IS NOT NULL ORDER BY train_no");
foreach ($tr as $row) {
$add($base . rr_train_url($row['train_no']));
}
$stq = $pdo->query(
"SELECT DISTINCT s.code
FROM stations s
JOIN train_stops ts ON ts.station_code = s.code
JOIN trains t ON t.train_no = ts.train_no AND t.run_days IS NOT NULL
ORDER BY s.code"
);
foreach ($stq as $row) {
$add($base . rr_station_url($row['code']));
}
$out .= '' . "\n";
@file_put_contents($cacheFile, $out);
echo $out;
-------------------- END OF FILE --------------------
FILE: station.php
TYPE: PHP
SIZE: 5.13 KB
------------------------------------------------------------
prepare("SELECT * FROM stations WHERE code = ?");
$sq->execute([$code]);
$st = $sq->fetch(PDO::FETCH_ASSOC);
if (!$st) {
http_response_code(404);
$page_title = 'Station ' . $code . ' not found';
$meta_robots = 'noindex';
require APP_DIR . '/header.php';
echo '
Indicative times based on a reference timetable. Confirm on official sources before travel.
-------------------- END OF FILE --------------------
FILE: stations.php
TYPE: PHP
SIZE: 532 B
------------------------------------------------------------
prepare(
'SELECT code, name FROM stations
WHERE name LIKE ? OR code LIKE ?
ORDER BY (code = ?) DESC, CHAR_LENGTH(name) ASC
LIMIT 10');
$st->execute([$like, $like, strtoupper($q)]);
echo json_encode($st->fetchAll(), JSON_UNESCAPED_UNICODE);
-------------------- END OF FILE --------------------
FILE: terms.php
TYPE: PHP
SIZE: 5.11 KB
------------------------------------------------------------
← Home
Terms & Conditions
Last updated: = e($EFFECTIVE) ?>
These Terms govern your use of = e($SITE) ?> (the "Service"), operated by = e($OPERATOR) ?> ("we", "us", "our"). By accessing or using the Service, you agree to these Terms. If you do not agree, do not use the Service.
1. About the Service
The Service is an independent train journey-planning tool that suggests direct and connecting (multi-hop) routes between stations using a reference timetable and third-party data. It is provided for general information and planning convenience only.
2. No affiliation with IRCTC or Indian Railways
= e($OPERATOR) ?> is a private, independent service. We are not affiliated with, authorised by, endorsed by, or connected to IRCTC, the Indian Railways, the Ministry of Railways, or any government authority. All railway names, train names, station names, and trademarks are the property of their respective owners and are used for identification and informational purposes only.
3. Accuracy and verification
Train numbers, timings, running days, routes, and connections shown on the Service are indicative only. They are derived from reference data that may be incomplete, outdated, or inaccurate, and running days for some trains may be unverified. Schedules, platforms, cancellations, and availability change frequently.
You must independently verify all train information with official and reliable railway sources — such as IRCTC (irctc.co.in), the National Train Enquiry System (NTES / enquiry.indianrail.gov.in), or the railway station enquiry — before booking, purchasing tickets, or travelling. The Service does not sell tickets and does not process bookings or payments for travel.
4. No warranties
The Service is provided "as is" and "as available", without warranties of any kind, express or implied, including accuracy, completeness, fitness for a particular purpose, or uninterrupted availability.
5. Limitation of liability
To the maximum extent permitted by law, = e($OPERATOR) ?> shall not be liable for any direct, indirect, incidental, or consequential loss or damage — including missed trains or connections, additional costs, or losses arising from reliance on information shown on the Service. Your use of the Service is at your own risk.
6. Acceptable use
You agree not to misuse the Service, including: attempting to disrupt or overload it; scraping or harvesting data at scale; reverse-engineering or copying it; using it for unlawful purposes; or infringing the rights of others.
7. Accounts
If you create an account, you are responsible for the accuracy of your details and for keeping your credentials confidential and for all activity under your account. We may suspend or terminate accounts that violate these Terms.
8. Advertising
The Service displays advertisements, including from third-party advertising networks. Advertisements and any third-party content, products, or offers are the responsibility of the respective advertisers. We do not endorse and are not responsible for advertiser content or any dealings you have with advertisers.
9. Intellectual property
The Service's design, software, and original content are owned by = e($OPERATOR) ?> and protected by applicable laws. You may not copy, reproduce, or redistribute them without permission. Third-party names and marks remain the property of their owners.
10. Third-party links
The Service may link to third-party websites and resources. We are not responsible for their content, availability, or practices.
11. Changes
We may modify the Service or these Terms at any time. Updated Terms will be posted on this page with a revised date. Continued use after changes take effect constitutes acceptance.
12. Governing law and jurisdiction
These Terms are governed by the laws of India. Subject to applicable law, the courts at = e($JURIS) ?> shall have exclusive jurisdiction over any disputes arising from the Service.
= e($no . ' ' . $nm) ?> is a = e($typeLabel) ?> running between = e($srcName) ?> (= e($src) ?>)
and = e($dstName) ?> (= e($dst) ?>). It operates = e($daysTxt === 'Daily' ? 'daily' : 'on ' . $daysTxt) ?>
and calls at = e($nStops) ?> stations.
= $isUn
? 'It is an unreserved service — travel on a general / platform ticket; online reservation is not available.'
: 'Reserved classes are available for booking through official channels.' ?>
Timings are indicative, based on a reference timetable. Always confirm on the official Indian Railways / IRCTC sources before booking or travel.
-------------------- END OF FILE --------------------
FILE: admin/ai_rundays.php
TYPE: PHP
SIZE: 6.33 KB
------------------------------------------------------------
'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'];
$ORDER = ['Mon','Tue','Wed','Thu','Fri','Sat','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;
}
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, // return before nginx's ~60s, fail gracefully
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, 200)];
$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 reply: ' . substr($text, 0, 160)];
return ['days' => $json['run_days'] ?? [], 'conf' => (string)($json['confidence'] ?? '?'), 'src' => (string)($json['source'] ?? '')];
}
$haveKey = defined('OPENAI_API_KEY') && OPENAI_API_KEY;
/* ---- AJAX: test exactly one train, return JSON ---- */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'one') {
csrf_check();
header('Content-Type: application/json');
if (!$haveKey) { 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;
$exp = trim((string)($_POST['exp'] ?? ''));
if ($tn === '') { echo json_encode(['err' => 'no train number']); exit; }
$r = openai_rundays($model, $tn);
if (isset($r['err'])) { echo json_encode(['err' => $r['err']]); exit; }
$got = rd_canon($r['days']);
$match = $exp === '' ? null : ($got === rd_canon(explode(',', $exp)));
echo json_encode(['got' => $got, 'match' => $match, 'conf' => $r['conf'], 'src' => $r['src']]);
exit;
}
$seed = "12244: Mon,Wed,Thu,Fri,Sat,Sun\n15607: Wed\n12512: Tue,Wed,Sun";
$page_title = 'AI running-days test';
require APP_DIR . '/header.php';
?>
← Admin
AI running-days test
Asks OpenAI (with web search) for each train's running days and checks it against your verified answer. Trains are tested one at a time. One miss means the model isn't reliable enough.
Add your key to app/secrets.php: define('OPENAI_API_KEY', 'sk-...');
One line per train as number: Mon,Wed,Sun. Leave the days off to just see what the model returns.
Verify the days in the free irctc1 playground first so the answer key is correct. Each train is one paid API call.
Results
Train
Expected
Model says
Result
Conf.
Source
-------------------- END OF FILE --------------------
FILE: admin/ai_vs_irctc.php
TYPE: PHP
SIZE: 10.05 KB
------------------------------------------------------------
'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.
Results
Train
Name
irctc1 (truth)
OpenAI (origin → boarding)
Result
Conf.
Source
-------------------- END OF FILE --------------------
FILE: admin/index.php
TYPE: PHP
SIZE: 1.86 KB
------------------------------------------------------------
(int) db()->query('SELECT COUNT(*) FROM users')->fetchColumn(),
'admins' => (int) db()->query('SELECT COUNT(*) FROM users WHERE role="admin"')->fetchColumn(),
'trains' => (int) db()->query('SELECT COUNT(*) FROM trains')->fetchColumn(),
'stations' => (int) db()->query('SELECT COUNT(*) FROM stations')->fetchColumn(),
'verified' => (int) db()->query('SELECT COUNT(*) FROM trains WHERE run_days IS NOT NULL')->fetchColumn(),
];
$pending = max(0, $stats['trains'] - $stats['verified']);
$page_title = 'Admin';
require APP_DIR . '/header.php';
?>