from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from pydantic import BaseModel from app.models import get_db from app.models.dit import DitRecord from app.models.sample import SampleRecord from app.models.order import OrderRecord from app.models.match import MatchResult, MatchStatus, TargetType from app.services.fuzzy_matcher import FuzzyMatcher router = APIRouter(prefix="/match", tags=["Matching"]) class MatchRunResponse(BaseModel): match_count: int auto_matched: int pending_review: int class DitInfo(BaseModel): id: int op_id: str customer: str pn: str eau: int stage: Optional[str] class Config: from_attributes = True class TargetInfo(BaseModel): id: int customer: str pn: str order_no: Optional[str] qty: Optional[int] class MatchResultResponse(BaseModel): id: int dit_id: int target_type: str target_id: int score: float reason: str status: str dit: Optional[DitInfo] target: Optional[TargetInfo] class Config: from_attributes = True class ReviewRequest(BaseModel): action: str # 'accept' or 'reject' @router.post("/run", response_model=MatchRunResponse) def run_matching(db: Session = Depends(get_db)): """執行模糊比對""" matcher = FuzzyMatcher(db) result = matcher.run_matching() return MatchRunResponse(**result) @router.get("/results", response_model=List[MatchResultResponse]) def get_results(db: Session = Depends(get_db)): """取得所有比對結果""" matches = db.query(MatchResult).all() results = [] for match in matches: # 取得 DIT 資訊 dit = db.query(DitRecord).filter(DitRecord.id == match.dit_id).first() dit_info = DitInfo( id=dit.id, op_id=dit.op_id, customer=dit.customer, pn=dit.pn, eau=dit.eau, stage=dit.stage ) if dit else None # 取得目標資訊 target_info = None if match.target_type == TargetType.SAMPLE: sample = db.query(SampleRecord).filter(SampleRecord.id == match.target_id).first() if sample: target_info = TargetInfo( id=sample.id, customer=sample.customer, pn=sample.pn, order_no=sample.order_no, qty=sample.qty ) elif match.target_type == TargetType.ORDER: order = db.query(OrderRecord).filter(OrderRecord.id == match.target_id).first() if order: target_info = TargetInfo( id=order.id, customer=order.customer, pn=order.pn, order_no=order.order_no, qty=order.qty ) results.append(MatchResultResponse( id=match.id, dit_id=match.dit_id, target_type=match.target_type.value, target_id=match.target_id, score=match.score, reason=match.reason, status=match.status.value, dit=dit_info, target=target_info )) return results @router.put("/{match_id}/review", response_model=MatchResultResponse) def review_match(match_id: int, request: ReviewRequest, db: Session = Depends(get_db)): """審核比對結果""" if request.action not in ['accept', 'reject']: raise HTTPException(status_code=400, detail="Invalid action") matcher = FuzzyMatcher(db) match = matcher.review_match(match_id, request.action) if not match: raise HTTPException(status_code=404, detail="Match not found") # 取得相關資訊 dit = db.query(DitRecord).filter(DitRecord.id == match.dit_id).first() dit_info = DitInfo( id=dit.id, op_id=dit.op_id, customer=dit.customer, pn=dit.pn, eau=dit.eau, stage=dit.stage ) if dit else None target_info = None if match.target_type == TargetType.SAMPLE: sample = db.query(SampleRecord).filter(SampleRecord.id == match.target_id).first() if sample: target_info = TargetInfo( id=sample.id, customer=sample.customer, pn=sample.pn, order_no=sample.order_no, qty=sample.qty ) elif match.target_type == TargetType.ORDER: order = db.query(OrderRecord).filter(OrderRecord.id == match.target_id).first() if order: target_info = TargetInfo( id=order.id, customer=order.customer, pn=order.pn, order_no=order.order_no, qty=order.qty ) return MatchResultResponse( id=match.id, dit_id=match.dit_id, target_type=match.target_type.value, target_id=match.target_id, score=match.score, reason=match.reason, status=match.status.value, dit=dit_info, target=target_info )