import datetime import uuid from sqlalchemy import ( Column, String, Integer, Float, DateTime, ForeignKey, Boolean, Text, JSON, ) from sqlalchemy.orm import relationship, backref from core.subscription.db import Base class Project(Base): __tablename__ = "projects" id = Column(String, primary_key=True, index=True, default=lambda: str(uuid.uuid4())) clerk_user_id = Column( String, ForeignKey("users.clerk_id"), index=True, nullable=False ) title = Column(String, nullable=False) description = Column(String, nullable=True) status = Column(String, default="draft") # draft / in_progress / completed estimated_value = Column(Float, nullable=True) program_type = Column( String, default="SMART", nullable=False ) # e.g. "SMART", "ARIMR" program_name = Column( String, nullable=True ) # e.g. "Ścieżka SMART - Innowacje 2026" last_generated_at = Column(DateTime, nullable=True) final_document_markdown = Column(Text, nullable=True) final_document_generated_at = Column(DateTime, nullable=True) final_document_audit_result = Column(JSON, nullable=True) external_context = Column(JSON, nullable=True) foreign_grant_extract_text = Column( Text, nullable=True ) # Tekst wniosku zewnętrznego z LlamaParse created_at = Column( DateTime, default=lambda: datetime.datetime.now(datetime.timezone.utc) ) updated_at = Column( DateTime, default=lambda: datetime.datetime.now(datetime.timezone.utc), onupdate=lambda: datetime.datetime.now(datetime.timezone.utc), ) user = relationship("User", backref="projects") class ProjectSectionTemplate(Base): __tablename__ = "project_section_templates" id = Column(Integer, primary_key=True) program_type = Column( String, index=True, nullable=False ) # "SMART", "ARIMR", "ZUS_BHP", "REGIONAL" section_type = Column( String, nullable=False ) # unique identifier like "description", "market_analysis" order = Column(Integer, default=0) title = Column(String, nullable=False) description = Column(String, nullable=True) # krótki opis sekcji dla użytkownika default_prompt = Column(Text, nullable=True) # podpowiedź dla Wizard ai_prompt_template = Column( Text, nullable=True ) # szczegółowy precyzyjny prompt per sekcja is_required = Column(Boolean, default=True) class ProjectSection(Base): __tablename__ = "project_sections" id = Column(String, primary_key=True, index=True, default=lambda: str(uuid.uuid4())) project_id = Column( String, ForeignKey("projects.id", ondelete="CASCADE"), index=True, nullable=False, ) order = Column(Integer, default=0) section_type = Column(String, nullable=False) # np. description, budget, innovation content = Column(String, nullable=True) # Treść Markdown is_approved = Column(Boolean, default=False) generated_by_ai = Column(Boolean, default=False) last_reviewed_at = Column(DateTime, nullable=True) project = relationship( "Project", backref=backref("sections", cascade="all, delete-orphan") ) class ProjectSectionVersion(Base): __tablename__ = "section_versions" id = Column(String, primary_key=True, index=True, default=lambda: str(uuid.uuid4())) section_id = Column( String, ForeignKey("project_sections.id", ondelete="CASCADE"), index=True, nullable=False, ) old_content = Column(String, nullable=True) author = Column(String, default="Ręczna") # np. "Ręczna", "Asystent AI", "Autofix" summary = Column( String, nullable=True ) # krótki opis, np. "Aktualizacja na podstawie audytu" timestamp = Column( DateTime, default=lambda: datetime.datetime.now(datetime.timezone.utc) ) section = relationship( "ProjectSection", backref=backref( "versions", cascade="all, delete-orphan", order_by="desc(ProjectSectionVersion.timestamp)", ), ) class ProjectQuestion(Base): __tablename__ = "project_questions" id = Column(String, primary_key=True, index=True, default=lambda: str(uuid.uuid4())) project_id = Column( String, ForeignKey("projects.id", ondelete="CASCADE"), index=True, nullable=False, ) question = Column(String, nullable=False) answer = Column(String, nullable=False) sources = Column( String, nullable=True ) # Zapisywanie jako string połączony lub JSON confidence = Column(Float, nullable=True) recommendation = Column(String, nullable=True) created_at = Column( DateTime, default=lambda: datetime.datetime.now(datetime.timezone.utc) ) project = relationship( "Project", backref=backref("questions_history", cascade="all, delete-orphan") ) class ProjectExportVersion(Base): __tablename__ = "project_export_versions" id = Column(String, primary_key=True, index=True, default=lambda: str(uuid.uuid4())) project_id = Column( String, ForeignKey("projects.id", ondelete="CASCADE"), index=True, nullable=False, ) version_number = Column(Integer, default=1) title = Column(String, nullable=True) # np. "Wersja Draft" export_type = Column(String, default="archived") # "current" | "archived" content_markdown = Column(Text, nullable=True) created_at = Column( DateTime, default=lambda: datetime.datetime.now(datetime.timezone.utc) ) project = relationship( "Project", backref=backref("export_versions", cascade="all, delete-orphan") ) class ProjectChatMessage(Base): __tablename__ = "project_chat_messages" id = Column(String, primary_key=True, index=True, default=lambda: str(uuid.uuid4())) project_id = Column( String, ForeignKey("projects.id", ondelete="CASCADE"), index=True, nullable=False, ) role = Column(String, nullable=False) # 'user' or 'assistant' content = Column(Text, nullable=False) created_at = Column( DateTime, default=lambda: datetime.datetime.now(datetime.timezone.utc) ) project = relationship( "Project", backref=backref( "chat_messages", cascade="all, delete-orphan", order_by="ProjectChatMessage.created_at", ), ) class ProjectDocument(Base): """ Dokument PDF przesłany przez użytkownika do projektu. Po indeksacji trafia do Pinecone (namespace=tenant_{user_id}). Statusy: uploaded — plik zapisany, oczekuje na przetworzenie processing — trwa parsowanie PDF i indeksacja w RAG indexed — dostępny w vector store error — błąd parsowania/indeksacji """ __tablename__ = "project_documents" id = Column(String, primary_key=True, index=True, default=lambda: str(uuid.uuid4())) project_id = Column( String, ForeignKey("projects.id", ondelete="CASCADE"), index=True, nullable=False, ) filename = Column(String, nullable=False) original_filename = Column(String, nullable=False) file_size_bytes = Column(Integer, nullable=True) mime_type = Column(String, default="application/pdf") doc_type = Column( String, default="knowledge_base" ) # knowledge_base | external_grant # Ścieżka pliku na dysku / URL (np. /data/uploads/{project_id}/{id}.pdf) storage_path = Column(String, nullable=True) # Pipeline RAG status = Column( String, default="uploaded" ) # uploaded | processing | indexed | error parser_used = Column(String, nullable=True) # llamaparse | pypdf | unstructured chunks_count = Column(Integer, nullable=True) # liczba child chunks w Pinecone rag_namespace = Column(String, nullable=True) # tenant_{user_id}_{project_id} error_message = Column(Text, nullable=True) processing_metadata = Column(JSON, nullable=True) # dodatkowe dane z parsera uploaded_at = Column( DateTime, default=lambda: datetime.datetime.now(datetime.timezone.utc) ) indexed_at = Column(DateTime, nullable=True) project = relationship( "Project", backref=backref("documents", cascade="all, delete-orphan") )