GrantForge Bot
Deploy to Hugging Face
afd56bc
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")
)