prism-models 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of prism-models might be problematic. Click here for more details.

Files changed (38) hide show
  1. prism_models/__init__.py +45 -0
  2. prism_models/agent_profile.py +101 -0
  3. prism_models/base.py +75 -0
  4. prism_models/chat.py +349 -0
  5. prism_models/config.py +37 -0
  6. prism_models/content.py +250 -0
  7. prism_models/feedback.py +145 -0
  8. prism_models/migration/README +1 -0
  9. prism_models/migration/__init__.py +0 -0
  10. prism_models/migration/env.py +131 -0
  11. prism_models/migration/script.py.mako +28 -0
  12. prism_models/migration/versions/2025_09_11_1516_161f8829d93f_initial_schema.py +492 -0
  13. prism_models/migration/versions/2025_09_11_1558_5e011849ea76_changes_for_feedback.py +79 -0
  14. prism_models/migration/versions/2025_09_14_2243_059af231c2b2_profile_entities.py +108 -0
  15. prism_models/migration/versions/2025_09_15_1646_3219fec0bb10_agent_changes.py +32 -0
  16. prism_models/migration/versions/2025_09_16_1627_f2013b08daac_rename_metadata_to_additional_data.py +37 -0
  17. prism_models/migration/versions/2025_09_17_1147_327febbf555f_display_name_added.py +34 -0
  18. prism_models/migration/versions/2025_09_18_1106_b0bcb7ca1dc9_add_support_for_profile_on_create_convo.py +41 -0
  19. prism_models/migration/versions/2025_09_18_1511_bbc1955191e6_preview_mode.py +36 -0
  20. prism_models/migration/versions/2025_09_26_1115_6eb70e848451_added_publish_status_to_document.py +38 -0
  21. prism_models/migration/versions/2025_09_26_1240_f8b0ea2e743c_drop_unique_title_version_on_document.py +40 -0
  22. prism_models/migration/versions/2025_09_26_1505_07dc8c2589e0_added_chunk_id_and_vector_embeddings_to_.py +44 -0
  23. prism_models/migration/versions/2025_09_29_1220_46ba2693b883_add_markdown_markdown_file_path_s3_.py +32 -0
  24. prism_models/migration/versions/2025_10_02_1520_bf1472a9b021_removed_doc_id_from_config_table_and_.py +34 -0
  25. prism_models/migration/versions/2025_10_02_1525_6c0e63e0fef8_removed_doc_id_from_config_table_.py +34 -0
  26. prism_models/migration/versions/2025_10_02_1608_1b3eb48f5017_config_id_on_delete_will_be_set_to_null.py +34 -0
  27. prism_models/migration/versions/2025_10_03_1109_ac85b606d8a4_added_docling_hybrid_to_chunkstrategy.py +32 -0
  28. prism_models/migration/versions/2025_10_03_1204_7d1cb343a63f_added_s3_bucket_and_s3_dir_in_source_.py +42 -0
  29. prism_models/migration/versions/2025_10_03_1452_f9c750ec2a0b_1_to_1_relationship_between_collection_.py +52 -0
  30. prism_models/migration/versions/2025_10_07_1722_5cfa0c462948_added_travel_advisory_enum.py +38 -0
  31. prism_models/migration/versions/2025_10_08_1304_c91eb8e38cc7_added_destination_report_and_event_.py +38 -0
  32. prism_models/migration/versions/2025_10_09_1308_796b720ea35f_added_qa_id_to_vovetor.py +42 -0
  33. prism_models/migration/versions/2025_10_16_1611_663c66268631_added_sharepoint_drive_item_id_as_an_.py +32 -0
  34. prism_models/qdrant.py +97 -0
  35. prism_models-0.1.0.dist-info/METADATA +10 -0
  36. prism_models-0.1.0.dist-info/RECORD +38 -0
  37. prism_models-0.1.0.dist-info/WHEEL +5 -0
  38. prism_models-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,250 @@
1
+ import enum
2
+ from typing import Any, Optional
3
+
4
+ from sqlalchemy import (
5
+ JSON,
6
+ Boolean,
7
+ Column,
8
+ Enum,
9
+ Float,
10
+ ForeignKey,
11
+ Index,
12
+ Integer,
13
+ String,
14
+ Table,
15
+ Text,
16
+ UniqueConstraint,
17
+ )
18
+ from sqlalchemy.dialects.postgresql import ARRAY
19
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
20
+
21
+ from prism_models.base import Base, BaseModel
22
+ from prism_models.chat import Contact
23
+ from prism_models.qdrant import QdrantVectorPayload, PydanticType
24
+
25
+
26
+ class DocumentStatus(str, enum.Enum):
27
+ PENDING = "pending"
28
+ PROCESSING = "processing"
29
+ COMPLETED = "completed"
30
+ FAILED = "failed"
31
+
32
+ class DocumentPublishStatus(str, enum.Enum):
33
+ PREVIEW = "preview"
34
+ ACTIVE = "active"
35
+ INACTIVE = "inactive"
36
+
37
+ class SyncFrequency(str, enum.Enum):
38
+ HOURLY = "hourly"
39
+ DAILY = "daily"
40
+ WEEKLY = "weekly"
41
+ REALTIME = "realtime"
42
+
43
+
44
+ class ChunkStrategy(str, enum.Enum):
45
+ FIXED_SIZE = "fixed_size"
46
+ SEMANTIC = "semantic"
47
+ PARAGRAPH = "paragraph"
48
+ DOCLING_HYBRID = "docling_hybrid"
49
+ LLM = "llm"
50
+
51
+
52
+ class ProvenanceType(str, enum.Enum):
53
+ GENERATED = "generated"
54
+ MANUAL = "manual"
55
+ VERIFIED = "verified"
56
+
57
+
58
+ class SourceName(str, enum.Enum):
59
+ S3 = "S3"
60
+ SHARE_POINT = "SHARE_POINT"
61
+ GRID = "GRID"
62
+ GRID_DESTINATION_REPORT = "GRID_DESTINATION_REPORT"
63
+ GRID_EVENT_REPORT = "GRID_EVENT_REPORT"
64
+ CRM = "CRM"
65
+ CONFLUENCE = "CONFLUENCE"
66
+ CUSTOM = "CUSTOM"
67
+ TRAVEL_ADVISORY = "TRAVEL_ADVISORY"
68
+ MSA = "MSA"
69
+
70
+ class Source(BaseModel):
71
+ """Source model for tracking origin of content."""
72
+
73
+ name: Mapped[str] = mapped_column(
74
+ String(255),
75
+ nullable=False,
76
+ unique=True,
77
+ index=True
78
+ )
79
+
80
+ type: Mapped[SourceName] = mapped_column(
81
+ Enum(SourceName, native_enum=False),
82
+ nullable=True,
83
+ index=True,
84
+ default=SourceName.CUSTOM
85
+ )
86
+
87
+ connection_config: Mapped[dict[str, Any] | None] = mapped_column(
88
+ JSON, nullable=True, default=dict
89
+ )
90
+ additional_data: Mapped[dict[str, Any] | None] = mapped_column(
91
+ "additional_data", JSON, nullable=True, default=dict
92
+ )
93
+ is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
94
+
95
+ # New fields (nullable, only meaningful if name == S3)
96
+ s3_bucket: Mapped[str | None] = mapped_column(String(255), nullable=True)
97
+ s3_directory: Mapped[str | None] = mapped_column(String(2048), nullable=True)
98
+
99
+ # One-to-one relationship to Collection via unique FK - NOTE NOT UNIQUE
100
+ collection_id: Mapped[int | None] = mapped_column(
101
+ Integer, ForeignKey("collection.id"), nullable=False, unique=True, index=True
102
+ )
103
+ collection: Mapped[Optional["Collection"]] = relationship(
104
+ "Collection", back_populates="source", uselist=False
105
+ )
106
+
107
+
108
+ def __repr__(self):
109
+ return f"<Source(id={getattr(self, 'id', None)}, name='{self.name}')>"
110
+
111
+
112
+ class Collection(BaseModel):
113
+ """Collection model for grouping documents."""
114
+
115
+ name: Mapped[str] = mapped_column(String(200), nullable=False, index=True)
116
+ description: Mapped[str | None] = mapped_column(Text, nullable=True)
117
+ additional_data: Mapped[dict[str, Any] | None] = mapped_column("additional_data", JSON, nullable=True, default=dict)
118
+ is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
119
+ owner_id: Mapped[int | None] = mapped_column(ForeignKey("contact.id"), nullable=True, index=True)
120
+ # Relationships
121
+ owner: Mapped[Optional["Contact"]] = relationship("Contact")
122
+ documents: Mapped[list["CollectionDocument"]] = relationship(back_populates="collection")
123
+ # One-to-one backref to Source
124
+ source: Mapped[Optional["Source"]] = relationship(
125
+ "Source", back_populates="collection", uselist=False
126
+ )
127
+
128
+ __table_args__ = (UniqueConstraint("name", name="uq_collection_name"),)
129
+
130
+ def __repr__(self):
131
+ return f"<Collection(id={self.id}, name='{self.name}', is_active={self.is_active})>"
132
+
133
+
134
+ class Document(BaseModel):
135
+ """Document model for storing various types of content."""
136
+
137
+ title: Mapped[str] = mapped_column(String(500), nullable=False, index=True)
138
+ source_id: Mapped[int] = mapped_column(Integer, ForeignKey("source.id"), nullable=False, index=True)
139
+ parent_document_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("document.id"), nullable=True, index=True)
140
+
141
+ uploaded_by_id: Mapped[int | None] = mapped_column(ForeignKey("contact.id"), nullable=True, index=True)
142
+ file_path_s3: Mapped[str | None] = mapped_column(String(2048), nullable=True)
143
+ file_hash: Mapped[str | None] = mapped_column(String(255), nullable=True)
144
+ file_size: Mapped[int | None] = mapped_column(Integer, nullable=True)
145
+ version: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
146
+ is_latest: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
147
+ additional_data: Mapped[dict[str, Any] | None] = mapped_column("additional_data", JSON, nullable=True, default=dict)
148
+ status: Mapped[DocumentStatus] = mapped_column(String(50), nullable=False, index=True, default=DocumentStatus.PENDING)
149
+ publish_status: Mapped[DocumentPublishStatus] = mapped_column(String(50), nullable=False, index=True, default=DocumentPublishStatus.PREVIEW)
150
+ markdown_file_path_s3:Mapped[str | None] = mapped_column(String(2048), nullable=True)
151
+ chunk_config_id: Mapped[int | None] = mapped_column(ForeignKey("chunk_config.id", ondelete="SET NULL"), nullable=True)
152
+ sharepoint_drive_item_id: Mapped[str | None] = mapped_column(String(1024), nullable=True)
153
+ # Relationships
154
+ source: Mapped["Source"] = relationship("Source")
155
+ # Use string-based remote_side to avoid resolving Python built-in id
156
+ parent_document: Mapped[Optional["Document"]] = relationship("Document", remote_side="Document.id")
157
+ chunk_config: Mapped[Optional["ChunkConfig"]] = relationship("ChunkConfig", back_populates="documents")
158
+ uploaded_by: Mapped[Optional["Contact"]] = relationship("Contact", foreign_keys=[uploaded_by_id])
159
+ collections: Mapped[list["CollectionDocument"]] = relationship(back_populates="document")
160
+ chunks: Mapped[list["Chunk"]] = relationship(back_populates="document", cascade="all, delete-orphan")
161
+ qa_pairs: Mapped[list["QAPair"]] = relationship(back_populates="document", cascade="all, delete-orphan")
162
+
163
+
164
+ def __repr__(self):
165
+ return f"<Document(id={self.id}, title='{self.title[:50]}...')>"
166
+
167
+
168
+ class CollectionDocument(BaseModel):
169
+ """Association object between Collections and Documents."""
170
+
171
+ collection_id: Mapped[int] = mapped_column(ForeignKey("collection.id"), nullable=False)
172
+ document_id: Mapped[int] = mapped_column(ForeignKey("document.id"), nullable=False)
173
+
174
+ collection: Mapped["Collection"] = relationship(back_populates="documents")
175
+ document: Mapped["Document"] = relationship(back_populates="collections")
176
+
177
+ __table_args__ = (UniqueConstraint("collection_id", "document_id", name="uq_collection_document"),)
178
+
179
+
180
+ class IntegrationConfig(BaseModel):
181
+ source_id: Mapped[int] = mapped_column(ForeignKey("source.id"), nullable=False, index=True)
182
+ chunk_config_id: Mapped[int | None] = mapped_column(ForeignKey("chunk_config.id"))
183
+ external_id: Mapped[str | None] = mapped_column(String(255))
184
+ target_collection_ids: Mapped[list[int] | None] = mapped_column(ARRAY(Integer))
185
+ sync_frequency: Mapped[SyncFrequency] = mapped_column(String(20), default=SyncFrequency.DAILY)
186
+ sync_enabled: Mapped[bool] = mapped_column(Boolean, default=True)
187
+
188
+ source: Mapped["Source"] = relationship()
189
+ chunk_config: Mapped["ChunkConfig"] = relationship()
190
+
191
+
192
+ class ChunkConfig(BaseModel):
193
+ name: Mapped[str] = mapped_column(String(255))
194
+ strategy: Mapped[ChunkStrategy] = mapped_column(String(50), default=ChunkStrategy.DOCLING_HYBRID)
195
+ chunk_size: Mapped[int] = mapped_column(Integer, default=600)
196
+ chunk_overlap: Mapped[int] = mapped_column(Integer, default=60)
197
+ internal_qa_enabled: Mapped[bool] = mapped_column(Boolean, default=False)
198
+ external_qa_enabled: Mapped[bool] = mapped_column(Boolean, default=False)
199
+ internal_qa_prompt: Mapped[str | None] = mapped_column(Text)
200
+ external_qa_prompt: Mapped[str | None] = mapped_column(Text)
201
+ additional_data: Mapped[dict[str, Any] | None] = mapped_column("additional_data", JSON, nullable=True, default=dict)
202
+ is_template: Mapped[bool] = mapped_column(Boolean, default=False)
203
+
204
+ documents: Mapped[list["Document"]] = relationship("Document", back_populates="chunk_config")
205
+
206
+
207
+ qa_pair_chunk_association_table = Table(
208
+ "qa_pair_chunk_association",
209
+ Base.metadata,
210
+ Column("qa_pair_id", ForeignKey("qa_pair.id"), primary_key=True),
211
+ Column("chunk_id", ForeignKey("chunk.id"), primary_key=True),
212
+ )
213
+
214
+
215
+ class Chunk(BaseModel):
216
+ document_id: Mapped[int] = mapped_column(ForeignKey("document.id"), nullable=False, index=True)
217
+ chunk_config_id: Mapped[int | None] = mapped_column(ForeignKey("chunk_config.id"))
218
+ text: Mapped[str] = mapped_column(Text, nullable=False)
219
+ text_hash: Mapped[str | None] = mapped_column(String(64), index=True)
220
+
221
+ document: Mapped["Document"] = relationship(back_populates="chunks")
222
+ chunk_config: Mapped[Optional["ChunkConfig"]] = relationship()
223
+ qa_pairs: Mapped[list["QAPair"]] = relationship(secondary=qa_pair_chunk_association_table, back_populates="source_chunks")
224
+ vector: Mapped[Optional["Vector"]] = relationship(back_populates="chunk", uselist=False, cascade="all, delete-orphan")
225
+
226
+
227
+ class QAPair(BaseModel):
228
+ document_id: Mapped[int] = mapped_column(ForeignKey("document.id"), nullable=False, index=True)
229
+ question: Mapped[str] = mapped_column(Text, nullable=False)
230
+ answer: Mapped[str] = mapped_column(Text, nullable=False)
231
+ provenance: Mapped[ProvenanceType] = mapped_column(String(50), default=ProvenanceType.GENERATED)
232
+ confidence_score: Mapped[float | None] = mapped_column(Float, nullable=True)
233
+ document: Mapped["Document"] = relationship(back_populates="qa_pairs")
234
+ source_chunks: Mapped[list["Chunk"]] = relationship(secondary=qa_pair_chunk_association_table, back_populates="qa_pairs")
235
+ vector: Mapped[Optional["Vector"]] = relationship(back_populates="qa_pair", uselist=False, cascade="all, delete-orphan")
236
+
237
+ class Vector(BaseModel):
238
+ chunk_id: Mapped[int] = mapped_column(ForeignKey("chunk.id"), nullable=True, unique=True)
239
+ qa_pair_id: Mapped[int] = mapped_column(ForeignKey("qa_pair.id"), nullable=True, unique=True)
240
+ model: Mapped[str] = mapped_column(String(100))
241
+ qdrant_point_id: Mapped[str | None] = mapped_column(String(100), unique=True)
242
+ vector_embeddings: Mapped[list[float] | None] = mapped_column(ARRAY(Float), nullable=True)
243
+ additional_data: Mapped[QdrantVectorPayload | None] = mapped_column(
244
+ PydanticType(QdrantVectorPayload),
245
+ nullable=True
246
+ )
247
+
248
+
249
+ chunk: Mapped["Chunk"] = relationship(back_populates="vector")
250
+ qa_pair: Mapped["QAPair"] = relationship(back_populates="vector")
@@ -0,0 +1,145 @@
1
+ import enum
2
+ from datetime import datetime
3
+ from typing import Any, Optional
4
+
5
+ from sqlalchemy import (
6
+ JSON,
7
+ TIMESTAMP,
8
+ Boolean,
9
+ Float,
10
+ ForeignKey,
11
+ Integer,
12
+ String,
13
+ Text,
14
+ func,
15
+ )
16
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
17
+
18
+ from prism_models.base import BaseModel
19
+ from prism_models.chat import Contact, ConversationMessage
20
+ from prism_models.content import Chunk, QAPair
21
+
22
+ # MessageFeedback model removed - using new Feedback model below
23
+
24
+
25
+ class FeedbackType(str, enum.Enum):
26
+ THUMBS_DOWN = "thumbs_down"
27
+ CORRECTION = "correction"
28
+ ENHANCEMENT = "enhancement"
29
+ DELETION = "deletion"
30
+ ACCURACY = "accuracy"
31
+ COMPLETENESS = "completeness"
32
+
33
+
34
+ class FeedbackStatus(str, enum.Enum):
35
+ PENDING = "pending"
36
+ APPROVED = "approved"
37
+ REJECTED = "rejected"
38
+ IMPLEMENTED = "implemented"
39
+
40
+
41
+ class FeedbackConfidence(str, enum.Enum):
42
+ LOW = "low"
43
+ MEDIUM = "medium"
44
+ HIGH = "high"
45
+ ABSOLUTE = "absolute"
46
+
47
+
48
+ class AugmentationAction(str, enum.Enum):
49
+ CREATE = "create"
50
+ CORRECT = "correct"
51
+ ENHANCE = "enhance"
52
+ DELETE = "delete"
53
+
54
+
55
+ class AugmentationStatus(str, enum.Enum):
56
+ PENDING = "pending"
57
+ COMPLETED = "completed"
58
+ FAILED = "failed"
59
+
60
+
61
+ class AnalysisType(str, enum.Enum):
62
+ CONTENT_ANALYSIS = "content_analysis"
63
+ IMPACT_ASSESSMENT = "impact_assessment"
64
+ ROOT_CAUSE_ANALYSIS = "root_cause_analysis"
65
+
66
+
67
+ class Feedback(BaseModel):
68
+ manager_id: Mapped[int] = mapped_column(Integer, ForeignKey("contact.id"), nullable=False, index=True)
69
+ chunk_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("chunk.id"), nullable=True, index=True)
70
+ qa_pair_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("qa_pair.id"), nullable=True, index=True)
71
+ # Link feedback to a specific conversation message
72
+ message_id: Mapped[int | None] = mapped_column(
73
+ Integer,
74
+ ForeignKey("conversation_message.id"),
75
+ nullable=True,
76
+ index=True,
77
+ )
78
+ assigned_reviewer_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("contact.id"), nullable=True, index=True)
79
+
80
+ authority_domain: Mapped[str | None] = mapped_column(String(255))
81
+ confidence_level: Mapped[FeedbackConfidence | None] = mapped_column(String(50))
82
+ feedback_type: Mapped[FeedbackType] = mapped_column(String(50), nullable=False)
83
+
84
+ # user query, agent response, correction note,
85
+ query: Mapped[str | None] = mapped_column(Text)
86
+ provided_response: Mapped[str | None] = mapped_column(Text)
87
+ correction: Mapped[str | None] = mapped_column(Text)
88
+ requires_review: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
89
+ status: Mapped[FeedbackStatus] = mapped_column(String(50), default=FeedbackStatus.PENDING, nullable=False, index=True)
90
+ admin_notes: Mapped[str | None] = mapped_column(Text)
91
+ reviewed_at: Mapped[datetime | None] = mapped_column(TIMESTAMP(timezone=True))
92
+ routed: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
93
+
94
+ # Relationships
95
+ manager: Mapped["Contact"] = relationship(foreign_keys=[manager_id])
96
+ chunk: Mapped[Optional["Chunk"]] = relationship()
97
+ qa_pair: Mapped[Optional["QAPair"]] = relationship()
98
+ message: Mapped[Optional["ConversationMessage"]] = relationship()
99
+ assigned_reviewer: Mapped[Optional["Contact"]] = relationship(foreign_keys=[assigned_reviewer_id])
100
+
101
+ analysis: Mapped[list["FeedbackAnalysis"]] = relationship(back_populates="feedback", cascade="all, delete-orphan")
102
+ augmentations: Mapped[list["Augmentation"]] = relationship(back_populates="feedback", cascade="all, delete-orphan")
103
+
104
+ def __repr__(self):
105
+ return f"<Feedback(id={self.id}, type='{self.feedback_type}', status='{self.status}')>"
106
+
107
+
108
+ class FeedbackAnalysis(BaseModel):
109
+ __tablename__ = "feedback_analysis"
110
+ feedback_id: Mapped[int] = mapped_column(Integer, ForeignKey("feedback.id"), nullable=False, index=True)
111
+
112
+ analysis_type: Mapped[AnalysisType] = mapped_column(String(50), nullable=False)
113
+ llm_response: Mapped[dict[str, Any] | None] = mapped_column(JSON)
114
+ confidence_score: Mapped[float | None] = mapped_column(Float)
115
+ model_used: Mapped[str | None] = mapped_column(String(255))
116
+ analyzed_at: Mapped[datetime] = mapped_column(TIMESTAMP(timezone=True), server_default=func.now(), nullable=False)
117
+ approved: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
118
+ reason_to_reject: Mapped[str | None] = mapped_column(Text)
119
+
120
+ # Relationships
121
+ feedback: Mapped["Feedback"] = relationship(back_populates="analysis")
122
+
123
+ def __repr__(self):
124
+ return f"<FeedbackAnalysis(id={self.id}, feedback_id={self.feedback_id}, type='{self.analysis_type}')>"
125
+
126
+
127
+ class Augmentation(BaseModel):
128
+ feedback_id: Mapped[int] = mapped_column(Integer, ForeignKey("feedback.id"), nullable=False, index=True)
129
+ original_chunk_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("chunk.id"), nullable=True)
130
+ original_qa_pair_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("qa_pair.id"), nullable=True)
131
+ generated_chunk_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("chunk.id"), nullable=True)
132
+ generated_qa_pair_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("qa_pair.id"), nullable=True)
133
+
134
+ action_type: Mapped[AugmentationAction] = mapped_column(String(50), nullable=False)
135
+ change_summary: Mapped[dict[str, Any] | None] = mapped_column(JSON)
136
+ status: Mapped[AugmentationStatus] = mapped_column(String(50), default=AugmentationStatus.PENDING, nullable=False, index=True)
137
+ # Relationships
138
+ feedback: Mapped["Feedback"] = relationship(back_populates="augmentations")
139
+ original_chunk: Mapped[Optional["Chunk"]] = relationship(foreign_keys=[original_chunk_id])
140
+ original_qa_pair: Mapped[Optional["QAPair"]] = relationship(foreign_keys=[original_qa_pair_id])
141
+ generated_chunk: Mapped[Optional["Chunk"]] = relationship(foreign_keys=[generated_chunk_id])
142
+ generated_qa_pair: Mapped[Optional["QAPair"]] = relationship(foreign_keys=[generated_qa_pair_id])
143
+
144
+ def __repr__(self):
145
+ return f"<Augmentation(id={self.id}, action='{self.action_type}', status='{self.status}')>"
@@ -0,0 +1 @@
1
+ Generic single-database configuration.
File without changes
@@ -0,0 +1,131 @@
1
+ import asyncio
2
+ import sys
3
+ from logging.config import fileConfig
4
+ from pathlib import Path
5
+
6
+ from alembic import context
7
+ from sqlalchemy import pool
8
+ from sqlalchemy.engine import Connection
9
+ from sqlalchemy.ext.asyncio import create_async_engine
10
+
11
+ # Add prism-models to Python path
12
+ project_root = Path(__file__).parent.parent
13
+ sys.path.insert(0, str(project_root))
14
+
15
+ # Import models and configuration - after path setup
16
+ from prism_models.config import settings
17
+ from prism_models.base import Base
18
+
19
+ # Import all models to ensure they're registered
20
+ import prism_models.agent_profile
21
+ import prism_models.chat
22
+ import prism_models.content
23
+ import prism_models.feedback
24
+
25
+ # this is the Alembic Config object, which provides
26
+ # access to the values within the .ini file in use.
27
+ config = context.config
28
+
29
+ # Interpret the config file for Python logging.
30
+ # This line sets up loggers basically.
31
+ if config.config_file_name is not None:
32
+ fileConfig(config.config_file_name)
33
+
34
+ # Set the SQLAlchemy URL from environment
35
+ if not config.get_main_option("sqlalchemy.url"):
36
+ config.set_main_option("sqlalchemy.url", settings.PG_DATABASE_URL_PRISM)
37
+
38
+ # Import all models to ensure they're registered with Base.metadata
39
+
40
+ # Add your model's MetaData object here for 'autogenerate' support
41
+ target_metadata = Base.metadata
42
+
43
+ # Other values from the config can be acquired as needed
44
+
45
+
46
+ def run_migrations_offline() -> None:
47
+ """Run migrations in 'offline' mode.
48
+
49
+ This configures the context with just a URL
50
+ and not an Engine, though an Engine is acceptable
51
+ here as well. By skipping the Engine creation
52
+ we don't even need a DBAPI to be available.
53
+
54
+ Calls to context.execute() here emit the given string to the
55
+ script output.
56
+
57
+ """
58
+ url = config.get_main_option("sqlalchemy.url")
59
+ context.configure(
60
+ url=url,
61
+ target_metadata=target_metadata,
62
+ literal_binds=True,
63
+ dialect_opts={"paramstyle": "named"},
64
+ compare_type=True, # Compare column types
65
+ compare_server_default=True, # Compare server defaults
66
+ include_schemas=True, # Include schema-specific tables
67
+ )
68
+
69
+ with context.begin_transaction():
70
+ context.run_migrations()
71
+
72
+
73
+ def do_run_migrations(connection: Connection) -> None:
74
+ """Run migrations with the given connection."""
75
+ context.configure(
76
+ connection=connection,
77
+ target_metadata=target_metadata,
78
+ compare_type=True, # Compare column types for better autogeneration
79
+ compare_server_default=True, # Compare server defaults
80
+ include_schemas=True, # Include schema-specific tables (chat schema)
81
+ render_as_batch=False, # PostgreSQL doesn't need batch mode
82
+ )
83
+
84
+ with context.begin_transaction():
85
+ context.run_migrations()
86
+
87
+
88
+ async def run_async_migrations() -> None:
89
+ """Run migrations in async mode for async engines."""
90
+ # Use async engine for PostgreSQL
91
+ connectable = create_async_engine(
92
+ settings.PG_DATABASE_URL_PRISM,
93
+ poolclass=pool.NullPool,
94
+ echo=settings.is_development,
95
+ )
96
+
97
+ async with connectable.connect() as connection:
98
+ await connection.run_sync(do_run_migrations)
99
+
100
+ await connectable.dispose()
101
+
102
+
103
+ def run_migrations_online() -> None:
104
+ """Run migrations in 'online' mode.
105
+
106
+ For async databases, we use run_async_migrations.
107
+ For sync databases, we use the traditional approach.
108
+ """
109
+ database_url = config.get_main_option("sqlalchemy.url")
110
+
111
+ if "asyncpg" in database_url or "aiosqlite" in database_url:
112
+ # Async database - use async migrations
113
+ asyncio.run(run_async_migrations())
114
+ else:
115
+ # Sync database - use traditional approach
116
+ from sqlalchemy import engine_from_config
117
+
118
+ connectable = engine_from_config(
119
+ config.get_section(config.config_ini_section, {}),
120
+ prefix="sqlalchemy.",
121
+ poolclass=pool.NullPool,
122
+ )
123
+
124
+ with connectable.connect() as connection:
125
+ do_run_migrations(connection)
126
+
127
+
128
+ if context.is_offline_mode():
129
+ run_migrations_offline()
130
+ else:
131
+ run_migrations_online()
@@ -0,0 +1,28 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ ${imports if imports else ""}
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = ${repr(up_revision)}
16
+ down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
17
+ branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
18
+ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
19
+
20
+
21
+ def upgrade() -> None:
22
+ """Upgrade schema."""
23
+ ${upgrades if upgrades else "pass"}
24
+
25
+
26
+ def downgrade() -> None:
27
+ """Downgrade schema."""
28
+ ${downgrades if downgrades else "pass"}