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>
PartSourceExample
AREA2- or 3-letter body-area code derived from finding.panel.DFL (door front left).
TYPE2-letter damage-type code derived from finding.damage_type.DN (dent).
SEVSeverity 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_hoodHD
car_roofRF
car_trunkTR
car_front_bumperBF
car_rear_bumperBR
car_door_flDFL
car_door_frDFR
car_door_rlDRL
car_door_rrDRR
car_fender_flFFL
car_fender_frFFR
car_quarter_rlQRL
car_quarter_rrQRR
car_rockerRK
car_mirrorMR

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.

GradeLabelRule (any clause matches)
K1Showroom conditionZero findings.
K2Minor damage — cosmetic onlyAt least one light finding (and not enough to escalate).
K3Moderate damage — repair recommendedAt least one medium finding, OR five-plus light findings.
K4Major damage — requires repair before deliveryAt least one severe finding, OR four-plus medium findings.
K5Not deliverableTwo-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 caseUse
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.