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.
Files changed (33) hide show
  1. truthound_dashboard/api/catalog.py +343 -0
  2. truthound_dashboard/api/collaboration.py +148 -0
  3. truthound_dashboard/api/glossary.py +329 -0
  4. truthound_dashboard/api/router.py +29 -0
  5. truthound_dashboard/cli.py +397 -0
  6. truthound_dashboard/core/__init__.py +12 -0
  7. truthound_dashboard/core/phase5/__init__.py +17 -0
  8. truthound_dashboard/core/phase5/activity.py +144 -0
  9. truthound_dashboard/core/phase5/catalog.py +868 -0
  10. truthound_dashboard/core/phase5/collaboration.py +305 -0
  11. truthound_dashboard/core/phase5/glossary.py +828 -0
  12. truthound_dashboard/db/__init__.py +37 -0
  13. truthound_dashboard/db/models.py +693 -0
  14. truthound_dashboard/schemas/__init__.py +114 -0
  15. truthound_dashboard/schemas/catalog.py +352 -0
  16. truthound_dashboard/schemas/collaboration.py +169 -0
  17. truthound_dashboard/schemas/glossary.py +349 -0
  18. truthound_dashboard/translate/__init__.py +61 -0
  19. truthound_dashboard/translate/config_updater.py +327 -0
  20. truthound_dashboard/translate/exceptions.py +98 -0
  21. truthound_dashboard/translate/providers/__init__.py +49 -0
  22. truthound_dashboard/translate/providers/anthropic.py +135 -0
  23. truthound_dashboard/translate/providers/base.py +225 -0
  24. truthound_dashboard/translate/providers/mistral.py +138 -0
  25. truthound_dashboard/translate/providers/ollama.py +226 -0
  26. truthound_dashboard/translate/providers/openai.py +187 -0
  27. truthound_dashboard/translate/providers/registry.py +217 -0
  28. truthound_dashboard/translate/translator.py +443 -0
  29. {truthound_dashboard-1.0.2.dist-info → truthound_dashboard-1.2.0.dist-info}/METADATA +123 -4
  30. {truthound_dashboard-1.0.2.dist-info → truthound_dashboard-1.2.0.dist-info}/RECORD +33 -11
  31. {truthound_dashboard-1.0.2.dist-info → truthound_dashboard-1.2.0.dist-info}/WHEEL +0 -0
  32. {truthound_dashboard-1.0.2.dist-info → truthound_dashboard-1.2.0.dist-info}/entry_points.txt +0 -0
  33. {truthound_dashboard-1.0.2.dist-info → truthound_dashboard-1.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,329 @@
1
+ """Glossary API endpoints.
2
+
3
+ This module provides REST API endpoints for managing business glossary
4
+ terms, categories, and relationships.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Annotated
10
+
11
+ from fastapi import APIRouter, Depends, HTTPException, Query, status
12
+
13
+ from truthound_dashboard.core.phase5 import GlossaryService
14
+ from truthound_dashboard.schemas import (
15
+ CategoryCreate,
16
+ CategoryListResponse,
17
+ CategoryResponse,
18
+ CategoryUpdate,
19
+ MessageResponse,
20
+ RelationshipCreate,
21
+ RelationshipListResponse,
22
+ RelationshipResponse,
23
+ TermCreate,
24
+ TermHistoryListResponse,
25
+ TermHistoryResponse,
26
+ TermListItem,
27
+ TermListResponse,
28
+ TermResponse,
29
+ TermUpdate,
30
+ )
31
+
32
+ from .deps import SessionDep
33
+
34
+ router = APIRouter()
35
+
36
+
37
+ # =============================================================================
38
+ # Dependencies
39
+ # =============================================================================
40
+
41
+
42
+ async def get_glossary_service(session: SessionDep) -> GlossaryService:
43
+ """Get glossary service dependency."""
44
+ return GlossaryService(session)
45
+
46
+
47
+ GlossaryServiceDep = Annotated[GlossaryService, Depends(get_glossary_service)]
48
+
49
+
50
+ # =============================================================================
51
+ # Term Endpoints
52
+ # =============================================================================
53
+
54
+
55
+ @router.get("/terms", response_model=TermListResponse)
56
+ async def list_terms(
57
+ service: GlossaryServiceDep,
58
+ search: Annotated[str | None, Query(description="Search query")] = None,
59
+ category_id: Annotated[str | None, Query(description="Filter by category")] = None,
60
+ status: Annotated[str | None, Query(description="Filter by status")] = None,
61
+ offset: Annotated[int, Query(ge=0)] = 0,
62
+ limit: Annotated[int, Query(ge=1, le=100)] = 100,
63
+ ) -> TermListResponse:
64
+ """List glossary terms with optional filters.
65
+
66
+ - **search**: Search in term name and definition
67
+ - **category_id**: Filter by category
68
+ - **status**: Filter by status (draft, approved, deprecated)
69
+ """
70
+ terms, total = await service.list_terms(
71
+ query=search,
72
+ category_id=category_id,
73
+ status=status,
74
+ offset=offset,
75
+ limit=limit,
76
+ )
77
+ return TermListResponse(
78
+ data=[TermListItem.from_model(t) for t in terms],
79
+ total=total,
80
+ offset=offset,
81
+ limit=limit,
82
+ )
83
+
84
+
85
+ @router.post("/terms", response_model=TermResponse, status_code=status.HTTP_201_CREATED)
86
+ async def create_term(
87
+ service: GlossaryServiceDep,
88
+ data: TermCreate,
89
+ ) -> TermResponse:
90
+ """Create a new glossary term."""
91
+ try:
92
+ term = await service.create_term(
93
+ name=data.name,
94
+ definition=data.definition,
95
+ category_id=data.category_id,
96
+ status=data.status.value,
97
+ owner_id=data.owner_id,
98
+ )
99
+ return TermResponse.from_model(term)
100
+ except ValueError as e:
101
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
102
+
103
+
104
+ @router.get("/terms/{term_id}", response_model=TermResponse)
105
+ async def get_term(
106
+ service: GlossaryServiceDep,
107
+ term_id: str,
108
+ ) -> TermResponse:
109
+ """Get a glossary term by ID."""
110
+ term = await service.get_term(term_id)
111
+ if not term:
112
+ raise HTTPException(
113
+ status_code=status.HTTP_404_NOT_FOUND,
114
+ detail=f"Term '{term_id}' not found",
115
+ )
116
+ return TermResponse.from_model(term)
117
+
118
+
119
+ @router.put("/terms/{term_id}", response_model=TermResponse)
120
+ async def update_term(
121
+ service: GlossaryServiceDep,
122
+ term_id: str,
123
+ data: TermUpdate,
124
+ ) -> TermResponse:
125
+ """Update a glossary term."""
126
+ try:
127
+ term = await service.update_term(
128
+ term_id,
129
+ name=data.name,
130
+ definition=data.definition,
131
+ category_id=data.category_id,
132
+ status=data.status.value if data.status else None,
133
+ owner_id=data.owner_id,
134
+ )
135
+ if not term:
136
+ raise HTTPException(
137
+ status_code=status.HTTP_404_NOT_FOUND,
138
+ detail=f"Term '{term_id}' not found",
139
+ )
140
+ return TermResponse.from_model(term)
141
+ except ValueError as e:
142
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
143
+
144
+
145
+ @router.delete("/terms/{term_id}", response_model=MessageResponse)
146
+ async def delete_term(
147
+ service: GlossaryServiceDep,
148
+ term_id: str,
149
+ ) -> MessageResponse:
150
+ """Delete a glossary term."""
151
+ deleted = await service.delete_term(term_id)
152
+ if not deleted:
153
+ raise HTTPException(
154
+ status_code=status.HTTP_404_NOT_FOUND,
155
+ detail=f"Term '{term_id}' not found",
156
+ )
157
+ return MessageResponse(message="Term deleted successfully")
158
+
159
+
160
+ @router.get("/terms/{term_id}/history", response_model=TermHistoryListResponse)
161
+ async def get_term_history(
162
+ service: GlossaryServiceDep,
163
+ term_id: str,
164
+ limit: Annotated[int, Query(ge=1, le=100)] = 50,
165
+ ) -> TermHistoryListResponse:
166
+ """Get change history for a term."""
167
+ # Verify term exists
168
+ term = await service.get_term(term_id)
169
+ if not term:
170
+ raise HTTPException(
171
+ status_code=status.HTTP_404_NOT_FOUND,
172
+ detail=f"Term '{term_id}' not found",
173
+ )
174
+
175
+ history = await service.get_term_history(term_id, limit=limit)
176
+ return TermHistoryListResponse(
177
+ data=[TermHistoryResponse.from_model(h) for h in history],
178
+ total=len(history),
179
+ )
180
+
181
+
182
+ # =============================================================================
183
+ # Category Endpoints
184
+ # =============================================================================
185
+
186
+
187
+ @router.get("/categories", response_model=CategoryListResponse)
188
+ async def list_categories(
189
+ service: GlossaryServiceDep,
190
+ offset: Annotated[int, Query(ge=0)] = 0,
191
+ limit: Annotated[int, Query(ge=1, le=100)] = 100,
192
+ ) -> CategoryListResponse:
193
+ """List all glossary categories."""
194
+ categories, total = await service.list_categories(offset=offset, limit=limit)
195
+ return CategoryListResponse(
196
+ data=[CategoryResponse.from_model(c) for c in categories],
197
+ total=total,
198
+ offset=offset,
199
+ limit=limit,
200
+ )
201
+
202
+
203
+ @router.post("/categories", response_model=CategoryResponse, status_code=status.HTTP_201_CREATED)
204
+ async def create_category(
205
+ service: GlossaryServiceDep,
206
+ data: CategoryCreate,
207
+ ) -> CategoryResponse:
208
+ """Create a new glossary category."""
209
+ try:
210
+ category = await service.create_category(
211
+ name=data.name,
212
+ description=data.description,
213
+ parent_id=data.parent_id,
214
+ )
215
+ return CategoryResponse.from_model(category)
216
+ except ValueError as e:
217
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
218
+
219
+
220
+ @router.get("/categories/{category_id}", response_model=CategoryResponse)
221
+ async def get_category(
222
+ service: GlossaryServiceDep,
223
+ category_id: str,
224
+ ) -> CategoryResponse:
225
+ """Get a glossary category by ID."""
226
+ category = await service.get_category(category_id)
227
+ if not category:
228
+ raise HTTPException(
229
+ status_code=status.HTTP_404_NOT_FOUND,
230
+ detail=f"Category '{category_id}' not found",
231
+ )
232
+ return CategoryResponse.from_model(category)
233
+
234
+
235
+ @router.put("/categories/{category_id}", response_model=CategoryResponse)
236
+ async def update_category(
237
+ service: GlossaryServiceDep,
238
+ category_id: str,
239
+ data: CategoryUpdate,
240
+ ) -> CategoryResponse:
241
+ """Update a glossary category."""
242
+ try:
243
+ category = await service.update_category(
244
+ category_id,
245
+ name=data.name,
246
+ description=data.description,
247
+ parent_id=data.parent_id,
248
+ )
249
+ if not category:
250
+ raise HTTPException(
251
+ status_code=status.HTTP_404_NOT_FOUND,
252
+ detail=f"Category '{category_id}' not found",
253
+ )
254
+ return CategoryResponse.from_model(category)
255
+ except ValueError as e:
256
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
257
+
258
+
259
+ @router.delete("/categories/{category_id}", response_model=MessageResponse)
260
+ async def delete_category(
261
+ service: GlossaryServiceDep,
262
+ category_id: str,
263
+ ) -> MessageResponse:
264
+ """Delete a glossary category."""
265
+ deleted = await service.delete_category(category_id)
266
+ if not deleted:
267
+ raise HTTPException(
268
+ status_code=status.HTTP_404_NOT_FOUND,
269
+ detail=f"Category '{category_id}' not found",
270
+ )
271
+ return MessageResponse(message="Category deleted successfully")
272
+
273
+
274
+ # =============================================================================
275
+ # Relationship Endpoints
276
+ # =============================================================================
277
+
278
+
279
+ @router.get("/terms/{term_id}/relationships", response_model=RelationshipListResponse)
280
+ async def get_term_relationships(
281
+ service: GlossaryServiceDep,
282
+ term_id: str,
283
+ ) -> RelationshipListResponse:
284
+ """Get all relationships for a term."""
285
+ # Verify term exists
286
+ term = await service.get_term(term_id)
287
+ if not term:
288
+ raise HTTPException(
289
+ status_code=status.HTTP_404_NOT_FOUND,
290
+ detail=f"Term '{term_id}' not found",
291
+ )
292
+
293
+ relationships = await service.get_term_relationships(term_id)
294
+ return RelationshipListResponse(
295
+ data=[RelationshipResponse.from_model(r) for r in relationships],
296
+ total=len(relationships),
297
+ )
298
+
299
+
300
+ @router.post("/relationships", response_model=RelationshipResponse, status_code=status.HTTP_201_CREATED)
301
+ async def create_relationship(
302
+ service: GlossaryServiceDep,
303
+ data: RelationshipCreate,
304
+ ) -> RelationshipResponse:
305
+ """Create a relationship between terms."""
306
+ try:
307
+ relationship = await service.create_relationship(
308
+ source_term_id=data.source_term_id,
309
+ target_term_id=data.target_term_id,
310
+ relationship_type=data.relationship_type.value,
311
+ )
312
+ return RelationshipResponse.from_model(relationship)
313
+ except ValueError as e:
314
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
315
+
316
+
317
+ @router.delete("/relationships/{relationship_id}", response_model=MessageResponse)
318
+ async def delete_relationship(
319
+ service: GlossaryServiceDep,
320
+ relationship_id: str,
321
+ ) -> MessageResponse:
322
+ """Delete a term relationship."""
323
+ deleted = await service.delete_relationship(relationship_id)
324
+ if not deleted:
325
+ raise HTTPException(
326
+ status_code=status.HTTP_404_NOT_FOUND,
327
+ detail=f"Relationship '{relationship_id}' not found",
328
+ )
329
+ return MessageResponse(message="Relationship deleted successfully")
@@ -6,6 +6,7 @@ This module configures the main API router and includes all sub-routers.
6
6
  from fastapi import APIRouter
7
7
 
8
8
  from . import (
9
+ # Phase 1-4
9
10
  drift,
10
11
  health,
11
12
  history,
@@ -16,6 +17,10 @@ from . import (
16
17
  schemas,
17
18
  sources,
18
19
  validations,
20
+ # Phase 5
21
+ catalog,
22
+ collaboration,
23
+ glossary,
19
24
  )
20
25
 
21
26
  api_router = APIRouter()
@@ -81,3 +86,27 @@ api_router.include_router(
81
86
  notifications.router,
82
87
  tags=["notifications"],
83
88
  )
89
+
90
+ # =============================================================================
91
+ # Phase 5: Business Glossary & Data Catalog
92
+ # =============================================================================
93
+
94
+ # Glossary management endpoints
95
+ api_router.include_router(
96
+ glossary.router,
97
+ prefix="/glossary",
98
+ tags=["glossary"],
99
+ )
100
+
101
+ # Catalog management endpoints
102
+ api_router.include_router(
103
+ catalog.router,
104
+ prefix="/catalog",
105
+ tags=["catalog"],
106
+ )
107
+
108
+ # Collaboration endpoints (comments, activities)
109
+ api_router.include_router(
110
+ collaboration.router,
111
+ tags=["collaboration"],
112
+ )