Benard John
feat: implement full-stack architecture with database models, authentication services, and web dashboard components
37b5223 | """ | |
| Ukweli — SQLAlchemy ORM Models | |
| Defines the relational schema for PostgreSQL per Architecture Section 3.3.4. | |
| """ | |
| from __future__ import annotations | |
| import uuid | |
| from datetime import datetime | |
| from sqlalchemy import ( | |
| Boolean, | |
| DateTime, | |
| Enum, | |
| Float, | |
| ForeignKey, | |
| Index, | |
| Integer, | |
| String, | |
| Text, | |
| func, | |
| ) | |
| from sqlalchemy.dialects.postgresql import JSONB, UUID | |
| from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship | |
| class Base(DeclarativeBase): | |
| """Declarative base for all ORM models.""" | |
| pass | |
| class Document(Base): | |
| """ | |
| Catalog of all ingested documents. | |
| Maps to the Metadata Registry described in Section 3.3.4. | |
| """ | |
| __tablename__ = "documents" | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| title: Mapped[str] = mapped_column(String(1024), nullable=False) | |
| source_url: Mapped[str | None] = mapped_column(String(2048), nullable=True) | |
| pdf_url: Mapped[str | None] = mapped_column(String(2048), nullable=True) | |
| fiscal_year: Mapped[str | None] = mapped_column(String(20), nullable=True, index=True) | |
| auditee: Mapped[str | None] = mapped_column(String(512), nullable=True, index=True) | |
| report_type: Mapped[str] = mapped_column( | |
| Enum( | |
| "financial_audit", | |
| "performance_audit", | |
| "special_audit", | |
| "public_debt_audit", | |
| "county_audit", | |
| "summary_report", | |
| "strategic_document", | |
| "annual_corporate_report", | |
| name="report_type_enum", | |
| ), | |
| nullable=False, | |
| default="financial_audit", | |
| ) | |
| page_count: Mapped[int | None] = mapped_column(Integer, nullable=True) | |
| published_date: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) | |
| checksum_sha256: Mapped[str | None] = mapped_column(String(64), nullable=True, unique=True) | |
| status: Mapped[str] = mapped_column( | |
| Enum( | |
| "pending", | |
| "downloading", | |
| "parsing", | |
| "parsed", | |
| "embedding", | |
| "ready", | |
| "failed", | |
| name="document_status_enum", | |
| ), | |
| nullable=False, | |
| default="pending", | |
| index=True, | |
| ) | |
| raw_storage_path: Mapped[str | None] = mapped_column(String(1024), nullable=True) | |
| language: Mapped[str] = mapped_column(String(5), nullable=False, default="en") | |
| file_size_bytes: Mapped[int | None] = mapped_column(Integer, nullable=True) | |
| error_message: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| created_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), server_default=func.now(), nullable=False | |
| ) | |
| updated_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False | |
| ) | |
| # Relationships | |
| chunks: Mapped[list[Chunk]] = relationship("Chunk", back_populates="document", cascade="all, delete-orphan") | |
| ingestion_logs: Mapped[list[IngestionLog]] = relationship( | |
| "IngestionLog", back_populates="document", cascade="all, delete-orphan" | |
| ) | |
| __table_args__ = ( | |
| Index("ix_documents_auditee_fy", "auditee", "fiscal_year"), | |
| ) | |
| class Chunk(Base): | |
| """ | |
| Individual content chunks derived from parsed documents. | |
| Schema matches Section 3.2.4 Chunk Metadata Schema. | |
| """ | |
| __tablename__ = "chunks" | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| document_id: Mapped[uuid.UUID] = mapped_column( | |
| UUID(as_uuid=True), ForeignKey("documents.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| chunk_type: Mapped[str] = mapped_column( | |
| Enum( | |
| "narrative", | |
| "table", | |
| "finding", | |
| "recommendation", | |
| "legal_reference", | |
| name="chunk_type_enum", | |
| ), | |
| nullable=False, | |
| ) | |
| content: Mapped[str] = mapped_column(Text, nullable=False) | |
| page_range_start: Mapped[int | None] = mapped_column(Integer, nullable=True) | |
| page_range_end: Mapped[int | None] = mapped_column(Integer, nullable=True) | |
| section_heading: Mapped[str | None] = mapped_column(String(512), nullable=True) | |
| entities: Mapped[dict | None] = mapped_column(JSONB, nullable=True) | |
| language: Mapped[str] = mapped_column(String(5), nullable=False, default="en") | |
| audit_period: Mapped[str | None] = mapped_column(String(20), nullable=True) | |
| auditee: Mapped[str | None] = mapped_column(String(512), nullable=True) | |
| finding_category: Mapped[str | None] = mapped_column(String(64), nullable=True) | |
| embedding_vector_id: Mapped[str | None] = mapped_column(String(64), nullable=True) | |
| token_count: Mapped[int | None] = mapped_column(Integer, nullable=True) | |
| chunk_index: Mapped[int] = mapped_column(Integer, nullable=False, default=0) | |
| created_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), server_default=func.now(), nullable=False | |
| ) | |
| # Relationships | |
| document: Mapped[Document] = relationship("Document", back_populates="chunks") | |
| __table_args__ = ( | |
| Index("ix_chunks_document_index", "document_id", "chunk_index"), | |
| Index("ix_chunks_auditee_period", "auditee", "audit_period"), | |
| # Index( | |
| # "ix_chunks_content_fts", | |
| # func.to_tsvector("english", "content"), | |
| # postgresql_using="gin", | |
| # ), | |
| ) | |
| class IngestionLog(Base): | |
| """ | |
| Audit trail for every document processing step. | |
| Provides transparency per Section 3.3.4. | |
| """ | |
| __tablename__ = "ingestion_logs" | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| document_id: Mapped[uuid.UUID] = mapped_column( | |
| UUID(as_uuid=True), ForeignKey("documents.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| stage: Mapped[str] = mapped_column( | |
| String(64), nullable=False | |
| ) # e.g. "download", "parse", "chunk", "embed" | |
| status: Mapped[str] = mapped_column(String(32), nullable=False) # "started", "completed", "failed" | |
| started_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), server_default=func.now(), nullable=False | |
| ) | |
| completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) | |
| error_message: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| worker_id: Mapped[str | None] = mapped_column(String(128), nullable=True) | |
| metadata_extra: Mapped[dict | None] = mapped_column(JSONB, nullable=True) | |
| # Relationships | |
| document: Mapped[Document] = relationship("Document", back_populates="ingestion_logs") | |
| class QueryLog(Base): | |
| """ | |
| Immutable audit trail of every RAG query. | |
| Required by Section 7.2 — transparency logging (WORM-style). | |
| """ | |
| __tablename__ = "query_logs" | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| query_text: Mapped[str] = mapped_column(Text, nullable=False) | |
| language: Mapped[str] = mapped_column(String(5), nullable=False, default="en") | |
| mode: Mapped[str] = mapped_column(String(20), nullable=False, default="concise") | |
| user_tier: Mapped[str] = mapped_column(String(20), nullable=False, default="public") | |
| user_id: Mapped[uuid.UUID | None] = mapped_column( | |
| UUID(as_uuid=True), ForeignKey("users.id"), nullable=True, index=True | |
| ) | |
| api_key_id: Mapped[uuid.UUID | None] = mapped_column( | |
| UUID(as_uuid=True), ForeignKey("api_keys.id"), nullable=True | |
| ) | |
| fingerprint: Mapped[str | None] = mapped_column(String(16), nullable=True, index=True) | |
| filters: Mapped[dict | None] = mapped_column(JSONB, nullable=True) | |
| answer: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| citations: Mapped[dict | None] = mapped_column(JSONB, nullable=True) | |
| chunks_considered: Mapped[int | None] = mapped_column(Integer, nullable=True) | |
| latency_ms: Mapped[int | None] = mapped_column(Integer, nullable=True) | |
| confidence: Mapped[str | None] = mapped_column(String(32), nullable=True) | |
| llm_model_used: Mapped[str | None] = mapped_column(String(128), nullable=True) | |
| llm_tokens_used: Mapped[int | None] = mapped_column(Integer, nullable=True) | |
| conversation_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), nullable=True) | |
| created_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), server_default=func.now(), nullable=False | |
| ) | |
| # Relationships | |
| feedbacks: Mapped[list[Feedback]] = relationship("Feedback", back_populates="query_log") | |
| class Feedback(Base): | |
| """ | |
| User feedback and corrections on RAG responses. | |
| Maps to Section 4.2 POST /feedback. | |
| """ | |
| __tablename__ = "feedbacks" | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| query_id: Mapped[uuid.UUID] = mapped_column( | |
| UUID(as_uuid=True), ForeignKey("query_logs.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| rating: Mapped[int] = mapped_column(Integer, nullable=False) # -1, 0, 1 | |
| correction: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| suggested_citation: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| review_status: Mapped[str] = mapped_column( | |
| String(20), nullable=False, default="pending" | |
| ) | |
| created_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), server_default=func.now(), nullable=False | |
| ) | |
| # Relationships | |
| query_log: Mapped[QueryLog] = relationship("QueryLog", back_populates="feedbacks") | |
| class PageHash(Base): | |
| """ | |
| Stores content hashes of crawled pages for change detection. | |
| Used by the change_detection service. | |
| """ | |
| __tablename__ = "page_hashes" | |
| id: Mapped[uuid.UUID] = mapped_column( | |
| UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 | |
| ) | |
| url_path: Mapped[str] = mapped_column(String(2048), nullable=False, unique=True, index=True) | |
| content_hash: Mapped[str] = mapped_column(String(64), nullable=False) | |
| last_checked_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), server_default=func.now(), nullable=False | |
| ) | |
| changed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) | |