diff --git a/backend/app/api/v1/jobs.py b/backend/app/api/v1/jobs.py index f6f85a1..0d2277a 100644 --- a/backend/app/api/v1/jobs.py +++ b/backend/app/api/v1/jobs.py @@ -2,9 +2,12 @@ from datetime import datetime from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Query, status +from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import get_current_user, get_db +from app.models.job import LocaleInstance as LocaleInstanceModel +from app.models.output import OutputRow, ConfidenceTier from app.schemas.common import PaginatedResponse from app.schemas.job import JobCreate, JobListResponse, JobResponse, JobUpdate, LocaleInstanceResponse from app.services.audit_service import AuditService @@ -68,8 +71,33 @@ async def list_jobs( page=page, page_size=page_size, ) + # Batch-load confidence counts for all jobs in one query + job_ids = [job.id for job in jobs] + confidence_map: dict[str, dict] = {} + if job_ids: + conf_query = ( + select( + LocaleInstanceModel.job_id, + OutputRow.confidence_tier, + func.count().label("cnt"), + ) + .join(OutputRow, OutputRow.instance_id == LocaleInstanceModel.id) + .where(LocaleInstanceModel.job_id.in_(job_ids)) + .group_by(LocaleInstanceModel.job_id, OutputRow.confidence_tier) + ) + conf_result = await db.execute(conf_query) + for row in conf_result.all(): + jid = str(row.job_id) + if jid not in confidence_map: + confidence_map[jid] = {"high": 0, "moderate": 0, "low": 0, "total": 0} + tier_val = row.confidence_tier.value if hasattr(row.confidence_tier, "value") else row.confidence_tier + confidence_map[jid][tier_val] = row.cnt + confidence_map[jid]["total"] += row.cnt + items = [] for job in jobs: + jid = str(job.id) + conf = confidence_map.get(jid, {}) item = JobListResponse( id=job.id, client_id=job.client_id, @@ -86,6 +114,10 @@ async def list_jobs( updated_at=job.updated_at, client_name=job.client.name if job.client else None, created_by_name=job.creator.name if job.creator else None, + confidence_high=conf.get("high", 0), + confidence_moderate=conf.get("moderate", 0), + confidence_low=conf.get("low", 0), + total_output_rows=conf.get("total", 0), ) items.append(item) diff --git a/backend/app/schemas/job.py b/backend/app/schemas/job.py index 9c429e1..a4da7e3 100644 --- a/backend/app/schemas/job.py +++ b/backend/app/schemas/job.py @@ -99,5 +99,10 @@ class JobListResponse(BaseModel): # Enrichment fields (populated by API layer) client_name: str | None = None created_by_name: str | None = None + # Confidence breakdown (populated by API layer) + confidence_high: int = 0 + confidence_moderate: int = 0 + confidence_low: int = 0 + total_output_rows: int = 0 model_config = {"from_attributes": True} diff --git a/frontend/src/components/jobs/JobCard.tsx b/frontend/src/components/jobs/JobCard.tsx index f04c630..71bda6f 100644 --- a/frontend/src/components/jobs/JobCard.tsx +++ b/frontend/src/components/jobs/JobCard.tsx @@ -7,7 +7,7 @@ import { Badge } from "@/components/ui/badge"; import type { Job } from "@/lib/types"; import { formatDate } from "@/lib/utils"; import { cn } from "@/lib/utils"; -import { Clock, User, FileText, DollarSign } from "lucide-react"; +import { Clock, User, DollarSign } from "lucide-react"; const statusConfig: Record< string, @@ -90,6 +90,46 @@ export function JobCard({ job }: JobCardProps) { ))} + + {/* Confidence breakdown */} + {(job.total_output_rows ?? 0) > 0 && ( +