Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""add reason column to events and normalize status (dismissed → false_alarm)

Revision ID: c1a2b3d4e5f6
Revises: 3dfc3e213f69
Create Date: 2026-05-11 16:00:00.000000

- events.reason String NULL 컬럼 추가
- 기존 status='dismissed' 행을 'false_alarm' 으로 백필 (frontend 와 정렬)
"""
from typing import Sequence, Union

import sqlalchemy as sa
from alembic import op


# revision identifiers, used by Alembic.
revision: str = 'c1a2b3d4e5f6'
down_revision: Union[str, None] = '3dfc3e213f69'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
op.add_column('events', sa.Column('reason', sa.String(), nullable=True))
# frontend 가 'false_alarm' 을 기대 → 기존 'dismissed' 행 백필
op.execute("UPDATE events SET status='false_alarm' WHERE status='dismissed'")


def downgrade() -> None:
op.execute("UPDATE events SET status='dismissed' WHERE status='false_alarm'")
op.drop_column('events', 'reason')
8 changes: 5 additions & 3 deletions backend/app/api/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ async def get_event_stats(
today_total = await db.scalar(select(func.count(Event.id)).where(Event.timestamp >= start_of_day))
pending_count = await db.scalar(select(func.count(Event.id)).where(Event.status == 'pending'))
confirmed_count = await db.scalar(select(func.count(Event.id)).where(Event.status == 'confirmed'))
false_alarm_count = await db.scalar(select(func.count(Event.id)).where(Event.status == 'dismissed'))
false_alarm_count = await db.scalar(select(func.count(Event.id)).where(Event.status == 'false_alarm'))

return {
"today_total": today_total or 0,
Expand Down Expand Up @@ -122,7 +122,8 @@ async def create_event(
- AI 추론 엔진(inference.py)에서 호출됩니다.
"""
# 1. DB에 사건 기록 (Persistence)
event = Event(**body.model_dump())
# exclude_none: 비전송 필드는 모델 default 가 적용되도록 (event_type → 'unknown')
event = Event(**body.model_dump(exclude_none=True))
db.add(event)
await db.flush()

Expand Down Expand Up @@ -159,7 +160,8 @@ async def report_false_alarm(
if not event:
raise HTTPException(status_code=404, detail="해당 사건 기록을 찾을 수 없습니다.")

event.status = "dismissed"
event.status = "false_alarm"
event.reason = body.reason
event.handled_by = current_admin.id
event.handled_at = datetime.now()

Expand Down
5 changes: 3 additions & 2 deletions backend/app/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ class Event(Base):
clip_url: Mapped[Optional[str]] = mapped_column(String, nullable=True)
track_id: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
confidence: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
status: Mapped[str] = mapped_column(String, default="pending") # pending | confirmed | dismissed
event_type: Mapped[str] = mapped_column(String, default="unknown") # tag_fail | jump | emergency_door
status: Mapped[str] = mapped_column(String, default="pending") # pending | confirmed | false_alarm
event_type: Mapped[str] = mapped_column(String, default="unknown") # tailgating | unpaid | jump | crawling | unknown
reason: Mapped[Optional[str]] = mapped_column(String, nullable=True) # status == "false_alarm" 일 때 오탐 사유
handled_by: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("admins.id"), nullable=True)
handled_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)

Expand Down
5 changes: 4 additions & 1 deletion backend/app/schemas/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class EventCreate(BaseModel):
clip_url: Optional[str] = None
track_id: Optional[int] = None
confidence: Optional[float] = None
event_type: Optional[str] = None # AI 가 분류 결과를 함께 전송 (없으면 모델 default 'unknown')


class EventResponse(BaseModel):
Expand All @@ -55,14 +56,16 @@ class EventResponse(BaseModel):
track_id: Optional[int]
confidence: Optional[float]
status: str
event_type: str
reason: Optional[str] = None
handled_by: Optional[int]
handled_at: Optional[datetime]

model_config = {"from_attributes": True}


class EventStatusUpdate(BaseModel):
status: Literal["confirmed", "dismissed"]
status: Literal["confirmed", "false_alarm"]


# ── Notification ──────────────────────────────────────────────────────────────
Expand Down
Loading