htmlgraph 0.20.1__py3-none-any.whl → 0.27.5__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.
- htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/.htmlgraph/agents.json +72 -0
- htmlgraph/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/__init__.py +51 -1
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +26 -10
- htmlgraph/agent_registry.py +2 -1
- htmlgraph/analytics/__init__.py +8 -1
- htmlgraph/analytics/cli.py +86 -20
- htmlgraph/analytics/cost_analyzer.py +391 -0
- htmlgraph/analytics/cost_monitor.py +664 -0
- htmlgraph/analytics/cost_reporter.py +675 -0
- htmlgraph/analytics/cross_session.py +617 -0
- htmlgraph/analytics/dependency.py +10 -6
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/analytics/session_graph.py +707 -0
- htmlgraph/analytics/strategic/__init__.py +80 -0
- htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
- htmlgraph/analytics/strategic/pattern_detector.py +876 -0
- htmlgraph/analytics/strategic/preference_manager.py +709 -0
- htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
- htmlgraph/analytics/work_type.py +67 -27
- htmlgraph/analytics_index.py +53 -20
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +2498 -0
- htmlgraph/api/static/htmx.min.js +1 -0
- htmlgraph/api/static/style-redesign.css +1344 -0
- htmlgraph/api/static/style.css +1079 -0
- htmlgraph/api/templates/dashboard-redesign.html +1366 -0
- htmlgraph/api/templates/dashboard.html +794 -0
- htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
- htmlgraph/api/templates/partials/activity-feed.html +1100 -0
- htmlgraph/api/templates/partials/agents-redesign.html +317 -0
- htmlgraph/api/templates/partials/agents.html +317 -0
- htmlgraph/api/templates/partials/event-traces.html +373 -0
- htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
- htmlgraph/api/templates/partials/features.html +578 -0
- htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
- htmlgraph/api/templates/partials/metrics.html +346 -0
- htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
- htmlgraph/api/templates/partials/orchestration.html +198 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/api/templates/partials/work-items.html +613 -0
- htmlgraph/api/websocket.py +538 -0
- htmlgraph/archive/__init__.py +24 -0
- htmlgraph/archive/bloom.py +234 -0
- htmlgraph/archive/fts.py +297 -0
- htmlgraph/archive/manager.py +583 -0
- htmlgraph/archive/search.py +244 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/attribute_index.py +2 -1
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/base.py +57 -2
- htmlgraph/builders/bug.py +19 -3
- htmlgraph/builders/chore.py +19 -3
- htmlgraph/builders/epic.py +19 -3
- htmlgraph/builders/feature.py +27 -3
- htmlgraph/builders/insight.py +2 -1
- htmlgraph/builders/metric.py +2 -1
- htmlgraph/builders/pattern.py +2 -1
- htmlgraph/builders/phase.py +19 -3
- htmlgraph/builders/spike.py +29 -3
- htmlgraph/builders/track.py +42 -1
- htmlgraph/cigs/__init__.py +81 -0
- htmlgraph/cigs/autonomy.py +385 -0
- htmlgraph/cigs/cost.py +475 -0
- htmlgraph/cigs/messages_basic.py +472 -0
- htmlgraph/cigs/messaging.py +365 -0
- htmlgraph/cigs/models.py +771 -0
- htmlgraph/cigs/pattern_storage.py +427 -0
- htmlgraph/cigs/patterns.py +503 -0
- htmlgraph/cigs/posttool_analyzer.py +234 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cigs/tracker.py +317 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +1424 -0
- htmlgraph/cli/base.py +685 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +954 -0
- htmlgraph/cli/main.py +147 -0
- htmlgraph/cli/models.py +475 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +399 -0
- htmlgraph/cli/work/__init__.py +239 -0
- htmlgraph/cli/work/browse.py +115 -0
- htmlgraph/cli/work/features.py +568 -0
- htmlgraph/cli/work/orchestration.py +676 -0
- htmlgraph/cli/work/report.py +728 -0
- htmlgraph/cli/work/sessions.py +466 -0
- htmlgraph/cli/work/snapshot.py +559 -0
- htmlgraph/cli/work/tracks.py +486 -0
- htmlgraph/cli_commands/__init__.py +1 -0
- htmlgraph/cli_commands/feature.py +195 -0
- htmlgraph/cli_framework.py +115 -0
- htmlgraph/collections/__init__.py +2 -0
- htmlgraph/collections/base.py +197 -14
- htmlgraph/collections/bug.py +2 -1
- htmlgraph/collections/chore.py +2 -1
- htmlgraph/collections/epic.py +2 -1
- htmlgraph/collections/feature.py +2 -1
- htmlgraph/collections/insight.py +2 -1
- htmlgraph/collections/metric.py +2 -1
- htmlgraph/collections/pattern.py +2 -1
- htmlgraph/collections/phase.py +2 -1
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +13 -2
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +14 -1
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +2 -1
- htmlgraph/converter.py +116 -7
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2246 -248
- htmlgraph/dashboard.html.backup +6592 -0
- htmlgraph/dashboard.html.bak +7181 -0
- htmlgraph/dashboard.html.bak2 +7231 -0
- htmlgraph/dashboard.html.bak3 +7232 -0
- htmlgraph/db/__init__.py +38 -0
- htmlgraph/db/queries.py +790 -0
- htmlgraph/db/schema.py +1788 -0
- htmlgraph/decorators.py +317 -0
- htmlgraph/dependency_models.py +2 -1
- htmlgraph/deploy.py +26 -27
- htmlgraph/docs/API_REFERENCE.md +841 -0
- htmlgraph/docs/HTTP_API.md +750 -0
- htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
- htmlgraph/docs/README.md +532 -0
- htmlgraph/docs/__init__.py +77 -0
- htmlgraph/docs/docs_version.py +55 -0
- htmlgraph/docs/metadata.py +93 -0
- htmlgraph/docs/migrations.py +232 -0
- htmlgraph/docs/template_engine.py +143 -0
- htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
- htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
- htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
- htmlgraph/docs/templates/base_agents.md.j2 +78 -0
- htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
- htmlgraph/docs/version_check.py +163 -0
- htmlgraph/edge_index.py +2 -1
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +86 -37
- htmlgraph/event_migration.py +2 -1
- htmlgraph/file_watcher.py +12 -8
- htmlgraph/find_api.py +2 -1
- htmlgraph/git_events.py +67 -9
- htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/hooks/.htmlgraph/agents.json +72 -0
- htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
- htmlgraph/hooks/__init__.py +8 -0
- htmlgraph/hooks/bootstrap.py +169 -0
- htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
- htmlgraph/hooks/concurrent_sessions.py +208 -0
- htmlgraph/hooks/context.py +350 -0
- htmlgraph/hooks/drift_handler.py +525 -0
- htmlgraph/hooks/event_tracker.py +790 -99
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/installer.py +5 -1
- htmlgraph/hooks/orchestrator.py +327 -76
- htmlgraph/hooks/orchestrator_reflector.py +31 -4
- htmlgraph/hooks/post_tool_use_failure.py +32 -7
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +92 -19
- htmlgraph/hooks/pretooluse.py +527 -7
- htmlgraph/hooks/prompt_analyzer.py +637 -0
- htmlgraph/hooks/session_handler.py +668 -0
- htmlgraph/hooks/session_summary.py +395 -0
- htmlgraph/hooks/state_manager.py +504 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/subagent_stop.py +369 -0
- htmlgraph/hooks/task_enforcer.py +99 -4
- htmlgraph/hooks/validator.py +212 -91
- htmlgraph/ids.py +2 -1
- htmlgraph/learning.py +125 -100
- htmlgraph/mcp_server.py +2 -1
- htmlgraph/models.py +217 -18
- htmlgraph/operations/README.md +62 -0
- htmlgraph/operations/__init__.py +79 -0
- htmlgraph/operations/analytics.py +339 -0
- htmlgraph/operations/bootstrap.py +289 -0
- htmlgraph/operations/events.py +244 -0
- htmlgraph/operations/fastapi_server.py +231 -0
- htmlgraph/operations/hooks.py +350 -0
- htmlgraph/operations/initialization.py +597 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/operations/server.py +303 -0
- htmlgraph/orchestration/__init__.py +58 -0
- htmlgraph/orchestration/claude_launcher.py +179 -0
- htmlgraph/orchestration/command_builder.py +72 -0
- htmlgraph/orchestration/headless_spawner.py +281 -0
- htmlgraph/orchestration/live_events.py +377 -0
- htmlgraph/orchestration/model_selection.py +327 -0
- htmlgraph/orchestration/plugin_manager.py +140 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawner_event_tracker.py +383 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +173 -0
- htmlgraph/orchestration/spawners/codex.py +435 -0
- htmlgraph/orchestration/spawners/copilot.py +294 -0
- htmlgraph/orchestration/spawners/gemini.py +471 -0
- htmlgraph/orchestration/subprocess_runner.py +36 -0
- htmlgraph/{orchestration.py → orchestration/task_coordination.py} +16 -8
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +2 -1
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +115 -4
- htmlgraph/parallel.py +2 -1
- htmlgraph/parser.py +86 -6
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +2 -1
- htmlgraph/query_composer.py +509 -0
- htmlgraph/reflection.py +443 -0
- htmlgraph/refs.py +344 -0
- htmlgraph/repo_hash.py +512 -0
- htmlgraph/repositories/__init__.py +292 -0
- htmlgraph/repositories/analytics_repository.py +455 -0
- htmlgraph/repositories/analytics_repository_standard.py +628 -0
- htmlgraph/repositories/feature_repository.py +581 -0
- htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
- htmlgraph/repositories/feature_repository_memory.py +607 -0
- htmlgraph/repositories/feature_repository_sqlite.py +858 -0
- htmlgraph/repositories/filter_service.py +620 -0
- htmlgraph/repositories/filter_service_standard.py +445 -0
- htmlgraph/repositories/shared_cache.py +621 -0
- htmlgraph/repositories/shared_cache_memory.py +395 -0
- htmlgraph/repositories/track_repository.py +552 -0
- htmlgraph/repositories/track_repository_htmlfile.py +619 -0
- htmlgraph/repositories/track_repository_memory.py +508 -0
- htmlgraph/repositories/track_repository_sqlite.py +711 -0
- htmlgraph/sdk/__init__.py +398 -0
- htmlgraph/sdk/__init__.pyi +14 -0
- htmlgraph/sdk/analytics/__init__.py +19 -0
- htmlgraph/sdk/analytics/engine.py +155 -0
- htmlgraph/sdk/analytics/helpers.py +178 -0
- htmlgraph/sdk/analytics/registry.py +109 -0
- htmlgraph/sdk/base.py +484 -0
- htmlgraph/sdk/constants.py +216 -0
- htmlgraph/sdk/core.pyi +308 -0
- htmlgraph/sdk/discovery.py +120 -0
- htmlgraph/sdk/help/__init__.py +12 -0
- htmlgraph/sdk/help/mixin.py +699 -0
- htmlgraph/sdk/mixins/__init__.py +15 -0
- htmlgraph/sdk/mixins/attribution.py +113 -0
- htmlgraph/sdk/mixins/mixin.py +410 -0
- htmlgraph/sdk/operations/__init__.py +12 -0
- htmlgraph/sdk/operations/mixin.py +427 -0
- htmlgraph/sdk/orchestration/__init__.py +17 -0
- htmlgraph/sdk/orchestration/coordinator.py +203 -0
- htmlgraph/sdk/orchestration/spawner.py +204 -0
- htmlgraph/sdk/planning/__init__.py +19 -0
- htmlgraph/sdk/planning/bottlenecks.py +93 -0
- htmlgraph/sdk/planning/mixin.py +211 -0
- htmlgraph/sdk/planning/parallel.py +186 -0
- htmlgraph/sdk/planning/queue.py +210 -0
- htmlgraph/sdk/planning/recommendations.py +87 -0
- htmlgraph/sdk/planning/smart_planning.py +319 -0
- htmlgraph/sdk/session/__init__.py +19 -0
- htmlgraph/sdk/session/continuity.py +57 -0
- htmlgraph/sdk/session/handoff.py +110 -0
- htmlgraph/sdk/session/info.py +309 -0
- htmlgraph/sdk/session/manager.py +103 -0
- htmlgraph/sdk/strategic/__init__.py +26 -0
- htmlgraph/sdk/strategic/mixin.py +563 -0
- htmlgraph/server.py +295 -107
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +285 -3
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +2 -1
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +33 -1
- htmlgraph/track_manager.py +38 -0
- htmlgraph/transcript.py +18 -5
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +2 -1
- htmlgraph/work_type_utils.py +2 -1
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +95 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -4839
- htmlgraph/sdk.py +0 -2359
- htmlgraph-0.20.1.dist-info/RECORD +0 -118
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
htmlgraph/analytics/work_type.py
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
1
7
|
"""
|
|
2
8
|
Analytics API for HtmlGraph work type analysis.
|
|
3
9
|
|
|
@@ -25,19 +31,35 @@ Example:
|
|
|
25
31
|
# Returns: 25.5 (% of work spent on maintenance)
|
|
26
32
|
"""
|
|
27
33
|
|
|
28
|
-
from
|
|
29
|
-
|
|
30
|
-
from datetime import datetime
|
|
34
|
+
from datetime import datetime, timezone
|
|
31
35
|
from typing import TYPE_CHECKING
|
|
32
36
|
|
|
33
37
|
if TYPE_CHECKING:
|
|
34
38
|
from htmlgraph import SDK
|
|
35
39
|
|
|
36
40
|
from htmlgraph.converter import html_to_session
|
|
37
|
-
from htmlgraph.models import Session, WorkType
|
|
41
|
+
from htmlgraph.models import Session, WorkType, utc_now
|
|
38
42
|
from htmlgraph.session_manager import SessionManager
|
|
39
43
|
|
|
40
44
|
|
|
45
|
+
def normalize_datetime(dt: datetime | None) -> datetime | None:
|
|
46
|
+
"""
|
|
47
|
+
Normalize datetime to UTC-aware format for safe comparisons.
|
|
48
|
+
|
|
49
|
+
Handles three cases:
|
|
50
|
+
- None: returns None
|
|
51
|
+
- Naive (no timezone): assumes UTC and adds timezone
|
|
52
|
+
- Aware (has timezone): converts to UTC
|
|
53
|
+
"""
|
|
54
|
+
if dt is None:
|
|
55
|
+
return None
|
|
56
|
+
if dt.tzinfo is None:
|
|
57
|
+
# Naive datetime - assume UTC
|
|
58
|
+
return dt.replace(tzinfo=timezone.utc)
|
|
59
|
+
# Already aware - convert to UTC
|
|
60
|
+
return dt.astimezone(timezone.utc)
|
|
61
|
+
|
|
62
|
+
|
|
41
63
|
class Analytics:
|
|
42
64
|
"""
|
|
43
65
|
Analytics interface for work type analysis.
|
|
@@ -79,7 +101,7 @@ class Analytics:
|
|
|
79
101
|
Example:
|
|
80
102
|
>>> analytics = sdk.analytics
|
|
81
103
|
>>> dist = analytics.work_type_distribution(session_id="session-123")
|
|
82
|
-
>>>
|
|
104
|
+
>>> logger.info("%s", dist)
|
|
83
105
|
{
|
|
84
106
|
"feature-implementation": 45.2,
|
|
85
107
|
"spike-investigation": 28.3,
|
|
@@ -145,11 +167,11 @@ class Analytics:
|
|
|
145
167
|
|
|
146
168
|
Example:
|
|
147
169
|
>>> ratio = sdk.analytics.spike_to_feature_ratio(session_id="session-123")
|
|
148
|
-
>>>
|
|
170
|
+
>>> logger.info(f"Spike-to-feature ratio: {ratio:.2f}")
|
|
149
171
|
Spike-to-feature ratio: 0.63
|
|
150
172
|
|
|
151
173
|
>>> if ratio > 0.5:
|
|
152
|
-
...
|
|
174
|
+
... logger.info("This was a research-heavy session")
|
|
153
175
|
"""
|
|
154
176
|
events = self._get_events(session_id, start_date, end_date)
|
|
155
177
|
|
|
@@ -203,11 +225,11 @@ class Analytics:
|
|
|
203
225
|
|
|
204
226
|
Example:
|
|
205
227
|
>>> burden = sdk.analytics.maintenance_burden(session_id="session-123")
|
|
206
|
-
>>>
|
|
228
|
+
>>> logger.info(f"Maintenance burden: {burden:.1f}%")
|
|
207
229
|
Maintenance burden: 32.5%
|
|
208
230
|
|
|
209
231
|
>>> if burden > 40:
|
|
210
|
-
...
|
|
232
|
+
... logger.info("⚠️ High maintenance burden - consider addressing technical debt")
|
|
211
233
|
"""
|
|
212
234
|
events = self._get_events(session_id, start_date, end_date)
|
|
213
235
|
|
|
@@ -258,7 +280,7 @@ class Analytics:
|
|
|
258
280
|
>>> spike_sessions = sdk.analytics.get_sessions_by_work_type(
|
|
259
281
|
... "spike-investigation"
|
|
260
282
|
... )
|
|
261
|
-
>>>
|
|
283
|
+
>>> logger.info(f"Found {len(spike_sessions)} exploratory sessions")
|
|
262
284
|
"""
|
|
263
285
|
session_nodes = self.sdk.sessions.all()
|
|
264
286
|
matching_sessions = []
|
|
@@ -270,9 +292,11 @@ class Analytics:
|
|
|
270
292
|
continue
|
|
271
293
|
|
|
272
294
|
# Check date range
|
|
273
|
-
|
|
295
|
+
start_normalized = normalize_datetime(start_date)
|
|
296
|
+
end_normalized = normalize_datetime(end_date)
|
|
297
|
+
if start_normalized and session.started_at < start_normalized:
|
|
274
298
|
continue
|
|
275
|
-
if
|
|
299
|
+
if end_normalized and session.started_at > end_normalized:
|
|
276
300
|
continue
|
|
277
301
|
|
|
278
302
|
# Check primary work type
|
|
@@ -296,7 +320,7 @@ class Analytics:
|
|
|
296
320
|
|
|
297
321
|
Example:
|
|
298
322
|
>>> breakdown = sdk.analytics.calculate_session_work_breakdown("session-123")
|
|
299
|
-
>>>
|
|
323
|
+
>>> logger.info("%s", breakdown)
|
|
300
324
|
{
|
|
301
325
|
"feature-implementation": 45,
|
|
302
326
|
"spike-investigation": 28,
|
|
@@ -324,7 +348,7 @@ class Analytics:
|
|
|
324
348
|
|
|
325
349
|
Example:
|
|
326
350
|
>>> primary = sdk.analytics.calculate_session_primary_work_type("session-123")
|
|
327
|
-
>>>
|
|
351
|
+
>>> logger.info(f"Primary work type: {primary}")
|
|
328
352
|
Primary work type: spike-investigation
|
|
329
353
|
"""
|
|
330
354
|
session = self._get_session(session_id)
|
|
@@ -379,7 +403,7 @@ class Analytics:
|
|
|
379
403
|
|
|
380
404
|
Example:
|
|
381
405
|
>>> metrics = sdk.analytics.transition_time_metrics(session_id="session-123")
|
|
382
|
-
>>>
|
|
406
|
+
>>> logger.info(f"Transition time: {metrics['transition_percent']:.1f}%")
|
|
383
407
|
Transition time: 15.3%
|
|
384
408
|
"""
|
|
385
409
|
from pathlib import Path
|
|
@@ -413,18 +437,26 @@ class Analytics:
|
|
|
413
437
|
# Calculate time for each spike
|
|
414
438
|
for spike in all_spikes:
|
|
415
439
|
# Apply date filters
|
|
416
|
-
|
|
440
|
+
start_normalized = normalize_datetime(start_date)
|
|
441
|
+
end_normalized = normalize_datetime(end_date)
|
|
442
|
+
if start_normalized and spike.created < start_normalized:
|
|
417
443
|
continue
|
|
418
|
-
if
|
|
444
|
+
if end_normalized and spike.created > end_normalized:
|
|
419
445
|
continue
|
|
420
446
|
|
|
421
|
-
# Calculate duration
|
|
422
|
-
start_time = spike.created
|
|
447
|
+
# Calculate duration (normalize datetimes for safe comparison)
|
|
448
|
+
start_time = normalize_datetime(spike.created)
|
|
449
|
+
if not start_time:
|
|
450
|
+
continue # Skip if spike creation date is missing
|
|
423
451
|
if spike.status == "done" and spike.updated:
|
|
424
|
-
end_time = spike.updated
|
|
452
|
+
end_time = normalize_datetime(spike.updated)
|
|
425
453
|
else:
|
|
426
454
|
# If still in progress, use last updated time
|
|
427
|
-
end_time =
|
|
455
|
+
end_time = normalize_datetime(
|
|
456
|
+
spike.updated if spike.updated else utc_now()
|
|
457
|
+
)
|
|
458
|
+
if not end_time:
|
|
459
|
+
end_time = start_time # Fallback to start time if end time missing
|
|
428
460
|
|
|
429
461
|
duration = (
|
|
430
462
|
end_time - start_time
|
|
@@ -460,17 +492,25 @@ class Analytics:
|
|
|
460
492
|
|
|
461
493
|
for node in nodes:
|
|
462
494
|
# Apply date filters
|
|
463
|
-
|
|
495
|
+
start_normalized = normalize_datetime(start_date)
|
|
496
|
+
end_normalized = normalize_datetime(end_date)
|
|
497
|
+
if start_normalized and node.created < start_normalized:
|
|
464
498
|
continue
|
|
465
|
-
if
|
|
499
|
+
if end_normalized and node.created > end_normalized:
|
|
466
500
|
continue
|
|
467
501
|
|
|
468
|
-
# Calculate duration
|
|
469
|
-
start_time = node.created
|
|
502
|
+
# Calculate duration (normalize datetimes for safe comparison)
|
|
503
|
+
start_time = normalize_datetime(node.created)
|
|
504
|
+
if not start_time:
|
|
505
|
+
continue # Skip if node creation date is missing
|
|
470
506
|
if node.status == "done" and node.updated:
|
|
471
|
-
end_time = node.updated
|
|
507
|
+
end_time = normalize_datetime(node.updated)
|
|
472
508
|
else:
|
|
473
|
-
end_time =
|
|
509
|
+
end_time = normalize_datetime(
|
|
510
|
+
node.updated if node.updated else utc_now()
|
|
511
|
+
)
|
|
512
|
+
if not end_time:
|
|
513
|
+
end_time = start_time # Fallback to start time if end time missing
|
|
474
514
|
|
|
475
515
|
duration = (end_time - start_time).total_seconds() / 60
|
|
476
516
|
feature_minutes += duration
|
htmlgraph/analytics_index.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
SQLite analytics index for HtmlGraph event logs.
|
|
3
5
|
|
|
@@ -5,7 +7,6 @@ This is a rebuildable cache/index for fast dashboard queries.
|
|
|
5
7
|
The canonical source of truth is the JSONL event log under `.htmlgraph/events/`.
|
|
6
8
|
"""
|
|
7
9
|
|
|
8
|
-
from __future__ import annotations
|
|
9
10
|
|
|
10
11
|
import json
|
|
11
12
|
import sqlite3
|
|
@@ -14,7 +15,7 @@ from dataclasses import dataclass
|
|
|
14
15
|
from pathlib import Path
|
|
15
16
|
from typing import Any
|
|
16
17
|
|
|
17
|
-
SCHEMA_VERSION =
|
|
18
|
+
SCHEMA_VERSION = 4 # Bumped: renamed 'agent' column to 'agent_assigned'
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
@dataclass(frozen=True)
|
|
@@ -82,12 +83,14 @@ class AnalyticsIndex:
|
|
|
82
83
|
"""
|
|
83
84
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
84
85
|
session_id TEXT PRIMARY KEY,
|
|
85
|
-
|
|
86
|
+
agent_assigned TEXT,
|
|
86
87
|
start_commit TEXT,
|
|
87
88
|
continued_from TEXT,
|
|
88
89
|
status TEXT,
|
|
89
90
|
started_at TEXT,
|
|
90
|
-
ended_at TEXT
|
|
91
|
+
ended_at TEXT,
|
|
92
|
+
parent_session_id TEXT,
|
|
93
|
+
parent_event_id TEXT
|
|
91
94
|
)
|
|
92
95
|
"""
|
|
93
96
|
)
|
|
@@ -103,6 +106,9 @@ class AnalyticsIndex:
|
|
|
103
106
|
feature_id TEXT,
|
|
104
107
|
drift_score REAL,
|
|
105
108
|
payload_json TEXT,
|
|
109
|
+
parent_event_id TEXT,
|
|
110
|
+
cost_tokens INTEGER,
|
|
111
|
+
execution_duration_seconds REAL,
|
|
106
112
|
FOREIGN KEY(session_id) REFERENCES sessions(session_id)
|
|
107
113
|
)
|
|
108
114
|
"""
|
|
@@ -157,6 +163,9 @@ class AnalyticsIndex:
|
|
|
157
163
|
)
|
|
158
164
|
|
|
159
165
|
# Indexes for typical dashboard queries
|
|
166
|
+
conn.execute(
|
|
167
|
+
"CREATE INDEX IF NOT EXISTS idx_sessions_parent ON sessions(parent_session_id)"
|
|
168
|
+
)
|
|
160
169
|
conn.execute("CREATE INDEX IF NOT EXISTS idx_events_ts ON events(ts)")
|
|
161
170
|
conn.execute(
|
|
162
171
|
"CREATE INDEX IF NOT EXISTS idx_events_session_ts ON events(session_id, ts)"
|
|
@@ -190,15 +199,17 @@ class AnalyticsIndex:
|
|
|
190
199
|
with self.connect() as conn:
|
|
191
200
|
conn.execute(
|
|
192
201
|
"""
|
|
193
|
-
INSERT INTO sessions(session_id,
|
|
194
|
-
VALUES(
|
|
202
|
+
INSERT INTO sessions(session_id, agent_assigned, start_commit, continued_from, status, started_at, ended_at, parent_session_id, parent_event_id)
|
|
203
|
+
VALUES(?,?,?,?,?,?,?,?,?)
|
|
195
204
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
196
|
-
|
|
205
|
+
agent_assigned=excluded.agent_assigned,
|
|
197
206
|
start_commit=excluded.start_commit,
|
|
198
207
|
continued_from=excluded.continued_from,
|
|
199
208
|
status=excluded.status,
|
|
200
209
|
started_at=excluded.started_at,
|
|
201
|
-
ended_at=excluded.ended_at
|
|
210
|
+
ended_at=excluded.ended_at,
|
|
211
|
+
parent_session_id=excluded.parent_session_id,
|
|
212
|
+
parent_event_id=excluded.parent_event_id
|
|
202
213
|
""",
|
|
203
214
|
(
|
|
204
215
|
session.get("session_id"),
|
|
@@ -208,6 +219,8 @@ class AnalyticsIndex:
|
|
|
208
219
|
session.get("status"),
|
|
209
220
|
session.get("started_at"),
|
|
210
221
|
session.get("ended_at"),
|
|
222
|
+
session.get("parent_session_id"),
|
|
223
|
+
session.get("parent_event_id"),
|
|
211
224
|
),
|
|
212
225
|
)
|
|
213
226
|
|
|
@@ -238,8 +251,8 @@ class AnalyticsIndex:
|
|
|
238
251
|
with self.connect() as conn:
|
|
239
252
|
conn.execute(
|
|
240
253
|
"""
|
|
241
|
-
INSERT OR IGNORE INTO events(event_id, session_id, ts, tool, summary, success, feature_id, drift_score, payload_json)
|
|
242
|
-
VALUES(
|
|
254
|
+
INSERT OR IGNORE INTO events(event_id, session_id, ts, tool, summary, success, feature_id, drift_score, payload_json, parent_event_id, cost_tokens, execution_duration_seconds)
|
|
255
|
+
VALUES(?,?,?,?,?,?,?,?,?,?,?,?)
|
|
243
256
|
""",
|
|
244
257
|
(
|
|
245
258
|
event_id,
|
|
@@ -251,6 +264,9 @@ class AnalyticsIndex:
|
|
|
251
264
|
event.get("feature_id"),
|
|
252
265
|
event.get("drift_score"),
|
|
253
266
|
payload_json,
|
|
267
|
+
event.get("parent_event_id"),
|
|
268
|
+
event.get("cost_tokens"),
|
|
269
|
+
event.get("execution_duration_seconds"),
|
|
254
270
|
),
|
|
255
271
|
)
|
|
256
272
|
# Insert file path rows, idempotent by (event_id, path)
|
|
@@ -367,6 +383,8 @@ class AnalyticsIndex:
|
|
|
367
383
|
"status": event.get("session_status"),
|
|
368
384
|
"started_at": None,
|
|
369
385
|
"ended_at": None,
|
|
386
|
+
"parent_session_id": event.get("parent_session_id"),
|
|
387
|
+
"parent_event_id": event.get("parent_event_id"),
|
|
370
388
|
},
|
|
371
389
|
)
|
|
372
390
|
if meta.get("agent") is None and event.get("agent"):
|
|
@@ -377,6 +395,12 @@ class AnalyticsIndex:
|
|
|
377
395
|
meta["continued_from"] = event.get("continued_from")
|
|
378
396
|
if meta.get("status") is None and event.get("session_status"):
|
|
379
397
|
meta["status"] = event.get("session_status")
|
|
398
|
+
if meta.get("parent_session_id") is None and event.get(
|
|
399
|
+
"parent_session_id"
|
|
400
|
+
):
|
|
401
|
+
meta["parent_session_id"] = event.get("parent_session_id")
|
|
402
|
+
if meta.get("parent_event_id") is None and event.get("parent_event_id"):
|
|
403
|
+
meta["parent_event_id"] = event.get("parent_event_id")
|
|
380
404
|
|
|
381
405
|
# Track time range (treat earliest event as started_at, latest as ended_at if session is ended)
|
|
382
406
|
if meta["started_at"] is None or ts < meta["started_at"]:
|
|
@@ -393,8 +417,8 @@ class AnalyticsIndex:
|
|
|
393
417
|
|
|
394
418
|
conn.execute(
|
|
395
419
|
"""
|
|
396
|
-
INSERT OR IGNORE INTO events(event_id, session_id, ts, tool, summary, success, feature_id, drift_score, payload_json)
|
|
397
|
-
VALUES(
|
|
420
|
+
INSERT OR IGNORE INTO events(event_id, session_id, ts, tool, summary, success, feature_id, drift_score, payload_json, parent_event_id, cost_tokens, execution_duration_seconds)
|
|
421
|
+
VALUES(?,?,?,?,?,?,?,?,?,?,?,?)
|
|
398
422
|
""",
|
|
399
423
|
(
|
|
400
424
|
event_id,
|
|
@@ -406,6 +430,9 @@ class AnalyticsIndex:
|
|
|
406
430
|
event.get("feature_id"),
|
|
407
431
|
event.get("drift_score"),
|
|
408
432
|
payload_json,
|
|
433
|
+
event.get("parent_event_id"),
|
|
434
|
+
event.get("cost_tokens"),
|
|
435
|
+
event.get("execution_duration_seconds"),
|
|
409
436
|
),
|
|
410
437
|
)
|
|
411
438
|
|
|
@@ -483,17 +510,19 @@ class AnalyticsIndex:
|
|
|
483
510
|
for meta in session_meta.values():
|
|
484
511
|
conn.execute(
|
|
485
512
|
"""
|
|
486
|
-
INSERT INTO sessions(session_id,
|
|
487
|
-
VALUES(
|
|
513
|
+
INSERT INTO sessions(session_id, agent_assigned, start_commit, continued_from, status, started_at, ended_at, parent_session_id, parent_event_id)
|
|
514
|
+
VALUES(?,?,?,?,?,?,?,?,?)
|
|
488
515
|
""",
|
|
489
516
|
(
|
|
490
517
|
meta.get("session_id"),
|
|
491
|
-
meta.get("agent"),
|
|
518
|
+
meta.get("agent"), # Source data still uses 'agent' key
|
|
492
519
|
meta.get("start_commit"),
|
|
493
520
|
meta.get("continued_from"),
|
|
494
521
|
meta.get("status"),
|
|
495
522
|
meta.get("started_at"),
|
|
496
523
|
meta.get("ended_at"),
|
|
524
|
+
meta.get("parent_session_id"),
|
|
525
|
+
meta.get("parent_event_id"),
|
|
497
526
|
),
|
|
498
527
|
)
|
|
499
528
|
|
|
@@ -676,13 +705,17 @@ class AnalyticsIndex:
|
|
|
676
705
|
with self.connect() as conn:
|
|
677
706
|
rows = conn.execute(
|
|
678
707
|
"""
|
|
679
|
-
SELECT event_id, session_id, ts, tool, summary, success, feature_id, drift_score
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
708
|
+
SELECT e.event_id, e.session_id, e.ts, e.tool, e.summary, e.success, e.feature_id, e.drift_score,
|
|
709
|
+
COALESCE(e.parent_event_id, s.parent_event_id) as parent_event_id,
|
|
710
|
+
e.cost_tokens, e.execution_duration_seconds
|
|
711
|
+
FROM events e
|
|
712
|
+
JOIN sessions s ON e.session_id = s.session_id
|
|
713
|
+
WHERE e.session_id = ?
|
|
714
|
+
OR s.parent_session_id = ?
|
|
715
|
+
ORDER BY e.ts DESC
|
|
683
716
|
LIMIT ?
|
|
684
717
|
""",
|
|
685
|
-
(session_id, int(limit)),
|
|
718
|
+
(session_id, session_id, int(limit)),
|
|
686
719
|
).fetchall()
|
|
687
720
|
return [dict(r) for r in rows]
|
|
688
721
|
|