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:
<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[]:
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.
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"}'{
"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:
{
"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:
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
- Detecting and grading damage — how the raw findings get produced in the first place.
- Before/after rental inspection delta — apply the same grading transforms to "new damage only".