viswanani commited on
Commit
94332c9
·
verified ·
1 Parent(s): b9985cf

Upload 16 files

Browse files
README.md CHANGED
@@ -1,8 +1,8 @@
1
  ---
2
- title: Tata Car Identifier (Model & Color)
3
- emoji: 🚘
4
- colorFrom: blue
5
- colorTo: green
6
  sdk: gradio
7
  sdk_version: "4.36.1"
8
  app_file: app.py
@@ -11,44 +11,46 @@ license: mit
11
  tags:
12
  - automotive
13
  - computer-vision
 
14
  - gradio
15
- - tata
16
  ---
17
 
18
- # Tata Car Identifier
19
 
20
- An image recognition tool tailored for **Tata** cars that identifies **model**, **color**, and **autofills** extra details (year ranges, engine sizes, features) from a **single uploaded image**.
 
 
 
 
 
21
 
22
- ## Quickstart
 
 
23
  ```bash
24
  pip install -r requirements.txt
25
  python app.py
26
  ```
27
 
28
- Open the local Gradio URL and upload a Tata car photo.
29
-
30
- ## How it works
31
- - **Model ID**: Zero-shot CLIP baseline over common Tata models (Nexon, Altroz, Tiago, Punch, Harrier, Safari, Tigor, etc.). Optional fine-tuning script included.
32
- - **Color**: Dominant body color via KMeans in LAB space with named-color snapping.
33
- - **Autofill**: Specs pulled from `data/tata_specs.yaml` using the predicted model.
34
-
35
- ## Train on your dataset
36
- - Put images under `data/your_dataset/images/` and labels in `data/your_dataset/annotations.csv`:
37
  ```csv
38
  image_path,label
39
- images/img_001.jpg,Tata Nexon
40
  ```
41
- - Run:
42
  ```bash
43
  python training/train_classifier.py --data_root data/your_dataset --annotations data/your_dataset/annotations.csv --out_dir checkpoints/vision
44
  ```
45
 
46
- ## FAQ
47
- **Q: Do I need to train first?**
48
- A: No. The app ships with a **CLIP zero-shot** baseline that works out-of-the-box. Training improves accuracy.
49
 
50
- **Q: Which models are supported?**
51
- A: See `tata_id/kb.py` (MODEL_LIST). You can add more models and update `data/tata_specs.yaml`.
52
 
53
- **Q: Can it guess year of manufacture?**
54
- A: We return a **likely year range** per generation. Exact year typically requires VIN/registration lookup.
 
 
1
  ---
2
+ title: Car Analysis Advisor (Multi‑Image, Deterministic)
3
+ emoji: 🛠️
4
+ colorFrom: purple
5
+ colorTo: indigo
6
  sdk: gradio
7
  sdk_version: "4.36.1"
8
  app_file: app.py
 
11
  tags:
12
  - automotive
13
  - computer-vision
14
+ - service-advisor
15
  - gradio
 
16
  ---
17
 
18
+ # Car Analysis Advisor (Hugging Face Space)
19
 
20
+ An end‑to‑end **car analysis AI** that ingests **multiple images** of a vehicle and outputs **precise, actionable recommendations**:
21
+ - **Model** (zero‑shot CLIP baseline; optional fine‑tune)
22
+ - **Color** (dominant body color with named snapping)
23
+ - **Issues** (mechanical/aesthetic) → **deterministic** final decisions
24
+ - **Exact price estimate** (parts + labor + region multipliers)
25
+ - **PDF & JSON** export
26
 
27
+ No probability scores are exposed—only clear decisions & totals.
28
+
29
+ ## Run locally
30
  ```bash
31
  pip install -r requirements.txt
32
  python app.py
33
  ```
34
 
35
+ ## Train (optional, improves accuracy)
36
+ See `training/train_classifier.py` for a ViT fine‑tuning script.
37
+ Dataset CSV:
 
 
 
 
 
 
38
  ```csv
39
  image_path,label
40
+ images/nexon_001.jpg,Tata Nexon
41
  ```
42
+ Then:
43
  ```bash
44
  python training/train_classifier.py --data_root data/your_dataset --annotations data/your_dataset/annotations.csv --out_dir checkpoints/vision
45
  ```
46
 
47
+ ## Pricing configuration
48
+ - `configs/parts_catalog.yaml` parts & standard labor hours per issue
49
+ - `configs/regions.yaml` labor rates & part multipliers per region (example values provided)
50
 
51
+ ## Deterministic decisions
52
+ Internally, scores are computed but **final outputs are rule‑based** (top evidence & thresholds) to provide **unambiguous** issue lists and **exact** costs.
53
 
54
+ ## Exports
55
+ - `exports/report.pdf`
56
+ - `exports/report.json`
app.py CHANGED
@@ -1,43 +1,120 @@
1
- import os, json
2
- from typing import Any, Dict
3
  import gradio as gr
4
  from PIL import Image
5
 
6
- from tata_id.model import TataModelIdentifier
7
- from tata_id.color import detect_color
8
- from tata_id.autofill import load_specs, autofill_details
 
 
 
9
 
10
- clf = TataModelIdentifier()
11
- SPECS = load_specs()
 
12
 
13
- def analyze(image: Image.Image) -> Dict[str, Any]:
14
- if image is None:
15
- raise gr.Error("Please upload an image of a Tata car.")
16
- # Model identification (top3)
17
- top3 = clf.predict_topk(image, k=3)
18
- model_top1 = top3[0][0]
 
 
 
19
 
20
- # Color detection
21
- color = detect_color(image)
 
 
 
 
 
 
 
 
 
22
 
23
- # Autofill
24
- details = autofill_details(model_top1, SPECS)
 
 
 
 
25
 
26
- return {
27
- "predictions": [{"model": m, "probability": round(float(p), 4)} for m,p in top3],
28
- "color": color,
29
- "autofill": details,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  }
 
31
 
32
- with gr.Blocks(fill_height=True, theme=gr.themes.Base()) as demo:
33
- gr.Markdown("## 🚘 Tata Car IdentifierModel, Color, and Specs (Single Image)")
34
  with gr.Row():
35
  with gr.Column(scale=1):
36
- img = gr.Image(type="pil", label="Upload a Tata car image")
 
37
  run = gr.Button("Analyze", variant="primary")
38
  with gr.Column(scale=1):
39
- out = gr.JSON(label="Results")
40
- run.click(analyze, inputs=[img], outputs=[out])
 
 
41
 
42
  if __name__ == "__main__":
43
  demo.launch()
 
1
+ import os, io, base64
2
+ from typing import List, Dict, Any
3
  import gradio as gr
4
  from PIL import Image
5
 
6
+ from car_core.specs_model_space import build_label_space
7
+ from car_core.models_zeroshot import ModelIDZeroShot
8
+ from car_core.color_detect import dominant_color
9
+ from car_core.issues import detect_issues
10
+ from car_core.pricing import price_issues, load_regions
11
+ from car_core.exporter import export_pdf, export_json
12
 
13
+ LABEL_SPACE = build_label_space()
14
+ MODEL = ModelIDZeroShot(LABEL_SPACE)
15
+ REGIONS = load_regions()
16
 
17
+ def _to_pil(obj):
18
+ if isinstance(obj, dict) and "image" in obj:
19
+ # Gradio File with base64 'image' key
20
+ return Image.open(io.BytesIO(base64.b64decode(obj["image"].split(",")[-1])))
21
+ if isinstance(obj, str):
22
+ return Image.open(obj)
23
+ if hasattr(obj, "read"):
24
+ return Image.open(obj)
25
+ return obj
26
 
27
+ def analyze(images: list, region: str):
28
+ if not images:
29
+ raise gr.Error("Upload at least one car image.")
30
+ imgs = []
31
+ for it in images:
32
+ try:
33
+ imgs.append(_to_pil(it))
34
+ except Exception:
35
+ pass
36
+ if not imgs:
37
+ raise gr.Error("Failed to decode images. Use JPG/PNG.")
38
 
39
+ # Model decision: pick the most frequent top label across images
40
+ votes = {}
41
+ for im in imgs:
42
+ lbl = MODEL.top_label(im)
43
+ votes[lbl] = votes.get(lbl, 0) + 1
44
+ model_final = sorted(votes.items(), key=lambda kv: kv[1], reverse=True)[0][0]
45
 
46
+ # Color decision: take the most frequent named color across images
47
+ color_votes = {}
48
+ for im in imgs:
49
+ c = dominant_color(im)["name"]
50
+ color_votes[c] = color_votes.get(c, 0) + 1
51
+ color_name = sorted(color_votes.items(), key=lambda kv: kv[1], reverse=True)[0][0]
52
+ color_any = None
53
+ # Recompute once to get rgb/hex for the chosen name
54
+ for im in imgs:
55
+ d = dominant_color(im)
56
+ if d["name"] == color_name:
57
+ color_any = d; break
58
+ color_final = color_any or {"name": color_name, "rgb": (0,0,0), "hex": "#000000"}
59
+
60
+ # Issues (deterministic)
61
+ issues = detect_issues(imgs)
62
+
63
+ # Pricing
64
+ pricing = price_issues(issues, region_code=region)
65
+
66
+ payload = {
67
+ "vehicle": {"model": model_final, "color": color_final},
68
+ "region": region,
69
+ "issues": issues,
70
+ "pricing": pricing
71
+ }
72
+
73
+ os.makedirs("exports", exist_ok=True)
74
+ pdf_path = "exports/report.pdf"
75
+ json_path = "exports/report.json"
76
+ export_pdf(payload, pdf_path)
77
+ export_json(payload, json_path)
78
+
79
+ def to_dl(path):
80
+ with open(path, "rb") as f:
81
+ return (os.path.basename(path), f.read())
82
+
83
+ # Deterministic, actionable output only
84
+ result = {
85
+ "vehicle": payload["vehicle"],
86
+ "region": pricing["region"],
87
+ "currency": pricing["currency"],
88
+ "issues_with_solutions": [
89
+ {
90
+ "issue": it["issue"],
91
+ "solution": it["solution"],
92
+ "labor_hours": it["labor_hours"],
93
+ "labor_cost": it["labor_cost"],
94
+ "parts_cost": it["parts_cost"],
95
+ "line_total": it["line_total"]
96
+ } for it in pricing["items"]
97
+ ],
98
+ "totals": {
99
+ "subtotal": pricing["subtotal"],
100
+ "tax": pricing["tax"],
101
+ "grand_total": pricing["grand_total"]
102
+ }
103
  }
104
+ return result, to_dl(pdf_path), to_dl(json_path)
105
 
106
+ with gr.Blocks(fill_height=True) as demo:
107
+ gr.Markdown("## 🛠️ Car Analysis Advisor multi‑image model/color/issue detection with exact pricing")
108
  with gr.Row():
109
  with gr.Column(scale=1):
110
+ imgs = gr.File(label="Upload car image(s)", file_count="multiple", file_types=["image"])
111
+ region = gr.Dropdown(choices=list(REGIONS["regions"].keys()), value=REGIONS.get("default_region","IN-HYD"), label="Region (pricing)")
112
  run = gr.Button("Analyze", variant="primary")
113
  with gr.Column(scale=1):
114
+ out = gr.JSON(label="Actionable results")
115
+ pdf = gr.File(label="Download PDF")
116
+ jj = gr.File(label="Download JSON")
117
+ run.click(analyze, inputs=[imgs, region], outputs=[out, pdf, jj])
118
 
119
  if __name__ == "__main__":
120
  demo.launch()
car_core/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ __version__ = '0.1.0'
car_core/color_detect.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Tuple
2
+ import numpy as np
3
+ from PIL import Image
4
+ from sklearn.cluster import KMeans
5
+
6
+ PALETTE = {
7
+ "White": (255,255,255),
8
+ "Black": (0,0,0),
9
+ "Silver": (192,192,192),
10
+ "Grey": (128,128,128),
11
+ "Red": (200,0,0),
12
+ "Blue": (0,80,180),
13
+ "Dark Blue": (0,40,100),
14
+ "Green": (0,150,0),
15
+ "Dark Green": (0,90,0),
16
+ "Yellow": (240,210,0),
17
+ "Orange": (255,130,0),
18
+ "Brown": (120,70,25),
19
+ "Beige": (210,190,150),
20
+ "Teal": (0,120,120),
21
+ "Purple": (110,0,140),
22
+ }
23
+
24
+ def _nearest(rgb):
25
+ r,g,b = rgb
26
+ best = None; dmin = 1e9
27
+ for name,(R,G,B) in PALETTE.items():
28
+ d = (r-R)**2 + (g-G)**2 + (b-B)**2
29
+ if d < dmin: dmin=d; best=name
30
+ return best
31
+
32
+ def dominant_color(image: Image.Image) -> Dict:
33
+ img = image.convert("RGB").resize((256,256))
34
+ arr = np.array(img).reshape(-1,3).astype("float32")
35
+ mask = (arr.mean(axis=1) > 25) & (arr.mean(axis=1) < 245)
36
+ arr = arr[mask] if mask.sum()>100 else arr
37
+ km = KMeans(n_clusters=4, n_init=4, random_state=42).fit(arr)
38
+ centers = km.cluster_centers_.astype(int)
39
+ labels, counts = np.unique(km.labels_, return_counts=True)
40
+ idx = int(labels[np.argmax(counts)])
41
+ rgb = tuple(map(int, centers[idx]))
42
+ return {"name": _nearest(rgb), "rgb": rgb, "hex": "#%02x%02x%02x" % rgb}
car_core/exporter.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from reportlab.lib.pagesizes import A4
3
+ from reportlab.lib import colors
4
+ from reportlab.lib.styles import getSampleStyleSheet
5
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
6
+
7
+ def export_json(payload, path):
8
+ with open(path, "w", encoding="utf-8") as f:
9
+ json.dump(payload, f, ensure_ascii=False, indent=2)
10
+ return path
11
+
12
+ def export_pdf(payload, path):
13
+ doc = SimpleDocTemplate(path, pagesize=A4)
14
+ styles = getSampleStyleSheet()
15
+ story = []
16
+ story.append(Paragraph("<b>Car Analysis Advisor Report</b>", styles["Title"]))
17
+ story.append(Spacer(1, 8))
18
+ story.append(Paragraph(f"<b>Region:</b> {payload['pricing']['region']} | <b>Currency:</b> {payload['pricing']['currency']}", styles["Normal"]))
19
+ story.append(Spacer(1, 8))
20
+ story.append(Paragraph(f"<b>Model:</b> {payload['vehicle']['model']} | <b>Color:</b> {payload['vehicle']['color']['name']} ({payload['vehicle']['color']['hex']})", styles["Normal"]))
21
+ story.append(Spacer(1, 10))
22
+ story.append(Paragraph("<b>Issues & Solutions</b>", styles["Heading2"]))
23
+ data = [["Issue","Solution","Labor (hrs)","Labor","Parts","Line Total"]]
24
+ for it in payload["pricing"]["items"]:
25
+ parts_cost = f"{payload['pricing']['currency']} {it['parts_cost']:.2f}"
26
+ data.append([it["issue"].replace('_',' '), it["solution"], f"{it['labor_hours']:.2f}",
27
+ f"{payload['pricing']['currency']} {it['labor_cost']:.2f}", parts_cost,
28
+ f"{payload['pricing']['currency']} {it['line_total']:.2f}"])
29
+ table = Table(data, hAlign="LEFT", colWidths=[90,220,70,70,70,80])
30
+ table.setStyle(TableStyle([
31
+ ('BACKGROUND',(0,0),(-1,0),colors.darkblue),
32
+ ('TEXTCOLOR',(0,0),(-1,0),colors.whitesmoke),
33
+ ('GRID',(0,0),(-1,-1),0.25,colors.grey),
34
+ ('ALIGN',(2,1),(-1,-1),'CENTER')
35
+ ]))
36
+ story.append(table)
37
+ story.append(Spacer(1, 8))
38
+ story.append(Paragraph(f"<b>Subtotal:</b> {payload['pricing']['currency']} {payload['pricing']['subtotal']:.2f}", styles["Normal"]))
39
+ story.append(Paragraph(f"<b>Tax:</b> {payload['pricing']['currency']} {payload['pricing']['tax']:.2f}", styles["Normal"]))
40
+ story.append(Paragraph(f"<b>Grand Total:</b> {payload['pricing']['currency']} {payload['pricing']['grand_total']:.2f}", styles["Heading3"]))
41
+ doc.build(story)
42
+ return path
car_core/issues.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, List
2
+ import numpy as np
3
+ from PIL import Image, ImageFilter
4
+
5
+ ISSUE_LIST = [
6
+ "scratch_dent","paint_damage","cracked_windshield","flat_tire","engine_leak",
7
+ "brake_wear","headlight_fault","battery_corrosion","rust","bumper_damage"
8
+ ]
9
+
10
+ def _edge_contrast(im: Image.Image) -> float:
11
+ g = im.convert("L").resize((256,256))
12
+ arr = np.array(g, dtype=np.float32)/255.0
13
+ return float(arr.std())
14
+
15
+ def _redness(im: Image.Image) -> float:
16
+ rgb = im.convert("RGB").resize((256,256))
17
+ arr = np.array(rgb, dtype=np.float32)/255.0
18
+ return float(arr[:,:,0].mean())
19
+
20
+ def _darkness(im: Image.Image) -> float:
21
+ g = im.convert("L").resize((256,256))
22
+ arr = np.array(g, dtype=np.float32)/255.0
23
+ return float(1.0 - arr.mean())
24
+
25
+ def detect_issues(images: List[Image.Image]) -> List[str]:
26
+ """Deterministic heuristics over multiple images; choose final set without probabilities."""
27
+ if not images: return ["diagnostic_inspection"]
28
+ # aggregate signals
29
+ contrast = np.mean([_edge_contrast(i) for i in images])
30
+ red = np.mean([_redness(i) for i in images])
31
+ dark = np.mean([_darkness(i) for i in images])
32
+ # thresholds chosen empirically; tune with dataset
33
+ issues = set()
34
+ if contrast > 0.22:
35
+ issues.update(["scratch_dent","paint_damage","bumper_damage"])
36
+ if red > 0.55:
37
+ issues.add("engine_leak")
38
+ if dark > 0.55:
39
+ issues.add("headlight_fault")
40
+ # Always narrow to a final deterministic list of max 4 using rule priority
41
+ priority = ["engine_leak","cracked_windshield","brake_wear","flat_tire","scratch_dent","paint_damage","bumper_damage","headlight_fault","battery_corrosion","rust"]
42
+ final = []
43
+ for p in priority:
44
+ if p in issues:
45
+ final.append(p)
46
+ if len(final) >= 4:
47
+ break
48
+ if not final:
49
+ final = ["diagnostic_inspection"]
50
+ return final
car_core/models_zeroshot.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Tuple
2
+ import torch
3
+ from PIL import Image
4
+ from transformers import CLIPModel, CLIPProcessor
5
+
6
+ class ModelIDZeroShot:
7
+ def __init__(self, label_space: List[str]):
8
+ self.labels = label_space
9
+ device = "cuda" if torch.cuda.is_available() else "cpu"
10
+ self.device = device
11
+ self.model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32").to(device)
12
+ self.processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
13
+
14
+ @torch.no_grad()
15
+ def top_label(self, image: Image.Image) -> str:
16
+ prompts = [f"A photo of a {x}" for x in self.labels]
17
+ inp = self.processor(text=prompts, images=image.convert("RGB"), return_tensors="pt", padding=True).to(self.device)
18
+ out = self.model(**inp)
19
+ idx = int(out.logits_per_image[0].argmax().item())
20
+ return self.labels[idx]
car_core/pricing.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, List
2
+ import yaml
3
+
4
+ def load_catalog(path="configs/parts_catalog.yaml"):
5
+ with open(path,"r",encoding="utf-8") as f:
6
+ return yaml.safe_load(f)
7
+
8
+ def load_regions(path="configs/regions.yaml"):
9
+ with open(path,"r",encoding="utf-8") as f:
10
+ return yaml.safe_load(f)
11
+
12
+ def price_issues(issues: List[str], region_code: str="IN-HYD", catalog=None, regions=None) -> Dict:
13
+ catalog = catalog or load_catalog()
14
+ regions = regions or load_regions()
15
+ parts = catalog.get("issues", {})
16
+ tax_rate = float(catalog.get("tax_rate", 0.18))
17
+ region = regions["regions"][region_code]
18
+ currency = region["currency"]
19
+ labor_rate = float(region["labor_rate_per_hour"])
20
+ p_mult = float(region["parts_multiplier"])
21
+ items = []
22
+ subtotal = 0.0
23
+ for label in issues:
24
+ if label == "diagnostic_inspection":
25
+ items.append({
26
+ "issue": label,
27
+ "solution": "Perform comprehensive inspection to localize faults.",
28
+ "labor_hours": 0.5,
29
+ "labor_cost": round(0.5*labor_rate,2),
30
+ "parts": [],
31
+ "parts_cost": 0.0,
32
+ "line_total": round(0.5*labor_rate,2),
33
+ "currency": currency
34
+ })
35
+ subtotal += 0.5*labor_rate
36
+ continue
37
+ spec = parts.get(label, {"parts": [], "labor_hours": 1.0})
38
+ hours = float(spec.get("labor_hours",1.0))
39
+ parts_cost = sum([float(p.get("cost",0.0))*p_mult for p in spec.get("parts",[])])
40
+ labor_cost = hours * labor_rate
41
+ line = labor_cost + parts_cost
42
+ solution = DEFAULT_SOLUTIONS.get(label, "Repair as per standard service procedure.")
43
+ items.append({
44
+ "issue": label,
45
+ "solution": solution,
46
+ "labor_hours": hours,
47
+ "labor_cost": round(labor_cost,2),
48
+ "parts": spec.get("parts",[]),
49
+ "parts_cost": round(parts_cost,2),
50
+ "line_total": round(line,2),
51
+ "currency": currency
52
+ })
53
+ subtotal += line
54
+ tax = round(tax_rate * subtotal,2)
55
+ grand = round(subtotal + tax,2)
56
+ return {"region": region_code, "currency": currency, "items": items, "subtotal": round(subtotal,2), "tax": tax, "grand_total": grand}
57
+
58
+ DEFAULT_SOLUTIONS = {
59
+ "scratch_dent": "Panel dent repair and surface leveling; finish with primer and paint blend.",
60
+ "paint_damage": "Prep, prime, color-match repaint, and clear coat application.",
61
+ "cracked_windshield": "Replace windshield and seal; calibrate sensors if equipped.",
62
+ "flat_tire": "Replace tire; balance and perform wheel alignment check.",
63
+ "engine_leak": "Identify leak source; replace gaskets/seals; top up fluids and clean bay.",
64
+ "brake_wear": "Replace brake pads; inspect rotors and bleed lines if required.",
65
+ "headlight_fault": "Replace bulb/assembly; verify wiring and aim beam.",
66
+ "battery_corrosion": "Clean terminals; replace corroded connectors; apply protective grease.",
67
+ "rust": "Sand, treat with rust converter; prime and protect underbody.",
68
+ "bumper_damage": "Replace or plastic-weld bumper; refit and paint as needed.",
69
+ "diagnostic_inspection": "Full diagnostic scan and physical inspection to isolate faults."
70
+ }
car_core/specs_model_space.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import yaml
2
+
3
+ def build_label_space(models_yaml="configs/models.yaml"):
4
+ cfg = yaml.safe_load(open(models_yaml, "r", encoding="utf-8"))
5
+ space = []
6
+ for make, models in cfg.get("makes_models", {}).items():
7
+ for m in models:
8
+ space.append(f"{make} {m}")
9
+ return space
configs/models.yaml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ makes_models:
2
+ Tata: ["Tiago","Tigor","Altroz","Punch","Nexon","Harrier","Safari"]
3
+ Maruti: ["Swift","Baleno","Brezza","Dzire","Alto"]
4
+ Hyundai: ["i20","i10","Creta","Venue","Verna"]
5
+ Toyota: ["Corolla","Glanza","Urban Cruiser","Innova","Fortuner"]
configs/parts_catalog.yaml ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ issues:
2
+ scratch_dent:
3
+ parts: [{name: "Body filler & consumables", cost: 1800}]
4
+ labor_hours: 1.5
5
+ paint_damage:
6
+ parts: [{name: "Paint & materials", cost: 2500}]
7
+ labor_hours: 2.0
8
+ cracked_windshield:
9
+ parts: [{name: "Windshield glass", cost: 8000}, {name: "Sealant kit", cost: 900}]
10
+ labor_hours: 2.5
11
+ flat_tire:
12
+ parts: [{name: "New tire", cost: 4500}]
13
+ labor_hours: 0.6
14
+ engine_leak:
15
+ parts: [{name: "Gasket/seal kit", cost: 3200}, {name: "Engine oil", cost: 1800}]
16
+ labor_hours: 3.0
17
+ brake_wear:
18
+ parts: [{name: "Brake pads (pair)", cost: 3500}]
19
+ labor_hours: 1.4
20
+ headlight_fault:
21
+ parts: [{name: "Headlight bulb/assembly", cost: 2200}]
22
+ labor_hours: 0.8
23
+ battery_corrosion:
24
+ parts: [{name: "Battery terminals/cleaner", cost: 600}]
25
+ labor_hours: 0.5
26
+ rust:
27
+ parts: [{name: "Rust converter & primer", cost: 1000}]
28
+ labor_hours: 2.0
29
+ bumper_damage:
30
+ parts: [{name: "Bumper cover", cost: 7000}, {name: "Clips/fasteners", cost: 500}]
31
+ labor_hours: 2.2
32
+ tax_rate: 0.18 # 18% example
configs/regions.yaml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ regions:
2
+ IN-HYD:
3
+ currency: "INR"
4
+ labor_rate_per_hour: 1200
5
+ parts_multiplier: 1.00
6
+ IN-MUM:
7
+ currency: "INR"
8
+ labor_rate_per_hour: 1400
9
+ parts_multiplier: 1.05
10
+ IN-DEL:
11
+ currency: "INR"
12
+ labor_rate_per_hour: 1350
13
+ parts_multiplier: 1.02
14
+ US-CA:
15
+ currency: "USD"
16
+ labor_rate_per_hour: 130
17
+ parts_multiplier: 1.30
18
+ default_region: IN-HYD
requirements.txt CHANGED
@@ -8,3 +8,4 @@ numpy>=1.26.4
8
  scikit-learn>=1.5.0
9
  scikit-image>=0.23.2
10
  pyyaml>=6.0.1
 
 
8
  scikit-learn>=1.5.0
9
  scikit-image>=0.23.2
10
  pyyaml>=6.0.1
11
+ reportlab>=4.1.0