import uuid from datetime import datetime from sqlalchemy import DateTime, ForeignKey, String, Text from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Mapped, mapped_column, relationship from api.models.base import Base, TimestampMixin, new_uuid class CapabilityRequest(Base, TimestampMixin): __tablename__ = "capability_requests" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=new_uuid ) title: Mapped[str] = mapped_column(String(500), nullable=False) description: Mapped[str | None] = mapped_column(Text, nullable=True) capability_type: Mapped[str] = mapped_column(String(50), nullable=False) priority: Mapped[str] = mapped_column( String(20), nullable=False, default="medium", server_default="medium" ) status: Mapped[str] = mapped_column( String(20), nullable=False, default="requested", server_default="requested" ) # Requester side requesting_domain_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("domains.id", ondelete="RESTRICT"), nullable=False, index=True, ) requesting_workstream_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("workstreams.id", ondelete="SET NULL"), nullable=True, ) requesting_agent: Mapped[str] = mapped_column(String(100), nullable=False) # Fulfiller side (populated on accept / auto-route) fulfilling_domain_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("domains.id", ondelete="SET NULL"), nullable=True, index=True, ) fulfilling_workstream_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("workstreams.id", ondelete="SET NULL"), nullable=True, ) fulfilling_agent: Mapped[str | None] = mapped_column(String(100), nullable=True) # Links blocking_task_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("tasks.id", ondelete="SET NULL"), nullable=True, ) catalog_entry_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("capability_catalog.id", ondelete="SET NULL"), nullable=True, ) resolution_note: Mapped[str | None] = mapped_column(Text, nullable=True) routing_note: Mapped[str | None] = mapped_column(Text, nullable=True) # Dispute fields (populated when status = routing_disputed) dispute_reason: Mapped[str | None] = mapped_column(Text, nullable=True) disputed_by: Mapped[str | None] = mapped_column(String(100), nullable=True) dispute_suggested_domain: Mapped[str | None] = mapped_column(String(100), nullable=True) disputed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) accepted_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) completed_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) # Relationships requesting_domain: Mapped["Domain"] = relationship( # noqa: F821 "Domain", foreign_keys=[requesting_domain_id], lazy="selectin" ) fulfilling_domain: Mapped["Domain | None"] = relationship( # noqa: F821 "Domain", foreign_keys=[fulfilling_domain_id], lazy="selectin" ) blocking_task: Mapped["Task | None"] = relationship("Task", lazy="selectin") # noqa: F821 catalog_entry: Mapped["CapabilityCatalog | None"] = relationship( # noqa: F821 "CapabilityCatalog", lazy="selectin" ) @property def requesting_domain_slug(self) -> str: return self.requesting_domain.slug if self.requesting_domain else "" @property def fulfilling_domain_slug(self) -> str | None: return self.fulfilling_domain.slug if self.fulfilling_domain else None