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_adventure_activity

travel.book_adventure_activity — Full Intent Specification

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

User books a single adventure or experiential activity — trekking, paragliding, river rafting, scuba diving, bungee jumping, hot-air ballooning, skydiving, jungle safari, rock climbing, kayaking. Single-day or multi-day single-activity engagements. Distinct from travel.book_tour_guide (information-led walking tours) and travel.book_package (multi-component bundles). Safety dominates — these are physical, weather-dependent, certification-gated experiences. Partner exemplars: Thrillophilia, Padi Travel, IndiaHikes, Trek The Himalayas, Bhushan Adventures, BookMyAdventures, Adventure Nation, MakeMyTrip Activities, local-operator marketplaces.


1. NATURAL LANGUAGE COVERAGE

Classifies IN

  • "paragliding in Bir Billing"
  • "river rafting Rishikesh"
  • "scuba diving Andaman"
  • "trek to Hampta Pass"
  • "bungee Goa"
  • "skydive Mysuru"
  • "hot air balloon Jaipur"
  • "kayaking in Goa"
  • "rock climbing Hampi"
  • "jungle safari Bandhavgarh"

Classifies OUT — borderline NO

  • "tour guide for Hampi" → travel.book_tour_guide
  • "Hampi trip with hotel" → travel.book_package
  • "kayak rental only" — out of scope v1 (equipment rental marketplace)
  • "adventure park bangalore" — generic park entry is theme-park ticketing, not certified-adventure
  • "gym workout" → lifestyle.book_gym_session

MULTI-INTENT TRIGGERS

  • "Rafting + hotel Rishikesh" → travel.book_adventure_activity + travel.book_hotel
  • "Trek + flight to Manali" → travel.book_adventure_activity + travel.book_flight
  • "Scuba + travel insurance" → travel.book_adventure_activity + finance.buy_travel_insurance

2. INPUT — TOMO → PROVIDER

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

  "activity_request": {
    "activity_kind":     "paragliding",
    "activity_grade":    "beginner",
    "location_city":     "Bir",
    "location_state":    "Himachal Pradesh",
    "country_code":      "IN",
    "activity_date":     "2026-10-12",
    "flexible_days_window": 2,
    "preferred_time_window": {
      "start": "2026-10-12T09:00:00+05:30",
      "end":   "2026-10-12T15:00:00+05:30"
    },
    "duration_hours":    1.5,
    "party": {
      "adult_count":     2,
      "children_with_age": [],
      "seniors_count":   0
    },
    "participant_health_declarations": [
      { "age": 32, "weight_kg": 78, "height_cm": 178, "pre_existing_conditions": [], "pregnant": false }
    ],
    "experience_level":     "first_time",
    "training_included_required": true,
    "tandem_or_solo":       "tandem",
    "instructor_language":  "en",
    "instructor_language_alternates": ["hi"],
    "transport_included_required": false,
    "media_capture_included_required": true,
    "budget_band":          "good",
    "budget_max_inr_total": 8000
  },

  "context": {
    "user_locale":          "en-IN",
    "user_currency_pref":   "INR",
    "trust_signals": {
      "is_repeat_customer":          false,
      "user_account_age_days":       312
    }
  }
}
Field Type Constraint Notes
intent string REQUIRED, equals "travel.book_adventure_activity"
activity_request.activity_kind enum REQUIRED, STRICT §6 paragliding / rafting / scuba / trek / bungee / skydive / balloon / safari / climb / kayak / zipline
activity_request.activity_grade enum REQUIRED, STRICT §6 beginner / intermediate / advanced
activity_request.activity_date ISO_DATE REQUIRED Up to 180 days advance
activity_request.duration_hours float REQUIRED, 0.5-72 Multi-day for treks
activity_request.party.adult_count int REQUIRED, ≥1, ≤20
activity_request.participant_health_declarations array REQUIRED, ≥party.adult_count Per-participant health
activity_request.participant_health_declarations[].age int REQUIRED Age limits per activity
activity_request.participant_health_declarations[].weight_kg int REQUIRED Critical for paragliding / bungee / zipline
activity_request.participant_health_declarations[].height_cm int REQUIRED
activity_request.participant_health_declarations[].pre_existing_conditions array REQUIRED, STRICT §6, may be empty Triggers safety filter
activity_request.participant_health_declarations[].pregnant bool REQUIRED Hard filter for high-impact activities
activity_request.experience_level enum REQUIRED, STRICT §6 first_time / occasional / experienced / certified
activity_request.training_included_required bool REQUIRED Hard filter on operators who skip briefing
activity_request.tandem_or_solo enum REQUIRED, STRICT §6 tandem / solo / either
activity_request.instructor_language enum REQUIRED, STRICT §6
activity_request.media_capture_included_required bool REQUIRED Drives bundle vs add-on

Anti-fabrication preamble: no paid placement. Operator certifications (PG Association of India, ITRA for trekking, PADI / SSI for scuba, MoT Adventure Tour Operator AAT recognition) MUST be verifiable. Equipment-age + last-service-date MUST be disclosed. Weather-dependent operators MUST publish their cancellation criteria. TOMO never holds the rail.


3. PROVIDER TOOLS

Tool 1: search_activities

PURPOSE:        return operators offering activity in location on date
INPUT:          §2 request body
OUTPUT:         { activities: ActivityOption[], result_token, expires_at }
SLA:            p50 < 800ms, p95 < 2500ms, p99 < 4500ms
RATE LIMIT:     ≤ 1/sec per (user_session_id, partner)
RESULT SET:     up to 15 operators

Tool 2: get_activity_detail

PURPOSE:        full briefing + equipment + instructor + safety record
INPUT:          { activity_id, request_id }
OUTPUT:         ActivityDetail (§5)
SLA:            p95 < 1500ms

Tool 3: hold_activity_slot

PURPOSE:        soft-lock for 15 minutes
INPUT:          { activity_id, party, health_declarations, request_id }
OUTPUT:         { hold_id, hold_expires_at, locked_price_inr, indemnity_url }
SLA:            p95 < 1200ms

Tool 4: confirm_activity

PURPOSE:        confirm + indemnity-signature capture + voucher
INPUT:          { hold_id, indemnity_e_signed, payment_method_id, request_id }
OUTPUT:         ConfirmedActivity (§5)
SLA:            p95 < 3500ms
IDEMPOTENCY:    request_id

Tool 5: cancel_activity

PURPOSE:        cancel + refund (auto-full-refund on weather cancel)
INPUT:          { booking_id, reason_code, request_id }
OUTPUT:         CancellationResult (§5)
SLA:            p95 < 2500ms

4. RESPONSE SHAPE

ActivityOption

ActivityOption:
  activity_id: { type: string, constraint: REQUIRED, opaque }
  activity_kind: { type: enum, constraint: REQUIRED, STRICT §6 }
  activity_grade: { type: enum, constraint: REQUIRED, STRICT §6 }
  operator:
    operator_id: { type: string, constraint: REQUIRED }
    name: { type: string, constraint: REQUIRED }
    aat_recognition: { type: boolean, constraint: REQUIRED, semantics: "Ministry of Tourism Adventure Tour Operator recognition" }
    certifying_bodies: { type: array<enum>, constraint: REQUIRED, STRICT §6, ≥1, semantics: "PADI / SSI / IFMGA / ITRA / PGAI etc." }
    operator_registration_number: { type: string, constraint: REQUIRED }
    years_operating: { type: int, constraint: REQUIRED, ≥0 }
    incident_count_past_12_months: { type: int, constraint: REQUIRED, ≥0, semantics: "Reported incidents to MoT or insurer" }
    rescue_team_onsite: { type: boolean, constraint: REQUIRED }
    medical_kit_certified: { type: boolean, constraint: REQUIRED }
    insurance_covers_participants: { type: boolean, constraint: REQUIRED }
    insurance_sum_assured_inr_per_participant: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }

  instructor:
    name: { type: string, constraint: REQUIRED }
    certification_kind: { type: enum, constraint: REQUIRED, STRICT §6 }
    certification_id: { type: string, constraint: REQUIRED }
    certification_valid_until: { type: string, constraint: REQUIRED, ISO_DATE }
    years_instructing: { type: int, constraint: REQUIRED, ≥0 }
    languages_spoken: { type: array<enum>, constraint: REQUIRED, STRICT §6, ≥1 }

  schedule:
    starts_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
    ends_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
    briefing_starts_at: { type: string, constraint: REQUIRED, ISO_DATETIME }

  pricing:
    base_per_participant_inr: { type: int, constraint: REQUIRED, INR_INTEGER }
    tandem_premium_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    media_capture_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    transport_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    gst_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
    total_for_party_inr: { type: int, constraint: REQUIRED, INR_INTEGER }

  equipment:
    list: { type: array<string>, constraint: REQUIRED, ≥1 }
    last_service_dates: { type: array<string>, constraint: REQUIRED, ISO_DATE, ≥1 }
    manufacturer_certified: { type: boolean, constraint: REQUIRED }

  eligibility:
    age_min: { type: int, constraint: REQUIRED }
    age_max: { type: int, constraint: REQUIRED }
    weight_min_kg: { type: int, constraint: REQUIRED, ≥0 }
    weight_max_kg: { type: int, constraint: REQUIRED, ≥0 }
    height_min_cm: { type: int, constraint: REQUIRED, ≥0 }
    height_max_cm: { type: int, constraint: REQUIRED, ≥0 }
    fitness_requirement: { type: enum, constraint: REQUIRED, STRICT §6, values: [low, moderate, high, expert] }
    contraindications: { type: array<enum>, constraint: REQUIRED, STRICT §6, may be empty }

  weather_policy:
    auto_cancel_conditions: { type: array<enum>, constraint: REQUIRED, STRICT §6, may be empty }
    auto_refund_on_weather: { type: boolean, constraint: REQUIRED }

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

  rating:
    average_score: { type: float, constraint: REQUIRED, 0.0-5.0 }
    review_count: { type: int, constraint: REQUIRED, ≥0 }

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

ActivityDetail

ActivityDetail:
  activity_id: { type: string, constraint: REQUIRED }
  full_briefing_text: { type: string, constraint: REQUIRED }
  pre_activity_requirements: { type: array<string>, constraint: REQUIRED, ≥1 }
  what_to_carry_text: { type: array<string>, constraint: REQUIRED, ≥1 }
  what_is_provided_text: { type: array<string>, constraint: REQUIRED, ≥1 }
  emergency_protocol_text: { type: string, constraint: REQUIRED }
  nearest_hospital_name: { type: string, constraint: REQUIRED }
  nearest_hospital_distance_km: { type: float, constraint: REQUIRED, ≥0 }
  evacuation_plan_text: { type: string, constraint: REQUIRED }
  indemnity_form_url: { type: string, constraint: REQUIRED, HTTPS URL }

ConfirmedActivity

ConfirmedActivity:
  booking_id: { type: string, constraint: REQUIRED, immutable }
  activity_id: { type: string, constraint: REQUIRED }
  operator_id: { type: string, constraint: REQUIRED }
  starts_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
  briefing_starts_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
  meeting_point:
    address: { type: string, constraint: REQUIRED }
    lat: { type: float, constraint: REQUIRED }
    lng: { type: float, constraint: REQUIRED }
    landmark: { type: string, constraint: REQUIRED }
  participants_confirmed:
    type: array<object>
    constraint: REQUIRED, ≥1
    shape:
      participant_name: { type: string, constraint: REQUIRED }
      age: { type: int, constraint: REQUIRED }
      health_declared: { type: boolean, constraint: REQUIRED }
      indemnity_signed: { type: boolean, constraint: REQUIRED }
      indemnity_signed_at: { type: string, constraint: REQUIRED, ISO_DATETIME }
  total_paid_inr: { type: int, constraint: REQUIRED, INR_INTEGER }
  voucher_url: { type: string, constraint: REQUIRED, HTTPS URL }
  operator_phone_at_t_minus_4h: { type: string, constraint: REQUIRED, E.164, semantics: "Empty until 4h before" }
  emergency_24_7_phone: { type: string, constraint: REQUIRED, E.164 }
  weather_check_at: { type: string, constraint: REQUIRED, ISO_DATETIME, semantics: "Operator posts go/no-go decision at this time" }
  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 }
  cancellation_reason: { type: enum, constraint: REQUIRED, STRICT §6 }
  refund_amount_inr: { type: int, constraint: REQUIRED, INR_INTEGER, ≥0 }
  refund_method: { type: enum, constraint: REQUIRED, STRICT §6, values: [original_payment, bank_transfer] }
  refund_eta_days: { type: int, constraint: REQUIRED, 0-10 }
  weather_initiated: { type: boolean, constraint: REQUIRED }

FORBIDDEN FIELDS

  • paid_placement_score, ad_bid, sponsored_rank, featured_operator
  • editor_pick, tomo_recommended, top_choice
  • understated_incident_count, concealed_incident
  • expired_certification_id
  • unservied_equipment — last_service_date older than 12 months without disclosure
  • ai_generated_activity_photo, staged_safety_photo
  • commission_padded_per_participant_inr
  • success_guarantee_text ("100% safe!" — adventure activities have residual risk; absolute guarantee forbidden)
  • participant_real_names in reviews

5. CONTROLLED VOCABULARIES

activity_request.activity_kind / ActivityOption.activity_kind:
  values:
    paragliding, river_rafting, scuba_diving, trekking_day, trekking_multi_day,
    mountaineering, bungee_jumping, hot_air_balloon, skydiving,
    jungle_safari_wildlife, jungle_safari_birding, rock_climbing,
    rappelling, kayaking, white_water_kayaking, surfing, kitesurfing,
    zipline, atv_quad, snorkeling, caving_spelunking, ice_skating_natural,
    snowboarding, skiing, dog_sledding, microlight_flight, helicopter_joyride

activity_grade:
  values: [beginner, intermediate, advanced]

operator.certifying_bodies / instructor.certification_kind:
  values:
    PADI:           "PADI scuba (recreational)"
    SSI:            "Scuba Schools International"
    IFMGA:          "International Federation of Mountain Guides Associations"
    ITRA:           "Indian Travelers' & Trekkers' Association"
    PGAI:           "Paragliding Association of India"
    IMF:            "Indian Mountaineering Foundation"
    AAT:            "Approved Adventure Tour Operator (MoT)"
    RBI_safety:     "Rafting Body of India safety certification"
    SAA:            "Skydiving Association of America (USPA)"
    INSA:           "Indian National Skydiving Association"
    BBA:            "Balloon Federation operating license"
    state_safari:   "State forest department safari permit"
    USCG:           "US Coast Guard kayak instructor"
    other_certified: "Other recognized international body"

eligibility.fitness_requirement:
  values: [low, moderate, high, expert]

eligibility.contraindications:
  values:
    cardiac_condition, hypertension_uncontrolled, asthma_moderate,
    asthma_severe, diabetes_brittle, epilepsy_uncontrolled, pregnancy,
    recent_surgery_6_months, recent_fracture_6_months, ear_problems,
    sinus_problems, vertigo, severe_obesity, severe_anxiety_disorder,
    blood_thinners

weather_policy.auto_cancel_conditions:
  values:
    high_wind_above_30kmph, lightning_within_10km, fog_visibility_under_500m,
    rain_above_moderate, water_current_above_safe_threshold,
    sea_swell_above_2m, snowstorm, monsoon_active_period,
    avalanche_advisory_active

participant_health_declarations[].pre_existing_conditions:
  values: (same as eligibility.contraindications + 'none' + 'other_disclosed')

experience_level:
  values: [first_time, occasional, experienced, certified]

tandem_or_solo:
  values: [tandem, solo, either]

instructor.languages_spoken / activity_request.instructor_language:
  values: [en, hi, te, ta, kn, ml, mr, bn, gu, pa, fr, es, de, ja, zh]

CancellationResult.cancellation_reason:
  values:
    user_changed_mind, weather_unsafe, equipment_failure,
    operator_overbooked, participant_health_disclosed_post_book,
    minimum_party_not_met, government_advisory, natural_disaster

6. TTBS DIMENSIONS

TIME (weight = 0.15):
  signals_used:
    - earliest available slot in preferred_time_window
    - briefing_starts_at fits user's arrival window
  weighting:
    slot_fit: 0.70
    briefing_overlap: 0.30
  user_band_handling:
    fast: prefer first-available
    balanced: standard
    flexible: 3-day window OK

TASTE (weight = 0.25):
  signals_used:
    - activity_grade match with experience_level
    - tandem_or_solo match
    - media_capture_included_required match
    - instructor_language match
    - DNA repeat operator
  weighting:
    grade_experience_fit: 0.30
    tandem_solo_match: 0.20
    media_match: 0.20
    instructor_language: 0.15
    dna_repeat: 0.15

BUDGET (weight = 0.20):
  signals_used:
    - pricing.total_for_party_inr
    - media_capture_inr (add-on cost)
  weighting:
    total: 0.85
    addons: 0.15
  user_band_handling:
    ok: cheapest passing safety + grade-fitness floors
    good: balanced
    great: premium operator with rescue team + best gear

SAFETY (weight = 0.40):
  signals_used:
    - operator.aat_recognition (boost when TRUE)
    - operator.certifying_bodies covers activity_kind (HARD filter for scuba/paragliding/skydive)
    - operator.years_operating ≥3 floor
    - operator.incident_count_past_12_months / participants_per_year (lower preferred)
    - operator.rescue_team_onsite (HARD for water / high-altitude / high-impact)
    - operator.medical_kit_certified (HARD)
    - operator.insurance_covers_participants (HARD)
    - instructor.certification valid + active
    - instructor.years_instructing ≥2 floor for advanced grade
    - equipment.last_service_dates within 12 months
    - eligibility checks pass for ALL participants
    - weather_policy.auto_refund_on_weather (boost when TRUE)
    - nearest_hospital_distance_km ≤30
  weighting:
    aat_certifying: 0.25
    rescue_team_medical: 0.15
    insurance: 0.10
    instructor_cert: 0.15
    equipment_freshness: 0.10
    incident_history: 0.15
    hospital_proximity: 0.10
  user_band_handling:
    fast: floor years_operating to 2
    balanced: floor 3
    great: AAT-recognized + 7+ yrs + 0 incidents past 12 months

Locked weights: time 0.15 / taste 0.25 / budget 0.20 / safety 0.40. Safety dominates — adventure has irreducible risk; the catalog must filter out under-insured / under-certified / under-rescued operators before TTBS even runs.


7. COMPLETION CONTRACT

POST /api/v1/cpc/mcp_provider/<your_partner_id>
Body:
{
  "intent":           "travel.book_adventure_activity",
  "external_id":      "<booking_id>",
  "request_id":       "<request_id>",
  "amount_inr":       450,    // NET partner commission only
  "gst_inr":          81,
  "tips_inr":         0,
  "pass_through_inr": 4500,   // remitted to operator (instructor + equipment + insurance + tickets)
  "closed_at":        "2026-10-12T13:00:00+05:30",
  "status":           "completed",
  "activity_kind":    "paragliding",
  "activity_grade":   "beginner",
  "participants":     2,
  "operator_id":      "bir_skies",
  "aat_recognized":   true
}

User pays full fee. Partner's commission (~₹450) is amount_inr. Operator remittance is pass_through_inr. TOMO charges 10% × ₹450. HMAC-SHA256, 5-min replay.

Weather-cancel bookings file status: cancelled with cancellation_reason: weather_unsafe and amount_inr: 0 (no commission earned on weather cancels).


8. WIDGET

AdventureActivityWidget.

Field mapping:
  - activity_kind + activity_grade pill
  - operator.name + AAT badge when recognized
  - certifying_bodies pill row
  - rating.average_score + review_count → star pill
  - pricing.total_for_party_inr → big price
  - duration_hours → "1.5h activity"
  - rescue_team_onsite → "Rescue team" badge
  - insurance_covers_participants → "Insured ₹X cover" pill
  - weather_policy.auto_refund_on_weather → "Weather refund" pill
  - eligibility.contraindications (when user matches) → safety warning

9. CACHING POLICY

Call TTL Rationale
search_activities 90s Slot availability moves
get_activity_detail 30min Briefing content stable; weather flagging volatile
hold_activity_slot NO CACHE Stateful lock
confirm_activity NO CACHE Idempotent
cancel_activity NO CACHE
Operator AAT / certification verification 7d MoT updates monthly

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_OPERATOR_MATCH 200 (empty) None n/a
SLOT_TAKEN 409 Hold race-lost UI re-queries
HOLD_EXPIRED 410 Hold elapsed UI re-holds
CERTIFICATION_EXPIRED 422 Instructor cert lapsed No
PARTICIPANT_INELIGIBLE 422 Health / age / weight fails No (UI re-asks)
CONTRAINDICATION_DISCLOSED 422 Pre-existing condition flagged No (UI re-routes / refuses)
EQUIPMENT_NOT_SERVICED 422 last_service older than 12 months No (operator removed)
INDEMNITY_NOT_SIGNED 422 indemnity_e_signed=false at confirm No
PAYMENT_FAILED 402 Gateway declined No
WEATHER_CANCEL 200 (advisory) Auto-full-refund n/a
EVACUATION_PLAN_MISSING 422 Operator without evac plan filtered No
CANCELLED_TOO_LATE 410 Past last_cancellable_at No

11. SANDBOX → PRODUCTION CHECKLIST

[ ] All five tools implemented
[ ] At least 30 operators across 10+ activity kinds + locations
[ ] Every AAT recognition verifiable on Ministry of Tourism's adventure list
[ ] Every certifying body (PADI / PGAI / ITRA etc.) license verifiable
[ ] Every instructor certification id verifiable; expiry checked
[ ] Equipment service-dates audited (sample of 5 operators)
[ ] Insurance per-participant cover ≥₹2,00,000 minimum (TOMO floor)
[ ] Rescue team + medical kit + hospital proximity validated for water / high-altitude
[ ] Indemnity form per BIS / industry-standard wording reviewed
[ ] Auto-refund on weather cancel tested end-to-end
[ ] Cancellation policy publishes before payment
[ ] HMAC signing verified on test CPC webhook
[ ] amount_inr in CPC is COMMISSION (NET); pass_through_inr is operator total
[ ] All controlled vocabularies respected
[ ] No forbidden fields anywhere
[ ] SLA p95 met
[ ] Privacy policy + grievance officer contact uploaded
[ ] Customer support: 24×7 adventure helpline + emergency escalation
[ ] Incident reporting pipeline verified (operator → TOMO → MoT)
[ ] Health declarations encrypted at rest; deleted 90 days post-activity

12. ANTI-FABRICATION RULES

RULE 1: AAT (Ministry of Tourism Approved Adventure Tour Operator)
        recognition flag must match MoT public list. Re-checked weekly.

RULE 2: Certifying bodies (PADI / PGAI / ITRA / IFMGA / IMF etc.) must be
        verifiable on the issuing body's register. Re-checked weekly.

RULE 3: Instructor certification id verifiable on certifying body's
        register; expired = ingest reject.

RULE 4: incident_count_past_12_months is auditable; partner cannot suppress.
        TOMO maintains a shadow incident log from MoT advisories + insurer
        claim data + user reports.

RULE 5: Equipment last_service_dates must reflect documented service logs.
        Service-log audits on 5 random operators per quarter.

RULE 6: rescue_team_onsite + medical_kit_certified are HARD filters for
        scuba / paragliding / skydive / rafting / high-altitude trek;
        partner false-flagging = customer-harm + suspension.

RULE 7: insurance_covers_participants must be ≥₹2,00,000 per participant;
        below = ingest reject. Real insurer policy must be verifiable.

RULE 8: No paid_placement, ad_bid, sponsored_rank, featured_operator.

RULE 9: rating.average_score from completed sessions only; review_count
         from verified bookings only.

RULE 10: No success_guarantee or "100% safe" text. Adventure has residual
         risk; absolute claims forbidden. TOMO ingest scans for "100% safe",
         "guaranteed safe", "zero risk."

RULE 11: amount_inr is partner COMMISSION only. Operator remittance is
         pass_through_inr. Inflated commission base = audit.

RULE 12: AI-generated activity photos forbidden. Real recent photos required.

RULE 13: Health declarations are sensitive PII. Partner must encrypt at
         rest, delete 90 days post-activity, never use for marketing.

RULE 14: Indemnity form MUST be e-signed before activity start. Operators
         hand-signing onsite without TOMO booking link breaks audit trail.

RULE 15: Weather cancellations auto-refund full amount within 24h
         regardless of policy windows; partner cannot block. TOMO audits.

RULE 16: Information-completeness score (hidden rank factor weight 0.10)
         rewards full §4 shape.

RULE 17: Operators with incident in past 12 months MUST disclose;
         concealment = suspension. Disclosed incidents trigger TOMO
         editorial review (not ban, but downranked for 6 months).

VERSION HISTORY

v1.0.0 — 2026-05-14 — Initial spec. AAT-recognized operators with verified
                       certifying bodies (PADI/PGAI/ITRA/IFMGA/IMF).
                       Per-participant insurance ≥₹2L floor. Indemnity
                       e-signed pre-activity. Weather auto-refund.
                       Safety-dominant TTBS (0.40). Net-commission base.
                       Health declarations encrypted, 90-day deletion.