Decision support for ONE trip: "should I care?", answered honestly.
Give the destinations and the travel window (date_from/date_to,
YYYY-MM-DD). Destinations are the EU-27 ISO2 codes (Greece = "EL") PLUS the
non-EU27 countries we actively monitor: Norway ("NO", rail via Entur, live),
the United Kingdom ("UK" or "GB", transit via TfL, live), and Switzerland
("CH", rail via SBB — key-pending, so it is reported as a declared blind
spot until the feed is keyed, never a false all-clear). A code we do not
monitor is rejected with {"error": "unknown_country"} rather than silently
all-cleared. Returns a Decision-Support answer, not raw data:
* travel_status: NORMAL | MINOR_DISRUPTION | MAJOR_DISRUPTION;
* actionable_lines: per-event DECISION-IMPACT guidance — what the
disruption means for THIS trip and what to do (e.g. "affects regional
trains, not airports -> take a road airport transfer, leave ~30 min
earlier"), or a clearly-labelled "nothing material" line when calm;
* confidence: a LABELLED model output (coverage/corroboration/recency/
blind-spots blend, not a probability) — read its caveats;
* sources_checked: proof of what was monitored (sources_ok, blind spots);
* events + caveats.
Sub-floor noise (a deep, far-field seismic blip) is omitted; calm is a
monitoring result for the window, never an invented forecast. Invalid
inputs return an explicit {"error": ...}; nothing is fabricated.
Top-level MCP-facing structure (additive; existing fields preserved):
* presentation: a three-section block — affects_your_trip[] (each
item with verified_sources[] as display-ready names, source_count,
corroborated flag (≥2 distinct sources), an honest for_you line
bound to destinations+dates only, report_url, first_detected_at,
last_verified_at); doesnt_affect_your_trip (the proof-of-work
pile — shown[] of {headline, reason_excluded}, additional_checked_count,
summary_line, total_checked); next_steps[] (deterministic — re-check
date, aviation-handoff watch when blind spot, per-active monitor URLs);
* track_record_ref: lean {window_days, flagged, ended, still_active,
monitoring_since, url} — numbers + URL only, no narrative;
* suggested_next_call: factual {tool, context} continuity hint to
watch_trip — no claim narrative, just the suggested next action.
These exist so an LLM consumer can quote verbatim — every fact is
traceable to a named source or an input field, never invented.
Destinations also accept natural input: IATA airport codes (e.g. 'TSR',
'AMS', 'ZRH') and major city names (e.g. 'Timișoara', 'Amsterdam',
'Zürich', 'London'), resolved deterministically to a monitored country
code. The response includes a 'resolved' list ([{input, country, kind}])
disclosing how each token was mapped (e.g. 'TSR -> RO via iata-airport').
A token that resolves to a country we do not monitor is rejected with
{'error': 'unknown_country'}; a token we cannot resolve at all is rejected
with {'error': 'unknown_destination', 'tokens': [...]} — we reject rather
than guess.
Pass `lang` (e.g. "de", "ro", "pl", "fr", "es", "it"; default English) to
answer in the TRAVELLER'S language — highest-value for a foreign traveller
in a country whose language they do not speak. The response then carries a
`localized` block with the status sentence, an honest reassurance line
(calm ONLY when status is NORMAL), the decision-impact lines, AND — never
dropped — the localized caveats + blind_spots. Source-derived free text the
traveller cannot read (an event headline in the source language) is
AI-translated via Gemini and carries the label "AI-translated — verify
against the linked official source"; when no GEMINI_API_KEY is set or a
translation fails, the original source text is kept with an honest note —
never a fake translation. Our own wording falls back to English (flagged in
`localized.fallback_lang_parts`) when no template exists for `lang`; an
unknown `lang` answers in English and says so (`is_known_lang=false`).
Localization NEVER becomes a false all-clear and the aviation handoff is a
SIGNPOST that DISCLOSES the blind spot, not coverage.
Pass `audience` for role-specific operational actions (B2B travel-risk /
duty-of-care): one of "tmc" (travel management company / corporate travel
risk), "hotel", "ota", "tour_operator". The response then carries a
`persona` block: {audience, actions[]} where each action ties an affecting
event to that role's recommended steps (e.g. TMC: flexible-rebooking policy,
reroute inventory, proactive guest comms) — a PURE PROJECTION of the
audience-tagged recommendations already computed per event, each carrying a
`based_on` disclosure of the inputs it used. An unknown audience is reported
honestly with the valid set, never guessed. Omit `audience` for the default
(no persona block).