A single source of truth for every vehicle API field displayed in the report. The schema defines what each field is, how to format it, and where it belongs. Both the API (PDF generation) and WUI (browser report) consume the same schema. Brand-agnostic engine — each car manufacturer provides its own field mappings.
bnc-cpt-api/src/python/cpt-api/app/services/
├── report_field_schema/
│ ├── __init__.py # Engine: FieldDef, FieldType, RegionContext, format_field(),
│ │ # REGION_DEFAULTS, FORMATTERS, build_context(), format_report()
│ ├── tesla_fields.py # Tesla field mappings (250 fields)
│ ├── bmw_fields.py # (future) BMW field mappings
│ ├── volvo_fields.py # (future) Volvo field mappings
│ └── ... # One file per brand
└── tesla_provider_normalization.py # Integration point — calls format_report(),
# adds formattedFields + regionContext to report
┌──────────────────────────────────────────────────────────────────┐
│ report_field_schema/__init__.py — THE ENGINE (brand-agnostic) │
│ │
│ FieldDef dataclass — metadata for one field │
│ FieldType enum — selects which formatter to use │
│ RegionContext dataclass — region, locale, units, currency │
│ REGION_DEFAULTS dict — region → default units/currency │
│ FORMATTERS dict — FieldType → formatter function │
│ build_context() region + gui_settings + locale → RegionContext│
│ format_field() field_path + raw_value + context → string │
│ format_report() raw_report + brand_fields + context → fields[]│
└──────────────┬───────────────────────────────────────────────────┘
│ uses
▼
┌──────────────────────────────────────────────────────────────────┐
│ report_field_schema/tesla.py — TESLA FIELD MAPPINGS │
│ │
│ TESLA_FIELDS: dict[str, FieldDef] │
│ key = "{source}.{api_field}" e.g. "vehicle_state.df" │
│ value = FieldDef (all metadata for that field) │
│ │
│ Every Tesla API field that appears in the report is listed here.│
│ Adding a field = one entry. Removing = delete the entry. │
└──────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ report_field_schema/bmw.py — BMW FIELD MAPPINGS (future) │
│ │
│ BMW_FIELDS: dict[str, FieldDef] │
│ Same FieldDef, same FieldTypes, same formatters. │
│ Only the API field paths and labels change. │
└──────────────────────────────────────────────────────────────────┘
class FieldType(str, Enum):
"""Every Tesla field maps to exactly one type. The type selects the formatter."""
# Boolean / state
OPEN_CLOSE = "open_close" # 0/1 → Closed/Open
BOOLEAN = "boolean" # true/false → Yes/No
ENABLED = "enabled" # true/false → Enabled/Disabled
ACTIVE = "active" # true/false → Active/Inactive
# Numeric with units
DISTANCE = "distance" # miles → km (if metric region)
SPEED = "speed" # mph → km/h (if metric region)
TEMPERATURE = "temperature" # °F → °C (if metric region)
PRESSURE = "pressure" # psi/bar (from gui_settings)
PERCENT = "percent" # append %
POWER_KW = "power_kw" # append kW
ENERGY_KWH = "energy_kwh" # append kWh
CURRENT_A = "current_a" # append A
VOLTAGE_V = "voltage_v" # append V
CURRENCY = "currency" # 0 → "Included", else €34,990 / $34,990
DURATION_SEC = "duration_sec" # 2700 → "45 min", 7200 → "2h 0m"
DURATION_MIN = "duration_min" # 90 → "1h 30m"
DURATION_MS = "duration_ms" # milliseconds → "3:45" (media)
TIMESTAMP_MS = "timestamp_ms" # unix ms → locale date string
TIMESTAMP_ISO = "timestamp_iso" # ISO 8601 → locale date string
DATE = "date" # date string → locale date
# Enums / coded values
SNAKE_CASE = "snake_case" # all_week → "All Week"
CLIMATE_MODE = "climate_mode" # dog_mode → "Dog Mode"
SEAT_HEATER = "seat_heater" # 0-3 → Off/Low/Medium/High
SEAT_FAN = "seat_fan" # 0-3 → Off/Low/Medium/High
CHARGING_STATE = "charging_state" # Charging/Complete/Stopped/etc.
SHIFT_STATE = "shift_state" # P/R/N/D → Park/Reverse/Neutral/Drive
# Pass-through
TEXT = "text" # display as-is
MONO = "mono" # display as-is, monospace (VINs, IDs)
VERSION = "version" # software version string, as-is
HIDDEN = "hidden" # do not display (tokens, internal IDs)
class FieldDef:
"""Metadata for a single Tesla API field."""
# Identity
key: str # "{source}.{api_field}" — unique registry key
label: str # Human-readable label for the report
type: FieldType # Selects the formatter
# Source mapping
source: str # Tesla API object: vehicle_state, charge_state, climate_state,
# vehicle_config, vehicle_data, gui_settings, vehicle_specs, etc.
api_field: str # Field name within the source object
# Presentation
section: str # Report section this field belongs to
panel: str # Sub-panel within the section
mono: bool # Render in monospace font (VIN, IDs)
precision: int | None # Decimal places for numeric values (None = auto)
# Unit context
native_unit: str | None # What Tesla API returns: "mph", "miles", "ms", "sec", "fahrenheit"
display_unit: str | None # Override display unit (normally derived from RegionContext)
# Documentation
description: str | None # Human explanation — also feeds glossary
tesla_docs_ref: str | None # Link to Tesla API docs for this field
# Visibility
plan: str # Minimum plan: "basic" | "pro" | "expert"
hidden: bool # Suppress from customer-facing report
deprecated: bool # Field still in API but no longer shown
| FieldType | Input example | EU output | US output | Rule |
|---|---|---|---|---|
open_close |
0 |
Closed | Closed | 0→Closed, 1→Open |
boolean |
true |
Yes | Yes | true→Yes, false→No |
enabled |
true |
Enabled | Enabled | true→Enabled, false→Disabled |
active |
true |
Active | Active | true→Active, false→Inactive |
distance |
102.5 (mi) |
165.0 km | 102.5 mi | × 1.60934 if metric |
speed |
85 (mph) |
137 km/h | 85 mph | × 1.60934 if metric |
temperature |
72.0 (°F) |
22.2 °C | 72.0 °F | (F-32)×5/9 if metric |
pressure |
2.8 (bar) |
2.8 bar | 40.6 psi | convert per gui_settings |
percent |
42 |
42% | 42% | append % |
power_kw |
11 |
11 kW | 11 kW | append kW |
energy_kwh |
32.5 |
32.5 kWh | 32.5 kWh | append kWh |
current_a |
16 |
16 A | 16 A | append A |
voltage_v |
233 |
233 V | 233 V | append V |
currency |
34990 |
€34,990 | $34,990 | region→symbol, 0→"Included" |
duration_sec |
2700 |
45 min | 45 min | sec→human (Xh Ym) |
duration_min |
90 |
1h 30m | 1h 30m | min→human |
duration_ms |
225000 |
3:45 | 3:45 | ms→M:SS (media) |
timestamp_ms |
1773766748638 |
15 Mar 2026, 14:32 | Mar 15, 2026, 2:32 PM | locale-aware |
timestamp_iso |
2026-03-15T14:32:00Z |
15 Mar 2026, 14:32 | Mar 15, 2026, 2:32 PM | locale-aware |
date |
2024-06-15 |
15 Jun 2024 | Jun 15, 2024 | locale-aware |
snake_case |
all_week |
All Week | All Week | replace _ with space, title case |
climate_mode |
dog_mode |
Dog Mode | Dog Mode | known mapping |
seat_heater |
2 |
Medium | Medium | 0→Off, 1→Low, 2→Medium, 3→High |
seat_fan |
1 |
Low | Low | same as seat_heater |
charging_state |
Charging |
Charging | Charging | pass-through (already readable) |
shift_state |
P |
Park | Park | P→Park, R→Reverse, N→Neutral, D→Drive |
text |
JT3 |
JT3 | JT3 | as-is |
mono |
LRW3E7... |
LRW3E7... | LRW3E7... | as-is, monospace |
version |
2026.2.3 |
2026.2.3 | 2026.2.3 | as-is |
hidden |
(any) | — | — | not rendered |
| Region | Currency | Distance | Speed | Temp | Pressure | Date format |
|---|---|---|---|---|---|---|
| EU | EUR (€) | km | km/h | °C | bar | dd MMM yyyy |
| US | USD ($) | mi | mph | °F | psi | MMM dd, yyyy |
| UK | GBP (£) | mi | mph | °C | psi | dd MMM yyyy |
| CN | CNY (¥) | km | km/h | °C | bar | yyyy-MM-dd |
| CA | CAD ($) | km | km/h | °C | psi | MMM dd, yyyy |
| AU | AUD ($) | km | km/h | °C | psi | dd MMM yyyy |
| JP | JPY (¥) | km | km/h | °C | kPa | yyyy/MM/dd |
| KR | KRW (₩) | km | km/h | °C | bar | yyyy.MM.dd |
| MX | MXN ($) | km | km/h | °C | psi | dd/MM/yyyy |
| AE | AED (د.إ) | km | km/h | °C | bar | dd MMM yyyy |
Note: gui_settings from the vehicle ALWAYS override region defaults. The owner may have set their car to miles in an EU region — we respect that.
Each field declares its section and panel. This defines the report structure:
| Section | Panel | Description |
|---|---|---|
identity |
model |
Make, Model, Year, Vehicle Name |
identity |
registration |
VIN, Region, Software Version |
identity |
ids |
User ID, Vehicle ID, ID String |
state |
security |
Locked, Sentry, Valet, User Present |
state |
controls |
Dashcam, Camera, Remote Start, Autopark |
state |
diagnostics |
Speed limits, Feature bitmask |
access |
access_service |
Access Type, Calendar, Supercharging |
access |
connectivity |
Notifications, Remote Control, Parsed Calendar |
access |
camera_tokens |
Webcam, Dashcam clip, Backseat tokens |
doors |
doors_trunks |
DF, DR, FT, RT, PF, PR |
doors |
windows |
FD, FP, RD, RP windows |
climate |
temperature |
Inside/outside temp, driver/passenger set temps |
climate |
systems |
AC, fan, defrost, HVAC, heater, COP |
climate |
seats |
Seat heaters (FL/FR/RL/RR), seat fans, steering wheel |
battery |
health |
SoH, battery level, usable level, capacity |
battery |
range |
Battery range, estimated range, ideal range |
battery |
condition |
Battery condition, age context, mileage context |
charging |
current_session |
Charging state, power, rate, voltage, amps |
charging |
limits |
Charge limit, limit std, limit max |
charging |
additions |
Miles added (rated/ideal), energy added |
charging |
hardware |
Fast charger type, charger phases, connector |
charging |
schedule |
Off-peak, preconditioning, scheduled departure |
warranty |
coverages |
Basic warranty, battery warranty, odometer limit |
alerts |
recent |
Alert list with date, text, category, code |
specs |
vehicle_specs |
Trim, paint, wheels, interior, production date |
specs |
equipment_price |
Equipment price breakdown |
specs |
option_codes |
Factory option codes |
software |
status |
Installed version, pending, update status |
software |
release_notes |
Release note titles + versions |
media |
playback |
Now playing, source, volume |
gui |
settings |
Distance units, temp units, pressure units |
Add entry to TESLA_FIELDS in tesla_fields.py:
python
"charge_state.new_field": FieldDef(
key="charge_state.new_field",
label="New Field Label",
type=FieldType.PERCENT,
source="charge_state",
api_field="new_field",
section="charging",
panel="current_session",
description="What this field means to the end user",
),
If the field needs a new FieldType, add the type to the enum and
add a formatter function in report_field_schema/__init__.py.
Run tests — the registry has validation that catches:
All formatting logic is in report_field_schema/__init__.py in the FORMATTERS dict.
Each FieldType maps to one function. Change the function, every field of that type updates.
Example: to change how currencies display:
# report_field_schema/__init__.py
def format_currency(value: Any, ctx: RegionContext) -> str:
if not isinstance(value, (int, float)):
return "Not available"
if value == 0:
return "Included"
symbol = CURRENCY_SYMBOLS[ctx.currency]
formatted = f"{value:,.0f}" if value == int(value) else f"{value:,.2f}"
return f"{symbol}{formatted}"
Region defaults are in report_field_schema/__init__.py in REGION_DEFAULTS.
The vehicle's gui_settings override these defaults.
To add a new region:
REGION_DEFAULTS["NO"] = RegionDefaults(
currency="NOK",
currency_symbol="kr",
distance_unit="km",
speed_unit="km/h",
temp_unit="C",
pressure_unit="bar",
date_format="dd.MM.yyyy",
)
tesla_provider_normalization.py builds the report dict and then calls format_report() to add
formattedFields and regionContext. The integration is in build_vehicle_acquisition_outcome().
If formatting fails, the report still returns with empty formattedFields (graceful degradation).
The report endpoint now returns both raw and formatted:
{
"vehicle": { ... },
"vehicleData": { ... },
"formatted_fields": [
{
"key": "vehicle_state.df",
"label": "Driver Front Door",
"value": "Closed",
"raw_value": 0,
"type": "open_close",
"section": "doors",
"panel": "doors_trunks",
"mono": false
},
{
"key": "charge_state.timestamp",
"label": "Battery Timestamp",
"value": "15 Mar 2026, 14:32",
"raw_value": 1773766748638,
"type": "timestamp_ms",
"section": "battery",
"panel": "health",
"mono": false
}
],
"region_context": {
"region": "EU",
"currency": "EUR",
"distance_unit": "km",
"speed_unit": "km/h",
"temp_unit": "C",
"pressure_unit": "bar",
"locale": "en"
}
}
The WUI renders formatted_fields grouped by section → panel. No formatting logic needed in the frontend.
The WUI report component becomes a pure layout engine:
// Group fields by section → panel
const sections = computed(() => {
const grouped = {};
for (const field of report.value.formatted_fields) {
if (!grouped[field.section]) grouped[field.section] = {};
if (!grouped[field.section][field.panel]) grouped[field.section][field.panel] = [];
grouped[field.section][field.panel].push(field);
}
return grouped;
});
<section v-for="(panels, sectionKey) in sections" :key="sectionKey" class="chapter">
<div v-for="(fields, panelKey) in panels" :key="panelKey" class="subpanel">
<h3 class="subpanel-title">{{ panelTitle(panelKey) }}</h3>
<div class="rows">
<div v-for="field in fields" :key="field.key" class="row">
<span class="label">{{ field.label }}</span>
<span class="value" :class="{ mono: field.mono }">{{ field.value }}</span>
</div>
</div>
</div>
</section>
No makeRow(), no displayText(), no formatPercent() — the API handles all of it.
Migrated from
bnc-cpt-api.TESLA-REPORT-FIELD-MAPPING.md(original audit by ysg, 2026-03-17).
src/python/cpt-api/tests/tesla_api_responses.json — older raw Tesla endpoint fixture../bnc-cpt-wui/dat/tmp/sec/tesla-report-data.json — newer normalized report payloadBackend report builder fetches these modules:
| Module | Code location |
|---|---|
vehicle_data |
tesla_fleet.py |
charging_history |
tesla_fleet.py |
recent_alerts |
tesla_fleet.py |
service_data |
tesla_fleet.py |
warranty |
tesla_fleet.py |
release_notes |
tesla_fleet.py |
options |
tesla_fleet.py |
vehicle_specs |
tesla_fleet.py |
Name mapping notes:
- warranty_details → current code maps to warrantyDetails
- specs → current code maps to vehicleSpecs
Not available.Packages must depend on capability groups, not on every field always being present.
| Sample payload section | Fetched now | Rendered now | Target |
|---|---|---|---|
vehicles |
partial | no | low |
vehicle_data |
yes | partial | yes |
charging_history |
yes | partial | yes |
recent_alerts |
yes | yes | yes |
service_data |
yes | partial | yes |
warranty_details |
yes | yes | yes |
release_notes |
yes | partial | yes |
options |
yes | partial | yes |
specs |
yes | partial | yes |
The field comparison was checked against three concrete layers:
src/python/cpt-api/tests/tesla_api_responses.json../bnc-cpt-wui/dat/tmp/sec/tesla-report-data.jsonResult:
- The newer report payload still contains the same broad vehicleData field groups as the older raw fixture
- The main gap is still rendering, not source availability
- This document therefore distinguishes between fetched / present in report payload / rendered
Observed on 2026-03-17: live WUI raw report payloads are not always as complete as the normalized sample.
LRW3E7EK1RC988948) — only carried vehicleData and recentAlerts; chargingHistory, serviceData, warrantyDetails, releaseNotes, options, and vehicleSpecs were nullXP7YGCELXPB059702) — same reduction patternThe report stack must distinguish these states per module:
| State | Meaning |
|---|---|
present |
Module data available |
empty |
Module returned but contains no data |
failed |
Module fetch failed |
unauthorized |
Tesla returned 401/403 |
not_requested |
Module was not fetched |
dropped_during_assembly |
Available upstream but absent in final report object |
The last state matters — current live evidence shows cases where a module is available upstream but absent in the final live report object.
Fields present in audited raw/new payloads but not fully surfaced in the rendered report.
vehicle_state.sentry_mode_availablevehicle_state.valet_pin_neededvehicle_config.dashcam_clip_save_supportedvehicle_state.dashcam_clip_save_availablevehicle_state.notifications_supportedvehicle_state.service_mode_plusvehicle_state.santa_modevehicle_state.speed_limit_mode.current_limit_mphvehicle_state.is_user_presentvehicle_config.webcam_supportedvehicle_config.webcam_selfie_supportedvehicle_state.webcam_availablecharge_state.charge_current_requestcharge_state.charge_current_request_maxcharge_state.charger_phasescharge_state.fast_charger_brandcharge_state.fast_charger_typecharge_state.scheduled_charging_pendingcharge_state.scheduled_charging_start_timecharge_state.supercharger_session_trip_plannercharge_state.trip_chargingclimate_state.allow_cabin_overheat_protectionclimate_state.cabin_overheat_protection_actively_coolingclimate_state.fan_statusclimate_state.is_front_defroster_onclimate_state.is_rear_defroster_onclimate_state.seat_fan_front_leftclimate_state.seat_fan_front_rightclimate_state.steering_wheel_heat_levelvehicle_config.roof_colorvehicle_config.rear_seat_heatersvehicle_config.has_seat_coolingvehicle_config.has_air_suspensionvehicle_config.has_ludicrous_modevehicle_config.spoiler_typevehicle_config.eu_vehiclevehicle_config.supports_qr_pairingvehicle_state.tpms_soft_warning_fl / fr / rl / rrvehicle_state.tpms_hard_warning_fl / fr / rl / rrvehicle_state.tpms_last_seen_pressure_time_fl / fr / rl / rrgui_settings.gui_range_displaygui_settings.gui_24_hour_timevehicle_state.media_infovehicle_state.media_stateoptions.codes[] — not rendered as a full factory-options sectionrelease_notes.release_notes[] — only partially surfacedvehicleSpecs.response.equipmentPrice[] — not rendered as a full options breakdownvehicleSpecs.response.autopilotHardwareCodevehicleSpecs.response.thirdRowSeatsCodevehicleSpecs.response.decorCodevehicleSpecs.response.rearSeatsNamevehicleSpecs.response.seatsNamevehicleSpecs.response.towingCodeFields present in audited payloads and/or live raw reports, not explicitly enumerated in the missing lists above. These are documented report candidates.
charge_state.battery_level, usable_battery_level, battery_range, est_battery_range, ideal_battery_rangecharge_state.charge_limit_soc, charging_state, charger_power, charger_voltagecharge_state.charger_actual_current, charger_pilot_current, conn_charge_cablecharge_state.minutes_to_full_charge, max_range_charge_counter, scheduled_charging_modeclimate_state.inside_temp, outside_temp, hvac_auto_request, climate_keeper_modeclimate_state.cop_activation_temperature, is_auto_conditioning_on, is_preconditioningclimate_state.remote_heater_control_enabled, supports_fan_only_cabin_overheat_protectionclimate_state.bioweapon_mode, seat_heater_rear_centerNote: bioweapon_mode and seat_heater_rear_center are model-specific (observed in Model Y) — must remain null-safe.
vehicle_config.aux_park_lamps, car_type, efficiency_package, paint_color_overridevehicle_config.trim_badging, can_accept_navigation_requests, can_actuate_trunksvehicle_config.headlamp_type, motorized_charge_portvehicle_state.odometer, calendar_supported, parsed_calendar_supportedvehicle_state.software_update.status, version, download_perc, install_perc, expected_duration_secvehicle_state.media_info.a2dp_source_name, media_playback_status, now_playing_sourcevehicle_state.media_info.now_playing_station, now_playing_title, now_playing_artistvehicle_state.media_info.now_playing_album, now_playing_elapsed, now_playing_durationvehicle_state.tpms_pressure_fl / fr / rl / rrvehicle_state.tpms_rcp_front_value, tpms_rcp_rear_valuereleaseNotes.release_notes[].title, description, customer_version, icon, show_in_historyreleaseNotes.release_notes[].image_url, light_image_url, is_fallbackreleaseNotes.deployed_version, staged_version, release_notes_versionvehicleSpecs.response.batterySoH, batterySoHTimestamp, batteryCapacityKwhvehicleSpecs.response.bodyType, typeOfDrive, numberOfSeats, numberOfDoorsvehicleSpecs.response.paintCode, interiorCode, totalPrice, countryOfOriginvehicleSpecs.response.productionDate, maxPowerInKw, batteryRangeInKmVerified present in both tesla_api_responses.json and tesla-report-data.json:
vehicleData.charge_statevehicleData.climate_statevehicleData.gui_settingsvehicleData.vehicle_configvehicleData.vehicle_stateoptions.codesvehicleSpecs.responsereleaseNotes.release_notesThe missing items above are not blocked by the audited sample payloads themselves.
Already useful, still underused: display_name, vehicle_name, locked, sentry_mode, sentry_mode_available, valet_mode, valet_pin_needed, webcam_available, webcam_supported, webcam_selfie_supported, dashcam_clip_save_supported, dashcam_clip_save_available, dashcam_state
Already fetched in vehicle_data, not fully surfaced: charge_amps, charge_current_request, charge_current_request_max, charge_energy_added, charge_rate, charge_port_door_open, charge_port_latch, charge_port_color, fast_charger_present, fast_charger_brand, fast_charger_type, off_peak_charging_enabled, off_peak_charging_times, preconditioning_enabled, preconditioning_times, scheduled_charging_pending, scheduled_charging_start_time, scheduled_departure_time, scheduled_departure_time_minutes, supercharger_session_trip_planner, time_to_full_charge, trip_charging
Mostly available but still underused: cabin_overheat_protection, allow_cabin_overheat_protection, cabin_overheat_protection_actively_cooling, defrost_mode, fan_status, driver_temp_setting, passenger_temp_setting, is_climate_on, is_front_defroster_on, is_rear_defroster_on, seat heaters (all positions), seat fans, steering_wheel_heater, steering_wheel_heat_level, side_mirror_heaters, wiper_blade_heater
Good commercial value: charge_port_type, driver_assist, exterior_color, exterior_trim, interior_trim_type, wheel_type, roof_color, rear_drive_unit, rear_seat_heaters, has_seat_cooling, has_air_suspension, has_ludicrous_mode, performance_package, spoiler_type, eu_vehicle, supports_qr_pairing
Useful for ownership insight: car_version, software_update, autopark_state_v2, notifications_supported, remote_start, remote_start_enabled, remote_start_supported, speed_limit_mode, service_mode, service_mode_plus, santa_mode, is_user_present
Partly rendered, should be expanded: all four pressures, soft warnings, hard warnings, last-seen pressure timestamps.
Secondary but available: gui_charge_rate_units, gui_distance_units, gui_temperature_units, gui_tirepressure_units, gui_range_display, gui_24_hour_time
Lower priority: media_info, media_state. Optional "Context / Infotainment" section.
These fields are already inside vehicleData:
charge_state scheduling and charger detailsclimate_state comfort/heating fieldsvehicle_config hardware and trim detailsvehicle_state dashcam / sentry / software / remote-start stateThe report stack must stay resilient when Tesla returns less than the sample:
Guardrails covered by: src/python/cpt-api/tests/test_pdf_service.py