> ## Documentation Index
> Fetch the complete documentation index at: https://lmn.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Rendering the Inspection UI

> Comprehensive guide to building the Body Inspection and Inspection Report panels from GET /v1/vehicles/{id}, with per-source field semantics, damage-code reference, color tokens, Korean→English glossary, full React reference implementation, and a verification checklist.

This guide is everything you need to render the two inspection panels that appear on `lmnauto.com` car-detail pages — using only the JSON returned by `GET /v1/vehicles/{id}`. It is intentionally exhaustive: a partner engineer should be able to build a pixel-faithful rendering from scratch using only this page plus the response.

<Tip>
  **Using React? Don't build this from scratch.** LMN ships a drop-in React package,
  **`@lmnauto/inspection-ui`**, that renders the entire inspection UI — grades, body-damage
  diagrams, checklist, accident/insurance, inspector notes — **exactly as lmnauto.com does**,
  for all six sources. You pass the `GET /v1/vehicles/{id}` payload and it does the mapping:

  ```tsx theme={null}
  import { InspectionReport } from '@lmnauto/inspection-ui';

  <InspectionReport vehicle={vehicle} />;
  ```

  The mapping logic lives with LMN (who own the payload), so your UI stays correct as sources
  evolve — no per-source damage-code/panel-ID handling on your side. It's zero-runtime-dependency
  (React peer only), fully themeable via a typed `theme` prop ([§8](#8-theming)), and every section is toggleable. The package
  is distributed **privately** (tarball or git) — **contact LMN for access**.

  **This page remains the reference** for non-React partners, custom rendering, or understanding
  the underlying data semantics.
</Tip>

## Why a dedicated guide?

The inspection data is the densest, most source-variable part of the response. The unified shape covers six inspectors (Glovis, SK, AJ, Lotte, KCar, Encar dealer) that all expose different raw data, normalized into a single response contract. The shape is mostly consistent across sources, with a few documented asymmetries:

* **Shape asymmetries** — auction and dealer both carry `checklist[]`; only dealer adds the `dealer_inspection` damage-count block. `accident_history[]` remains auction-only for now (dealer surfaces accident state via `has_accident` / `accident_summary`).
* **Value asymmetries** — SK and Lotte return a mirrored diagram `image_url` (damage baked in) and AJ a blueprint base image; Glovis, K-Car, and dealer return `null` and need a client-rendered diagram from `panels[]`. Damage codes and panel IDs differ per source (see [Damage Code Dictionary](#4-damage-code-dictionary) and [Panel ID Catalog](#5-panel-id-catalog)) — render-time partner code must tolerate the variations.

Getting the rendering right depends on understanding where these asymmetries surface in your JSON.

If you only need the API reference (path, query params, error codes), see [Vehicles](/endpoints/vehicles). This page is the rendering companion.

***

## 1. Anatomy of the response

The detail endpoint returns these two top-level objects relevant to inspection:

| JSON path           | Drives                                                          | Type             |
| ------------------- | --------------------------------------------------------------- | ---------------- |
| `body_condition`    | The car-diagram panel ("Body Inspection")                       | `Object \| null` |
| `inspection_report` | The grade-summary card ("Inspection Report" / "{Source} Grade") | `Object \| null` |

Either can be `null` independently — feature-detect before rendering.

```js theme={null}
const { body_condition: bc, inspection_report: ir } = vehicle;
if (!bc && !ir) return <EmptyState text="Inspection report not yet available." />;
return (
  <>
    {ir && <InspectionReportCard data={ir} />}
    {bc && <BodyInspectionCard data={bc} />}
  </>
);
```

### Why both can be null

| Scenario                                                             | `body_condition`         | `inspection_report` |
| -------------------------------------------------------------------- | ------------------------ | ------------------- |
| Auction listing freshly scraped, inspector hasn't filed report yet   | `null`                   | `null`              |
| Auction with grade filed but no panel damage                         | populated (`panels: []`) | populated           |
| Dealer car where Encar has no inspection record (upstream 404)       | `null`                   | `null`              |
| Dealer car where Encar returned data but no panel damage (clean car) | populated (`panels: []`) | populated           |
| Dealer car with both inspection metadata and panel damage            | populated                | populated           |

The full report and the diagram are independent ingestion pipelines, so partial states are expected. Always feature-detect at the field level too — `accident_cost_summary` may be `null` even when `inspection_report` itself is populated.

***

## 2. Per-source field matrix

Every field below is part of the same response shape, but populated differently depending on `inspection_report.source`. Knowing which fields you can expect per source lets you build skeletons and empty states that don't flicker.

| Field                                     |                    Glovis                   |                 SK                 |                    AJ                   |                Lotte               |                KCar                |                     Dealer (Encar)                    |
| ----------------------------------------- | :-----------------------------------------: | :--------------------------------: | :-------------------------------------: | :--------------------------------: | :--------------------------------: | :---------------------------------------------------: |
| `body_condition.image_url`                |                      —                      |              ✅ mirror              |               ✅ blueprint               |              ✅ mirror              |                  —                 |                           —                           |
| `body_condition.panels[]`                 |              ✅ \~12-20 entries              |            ✅ 4-8 entries           |              ✅ 6-14 entries             |           ✅ 4-10 entries           |           ✅ 4-12 entries           |                     ✅ 0-10 entries                    |
| `inspection_report.overall_grade`         |                  ✅ `"A/7"`                  |              ✅ `"A/A"`             |                ✅ `"A/A"`                |              ✅ `"A/A"`             |              ✅ `"A/A"`             |                           —                           |
| `inspection_report.grade_description`     |    ✅ accident grade / 1-9 condition score   | ✅ accident grade / exterior letter | ✅ frame grade / exterior letter (digit) | ✅ accident grade / exterior letter | ✅ accident grade / exterior letter |             ✅ `"Encar dealer inspection"`             |
| `inspection_report.accident_summary`      |                      ✅                      |                  ✅                 |                    ✅                    |                  ✅                 |                  ✅                 | ✅ ("No structural damage" or "Frame damage reported") |
| `inspection_report.has_accident`          |                      ✅                      |                  ✅                 |                    ✅                    |                  ✅                 |                  ✅                 |                           ✅                           |
| `inspection_report.has_frame_damage`      |                      ✅                      |                  ✅                 |                    ✅                    |                  ✅                 |                  ✅                 |                           ✅                           |
| `inspection_report.exterior_summary`      | ✅ "6 panels: 4× needs bodywork, 1× scratch" |               ✅ short              |                 ✅ short                 |               ✅ short              |               ✅ short              |               ✅ N exterior damage cases               |
| `inspection_report.accident_reason`       |   ✅ "Welded (W): rear door (L), trunk (L)"  |                  —                 |                    ✅                    |                  —                 |                  —                 |                           —                           |
| `inspection_report.category_grades[]`     |               ✅ 5-8 categories              |                ✅ 3-5               |                  ✅ 5-7                  |                ✅ 3-5               |                ✅ 3-5               |             ✅ `[{frame},{exterior}]` only             |
| `inspection_report.checklist[]`           |                ✅ **17 items**               |          ✅ limited (\~5-8)         |           ✅ **up to 39 items**          |              ✅ limited             |              ✅ limited             |     ✅ up to \~35 items (mechanical, EN-translated)    |
| `inspection_report.accident_history[]`    |                      ✅                      |                  —                 |                    ✅                    |                  —                 |                  —                 |                          `[]`                         |
| `inspection_report.accident_cost_summary` |           ✅ when accidents present          |                  —                 |         ✅ when accidents present        |                  —                 |                  —                 |                         `null`                        |
| `inspection_report.issues[]`              |                      ✅                      |                  ✅                 |                    ✅                    |                  ✅                 |                  ✅                 |                          `[]`                         |
| `inspection_report.inspection_sheet_url`  |                      —                      |        ✅ original sheet PDF        |           ✅ original sheet PDF          |                  —                 |                  —                 |                         `null`                        |
| `inspection_report.inspector_notes`       |               ✅ EN-translated               |           ✅ EN-translated          |             ✅ EN-translated             |           ✅ EN-translated          |           ✅ EN-translated          |        ✅ EN-translated (live Google Translate)        |
| `inspection_report.dealer_inspection`     |                    absent                   |               absent               |                  absent                 |               absent               |               absent               |                           ✅                           |

**Practical reading:** Glovis and AJ are the most data-rich (full checklist + accident history + cost breakdown). SK / Lotte / KCar return enough to render a graded card but no checklist depth. Dealer (Encar) now returns the mechanical `checklist[]` (engine, transmission, steering, braking, electrical, …, EN-translated) alongside the `dealer_inspection` damage counts. Render whatever is non-empty; the renderer below handles all six.

<Note>
  **Clean dealer cars now return a populated `inspection_report`.** Previously a dealer car with no panel damage returned `inspection_report: null` (and the UI showed "No inspection report available"), even though the inspection existed and was simply clean. As of the change below, a successfully-fetched dealer inspection always returns a report — a clean car reads as zero damage with a populated mechanical `checklist[]`. `inspection_report` is still `null` only when the inspection genuinely doesn't exist upstream (Encar 404) or the fetch failed transiently.
</Note>

***

## 3. The Body Inspection card

The card titled **"Body Inspection"** on `lmnauto.com`. For live comparison, see any dealer listing at `https://lmnauto.com/en/auction?source=retail&tab=encar_<id>` — the worked example in [§12](#12-worked-example-b--encar-dealer-real-api-response) uses `encar_41657860` (BMW M5 2019).

### 3.1 Visual structure

```
┌───────────────────────────────────────────────────────────────────┐
│ 📄  Body Inspection                                  View Report → │
│                                                                   │
│  ┌─────────────────────────┐    ┌─────────────────────────┐      │
│  │ 🛡  Frame Damage         │    │ ⚠  Exterior Damage       │      │
│  │  0                       │    │  1                       │      │
│  │ No structural damage     │    │ Minor damage             │      │
│  └─────────────────────────┘    └─────────────────────────┘      │
│                                                                   │
│                                              [x] Exchange         │
│  ┌─ Exterior Panels ──────┐    ┌─ Frame Structure ──────┐         │
│  │                        │    │                        │         │
│  │       [Top-down        │    │      [Top-down         │         │
│  │        car silhouette  │    │       chassis          │         │
│  │        with markers]   │    │       silhouette]      │         │
│  │                        │    │                        │         │
│  └────────────────────────┘    └────────────────────────┘         │
│                                                                   │
│  Rank 1   1 cases               Rank A   None                    │
│  Rank 2   None                  Rank B   None                    │
│                                  Rank C   None                    │
└───────────────────────────────────────────────────────────────────┘
```

### 3.2 Response shape

```json theme={null}
{
  "body_condition": {
    "image_url": null,
    "panels": [
      {
        "panel_id": "hood",
        "panel_type": "exterior",
        "damage_code": "CHANGE",
        "damage_codes": ["CHANGE"],
        "label": "Hood",
        "description": "Exchange",
        "severity": "high"
      },
      {
        "panel_id": "rear-bumper",
        "panel_type": "exterior",
        "damage_code": "SCRATCH",
        "damage_codes": ["SCRATCH"],
        "label": "Rear Bumper",
        "description": "Scratch",
        "severity": "low"
      }
    ]
  }
}
```

### 3.3 Field reference

| Field                   | Type                                    | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| ----------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `image_url`             | `string \| null`                        | HTTPS URL to the source's mirrored diagram image — populated for **SK** and **Lotte** (damage baked in) and **AJ** (a blueprint base image drawn under the panel markers). **`null` for Glovis, K-Car, and dealer** — render those from `panels[]`.                                                                                                                                                                                                                                 |
| `panels[].panel_id`     | `string`                                | Stable identifier — see the [Panel ID Catalog](#5-panel-id-catalog) below. Use as marker-position key.                                                                                                                                                                                                                                                                                                                                                                              |
| `panels[].panel_type`   | `"exterior" \| "frame"`                 | Determines which of the two silhouettes the marker goes on.                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `panels[].damage_code`  | `string`                                | The primary damage code. See the [Damage Code Dictionary](#4-damage-code-dictionary).                                                                                                                                                                                                                                                                                                                                                                                               |
| `panels[].damage_codes` | `string[]`                              | All damage codes for this panel — one panel may have multiple non-rank codes (e.g. welded + sheet-metalled). Order is most-significant first. **Dealer-only caveat:** when a panel has both a rank attribute (e.g. `RANK_ONE`) and a non-rank damage code (e.g. `CHANGE`) upstream, only the non-rank codes survive in this array; rank attrs are reflected in the aggregate `dealer_inspection` counts instead. See [Damage Code Dictionary §4.6](#46-dealer-encar-source-dealer). |
| `panels[].label`        | `string`                                | English display label (e.g. "Hood", "Front Door (L)", "Rear Quarter Panel (R)").                                                                                                                                                                                                                                                                                                                                                                                                    |
| `panels[].description`  | `string`                                | Human-readable description of `damage_code`. Localized to English.                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `panels[].severity`     | `"high" \| "medium" \| "low" \| "none"` | Roll-up severity for color coding. See the [Severity Computation Rules](#43-severity-computation-rules). `"none"` only appears in rare cases where a panel is listed for completeness without actual damage.                                                                                                                                                                                                                                                                        |

### 3.4 Count derivation

Compute the two count boxes from the panels array:

```js theme={null}
const damagedPanels = body_condition.panels.filter(p => p.severity !== "none");

const frameCount = damagedPanels.filter(p => p.panel_type === "frame").length;
const exteriorCount = damagedPanels.filter(p => p.panel_type === "exterior").length;

const maxFrameSeverity = maxSeverity(damagedPanels.filter(p => p.panel_type === "frame"));
const maxExteriorSeverity = maxSeverity(damagedPanels.filter(p => p.panel_type === "exterior"));

function maxSeverity(panels) {
  const order = ["none", "low", "medium", "high"];
  return panels.reduce((max, p) => (order.indexOf(p.severity) > order.indexOf(max) ? p.severity : max), "none");
}
```

### 3.5 Status text per count box

| Count | Max severity | Frame box text         | Exterior box text    |
| ----- | ------------ | ---------------------- | -------------------- |
| `0`   | —            | "No structural damage" | "No exterior damage" |
| `≥1`  | `low`        | "Minor damage"         | "Minor damage"       |
| `≥1`  | `medium`     | "Repair history"       | "Repair history"     |
| `≥1`  | `high`       | "Major damage"         | "Major damage"       |

### 3.6 Count box color

| Count | Severity | Background             | Text                    |
| ----- | -------- | ---------------------- | ----------------------- |
| `0`   | —        | `#ECFDF5` (emerald-50) | `#047857` (emerald-700) |
| `≥1`  | `low`    | `#FEFCE8` (yellow-50)  | `#A16207` (yellow-700)  |
| `≥1`  | `medium` | `#FFFBEB` (amber-50)   | `#B45309` (amber-700)   |
| `≥1`  | `high`   | `#FEF2F2` (red-50)     | `#B91C1C` (red-700)     |

### 3.7 Rank breakdown (dealer-source only)

When `inspection_report.source === "dealer"`, render a small summary beneath the diagrams using the **server-computed totals** in `inspection_report.dealer_inspection`:

```js theme={null}
const di = inspection_report.dealer_inspection;
// di.critical_frame_damage  → total count across RANK_A + RANK_B + RANK_C
// di.exterior_damage        → total count across RANK_ONE + RANK_TWO
// di.simple_repair          → boolean | null — Encar "단순수리" flag, null when not stated
// di.frame_status / di.panel_status → KO status string | null (only when the page carries a status row)
```

The mechanical condition of the car (engine, transmission, steering, braking, electrical, fuel) is in `inspection_report.checklist[]` for dealer cars too — each entry is `{ group, item, result }`, already KO→EN translated. Render it with the same checklist component used for auction sources.

Display under each diagram:

```
Under "Exterior Panels":   Total ranked panels — {di.exterior_damage}   (or "None" when 0)
Under "Frame Structure":   Total ranked panels — {di.critical_frame_damage}   (or "None" when 0)
```

<Note>
  **Per-rank breakdown is available at `dealer_inspection.rank_counts`.** To reproduce the reference UI's "Rank 1 / Rank 2 / Rank A / Rank B / Rank C" lines, read `dealer_inspection.rank_counts` (`{ rank_one, rank_two, rank_a, rank_b, rank_c }`, integers, dealer-only). By construction `rank_a + rank_b + rank_c === critical_frame_damage` and `rank_one + rank_two === exterior_damage`, so the aggregate count fields above remain valid for older clients.

  Do **not** try to compute per-rank counts by scanning `body_condition.panels[].damage_codes`. When a panel has both a rank attribute and a regular damage code (the common case — `RANK_ONE` together with `CHANGE`), the rank attribute is dropped from `damage_codes` in favor of the more specific damage. Counting from `damage_codes` would systematically undercount — use `rank_counts`, which is tallied before that collapse.
</Note>

For auction sources, omit this section — they don't use rank vocabulary.

***

## 4. Damage Code Dictionary

**The `damage_code` vocabulary is source-specific.** Each inspector emits its own codes — Glovis uses lowercase mnemonics, SK/AJ/Lotte use uppercase letter codes, and the Encar dealer path uses descriptive uppercase tokens. The unified `panel.severity` and `panel.description` fields paper over this — those are normalized for display. Use `damage_code` for *icon* mapping and case-by-case handling; rely on `description` for textual rendering.

<Tip>
  **Practical rendering rule:** Map `damage_code` → icon using the source-specific tables below. Render `panel.description` as the primary user-visible text. That way new codes that aren't yet in your icon map degrade to "no icon + correct text" rather than dropping the panel.
</Tip>

The full set of codes you may receive, per `inspection_report.source`:

### 4.1 Glovis (`source: "glovis"`)

Lowercase mnemonics derived from Korean damage type names.

| Code | English               | Korean origin | Severity (server) | Icon glyph           |
| ---- | --------------------- | ------------- | ----------------- | -------------------- |
| `xx` | Replaced              | 교환            | `high`            | `×` (white on red)   |
| `w`  | Sheet metal / Welding | 판금/용접, 용접     | `medium`          | `W` (white on amber) |
| `pp` | Sheet metal repair    | 판금            | `medium`          | `P` (white on amber) |
| `f`  | Bent / Crumpled       | 꺾임            | `medium`          | `F` (white on amber) |
| `m`  | Adjusted              | 조정            | `low`             | outlined dot         |

### 4.2 SK (`source: "sk"`)

Uppercase single- or double-letter codes.

| Code | English             | Korean origin | Severity (server) | Icon glyph           |
| ---- | ------------------- | ------------- | ----------------- | -------------------- |
| `XX` | Replacement history | 교환이력          | `high`            | `×` (white on red)   |
| `X`  | Replaced            | 교환            | `high`            | `×` (white on red)   |
| `W`  | Welded              | 판금용접          | `medium`          | `W` (white on amber) |
| `Q`  | Panel work          | 쿼터패널          | `medium`          | `Q` (white on amber) |
| `P`  | Paint               | 도장            | `low`             | outlined dot         |
| `M`  | Adjusted            | 조정            | `low`             | outlined dot         |

Codes `G`, `G1`, `G2` may appear in raw upstream data but are filtered out before reaching the partner API — you will not see them.

### 4.3 AJ (`source: "aj"`)

Uppercase letter codes. Shares some letters with SK (e.g. `X`, `W`) — never mix the two dictionaries; always switch on `source` first.

| Code | English             | Korean origin | Severity (server) | Icon glyph           |
| ---- | ------------------- | ------------- | ----------------- | -------------------- |
| `XX` | Replacement history | 교환이력          | `high`            | `×` (white on red)   |
| `X`  | Replaced            | 교환            | `high`            | `×` (white on red)   |
| `W`  | Panel / Weld        | 판금/용접         | `high`            | `W` (white on amber) |
| `A`  | Scratch             | 상처            | `low`             | outlined dot         |
| `P`  | Paint               | 도장            | `low`             | outlined dot         |

### 4.4 Lotte (`source: "lotte"`)

Lotte uses a code set similar to SK. Treat unknown codes by falling back to `description` text — the inspector's free-text describes the damage in English.

### 4.5 KCar (`source: "kcar"`)

Same fallback rule as Lotte — handle unknown codes gracefully via `description`. KCar emits a sparse, mostly-letter-code set; expect coverage to grow over time.

### 4.6 Dealer (Encar) (`source: "dealer"`)

Descriptive uppercase tokens derived from Encar's `outers[].attributes[]` raw values.

| Code        | English             | Severity (server) | Notes                                                                                                                                          |
| ----------- | ------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `CHANGE`    | Replaced (Exchange) | `high`            | Panel was swapped out. Rendered with the `×` exchange overlay.                                                                                 |
| `WELD`      | Welded              | `medium`          | Structural repair via welding. See ‡ below.                                                                                                    |
| `METAL`     | Sheet metal repair  | `medium`          | Body work without replacement.                                                                                                                 |
| `CORROSION` | Corrosion           | `medium`          | Rust / oxidation.                                                                                                                              |
| `SCRATCH`   | Scratch             | `low`             | Surface scratch.                                                                                                                               |
| `DAMAGE`    | Damage (general)    | `medium`          | Catch-all for unspecified damage.                                                                                                              |
| `HILLS`     | Dent                | `low`             | Encar's term for a dent / dimple. See ‡ below.                                                                                                 |
| `RANK_A`    | Frame rank A        | `high`            | Frame panel (most severe of A/B/C). See note below.                                                                                            |
| `RANK_B`    | Frame rank B        | `high`            | Frame panel.                                                                                                                                   |
| `RANK_C`    | Frame rank C        | `high`            | Frame panel.                                                                                                                                   |
| `RANK_ONE`  | Exterior rank 1     | `low`–`high`      | Severity is set by the accompanying `statusTypes` (W → `medium`, X → `high`). Bare rank-only panels with no `statusTypes` upstream emit `low`. |
| `RANK_TWO`  | Exterior rank 2     | `low`–`high`      | Same rule as RANK\_ONE — severity is set by the canonical damage code derived from `statusTypes`.                                              |

‡ `WELD` and `HILLS` are recognized by the server's severity and description logic for forward-compatibility, but are **not currently produced** by any upstream mapping. The dealer codes you can actually receive today are `CHANGE`, `METAL`, `CORROSION`, `SCRATCH`, `DAMAGE`, and the `RANK_*` attributes. `WELD` / `HILLS` are listed so your renderer tolerates them if they start appearing — fall back to `panel.description` for any code you don't recognize.

<Note>
  **Rank attrs are stripped from `damage_codes` when a non-rank code is also present.** A panel with `["RANK_ONE", "CHANGE"]` upstream emits `damage_codes: ["CHANGE"]` from this API — the rank attribute is collapsed in favor of the more specific damage code. You will only see `RANK_ONE`/`RANK_TWO`/`RANK_A`/`RANK_B`/`RANK_C` in `damage_codes` when it is the *only* code for that panel (i.e., when Encar reported a rank with no accompanying `statusTypes` entry — rare in practice).

  For per-rank breakdown, do not derive from `damage_codes`. Use the aggregate counts in `inspection_report.dealer_inspection.{critical_frame_damage, exterior_damage}`. See [section 3.7](#37-rank-breakdown-dealer-source-only).
</Note>

### 4.7 Severity is server-computed — trust the emitted field

The `severity` field on each panel is set server-side and is the **only** signal you should use for color coding. Severity rules differ per source (each source has its own code-to-severity table at ingest time, e.g. Glovis maps `w` → `medium` while AJ maps `W` → `high`), and frame panels may be promoted at the unified-mapping layer for some sources. The compounding rules are not stable contract — they may evolve as we ingest new inspectors.

What **is** stable contract:

| Field                                                                          | Stability                                              |
| ------------------------------------------------------------------------------ | ------------------------------------------------------ |
| The set of possible `severity` values: `"high" \| "medium" \| "low" \| "none"` | Stable                                                 |
| The color you should pick for each value                                       | Stable (see [Color tokens](#8-color-tokens-reference)) |
| The per-source code-to-severity mapping at ingest time                         | **Not stable** — may evolve                            |
| Whether frame panels are auto-promoted                                         | **Not stable** — varies per source                     |

Use `panel.severity` for color picking. Do not re-derive from `damage_code` or `damage_codes` — your local re-derivation will drift from server behavior as we tune the per-source tables, and you'll either over-color (false alarms) or under-color (silent damage) edge cases.

If you need to display a textual rationale alongside the marker, render `panel.description` — it is the localized English explanation of the panel's damage state, regardless of source.

***

## 5. Panel ID Catalog

`panel_id` values use two different naming conventions depending on the source:

| Source                                | Convention                                                                   | Example                               |
| ------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------- |
| Glovis, SK, AJ, Lotte, KCar (auction) | **kebab-case**                                                               | `front-door-l`, `front-wheel-house-r` |
| Encar (dealer)                        | **camelCase**, passed through from Encar's raw `outers[].partId` / `panelId` | `rearDoorRight`, `frontDoor`, `hood`  |

The dealer values are not normalized — they're whatever Encar's API returns for that listing. We expose them verbatim so your client can correlate against Encar's own UI if needed.

<Warning>
  **Build two PANEL\_COORDS maps**, one for kebab-case auction panels and one for camelCase dealer panels. Switching on `inspection_report.source` is the cleanest way to choose. We recognize this asymmetry is friction — a future API revision is planned to add canonical kebab-case IDs alongside the raw values. Until then, your client carries both maps.
</Warning>

### 5.1 Auction panel IDs (kebab-case)

This is the complete set currently emitted by the auction inspectors. New IDs may be added over time (additive); your client should fall back gracefully on unknowns (see the Note at the end of this section).

#### Exterior panels

| `panel_id`                          | English label      | Marker placement (top-down body view)        |
| ----------------------------------- | ------------------ | -------------------------------------------- |
| `hood`                              | Hood               | Front-center, just behind the front bumper   |
| `roof`                              | Roof               | Center, above the cabin                      |
| `trunk`                             | Trunk / Tailgate   | Rear-center, just ahead of the rear bumper   |
| `front-bumper`                      | Front Bumper       | Front edge, full width                       |
| `rear-bumper`                       | Rear Bumper        | Rear edge, full width                        |
| `front-door-l` / `front-door-r`     | Front Door (L/R)   | Mid sides, ahead of B-pillar                 |
| `rear-door-l` / `rear-door-r`       | Rear Door (L/R)    | Mid-rear sides, behind B-pillar              |
| `front-fender-l` / `front-fender-r` | Front Fender (L/R) | Front sides, between bumper and door         |
| `rear-fender-l` / `rear-fender-r`   | Rear Fender (L/R)  | Rear sides, between door and bumper          |
| `side-sill-l` / `side-sill-r`       | Side Sill (L/R)    | Lower body edge, full length under the doors |
| `front-light-l` / `front-light-r`   | Front Light (L/R)  | Headlamp positions on front fascia           |
| `rear-light-l` / `rear-light-r`     | Rear Light (L/R)   | Tail-lamp positions on rear fascia           |

#### Frame panels

| `panel_id`                                    | English label           | Marker placement (top-down chassis view) |
| --------------------------------------------- | ----------------------- | ---------------------------------------- |
| `radiator-support`                            | Radiator Support        | Front-center, behind front panel         |
| `cross-member`                                | Cross Member            | Front, transverse beam                   |
| `front-side-member-l` / `front-side-member-r` | Front Side Member (L/R) | Front longitudinal beams                 |
| `side-member-l` / `side-member-r`             | Side Member (L/R)       | Mid rocker / frame rails                 |
| `side-member-frame`                           | Side Member (Frame)     | Truck-style frame rail when present      |
| `front-side-member`                           | Front Side Member       | Single-rail front variant when present   |
| `rear-side-member-l` / `rear-side-member-r`   | Rear Side Member (L/R)  | Rear longitudinal beams                  |
| `front-wheel-house-l` / `front-wheel-house-r` | Front Wheel House (L/R) | Around front wheel arches                |
| `rear-wheel-house-l` / `rear-wheel-house-r`   | Rear Wheel House (L/R)  | Around rear wheel arches                 |
| `wheel-house-l` / `wheel-house-r`             | Wheel House (L/R)       | Generic wheel-arch variant               |
| `a-pillar-l` / `a-pillar-r`                   | A-Pillar (L/R)          | Between hood and front door              |
| `b-pillar-l` / `b-pillar-r`                   | B-Pillar (L/R)          | Between front and rear doors             |
| `c-pillar-l` / `c-pillar-r`                   | C-Pillar (L/R)          | Between rear door and quarter            |
| `inside-panel-l` / `inside-panel-r`           | Inside Panel (L/R)      | Left/right longitudinal inner beams      |
| `rear-inside-panel-l` / `rear-inside-panel-r` | Rear Inside Panel (L/R) | Rear inner beams                         |
| `dash-panel`                                  | Dash Panel              | Mid, between engine bay and cabin        |
| `floor-panel`                                 | Floor Panel             | Center, full underbody                   |
| `trunk-floor`                                 | Trunk Floor             | Rear, behind rear seats                  |
| `rear-panel`                                  | Rear Panel              | Rear-center under trunk                  |
| `package-tray`                                | Package Tray            | Behind rear seats, under rear glass      |
| `seat-back-panel`                             | Seat Back Panel         | Vertical panel behind rear seats         |

### 5.2 Dealer panel IDs (camelCase, derived from Encar `type.code`)

Dealer responses come from Encar's `https://api.encar.com/v1/readside/inspection/vehicle/{id}` endpoint. Each `outers[]` entry has a nested `type.code` (Encar's panel identifier, e.g. `P062`) and a `type.title` (Korean label). The partner API reads these and emits:

1. **`panel_id`** — translated via the internal Encar panel-code map to a canonical camelCase token (`quarterPanelRight` for `P062`, `frontFenderLeft` for `P021`). When the upstream `type.code` is not yet in the map, the raw code passes through verbatim (`"P099"`) so partners still have a stable per-panel identifier.
2. **`raw_panel_code`** — the unmodified Encar `type.code` value (e.g. `"P062"`). Always present when upstream provided it.
3. **`raw_label_ko`** — the unmodified Encar `type.title` value (Korean, e.g. `"쿼터 패널(우)"`). Always present when upstream provided it.
4. **`label`** — the English label from the map when canonical-mapped, otherwise the raw Korean title.

The canonical map is seeded with the codes observed in real responses and grows additively as new codes are encountered. Adding a code never breaks existing partner integrations.

<Note>
  **Handling unknown `panel_id` values.** If your `PANEL_COORDS` map does not contain the `panel_id` you receive (because it's a raw P-code that we haven't yet added to our canonical map, or a new code Encar emitted that we haven't seen), fall back to listing the panel by `label` in a textual "Affected panels" list under the diagram. Use the `raw_label_ko` field if you want to display the Korean label alongside or instead. Do **not** drop the panel silently.

  A pattern that handles both canonical and raw passthrough:

  ```js theme={null}
  const drawable = body_condition.panels.filter(p => PANEL_COORDS[p.panel_id]);
  const textual  = body_condition.panels.filter(p => !PANEL_COORDS[p.panel_id]);
  // Render `drawable` on the diagram, `textual` in a list below
  // (with `p.label || p.raw_label_ko` as the display).
  ```

  Synthetic `panel_${index+1}` IDs are rare today — they only appear when Encar's response omits both `type.code` and all legacy flat fields, which is unusual. Most dealer panels you receive will have either a canonical camelCase ID or a raw P-code.
</Note>

The canonical camelCase tokens you can expect to see (set grows as we add map entries):

| Example `panel_id` (camelCase)                            | Equivalent English label |
| --------------------------------------------------------- | ------------------------ |
| `hood`                                                    | Hood                     |
| `frontDoor` / `frontDoorLeft` / `frontDoorRight`          | Front Door (L/R)         |
| `rearDoor` / `rearDoorLeft` / `rearDoorRight`             | Rear Door (L/R)          |
| `frontFender` / `frontFenderLeft` / `frontFenderRight`    | Front Fender (L/R)       |
| `rearFender` / `rearFenderLeft` / `rearFenderRight`       | Rear Fender (L/R)        |
| `trunkLid`                                                | Trunk Lid                |
| `roof`                                                    | Roof                     |
| `frontBumper` / `rearBumper`                              | Front / Rear Bumper      |
| `quarterPanel` / `quarterPanelLeft` / `quarterPanelRight` | Quarter Panel (L/R)      |

The exact set of camelCase IDs Encar returns varies by listing — we pass through verbatim rather than normalizing, so any value Encar produces is the value you receive. The English `panel.label` field carries a human-readable rendering when available; when it is not, it falls back to the raw `panel_id` string.

### 5.3 Catalog completeness and fallbacks

This catalog reflects the set of IDs currently in use as of the latest release. New IDs may be added (additive — never a breaking change) as inspector data formats evolve. Your client **must** handle unknown IDs gracefully:

<Note>
  If `panel_id` arrives that is not in your `PANEL_COORDS` map, **do not drop the panel silently**. Fall back to listing it by `label` in a textual "Affected panels" section under the diagram. The damage is real even when you don't have a marker position for it; suppressing the entry hides information from your customer.

  A change-resilient pattern:

  ```js theme={null}
  const known = body_condition.panels.filter(p => PANEL_COORDS[p.panel_id]);
  const unknown = body_condition.panels.filter(p => !PANEL_COORDS[p.panel_id]);

  // Render `known` on the diagram, `unknown` in a textual list below.
  ```
</Note>

***

## 6. The Inspection Report card

The card titled **"Inspection Report"** or **"{Source} Grade"** on `lmnauto.com`. For live comparison, see any auction listing at `https://lmnauto.com/en/auction?source=auctions&tab=glovis_<date>_<auction_id>_<lot_id>` — the worked example in [§11](#11-worked-example-a--glovis-auction-real-api-response) uses `glovis_20260521_1078_1005` (Hyundai Sonata 2020).

### 6.1 Visual structure

```
┌───────────────────────────────────────────────────────────────────┐
│ Inspection Report  ⓘ                                          ⌄ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ┌────┐                                                       │ │
│ │ │A/A │  AJ Sellcar Grade               ✓ No Accident         │ │
│ │ └────┘                                                       │ │
│ │                                                              │ │
│ │ ┌────────────────────────────────────────────────────────┐  │ │
│ │ │ A  Accident Grade  —  No Accident                       │  │ │
│ │ └────────────────────────────────────────────────────────┘  │ │
│ │ ┌────────────────────────────────────────────────────────┐  │ │
│ │ │ A  Exterior Grade  —  4-5 panels                        │  │ │
│ │ │      This car: 2 panels need attention                  │  │ │
│ │ └────────────────────────────────────────────────────────┘  │ │
│ │                          Learn more about grading systems → │ │
│ └─────────────────────────────────────────────────────────────┘ │
│                                                                 │
│   ─── Checklist (auction + dealer, EN-translated) ───           │
│   • Engine — Oil leak: Good                                     │
│   • Brakes — Master cylinder: Good                              │
│   • …                                                           │
│                                                                 │
│   ─── Issues Found (when present) ───                           │
│   • Body — caution — Hood needs sheet-metal work                │
│                                                                 │
│   ─── Accident Cost (when present) ───                          │
│   Total: $4,231 USD (2 incidents)                               │
│     Parts: $2,140    Labor: $1,580    Paint: $511               │
│   Insurance paid: $3,890                                        │
│                                                                 │
│   ─── Inspector Notes (when present) ───                        │
│   > "Cosmetic only — no structural concerns."                   │
│                                                                 │
│   ─── Dealer Inspection (dealer only) ───                       │
│   Frame damage: 0      Exterior damage: 1                       │
│   Insurance: 0 claims  Frame: 이상없음   Panel: 교환 1             │
└───────────────────────────────────────────────────────────────────┘
```

### 6.2 Response shape

```json theme={null}
{
  "inspection_report": {
    "source": "aj",
    "overall_grade": "A/A",
    "grade_description": "Frame grade / Exterior letter grade (digit-converted)",
    "accident_summary": "No structural damage",
    "has_accident": false,
    "has_frame_damage": false,
    "exterior_summary": "4-5 panels: this car: 2 panels need attention",
    "accident_reason": null,
    "category_grades": [
      { "category": "frame", "status": "good" },
      { "category": "exterior", "status": "caution" }
    ],
    "checklist": [
      { "group": "Engine", "item": "Oil leak", "result": "양호" }
    ],
    "accident_history": [],
    "accident_cost_summary": null,
    "issues": [],
    "inspection_sheet_url": "https://.../aj_inspection_sheet.pdf",
    "inspector_notes": null
  }
}
```

<Note>
  This is an auction (`source: "aj"`) response, so `dealer_inspection` is **omitted entirely** from the JSON — it would only appear when `source === "dealer"`.
</Note>

### 6.3 Field reference

| Field                   | Type             | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| ----------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `source`                | `string`         | `"glovis" \| "sk" \| "aj" \| "lotte" \| "kcar" \| "dealer"`. Drives the badge label ("Glovis Grade" / "SK Grade" / "AJ Sellcar Grade" / "Lotte Grade" / "KCar Grade" / "Encar Dealer Inspection").                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `overall_grade`         | `string \| null` | Two-part grade rendered in the top-left badge. Examples below. `null` for dealer.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `grade_description`     | `string`         | One-line explanation of what the two parts of `overall_grade` represent for this source. Render as a small italic line under the badge or as a tooltip on hover.                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `accident_summary`      | `string \| null` | One-line accident verdict. Display next to the Accident Grade row.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `has_accident`          | `boolean`        | Drives the green/red top-right pill.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `has_frame_damage`      | `boolean`        | More specific — distinguishes accident *history* from current *frame damage* (a repaired accident may leave `has_accident=true` but `has_frame_damage=false`). Useful if you want a separate visual signal for "structural risk" vs "any accident in the past".                                                                                                                                                                                                                                                                                                                                                                         |
| `exterior_summary`      | `string \| null` | One-line exterior verdict. Display next to the Exterior Grade row.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `accident_reason`       | `string \| null` | More detailed accident description. Glovis and AJ only. Renders as a smaller secondary line under `accident_summary`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `category_grades[]`     | array            | One row per category (frame, exterior, engine, suspension, electrical, interior, etc.). Each `{ category, status }`. See section 6.4 for status colors.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `checklist[]`           | array            | Per-item checklist. Each `{ group, item, result }`. Populated for auction **and** dealer (`encar_*`) sources, EN-translated. See section 6.5.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `accident_history[]`    | array            | Per-incident records. Glovis + AJ. Each entry has structured incident fields (date, description, severity). See section 6.6.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `accident_cost_summary` | object \| null   | `{ total_incidents, total_repair_cost, breakdown: { parts, labor, paint }, insurance_paid, incidents[] }`. **All values are USD whole integers.** Already FX-converted server-side. `incidents[]` (Glovis; `[]` when the source has no per-incident breakdown) lists one entry per insurance-settled accident: `{ date: string \| null, insurance_paid, repair_cost: number \| null }`. **`insurance_paid` is the sum of `incidents[].insurance_paid`** — i.e. the per-incident payouts always reconcile exactly to the total (rounding is applied per incident, then summed). Render as: accident count → per-incident payout → total. |
| `issues[]`              | array            | Normalized "issues found" entries from all sources. Each `{ category, status, detail, faults: string[], type }`. See section 6.7.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `inspection_sheet_url`  | `string \| null` | HTTPS link to the original inspection-sheet PDF. Populated for SK and AJ auction sources; `null` for all other sources, including dealer. Render as a "View original inspection sheet" link when present.                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `inspector_notes`       | `string \| null` | Free-text observations, in English. Auction reports carry the LMN inspector's notes (translated offline). Dealer (`encar_*`) reports carry the Encar inspector's opinion (특기사항 및 점검자의 의견), translated to English via Google Translate on the fetch path. Render as a quote block.                                                                                                                                                                                                                                                                                                                                                       |
| `dealer_inspection`     | object           | Dealer-only sub-object — **absent (not `null`)** on auction responses. Present only when `source === "dealer"`. See section 6.8.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |

### 6.4 `category_grades[]` rendering

| `status`  | English label     | Pill background        | Pill text               |
| --------- | ----------------- | ---------------------- | ----------------------- |
| `good`    | "Good"            | `#ECFDF5` (emerald-50) | `#047857` (emerald-700) |
| `caution` | "Caution"         | `#FFFBEB` (amber-50)   | `#B45309` (amber-700)   |
| `bad`     | "Needs attention" | `#FEF2F2` (red-50)     | `#B91C1C` (red-700)     |

Each row is laid out as:

```
[ {status-pill} ]  {Category label}  —  {accident_summary or exterior_summary}
```

The "Category label" is human-readable English derived from `category`:

| `category` raw value | Display label                                       |
| -------------------- | --------------------------------------------------- |
| `frame`              | "Accident Grade"                                    |
| `exterior`           | "Exterior Grade"                                    |
| `engine`             | "Engine"                                            |
| `transmission`       | "Transmission"                                      |
| `brakes`             | "Brakes"                                            |
| `suspension`         | "Suspension"                                        |
| `electrical`         | "Electrical"                                        |
| `interior`           | "Interior"                                          |
| `glass`              | "Glass"                                             |
| `lighting`           | "Lighting"                                          |
| `(unknown)`          | Fall back to capitalize-first (`engine` → "Engine") |

### 6.5 `checklist[]` rendering

The checklist is grouped by `group`. Render as a collapsible list, with `group` as section header and one row per `item`. The `result` field contains the raw inspector verdict and is often in Korean — display as-is unless you maintain a translation map (see [Korean Glossary](#9-korean-glossary)).

Item count by source:

* **Glovis**: exactly 17 items. Groups: Engine, Transmission, Brakes, Steering, Electrical, Body, Glass, Interior.
* **AJ**: up to 39 items. Same group taxonomy plus suspension, fuel system, cooling system.
* **SK / Lotte / KCar**: 5-8 items, sparse coverage.
* **Dealer**: always `[]`.

```jsx theme={null}
<Accordion title={`Checklist (${ir.checklist.length} items)`}>
  {groupBy(ir.checklist, "group").map(([group, items]) => (
    <Section key={group} title={group}>
      {items.map((it, i) => (
        <Row key={i}>
          <span>{it.item}</span>
          <span>{translateKR(it.result)}</span>
        </Row>
      ))}
    </Section>
  ))}
</Accordion>
```

### 6.6 `accident_history[]` rendering

Each entry has structured fields (subject to source variation):

```json theme={null}
{
  "date": "2022-08-15",
  "type": "owner_self",
  "description": "Welded: rear door (L), trunk (L)",
  "severity": "medium"
}
```

Render as a timeline or table. The `type` field distinguishes "owner\_self" (the current/past owner caused it) from "third\_party" (someone else was at fault for the damage). Korean carfax convention treats these very differently for pricing.

### 6.7 `issues[]` rendering

Normalized "issues found" across all sources. Each entry:

```json theme={null}
{
  "category": "Body",
  "status": "caution",
  "detail": "Hood needs sheet-metal work",
  "faults": ["pp"],
  "type": "body"
}
```

| Field      | Meaning                                                                                          |
| ---------- | ------------------------------------------------------------------------------------------------ |
| `category` | Display label for the issue's category.                                                          |
| `status`   | `"bad" \| "caution"` — color coding.                                                             |
| `detail`   | One-line description of the issue.                                                               |
| `faults[]` | Damage codes associated (links back to the [Damage Code Dictionary](#4-damage-code-dictionary)). |
| `type`     | `"mechanical" \| "body" \| "functional"` — drives the icon (wrench / car-outline / power-cog).   |

Render as a vertical list of pills with the detail text. Color the pill by `status`.

### 6.8 `dealer_inspection` (dealer-only)

When `source === "dealer"`, this sub-object carries Encar-specific aggregate data:

```json theme={null}
{
  "dealer_inspection": {
    "critical_frame_damage": 0,
    "exterior_damage": 1,
    "frame_status": "이상없음",
    "panel_status": "교환 1",
    "simple_repair": false,
    "insurance_history": 1,
    "insurance_amount": 1370,
    "registration_no": "20-12345678",
    "vehicle_info": {
      "Valid Period": "Apr 29, 2025 ~ Apr 28, 2030",
      "First Registered": "Apr 29, 2025",
      "Warranty": "Manufacturer warranty",
      "Engine Model": "S63B44T4",
      "Base Price": "-"
    },
    "overall_status": [
      { "item": "Emissions", "status": "Pass", "detail": "" },
      { "item": "Tuning", "status": "None", "detail": "" },
      { "item": "Odometer", "status": "Normal", "detail": "" },
      { "item": "Color", "status": "Same", "detail": "" }
    ],
    "photos": [
      { "url": "https://.../front.jpg", "label": "Front" },
      { "url": "https://.../rear.jpg", "label": "Rear" }
    ],
    "certification": {
      "inspector": "한국자동차진단보증",
      "notifier": "○○모터스",
      "date": "2026-05-12"
    }
  }
}
```

| Field                   | Type                                           | Meaning                                                                                                                                                                                                                                                                                                                                                           |
| ----------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `critical_frame_damage` | `integer`                                      | Count of frame-rank panels (RANK\_A/B/C). Matches the Frame Damage count box.                                                                                                                                                                                                                                                                                     |
| `exterior_damage`       | `integer`                                      | Count of exterior-rank panels (RANK\_ONE/TWO). Matches the Exterior Damage count box.                                                                                                                                                                                                                                                                             |
| `frame_status`          | `string \| null`                               | Raw Encar frame status string in Korean. Common values: `"이상없음"` (no issues), `"교환"` (replaced), `"판금"` (sheet metal).                                                                                                                                                                                                                                            |
| `panel_status`          | `string \| null`                               | Raw Encar panel status string. Often includes a count, e.g. `"교환 1"` (1 replacement), `"이상없음"`.                                                                                                                                                                                                                                                                   |
| `simple_repair`         | `boolean \| null`                              | Encar's 단순수리 (simple repair) flag. `null` when the page doesn't state it.                                                                                                                                                                                                                                                                                         |
| `insurance_history`     | `integer \| null`                              | Own-damage accident claim count, sourced from a separate insurance-record upstream. `null` means "no data reported", `0` means "no claims".                                                                                                                                                                                                                       |
| `insurance_amount`      | `integer \| null`                              | Total own-damage insurance payout in **whole USD**, FX-converted server-side (same convention as `accident_cost_summary`). Do **not** re-apply `fx_rate`. `null` means "no data reported", `0` means "no claims".                                                                                                                                                 |
| `registration_no`       | `string \| null`                               | 성능번호 — the inspection-sheet registration number.                                                                                                                                                                                                                                                                                                                  |
| `vehicle_info`          | `object`                                       | Inspection-form-specific particulars only — `Valid Period`, `First Registered`, `Warranty`, `Engine Model`, `Base Price` (KO→EN translated). **Identity fields that duplicate the top-level resource (make/model, year, plate, VIN, transmission, fuel) are intentionally omitted** — read those from the top-level `Vehicle` fields. Render as a key-value list. |
| `overall_status`        | array of `{ item, status, detail }`            | Section-2 rows (emissions, tuning, special history, usage change, recall, odometer, VIN marking, color, major options). `item` and `status` are translated; `detail` is raw.                                                                                                                                                                                      |
| `photos`                | array of `{ url, label }`                      | Inspection front/rear photos. `label` is translated.                                                                                                                                                                                                                                                                                                              |
| `certification`         | object `{ inspector, notifier, date }` \| null | Inspecting body and notifier. `inspector` and `notifier` are raw Korean company names; `date` is translated. `null` when the sheet carries no certification block.                                                                                                                                                                                                |

Render as a small key-value list at the bottom of the report card. The raw Korean strings can be displayed verbatim (your end customers may include Korean speakers) or translated using the [Korean Glossary](#9-korean-glossary).

***

## 7. Overall grade interpretation

The `overall_grade` field is a two-part string `"X/Y"`. The semantics differ per source. The `grade_description` field tells you which is which for the current row, but here's the master decoder:

| Source | Format                                                | Left part                                                             | Right part                                                                  |
| ------ | ----------------------------------------------------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Glovis | `"A/7"` to `"D/1"`                                    | Letter: accident grade (A = no accident, B/C/D = increasing severity) | Digit 1-9: condition score (9 = best, 1 = worst)                            |
| SK     | `"A/A"` to `"C/C"`                                    | Letter: accident grade                                                | Letter: exterior letter grade (A = best)                                    |
| AJ     | `"A/A"` to `"C/C"` (digit-converted from raw numbers) | Letter: frame grade (A/B/C)                                           | Letter: exterior letter grade (converted from a 1-5 digit scale internally) |
| Lotte  | `"A/A"` to `"C/C"`                                    | Letter: accident grade                                                | Letter: exterior letter grade                                               |
| KCar   | `"A/A"` to `"C/C"`                                    | Letter: accident grade                                                | Letter: exterior letter grade                                               |
| Dealer | `null`                                                | —                                                                     | —                                                                           |

In the badge, render the two parts stacked or with a `/` separator. The exact pixel layout in the LMN reference UI is a 56×56 square with both characters rendered in a serif font centered.

### Pill colors by overall grade

A quick heuristic for badge background:

| Pattern                     | Color               |
| --------------------------- | ------------------- |
| Both parts `A` or score ≥ 7 | Emerald (`#10B981`) |
| One part B / score 5-6      | Amber (`#D97706`)   |
| Any part C/D or score ≤ 4   | Red (`#DC2626`)     |
| `null` (dealer)             | Slate (`#475569`)   |

***

## 8. Theming

`@lmnauto/inspection-ui` **v0.2.0+** is fully themeable. Pass a partial `theme` to any root component — only the keys you set change, everything else keeps the lmnauto.com reference default. An unthemed `<InspectionReport>` is byte-for-byte unchanged.

```tsx theme={null}
import { InspectionReport, type InspectionTheme } from '@lmnauto/inspection-ui';

const theme: InspectionTheme = {
  accent: '#EC615B',                  // your brand accent (see fan-out below)
  good: '#15803D',
  sevBadBg: '#FDECEA',
  radius: 8,                          // card corner radius (px)
  font: 'Plus Jakarta Sans, sans-serif',
};

<InspectionReport vehicle={vehicle} theme={theme} />;
```

<Tip>
  **Accent fan-out** — setting `accent` also retints the bad-severity count text, the accident "bad" badge, and the `CHANGE` damage marker, *unless* you set those tokens explicitly. A single `accent` gets you most of the way to on-brand.
</Tip>

### 8.1 CSS-variable escape hatch

Every token is also a `--lmn-insp-*` CSS custom property on the component's root, so you can theme from your own stylesheet instead of (or alongside) the prop:

```css theme={null}
.your-wrapper { --lmn-insp-accent: #EC615B; --lmn-insp-radius: 8px; }
```

The `theme` prop wins where both are set. Mind the unit difference: the prop takes a unitless number (`radius: 8`); the CSS variable needs the unit (`--lmn-insp-radius: 8px`).

### 8.2 Token reference

Defaults are the exact lmnauto.com reference colors. Each row is a `theme` key **and** a CSS variable.

**Brand**

| `theme` key | CSS variable        | Default   | Purpose                                  |
| ----------- | ------------------- | --------- | ---------------------------------------- |
| `accent`    | `--lmn-insp-accent` | `#ef4444` | brand accent; fans out to the red family |

**Surfaces & text**

| `theme` key  | CSS variable             | Default   | Purpose                 |
| ------------ | ------------------------ | --------- | ----------------------- |
| `ink`        | `--lmn-insp-ink`         | `#111827` | headings, count numbers |
| `textMuted`  | `--lmn-insp-text-muted`  | `#6b7280` | secondary text          |
| `textFaint`  | `--lmn-insp-text-faint`  | `#9ca3af` | faint labels / icons    |
| `border`     | `--lmn-insp-border`      | `#e5e7eb` | card / table borders    |
| `borderLite` | `--lmn-insp-border-lite` | `#f3f4f6` | light dividers          |
| `surfaceAlt` | `--lmn-insp-surface-alt` | `#f9fafb` | alternate surface       |
| `white`      | `--lmn-insp-white`       | `#ffffff` | card background         |
| `good`       | `--lmn-insp-good`        | `#22c55e` | "good / pass" icon      |

**Severity** (dealer count cards & accident text)

| `theme` key      | CSS variable                  | Default   |
| ---------------- | ----------------------------- | --------- |
| `sevGoodText`    | `--lmn-insp-sev-good-text`    | `#16a34a` |
| `sevGoodBg`      | `--lmn-insp-sev-good-bg`      | `#f0fdf4` |
| `sevCautionText` | `--lmn-insp-sev-caution-text` | `#d97706` |
| `sevCautionBg`   | `--lmn-insp-sev-caution-bg`   | `#fffbeb` |
| `sevBadText`     | `--lmn-insp-sev-bad-text`     | `#dc2626` |
| `sevBadBg`       | `--lmn-insp-sev-bad-bg`       | `#fef2f2` |

**Accident badges** (dealer)

| `theme` key                           | CSS variable                            | Default               |
| ------------------------------------- | --------------------------------------- | --------------------- |
| `badgeNeutralText` / `badgeNeutralBg` | `--lmn-insp-badge-neutral-text` / `-bg` | `#6b7280` / `#f3f4f6` |
| `badgeGoodText` / `badgeGoodBg`       | `--lmn-insp-badge-good-text` / `-bg`    | `#15803d` / `#dcfce7` |
| `badgeCautionText` / `badgeCautionBg` | `--lmn-insp-badge-caution-text` / `-bg` | `#b45309` / `#fef3c7` |
| `badgeBadText` / `badgeBadBg`         | `--lmn-insp-badge-bad-text` / `-bg`     | `#b91c1c` / `#fee2e2` |

**Damage markers** — set via the `damage` map: `theme={{ damage: { CHANGE: '#…' } }}`

| Code        | CSS variable               | Default   | Marker         |
| ----------- | -------------------------- | --------- | -------------- |
| `CHANGE`    | `--lmn-insp-dmg-change`    | `#ef4444` | Exchange (X)   |
| `METAL`     | `--lmn-insp-dmg-metal`     | `#3b82f6` | Metal work (W) |
| `CORROSION` | `--lmn-insp-dmg-corrosion` | `#0891b2` | Corrosion (C)  |
| `SCRATCH`   | `--lmn-insp-dmg-scratch`   | `#78716c` | Scratch (A)    |
| `HILLS`     | `--lmn-insp-dmg-hills`     | `#b45309` | Dent (U)       |
| `DAMAGE`    | `--lmn-insp-dmg-damage`    | `#fb7185` | Damage (T)     |

**Shape**

| `theme` key | CSS variable        | Default   | Purpose            |
| ----------- | ------------------- | --------- | ------------------ |
| `radius`    | `--lmn-insp-radius` | `12` (px) | card corner radius |
| `font`      | `--lmn-insp-font`   | `inherit` | root font-family   |

<Note>
  **v0.2.0 scope:** the **dealer (encar)** path is fully tokenized. On **auction** listings the shared surface/text, `radius`, and `font` tokens apply, but the auction grade badge and body-damage diagram colors are not yet themeable — tracked as a follow-up.
</Note>

***

## 9. Korean Glossary

The following raw Korean strings may appear in `dealer_inspection.frame_status`, `dealer_inspection.panel_status`, `inspection_report.checklist[].result`, and in some `accident_history[].description` entries. Maintain a client-side translation map if you want English-only UI.

### 9.1 Status terms

| Korean | English             | Where it appears                               |
| ------ | ------------------- | ---------------------------------------------- |
| 이상없음   | "No issues"         | frame\_status, panel\_status, checklist result |
| 양호     | "Good" / "Pass"     | checklist result                               |
| 불량     | "Bad" / "Fail"      | checklist result                               |
| 이음     | "Joint" / "Caution" | checklist result                               |
| 교환     | "Replaced"          | panel\_status                                  |
| 판금     | "Sheet metal"       | panel\_status, description                     |
| 용접     | "Welded"            | panel\_status, description                     |
| 부식     | "Corrosion"         | panel\_status, description                     |
| 손상     | "Damaged"           | panel\_status                                  |
| 균열     | "Cracked"           | panel\_status                                  |
| 누유     | "Oil leak"          | checklist result                               |
| 누수     | "Water leak"        | checklist result                               |
| 소음     | "Noise"             | checklist result                               |
| 미세누유   | "Minor oil leak"    | checklist result                               |
| 미세누수   | "Minor water leak"  | checklist result                               |

### 9.2 Category terms (in `accident_history[].description`)

| Korean   | English               |
| -------- | --------------------- |
| 본넷       | Hood                  |
| 트렁크      | Trunk                 |
| 도어       | Door                  |
| 펜더       | Fender                |
| 휀더       | Fender (alt spelling) |
| 쿼터패널     | Quarter panel         |
| 루프       | Roof                  |
| 프론트범퍼    | Front bumper          |
| 리어범퍼     | Rear bumper           |
| 라디에이터서포트 | Radiator support      |
| 사이드멤버    | Side member           |
| 인사이드패널   | Inside panel          |
| 휠하우스     | Wheelhouse            |
| 대시패널     | Dash panel            |
| 플로어패널    | Floor panel           |
| 좌        | Left                  |
| 우        | Right                 |
| 전        | Front                 |
| 후        | Rear                  |

### 9.3 Translation strategy

The safest approach is **client-side translation with raw fallback** — translate known terms, leave unknowns intact:

```js theme={null}
const KR_EN = {
  "이상없음": "No issues",
  "양호": "Pass",
  "불량": "Fail",
  // … full map from sections 9.1-9.2
};
const translateKR = (s) => KR_EN[s?.trim()] ?? s;
```

This keeps you safe when LMN adds new Korean status terms (you'll see them in raw form rather than dropping them).

***

## 10. Full reference renderer (React)

This is a complete, production-ready React component that consumes the JSON and renders both panels. Adapt to your component library — the logic is the contract.

```jsx theme={null}
import React from "react";

const SEVERITY_ORDER = ["none", "low", "medium", "high"];

function maxSeverity(panels) {
  return panels.reduce(
    (max, p) => (SEVERITY_ORDER.indexOf(p.severity) > SEVERITY_ORDER.indexOf(max) ? p.severity : max),
    "none"
  );
}

function statusText({ count, severity, kind /* "frame" | "exterior" */ }) {
  if (count === 0) return kind === "frame" ? "No structural damage" : "No exterior damage";
  if (severity === "high") return "Major damage";
  if (severity === "medium") return "Repair history";
  return "Minor damage";
}

function statusColor({ count, severity }) {
  if (count === 0) return { bg: "#ECFDF5", fg: "#047857" };
  if (severity === "high") return { bg: "#FEF2F2", fg: "#B91C1C" };
  if (severity === "medium") return { bg: "#FFFBEB", fg: "#B45309" };
  return { bg: "#FEFCE8", fg: "#A16207" };
}

function severityMarkerColor(severity) {
  return { high: "#DC2626", medium: "#D97706", low: "#CA8A04", none: "transparent" }[severity];
}

function categoryLabel(cat) {
  const map = {
    frame: "Accident Grade",
    exterior: "Exterior Grade",
    engine: "Engine",
    transmission: "Transmission",
    brakes: "Brakes",
    suspension: "Suspension",
    electrical: "Electrical",
    interior: "Interior",
    glass: "Glass",
    lighting: "Lighting",
  };
  return map[cat] ?? cat.charAt(0).toUpperCase() + cat.slice(1);
}

function sourceLabel(source) {
  return {
    glovis: "Glovis Grade",
    sk: "SK Grade",
    aj: "AJ Sellcar Grade",
    lotte: "Lotte Grade",
    kcar: "KCar Grade",
    dealer: "Encar Dealer Inspection",
  }[source] ?? `${source} Grade`;
}

function badgeColor(grade) {
  if (!grade) return { bg: "#475569", fg: "white" };
  const [left, right] = grade.split("/");
  const numRight = parseInt(right, 10);
  if (left === "A" && (right === "A" || numRight >= 7)) return { bg: "#10B981", fg: "white" };
  if (left === "C" || left === "D" || numRight <= 4) return { bg: "#DC2626", fg: "white" };
  return { bg: "#D97706", fg: "white" };
}

// ─── Body Inspection ──────────────────────────────────────────────────────

export function BodyInspectionCard({ data, source, dealerInspection }) {
  if (!data) return null;
  const { panels, image_url } = data;
  const exteriorPanels = panels.filter((p) => p.panel_type === "exterior" && p.severity !== "none");
  const framePanels = panels.filter((p) => p.panel_type === "frame" && p.severity !== "none");

  const exteriorCount = exteriorPanels.length;
  const frameCount = framePanels.length;
  const exteriorSev = maxSeverity(exteriorPanels);
  const frameSev = maxSeverity(framePanels);

  return (
    <Card title="Body Inspection">
      <div className="counts">
        <CountBox
          label="Frame Damage"
          icon="shield"
          count={frameCount}
          color={statusColor({ count: frameCount, severity: frameSev })}
          text={statusText({ count: frameCount, severity: frameSev, kind: "frame" })}
        />
        <CountBox
          label="Exterior Damage"
          icon="warn"
          count={exteriorCount}
          color={statusColor({ count: exteriorCount, severity: exteriorSev })}
          text={statusText({ count: exteriorCount, severity: exteriorSev, kind: "exterior" })}
        />
      </div>

      <DamageLegend panels={panels} />

      <div className="diagrams">
        <DiagramPanel
          title="Exterior Panels"
          imageUrl={image_url}
          panels={exteriorPanels}
          silhouette="body"
        />
        <DiagramPanel
          title="Frame Structure"
          imageUrl={image_url}
          panels={framePanels}
          silhouette="chassis"
        />
      </div>

      {source === "dealer" && dealerInspection && <DealerRankTotals data={dealerInspection} />}
    </Card>
  );
}

function CountBox({ label, count, color, text, icon }) {
  return (
    <div style={{ background: color.bg, color: color.fg }}>
      <header>{icon === "shield" ? "🛡" : "⚠"} {label}</header>
      <strong>{count}</strong>
      <small>{text}</small>
    </div>
  );
}

function DiagramPanel({ title, imageUrl, panels, silhouette }) {
  return (
    <section>
      <h4>{title}</h4>
      {imageUrl ? (
        <img src={imageUrl} alt={title} />
      ) : (
        <svg viewBox="0 0 400 300">
          <use href={`#silhouette-${silhouette}`} />
          {panels.map((p) => {
            const coords = PANEL_COORDS[p.panel_id];
            if (!coords) return null;
            const isExchange = p.damage_codes.includes("CHANGE");
            return (
              <g key={p.panel_id}>
                <circle
                  cx={coords.x}
                  cy={coords.y}
                  r={10}
                  fill={severityMarkerColor(p.severity)}
                />
                {isExchange && (
                  <text x={coords.x} y={coords.y + 4} textAnchor="middle" fill="white" fontSize="12">
                    ×
                  </text>
                )}
              </g>
            );
          })}
        </svg>
      )}
      <UnknownPanels panels={panels} />
    </section>
  );
}

function UnknownPanels({ panels }) {
  const unknown = panels.filter((p) => !PANEL_COORDS[p.panel_id]);
  if (unknown.length === 0) return null;
  return (
    <details>
      <summary>{unknown.length} affected panel(s) not shown on diagram</summary>
      <ul>
        {unknown.map((p) => (
          <li key={p.panel_id}>
            {p.label} — {p.description}
          </li>
        ))}
      </ul>
    </details>
  );
}

// Severity-based legend. Source-agnostic: we group by `panel.severity` and
// label each group by its representative `panel.description`. This avoids
// hard-coding any per-source damage-code vocabulary into the renderer.
function DamageLegend({ panels }) {
  const bySeverity = new Map(); // severity → first-seen description
  panels.forEach((p) => {
    if (p.severity === "none") return;
    if (!bySeverity.has(p.severity)) bySeverity.set(p.severity, p.description);
  });
  if (bySeverity.size === 0) return null;
  const order = ["high", "medium", "low"];
  return (
    <div className="legend">
      {order
        .filter((sev) => bySeverity.has(sev))
        .map((sev) => (
          <span key={sev}>
            <i style={{ background: severityMarkerColor(sev) }} />
            {bySeverity.get(sev)}
          </span>
        ))}
      {/* The Exchange overlay is shown separately because it applies on top of any severity. */}
      {panels.some((p) => p.damage_codes?.includes("CHANGE")) && (
        <span>
          <i style={{ background: "#DC2626" }}>×</i> Exchange
        </span>
      )}
    </div>
  );
}

// Dealer-only totals from `inspection_report.dealer_inspection`. This helper
// renders the aggregate totals; for the per-rank lines (Rank 1/2/A/B/C) read
// `dealer_inspection.rank_counts` ({ rank_one, rank_two, rank_a, rank_b, rank_c }).
function DealerRankTotals({ data }) {
  const fmt = (n) => (n === 0 ? "None" : `${n}`);
  return (
    <div className="ranks">
      <div>
        <strong>Exterior</strong>
        <span>Ranked panels — {fmt(data.exterior_damage)}</span>
      </div>
      <div>
        <strong>Frame</strong>
        <span>Ranked panels — {fmt(data.critical_frame_damage)}</span>
      </div>
    </div>
  );
}

// ─── Inspection Report ────────────────────────────────────────────────────

export function InspectionReportCard({ data }) {
  if (!data) return null;
  const ir = data;
  const badge = badgeColor(ir.overall_grade);

  return (
    <Card title="Inspection Report" tooltip={ir.grade_description}>
      <header>
        <div className="badge" style={{ background: badge.bg, color: badge.fg }}>
          {ir.overall_grade ?? "—"}
        </div>
        <h3>{sourceLabel(ir.source)}</h3>
        <span
          className="pill"
          style={
            ir.has_accident
              ? { background: "#FEF2F2", color: "#B91C1C" }
              : { background: "#ECFDF5", color: "#047857" }
          }
        >
          {ir.has_accident ? "Accident History" : "No Accident"}
        </span>
      </header>

      <small className="muted">{ir.grade_description}</small>

      {ir.category_grades.map((row) => (
        <CategoryRow
          key={row.category}
          status={row.status}
          label={categoryLabel(row.category)}
          summary={
            row.category === "frame"
              ? ir.accident_summary
              : row.category === "exterior"
              ? ir.exterior_summary
              : null
          }
        />
      ))}

      {ir.accident_reason && <p className="reason">{ir.accident_reason}</p>}

      <a className="learn-more">Learn more about grading systems →</a>

      {ir.inspection_sheet_url && (
        <a className="sheet-link" href={ir.inspection_sheet_url} target="_blank" rel="noreferrer">
          View original inspection sheet →
        </a>
      )}

      {ir.checklist.length > 0 && <Checklist items={ir.checklist} />}
      {ir.issues.length > 0 && <IssuesFound items={ir.issues} />}
      {ir.accident_cost_summary && <AccidentInsurance data={ir.accident_cost_summary} />}
      {ir.accident_history.length > 0 && <AccidentHistory items={ir.accident_history} />}
      {ir.inspector_notes && <Quote>{ir.inspector_notes}</Quote>}
      {ir.dealer_inspection && <DealerSummary data={ir.dealer_inspection} />}
    </Card>
  );
}

function CategoryRow({ status, label, summary }) {
  const color =
    status === "good"
      ? { bg: "#ECFDF5", fg: "#047857" }
      : status === "caution"
      ? { bg: "#FFFBEB", fg: "#B45309" }
      : { bg: "#FEF2F2", fg: "#B91C1C" };
  return (
    <div className="row" style={{ background: color.bg, color: color.fg }}>
      <span className="pill">{status === "good" ? "A" : status === "caution" ? "B" : "C"}</span>
      <strong>{label}</strong>
      {summary && <span>— {summary}</span>}
    </div>
  );
}

function Checklist({ items }) {
  const groups = items.reduce((acc, it) => {
    (acc[it.group] ??= []).push(it);
    return acc;
  }, {});
  return (
    <details>
      <summary>Checklist ({items.length} items)</summary>
      {Object.entries(groups).map(([group, list]) => (
        <section key={group}>
          <h5>{group}</h5>
          {list.map((it, i) => (
            <div key={i} className="checklist-row">
              <span>{it.item}</span>
              <span>{translateKR(it.result)}</span>
            </div>
          ))}
        </section>
      ))}
    </details>
  );
}

function IssuesFound({ items }) {
  return (
    <section>
      <h4>Issues Found</h4>
      <ul>
        {items.map((it, i) => (
          <li key={i}>
            <span className={`pill status-${it.status}`}>{it.category}</span>
            {it.detail}
            {it.faults?.length > 0 && <span className="faults">[{it.faults.join(", ")}]</span>}
          </li>
        ))}
      </ul>
    </section>
  );
}

function AccidentInsurance({ data }) {
  const incidents = data.incidents ?? [];
  // Most-recent first; source dates are "YYYY.MM.DD" so a lexical sort works.
  const sorted = [...incidents].sort((a, b) => (b.date ?? "").localeCompare(a.date ?? ""));
  // The total is the sum of the rows shown, so the figures always reconcile.
  const total = incidents.length
    ? incidents.reduce((sum, i) => sum + i.insurance_paid, 0)
    : data.insurance_paid;
  const count = incidents.length || data.total_incidents;
  return (
    <section>
      <h4>Accident History</h4>
      <div><strong>{count} accident{count === 1 ? "" : "s"}</strong></div>
      {sorted.map((inc, i) => (
        <div key={i} style={{ display: "flex", justifyContent: "space-between" }}>
          <span>{inc.date ?? `Accident ${i + 1}`}</span>
          <span>${inc.insurance_paid.toLocaleString()}</span>
        </div>
      ))}
      <div style={{ display: "flex", justifyContent: "space-between", fontWeight: 700 }}>
        <span>Total</span>
        <span>${total.toLocaleString()}</span>
      </div>
    </section>
  );
}

function AccidentHistory({ items }) {
  return (
    <section>
      <h4>Accident History</h4>
      <ul>
        {items.map((a, i) => (
          <li key={i}>
            <strong>{a.date}</strong> — {a.description} ({a.type === "owner_self" ? "self" : "third-party"})
          </li>
        ))}
      </ul>
    </section>
  );
}

function Quote({ children }) {
  return <blockquote>“{children}”</blockquote>;
}

function DealerSummary({ data }) {
  return (
    <section className="dealer-summary">
      <div>Frame damage: {data.critical_frame_damage}</div>
      <div>Exterior damage: {data.exterior_damage}</div>
      <div>
        Insurance: {data.insurance_history ?? 0} claim{data.insurance_history === 1 ? "" : "s"}
        {data.insurance_amount != null && `, $${data.insurance_amount.toLocaleString()}`}
      </div>
      <div>Frame status: {translateKR(data.frame_status) ?? "—"}</div>
      <div>Panel status: {translateKR(data.panel_status) ?? "—"}</div>
    </section>
  );
}

// ─── PANEL_COORDS (excerpt — fill in per your silhouette) ─────────────────

const PANEL_COORDS = {
  hood: { x: 200, y: 60 },
  roof: { x: 200, y: 150 },
  "trunk-lid": { x: 200, y: 240 },
  "front-fender-l": { x: 130, y: 80 },
  "front-fender-r": { x: 270, y: 80 },
  "front-door-l": { x: 130, y: 140 },
  "front-door-r": { x: 270, y: 140 },
  "rear-door-l": { x: 130, y: 190 },
  "rear-door-r": { x: 270, y: 190 },
  "rear-fender-l": { x: 130, y: 240 },
  "rear-fender-r": { x: 270, y: 240 },
  "front-bumper": { x: 200, y: 20 },
  "rear-bumper": { x: 200, y: 280 },
  // … extend per your silhouette
};

// ─── translateKR (excerpt — extend per Korean Glossary) ───────────────────

const KR_EN = {
  이상없음: "No issues",
  양호: "Pass",
  불량: "Fail",
  교환: "Replaced",
  판금: "Sheet metal",
  용접: "Welded",
  부식: "Corrosion",
  손상: "Damaged",
  누유: "Oil leak",
  // … extend per section 9
};
function translateKR(s) {
  if (!s) return s;
  return KR_EN[s.trim()] ?? s;
}
```

***

## 11. Worked example A — Glovis auction (real API response)

This is a verbatim slice of a real `GET /v1/vehicles/{id}` response from sandbox at the time of writing. Sandbox base URL is `https://sandbox-api.lmnauto.com`; production is `https://api.lmnauto.com` (see [Quickstart](/quickstart) for both). The response shape is identical across environments — sandbox just runs against the staging data snapshot.

**Request**

```bash theme={null}
curl -H "x-api-key: $LMN_KEY" "https://sandbox-api.lmnauto.com/v1/vehicles/glovis_20260521_1078_1005"
```

**Response (inspection portion)** — note `image_url: null` and `dealer_inspection` absent. This is a Hyundai Sonata 2020 with significant frame damage history (overall grade C/1).

```json theme={null}
{
  "id": "glovis_20260521_1078_1005",
  "make": "Hyundai",
  "model": "Sonata",
  "year": 2020,
  "vin": "KMHL341DBLA053529",
  "body_condition": {
    "image_url": null,
    "panels": [
      { "panel_id": "rear-fender-l",       "panel_type": "exterior", "damage_code": "xx", "damage_codes": ["xx"], "label": "Rear Fender (L)",          "description": "Replaced",           "severity": "high"   },
      { "panel_id": "rear-fender-r",       "panel_type": "exterior", "damage_code": "xx", "damage_codes": ["xx"], "label": "Rear Fender (R)",          "description": "Replaced",           "severity": "high"   },
      { "panel_id": "trunk",               "panel_type": "exterior", "damage_code": "xx", "damage_codes": ["xx"], "label": "Trunk",                    "description": "Replaced",           "severity": "high"   },
      { "panel_id": "side-sill-l",         "panel_type": "exterior", "damage_code": "xx", "damage_codes": ["xx"], "label": "Side Sill (L)",            "description": "Replaced",           "severity": "high"   },
      { "panel_id": "side-sill-r",         "panel_type": "exterior", "damage_code": "xx", "damage_codes": ["xx"], "label": "Side Sill (R)",            "description": "Replaced",           "severity": "high"   },
      { "panel_id": "rear-panel",          "panel_type": "frame",    "damage_code": "xx", "damage_codes": ["xx"], "label": "Rear Panel",               "description": "Replaced",           "severity": "high"   },
      { "panel_id": "a-pillar-l",          "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "A-Pillar (L)",             "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "a-pillar-r",          "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "A-Pillar (R)",             "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "b-pillar-l",          "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "B-Pillar (L)",             "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "b-pillar-r",          "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "B-Pillar (R)",             "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "rear-wheel-house-l",  "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "리어휠하우스(좌)",         "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "rear-wheel-house-r",  "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "리어휠하우스(우)",         "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "seat-back-panel",     "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "시트백패널",                "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "trunk-floor",         "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "Trunk Floor",              "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "side-member-frame",   "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "사이드멤버(프레임)",       "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "rear-inside-panel-l", "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "리어인사이드패널(좌)",     "description": "Sheet Metal/Welding","severity": "medium" },
      { "panel_id": "rear-inside-panel-r", "panel_type": "frame",    "damage_code": "w",  "damage_codes": ["w"],  "label": "리어인사이드패널(우)",     "description": "Sheet Metal/Welding","severity": "medium" }
    ]
  },
  "inspection_report": {
    "source": "glovis",
    "overall_grade": "C/1",
    "grade_description": "Accident/Condition (1=Worst, 9=Best)",
    "accident_summary": null,
    "has_accident": false,
    "has_frame_damage": true,
    "exterior_summary": "Engine: oil leak, major part replacement needed, oil leak (turbo). Transmission: major part replacement needed",
    "accident_reason": null,
    "category_grades": [
      { "category": "Engine",       "status": "bad"  },
      { "category": "Brakes",       "status": "good" },
      { "category": "Steering",     "status": "good" },
      { "category": "Electrical",   "status": "good" },
      { "category": "Shift Issue",  "status": "bad"  },
      { "category": "HVAC",         "status": "good" },
      { "category": "동력",          "status": "good" },
      { "category": "Interior",     "status": "good" },
      { "category": "Lighting",     "status": "good" }
    ],
    "checklist": [
      "… 48 items elided …"
    ],
    "accident_history": [],
    "accident_cost_summary": null,
    "issues": [
      "… 2 entries elided …"
    ],
    "inspection_sheet_url": null,
    "inspector_notes": ", Consultation Unavailable; Rear Fender (Left) Replacement, Rear Fender (Right) Replacement, Trunk Replacement, Rear Panel Replacement, Side Sill Panel (Left) Replacement, Side Sill Panel (Right) Replacement, A-Pillar (Left) Sheet Metal/Welding, A-Pillar (Right) Sheet Metal/Welding, B-Pillar (Left) Sheet Metal/Welding, B-Pillar (Right) Sheet Metal/Welding, Rear Wheelhouse (Left) Sheet Metal/Welding, Rear Wheelhouse (Right) Sheet Metal/Welding, Seat Back Panel Sheet Metal/Welding, Trunk Floor Sheet Metal/Welding, Side Member (Frame) Sheet Metal/Welding, Rear Inside Panel (Left) Sheet Metal/Welding, Rear Inside Panel (Right) Sheet Metal/Welding"
  }
}
```

**Things to notice about this real response (vs. documented ideal):**

* **`image_url` is `null` for Glovis.** Glovis (and K-Car, dealer) have no mirrored image — render the diagram from `panels[]` directly. SK and Lotte **do** return a mirrored `image_url` (damage baked in), and AJ returns a blueprint base image drawn under the markers.
* **`accident_summary` and `accident_reason` are `null` even though `has_frame_damage: true`.** Don't gate the "Accident History" pill on `accident_summary` being non-null; use `has_accident` / `has_frame_damage` directly.
* **`checklist` has 48 items**, not the canonical "17 items" that older documentation mentions. The count varies per inspection — render whatever the array contains.
* **Some `panel.label` values are Korean** (e.g. `"리어휠하우스(좌)"`) even though others are English. The Korean-to-English mapping is incomplete; display `label` as-is and translate client-side if you need an English-only UI. See the [Korean Glossary](#9-korean-glossary).
* **`category_grades[].category` can be Korean** (`"동력"` in this row) for categories not yet in the English mapping.
* **`exterior_summary` here describes mechanical issues** (engine/transmission), not exterior panels. The field name is historically misleading on Glovis — it's effectively a free-text "summary of issues found" rather than strictly panel-related.

**UI mapping**

* Badge: `C/1` on red (C is left, 1 is the worst condition score).
* Pill: green **No Accident** (`has_accident: false`) — but a separate caution callout for `has_frame_damage: true` is healthier UX, since the car has significant frame welding.
* Body Inspection card:
  * Counts: Frame 11 panels (mostly medium `w`, one high `xx` = `rear-panel`), Exterior 6 panels (high `xx` only — five rear/side replacements).
  * Diagram: render from `panels[]` because `image_url` is null. Markers: 6 red on exterior silhouette, 1 red + 10 amber on frame silhouette.
* Category rows: 9 entries — render two reds ("Engine", "Shift Issue") and seven greens. The Korean `동력` row renders as-is unless you translate client-side.
* Checklist: collapsible list with 48 items (group-by-group).
* Issues Found: 2 entries.
* Inspector notes block: long run-on string of replacement summaries.
* No accident\_cost\_summary, no accident\_history entries (the frame damage exists but no structured accident record is currently associated).

***

## 12. Worked example B — Encar dealer (real API response)

This is a verbatim slice of a real `GET /v1/vehicles/{id}` response from sandbox. It is a BMW M5 2019 listing with two ranked exterior panels — a clean worked example of the canonical dealer shape described in §5.2 and §4.6: nested `type.code` translated to a drawable camelCase `panel_id`, and `statusTypes[]` normalized into canonical damage codes.

**Request**

```bash theme={null}
curl -H "x-api-key: $LMN_KEY" "https://sandbox-api.lmnauto.com/v1/vehicles/encar_41657860"
```

**Response (inspection portion)** — verbatim from sandbox post-fix.

```json theme={null}
{
  "id": "encar_41657860",
  "make": "BMW",
  "model": "M5",
  "year": 2019,
  "vin": null,
  "body_condition": {
    "image_url": null,
    "panels": [
      {
        "panel_id": "quarterPanelRight",
        "raw_panel_code": "P062",
        "raw_label_ko": "쿼터 패널(우)",
        "panel_type": "exterior",
        "damage_code": "METAL",
        "damage_codes": ["METAL"],
        "label": "Quarter Panel (R)",
        "description": "Sheet metal / Welding",
        "severity": "medium"
      },
      {
        "panel_id": "frontFenderLeft",
        "raw_panel_code": "P021",
        "raw_label_ko": "프론트 휀더(좌)",
        "panel_type": "exterior",
        "damage_code": "CHANGE",
        "damage_codes": ["CHANGE"],
        "label": "Front Fender (L)",
        "description": "Exchange (replaced)",
        "severity": "high"
      }
    ]
  },
  "inspection_report": {
    "source": "dealer",
    "overall_grade": null,
    "grade_description": "Encar dealer inspection",
    "accident_summary": "No structural damage",
    "has_accident": false,
    "has_frame_damage": false,
    "exterior_summary": "2 exterior damage cases",
    "accident_reason": null,
    "category_grades": [
      { "category": "frame", "status": "good" },
      { "category": "exterior", "status": "caution" }
    ],
    "checklist": [
      { "group": "Engine", "item": "Engine oil leak", "result": "No issues" },
      { "group": "Transmission", "item": "Oil leak (auto)", "result": "No issues" }
    ],
    "accident_history": [],
    "accident_cost_summary": null,
    "issues": [],
    "inspection_sheet_url": null,
    "inspector_notes": "No notable defects beyond the recorded exterior damage.",
    "dealer_inspection": {
      "critical_frame_damage": 0,
      "exterior_damage": 2,
      "frame_status": null,
      "panel_status": null,
      "simple_repair": false,
      "insurance_history": 1,
      "insurance_amount": 1370,
      "registration_no": "20-12345678",
      "vehicle_info": {
        "Valid Period": "Apr 29, 2025 ~ Apr 28, 2030",
        "First Registered": "Apr 29, 2025",
        "Warranty": "Manufacturer warranty",
        "Engine Model": "S63B44T4",
        "Base Price": "-"
      },
      "overall_status": [
        { "item": "Emissions", "status": "Pass", "detail": "" },
        { "item": "Tuning", "status": "None", "detail": "" },
        { "item": "Odometer", "status": "Normal", "detail": "" },
        { "item": "Color", "status": "Same", "detail": "" }
      ],
      "photos": [
        { "url": "https://.../front.jpg", "label": "Front" },
        { "url": "https://.../rear.jpg", "label": "Rear" }
      ],
      "certification": {
        "inspector": "한국자동차진단보증",
        "notifier": "○○모터스",
        "date": "2026-05-12"
      }
    }
  }
}
```

**Things to notice:**

* Both panels have **canonical camelCase `panel_id`** values (`quarterPanelRight`, `frontFenderLeft`) drawable directly on the standard Encar silhouette.
* **`raw_panel_code`** preserves the upstream Encar P-code (`P062`, `P021`) so you can correlate against Encar's own admin tooling or display the raw value alongside the canonical one.
* **`raw_label_ko`** carries the upstream Korean label verbatim — useful if your customer base includes Korean speakers or for showing both languages side-by-side.
* **`damage_code`** is the canonical normalized code derived from Encar's `statusTypes[]` (W → `METAL`, X → `CHANGE`) — not the rank attribute. The rank attribute (`RANK_TWO`, `RANK_ONE`) remains preserved in the aggregate `dealer_inspection.exterior_damage` count (`2`).
* **`severity`** is computed from the canonical damage code: `METAL` → `medium` (sheet metal repair), `CHANGE` → `high` (replacement). Use the emitted value directly; do not re-derive locally.
* **`frame_status: null`, `panel_status: null`** for this car — Encar didn't return the descriptive status strings. Render the dealer summary with placeholder dashes for those fields.
* **`insurance_history: 1`, `insurance_amount: 1370`** — own-damage accident history is populated for dealer cars from a separate insurance-record upstream; the amount is **whole USD** (FX-converted server-side), matching the auction `accident_cost_summary` convention — don't re-apply `fx_rate`. A `null` (not `0`) still means "no data reported"; `0` means "no claims".
* **`vehicle_info`, `overall_status`, `photos`, `certification`** carry the full inspection-sheet detail. Render `vehicle_info` as a key-value list, `overall_status` as a status table, `photos` as a thumbnail row, and `certification` as a small attribution line.

**UI mapping**

* Badge: slate `—` (no `overall_grade`). Title: "Encar Dealer Inspection".
* Pill: green **No Accident** (`has_accident: false`).
* Two grade rows: green "Accident Grade" + "No structural damage", amber "Exterior Grade" + "2 exterior damage cases".
* Mechanical `checklist[]` present (engine, transmission, steering, braking, electrical, …, EN-translated); inspector notes present; no auction-only accident-cost summary.
* Dealer summary block at bottom: `frame_status` / `panel_status` dash (null this car); `exterior_damage: 2`, insurance history (`1` claim, \$1,370), plus the `vehicle_info` / `overall_status` / `photos` / `certification` sheet detail carry the real signal.
* Body Inspection card:
  * Count boxes: Frame **0** (green "No structural damage"), Exterior **2** (red "Major damage" — driven by the high-severity `CHANGE` on the front-fender-left).
  * Diagram: amber marker on the right quarter panel (`quarterPanelRight`, METAL/medium), red marker with `×` overlay on the left front fender (`frontFenderLeft`, CHANGE/high).
  * Affected panels list: empty — both panels are drawable.
* Rank summary below diagrams: "Exterior — 2 ranked panels", "Frame — None".

***

## 13. Edge cases (full list)

<AccordionGroup>
  <Accordion title="Both objects null">
    Some auction listings haven't been inspected yet (ingestion can lag by minutes to hours, especially for KCar). Render a neutral empty state: *"Inspection report not yet available. Check back in a few hours."* Do not show a "report" card with all blank fields — it implies "we inspected and found nothing", which is wrong.
  </Accordion>

  <Accordion title="One null, one populated">
    Render only the populated card. Specifically:

    * `body_condition` null but `inspection_report` populated → no diagram, show the grade card alone with all available fields.
    * `body_condition` populated but `inspection_report` null → diagram alone with the count boxes; omit any reference to grade.
  </Accordion>

  <Accordion title="Unknown panel_id">
    If we emit a `panel_id` not in your `PANEL_COORDS` map (we extend the catalog as inspector data evolves), do **not** drop the panel. Render it in a textual list under the diagram via the `UnknownPanels` component in section 10. The damage is real even if you don't have a marker position for it.
  </Accordion>

  <Accordion title="Korean status strings in dealer fields">
    `dealer_inspection.frame_status` and `panel_status` are raw Encar Korean. Either display as-is (your end customers may include Korean speakers) or translate via section 9's dictionary. The `panel_status` value sometimes includes a count suffix (`"교환 1"`) — strip and translate the term, preserve the digit.
  </Accordion>

  <Accordion title="accident_cost_summary in USD">
    All monetary fields here are already FX-converted to USD whole integers (computed server-side using the day's exchange rate). Do not re-convert. If `fx_rate` on the vehicle response is `null`, this field is also `null`.

    `incidents[]` (Glovis) lists one row per insurance-settled accident — `{ date, insurance_paid, repair_cost }`. The aggregate `insurance_paid` **equals the sum of `incidents[].insurance_paid`**: rounding to whole USD is applied per incident and then summed, so a per-incident breakdown always reconciles exactly to the total (don't recompute from the won amounts client-side). When `incidents[]` is `[]` (a source with no per-incident detail), fall back to showing the count + aggregate only.
  </Accordion>

  <Accordion title="Auction sources without image_url">
    Only Glovis returns `image_url`. SK / AJ / Lotte / KCar all return `null` — draw your own diagram from `panels[]`. Dealer is the same.
  </Accordion>

  <Accordion title="Checklist with empty result">
    Some `checklist[].result` values may be empty strings (the inspector marked the item but left the result blank). Treat as missing — render the item with a `—` placeholder, do not skip the item.
  </Accordion>

  <Accordion title="Dealer panels with both rank and damage codes">
    For dealer rows, upstream Encar data may classify a panel with both a rank attribute (e.g., `RANK_ONE`) **and** a damage marker (e.g., `CHANGE`). When this happens the API **collapses** `damage_codes` to the non-rank code(s) only — so you will see `damage_codes: ["CHANGE"]`, not `["RANK_ONE", "CHANGE"]`.

    The rank attribute is not lost — it is reflected both in the aggregate counts under `inspection_report.dealer_inspection.{critical_frame_damage, exterior_damage}` and, per-rank, in `inspection_report.dealer_inspection.rank_counts` (`{ rank_one, rank_two, rank_a, rank_b, rank_c }`). `rank_counts` is tallied from the rank attributes **before** the `damage_codes` collapse described above, so it stays accurate even for panels that carry both a rank and a damage code.

    This means: on a single dealer panel you see one marker on the diagram colored by the panel's `severity` (computed from the strongest code present), and the totals beneath the diagram come from `dealer_inspection`, not from `damage_codes`.
  </Accordion>

  <Accordion title="Accident history with no cost summary">
    `accident_history` may be populated while `accident_cost_summary` is null (the source recorded an accident but the cost details weren't captured). Render the history regardless.
  </Accordion>

  <Accordion title="Stale checklist items from older inspections">
    Glovis occasionally updates its inspection format (e.g., adds new items). Older inspections may have an item from the previous taxonomy that doesn't match the current 17-item set. Just render whatever's in the array; we don't filter for "canonical" items server-side.
  </Accordion>
</AccordionGroup>

***

## 14. Localization

The inspection text is in English by default (translated server-side from Korean source data). For RTL languages (Arabic in particular):

| Source                                            | Output                                                                            |
| ------------------------------------------------- | --------------------------------------------------------------------------------- |
| `inspection_report.exterior_summary`              | English-only                                                                      |
| `inspection_report.accident_summary`              | English-only                                                                      |
| `inspection_report.accident_reason`               | English-only                                                                      |
| `inspection_report.inspector_notes`               | English — auction translated offline, dealer translated live via Google Translate |
| `inspection_report.grade_description`             | English-only                                                                      |
| `inspection_report.checklist[].result`            | Korean — translate client-side                                                    |
| `inspection_report.checklist[].item`              | English                                                                           |
| `dealer_inspection.frame_status` / `panel_status` | Korean — translate client-side                                                    |
| `accident_history[].description`                  | Mixed; often Korean for older records — translate client-side                     |

For Arabic UI, mirror the layout (badges on the right, summary text left-aligned) and run the English strings through your translation pipeline. The reference UI on lmnauto.com supports Arabic via [next-intl](https://next-intl-docs.vercel.app/); equivalent client-side translation is straightforward.

***

## 15. Performance and caching

* The detail response is cached server-side for **30 minutes** per ID (Encar dealer detail) or until the next inspection ingest (auction). Refetching more often than every 5 minutes per car is wasteful.
* The inspection diagram (`image_url`) is served from GCS with a 7-day cache header. Safe to use directly in `<img src>` without your own caching layer.
* The full response size with inspection populated runs 8-25KB. Keep that in mind if you're streaming many details client-side.

***

## 16. Verification checklist

Before you ship your rendering, verify each of these against the reference UI:

<Steps>
  <Step title="Empty state renders correctly">
    Hit a freshly-scraped Glovis listing where inspection hasn't been filed yet (`body_condition` and `inspection_report` both null). Confirm a single neutral message appears, not an empty card.
  </Step>

  <Step title="Dealer-source renders correctly">
    Hit `encar_*` ID. Confirm:

    * No overall\_grade badge (slate `—`)
    * "Encar Dealer Inspection" source label
    * Dealer summary block at bottom (vehicle info, overall status, photos, certification, insurance history)
    * Rank breakdown table under diagrams
    * Mechanical `checklist[]` present (EN-translated); inspector notes present; no auction-only accident-cost summary
  </Step>

  <Step title="Glovis rich auction renders correctly">
    Hit `glovis_*` ID with frame damage. Confirm:

    * `overall_grade` badge in correct color band
    * Red "Accident History" pill
    * All category rows render with correct status colors
    * Checklist with 17 items, collapsible
    * Accident History with at least one entry
    * Accident Cost section with USD amounts
    * Inspector notes as quote block
    * `image_url` rendered as `<img>`, not SVG
  </Step>

  <Step title="Severity coloring matches">
    For at least 3 cars with mixed damage:

    * High-severity panels show red markers
    * Medium-severity show amber
    * Low-severity show yellow outline
    * `CHANGE` panels have an `×` overlay
    * Count boxes color-match their max severity
  </Step>

  <Step title="Unknown panel fallback works">
    Manually inject a `panel_id` not in your `PANEL_COORDS` (e.g., temporarily delete one entry). Confirm the panel appears in the "X affected panels not shown on diagram" expandable list, not dropped silently.
  </Step>

  <Step title="Korean fallback works">
    For a dealer car, confirm raw Korean `frame_status` / `panel_status` either translates correctly or displays as-is (no `undefined`, no empty render).
  </Step>

  <Step title="USD amounts not re-converted">
    For an auction with `accident_cost_summary`, confirm the values render exactly as returned — no client-side division by FX, no rounding errors.
  </Step>

  <Step title="Refresh behavior">
    Hit the same car twice within 30 seconds. The second render should produce identical output. If your client adds metadata (timestamp, request ID), exclude it from the comparison.
  </Step>
</Steps>

***

## 17. FAQ

<AccordionGroup>
  <Accordion title="Why is body_condition.image_url only set for Glovis?">
    Glovis ships a pre-rendered SVG/PNG with damage markers as part of their inspection package; we mirror it to GCS. SK, AJ, Lotte, KCar provide only structured panel data — no diagram image — so we expect the client to render. Encar dealer follows the same pattern (structured `outers[]` from their API, no diagram). If you can't build SVG rendering, fall back to listing damaged panels by `label` and skipping the diagram entirely; the count boxes still convey the high-signal info.
  </Accordion>

  <Accordion title="Why is overall_grade null for dealer cars?">
    Encar's inspection API doesn't return an overall letter grade. They publish the panel attributes and statuses but no composite grade. We could synthesize one ("Letter equivalent to RANK\_X count") but chose not to — it would mix our editorial judgment with raw data, and partners are better served by the raw numbers and a clear "no overall grade" signal.
  </Accordion>

  <Accordion title="How accurate is the dealer inspection?">
    It's only as accurate as what Encar's dealer ecosystem self-reports. Encar inspectors do physically examine cars, and the response now carries near-full detail: vehicle info, overall status, the mechanical `checklist[]` (engine, transmission, steering, braking, electrical, …), inspection photos, certification, insurance history, and inspector notes. The remaining gap versus an LMN auction inspector's report is the auction-only `accident_history[]` per-incident breakdown and `accident_cost_summary`. For high-stakes decisions (export, customs), encourage your customer to request an LMN secondary inspection — that's available on the auction tab but not for dealer listings.
  </Accordion>

  <Accordion title="Can I rely on category_grades order?">
    Yes — server-side we always emit in this order: `frame, exterior, engine, transmission, brakes, suspension, electrical, interior, glass, lighting`. Categories not applicable to the source are omitted. Don't sort client-side; just render in array order.
  </Accordion>

  <Accordion title="What's the difference between has_accident and has_frame_damage?">
    `has_accident` = there's at least one accident in the car's history (could be a long-ago repair, or a current visible issue). `has_frame_damage` = there's structural (frame) damage currently visible. A repaired car may have `has_accident: true` but `has_frame_damage: false`. Use both signals for nuanced messaging — "previously repaired" vs "currently damaged" are very different stories to a buyer.
  </Accordion>

  <Accordion title="Are accident_history entries reliably ordered?">
    By date descending (most recent first). If a record has a null date (rare), it sorts to the end.
  </Accordion>

  <Accordion title="What if the same panel appears twice in panels[]?">
    That shouldn't happen — we dedupe server-side by `panel_id`. If you see it, file a bug; meanwhile, render the first occurrence and ignore subsequent.
  </Accordion>

  <Accordion title="What if I want to show the original Korean alongside the English?">
    Render the English from `accident_reason`, `exterior_summary`, etc. directly. For fields that are raw Korean (`dealer_inspection.frame_status`, `checklist[].result`), show both: `{translateKR(value)} ({value})` — gives Korean speakers the original and English speakers the translation simultaneously.
  </Accordion>

  <Accordion title="How is severity computed?">
    Use `panel.severity` directly; do not infer from `panel_type` or `damage_code`. Severity is set server-side per inspector source — each source has its own code-to-severity table at ingest time, and frame-panel promotion (when it happens) varies by source. Local re-derivation will drift from server behavior as we tune the per-source tables. See [section 4.7](#47-severity-is-server-computed--trust-the-emitted-field). The stable contract is the **set of possible values** (`"high" \| "medium" \| "low" \| "none"`) and the color you pick for each, not the rule that produced it.
  </Accordion>

  <Accordion title="Why are some panel labels in Korean while others are English?">
    For auction sources we maintain a Korean-to-English mapping for panel names; when a panel name isn't in the mapping (typically a niche or recently-added panel), the Korean falls through to `panel.label`. For dealer sources, `panel.label` is what Encar's API returns — usually English or the raw camelCase ID. In both cases, your client should display `label` as-is and not assume it is always English. If you spot consistently-untranslated values, email [integrations@lmnauto.com](mailto:integrations@lmnauto.com) — additions are quick.
  </Accordion>

  <Accordion title="Can I use this guide for the deprecated v1.16 response shape?">
    This guide is for v1.18+ (the unified inspection shape that landed alongside the dealer-inspection enrichment). Earlier versions used a flatter structure; consult the [Changelog](/changelog) for the migration table.
  </Accordion>
</AccordionGroup>

***

## 18. Versioning and stability

This is part of `GET /v1/vehicles/{id}` and follows our [versioning policy](/conventions#versioning):

* **Additive changes** (new fields on `body_condition.panels[]`, new fields on `inspection_report`, new fields on `dealer_inspection`) ship without a version bump. Your renderer must ignore unknown fields safely.
* **New panel IDs** ship without notice (additive). Your `PANEL_COORDS` should have a graceful fallback.
* **New damage codes** ship without notice (additive). Your damage legend should tolerate unknown codes by falling back to `panels[].description` for display.
* **Removing a field** is a breaking change → new major version + 90-day deprecation window + changelog notice.
* **Renaming a field** is a breaking change → same policy.
* **Changing a `severity` rule** is treated as additive (the rule is server-side; you receive the computed value). We may move panels between severity buckets — the partner-visible contract is the field name and type, not the computation.

The `inspection_report.dealer_inspection` sub-object is new in v1.18 (shipped 2026-05-20). Earlier versions of the doc may not show it.

***

## 19. Where to look on lmnauto.com

For pixel-faithful comparison, open the same ID in both places — the consumer site at `lmnauto.com` and the partner API at `sandbox-api.lmnauto.com` — and diff the JSON against the rendered UI.

| Source                                    | Consumer URL pattern                                      | Worked example in this guide                                                                |
| ----------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| Glovis / SK / AJ / Lotte / KCar (auction) | `https://lmnauto.com/en/auction?source=auctions&tab=<id>` | [§11](#11-worked-example-a--glovis-auction-real-api-response) — `glovis_20260521_1078_1005` |
| Encar (dealer)                            | `https://lmnauto.com/en/auction?source=retail&tab=<id>`   | [§12](#12-worked-example-b--encar-dealer-real-api-response) — `encar_41657860`              |

The IDs in the worked examples are real listings drawn from sandbox at the time of writing; they may roll out of the active inventory window. For a fresh comparison, pick any current ID from `GET /v1/vehicles?source=<src>&limit=1` and use that same ID in both URLs.

If your rendering ever looks materially different from the lmnauto.com reference, **diff against the JSON returned from `GET /v1/vehicles/{id}` for the same ID** rather than trying to reverse-engineer the UI. The JSON is the contract; the UI is one possible rendering of it.

***

## 20. Support

If you encounter rendering edge cases this guide doesn't cover, or you find an inconsistency between the JSON and the reference UI, email [integrations@lmnauto.com](mailto:integrations@lmnauto.com) with:

* The vehicle ID (auction or dealer)
* The raw JSON response from `GET /v1/vehicles/{id}` (paste in full)
* A screenshot or description of how your UI rendered it
* A link or screenshot of the corresponding `lmnauto.com` page

We'll diagnose within one business day and either patch the rendering guide, fix the data, or both.
