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.
- prism_models/__init__.py +45 -0
- prism_models/agent_profile.py +101 -0
- prism_models/base.py +75 -0
- prism_models/chat.py +349 -0
- prism_models/config.py +37 -0
- prism_models/content.py +250 -0
- prism_models/feedback.py +145 -0
- prism_models/migration/README +1 -0
- prism_models/migration/__init__.py +0 -0
- prism_models/migration/env.py +131 -0
- prism_models/migration/script.py.mako +28 -0
- prism_models/migration/versions/2025_09_11_1516_161f8829d93f_initial_schema.py +492 -0
- prism_models/migration/versions/2025_09_11_1558_5e011849ea76_changes_for_feedback.py +79 -0
- prism_models/migration/versions/2025_09_14_2243_059af231c2b2_profile_entities.py +108 -0
- prism_models/migration/versions/2025_09_15_1646_3219fec0bb10_agent_changes.py +32 -0
- prism_models/migration/versions/2025_09_16_1627_f2013b08daac_rename_metadata_to_additional_data.py +37 -0
- prism_models/migration/versions/2025_09_17_1147_327febbf555f_display_name_added.py +34 -0
- prism_models/migration/versions/2025_09_18_1106_b0bcb7ca1dc9_add_support_for_profile_on_create_convo.py +41 -0
- prism_models/migration/versions/2025_09_18_1511_bbc1955191e6_preview_mode.py +36 -0
- prism_models/migration/versions/2025_09_26_1115_6eb70e848451_added_publish_status_to_document.py +38 -0
- prism_models/migration/versions/2025_09_26_1240_f8b0ea2e743c_drop_unique_title_version_on_document.py +40 -0
- prism_models/migration/versions/2025_09_26_1505_07dc8c2589e0_added_chunk_id_and_vector_embeddings_to_.py +44 -0
- prism_models/migration/versions/2025_09_29_1220_46ba2693b883_add_markdown_markdown_file_path_s3_.py +32 -0
- prism_models/migration/versions/2025_10_02_1520_bf1472a9b021_removed_doc_id_from_config_table_and_.py +34 -0
- prism_models/migration/versions/2025_10_02_1525_6c0e63e0fef8_removed_doc_id_from_config_table_.py +34 -0
- prism_models/migration/versions/2025_10_02_1608_1b3eb48f5017_config_id_on_delete_will_be_set_to_null.py +34 -0
- prism_models/migration/versions/2025_10_03_1109_ac85b606d8a4_added_docling_hybrid_to_chunkstrategy.py +32 -0
- prism_models/migration/versions/2025_10_03_1204_7d1cb343a63f_added_s3_bucket_and_s3_dir_in_source_.py +42 -0
- prism_models/migration/versions/2025_10_03_1452_f9c750ec2a0b_1_to_1_relationship_between_collection_.py +52 -0
- prism_models/migration/versions/2025_10_07_1722_5cfa0c462948_added_travel_advisory_enum.py +38 -0
- prism_models/migration/versions/2025_10_08_1304_c91eb8e38cc7_added_destination_report_and_event_.py +38 -0
- prism_models/migration/versions/2025_10_09_1308_796b720ea35f_added_qa_id_to_vovetor.py +42 -0
- prism_models/migration/versions/2025_10_16_1611_663c66268631_added_sharepoint_drive_item_id_as_an_.py +32 -0
- prism_models/qdrant.py +97 -0
- prism_models-0.1.0.dist-info/METADATA +10 -0
- prism_models-0.1.0.dist-info/RECORD +38 -0
- prism_models-0.1.0.dist-info/WHEEL +5 -0
- prism_models-0.1.0.dist-info/top_level.txt +1 -0
prism_models/content.py
ADDED
|
@@ -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")
|
prism_models/feedback.py
ADDED
|
@@ -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"}
|