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.dialects.postgresql import insert
|
||||||
from sqlalchemy.orm import Session
|
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.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.security.dependencies import get_db, require_permission
|
||||||
from app.utils.supabase_client import SupabaseAPIError, upsert_to_supabase_sync
|
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_CHECKPOINT_WRITE = "feed.checkpoint:write"
|
||||||
PERM_FEED_OLD_CHECKPOINT_WRITE = "feed.old-checkpoint:write"
|
PERM_FEED_OLD_CHECKPOINT_WRITE = "feed.old-checkpoint:write"
|
||||||
PERM_FEED_PATIENT_APPOINTMENT_WRITE = "feed.patient-appointment:write"
|
PERM_FEED_PATIENT_APPOINTMENT_WRITE = "feed.patient-appointment:write"
|
||||||
|
PERM_VOC_DATA_WRITE = "voc.data:write"
|
||||||
|
|
||||||
|
|
||||||
def _to_tz(dt):
|
def _to_tz(dt):
|
||||||
@@ -304,3 +305,41 @@ def upsert_patient_appointment(
|
|||||||
"error": supabase_error,
|
"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
|
doctor_code: str | None = None
|
||||||
period: str | None = None
|
period: str | None = None
|
||||||
appointment_type: 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 __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.dialects.postgresql import JSONB
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
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())
|
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):
|
class ApiClient(Base):
|
||||||
__tablename__ = "api_client"
|
__tablename__ = "api_client"
|
||||||
__table_args__ = {"schema": "fastapi"}
|
__table_args__ = {"schema": "fastapi"}
|
||||||
|
|||||||
Reference in New Issue
Block a user