import uuid from datetime import datetime from sqlalchemy import String, Integer, Float, Boolean, Text, DateTime, ForeignKey, Uuid, func from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base class VoteSession(Base): __tablename__ = "vote_sessions" id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4) decision_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("decisions.id")) item_version_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("item_versions.id")) voting_protocol_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("voting_protocols.id"), nullable=False) # Snapshot at session start wot_size: Mapped[int] = mapped_column(Integer, default=0) smith_size: Mapped[int] = mapped_column(Integer, default=0) techcomm_size: Mapped[int] = mapped_column(Integer, default=0) # Dates starts_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) ends_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) # Status status: Mapped[str] = mapped_column(String(32), default="open") # open, closed, tallied # Tallies votes_for: Mapped[int] = mapped_column(Integer, default=0) votes_against: Mapped[int] = mapped_column(Integer, default=0) votes_total: Mapped[int] = mapped_column(Integer, default=0) smith_votes_for: Mapped[int] = mapped_column(Integer, default=0) techcomm_votes_for: Mapped[int] = mapped_column(Integer, default=0) threshold_required: Mapped[float] = mapped_column(Float, default=0.0) result: Mapped[str | None] = mapped_column(String(32)) # adopted, rejected, null # Chain recording chain_recorded: Mapped[bool] = mapped_column(Boolean, default=False) chain_tx_hash: Mapped[str | None] = mapped_column(String(128)) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) votes: Mapped[list["Vote"]] = relationship(back_populates="session", cascade="all, delete-orphan") class Vote(Base): __tablename__ = "votes" id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4) session_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("vote_sessions.id"), nullable=False) voter_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("duniter_identities.id"), nullable=False) vote_value: Mapped[str] = mapped_column(String(32), nullable=False) # for, against, or nuanced levels nuanced_level: Mapped[int | None] = mapped_column(Integer) # 0-5 for nuanced votes comment: Mapped[str | None] = mapped_column(Text) # Cryptographic proof signature: Mapped[str] = mapped_column(Text, nullable=False) signed_payload: Mapped[str] = mapped_column(Text, nullable=False) # Voter status snapshot voter_wot_status: Mapped[str] = mapped_column(String(32), default="member") voter_is_smith: Mapped[bool] = mapped_column(Boolean, default=False) voter_is_techcomm: Mapped[bool] = mapped_column(Boolean, default=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) session: Mapped["VoteSession"] = relationship(back_populates="votes")