| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494 |
- from typing import Any
- from fastapi import Depends, FastAPI, Header, HTTPException, Query
- from fastapi.middleware.cors import CORSMiddleware
- from pydantic import BaseModel, Field
- from .auth import create_token, revoke_token, validate_token, verify_credentials
- from .config import settings
- from .db import ensure_annotation_table, ensure_label_table
- from .services.data_service import data_service
- from .services.label_import_service import file_status, job_status, start_import
- from .services.label_service import create_label, delete_label, delete_label_range, query_labels, update_label
- from .services.tag_manual_service import tag_manual_service
- from .services.temperature_transition_service import temperature_transition_service
- class TimePointInput(BaseModel):
- index: int = Field(ge=0)
- sampleTime: str
- files: dict[str, Any]
- siteValues: dict[str, float | None] = Field(default={})
- class WaveWindowInput(BaseModel):
- devicePart: str = Field(min_length=1)
- devicePoints: list[str]
- points: list[TimePointInput]
- maxPoints: int = Field(default=200000, ge=256, le=200000)
- noSampling: bool = False
- firstCycleOnly: bool = False
- sitePoints: list[str] = Field(default=[])
- class AnnotationInput(BaseModel):
- wave_file_id: int = Field(gt=0)
- label: str = Field(min_length=1, max_length=8)
- period_start: int = Field(gt=0)
- period_end: int = Field(gt=0)
- sample_index_start: int = Field(ge=0)
- sample_index_end: int = Field(ge=0)
- class AnnotationQueryInput(BaseModel):
- wave_file_ids: list[int] = Field(default=[])
- class AlarmScanInput(BaseModel):
- forecast_time: str = Field(min_length=1)
- device_code: str = Field(min_length=1)
- alarm_type: str = Field(min_length=1)
- hours: float = Field(gt=0)
- class PressureAngleSyncInput(BaseModel):
- device_part: str = Field(min_length=1)
- device_points: list[str] = Field(default=[])
- class PressureAngleQueryInput(BaseModel):
- device_part: str = Field(min_length=1)
- device_points: list[str] = Field(default=[])
- min_time: str | None = None
- max_time: str | None = None
- angles: list[int] = Field(default=[])
- class LabelInput(BaseModel):
- target_type: str = Field(min_length=1)
- target_id: int | None = None
- channel_key: str = Field(min_length=1)
- unit: str = Field(min_length=1)
- device_code: str | None = None
- device_part: str | None = None
- device_point: str | None = None
- metric_key: str | None = None
- metric_label: str | None = None
- start_time: str = Field(min_length=1)
- end_time: str = Field(min_length=1)
- label_type: str = Field(min_length=1)
- label_subtype: str | None = None
- note: str | None = None
- class LabelRangeInput(BaseModel):
- start_time: str = Field(min_length=1)
- end_time: str = Field(min_length=1)
- class LoginInput(BaseModel):
- username: str = Field(min_length=1)
- password: str = Field(min_length=1)
- app = FastAPI(
- title="压缩机故障预测波形服务",
- version="0.1.0",
- )
- app.add_middleware(
- CORSMiddleware,
- allow_origins=[settings.cors_origin, "http://127.0.0.1:5173"],
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
- )
- def require_auth(authorization: str | None = Header(default=None)) -> str:
- token = (authorization or "").removeprefix("Bearer ").strip()
- if not validate_token(token):
- raise HTTPException(status_code=401, detail="未登录或登录已过期")
- return token
- @app.on_event("startup")
- def startup() -> None:
- try:
- ensure_annotation_table()
- ensure_label_table()
- except Exception:
- # 数据库不可达时跳过建表,演示模式仍可正常启动。
- pass
- @app.post("/api/login")
- def login(payload: LoginInput) -> dict[str, Any]:
- if not verify_credentials(payload.username, payload.password):
- raise HTTPException(status_code=401, detail="用户名或密码错误")
- return {"token": create_token(), "username": payload.username}
- @app.post("/api/logout")
- def logout(authorization: str | None = Header(default=None)) -> dict[str, Any]:
- token = (authorization or "").removeprefix("Bearer ").strip()
- revoke_token(token)
- return {"ok": True}
- @app.get("/api/health")
- def health(_auth: str = Depends(require_auth)) -> dict[str, Any]:
- return data_service.health()
- @app.get("/api/tag-manual/labels")
- def tag_manual_labels() -> dict[str, Any]:
- try:
- return tag_manual_service.labels()
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.get("/api/tag-manual/series")
- def tag_manual_series(key: str = Query(min_length=1)) -> dict[str, Any]:
- try:
- return tag_manual_service.series(key)
- except KeyError as error:
- raise HTTPException(status_code=404, detail="标签不存在") from error
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.get("/api/temperature-transition/labels")
- def temperature_transition_labels() -> dict[str, Any]:
- try:
- return temperature_transition_service.labels()
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.get("/api/temperature-transition/series")
- def temperature_transition_series(key: str = Query(min_length=1)) -> dict[str, Any]:
- try:
- return temperature_transition_service.series(key)
- except KeyError as error:
- raise HTTPException(status_code=404, detail="过渡标签不存在") from error
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.get("/api/query-options")
- def query_options(_auth: str = Depends(require_auth)) -> dict[str, Any]:
- try:
- result = data_service.query_options()
- # Counts are keyed by the full point_name so the UI can scope them to
- # the currently selected device_part.
- result["abnormalCounts"] = data_service.abnormal_counts()
- return result
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.get("/api/time-points")
- def time_points(
- device_part: str = Query(min_length=1),
- device_points: list[str] = Query(default=[]),
- min_time: str | None = None,
- max_time: str | None = None,
- include_stopped: bool = False,
- min_status: int | None = Query(default=None, ge=0),
- status_filter: list[str] = Query(default=[]),
- site_points: list[str] = Query(default=[]),
- _auth: str = Depends(require_auth),
- ) -> dict[str, Any]:
- try:
- return data_service.time_points(
- device_part,
- device_points,
- min_time,
- max_time,
- include_stopped,
- min_status,
- status_filter,
- site_points,
- )
- except ValueError as error:
- raise HTTPException(status_code=400, detail=str(error)) from error
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.get("/api/site-points")
- def site_points(
- device_part: str = Query(min_length=1),
- _auth: str = Depends(require_auth),
- ) -> dict[str, Any]:
- try:
- return data_service.site_points(device_part)
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.get("/api/faults")
- def faults(
- device_part: str = Query(min_length=1),
- min_time: str | None = None,
- max_time: str | None = None,
- _auth: str = Depends(require_auth),
- ) -> dict[str, Any]:
- try:
- return data_service.faults(device_part, min_time, max_time)
- except ValueError as error:
- raise HTTPException(status_code=400, detail=str(error)) from error
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.get("/api/tspluse-ruler")
- def tspluse_ruler(_auth: str = Depends(require_auth)) -> dict[str, Any]:
- try:
- return data_service.tspluse_ruler()
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.get("/api/alarms")
- def list_alarms(
- current_time: str | None = None,
- _auth: str = Depends(require_auth),
- ) -> dict[str, Any]:
- try:
- return data_service.list_alarms(current_time)
- except ValueError as error:
- raise HTTPException(status_code=400, detail=str(error)) from error
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.get("/api/alarm-compressor-options")
- def alarm_compressor_options(_auth: str = Depends(require_auth)) -> dict[str, Any]:
- try:
- return data_service.alarm_compressor_options()
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.post("/api/alarm-scan")
- def alarm_scan(payload: AlarmScanInput, _auth: str = Depends(require_auth)) -> dict[str, Any]:
- try:
- return data_service.scan_alarm(payload.model_dump())
- except ValueError as error:
- raise HTTPException(status_code=400, detail=str(error)) from error
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.post("/api/pressure-angle/sync")
- def pressure_angle_sync(payload: PressureAngleSyncInput, _auth: str = Depends(require_auth)) -> dict[str, Any]:
- try:
- return data_service.sync_pressure_angle(payload.device_part, payload.device_points)
- except ValueError as error:
- raise HTTPException(status_code=400, detail=str(error)) from error
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.post("/api/pressure-angle/query")
- def pressure_angle_query(payload: PressureAngleQueryInput, _auth: str = Depends(require_auth)) -> dict[str, Any]:
- try:
- return data_service.query_pressure_angle(
- payload.device_part,
- payload.device_points,
- payload.min_time,
- payload.max_time,
- payload.angles,
- )
- except ValueError as error:
- raise HTTPException(status_code=400, detail=str(error)) from error
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.post("/api/wave-window")
- def wave_window(payload: WaveWindowInput, _auth: str = Depends(require_auth)) -> dict[str, Any]:
- try:
- return data_service.wave_window(
- payload.devicePart,
- payload.devicePoints,
- [point.model_dump() if hasattr(point, "model_dump") else point.dict() for point in payload.points],
- payload.maxPoints,
- payload.noSampling,
- payload.firstCycleOnly,
- payload.sitePoints,
- )
- except ValueError as error:
- raise HTTPException(status_code=400, detail=str(error)) from error
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.get("/api/wave-files/{wave_file_id}/periods/{period_number}")
- def period_detail(
- wave_file_id: int,
- period_number: int,
- _auth: str = Depends(require_auth),
- ) -> dict[str, Any]:
- try:
- return data_service.period_detail(wave_file_id, period_number)
- except ValueError as error:
- raise HTTPException(status_code=400, detail=str(error)) from error
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.get("/api/annotation-config")
- def annotation_config(_auth: str = Depends(require_auth)) -> dict[str, Any]:
- return data_service.annotation_config()
- @app.get("/api/annotations")
- def list_annotations(
- wave_file_ids: list[int] = Query(default=[]),
- _auth: str = Depends(require_auth),
- ) -> dict[str, Any]:
- try:
- return data_service.list_annotations(wave_file_ids)
- except ValueError as error:
- raise HTTPException(status_code=400, detail=str(error)) from error
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.post("/api/annotations/query")
- def query_annotations(
- payload: AnnotationQueryInput,
- _auth: str = Depends(require_auth),
- ) -> dict[str, Any]:
- try:
- return data_service.list_annotations(payload.wave_file_ids)
- except ValueError as error:
- raise HTTPException(status_code=400, detail=str(error)) from error
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.post("/api/annotations")
- def create_annotation(payload: AnnotationInput, _auth: str = Depends(require_auth)) -> dict[str, Any]:
- try:
- return data_service.create_annotation(payload.model_dump())
- except ValueError as error:
- raise HTTPException(status_code=400, detail=str(error)) from error
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.delete("/api/annotations/{annotation_id}")
- def delete_annotation(annotation_id: int, _auth: str = Depends(require_auth)) -> dict[str, Any]:
- try:
- return data_service.delete_annotation(annotation_id)
- except ValueError as error:
- raise HTTPException(status_code=404, detail=str(error)) from error
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.get("/api/labels")
- def list_labels(
- target_type: str | None = None,
- unit: str | None = None,
- device_code: str | None = None,
- metric_keys: list[str] = Query(default=[]),
- channel_keys: list[str] = Query(default=[]),
- wave_file_ids: list[int] = Query(default=[]),
- start: str | None = None,
- end: str | None = None,
- _auth: str = Depends(require_auth),
- ) -> dict[str, Any]:
- try:
- return query_labels(
- target_type=target_type,
- unit=unit,
- device_code=device_code,
- metric_keys=metric_keys,
- channel_keys=channel_keys,
- wave_file_ids=wave_file_ids,
- start=start,
- end=end,
- )
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.post("/api/labels")
- def create_label_endpoint(payload: LabelInput, _auth: str = Depends(require_auth)) -> dict[str, Any]:
- try:
- return create_label(payload.model_dump())
- except ValueError as error:
- raise HTTPException(status_code=400, detail=str(error)) from error
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.put("/api/labels/{label_id}")
- def update_label_endpoint(
- label_id: int,
- payload: LabelInput,
- _auth: str = Depends(require_auth),
- ) -> dict[str, Any]:
- try:
- return update_label(label_id, payload.model_dump())
- except ValueError as error:
- raise HTTPException(status_code=400, detail=str(error)) from error
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.delete("/api/labels/{label_id}")
- def delete_label_endpoint(label_id: int, _auth: str = Depends(require_auth)) -> dict[str, Any]:
- try:
- return delete_label(label_id)
- except ValueError as error:
- raise HTTPException(status_code=404, detail=str(error)) from error
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.post("/api/labels/{label_id}/delete-range")
- def delete_label_range_endpoint(
- label_id: int,
- payload: LabelRangeInput,
- _auth: str = Depends(require_auth),
- ) -> dict[str, Any]:
- try:
- return delete_label_range(label_id, payload.start_time, payload.end_time)
- except ValueError as error:
- raise HTTPException(status_code=400, detail=str(error)) from error
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.get("/api/label-import/status")
- def label_import_status(_auth: str = Depends(require_auth)) -> dict[str, Any]:
- try:
- return {"files": file_status(), "job": job_status()}
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- @app.post("/api/label-import")
- def label_import_start(_auth: str = Depends(require_auth)) -> dict[str, Any]:
- try:
- start_import()
- except ValueError as error:
- raise HTTPException(status_code=409, detail=str(error)) from error
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
- return {"started": True}
- @app.get("/api/label-import/progress")
- def label_import_progress(_auth: str = Depends(require_auth)) -> dict[str, Any]:
- try:
- return job_status()
- except Exception as error:
- raise HTTPException(status_code=503, detail=str(error)) from error
|