"""Independent sample API for synthetic work orders and monitoring data.""" from typing import get_args from uuid import UUID from fastapi import Depends, FastAPI, Query, Request from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from .auth import Identity, identity from .config import SampleSettings from .errors import SampleFailure from .store import TRANSITIONS from .schemas import ( AlertPage, Context, CreateTestDataRecord, CreateWorkOrder, DeleteResource, DeletedResource, DemoDataset, Observations, PointPage, Receipt, SampleError, Scenario, ScenarioCommand, ScenarioState, TestDataRecord, TestDataRecordPage, UpdateTestDataRecord, UpdateWorkOrder, WorkOrder, WorkOrderPage, ) def store(request: Request): from .store import PostgresStore return PostgresStore(request.app.state.settings) def create_app(settings: SampleSettings | None = None) -> FastAPI: app = FastAPI(title="TJWater 外围系统模拟接入", version="1.0.0", description="测试监测与核查反馈样板,不是正式工单平台或设备控制服务。") app.state.settings = settings or SampleSettings() app.add_middleware(CORSMiddleware, allow_origins=app.state.settings.cors_origins, allow_credentials=False, allow_methods=["GET", "POST", "PATCH", "DELETE"], allow_headers=["Authorization", "Content-Type", "X-Project-Id"]) @app.exception_handler(SampleFailure) async def failure(request, exc): return JSONResponse(status_code=exc.status, content=exc.payload()) @app.exception_handler(RequestValidationError) async def invalid(request, exc): return JSONResponse(status_code=422, content=SampleFailure("SCHEMA_INVALID", "请求字段不符合样板契约").payload()) @app.get("/health") def health(): return {"service": "peripheral-sample", "origin": "synthetic", "storage": "postgresql", "status": "up", "storage_verified": False} prefix = "/api/sample/v1" errors = {code: {"model": SampleError} for code in (401, 403, 404, 409, 422, 502, 503)} @app.get(prefix + "/context", response_model=Context, responses=errors) def context(request: Request, who: Identity = Depends(identity), db=Depends(store)): return {"protocol_version": "tjwater-peripheral-sample@1", "origin": "synthetic", "project_id": who.project_id, "user_id": who.user_id, "can_write": who.can_write, "scenarios": list(get_args(Scenario)) if request.app.state.settings.enable_faults else ["normal"], "state_transitions": TRANSITIONS, "managed_resources": db.managed_resource_capabilities()} @app.get(prefix + "/scenario", response_model=ScenarioState, responses=errors) def scenario(who: Identity = Depends(identity), db=Depends(store)): return db.scenario(who) @app.post(prefix + "/scenario", response_model=ScenarioState, responses=errors) def configure_scenario(command: ScenarioCommand, who: Identity = Depends(identity), db=Depends(store)): return db.set_scenario(who, command) @app.get(prefix + "/requests/{request_id}", response_model=Receipt, responses=errors) def receipt(request_id: UUID, who: Identity = Depends(identity), db=Depends(store)): return db.receipt(who, request_id) @app.get(prefix + "/work-orders", response_model=WorkOrderPage, responses=errors) def work_orders(limit: int = Query(20, ge=1, le=100), cursor: UUID | None = None, who: Identity = Depends(identity), db=Depends(store)): return db.list_work_orders(who, limit, cursor) @app.post(prefix + "/work-orders", response_model=WorkOrder, status_code=201, responses=errors) def create_work_order(command: CreateWorkOrder, who: Identity = Depends(identity), db=Depends(store)): return db.create_work_order(who, command) @app.get(prefix + "/work-orders/{work_order_id}", response_model=WorkOrder, responses=errors) def work_order(work_order_id: UUID, who: Identity = Depends(identity), db=Depends(store)): return db.get_work_order(who, work_order_id) @app.patch(prefix + "/work-orders/{work_order_id}", response_model=WorkOrder, responses=errors) def update_work_order(work_order_id: UUID, command: UpdateWorkOrder, who: Identity = Depends(identity), db=Depends(store)): return db.update_work_order(who, work_order_id, command) @app.delete(prefix + "/work-orders/{work_order_id}", response_model=DeletedResource, responses=errors) def delete_work_order(work_order_id: UUID, command: DeleteResource, who: Identity = Depends(identity), db=Depends(store)): return db.delete_work_order(who, work_order_id, command) @app.get(prefix + "/test-data", response_model=TestDataRecordPage, responses=errors) def test_data(limit: int = Query(20, ge=1, le=100), cursor: UUID | None = None, who: Identity = Depends(identity), db=Depends(store)): return db.list_test_data(who, limit, cursor) @app.post(prefix + "/test-data", response_model=TestDataRecord, status_code=201, responses=errors) def create_test_data(command: CreateTestDataRecord, who: Identity = Depends(identity), db=Depends(store)): return db.create_test_data(who, command) @app.get(prefix + "/test-data/{record_id}", response_model=TestDataRecord, responses=errors) def test_data_record(record_id: UUID, who: Identity = Depends(identity), db=Depends(store)): return db.get_test_data(who, record_id) @app.patch(prefix + "/test-data/{record_id}", response_model=TestDataRecord, responses=errors) def update_test_data(record_id: UUID, command: UpdateTestDataRecord, who: Identity = Depends(identity), db=Depends(store)): return db.update_test_data(who, record_id, command) @app.delete(prefix + "/test-data/{record_id}", response_model=DeletedResource, responses=errors) def delete_test_data(record_id: UUID, command: DeleteResource, who: Identity = Depends(identity), db=Depends(store)): return db.delete_test_data(who, record_id, command) @app.get(prefix + "/monitoring/points", response_model=PointPage, responses=errors) def points(limit: int = Query(100, ge=1, le=500), cursor: str | None = Query(None, max_length=128), who: Identity = Depends(identity), db=Depends(store)): return db.points(who, limit, cursor) @app.get(prefix + "/monitoring/observations", response_model=Observations, responses=errors) def observations(point_id: str, start: str, end: str, who: Identity = Depends(identity), db=Depends(store)): return db.observations(who, point_id, start, end) @app.get(prefix + "/monitoring/alerts", response_model=AlertPage, responses=errors) def alerts(who: Identity = Depends(identity), db=Depends(store)): return db.alerts(who) @app.get(prefix + "/demo-data", response_model=DemoDataset, responses=errors) def demo_data(who: Identity = Depends(identity), db=Depends(store)): return db.demo_data(who) return app app = create_app()