feat: add VOC data endpoint (POST /api/v1/voc-data)
- Add VocDataIn schema (date, topic, sub_topic, level, depart_id, dep_name) - Add RawVocData SQLAlchemy model (rawdata.raw_voc_data, BIGSERIAL PK) - Add POST /api/v1/voc-data endpoint with voc.data:write permission - Dual-write to local PostgreSQL + Supabase - Table auto-created on startup via Base.metadata.create_all()
This commit is contained in:
@@ -9,9 +9,9 @@ from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.dialects.postgresql import insert
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.v1.schemas import FeedCheckpointIn, FeedWaitingTimeIn, PatientAppointmentIn
|
||||
from app.api.v1.schemas import FeedCheckpointIn, FeedWaitingTimeIn, PatientAppointmentIn, VocDataIn
|
||||
from app.core.config import settings
|
||||
from app.db.models import RawOpdCheckpoint, RawWaitingTime, PatientAppointment
|
||||
from app.db.models import RawOpdCheckpoint, RawWaitingTime, PatientAppointment, RawVocData
|
||||
from app.security.dependencies import get_db, require_permission
|
||||
from app.utils.supabase_client import SupabaseAPIError, upsert_to_supabase_sync
|
||||
|
||||
@@ -22,6 +22,7 @@ router = APIRouter(prefix="/api/v1")
|
||||
PERM_FEED_CHECKPOINT_WRITE = "feed.checkpoint:write"
|
||||
PERM_FEED_OLD_CHECKPOINT_WRITE = "feed.old-checkpoint:write"
|
||||
PERM_FEED_PATIENT_APPOINTMENT_WRITE = "feed.patient-appointment:write"
|
||||
PERM_VOC_DATA_WRITE = "voc.data:write"
|
||||
|
||||
|
||||
def _to_tz(dt):
|
||||
@@ -304,3 +305,41 @@ def upsert_patient_appointment(
|
||||
"error": supabase_error,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@router.post("/voc-data")
|
||||
def insert_voc_data(
|
||||
payload: list[VocDataIn],
|
||||
_: Annotated[object, Depends(require_permission(PERM_VOC_DATA_WRITE))],
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
):
|
||||
rows = [r.model_dump() for r in payload]
|
||||
|
||||
stmt = insert(RawVocData).values(rows)
|
||||
result = db.execute(stmt)
|
||||
db.commit()
|
||||
|
||||
supabase_rows = [{**r, "date": r["date"].isoformat()} for r in rows]
|
||||
supabase_result = None
|
||||
supabase_error = None
|
||||
|
||||
try:
|
||||
logger.info(f"Sending {len(supabase_rows)} VOC records to Supabase API")
|
||||
supabase_result = upsert_to_supabase_sync(table="raw_voc_data", data=supabase_rows)
|
||||
logger.info(f"Successfully sent VOC data to Supabase: {supabase_result.get('status_code')}")
|
||||
except SupabaseAPIError as e:
|
||||
logger.error(f"Failed to send VOC data to Supabase: {str(e)}")
|
||||
supabase_error = str(e)
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error sending VOC data to Supabase: {str(e)}")
|
||||
supabase_error = f"Unexpected error: {str(e)}"
|
||||
|
||||
return {
|
||||
"inserted": len(rows),
|
||||
"rowcount": result.rowcount,
|
||||
"supabase": {
|
||||
"success": supabase_result is not None,
|
||||
"result": supabase_result,
|
||||
"error": supabase_error,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -37,3 +37,12 @@ class PatientAppointmentIn(BaseModel):
|
||||
doctor_code: str | None = None
|
||||
period: str | None = None
|
||||
appointment_type: str | None = None
|
||||
|
||||
|
||||
class VocDataIn(BaseModel):
|
||||
date: date
|
||||
topic: str
|
||||
sub_topic: str
|
||||
level: str
|
||||
depart_id: str
|
||||
dep_name: str | None = None
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import date, datetime
|
||||
|
||||
from sqlalchemy import BigInteger, Boolean, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, func
|
||||
from sqlalchemy import BigInteger, Boolean, Date, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, func
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
@@ -65,6 +65,20 @@ class PatientAppointment(Base):
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
|
||||
|
||||
class RawVocData(Base):
|
||||
__tablename__ = "raw_voc_data"
|
||||
__table_args__ = {"schema": "rawdata"}
|
||||
|
||||
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
|
||||
date: Mapped[date] = mapped_column(Date, nullable=False)
|
||||
topic: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
sub_topic: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
level: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
depart_id: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
dep_name: Mapped[str | None] = mapped_column(String(200), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
|
||||
class ApiClient(Base):
|
||||
__tablename__ = "api_client"
|
||||
__table_args__ = {"schema": "fastapi"}
|
||||
|
||||
Reference in New Issue
Block a user