mobility.book_ev_with_charge_planning — Full Intent Specification
INTENT NAMESPACE: mobility
INTENT NAME: book_ev_with_charge_planning
FULL ID: mobility.book_ev_with_charge_planning
VERSION: v1.0.0
STATUS: draft
LAST UPDATED: 2026-05-10
TTBS WEIGHTS: time 0.30 · taste 0.10 · budget 0.20 · safety 0.40
EV-with-charge-planning is a journey intent where the vehicle is electric AND the route is long enough that one or more charging stops must be planned, scheduled, and verified before booking. It applies across journey kinds (self-drive EV, EV with driver intercity, EV outstation) — the charging logic is the locked novelty. Structural shape: (a) journey_kind enum determines whether driver_meta + party are present; (b) charge_plan block with charge_stops[] is first-class — each stop has charger_kind, expected SoC at arrival, expected charge minutes, contingency charger; (c) battery_telemetry in track is mandatory live signal; (d) safety weight 0.40 (highest of any mobility intent — battery range anxiety + charger reliability are first-order safety concerns).
1. NATURAL LANGUAGE COVERAGE
Classifies IN
- "EV cab to Tirupati with charging stops"
- "self-drive EV Hyderabad to Bangalore"
- "rent Tata Nexon EV for weekend with charge planning"
- "EV intercity to Vijayawada plan the chargers"
- "MG ZS EV outstation to Coorg, plan charging"
- "electric car for Hyderabad to Mumbai with stops"
- "EV with driver to Goa, charging route"
- "long range EV trip with charge planning"
- "BluSmart EV intercity"
Classifies OUT — borderline NO
- "EV cab to airport" →
mobility.book_airport_transfer(route too short for charge planning) - "EV auto to MG Road" →
mobility.book_intracity_ride(intra-city, no plan needed) - "rent EV scooter" →
mobility.book_two_wheeler_rental - "buy EV" → no intent (informational)
- "install EV charger at home" →
auto.book_ev_charger_install
MULTI-INTENT TRIGGERS
- "EV intercity Hyderabad to Bangalore and book hotel" →
mobility.book_ev_with_charge_planning+travel.book_hotel - "EV self-drive weekend and FASTag top-up" →
mobility.book_ev_with_charge_planning+pay.fastag_topup - "EV outstation to Goa 4N5D with sightseeing" →
mobility.book_ev_with_charge_planning(uses outstation shape internally) + nothing else if entirely EV
2. INPUT — TOMO → PROVIDER
{
"intent": "mobility.book_ev_with_charge_planning",
"intent_version": "v1.0.0",
"request_id": "req_evcp_8h3k_2026-05-10T08:00:00Z",
"user_session_id": "anon_user_token_or_uid",
"journey_kind": "ev_with_driver_intercity",
"origin": {
"lat": 17.4435,
"lng": 78.3772,
"address": "HITEC City, Hyderabad",
"city": "Hyderabad",
"state_code": "TS",
"country_code": "IN",
"place_kind": "office"
},
"destination": {
"lat": 12.9716,
"lng": 77.5946,
"address": "MG Road, Bangalore",
"city": "Bangalore",
"state_code": "KA",
"country_code": "IN",
"place_kind": "hotel"
},
"intermediate_stops": [
{
"lat": 16.5062,
"lng": 80.6480,
"address": "Vijayawada food court NH-65",
"city": "Vijayawada",
"stop_kind": "food_break",
"expected_minutes": 30
}
],
"trip_kind": "one_way",
"scheduled_pickup_iso": "2026-05-11T06:00:00+05:30",
"scheduled_return_iso": null,
"user_driving_profile": {
"user_age": 28,
"license_state_code": "TS",
"license_class_includes_lmv": true,
"license_held_for_years": 6,
"license_verified_with_partner": false,
"ev_familiarity_level": "moderate",
"international_license": false
},
"party": {
"passenger_count": 3,
"luggage_pieces": 4,
"luggage_size": "checkin",
"minor_count": 0,
"senior_count": 0
},
"preferences": {
"vehicle_kinds_acceptable": ["ev_sedan", "ev_suv", "ev_premium_sedan"],
"ev_min_battery_capacity_kwh": 50,
"ev_min_real_range_km": 400,
"max_charge_stops": 2,
"max_total_charge_minutes": 60,
"preferred_charger_kinds": ["ccs2_fast", "ccs2_ultrafast"],
"acceptable_charger_kinds": ["ccs2_fast", "ccs2_ultrafast", "type2_ac"],
"min_charger_kw": 50,
"min_arrival_battery_pct": 20,
"target_arrival_battery_pct": 30,
"budget_band": "good",
"budget_max_inr": 16000,
"ac_required": true,
"female_driver_required": false,
"wheelchair_accessible_required": false,
"max_seat_capacity_min": 4,
"driver_must_speak_locales": ["en-IN", "te-IN", "kn-IN"],
"no_pool": true
},
"route_context": {
"estimated_distance_km": 570.0,
"estimated_duration_min": 540,
"estimated_climbs_meters": 180,
"expected_average_speed_kmh": 65,
"expected_temperature_celsius": 32,
"involves_toll": true,
"toll_count": 7,
"states_crossed": ["TS", "AP", "KA"],
"state_border_count": 2,
"involves_highway": true,
"primary_highway_codes": ["NH-65", "NH-44"],
"is_overnight": false,
"is_late_night_departure": false,
"is_late_night_arrival": false
},
"context": {
"user_locale": "en-IN",
"user_currency_pref": "INR",
"trip_purpose": "leisure",
"trust_signals": {
"is_repeat_customer": true,
"prior_ev_journeys_with_partner": 2,
"user_account_age_days": 312,
"fastag_present": true,
"fastag_balance_inr": 1840
}
}
}
| Field | Type | Constraint | Notes |
|---|---|---|---|
intent |
string | REQUIRED, equals "mobility.book_ev_with_charge_planning" |
|
journey_kind |
enum | REQUIRED, see §5 | switches whether driver_meta is present |
origin.state_code |
string | REQUIRED | |
destination.state_code |
string | REQUIRED | |
intermediate_stops |
array | REQUIRED, may be empty | |
trip_kind |
enum | REQUIRED, STRICT one_way | round_trip |
|
scheduled_pickup_iso |
ISO_DATETIME | REQUIRED | |
user_driving_profile.ev_familiarity_level |
enum | REQUIRED, see §5 | self-drive only; ignored for chauffeur |
preferences.ev_min_battery_capacity_kwh |
int | REQUIRED, ≥0 | |
preferences.ev_min_real_range_km |
int | REQUIRED, ≥0 | real-world range per partner cert |
preferences.max_charge_stops |
int | REQUIRED, ≥0 | |
preferences.max_total_charge_minutes |
int | REQUIRED, ≥0 | total cumulative, all stops |
preferences.preferred_charger_kinds |
array |
REQUIRED, ≥1 | see §5 |
preferences.acceptable_charger_kinds |
array |
REQUIRED, ≥1 | superset of preferred |
preferences.min_charger_kw |
int | REQUIRED, ≥0 | |
preferences.min_arrival_battery_pct |
int | REQUIRED, 0-100 | safety floor |
preferences.target_arrival_battery_pct |
int | REQUIRED, 0-100 | preferred |
route_context.estimated_climbs_meters |
int | REQUIRED, ≥0 | drives range estimation |
route_context.expected_temperature_celsius |
int | REQUIRED | drives battery efficiency |
route_context.expected_average_speed_kmh |
int | REQUIRED, ≥0 | drives consumption modeling |
route_context.states_crossed |
array |
REQUIRED, ≥1 |
Anti-fabrication preamble (universal): no paid placement, no urgency text, no commission-influenced fields.
3. PROVIDER TOOLS
Tool 1: get_ev_journey_estimates
PURPOSE: return EV journey options with full charge plans
INPUT: §2 request body
OUTPUT: { options: EvJourneyOption[], result_token, expires_at }
SLA: p50 < 1000ms, p95 < 2500ms (charge planning is compute-intensive)
RATE LIMIT: ≤ 1/sec per user
Tool 2: book_ev_journey
PURPOSE: commit a journey with charge plan
INPUT: { option_id, payment_token, request_id, idempotency_key, user_phone, otp_required, charge_plan_token }
OUTPUT: { journey_ref, status, vehicle, driver, charge_plan_locked, fare_quote }
SLA: p95 < 6000ms
IDEMPOTENCY: REQUIRED on idempotency_key
Tool 3: track_ev_journey
PURPOSE: live journey state including battery + charge-stop progress
INPUT: { journey_ref, request_id }
OUTPUT: EvJourneyTrack (§4)
SLA: p95 < 500ms
RATE LIMIT: ≤ 1 every 5s
Tool 4: cancel_ev_journey
PURPOSE: cancel
INPUT: { journey_ref, reason, request_id }
OUTPUT: { status, cancellation_charge_inr, refund_amount_inr }
SLA: p95 < 2000ms
Tool 5: update_charge_plan
PURPOSE: re-plan charging mid-trip if charger unavailable / fails
INPUT: { journey_ref, failed_stop_index, request_id }
OUTPUT: { revised_charge_stops, revised_total_minutes, revised_fare_inr, status }
SLA: p95 < 2000ms
Tool 6: confirm_charge_at_stop
PURPOSE: partner records actual charge session (kWh in, minutes, cost)
INPUT: { journey_ref, stop_index, kwh_added, charge_minutes, cost_inr, charger_id, request_id }
OUTPUT: { acknowledged: true, battery_pct_observed, stop_completion_iso }
SLA: p95 < 1500ms
Tool 7: rate_ev_journey
PURPOSE: post-journey rating with charge experience axis
INPUT: { journey_ref, rating_5star, charge_experience_5star, comment, tip_inr, request_id }
OUTPUT: { acknowledged: true }
SLA: p95 < 800ms
Tool 8: share_journey_status
PURPOSE: tokenized live tracking URL (includes battery strip)
INPUT: { journey_ref, recipient_phone_optional, request_id }
OUTPUT: { share_url, expires_at }
SLA: p95 < 500ms
All eight REQUIRED.
4. RESPONSE SHAPE
EvJourneyOption (returned by get_ev_journey_estimates)
id: string, REQUIRED
option_token: string, REQUIRED
charge_plan_token: string, REQUIRED
expires_at: ISO_DATETIME, REQUIRED
journey_kind: STRICT ENUM, REQUIRED
vehicle_kind: STRICT ENUM, REQUIRED # ev_* values only
vehicle_class: STRICT ENUM, REQUIRED
display_label: string, REQUIRED # "MG ZS EV Excite Pro 2024"
make: string, REQUIRED
model: string, REQUIRED
variant: string, REQUIRED
year_of_manufacture: int, REQUIRED
seat_capacity: int, REQUIRED, ≥1
luggage_capacity: STRICT ENUM, REQUIRED
ev_specs:
battery_capacity_kwh: float, REQUIRED, ≥0
battery_chemistry: STRICT ENUM, REQUIRED # see §5
warm_state_real_range_km: int, REQUIRED # at expected temp + speed
cold_state_real_range_km: int, REQUIRED # for high-altitude or cold ops
ac_consumption_kwh_per_hour: float, REQUIRED
charger_kinds_supported: array<STRICT ENUM>, REQUIRED, ≥1
max_charge_kw_dc: int, REQUIRED, ≥0
max_charge_kw_ac: int, REQUIRED, ≥0
battery_health_pct: int, REQUIRED, 0-100 # honest, audited
battery_age_months: int, REQUIRED, ≥0
start_battery:
pct_at_dispatch: int, REQUIRED, 0-100
km_range_at_dispatch: int, REQUIRED
expected_pct_at_pickup: int, REQUIRED, 0-100 # may be slightly less by drive to pickup
charge_plan:
total_stops: int, REQUIRED, ≥0
total_charge_minutes: int, REQUIRED, ≥0
total_charge_cost_inr: INR_INTEGER, REQUIRED, ≥0
total_added_kwh: float, REQUIRED, ≥0
arrival_battery_pct: int, REQUIRED, 0-100 # must be ≥ min_arrival_battery_pct
arrival_battery_km_range: int, REQUIRED, ≥0
charge_stops: array, REQUIRED, may be empty
- stop_index: int, REQUIRED, ≥0
charger_id: string, REQUIRED
charger_brand: string, REQUIRED
charger_kind: STRICT ENUM, REQUIRED
charger_kw: int, REQUIRED, ≥0
lat: float, REQUIRED
lng: float, REQUIRED
address: string, REQUIRED
city: string, REQUIRED
state_code: string, REQUIRED
arrival_eta_iso: ISO_DATETIME, REQUIRED
arrival_battery_pct: int, REQUIRED, 0-100
target_battery_pct: int, REQUIRED, 0-100
expected_charge_minutes: int, REQUIRED
expected_kwh_added: float, REQUIRED
expected_cost_inr: INR_INTEGER, REQUIRED
charger_compatibility_verified: boolean, REQUIRED
charger_availability_check_iso: ISO_DATETIME, REQUIRED
charger_uptime_30d_pct: float, REQUIRED, 0-1
backup_charger_within_5km: boolean, REQUIRED
backup_charger_id: string, REQUIRED # may be empty if backup_charger_within_5km=false
amenities_at_stop: array<STRICT ENUM>, REQUIRED, may be empty # see §5
stop_kind: STRICT ENUM, REQUIRED # planned_charge | also_food_break | also_washroom
eta:
pickup_minutes: int, REQUIRED
drive_minutes_total: int, REQUIRED # excludes charging
charge_minutes_total: int, REQUIRED
total_minutes: int, REQUIRED # drive + charge
arrival_eta_iso: ISO_DATETIME, REQUIRED
fare:
total_inr: INR_INTEGER, REQUIRED
base_inr: INR_INTEGER, REQUIRED
per_km_inr: float, REQUIRED
total_km_charged: float, REQUIRED
charge_cost_inr: INR_INTEGER, REQUIRED # who pays = depends on journey_kind
charge_cost_borne_by: STRICT ENUM, REQUIRED # user | partner | shared (see §5)
driver_allowance_inr: INR_INTEGER, REQUIRED # 0 if self_drive
night_halt_charge_inr: INR_INTEGER, REQUIRED
toll_inr: INR_INTEGER, REQUIRED
toll_count: int, REQUIRED, ≥0
state_border_charge_inr: INR_INTEGER, REQUIRED
state_border_count: int, REQUIRED, ≥0
ac_charge_inr: INR_INTEGER, REQUIRED
late_night_charge_inr: INR_INTEGER, REQUIRED
parking_charge_inr: INR_INTEGER, REQUIRED
platform_fee_inr: INR_INTEGER, REQUIRED
gst_inr: INR_INTEGER, REQUIRED
rider_tip_optional_inr: INR_INTEGER, REQUIRED
fare_breakdown_text: string, REQUIRED
is_upfront_fare: boolean, REQUIRED
fare_locked_until_iso: ISO_DATETIME, REQUIRED
vehicle_amenities:
ac: boolean, REQUIRED
heat_pump: boolean, REQUIRED # better cold-weather efficiency
preconditioning_supported: boolean, REQUIRED # battery preheats while charging
music_on_demand: boolean, REQUIRED
charging_port_inside_cabin: boolean, REQUIRED # for user device, not the EV itself
bottled_water: boolean, REQUIRED
child_seat_available: boolean, REQUIRED
child_seat_kind: STRICT ENUM, REQUIRED
wheelchair_accessible: boolean, REQUIRED
spare_tyre_present: boolean, REQUIRED
first_aid_kit_present: boolean, REQUIRED
fire_extinguisher_lithium_safe: boolean, REQUIRED # required for EV
vehicle_meta:
age_years: int, REQUIRED
fuel_kind: STRICT ENUM, REQUIRED # always ev_full
emission_norm: STRICT ENUM, REQUIRED # always ev
registration_state_code: string, REQUIRED
vehicle_class_certification: STRICT ENUM, REQUIRED
comprehensive_insurance: boolean, REQUIRED
insurance_valid_until_iso: ISO_DATE, REQUIRED
fitness_certificate_valid_until_iso: ISO_DATE, REQUIRED
permit_kind: STRICT ENUM, REQUIRED
permit_states_covered: array<string>, REQUIRED, ≥1
vehicle_color: string, REQUIRED
last_serviced_iso: ISO_DATE, REQUIRED
battery_last_diagnostic_iso: ISO_DATE, REQUIRED
odometer_reading_km: int, REQUIRED, ≥0
driver_meta: # null/absent if journey_kind = self_drive_ev
driver_id: string, REQUIRED
display_name: string, REQUIRED
photo_url: URL, REQUIRED
rating_avg: float, REQUIRED, 0-5
ev_journeys_completed: int, REQUIRED, ≥0 # not just any rides; EV-specific
ev_certification_held: boolean, REQUIRED
partner_account_age_days: int, REQUIRED, ≥0
languages_spoken: array<RFC_3066_LOCALE>, REQUIRED, ≥1
female_driver: boolean, REQUIRED
age_band: STRICT ENUM, REQUIRED
highway_experience_years: int, REQUIRED, ≥0
charge_planning_familiarity: STRICT ENUM, REQUIRED # see §5
driver_kyc: # null/absent if journey_kind = self_drive_ev
dl_verified: boolean, REQUIRED
dl_number_masked: string, REQUIRED
dl_valid_until_iso: ISO_DATE, REQUIRED
background_check_passed: boolean, REQUIRED
background_check_iso: ISO_DATETIME, REQUIRED
badge_id_displayed: boolean, REQUIRED
fatigue_compliance_certified: boolean, REQUIRED
rider_qualification_for_user: # populated only if journey_kind = self_drive_ev
min_user_age_required: int, REQUIRED, ≥18
min_license_held_years: int, REQUIRED, ≥0
ev_familiarity_required: STRICT ENUM, REQUIRED
user_meets_qualification: boolean, REQUIRED
qualification_failure_reason: STRICT ENUM, REQUIRED
safety_features:
sos_button_in_app: boolean, REQUIRED
trip_share_supported: boolean, REQUIRED
in_app_chat_supported: boolean, REQUIRED
in_app_call_supported: boolean, REQUIRED
emergency_contact_alerts: boolean, REQUIRED
cctv_in_cab: boolean, REQUIRED
panic_alert_to_local_police: boolean, REQUIRED
driver_drowsiness_detection: boolean, REQUIRED # null if self_drive
speed_limit_governing: boolean, REQUIRED
highway_assist_telematics: boolean, REQUIRED
battery_thermal_warning_alerts: boolean, REQUIRED
charger_failure_realtime_alert: boolean, REQUIRED # critical for EV journey
contingency_tow_to_charger: boolean, REQUIRED # if battery fails
emergency_charger_swap_window_minutes: int, REQUIRED # SLA for finding alternate
route_quality:
estimated_distance_km: float, REQUIRED
estimated_duration_min: int, REQUIRED
highway_kms: float, REQUIRED
inner_road_kms: float, REQUIRED
primary_highway_codes: array<string>, REQUIRED
toll_passes: array, REQUIRED, may be empty
cancellation:
free_cancel_until_iso: ISO_DATETIME, REQUIRED
cancel_charge_within_24h_inr: INR_INTEGER, REQUIRED
cancel_charge_within_2h_inr: INR_INTEGER, REQUIRED
no_show_charge_inr: INR_INTEGER, REQUIRED
partner_cancel_compensation_inr: INR_INTEGER, REQUIRED
refund_processing_days: int, REQUIRED, ≥0
fastag_check:
toll_count_on_route: int, REQUIRED, ≥0
fastag_required: boolean, REQUIRED
estimated_toll_total_inr: INR_INTEGER, REQUIRED
user_fastag_balance_sufficient: boolean, REQUIRED
freshness:
data_last_synced_iso: ISO_DATETIME, REQUIRED
_provider:
name: string, REQUIRED
tomo_partner_id: string, REQUIRED
partner_tier: STRICT ENUM, REQUIRED
deep_link: URL, REQUIRED
customer_support_phone: string, REQUIRED
customer_support_24x7: boolean, REQUIRED
in_app_chat_supported: boolean, REQUIRED
partner_ev_journey_volume_30d: int, REQUIRED, ≥0
partner_charge_plan_success_rate_30d: float, REQUIRED, 0-1
EvJourneyTrack (returned by track_ev_journey)
journey_ref: string, REQUIRED
status: STRICT ENUM, REQUIRED
status_updated_iso: ISO_DATETIME, REQUIRED
status_history: array, REQUIRED, ≥1
leg_progress:
current_leg_index: int, REQUIRED, ≥0
total_legs: int, REQUIRED, ≥1
next_charge_stop_index: int, REQUIRED, ≥-1 # -1 if no more
next_charge_stop_eta_iso: ISO_DATETIME, REQUIRED # may equal future iso
current_leg_distance_remaining_km: float, REQUIRED
battery_telemetry:
current_pct: int, REQUIRED, 0-100
current_km_range_remaining: int, REQUIRED
predicted_pct_at_next_charger: int, REQUIRED, 0-100
predicted_pct_at_destination: int, REQUIRED, 0-100
consumption_kwh_per_100km: float, REQUIRED
ac_consumption_active: boolean, REQUIRED
battery_temperature_celsius: float, REQUIRED
battery_thermal_warning_active: boolean, REQUIRED
range_anxiety_warning_active: boolean, REQUIRED # if predicted_pct_at_destination < min_arrival_pct
charge_session:
active: boolean, REQUIRED
current_charger_id: string, REQUIRED # may be empty if not charging
current_kwh_in: float, REQUIRED, ≥0
current_charge_minutes_so_far: int, REQUIRED, ≥0
charge_kw_observed: int, REQUIRED, ≥0 # 0 if not charging
expected_completion_iso: ISO_DATETIME, REQUIRED # may equal current iso if not charging
driver: # null/absent if self_drive
driver_id: string, REQUIRED
display_name: string, REQUIRED
phone_masked: string, REQUIRED
photo_url: URL, REQUIRED
rating_avg: float, REQUIRED, 0-5
in_app_chat_supported: boolean, REQUIRED
in_app_call_supported: boolean, REQUIRED
cumulative_drive_minutes_today: int, REQUIRED, ≥0
vehicle:
vehicle_kind: STRICT ENUM, REQUIRED
display_label: string, REQUIRED
color: string, REQUIRED
plate_masked: string, REQUIRED
location:
current_lat: float, REQUIRED
current_lng: float, REQUIRED
current_state_code: string, REQUIRED
heading_degrees: float, REQUIRED, 0-360
speed_kmh: float, REQUIRED, ≥0
location_updated_iso: ISO_DATETIME, REQUIRED
status: STRICT ENUM, REQUIRED
eta:
to_next_charge_stop_minutes: int, REQUIRED
to_destination_minutes: int, REQUIRED
revised_total_fare_inr: INR_INTEGER, REQUIRED
ride_safety:
on_route: boolean, REQUIRED
unusual_stop_detected: boolean, REQUIRED
battery_thermal_warning_active: boolean, REQUIRED
range_anxiety_warning_active: boolean, REQUIRED
trip_share_active: boolean, REQUIRED
trip_share_url: URL, REQUIRED
charge_plan_health:
charger_at_next_stop_available: boolean, REQUIRED
charger_at_next_stop_uptime_now: boolean, REQUIRED
backup_charger_path_active: boolean, REQUIRED # true if primary failed and rerouting
support_phone: string, REQUIRED
support_email: string, REQUIRED
Forbidden fields
paid_placement_score | sponsored_rank | promotion_priority |
artificial_demand_text | fake_recent_booking_text |
auto_inflate_warm_state_real_range_km | partner_paid_for_top_listing |
fake_battery_health_pct | fake_charger_uptime_30d_pct |
fake_charge_plan_success_rate_30d | hidden_charger_unavailability
5. CONTROLLED VOCABULARIES
journey_kind
self_drive_ev | ev_with_driver_intercity | ev_with_driver_outstation
vehicle_kind
ev_hatchback | ev_sedan | ev_suv | ev_premium_sedan | ev_premium_suv |
ev_luxury_sedan | ev_luxury_suv | ev_xl_van
vehicle_class
economy | comfort | premium | luxury | xl
EvJourneyOption.luggage_capacity
small | medium | large | xl
vehicle_meta.fuel_kind
ev_full
vehicle_meta.emission_norm
ev
vehicle_meta.permit_kind
tourist | all_india_tourist | contract_carriage | self_drive_permit | aggregator_permit
EV-with-driver intercity REQUIRES tourist | all_india_tourist | contract_carriage. Self-drive EV REQUIRES self_drive_permit.
vehicle_meta.vehicle_class_certification
commercial_yellow_plate | self_drive_yellow_plate | tourist | private | luxury_charter
ev_specs.battery_chemistry
nmc | lfp | lto | nca | unknown_legacy
ev_specs.charger_kinds_supported[] and preferred/acceptable_charger_kinds
ccs2_fast | ccs2_ultrafast | chademo | type2_ac | gb_t | tesla_supercharger | bharat_dc_001
India consumer EVs primarily use CCS2. Type-2 AC for slow home/destination charging.
charge_stops[].charger_kind
Same enum as charger_kinds_supported.
charge_stops[].amenities_at_stop[]
food_court | washroom | rest_lounge | air_conditioning | covered_parking |
shopping | drinking_water | wifi | mobile_charge | atm | ev_charging_only
charge_stops[].stop_kind
planned_charge | also_food_break | also_washroom | also_rest |
backup_swap_only
fare.charge_cost_borne_by
user | partner | shared
Self-drive EV: typically user. Chauffeur intercity/outstation: typically partner (cost rolled into total). Shared = some pre-charging by partner + user pays mid-trip charges.
start_battery.pct_at_dispatch constraint
Must be ≥ 80 unless explicitly justified in fare_breakdown_text.
vehicle_amenities.child_seat_kind
none | infant | toddler | booster | universal
driver_meta.charge_planning_familiarity
new | familiar_5_to_30_journeys | expert_30_plus_journeys
driver_meta.age_band
21-30 | 31-40 | 41-55 | 56+
user_driving_profile.ev_familiarity_level and rider_qualification_for_user.ev_familiarity_required
none | basic | moderate | expert
rider_qualification_for_user.qualification_failure_reason
none | user_age_below_minimum | license_held_too_short |
ev_familiarity_below_required | license_class_invalid | foreign_license_not_accepted
EvJourneyTrack.status
scheduled | driver_assigned | driver_dispatched | driver_arriving |
arrived_at_pickup | otp_required | trip_started | en_route_leg_1 |
approaching_charge_stop | at_charge_stop_charging | charge_complete |
en_route_leg_n | nearby_destination | reached_destination |
charge_plan_amended | range_anxiety_warning | thermal_warning |
completed | cancelled_by_user | cancelled_by_driver | failed
EvJourneyTrack.location.status
heading_to_pickup | at_pickup | trip_in_progress |
approaching_charger | at_charger_charging | leaving_charger |
nearby_destination | at_destination
cancel_ev_journey.reason
user_changed_mind | weather_block | charger_network_widespread_failure |
vehicle_unavailable | safety_concern | medical_emergency |
no_longer_needed | found_alternative | other
context.trip_purpose
commute_to_work | commute_to_home | leisure | family_visit |
business_trip | medical_visit | religious_pilgrimage | wedding_event |
emergency | other
6. TTBS DIMENSIONS
Per-domain weights (locked; EV override safety to 0.40)
mobility (ev_with_charge_planning): { time: 0.30, taste: 0.10, budget: 0.20, safety: 0.40 }
Safety is the highest of any mobility intent (battery thermal events + charger failure + range anxiety are first-order safety concerns). Taste lower (vehicle is the EV; user preferences narrower).
TIME
SIGNALS USED:
- eta.total_minutes (drive + charge) weight 0.40
- charge_plan.total_charge_minutes (lower=better) weight 0.20
- charge_plan.total_stops (lower=better, within max) weight 0.15
- eta.pickup_minutes weight 0.15
- cancellation.refund_processing_days weight 0.10
USER BAND HANDLING:
- max_charge_stops HARD FILTER
- max_total_charge_minutes HARD FILTER
TASTE
SIGNALS USED:
- vehicle.year_of_manufacture weight 0.20
- vehicle.make/model affinity (DNA) weight 0.20
- vehicle_amenities match w/ user prefs weight 0.20
- vehicle_amenities.heat_pump weight 0.10
- vehicle_amenities.preconditioning_supported weight 0.15
- charge_stops[].amenities_at_stop richness weight 0.15
HARD FILTERS:
- ac_required + ac=false → drop
- ev_min_battery_capacity_kwh > vehicle.battery_capacity_kwh → drop
- ev_min_real_range_km > vehicle.warm_state_real_range_km → drop
BUDGET
SIGNALS USED:
- fare.total_inr vs band:
ok → ev_hatchback / ev_sedan economy
good → ev_sedan / ev_suv
great → ev_premium / ev_luxury
- fare.is_upfront_fare=true weight 0.20
- fare.charge_cost_borne_by=partner (no surprise) weight 0.20
- fare.total_charge_cost_inr (lower) weight 0.15
- cancellation.free_cancel_until_iso (later=better) weight 0.10
HARD FILTERS:
- fare.total_inr > preferences.budget_max_inr → drop
SAFETY
SIGNALS USED:
- vehicle_meta.comprehensive_insurance HARD FILTER
- vehicle_meta.permit_kind valid for journey_kind HARD FILTER
- vehicle_meta.permit_states_covered ⊇ states_crossed HARD FILTER
- vehicle_meta.fitness_certificate_valid_until_iso > destination eta HARD FILTER
- ev_specs.battery_health_pct >= 80 HARD FILTER
- charge_plan.arrival_battery_pct >= preferences.min_arrival_battery_pct HARD FILTER
- all charge_stops[].charger_compatibility_verified=true HARD FILTER
- safety_features.fire_extinguisher_lithium_safe HARD FILTER
- safety_features.battery_thermal_warning_alerts weight 0.15
- safety_features.charger_failure_realtime_alert weight 0.20
- safety_features.contingency_tow_to_charger weight 0.15
- safety_features.emergency_charger_swap_window_minutes ≤ 30 weight 0.15
- all charge_stops[].backup_charger_within_5km weight 0.20
- all charge_stops[].charger_uptime_30d_pct ≥ 0.95 weight 0.15
- driver_meta.ev_certification_held (if not self_drive) HARD FILTER
- driver_meta.charge_planning_familiarity (expert preferred) weight 0.10
- is_overnight → safety scales 1.5x
HARD FILTERS:
- female_driver_required → drop male drivers
- any charge_stop has charger_uptime_30d_pct < 0.85 → drop (unreliable)
- any charge_stop has backup_charger_within_5km=false AND charger_uptime_30d_pct < 0.95 → drop
Hidden ranking factor
information_completeness_score weight 0.10.
Charge-plan integrity check
TOMO independently re-validates each charge_stops[].charger_id against the partner's stated charger_uptime_30d_pct via 1% audit (random ping at planned arrival_eta_iso). If mismatch >5%, the partner's partner_charge_plan_success_rate_30d gets adjusted.
7. COMPLETION CONTRACT
POST /api/v1/cpc/mcp_provider/{tomo_partner_id}
X-TOMO-Timestamp: <ms>
X-TOMO-Signature: sha256=<hex>
{
"intent": "mobility.book_ev_with_charge_planning",
"intent_version": "v1.0.0",
"external_id": "BLU-EV-XYZ",
"amount_inr": 15400,
"closed_at": "2026-05-11T15:34:00+05:30",
"request_id": "req_evcp_8h3k_...",
"status": "completed",
"journey_ref": "BLU-EV-XYZ",
"started_at": "2026-05-11T06:08:00+05:30",
"completed_at": "2026-05-11T15:34:00+05:30",
"distance_traveled_km": 583.2,
"drive_minutes_actual": 522,
"charge_minutes_actual": 56,
"promised_total_minutes": 540,
"actual_total_minutes": 578,
"charge_stops_completed": 1,
"charge_stops_planned": 1,
"kwh_added_total": 42.5,
"charge_cost_actual_inr": 680,
"charge_cost_borne_by": "partner",
"states_traversed": ["TS", "AP", "KA"],
"tolls_paid_inr": 1245,
"battery_pct_at_arrival": 28,
"any_thermal_warning": false,
"any_range_anxiety_event": false,
"any_charger_failure": false,
"currency": "INR",
"fare_breakdown_total_inr": 15400,
"rider_tip_inr": 0,
"ratings_pending": true,
"notes": ""
}
Status enum: completed | cancelled_by_user | cancelled_by_driver | failed | rerouted_with_extra_charge | partial_completion_battery_failure | charger_chain_failure_partner_compensated
8. WIDGET
WIDGET TYPE: ev_journey_options
SOURCE: src/widgets/types.ts
TYPE NAME: EvJourneyOptionsPayload
RENDERED IN: components/widgets/EvJourneyOptionsWidget.tsx
Default: 3 stacked rows showing make+model+year, battery_kwh + real_range_km badge, charge plan summary (X stops, Y minutes total), fare with charge_cost_borne_by clarity, key amenities. Tap row → confirmation card with charge-stop timeline (each stop with charger brand, kW, eta, expected charge minutes, backup_charger flag) → "Book". Then EvJourneyTrackingCard with battery strip + charge-session display + range-anxiety indicator.
9. CACHING POLICY
| Call | TTL | Rationale |
|---|---|---|
get_ev_journey_estimates |
60s | charger availability + charge plan must be fresh |
track_ev_journey |
0s | always live (battery telemetry critical) |
book_ev_journey |
0s | |
cancel_ev_journey |
0s | |
update_charge_plan |
0s | |
confirm_charge_at_stop |
0s | |
| Failure responses | 0s |
10. ERROR CODES
| Code | HTTP | Meaning | TOMO behavior |
|---|---|---|---|
NO_VEHICLES_AVAILABLE |
503 | no EV matches | retry or fall back |
OUT_OF_SERVICE_AREA |
400 | origin or destination outside coverage | surface |
STATE_NOT_COVERED_BY_PERMIT |
400 | permit doesn't cover required state | surface |
OPTION_EXPIRED |
410 | option_token / charge_plan_token invalid | re-quote |
CHARGE_PLAN_INFEASIBLE |
400 | route too long for vehicle range + max_stops | surface |
CHARGER_UNAVAILABLE |
503 | one or more planned chargers down | offer revised plan |
CHARGER_INCOMPATIBLE |
400 | vehicle ↔ charger mismatch | surface |
BATTERY_HEALTH_FAIL |
400 | partner's vehicle below health floor | surface alternate vehicle |
BOOKING_NOT_FOUND |
404 | journey_ref doesn't exist | surface |
ALREADY_CANCELLED |
409 | duplicate cancel | idempotent return |
THERMAL_WARNING_TRIP_HALTED |
503 | live thermal event forced halt | surface emergency response |
BACKUP_CHARGER_FAILED |
503 | primary AND backup down | surface tow contingency |
FASTAG_INSUFFICIENT |
400 | balance low | top-up CTA |
WEATHER_BLOCK |
503 | route flagged unsafe | suggest reschedule |
11. SANDBOX → PRODUCTION CHECKLIST
[ ] All §2 inputs validated, request_id echoed
[ ] get_ev_journey_estimates returns ≥3 options for "Hyderabad → Bangalore" with charge plans
[ ] All §4 required fields populated with REAL data
[ ] vehicle_meta + ev_specs truthful + verifiable on demand
[ ] battery_health_pct backed by partner diagnostic logs
[ ] charge_stops[].charger_compatibility_verified actually verified
[ ] charge_stops[].charger_uptime_30d_pct sourced from partner network telemetry
[ ] charge_stops[].backup_charger_within_5km verified via map distance
[ ] Charge-plan-success-rate-30d audited against TOMO 1% sample
[ ] book_ev_journey returns journey_ref + driver assignment within SLA
[ ] track_ev_journey returns battery telemetry ≤5s old
[ ] update_charge_plan triggered on simulated charger failure; new plan within SLA
[ ] confirm_charge_at_stop fires at each completed charge with kWh + cost
[ ] CPC webhook arrives within 60s of journey completion
[ ] HMAC verification passes
[ ] No forbidden fields anywhere
[ ] Trip share URL works with battery strip live
[ ] SOS button + battery thermal alert tested
[ ] Charger network outage simulated; backup charger reroute works
[ ] No paid placement / sponsored signals
[ ] customer_support_24x7 verified by TOMO field call
[ ] All operating-state permits uploaded
[ ] Lithium-safe fire extinguisher verified onboard
12. ANTI-FABRICATION RULES
RULE 1 — No paid placement signals.
RULE 2 — No fake battery_health_pct.
Must come from partner's diagnostic logs (manufacturer OBD, vehicle BMS).
TOMO samples 1% journeys and validates against vehicle BMS readout.
RULE 3 — No fake warm_state_real_range_km.
Range claims must be backed by partner's own real-world telematics across
the user's expected speed + temperature range. Stating range under WLTP
ideal conditions when reality is 70% = breach.
RULE 4 — No fake charger_uptime_30d_pct.
Uptime stats must come from partner's charger network telemetry. TOMO
pings each charger_id at planned arrival_eta_iso. Mismatch >5% = breach.
RULE 5 — No fake charge_plan_success_rate_30d.
RULE 6 — Charger compatibility cannot be fudged.
charger_compatibility_verified=true requires partner-confirmed plug type
match. Fudging CCS2 vs Type-2 = severe safety breach.
RULE 7 — Vehicle plate must match dispatched vehicle.
RULE 8 — Fare displayed must be honored.
is_upfront_fare=true means fare_locked_until_iso. Charge cost is the
ONLY field that may shift if charger_failure_realtime forces a
more-expensive backup, AND user must be notified before extra-charge
charging begins.
RULE 9 — Driver KYC + EV certification claims must be verifiable.
RULE 10 — Fatigue compliance is non-negotiable (driver journeys).
RULE 11 — No hidden charger network outage.
If partner becomes aware of charger network issue post-booking but before
pickup, partner MUST notify user + offer revised plan or full refund.
Silent ride into a known-outage charger = severe breach.
RULE 12 — No surge gaming on charge cost.
Charge cost lines must reflect actual partner contract rates with the
charger network. Inflating charge cost > 110% of network public rate = breach.
RULE 13 — Backup charger is real, not aspirational.
backup_charger_within_5km=true means an alternate charger has been
identified by lat/lng. Stating true with no backup_charger_id = breach.
RULE 14 — Battery thermal warnings are escalated, not suppressed.
battery_thermal_warning_active=true MUST trigger user notification +
partner safety dispatch. Hiding thermal events to avoid trip cancellation
cost = severe safety breach.
RULE 15 — Range anxiety warnings are honest.
range_anxiety_warning_active=true when predicted_pct_at_destination falls
below preferences.min_arrival_battery_pct. Suppressing this warning to
keep user committed to trip = safety breach.
RULE 16 — No commission-based response shaping.
RULE 17 — Cancel charges must match displayed thresholds.
VERSION HISTORY
v1.0.0 — 2026-05-10 — Initial spec