truthound-dashboard 1.0.2__py3-none-any.whl → 1.2.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.
- truthound_dashboard/api/catalog.py +343 -0
- truthound_dashboard/api/collaboration.py +148 -0
- truthound_dashboard/api/glossary.py +329 -0
- truthound_dashboard/api/router.py +29 -0
- truthound_dashboard/cli.py +397 -0
- truthound_dashboard/core/__init__.py +12 -0
- truthound_dashboard/core/phase5/__init__.py +17 -0
- truthound_dashboard/core/phase5/activity.py +144 -0
- truthound_dashboard/core/phase5/catalog.py +868 -0
- truthound_dashboard/core/phase5/collaboration.py +305 -0
- truthound_dashboard/core/phase5/glossary.py +828 -0
- truthound_dashboard/db/__init__.py +37 -0
- truthound_dashboard/db/models.py +693 -0
- truthound_dashboard/schemas/__init__.py +114 -0
- truthound_dashboard/schemas/catalog.py +352 -0
- truthound_dashboard/schemas/collaboration.py +169 -0
- truthound_dashboard/schemas/glossary.py +349 -0
- truthound_dashboard/translate/__init__.py +61 -0
- truthound_dashboard/translate/config_updater.py +327 -0
- truthound_dashboard/translate/exceptions.py +98 -0
- truthound_dashboard/translate/providers/__init__.py +49 -0
- truthound_dashboard/translate/providers/anthropic.py +135 -0
- truthound_dashboard/translate/providers/base.py +225 -0
- truthound_dashboard/translate/providers/mistral.py +138 -0
- truthound_dashboard/translate/providers/ollama.py +226 -0
- truthound_dashboard/translate/providers/openai.py +187 -0
- truthound_dashboard/translate/providers/registry.py +217 -0
- truthound_dashboard/translate/translator.py +443 -0
- {truthound_dashboard-1.0.2.dist-info → truthound_dashboard-1.2.0.dist-info}/METADATA +123 -4
- {truthound_dashboard-1.0.2.dist-info → truthound_dashboard-1.2.0.dist-info}/RECORD +33 -11
- {truthound_dashboard-1.0.2.dist-info → truthound_dashboard-1.2.0.dist-info}/WHEEL +0 -0
- {truthound_dashboard-1.0.2.dist-info → truthound_dashboard-1.2.0.dist-info}/entry_points.txt +0 -0
- {truthound_dashboard-1.0.2.dist-info → truthound_dashboard-1.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"""Pydantic schemas for Business Glossary API.
|
|
2
|
+
|
|
3
|
+
This module defines request/response schemas for glossary terms,
|
|
4
|
+
categories, and relationships.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from enum import Enum
|
|
11
|
+
|
|
12
|
+
from pydantic import Field, field_validator
|
|
13
|
+
|
|
14
|
+
from .base import BaseSchema, IDMixin, ListResponseWrapper, TimestampMixin
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# =============================================================================
|
|
18
|
+
# Enums
|
|
19
|
+
# =============================================================================
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TermStatus(str, Enum):
|
|
23
|
+
"""Status of a glossary term."""
|
|
24
|
+
|
|
25
|
+
DRAFT = "draft"
|
|
26
|
+
APPROVED = "approved"
|
|
27
|
+
DEPRECATED = "deprecated"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class RelationshipType(str, Enum):
|
|
31
|
+
"""Type of relationship between terms."""
|
|
32
|
+
|
|
33
|
+
SYNONYM = "synonym"
|
|
34
|
+
RELATED = "related"
|
|
35
|
+
PARENT = "parent"
|
|
36
|
+
CHILD = "child"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# =============================================================================
|
|
40
|
+
# Category Schemas
|
|
41
|
+
# =============================================================================
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class CategoryBase(BaseSchema):
|
|
45
|
+
"""Base schema for glossary categories."""
|
|
46
|
+
|
|
47
|
+
name: str = Field(..., min_length=1, max_length=255, description="Category name")
|
|
48
|
+
description: str | None = Field(None, description="Category description")
|
|
49
|
+
parent_id: str | None = Field(None, description="Parent category ID")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class CategoryCreate(CategoryBase):
|
|
53
|
+
"""Schema for creating a glossary category."""
|
|
54
|
+
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class CategoryUpdate(BaseSchema):
|
|
59
|
+
"""Schema for updating a glossary category."""
|
|
60
|
+
|
|
61
|
+
name: str | None = Field(None, min_length=1, max_length=255)
|
|
62
|
+
description: str | None = None
|
|
63
|
+
parent_id: str | None = None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class CategorySummary(BaseSchema, IDMixin):
|
|
67
|
+
"""Summary schema for category references."""
|
|
68
|
+
|
|
69
|
+
name: str
|
|
70
|
+
full_path: str | None = None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class CategoryResponse(BaseSchema, IDMixin, TimestampMixin):
|
|
74
|
+
"""Response schema for a glossary category."""
|
|
75
|
+
|
|
76
|
+
name: str
|
|
77
|
+
description: str | None
|
|
78
|
+
parent_id: str | None
|
|
79
|
+
parent: CategorySummary | None = None
|
|
80
|
+
term_count: int = 0
|
|
81
|
+
full_path: str
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def from_model(cls, category: any) -> CategoryResponse:
|
|
85
|
+
"""Create response from model."""
|
|
86
|
+
parent_summary = None
|
|
87
|
+
if category.parent:
|
|
88
|
+
parent_summary = CategorySummary(
|
|
89
|
+
id=category.parent.id,
|
|
90
|
+
name=category.parent.name,
|
|
91
|
+
full_path=category.parent.full_path,
|
|
92
|
+
)
|
|
93
|
+
return cls(
|
|
94
|
+
id=category.id,
|
|
95
|
+
name=category.name,
|
|
96
|
+
description=category.description,
|
|
97
|
+
parent_id=category.parent_id,
|
|
98
|
+
parent=parent_summary,
|
|
99
|
+
term_count=category.term_count,
|
|
100
|
+
full_path=category.full_path,
|
|
101
|
+
created_at=category.created_at,
|
|
102
|
+
updated_at=category.updated_at,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class CategoryListResponse(ListResponseWrapper[CategoryResponse]):
|
|
107
|
+
"""Paginated list of categories."""
|
|
108
|
+
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# =============================================================================
|
|
113
|
+
# Term Schemas
|
|
114
|
+
# =============================================================================
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class TermBase(BaseSchema):
|
|
118
|
+
"""Base schema for glossary terms."""
|
|
119
|
+
|
|
120
|
+
name: str = Field(..., min_length=1, max_length=255, description="Term name")
|
|
121
|
+
definition: str = Field(..., min_length=1, description="Term definition")
|
|
122
|
+
category_id: str | None = Field(None, description="Category ID")
|
|
123
|
+
status: TermStatus = Field(default=TermStatus.DRAFT, description="Term status")
|
|
124
|
+
owner_id: str | None = Field(None, description="Owner identifier")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class TermCreate(TermBase):
|
|
128
|
+
"""Schema for creating a glossary term."""
|
|
129
|
+
|
|
130
|
+
@field_validator("name")
|
|
131
|
+
@classmethod
|
|
132
|
+
def validate_name(cls, v: str) -> str:
|
|
133
|
+
"""Validate term name."""
|
|
134
|
+
return v.strip()
|
|
135
|
+
|
|
136
|
+
@field_validator("definition")
|
|
137
|
+
@classmethod
|
|
138
|
+
def validate_definition(cls, v: str) -> str:
|
|
139
|
+
"""Validate term definition."""
|
|
140
|
+
return v.strip()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class TermUpdate(BaseSchema):
|
|
144
|
+
"""Schema for updating a glossary term."""
|
|
145
|
+
|
|
146
|
+
name: str | None = Field(None, min_length=1, max_length=255)
|
|
147
|
+
definition: str | None = Field(None, min_length=1)
|
|
148
|
+
category_id: str | None = None
|
|
149
|
+
status: TermStatus | None = None
|
|
150
|
+
owner_id: str | None = None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class TermSummary(BaseSchema, IDMixin):
|
|
154
|
+
"""Summary schema for term references."""
|
|
155
|
+
|
|
156
|
+
name: str
|
|
157
|
+
status: TermStatus
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class RelatedTermSummary(BaseSchema, IDMixin):
|
|
161
|
+
"""Summary for related terms with relationship type."""
|
|
162
|
+
|
|
163
|
+
name: str
|
|
164
|
+
status: TermStatus
|
|
165
|
+
relationship_type: RelationshipType
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class TermResponse(BaseSchema, IDMixin, TimestampMixin):
|
|
169
|
+
"""Response schema for a glossary term."""
|
|
170
|
+
|
|
171
|
+
name: str
|
|
172
|
+
definition: str
|
|
173
|
+
category_id: str | None
|
|
174
|
+
category: CategorySummary | None = None
|
|
175
|
+
status: TermStatus
|
|
176
|
+
owner_id: str | None
|
|
177
|
+
synonyms: list[TermSummary] = Field(default_factory=list)
|
|
178
|
+
related_terms: list[TermSummary] = Field(default_factory=list)
|
|
179
|
+
mapped_column_count: int = 0
|
|
180
|
+
|
|
181
|
+
@classmethod
|
|
182
|
+
def from_model(cls, term: any) -> TermResponse:
|
|
183
|
+
"""Create response from model."""
|
|
184
|
+
category_summary = None
|
|
185
|
+
if term.category:
|
|
186
|
+
category_summary = CategorySummary(
|
|
187
|
+
id=term.category.id,
|
|
188
|
+
name=term.category.name,
|
|
189
|
+
full_path=term.category.full_path,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
synonyms = [
|
|
193
|
+
TermSummary(id=t.id, name=t.name, status=TermStatus(t.status))
|
|
194
|
+
for t in term.synonyms
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
related = [
|
|
198
|
+
TermSummary(id=t.id, name=t.name, status=TermStatus(t.status))
|
|
199
|
+
for t in term.related_terms
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
return cls(
|
|
203
|
+
id=term.id,
|
|
204
|
+
name=term.name,
|
|
205
|
+
definition=term.definition,
|
|
206
|
+
category_id=term.category_id,
|
|
207
|
+
category=category_summary,
|
|
208
|
+
status=TermStatus(term.status),
|
|
209
|
+
owner_id=term.owner_id,
|
|
210
|
+
synonyms=synonyms,
|
|
211
|
+
related_terms=related,
|
|
212
|
+
mapped_column_count=len(term.mapped_columns),
|
|
213
|
+
created_at=term.created_at,
|
|
214
|
+
updated_at=term.updated_at,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class TermListItem(BaseSchema, IDMixin, TimestampMixin):
|
|
219
|
+
"""List item schema for terms (lighter than full response)."""
|
|
220
|
+
|
|
221
|
+
name: str
|
|
222
|
+
definition: str
|
|
223
|
+
category_id: str | None
|
|
224
|
+
category_name: str | None = None
|
|
225
|
+
status: TermStatus
|
|
226
|
+
owner_id: str | None
|
|
227
|
+
synonym_count: int = 0
|
|
228
|
+
related_count: int = 0
|
|
229
|
+
|
|
230
|
+
@classmethod
|
|
231
|
+
def from_model(cls, term: any) -> TermListItem:
|
|
232
|
+
"""Create list item from model."""
|
|
233
|
+
return cls(
|
|
234
|
+
id=term.id,
|
|
235
|
+
name=term.name,
|
|
236
|
+
definition=term.definition,
|
|
237
|
+
category_id=term.category_id,
|
|
238
|
+
category_name=term.category.name if term.category else None,
|
|
239
|
+
status=TermStatus(term.status),
|
|
240
|
+
owner_id=term.owner_id,
|
|
241
|
+
synonym_count=len(term.synonyms),
|
|
242
|
+
related_count=len(term.related_terms),
|
|
243
|
+
created_at=term.created_at,
|
|
244
|
+
updated_at=term.updated_at,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class TermListResponse(ListResponseWrapper[TermListItem]):
|
|
249
|
+
"""Paginated list of terms."""
|
|
250
|
+
|
|
251
|
+
pass
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
# =============================================================================
|
|
255
|
+
# Relationship Schemas
|
|
256
|
+
# =============================================================================
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class RelationshipBase(BaseSchema):
|
|
260
|
+
"""Base schema for term relationships."""
|
|
261
|
+
|
|
262
|
+
source_term_id: str = Field(..., description="Source term ID")
|
|
263
|
+
target_term_id: str = Field(..., description="Target term ID")
|
|
264
|
+
relationship_type: RelationshipType = Field(..., description="Relationship type")
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class RelationshipCreate(RelationshipBase):
|
|
268
|
+
"""Schema for creating a term relationship."""
|
|
269
|
+
|
|
270
|
+
@field_validator("target_term_id")
|
|
271
|
+
@classmethod
|
|
272
|
+
def validate_different_terms(cls, v: str, info) -> str:
|
|
273
|
+
"""Validate that source and target are different."""
|
|
274
|
+
if info.data.get("source_term_id") == v:
|
|
275
|
+
raise ValueError("Source and target terms must be different")
|
|
276
|
+
return v
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class RelationshipResponse(BaseSchema, IDMixin):
|
|
280
|
+
"""Response schema for a term relationship."""
|
|
281
|
+
|
|
282
|
+
source_term_id: str
|
|
283
|
+
target_term_id: str
|
|
284
|
+
source_term: TermSummary
|
|
285
|
+
target_term: TermSummary
|
|
286
|
+
relationship_type: RelationshipType
|
|
287
|
+
created_at: datetime
|
|
288
|
+
|
|
289
|
+
@classmethod
|
|
290
|
+
def from_model(cls, rel: any) -> RelationshipResponse:
|
|
291
|
+
"""Create response from model."""
|
|
292
|
+
return cls(
|
|
293
|
+
id=rel.id,
|
|
294
|
+
source_term_id=rel.source_term_id,
|
|
295
|
+
target_term_id=rel.target_term_id,
|
|
296
|
+
source_term=TermSummary(
|
|
297
|
+
id=rel.source_term.id,
|
|
298
|
+
name=rel.source_term.name,
|
|
299
|
+
status=TermStatus(rel.source_term.status),
|
|
300
|
+
),
|
|
301
|
+
target_term=TermSummary(
|
|
302
|
+
id=rel.target_term.id,
|
|
303
|
+
name=rel.target_term.name,
|
|
304
|
+
status=TermStatus(rel.target_term.status),
|
|
305
|
+
),
|
|
306
|
+
relationship_type=RelationshipType(rel.relationship_type),
|
|
307
|
+
created_at=rel.created_at,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class RelationshipListResponse(ListResponseWrapper[RelationshipResponse]):
|
|
312
|
+
"""List of relationships."""
|
|
313
|
+
|
|
314
|
+
pass
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
# =============================================================================
|
|
318
|
+
# History Schemas
|
|
319
|
+
# =============================================================================
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
class TermHistoryResponse(BaseSchema, IDMixin):
|
|
323
|
+
"""Response schema for term change history."""
|
|
324
|
+
|
|
325
|
+
term_id: str
|
|
326
|
+
field_name: str
|
|
327
|
+
old_value: str | None
|
|
328
|
+
new_value: str | None
|
|
329
|
+
changed_by: str | None
|
|
330
|
+
changed_at: datetime
|
|
331
|
+
|
|
332
|
+
@classmethod
|
|
333
|
+
def from_model(cls, history: any) -> TermHistoryResponse:
|
|
334
|
+
"""Create response from model."""
|
|
335
|
+
return cls(
|
|
336
|
+
id=history.id,
|
|
337
|
+
term_id=history.term_id,
|
|
338
|
+
field_name=history.field_name,
|
|
339
|
+
old_value=history.old_value,
|
|
340
|
+
new_value=history.new_value,
|
|
341
|
+
changed_by=history.changed_by,
|
|
342
|
+
changed_at=history.changed_at,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
class TermHistoryListResponse(ListResponseWrapper[TermHistoryResponse]):
|
|
347
|
+
"""List of term history entries."""
|
|
348
|
+
|
|
349
|
+
pass
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Translation module for truthound-dashboard.
|
|
2
|
+
|
|
3
|
+
This module provides AI-powered translation capabilities for Intlayer content files.
|
|
4
|
+
It supports multiple AI providers and can automatically detect available providers
|
|
5
|
+
based on environment variables.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
# CLI usage
|
|
9
|
+
truthound translate -l ja,zh,de -p openai
|
|
10
|
+
|
|
11
|
+
# Auto-detect provider
|
|
12
|
+
truthound translate -l ja,zh
|
|
13
|
+
|
|
14
|
+
Supported Providers:
|
|
15
|
+
- OpenAI (GPT-4, GPT-3.5)
|
|
16
|
+
- Anthropic (Claude 3)
|
|
17
|
+
- Ollama (local LLM, no API key required)
|
|
18
|
+
- Mistral
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from truthound_dashboard.translate.providers import (
|
|
22
|
+
AIProvider,
|
|
23
|
+
ProviderConfig,
|
|
24
|
+
ProviderRegistry,
|
|
25
|
+
get_provider,
|
|
26
|
+
detect_provider,
|
|
27
|
+
list_available_providers,
|
|
28
|
+
)
|
|
29
|
+
from truthound_dashboard.translate.translator import (
|
|
30
|
+
ContentTranslator,
|
|
31
|
+
TranslationResult,
|
|
32
|
+
)
|
|
33
|
+
from truthound_dashboard.translate.config_updater import (
|
|
34
|
+
IntlayerConfigUpdater,
|
|
35
|
+
)
|
|
36
|
+
from truthound_dashboard.translate.exceptions import (
|
|
37
|
+
TranslationError,
|
|
38
|
+
ProviderNotFoundError,
|
|
39
|
+
APIKeyNotFoundError,
|
|
40
|
+
TranslationAPIError,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
# Providers
|
|
45
|
+
"AIProvider",
|
|
46
|
+
"ProviderConfig",
|
|
47
|
+
"ProviderRegistry",
|
|
48
|
+
"get_provider",
|
|
49
|
+
"detect_provider",
|
|
50
|
+
"list_available_providers",
|
|
51
|
+
# Translator
|
|
52
|
+
"ContentTranslator",
|
|
53
|
+
"TranslationResult",
|
|
54
|
+
# Config
|
|
55
|
+
"IntlayerConfigUpdater",
|
|
56
|
+
# Exceptions
|
|
57
|
+
"TranslationError",
|
|
58
|
+
"ProviderNotFoundError",
|
|
59
|
+
"APIKeyNotFoundError",
|
|
60
|
+
"TranslationAPIError",
|
|
61
|
+
]
|