from __future__ import annotations from datetime import datetime from typing import Any, Literal from fastapi import APIRouter, HTTPException, Query, Request from pydantic import BaseModel, Field from api.services.business_harness import ( get_platform_demand_history, get_published_demand_package, ) from supply_infra.business_harness.content_feedback import ( ingest_content_performance_fact, ) from supply_infra.business_harness.feedback import process_feedback from supply_infra.business_harness.strategy import ( approve_strategy_proposal, create_strategy_proposal, preview_strategy_proposal, ) router = APIRouter(prefix="/api/business-harness", tags=["business-harness"]) class ProcessFeedbackBody(BaseModel): status: Literal["accepted", "rejected", "needs_evidence"] reason: str = Field(min_length=1, max_length=2000) impact: dict[str, Any] | None = None class ContentPerformanceBody(BaseModel): content_id: str = Field(min_length=1, max_length=128) biz_dt: str = Field(pattern=r"^\d{8}$") source: str = Field(min_length=1, max_length=64) raw_payload: dict[str, Any] = Field(default_factory=dict) observed_at: datetime exposure_count: int | None = Field(default=None, ge=0) sample_size: int | None = Field(default=None, ge=0) content_quality: float | None = Field(default=None, ge=0, le=1) rov: float | None = None vov: float | None = None class StrategyProposalBody(BaseModel): requested_change: dict[str, Any] reason: str = Field(min_length=1, max_length=2000) @router.get("/packages/latest") def latest_package( biz_dt: str | None = Query(default=None, pattern=r"^\d{8}$"), run_id: str | None = None, ) -> dict: result = get_published_demand_package(biz_dt=biz_dt, run_id=run_id) if result is None: raise HTTPException(status_code=404, detail="published demand package not found") return result @router.get("/demands/{platform_demand_id}/history") def demand_history(platform_demand_id: str) -> dict: result = get_platform_demand_history(platform_demand_id) if result is None: raise HTTPException(status_code=404, detail="platform demand not found") return result @router.post("/feedback/{feedback_id}/process") def review_feedback( feedback_id: int, body: ProcessFeedbackBody, request: Request, ) -> dict: try: return process_feedback( feedback_id, status=body.status, processor=str(request.state.current_user["username"]), reason=body.reason, impact=body.impact, ) except LookupError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc except RuntimeError as exc: raise HTTPException(status_code=409, detail=str(exc)) from exc @router.post("/content-performance") def ingest_content_performance(body: ContentPerformanceBody) -> dict: return ingest_content_performance_fact(**body.model_dump()) @router.post("/strategy/proposals") def propose_strategy(body: StrategyProposalBody, request: Request) -> dict: try: return create_strategy_proposal( requested_change=body.requested_change, reason=body.reason, created_by=str(request.state.current_user["username"]), ) except (ValueError, RuntimeError) as exc: raise HTTPException(status_code=409, detail=str(exc)) from exc @router.post("/strategy/proposals/{proposal_id}/preview") def preview_strategy(proposal_id: str) -> dict: try: return preview_strategy_proposal(proposal_id) except LookupError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc except RuntimeError as exc: raise HTTPException(status_code=409, detail=str(exc)) from exc @router.post("/strategy/proposals/{proposal_id}/approve") def approve_strategy(proposal_id: str, request: Request) -> dict: try: return approve_strategy_proposal( proposal_id, approved_by=str(request.state.current_user["username"]), ) except LookupError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc except RuntimeError as exc: raise HTTPException(status_code=409, detail=str(exc)) from exc