T
TOMO
Developer Docs
BETA These docs are under partner review. Some features described are roadmap items, not yet shipped. Verify against your sandbox before relying on any contract.
● DRAFTv1.0.0travel.book_bus

travel.book_bus — Full Intent Specification

INTENT NAMESPACE: travel
INTENT NAME:      book_bus
FULL ID:          travel.book_bus
VERSION:          v1.0.0
STATUS:           draft
TTBS WEIGHTS:     time 0.30 · taste 0.20 · budget 0.30 · safety 0.20
LAST UPDATED:     2026-05-14

Inter-city and intra-state bus booking via aggregators or operator-direct channels. Covers government-run state transport corporations (APSRTC, TSRTC, KSRTC, MSRTC, etc.) and private operators (Volvo Multi-Axle, Mercedes Multi-Axle, Sleeper Coach, Seater AC, Seater Non-AC). Distinct from travel.book_train (railway-issued tickets), mobility.book_intercity_ride (private cab city-to-city), and mobility.book_recurring_commute (subscription pooling). Partner exemplars: RedBus, Abhibus, Paytm Bus, Ixigo Bus, MakeMyTrip Bus, state RTC direct booking portals.


1. NATURAL LANGUAGE COVERAGE

Classifies IN

  • "bus to Bangalore tomorrow night"
  • "Volvo Hyderabad to Vijayawada"
  • "sleeper bus to Mumbai"
  • "AC Seater Pune Bangalore"
  • "RedBus to Tirupati"
  • "APSRTC bus to Vizag"
  • "lower berth sleeper Coimbatore Bangalore"
  • "ladies seat bus Chennai"
  • "boarding point Madhapur"
  • "Mercedes multi axle Hyderabad Bangalore"

Classifies OUT — borderline NO

  • "train to Mumbai" → travel.book_train
  • "flight to Goa" → travel.book_flight
  • "intercity cab to Vizag" → mobility.book_intercity_ride
  • "intracity bus / city bus" — not a v1 TOMO intent
  • "carpool to office" → mobility.book_recurring_commute
  • "bus pass / monthly pass" — out of scope v1

MULTI-INTENT TRIGGERS

  • "Bangalore weekend — bus + hotel + scooter rental" → travel.book_bus + travel.book_hotel + mobility.book_two_wheeler_rental
  • "Bus + cab to boarding point" → travel.book_bus + mobility.book_intracity_ride
  • "Bus + bus to next city" → multi-leg single intent (handled inside this spec)

2. INPUT — TOMO → PROVIDER

{
  "intent":          "travel.book_bus",
  "intent_version":  "v1.0.0",
  "request_id":      "req_bus_2h4k_2026-05-14T10:00:00+05:30",
  "user_session_id": "anon_user_token_or_uid",

  "origin": {
    "city":           "Hyderabad",
    "state":          "Telangana",
    "country_code":   "IN",
    "preferred_boarding_points": ["Madhapur", "Ameerpet", "MGBS"]
  },

  "destination": {
    "city":           "Bangalore",
    "state":          "Karnataka",
    "country_code":   "IN",
    "preferred_dropping_points": ["Marathahalli", "Silk Board", "Majestic"]
  },

  "journey_date":         "2026-05-20",
  "departure_window":     "night",
  "flexible_days_window": 0,
  "return_date_for_round_trip": null,

  "party": {
    "passenger_count":    2,
    "adult_count":        2,
    "children_with_age":  [],
    "infants_under_2":    0,
    "senior_count":       0,
    "ladies_count":       0
  },

  "preferences": {
    "bus_kind_acceptable": ["volvo_multi_axle_sleeper", "mercedes_multi_axle_sleeper", "ac_sleeper"],
    "seat_kind_acceptable": ["sleeper_lower", "sleeper_side_lower"],
    "operator_kind_acceptable": ["private", "rtc"],
    "max_journey_hours":   12,
    "budget_band":         "good",
    "budget_max_inr_per_passenger": 1500,
    "budget_max_inr_total": 3000,
    "ladies_only_section_required": false,
    "single_lady_traveling_alone": false,
    "wheelchair_accessible_required": false,
    "luggage_kg_per_passenger": 15,
    "live_tracking_required": true,
    "boarding_buffer_minutes_min": 30
  },

  "context": {
    "user_locale":          "en-IN",
    "user_currency_pref":   "INR",
    "trip_purpose":         "leisure",
    "trust_signals": {
      "is_repeat_customer":          true,
      "prior_bus_with_partner":      6,
      "user_account_age_days":       312
    }
  }
}
Field Type Constraint Notes
intent string REQUIRED, equals "travel.book_bus"
origin.city, destination.city string REQUIRED Validated against partner's city dictionary
origin.preferred_boarding_points array REQUIRED, may be empty Empty = any
destination.preferred_dropping_points array REQUIRED, may be empty Empty = any
journey_date ISO_DATE REQUIRED Up to 60 days advance
departure_window enum REQUIRED, STRICT §6 morning / afternoon / evening / night / any
flexible_days_window int REQUIRED, 0-3 0 = strict date
return_date_for_round_trip ISO_DATE or null REQUIRED non-null triggers round-trip discount lookup
party.passenger_count int REQUIRED, 1-10
party.children_with_age array REQUIRED, may be empty each age 3-11
party.infants_under_2 int REQUIRED infants travel on adult lap, no separate seat
party.ladies_count int REQUIRED drives ladies-quota / ladies-section eligibility
preferences.bus_kind_acceptable array REQUIRED, ≥1, §6
preferences.seat_kind_acceptable array REQUIRED, ≥1, §6
preferences.operator_kind_acceptable array REQUIRED, ≥1, §6 private / rtc
preferences.ladies_only_section_required bool REQUIRED hard filter
preferences.single_lady_traveling_alone bool REQUIRED drives safety reranking
preferences.wheelchair_accessible_required bool REQUIRED hard filter
preferences.live_tracking_required bool REQUIRED hard filter on operators with GPS feed
preferences.boarding_buffer_minutes_min int REQUIRED, ≥0 walk-time from city centre to boarding point

Anti-fabrication preamble: no paid placement, no commission-based operator ordering, TOMO never holds the rail — money flows user → operator (direct or via aggregator's pass-through merchant account).


3. PROVIDER TOOLS

Tool 1: search_buses

PURPOSE:        return buses running origin → destination on journey_date matching filters
INPUT:          §2 request body
OUTPUT:         { buses: BusOption[], result_token, expires_at }
SLA:            p50 < 1200ms, p95 < 3000ms, p99 < 5000ms
RATE LIMIT:     ≤ 1/sec per (user_session_id, partner)
RESULT SET:     up to 40 buses (typical route has 8-25)

Tool 2: get_seat_layout

PURPOSE:        live seat availability matrix for one bus
INPUT:          { bus_id, journey_date, boarding_point, dropping_point, request_id }
OUTPUT:         SeatLayoutMatrix (§5)
SLA:            p95 < 1200ms
RATE LIMIT:     ≤ 2/sec per bus+date

Tool 3: hold_seats

PURPOSE:        lock chosen seats for 10 minutes
INPUT:          { bus_id, journey_date, seat_ids[], boarding_point, dropping_point, request_id }
OUTPUT:         { hold_id, hold_expires_at, fare_breakdown }
SLA:            p95 < 1500ms
RATE LIMIT:     ≤ 1/sec per user

Tool 4: confirm_booking

PURPOSE:        convert hold → confirmed booking + ticket PDF + e-receipt
INPUT:          { hold_id, party, contact, payment_method_id, request_id }
OUTPUT:         ConfirmedTicket (§5)
SLA:            p95 < 4000ms
RATE LIMIT:     ≤ 1/sec
IDEMPOTENCY:    request_id

Tool 5: cancel_booking

PURPOSE:        cancel ticket, compute refund per published policy
INPUT:          { booking_id, reason_code, request_id }
OUTPUT:         CancellationResult (§5)
SLA:            p95 < 3000ms

4. RESPONSE SHAPE

BusOption

BusOption:
  bus_id: { type: string, constraint: REQUIRED, opaque }
  operator:
    operator_id: { type: string, constraint: REQUIRED }
    name: { type: string, constraint: REQUIRED, semantics: "e.g. 'VRL Travels', 'KSRTC' " }
    kind: { type: enum, constraint: REQUIRED, STRICT §6, values: [private, rtc] }
    permit_number: { type: string, constraint: REQUIRED, semantics: "RTO permit; verifiable on parivahan.gov.in" }
    safety_certifications: { type: array<enum>, constraint: REQUIRED, STRICT §6, may be empty }
    fleet_avg_age_years: { type: float, constraint: REQUIRED, ≥0 }

  bus_kind: { type: enum, constraint: REQUIRED, STRICT §6 }
  seats_total: { type: int, constraint: REQUIRED }
  seats_available: { type: int, constraint: REQUIRED, semantics: "Live count for journey_date+boarding_point" }

  origin_boarding_point:
    point_name: { type: string, constraint: REQUIRED }
    address: { type: string, constraint: REQUIRED }
    lat: { type: float, constraint: REQUIRED }
    lng: { type: float, constraint: REQUIRED }
    landmark: { type: string, constraint: REQUIRED, semantics: "Plain-language landmark" }
    boarding_time: { type: string, constraint: REQUIRED, ISO_DATETIME }

  destination_dropping_point:
    point_name: { type: string, constraint: REQUIRED }
    address: { type: string, constraint: REQUIRED }
    lat: { type: float, constraint: REQUIRED }
    lng: { type: float, constraint: REQUIRED }
    landmark: { type: string, constraint: REQUIRED }
    dropping_time: { type: string, constraint: REQUIRED, ISO_DATETIME }

  estimated_duration_minutes: { type: int, constraint: REQUIRED }

  fare_breakdown:
    base_fare_inr: { type: int, constraint: REQUIRED, INR_INTEGER }
    operator_fee_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    aggregator_fee_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    gst_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0, semantics: "5% on AC, 0% on non-AC contract carriages" }
    total_per_seat_inr: { type: int, constraint: REQUIRED, INR_INTEGER }
    total_for_party_inr: { type: int, constraint: REQUIRED, INR_INTEGER }

  amenities: { type: array<enum>, constraint: REQUIRED, STRICT §6, may be empty }
  amenities_verified_within_days: { type: int, constraint: REQUIRED, semantics: "Days since amenity audit" }

  safety:
    live_tracking_available: { type: boolean, constraint: REQUIRED }
    sos_button_in_bus: { type: boolean, constraint: REQUIRED }
    cctv_in_bus: { type: boolean, constraint: REQUIRED }
    female_attendant_onboard: { type: boolean, constraint: REQUIRED }
    rest_stops_planned: { type: int, constraint: REQUIRED, ≥0 }

  ladies_only_seats_available: { type: int, constraint: REQUIRED, ≥0 }
  ladies_section_present: { type: boolean, constraint: REQUIRED }

  cancellation_policy:
    type: array<object>
    constraint: REQUIRED, may NOT be empty
    shape:
      window_hours_before_departure_min: { type: int, constraint: REQUIRED, ≥0 }
      window_hours_before_departure_max: { type: int, constraint: REQUIRED, ≥0 }
      refund_pct: { type: int, constraint: REQUIRED, 0-100 }

  rating:
    operator_score: { type: float, constraint: REQUIRED, 0.0-5.0 }
    on_time_pct: { type: float, constraint: REQUIRED, 0-100, semantics: "Past 90 days arrival within 30min of ETA" }
    review_count: { type: int, constraint: REQUIRED, ≥0 }

  partner_reference:
    source: { type: string, constraint: REQUIRED }
    deeplink: { type: string, constraint: REQUIRED, HTTPS URL }

SeatLayoutMatrix

SeatLayoutMatrix:
  bus_id: { type: string, constraint: REQUIRED }
  rows: { type: int, constraint: REQUIRED }
  columns_layout: { type: string, constraint: REQUIRED, semantics: "e.g. '2+1' for sleeper, '2+2' for seater" }
  decks: { type: enum, constraint: REQUIRED, STRICT §6, values: [single, double] }
  seats:
    type: array<object>
    constraint: REQUIRED, may NOT be empty
    shape:
      seat_id: { type: string, constraint: REQUIRED }
      seat_label: { type: string, constraint: REQUIRED, semantics: "e.g. 'L1', 'U5'" }
      seat_kind: { type: enum, constraint: REQUIRED, STRICT §6 }
      deck: { type: enum, constraint: REQUIRED, STRICT §6 }
      position: { type: enum, constraint: REQUIRED, STRICT §6, values: [window, aisle, single, lower, upper, side_lower, side_upper] }
      ladies_only: { type: boolean, constraint: REQUIRED }
      available: { type: boolean, constraint: REQUIRED }
      blocked_reason: { type: enum, constraint: REQUIRED, STRICT §6, values: [none, sold, on_hold, ladies_only_filter, maintenance] }
      fare_inr: { type: int, constraint: REQUIRED, INR_INTEGER }

ConfirmedTicket

ConfirmedTicket:
  booking_id: { type: string, constraint: REQUIRED, immutable }
  operator_pnr: { type: string, constraint: REQUIRED, semantics: "Operator's PNR; usable at boarding without TOMO" }
  bus_id: { type: string, constraint: REQUIRED }
  journey_date: { type: string, constraint: REQUIRED, ISO_DATE }
  boarding_point: { type: object, constraint: REQUIRED, semantics: "Same shape as origin_boarding_point" }
  dropping_point: { type: object, constraint: REQUIRED, semantics: "Same shape as destination_dropping_point" }
  passengers:
    type: array<object>
    constraint: REQUIRED, may NOT be empty
    shape:
      passenger_name: { type: string, constraint: REQUIRED }
      age: { type: int, constraint: REQUIRED }
      gender: { type: enum, constraint: REQUIRED, STRICT §6 }
      seat_id: { type: string, constraint: REQUIRED }
      seat_label: { type: string, constraint: REQUIRED }
  fare_paid_inr: { type: int, constraint: REQUIRED, INR_INTEGER }
  ticket_pdf_url: { type: string, constraint: REQUIRED, HTTPS URL }
  e_receipt_url: { type: string, constraint: REQUIRED, HTTPS URL }
  live_tracking_url: { type: string, constraint: REQUIRED, HTTPS URL, semantics: "Empty when live tracking unsupported" }
  driver_phone_at_t_minus_2h: { type: string, constraint: REQUIRED, E.164, semantics: "Empty until 2h before departure" }
  cancellation_window:
    full_refund_until: { type: string, constraint: REQUIRED, ISO_DATETIME }
    last_cancellable_at: { type: string, constraint: REQUIRED, ISO_DATETIME }

CancellationResult

CancellationResult:
  booking_id: { type: string, constraint: REQUIRED }
  cancelled_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
  refund_amount_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
  refund_method: { type: enum, constraint: REQUIRED, STRICT §6, values: [original_payment, partner_wallet] }
  refund_eta_days: { type: int, constraint: REQUIRED, 0-10 }
  cancellation_fee_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }

FORBIDDEN FIELDS

  • paid_placement_score, ad_bid, sponsored_rank, featured_operator, promotion_priority
  • editor_pick, tomo_recommended, top_choice
  • urgency_text ("Only 2 seats left!") unless seats_available ≤ 3
  • fake_review_block, inflated_rating, rounded_on_time_pct
  • ai_generated_bus_photo — real images required; expired imagery flagged
  • commission_padded_fare, partner_revenue_share
  • unpublished_amenity ("WiFi" without actual WiFi capability)
  • expired_permit_number — Parivahan-verifiable permits only

5. CONTROLLED VOCABULARIES

preferences.bus_kind_acceptable / BusOption.bus_kind:
  values:
    volvo_multi_axle_sleeper:      "Volvo multi-axle, sleeper berths"
    mercedes_multi_axle_sleeper:   "Mercedes-Benz multi-axle, sleeper berths"
    ac_sleeper:                    "AC sleeper, generic"
    non_ac_sleeper:                "Non-AC sleeper"
    ac_seater:                     "AC seater"
    non_ac_seater:                 "Non-AC seater"
    semi_sleeper_ac:               "Semi-sleeper recliner AC"
    semi_sleeper_non_ac:           "Semi-sleeper recliner non-AC"
    double_decker_sleeper:         "Double-decker with sleeper upper deck"
    luxury_sleeper:                "Premium luxury sleeper (e.g. flatbed)"

preferences.seat_kind_acceptable / seat_kind:
  values: [seater, sleeper_lower, sleeper_upper, sleeper_side_lower, sleeper_side_upper, semi_sleeper_lower, semi_sleeper_upper]

preferences.operator_kind_acceptable / operator.kind:
  values:
    private: "Private operator"
    rtc:     "State Road Transport Corporation"

departure_window:
  values: [morning, afternoon, evening, night, any]

party gender (per passenger):
  values: [male, female, other]

deck:
  values: [single, double]

amenities:
  values:
    charging_socket, wifi, water_bottle, blanket, pillow, reading_light,
    snacks, meals, recliner, footrest, usb_charger, entertainment_screen,
    air_purifier, female_attendant, toilet_onboard, hand_sanitizer

operator.safety_certifications:
  values: [iso_9001, iso_14001, isso_safety_audit, rto_permit_current, parivahan_verified, sebi_listed_operator]

CancellationResult.refund_method:
  values: [original_payment, partner_wallet]

6. TTBS DIMENSIONS

TIME (weight = 0.30):
  signals_used:
    - boarding_time vs departure_window
    - estimated_duration_minutes vs max_journey_hours
    - on_time_pct
    - boarding_point distance to user_location vs boarding_buffer_minutes_min
  weighting:
    departure_window_fit: 0.30
    duration: 0.30
    on_time: 0.25
    boarding_proximity: 0.15
  user_band_handling:
    fast: prefer shortest duration even at +15% fare
    balanced: standard
    flexible: longer routes acceptable for cheaper fare

TASTE (weight = 0.20):
  signals_used:
    - bus_kind matches preferences (Volvo > Mercedes > AC > non-AC by user-stated preference)
    - seat_kind preference fit
    - amenities count match
    - DNA repeat operator
  weighting:
    bus_kind_match: 0.35
    seat_match: 0.25
    amenities: 0.20
    dna_repeat: 0.20

BUDGET (weight = 0.30):
  signals_used:
    - fare_breakdown.total_per_seat_inr
    - fare_breakdown.total_for_party_inr
  weighting:
    per_seat: 0.50
    total: 0.50
  user_band_handling:
    ok: cheapest passing safety floor (rating ≥3.5, on-time ≥75%)
    good: balanced
    great: premium luxury_sleeper or multi_axle_volvo

SAFETY (weight = 0.20):
  signals_used:
    - operator.permit_number verified live
    - operator.fleet_avg_age_years (lower preferred)
    - safety.live_tracking_available (HARD filter when requested)
    - safety.sos_button_in_bus
    - safety.cctv_in_bus
    - safety.female_attendant_onboard (boost when single_lady_traveling_alone=true)
    - ladies_section_present (HARD when ladies_only_section_required=true)
    - rating.operator_score
    - rating.on_time_pct floor (75%)
  weighting:
    permit_verified: 0.20
    fleet_age: 0.15
    live_tracking: 0.15
    sos_cctv: 0.15
    female_attendant: 0.10
    rating_floor: 0.25
  user_band_handling:
    fast: relax operator_score floor to 3.5
    balanced: floor 4.0
    great: floor 4.3 + live tracking + SOS

Locked weights: time 0.30 / taste 0.20 / budget 0.30 / safety 0.20. Bus is a time-budget-driven domain; safety floors are HARD (permit verification, live tracking when requested) rather than dominant weight.


7. COMPLETION CONTRACT

POST /api/v1/cpc/mcp_provider/<your_partner_id>
Body:
{
  "intent":           "travel.book_bus",
  "external_id":      "<booking_id>",
  "request_id":       "<request_id>",
  "amount_inr":       150,    // NET partner commission only (typically 8-12% of fare)
  "gst_inr":          27,
  "tips_inr":         0,
  "pass_through_inr": 1800,   // fare remitted to operator
  "closed_at":        "2026-05-20T22:30:00+05:30",
  "status":           "completed",
  "operator_id":      "vrl_travels",
  "bus_kind":         "volvo_multi_axle_sleeper",
  "journey_date":     "2026-05-20",
  "seats_booked":     2
}

User pays full fare (e.g. ₹1,950); operator gets ₹1,800; partner's commission (~₹150) is amount_inr. TOMO charges 10% × ₹150 = ₹15. HMAC-SHA256, 5-min replay window.

Cancellation completions file with status: cancelled and amount_inr adjusted to net partner-retained-commission after refund.


8. WIDGET

BusListingWidget (planned). Interim: ListingsWidget.

Field mapping:
  - operator.name + bus_kind → header
  - boarding_time → big departure time
  - dropping_time + estimated_duration_minutes → "→ 6h 30m"
  - fare_breakdown.total_per_seat_inr → price
  - seats_available → "X seats left"
  - amenities (top 3) → chip row
  - rating.operator_score + on_time_pct → "4.3★ · 92% on-time"
  - safety.live_tracking_available → "Live tracking" badge
  - safety.female_attendant_onboard → "Female attendant" badge (boost when single_lady_traveling_alone)
  - ladies_section_present → "Ladies section" badge (when ladies_only_section_required)

9. CACHING POLICY

Call TTL Rationale
search_buses 90s Seat availability moves quickly
get_seat_layout 30s Live seat map
hold_seats NO CACHE Stateful lock
confirm_booking NO CACHE Idempotent by request_id
cancel_booking NO CACHE
Operator static (permit, fleet, ratings) 24h
Boarding/dropping point geocodes 7d

10. ERROR CODES

Code HTTP Meaning Retry
INVALID_REQUEST 400 Malformed No
RATE_LIMITED 429 Throttle 1, 2s
INTERNAL_ERROR 500 Partner failure 2, exp
SIGNATURE_INVALID 401 HMAC fail No
NO_BUSES_FOUND 200 (empty) No bus on route+date n/a
SEAT_TAKEN 409 Hold race-lost UI re-queries layout
HOLD_EXPIRED 410 Hold window elapsed UI re-holds
PERMIT_INVALID 422 Operator permit lapsed No (operator suspended)
BOARDING_POINT_INVALID 422 Boarding point not served on this date No
PAYMENT_FAILED 402 Gateway declined No
LADIES_FILTER_NO_SEATS 422 ladies_only_section_required + no female-eligible seats No
WHEELCHAIR_NO_ROUTE 422 No wheelchair-accessible bus on route No
CANCELLED_TOO_LATE 410 Past last_cancellable_at No
OPERATOR_DOWN 503 Operator API unavailable 1 retry, 2s
ROUTE_CANCELLED_BY_OPERATOR 410 Operator cancelled bus No (auto-refund flow)

11. SANDBOX → PRODUCTION CHECKLIST

[ ] All five tools implemented
[ ] At least 50 routes in catalog covering 10+ cities
[ ] At least 30 operators (mix of private + RTC)
[ ] Every permit_number Parivahan-verifiable; daily re-check
[ ] amenities_verified_within_days ≤ 60 on all listings
[ ] live_tracking_url returns real GPS feed (sample tested on 5 buses)
[ ] CCTV / SOS / female_attendant flags audited via partner sample
[ ] on_time_pct computed from past 90 days of completed journeys
[ ] Cancellation policy publishes BEFORE payment, tested end-to-end
[ ] HMAC signing verified on test CPC webhook
[ ] amount_inr in CPC is COMMISSION (NET); pass_through_inr is fare to operator
[ ] All controlled vocabularies respected
[ ] No forbidden fields anywhere
[ ] SLA p95 met (100-call sandbox run)
[ ] GSTIN, MSO / aggregator license number, privacy policy URL uploaded
[ ] Customer support: 24×7 bus helpline + escalation email
[ ] Refund processed within stated refund_eta_days on test cancellations
[ ] Ladies-only / female-attendant logic tested with single_lady_traveling_alone flag

12. ANTI-FABRICATION RULES

RULE 1: operator.permit_number must be verifiable on parivahan.gov.in;
        TOMO re-checks daily. Expired or fake permits = immediate suspension.

RULE 2: live_tracking_url must serve real GPS feed when live_tracking_available=true.
        Fake polylines / static maps = customer-harm + suspension.

RULE 3: No paid_placement, ad_bid, sponsored_rank, featured_operator.
        Bus ordering depends on TTBS only.

RULE 4: amenities listed must be physically present on the bus on the
        journey date. "WiFi" listed without actual WiFi = customer-harm.
        TOMO spot-audits 5% of new operators per quarter.

RULE 5: rating.operator_score must be unrounded over past 12 months of
        completed journeys; review_count from completed journeys only.

RULE 6: on_time_pct computed from actual scheduled-vs-arrival comparison
        in the past 90 days. Rounded up = ingest reject.

RULE 7: artificial_urgency_text "Only 2 left!" only when seats_available ≤ 3.
        TOMO ingest validates seats_available against the same response;
        mismatch = ingest reject.

RULE 8: ladies_only seat flagging must be operator-enforced at boarding
        (operator app prompts attendant). Partner showing fake ladies
        sections to bias ranking = suspension.

RULE 9: female_attendant_onboard must be verifiable from operator's
        staffing roster. Partner can be asked to produce.

RULE 10: amount_inr in CPC is partner COMMISSION only. Fare remitted to
         operator is pass_through_inr. Inflated commission base = suspension.

RULE 11: No AI-generated bus photos. Real recent photographs (within 12
         months) required; sample audited at partner intake.

RULE 12: Cancellation policy published in §5 cancellation_policy must match
         what partner enforces at cancel time. Mid-process policy changes
         = customer-harm + suspension.

RULE 13: Driver phone in ConfirmedTicket must be the actual driver's number
         from 2h before departure. Generic call-center numbers = ingest
         reject.

RULE 14: Information-completeness score (hidden rank factor weight 0.10)
         rewards full §4 shape. Sparse responses rank lower.

RULE 15: Operator-cancelled routes MUST trigger automatic refund within
         24h, regardless of policy windows.

VERSION HISTORY

v1.0.0 — 2026-05-14 — Initial spec. Private + RTC operators, Parivahan-verified
                       permits, daily re-check. Live-tracking + SOS + CCTV +
                       female-attendant audited. Ladies-section / single-lady-
                       solo safety reranking. Net-commission base in §7.