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:
jigoong
2026-06-04 18:22:14 +07:00
parent ee473aca8f
commit e4d32b86cb
3 changed files with 66 additions and 4 deletions

View File

@@ -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,
},
}

View File

@@ -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

View File

@@ -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"}