AIAG codes and K1–K5 grading

When damage mode is on, every verification gets two rollups computed automatically from the raw damage_findings list:

  • aiag_codes — stable, alphabetically sorted body codes used in North American finished-vehicle logistics.
  • k_grade — a single letter-and-digit code (K1..K5) used in European leasing returns.

Both rollups are deterministic pure functions in lib/verify-ai/damage/grading.ts. They take DamageFinding[] and a JSON table (aiag-table.json, k-grade-thresholds.json) and return the rollups synchronously — no I/O, no LLM call.

AIAG body codes

The AIAG (Automotive Industry Action Group) finished-vehicle convention encodes each damage spot as a three-part code:

plaintext
<AREA>-<TYPE>-<SEV>

| Part | Source | Example | | ------ | ------------------------------------------------------------------------- | ------------------------ | | AREA | 2- or 3-letter body-area code derived from finding.panel. | DFL (door front left). | | TYPE | 2-letter damage-type code derived from finding.damage_type. | DN (dent). | | SEV | Severity rank: 1 = light, 2 = medium, 3 = severe. | 2. |

So a medium dent on the front-left door becomes DFL-DN-2.

The table

The lookup table lives in lib/verify-ai/damage/aiag-table.json and is a flat JSON object — easy to extend without code changes. The current v1 codes:

| Panel (finding.panel) | Code | | ----------------------- | ----- | | car_hood | HD | | car_roof | RF | | car_trunk | TR | | car_front_bumper | BF | | car_rear_bumper | BR | | car_door_fl | DFL | | car_door_fr | DFR | | car_door_rl | DRL | | car_door_rr | DRR | | car_fender_fl | FFL | | car_fender_fr | FFR | | car_quarter_rl | QRL | | car_quarter_rr | QRR | | car_rocker | RK | | car_mirror | MR |

Damage types: scratch=SC, dent=DN, paint_chip=PC, crack=CR, broken=BR, missing=MS, rust=RU, tear=TR, stain=ST, glass_damage=GL, other=OT.

Unknown panels or types fall back to OT — codes are stable and lookups never miss.

Computing codes from findings

The transform is toAiagCodes(findings: DamageFinding[]): string[]:

ts
import { toAiagCodes } from "@/lib/verify-ai/damage/grading";
 
const codes = toAiagCodes([
  { panel: "car_door_fl", damage_type: "dent",    severity: "medium", /* ... */ },
  { panel: "car_front_bumper", damage_type: "scratch", severity: "light", /* ... */ },
  { panel: "car_door_fl", damage_type: "dent",    severity: "medium", /* dup */ },
]);
// → ["BF-SC-1", "DFL-DN-2"]

Notes:

  • severity: "none" is excluded. No-damage findings never produce a code.
  • Deduplicated. Two identical findings on the same panel produce one code.
  • Sorted. Output is alphabetically sorted for stable diffs and spreadsheet imports.

Codes are persisted in the aiag_codes JSONB column on verify_ai_verifications, ready for export to a TMS, a yard-management system, or an audit spreadsheet.

bash
curl https://verify.switchlabs.dev/api/v1/verify \
  -H "X-API-Key: vai_your_api_key" \
  -F "image=@intake.jpg" \
  -F "policy=pol_fleet_damage" \
  -F 'metadata={"vin":"1FAFP404X1F123456"}'
json
{
  "id": "ver_8x92m4k9",
  "is_compliant": false,
  "aiag_codes": ["BF-SC-1", "DFL-DN-2", "HD-PC-1"],
  "k_grade": "K3"
}

K1–K5 grades

K-grades collapse the full findings list to a single bucket — useful for European leasing returns where the contract pegs the residual value to a discrete condition grade.

| Grade | Label | Rule (any clause matches) | | ----- | ------------------------------------------- | ---------------------------------------------------------------------- | | K1 | Showroom condition | Zero findings. | | K2 | Minor damage — cosmetic only | At least one light finding (and not enough to escalate). | | K3 | Moderate damage — repair recommended | At least one medium finding, OR five-plus light findings. | | K4 | Major damage — requires repair before delivery | At least one severe finding, OR four-plus medium findings. | | K5 | Not deliverable | Two-plus severe findings, OR four-plus findings with a severe max. |

The rule table is evaluated top-down — first match wins — in k-grade-thresholds.json:

json
{
  "grades": [
    { "grade": "K5", "rule": { "any": [
      { "n_severe_gte": 2 },
      { "severity_max_eq": "severe", "n_total_gte": 4 }
    ]}},
    { "grade": "K4", "rule": { "any": [
      { "n_severe_gte": 1 },
      { "severity_max_eq": "medium", "n_total_gte": 4 }
    ]}},
    /* K3, K2, K1 ... */
  ]
}

The transform is toKGrade(findings: DamageFinding[]): KGrade:

ts
import { toKGrade, toOverallSeverity, gradeFindings } from "@/lib/verify-ai/damage/grading";
 
toKGrade([]);                                  // → "K1"
toKGrade([f("car_door_fl", "scratch", "light")]); // → "K2"
toKGrade([f("car_door_fl", "dent",    "medium")]); // → "K3"
toKGrade([f("car_door_fl", "crack",   "severe")]); // → "K4"
toKGrade([
  f("car_door_fl", "crack",  "severe"),
  f("car_door_fr", "broken", "severe"),
]); // → "K5"
 
// Convenience: get all three at once.
gradeFindings(findings); // { aiag_codes, k_grade, overall_severity }

toOverallSeverity() is the same family of pure helper — it returns the highest severity across all findings ("none" | "light" | "medium" | "severe").

When to use AIAG vs K-grade

Both rollups are computed on every damage-mode verification — you don't pick one ahead of time. Use the one that fits the downstream system:

| Use case | Use | | ----------------------------------------------------------------- | ----------- | | North American finished-vehicle logistics (rail, port, drayage). | aiag_codes | | Insurance claims systems / DRP integrations. | aiag_codes | | European OEM leasing returns and residual-value calculations. | k_grade | | Single-number dashboards or contract thresholds. | k_grade | | Engineering / drift analysis (per-panel histograms). | Raw damage_findings |

Two systems, same source — you don't need to keep two pipelines in sync. Bumping a threshold (e.g. demoting "five light findings" from K3 to K2) is a one-line JSON edit in k-grade-thresholds.json.

Versioning

Both lookup tables carry a version field ("1.0.0" in v1). Changing the rules should bump the version so downstream consumers can detect grade-drift across deployments. The grades themselves are stored as plain strings in verify_ai_verifications.k_grade — there's no FK constraint, so you can backfill historical rows by re-running the transform against the stored damage_findings.

What's next

Get in Touch

Questions about pricing, integrations, or custom deployments? We'd love to hear from you.