from __future__ import annotations import json from datetime import datetime, timezone from sqlalchemy import Boolean, DateTime, ForeignKey, String, Text from sqlalchemy.orm import Mapped, mapped_column, relationship from app.extensions import db def utcnow() -> datetime: return datetime.now(timezone.utc) class Person(db.Model): __tablename__ = "people" id: Mapped[int] = mapped_column(primary_key=True) full_name: Mapped[str] = mapped_column(String(160), nullable=False, index=True) employee_id: Mapped[str] = mapped_column(String(80), unique=True, index=True, nullable=False) notes: Mapped[str | None] = mapped_column(Text) active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, nullable=False) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False, ) face_encodings: Mapped[list["FaceEncoding"]] = relationship( back_populates="person", cascade="all, delete-orphan", lazy="selectin", ) attendance_events = relationship( "AttendanceEvent", back_populates="person", cascade="all, delete-orphan", lazy="selectin", ) class FaceEncoding(db.Model): __tablename__ = "face_encodings" id: Mapped[int] = mapped_column(primary_key=True) person_id: Mapped[int] = mapped_column(ForeignKey("people.id"), nullable=False, index=True) image_slot: Mapped[int] = mapped_column(default=1, nullable=False) vector_json: Mapped[str] = mapped_column(Text, nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, nullable=False) person: Mapped[Person] = relationship(back_populates="face_encodings") @property def vector(self) -> list[float]: return json.loads(self.vector_json) @vector.setter def vector(self, value: list[float]) -> None: self.vector_json = json.dumps([float(item) for item in value])