| """ |
| Class Routing - YOLO vs VLM |
| ------------------------------ |
| Two separate mappings needed for Agent 2's laptop damage detection: |
| |
| 1. TAXONOMY_MAP: every one of the 18 fine-grained Roboflow/YOLO classes maps |
| to one of the shared 9-category taxonomy used throughout Module 2 |
| (screen_display, structural_body, port_connector, missing_component, |
| liquid_moisture, input_control, power_boot_failure, cosmetic_wear, |
| other_unclassified). Needed regardless of the routing decision below, |
| since Agent 4/blockchain/mobile all expect the shared taxonomy, not |
| Roboflow's granular labels. |
| |
| 2. YOLO_TRAINED_CLASSES / VLM_FALLBACK_CLASSES: which of the 18 classes the |
| trained YOLO model is actually responsible for (had enough real source |
| data - >=100 instances) vs which get routed to the VLM classifier |
| instead (too little data to trust a trained detector on, per real counts |
| found by inspection). This does NOT mean those damage types are lost - |
| it means Agent 2 asks the VLM about them instead of the trained model, |
| the same way printer/projector already work VLM-only. |
| """ |
|
|
| |
| TAXONOMY_MAP = { |
| "scratch": "cosmetic_wear", |
| "chip": "cosmetic_wear", |
| "adhesive_residue": "cosmetic_wear", |
|
|
| "crack": "structural_body", |
| "broken_other": "structural_body", |
| "broken_lock": "structural_body", |
| "disassembled": "structural_body", |
| "depression": "structural_body", |
|
|
| "damaged_screen": "screen_display", |
| "dead_pixel": "screen_display", |
| "display_lines": "screen_display", |
| "display_spot": "screen_display", |
| "display_fade": "screen_display", |
|
|
| "missing_button": "missing_component", |
| "missing_screw": "missing_component", |
|
|
| "keyboard_issue": "input_control", |
| "broken_button": "input_control", |
|
|
| "normal": None, |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| YOLO_TRAINED_CLASSES = { |
| "broken_other", "chip", "missing_button", "keyboard_issue", |
| "missing_screw", "dead_pixel", "damaged_screen", "crack", "display_lines", |
| "normal", |
| } |
|
|
| VLM_FALLBACK_CLASSES = { |
| "scratch", "broken_lock", "depression", "disassembled", "display_spot", |
| "adhesive_residue", "display_fade", "broken_button", |
| } |
|
|
|
|
| def get_shared_category(fine_grained_class: str) -> str: |
| """Map a fine-grained YOLO class name to the shared 9-category taxonomy.""" |
| return TAXONOMY_MAP.get(fine_grained_class, "other_unclassified") |
|
|
|
|
| def yolo_can_handle(fine_grained_class: str) -> bool: |
| """True if the trained YOLO model has enough real data to be trusted on this class.""" |
| return fine_grained_class in YOLO_TRAINED_CLASSES |
|
|
|
|
| def route_detection(yolo_detections: list, confidence_threshold: float = 0.4) -> dict: |
| """ |
| Given YOLO's raw detections for one crop (list of {"class_name": str, |
| "confidence": float}), decide whether to trust YOLO's answer or fall |
| back to the VLM classifier. |
| |
| Trusts YOLO only if its TOP detection is (a) a class it has enough real |
| data for, and (b) above the confidence threshold. Otherwise signals a |
| VLM fallback - covering both "YOLO found nothing confident" and |
| "YOLO's best guess was one of the data-insufficient classes." |
| """ |
| if not yolo_detections: |
| return {"use_yolo": False, "reason": "no_detections", "fine_grained_class": None} |
|
|
| top = max(yolo_detections, key=lambda d: d["confidence"]) |
|
|
| if top["confidence"] < confidence_threshold: |
| return {"use_yolo": False, "reason": "low_confidence", "fine_grained_class": top["class_name"]} |
|
|
| if not yolo_can_handle(top["class_name"]): |
| return { |
| "use_yolo": False, |
| "reason": "class_needs_more_training_data", |
| "fine_grained_class": top["class_name"], |
| } |
|
|
| return { |
| "use_yolo": True, |
| "reason": "trained_class_confident", |
| "fine_grained_class": top["class_name"], |
| "shared_category": get_shared_category(top["class_name"]), |
| "confidence": top["confidence"], |
| } |
|
|