cortexdb-connectors 0.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.
- cortexdb_connectors/__init__.py +40 -0
- cortexdb_connectors/base.py +364 -0
- cortexdb_connectors/cli.py +476 -0
- cortexdb_connectors/confluence/__init__.py +359 -0
- cortexdb_connectors/discord/__init__.py +646 -0
- cortexdb_connectors/github/__init__.py +309 -0
- cortexdb_connectors/gitlab/__init__.py +749 -0
- cortexdb_connectors/google_workspace/__init__.py +1033 -0
- cortexdb_connectors/hubspot/__init__.py +489 -0
- cortexdb_connectors/insights/__init__.py +40 -0
- cortexdb_connectors/insights/api.py +252 -0
- cortexdb_connectors/insights/detectors.py +771 -0
- cortexdb_connectors/insights/engine.py +308 -0
- cortexdb_connectors/intercom/__init__.py +631 -0
- cortexdb_connectors/jira/__init__.py +298 -0
- cortexdb_connectors/linear/__init__.py +705 -0
- cortexdb_connectors/notion/__init__.py +702 -0
- cortexdb_connectors/pagerduty/__init__.py +298 -0
- cortexdb_connectors/salesforce/__init__.py +399 -0
- cortexdb_connectors/servicenow/__init__.py +460 -0
- cortexdb_connectors/slack/__init__.py +331 -0
- cortexdb_connectors/state.py +145 -0
- cortexdb_connectors/teams/__init__.py +656 -0
- cortexdb_connectors/webhooks.py +672 -0
- cortexdb_connectors/worker.py +192 -0
- cortexdb_connectors/zendesk/__init__.py +496 -0
- cortexdb_connectors-0.2.0.dist-info/METADATA +207 -0
- cortexdb_connectors-0.2.0.dist-info/RECORD +31 -0
- cortexdb_connectors-0.2.0.dist-info/WHEEL +5 -0
- cortexdb_connectors-0.2.0.dist-info/entry_points.txt +2 -0
- cortexdb_connectors-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""FastAPI router exposing the Insights Engine via HTTP endpoints.
|
|
2
|
+
|
|
3
|
+
Mount this router into an existing FastAPI application::
|
|
4
|
+
|
|
5
|
+
from fastapi import FastAPI
|
|
6
|
+
from cortexdb_connectors.insights.api import create_insights_router
|
|
7
|
+
from cortexdb_connectors.insights.engine import InsightsEngine
|
|
8
|
+
|
|
9
|
+
engine = InsightsEngine(cortex_url="...", cortex_api_key="...")
|
|
10
|
+
app = FastAPI()
|
|
11
|
+
app.include_router(create_insights_router(engine))
|
|
12
|
+
|
|
13
|
+
Endpoints
|
|
14
|
+
---------
|
|
15
|
+
GET /v1/insights List insights with optional filters.
|
|
16
|
+
GET /v1/insights/{id} Retrieve a single insight by id.
|
|
17
|
+
POST /v1/insights/{id}/dismiss Dismiss an insight.
|
|
18
|
+
GET /v1/insights/status Engine health / status.
|
|
19
|
+
POST /v1/insights/run Trigger an on-demand analysis pass.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from datetime import datetime
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
from fastapi import APIRouter, HTTPException, Query
|
|
28
|
+
from pydantic import BaseModel, Field
|
|
29
|
+
|
|
30
|
+
from cortexdb_connectors.insights.detectors import (
|
|
31
|
+
Insight,
|
|
32
|
+
InsightSeverity,
|
|
33
|
+
InsightType,
|
|
34
|
+
)
|
|
35
|
+
from cortexdb_connectors.insights.engine import InsightsEngine
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Response models
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
class InsightResponse(BaseModel):
|
|
43
|
+
"""Single insight in API responses."""
|
|
44
|
+
|
|
45
|
+
id: str
|
|
46
|
+
insight_type: InsightType
|
|
47
|
+
title: str
|
|
48
|
+
description: str
|
|
49
|
+
severity: InsightSeverity
|
|
50
|
+
entities: list[str]
|
|
51
|
+
detected_at: datetime
|
|
52
|
+
confidence: float
|
|
53
|
+
dismissed: bool
|
|
54
|
+
dismissed_at: datetime | None = None
|
|
55
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class InsightListResponse(BaseModel):
|
|
59
|
+
"""Paginated list of insights."""
|
|
60
|
+
|
|
61
|
+
insights: list[InsightResponse]
|
|
62
|
+
total: int
|
|
63
|
+
limit: int
|
|
64
|
+
offset: int
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class DismissResponse(BaseModel):
|
|
68
|
+
"""Response after dismissing an insight."""
|
|
69
|
+
|
|
70
|
+
id: str
|
|
71
|
+
dismissed: bool
|
|
72
|
+
dismissed_at: datetime | None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class StatusResponse(BaseModel):
|
|
76
|
+
"""Engine status response."""
|
|
77
|
+
|
|
78
|
+
running: bool
|
|
79
|
+
interval_seconds: int
|
|
80
|
+
detector_count: int
|
|
81
|
+
insight_count: int
|
|
82
|
+
active_insight_count: int
|
|
83
|
+
last_run_at: str | None
|
|
84
|
+
run_count: int
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class RunResponse(BaseModel):
|
|
88
|
+
"""Response from triggering an on-demand analysis pass."""
|
|
89
|
+
|
|
90
|
+
new_insights: int
|
|
91
|
+
total_insights: int
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
# Helpers
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
def _insight_to_response(insight: Insight) -> InsightResponse:
|
|
99
|
+
"""Convert an internal Insight model to an API response model."""
|
|
100
|
+
return InsightResponse(
|
|
101
|
+
id=insight.id,
|
|
102
|
+
insight_type=insight.insight_type,
|
|
103
|
+
title=insight.title,
|
|
104
|
+
description=insight.description,
|
|
105
|
+
severity=insight.severity,
|
|
106
|
+
entities=insight.entities,
|
|
107
|
+
detected_at=insight.detected_at,
|
|
108
|
+
confidence=insight.confidence,
|
|
109
|
+
dismissed=insight.dismissed,
|
|
110
|
+
dismissed_at=insight.dismissed_at,
|
|
111
|
+
metadata=insight.metadata,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
# Router factory
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
def create_insights_router(engine: InsightsEngine) -> APIRouter:
|
|
120
|
+
"""Create a FastAPI router wired to the given ``InsightsEngine``.
|
|
121
|
+
|
|
122
|
+
Parameters
|
|
123
|
+
----------
|
|
124
|
+
engine:
|
|
125
|
+
A configured InsightsEngine instance whose in-memory insight
|
|
126
|
+
store will be served through the API.
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
APIRouter
|
|
131
|
+
A router with prefix ``/v1/insights`` ready to be mounted on a
|
|
132
|
+
FastAPI application.
|
|
133
|
+
"""
|
|
134
|
+
router = APIRouter(prefix="/v1/insights", tags=["insights"])
|
|
135
|
+
|
|
136
|
+
@router.get(
|
|
137
|
+
"",
|
|
138
|
+
response_model=InsightListResponse,
|
|
139
|
+
summary="List insights",
|
|
140
|
+
description=(
|
|
141
|
+
"Return insights with optional filters for type, severity, "
|
|
142
|
+
"entity, and pagination."
|
|
143
|
+
),
|
|
144
|
+
)
|
|
145
|
+
async def list_insights(
|
|
146
|
+
type: InsightType | None = Query(
|
|
147
|
+
None,
|
|
148
|
+
description="Filter by insight type",
|
|
149
|
+
alias="type",
|
|
150
|
+
),
|
|
151
|
+
severity: InsightSeverity | None = Query(
|
|
152
|
+
None, description="Filter by severity level"
|
|
153
|
+
),
|
|
154
|
+
entity: str | None = Query(
|
|
155
|
+
None,
|
|
156
|
+
description=(
|
|
157
|
+
"Filter to insights mentioning this entity "
|
|
158
|
+
"(case-insensitive substring match)"
|
|
159
|
+
),
|
|
160
|
+
),
|
|
161
|
+
include_dismissed: bool = Query(
|
|
162
|
+
False, description="Include dismissed insights"
|
|
163
|
+
),
|
|
164
|
+
limit: int = Query(50, ge=1, le=500, description="Max results"),
|
|
165
|
+
offset: int = Query(0, ge=0, description="Pagination offset"),
|
|
166
|
+
) -> InsightListResponse:
|
|
167
|
+
"""List insights with optional filters."""
|
|
168
|
+
# Get unfiltered count for the total (with same filters minus pagination).
|
|
169
|
+
all_matching = engine.query_insights(
|
|
170
|
+
insight_type=type,
|
|
171
|
+
severity=severity,
|
|
172
|
+
entity=entity,
|
|
173
|
+
include_dismissed=include_dismissed,
|
|
174
|
+
limit=engine.max_insights,
|
|
175
|
+
offset=0,
|
|
176
|
+
)
|
|
177
|
+
total = len(all_matching)
|
|
178
|
+
|
|
179
|
+
page = engine.query_insights(
|
|
180
|
+
insight_type=type,
|
|
181
|
+
severity=severity,
|
|
182
|
+
entity=entity,
|
|
183
|
+
include_dismissed=include_dismissed,
|
|
184
|
+
limit=limit,
|
|
185
|
+
offset=offset,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
return InsightListResponse(
|
|
189
|
+
insights=[_insight_to_response(i) for i in page],
|
|
190
|
+
total=total,
|
|
191
|
+
limit=limit,
|
|
192
|
+
offset=offset,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
@router.get(
|
|
196
|
+
"/status",
|
|
197
|
+
response_model=StatusResponse,
|
|
198
|
+
summary="Engine status",
|
|
199
|
+
description="Return the current status of the insights engine.",
|
|
200
|
+
)
|
|
201
|
+
async def get_status() -> StatusResponse:
|
|
202
|
+
"""Return engine health information."""
|
|
203
|
+
return StatusResponse(**engine.status())
|
|
204
|
+
|
|
205
|
+
@router.get(
|
|
206
|
+
"/{insight_id}",
|
|
207
|
+
response_model=InsightResponse,
|
|
208
|
+
summary="Get single insight",
|
|
209
|
+
description="Retrieve a single insight by its id.",
|
|
210
|
+
)
|
|
211
|
+
async def get_insight(insight_id: str) -> InsightResponse:
|
|
212
|
+
"""Retrieve a single insight by id."""
|
|
213
|
+
insight = engine.get_insight(insight_id)
|
|
214
|
+
if insight is None:
|
|
215
|
+
raise HTTPException(status_code=404, detail="Insight not found")
|
|
216
|
+
return _insight_to_response(insight)
|
|
217
|
+
|
|
218
|
+
@router.post(
|
|
219
|
+
"/{insight_id}/dismiss",
|
|
220
|
+
response_model=DismissResponse,
|
|
221
|
+
summary="Dismiss an insight",
|
|
222
|
+
description=(
|
|
223
|
+
"Mark an insight as dismissed. Dismissed insights will not "
|
|
224
|
+
"reappear in default list queries."
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
async def dismiss_insight(insight_id: str) -> DismissResponse:
|
|
228
|
+
"""Dismiss an insight so it no longer surfaces in default queries."""
|
|
229
|
+
insight = engine.dismiss_insight(insight_id)
|
|
230
|
+
if insight is None:
|
|
231
|
+
raise HTTPException(status_code=404, detail="Insight not found")
|
|
232
|
+
return DismissResponse(
|
|
233
|
+
id=insight.id,
|
|
234
|
+
dismissed=insight.dismissed,
|
|
235
|
+
dismissed_at=insight.dismissed_at,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
@router.post(
|
|
239
|
+
"/run",
|
|
240
|
+
response_model=RunResponse,
|
|
241
|
+
summary="Trigger analysis",
|
|
242
|
+
description="Run all detectors immediately and return a summary.",
|
|
243
|
+
)
|
|
244
|
+
async def trigger_run() -> RunResponse:
|
|
245
|
+
"""Trigger an on-demand analysis pass."""
|
|
246
|
+
new_insights = await engine.run_once()
|
|
247
|
+
return RunResponse(
|
|
248
|
+
new_insights=len(new_insights),
|
|
249
|
+
total_insights=len(engine._insights),
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
return router
|