state-hub/api/models/capability_request.py

102 lines
3.9 KiB
Python
Raw Permalink Normal View History

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_workplan_id: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True),
ForeignKey("workplans.id", ondelete="SET NULL", onupdate="CASCADE"),
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_workplan_id: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True),
ForeignKey("workplans.id", ondelete="SET NULL", onupdate="CASCADE"),
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", onupdate="CASCADE"),
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