feat: Phase 2 구현 — AI 에이전트 4인방, EVMS, Vision AI, Geofence
AI 에이전트 (Layer 2): - GONGSA: 공사 담당 (공정 브리핑, 공기 지연 감지, 날씨 연동 작업 조정) - PUMJIL: 품질 담당 (시공 전 체크리스트, Vision 보조 판독, 시험 기한 추적) - ANJEON: 안전 담당 (위험 공정 경보, TBM 생성, 중대재해처벌법 Q&A) - GUMU: 공무 담당 (인허가 능동 추적, 기성청구 제안, 보고서 초안) - 에이전트 라우터 (키워드 기반 자동 분배), 아침 브리핑 엔드포인트 EVMS 기본: - PV·EV·AC·SPI·CPI 산출 (WBS/Task 기반) - EAC·ETC 예측, 스냅샷 이력 저장 Vision AI: - Level 1: 현장 사진 분류 (Claude Vision), 작업일보 자동 첨부 - Level 2: 안전장비(안전모/조끼) 착용 감지 Geofence 위험구역: - 구역 CRUD (굴착면, 크레인 반경, 밀폐공간 등) - 진입 이벤트 웹훅 (익명 — 개인 이동 경로 비수집) 인허가 자동도출: - 공종 입력 → AI가 필요 인허가 목록 자동 도출 + 체크리스트 생성 DB 마이그레이션 (002): - agent_conversations, agent_messages, evms_snapshots, geofence_zones Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
93
backend/alembic/versions/002_phase2_agents_evms_geofence.py
Normal file
93
backend/alembic/versions/002_phase2_agents_evms_geofence.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""Phase 2: agents, evms_snapshots, geofence_zones
|
||||
|
||||
Revision ID: 002
|
||||
Revises: 001
|
||||
Create Date: 2026-03-24
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision = "002"
|
||||
down_revision = "001"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ── agent_conversations ────────────────────────────────────────────────
|
||||
op.create_table(
|
||||
"agent_conversations",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
|
||||
sa.Column("project_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("projects.id", ondelete="CASCADE"), nullable=False),
|
||||
sa.Column("user_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False),
|
||||
sa.Column("agent_type", sa.Enum("gongsa", "pumjil", "anjeon", "gumu", name="agent_type"), nullable=False),
|
||||
sa.Column("title", sa.String(200), nullable=True),
|
||||
sa.Column("status", sa.Enum("active", "closed", name="conversation_status"), server_default="active", nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
)
|
||||
op.create_index("ix_agent_conversations_project", "agent_conversations", ["project_id"])
|
||||
op.create_index("ix_agent_conversations_user", "agent_conversations", ["user_id"])
|
||||
|
||||
# ── agent_messages ─────────────────────────────────────────────────────
|
||||
op.create_table(
|
||||
"agent_messages",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
|
||||
sa.Column("conversation_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("agent_conversations.id", ondelete="CASCADE"), nullable=False),
|
||||
sa.Column("role", sa.String(20), nullable=False),
|
||||
sa.Column("content", sa.Text, nullable=False),
|
||||
sa.Column("metadata", postgresql.JSONB, nullable=True),
|
||||
sa.Column("is_proactive", sa.Boolean, server_default="false", nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
)
|
||||
op.create_index("ix_agent_messages_conversation", "agent_messages", ["conversation_id"])
|
||||
|
||||
# ── evms_snapshots ─────────────────────────────────────────────────────
|
||||
op.create_table(
|
||||
"evms_snapshots",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
|
||||
sa.Column("project_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("projects.id", ondelete="CASCADE"), nullable=False),
|
||||
sa.Column("snapshot_date", sa.Date, nullable=False, index=True),
|
||||
sa.Column("total_budget", sa.Float, nullable=True),
|
||||
sa.Column("planned_progress", sa.Float, nullable=True),
|
||||
sa.Column("actual_progress", sa.Float, nullable=True),
|
||||
sa.Column("pv", sa.Float, nullable=True),
|
||||
sa.Column("ev", sa.Float, nullable=True),
|
||||
sa.Column("ac", sa.Float, nullable=True),
|
||||
sa.Column("spi", sa.Float, nullable=True),
|
||||
sa.Column("cpi", sa.Float, nullable=True),
|
||||
sa.Column("eac", sa.Float, nullable=True),
|
||||
sa.Column("etc", sa.Float, nullable=True),
|
||||
sa.Column("notes", sa.Text, nullable=True),
|
||||
sa.Column("detail_json", postgresql.JSONB, nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
)
|
||||
op.create_index("ix_evms_snapshots_project_date", "evms_snapshots", ["project_id", "snapshot_date"])
|
||||
|
||||
# ── geofence_zones ─────────────────────────────────────────────────────
|
||||
op.create_table(
|
||||
"geofence_zones",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
|
||||
sa.Column("project_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("projects.id", ondelete="CASCADE"), nullable=False),
|
||||
sa.Column("name", sa.String(100), nullable=False),
|
||||
sa.Column("zone_type", sa.String(50), nullable=False),
|
||||
sa.Column("coordinates", postgresql.JSONB, nullable=False),
|
||||
sa.Column("radius_m", sa.Float, nullable=True),
|
||||
sa.Column("is_active", sa.Boolean, server_default="true", nullable=False),
|
||||
sa.Column("description", sa.Text, nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
)
|
||||
op.create_index("ix_geofence_zones_project", "geofence_zones", ["project_id"])
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_table("geofence_zones")
|
||||
op.drop_table("evms_snapshots")
|
||||
op.drop_table("agent_messages")
|
||||
op.drop_table("agent_conversations")
|
||||
op.execute("DROP TYPE IF EXISTS agent_type")
|
||||
op.execute("DROP TYPE IF EXISTS conversation_status")
|
||||
Reference in New Issue
Block a user