htmlgraph 0.9.3__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 +173 -17
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +127 -0
- htmlgraph/agent_registry.py +45 -30
- htmlgraph/agents.py +160 -107
- htmlgraph/analytics/__init__.py +9 -2
- htmlgraph/analytics/cli.py +190 -51
- 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 +192 -100
- 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 +190 -14
- htmlgraph/analytics_index.py +135 -51
- 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 +208 -0
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/__init__.py +14 -0
- htmlgraph/builders/base.py +118 -29
- htmlgraph/builders/bug.py +150 -0
- htmlgraph/builders/chore.py +119 -0
- htmlgraph/builders/epic.py +150 -0
- htmlgraph/builders/feature.py +31 -6
- htmlgraph/builders/insight.py +195 -0
- htmlgraph/builders/metric.py +217 -0
- htmlgraph/builders/pattern.py +202 -0
- htmlgraph/builders/phase.py +162 -0
- htmlgraph/builders/spike.py +52 -19
- htmlgraph/builders/track.py +148 -72
- 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 +18 -0
- htmlgraph/collections/base.py +415 -98
- htmlgraph/collections/bug.py +53 -0
- htmlgraph/collections/chore.py +53 -0
- htmlgraph/collections/epic.py +53 -0
- htmlgraph/collections/feature.py +12 -26
- htmlgraph/collections/insight.py +100 -0
- htmlgraph/collections/metric.py +92 -0
- htmlgraph/collections/pattern.py +97 -0
- htmlgraph/collections/phase.py +53 -0
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +56 -16
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +511 -0
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +344 -0
- htmlgraph/converter.py +216 -28
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2406 -307
- 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 +19 -2
- htmlgraph/deploy.py +142 -125
- htmlgraph/deployment_models.py +474 -0
- 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 +182 -27
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +100 -52
- htmlgraph/event_migration.py +13 -4
- htmlgraph/exceptions.py +49 -0
- htmlgraph/file_watcher.py +101 -28
- htmlgraph/find_api.py +75 -63
- htmlgraph/git_events.py +145 -63
- htmlgraph/graph.py +1122 -106
- 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 +45 -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 +1314 -0
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/hooks-config.example.json +12 -0
- htmlgraph/hooks/installer.py +343 -0
- htmlgraph/hooks/orchestrator.py +674 -0
- htmlgraph/hooks/orchestrator_reflector.py +223 -0
- htmlgraph/hooks/post-checkout.sh +28 -0
- htmlgraph/hooks/post-commit.sh +24 -0
- htmlgraph/hooks/post-merge.sh +26 -0
- htmlgraph/hooks/post_tool_use_failure.py +273 -0
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +408 -0
- htmlgraph/hooks/pre-commit.sh +94 -0
- htmlgraph/hooks/pre-push.sh +28 -0
- htmlgraph/hooks/pretooluse.py +819 -0
- 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 +255 -0
- htmlgraph/hooks/task_validator.py +177 -0
- htmlgraph/hooks/validator.py +628 -0
- htmlgraph/ids.py +41 -27
- htmlgraph/index.d.ts +286 -0
- htmlgraph/learning.py +767 -0
- htmlgraph/mcp_server.py +69 -23
- htmlgraph/models.py +1586 -87
- 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/task_coordination.py +343 -0
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +669 -0
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +328 -0
- htmlgraph/orchestrator_validator.py +133 -0
- htmlgraph/parallel.py +646 -0
- htmlgraph/parser.py +160 -35
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/planning.py +147 -52
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +109 -72
- 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/routing.py +8 -19
- htmlgraph/scripts/deploy.py +1 -2
- 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 +685 -180
- htmlgraph/services/__init__.py +10 -0
- htmlgraph/services/claiming.py +199 -0
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +1392 -175
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +201 -0
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/setup.py +34 -17
- htmlgraph/spike_index.py +143 -0
- htmlgraph/sync_docs.py +12 -15
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph/templates/GEMINI.md.template +87 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +146 -15
- htmlgraph/track_manager.py +69 -21
- htmlgraph/transcript.py +890 -0
- htmlgraph/transcript_analytics.py +699 -0
- htmlgraph/types.py +323 -0
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +8 -5
- htmlgraph/work_type_utils.py +3 -2
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2406 -307
- htmlgraph-0.27.5.data/data/htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/GEMINI.md.template +87 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +97 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -2688
- htmlgraph/sdk.py +0 -709
- htmlgraph-0.9.3.dist-info/RECORD +0 -61
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CostReporter for CIGS Dashboard Generation
|
|
3
|
+
|
|
4
|
+
Generates interactive HTML dashboards showing cost analysis, violation tracking,
|
|
5
|
+
and savings potential from proper delegation.
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- Professional HTML5 dashboard with responsive design
|
|
9
|
+
- Cost visualization using Chart.js
|
|
10
|
+
- Violation metrics and compliance statistics
|
|
11
|
+
- Recommendations for cost optimization
|
|
12
|
+
- Dark theme matching HtmlGraph visual style
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
from htmlgraph.cigs.reporter import CostReporter
|
|
16
|
+
from htmlgraph.cigs.tracker import ViolationTracker
|
|
17
|
+
|
|
18
|
+
tracker = ViolationTracker()
|
|
19
|
+
reporter = CostReporter()
|
|
20
|
+
html = reporter.generate_dashboard(tracker)
|
|
21
|
+
reporter.save_dashboard(html, "dashboard.html")
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
from datetime import datetime
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
from htmlgraph.cigs.models import SessionViolationSummary
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CostReporter:
|
|
33
|
+
"""
|
|
34
|
+
Generate interactive HTML dashboards from cost analysis data.
|
|
35
|
+
|
|
36
|
+
Transforms ViolationTracker and cost data into professional HTML
|
|
37
|
+
visualizations with charts, tables, and recommendations.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
# Design theme
|
|
41
|
+
THEME = {
|
|
42
|
+
"bg_primary": "#1a1a2e",
|
|
43
|
+
"bg_secondary": "#16213e",
|
|
44
|
+
"text_primary": "#e0e0e0",
|
|
45
|
+
"text_secondary": "#b0b0b0",
|
|
46
|
+
"accent_primary": "#0f3460",
|
|
47
|
+
"accent_secondary": "#e94560",
|
|
48
|
+
"success": "#4caf50",
|
|
49
|
+
"warning": "#ff9800",
|
|
50
|
+
"error": "#f44336",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
def __init__(self) -> None:
|
|
54
|
+
"""Initialize CostReporter."""
|
|
55
|
+
self.generated_at = datetime.now()
|
|
56
|
+
|
|
57
|
+
def generate_dashboard(
|
|
58
|
+
self, violation_summary: SessionViolationSummary | None = None
|
|
59
|
+
) -> str:
|
|
60
|
+
"""
|
|
61
|
+
Generate complete interactive HTML dashboard.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
violation_summary: SessionViolationSummary with violation data
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Complete HTML document as string
|
|
68
|
+
"""
|
|
69
|
+
if violation_summary is None:
|
|
70
|
+
violation_summary = self._create_empty_summary()
|
|
71
|
+
|
|
72
|
+
# Build dashboard components
|
|
73
|
+
html_parts = [
|
|
74
|
+
self._html_header(),
|
|
75
|
+
self._html_styles(),
|
|
76
|
+
self._html_body_open(),
|
|
77
|
+
self._html_header_section(violation_summary),
|
|
78
|
+
self._html_summary_cards(violation_summary),
|
|
79
|
+
self._html_charts_section(violation_summary),
|
|
80
|
+
self._html_violations_table(violation_summary),
|
|
81
|
+
self._html_insights_section(violation_summary),
|
|
82
|
+
self._html_footer_section(),
|
|
83
|
+
self._html_body_close(),
|
|
84
|
+
self._html_scripts(),
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
return "\n".join(html_parts)
|
|
88
|
+
|
|
89
|
+
def save_dashboard(self, html: str, path: str | Path) -> None:
|
|
90
|
+
"""
|
|
91
|
+
Save HTML dashboard to file.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
html: HTML content to save
|
|
95
|
+
path: File path to save to
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
IOError: If file cannot be written
|
|
99
|
+
"""
|
|
100
|
+
path = Path(path)
|
|
101
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
102
|
+
path.write_text(html, encoding="utf-8")
|
|
103
|
+
|
|
104
|
+
def generate_summary_metrics(
|
|
105
|
+
self, violation_summary: SessionViolationSummary
|
|
106
|
+
) -> dict[str, Any]:
|
|
107
|
+
"""
|
|
108
|
+
Extract aggregated metrics from violation summary.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
violation_summary: Session violation data
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Dictionary with summary metrics
|
|
115
|
+
"""
|
|
116
|
+
total_violations = violation_summary.total_violations
|
|
117
|
+
total_waste = violation_summary.total_waste_tokens
|
|
118
|
+
compliance_rate = violation_summary.compliance_rate
|
|
119
|
+
|
|
120
|
+
# Calculate metrics
|
|
121
|
+
avg_waste_per_violation = (
|
|
122
|
+
int(total_waste / total_violations) if total_violations > 0 else 0
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Estimate direct vs delegated costs
|
|
126
|
+
direct_cost_estimate = total_waste
|
|
127
|
+
delegated_cost_estimate = int(direct_cost_estimate * 0.3) # ~70% savings
|
|
128
|
+
savings_potential = max(0, direct_cost_estimate - delegated_cost_estimate)
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
"total_violations": total_violations,
|
|
132
|
+
"total_waste_tokens": total_waste,
|
|
133
|
+
"avg_waste_per_violation": avg_waste_per_violation,
|
|
134
|
+
"compliance_rate": compliance_rate,
|
|
135
|
+
"circuit_breaker_triggered": violation_summary.circuit_breaker_triggered,
|
|
136
|
+
"direct_cost_estimate": direct_cost_estimate,
|
|
137
|
+
"delegated_cost_estimate": delegated_cost_estimate,
|
|
138
|
+
"savings_potential": savings_potential,
|
|
139
|
+
"savings_percentage": (
|
|
140
|
+
(savings_potential / direct_cost_estimate * 100)
|
|
141
|
+
if direct_cost_estimate > 0
|
|
142
|
+
else 0
|
|
143
|
+
),
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
def generate_cost_breakdown_by_type(
|
|
147
|
+
self, violation_summary: SessionViolationSummary
|
|
148
|
+
) -> dict[str, int]:
|
|
149
|
+
"""
|
|
150
|
+
Generate cost breakdown by violation type.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
violation_summary: Session violation data
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Dictionary mapping violation types to waste tokens
|
|
157
|
+
"""
|
|
158
|
+
breakdown: dict[str, int] = {}
|
|
159
|
+
|
|
160
|
+
for violation in violation_summary.violations:
|
|
161
|
+
vtype = str(violation.violation_type)
|
|
162
|
+
breakdown[vtype] = breakdown.get(vtype, 0) + violation.waste_tokens
|
|
163
|
+
|
|
164
|
+
return breakdown
|
|
165
|
+
|
|
166
|
+
def generate_cost_breakdown_by_tool(
|
|
167
|
+
self, violation_summary: SessionViolationSummary
|
|
168
|
+
) -> dict[str, int]:
|
|
169
|
+
"""
|
|
170
|
+
Generate cost breakdown by tool used.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
violation_summary: Session violation data
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Dictionary mapping tool names to waste tokens
|
|
177
|
+
"""
|
|
178
|
+
breakdown: dict[str, int] = {}
|
|
179
|
+
|
|
180
|
+
for violation in violation_summary.violations:
|
|
181
|
+
tool = violation.tool
|
|
182
|
+
breakdown[tool] = breakdown.get(tool, 0) + violation.waste_tokens
|
|
183
|
+
|
|
184
|
+
return breakdown
|
|
185
|
+
|
|
186
|
+
# ===== HTML Generation Methods =====
|
|
187
|
+
|
|
188
|
+
def _html_header(self) -> str:
|
|
189
|
+
"""Generate HTML document header."""
|
|
190
|
+
return """<!DOCTYPE html>
|
|
191
|
+
<html lang="en">
|
|
192
|
+
<head>
|
|
193
|
+
<meta charset="UTF-8">
|
|
194
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
195
|
+
<title>Cost Attribution Dashboard - HtmlGraph</title>
|
|
196
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"></script>
|
|
197
|
+
</head>"""
|
|
198
|
+
|
|
199
|
+
def _html_styles(self) -> str:
|
|
200
|
+
"""Generate CSS styles."""
|
|
201
|
+
return f"""<style>
|
|
202
|
+
* {{
|
|
203
|
+
margin: 0;
|
|
204
|
+
padding: 0;
|
|
205
|
+
box-sizing: border-box;
|
|
206
|
+
}}
|
|
207
|
+
|
|
208
|
+
body {{
|
|
209
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
210
|
+
background-color: {self.THEME["bg_primary"]};
|
|
211
|
+
color: {self.THEME["text_primary"]};
|
|
212
|
+
line-height: 1.6;
|
|
213
|
+
}}
|
|
214
|
+
|
|
215
|
+
a {{
|
|
216
|
+
color: {self.THEME["accent_secondary"]};
|
|
217
|
+
text-decoration: none;
|
|
218
|
+
}}
|
|
219
|
+
|
|
220
|
+
a:hover {{
|
|
221
|
+
text-decoration: underline;
|
|
222
|
+
}}
|
|
223
|
+
|
|
224
|
+
/* Layout */
|
|
225
|
+
.container {{
|
|
226
|
+
max-width: 1400px;
|
|
227
|
+
margin: 0 auto;
|
|
228
|
+
padding: 20px;
|
|
229
|
+
}}
|
|
230
|
+
|
|
231
|
+
/* Header */
|
|
232
|
+
.header {{
|
|
233
|
+
border-bottom: 2px solid {self.THEME["accent_primary"]};
|
|
234
|
+
padding-bottom: 20px;
|
|
235
|
+
margin-bottom: 30px;
|
|
236
|
+
}}
|
|
237
|
+
|
|
238
|
+
.header h1 {{
|
|
239
|
+
font-size: 2.5em;
|
|
240
|
+
margin-bottom: 10px;
|
|
241
|
+
color: {self.THEME["text_primary"]};
|
|
242
|
+
}}
|
|
243
|
+
|
|
244
|
+
.header .subtitle {{
|
|
245
|
+
color: {self.THEME["text_secondary"]};
|
|
246
|
+
font-size: 0.95em;
|
|
247
|
+
}}
|
|
248
|
+
|
|
249
|
+
.header-meta {{
|
|
250
|
+
margin-top: 15px;
|
|
251
|
+
display: flex;
|
|
252
|
+
gap: 20px;
|
|
253
|
+
flex-wrap: wrap;
|
|
254
|
+
font-size: 0.9em;
|
|
255
|
+
color: {self.THEME["text_secondary"]};
|
|
256
|
+
}}
|
|
257
|
+
|
|
258
|
+
/* Summary Cards */
|
|
259
|
+
.cards {{
|
|
260
|
+
display: grid;
|
|
261
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
262
|
+
gap: 20px;
|
|
263
|
+
margin-bottom: 40px;
|
|
264
|
+
}}
|
|
265
|
+
|
|
266
|
+
.card {{
|
|
267
|
+
background-color: {self.THEME["bg_secondary"]};
|
|
268
|
+
border: 1px solid {self.THEME["accent_primary"]};
|
|
269
|
+
border-radius: 8px;
|
|
270
|
+
padding: 25px;
|
|
271
|
+
transition: all 0.3s ease;
|
|
272
|
+
}}
|
|
273
|
+
|
|
274
|
+
.card:hover {{
|
|
275
|
+
border-color: {self.THEME["accent_secondary"]};
|
|
276
|
+
transform: translateY(-5px);
|
|
277
|
+
box-shadow: 0 10px 30px rgba(233, 69, 96, 0.2);
|
|
278
|
+
}}
|
|
279
|
+
|
|
280
|
+
.card-label {{
|
|
281
|
+
color: {self.THEME["text_secondary"]};
|
|
282
|
+
font-size: 0.85em;
|
|
283
|
+
text-transform: uppercase;
|
|
284
|
+
letter-spacing: 0.5px;
|
|
285
|
+
margin-bottom: 10px;
|
|
286
|
+
}}
|
|
287
|
+
|
|
288
|
+
.card-value {{
|
|
289
|
+
font-size: 2.5em;
|
|
290
|
+
font-weight: bold;
|
|
291
|
+
margin-bottom: 8px;
|
|
292
|
+
color: {self.THEME["text_primary"]};
|
|
293
|
+
}}
|
|
294
|
+
|
|
295
|
+
.card-unit {{
|
|
296
|
+
color: {self.THEME["text_secondary"]};
|
|
297
|
+
font-size: 0.9em;
|
|
298
|
+
font-weight: normal;
|
|
299
|
+
}}
|
|
300
|
+
|
|
301
|
+
.card-meta {{
|
|
302
|
+
color: {self.THEME["text_secondary"]};
|
|
303
|
+
font-size: 0.85em;
|
|
304
|
+
margin-top: 12px;
|
|
305
|
+
padding-top: 12px;
|
|
306
|
+
border-top: 1px solid {self.THEME["accent_primary"]};
|
|
307
|
+
}}
|
|
308
|
+
|
|
309
|
+
/* Status indicators */
|
|
310
|
+
.status-good {{
|
|
311
|
+
color: {self.THEME["success"]};
|
|
312
|
+
}}
|
|
313
|
+
|
|
314
|
+
.status-warning {{
|
|
315
|
+
color: {self.THEME["warning"]};
|
|
316
|
+
}}
|
|
317
|
+
|
|
318
|
+
.status-error {{
|
|
319
|
+
color: {self.THEME["error"]};
|
|
320
|
+
}}
|
|
321
|
+
|
|
322
|
+
/* Charts */
|
|
323
|
+
.charts {{
|
|
324
|
+
display: grid;
|
|
325
|
+
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
|
326
|
+
gap: 30px;
|
|
327
|
+
margin-bottom: 40px;
|
|
328
|
+
}}
|
|
329
|
+
|
|
330
|
+
.chart-container {{
|
|
331
|
+
background-color: {self.THEME["bg_secondary"]};
|
|
332
|
+
border: 1px solid {self.THEME["accent_primary"]};
|
|
333
|
+
border-radius: 8px;
|
|
334
|
+
padding: 20px;
|
|
335
|
+
}}
|
|
336
|
+
|
|
337
|
+
.chart-title {{
|
|
338
|
+
font-size: 1.3em;
|
|
339
|
+
font-weight: 600;
|
|
340
|
+
margin-bottom: 20px;
|
|
341
|
+
color: {self.THEME["text_primary"]};
|
|
342
|
+
}}
|
|
343
|
+
|
|
344
|
+
.chart-canvas {{
|
|
345
|
+
max-height: 400px;
|
|
346
|
+
}}
|
|
347
|
+
|
|
348
|
+
/* Table */
|
|
349
|
+
.table-section {{
|
|
350
|
+
margin-bottom: 40px;
|
|
351
|
+
}}
|
|
352
|
+
|
|
353
|
+
.table-section h2 {{
|
|
354
|
+
font-size: 1.5em;
|
|
355
|
+
margin-bottom: 20px;
|
|
356
|
+
color: {self.THEME["text_primary"]};
|
|
357
|
+
}}
|
|
358
|
+
|
|
359
|
+
.table-wrapper {{
|
|
360
|
+
overflow-x: auto;
|
|
361
|
+
background-color: {self.THEME["bg_secondary"]};
|
|
362
|
+
border: 1px solid {self.THEME["accent_primary"]};
|
|
363
|
+
border-radius: 8px;
|
|
364
|
+
}}
|
|
365
|
+
|
|
366
|
+
table {{
|
|
367
|
+
width: 100%;
|
|
368
|
+
border-collapse: collapse;
|
|
369
|
+
}}
|
|
370
|
+
|
|
371
|
+
th {{
|
|
372
|
+
background-color: {self.THEME["accent_primary"]};
|
|
373
|
+
padding: 15px;
|
|
374
|
+
text-align: left;
|
|
375
|
+
font-weight: 600;
|
|
376
|
+
color: {self.THEME["text_primary"]};
|
|
377
|
+
border-bottom: 2px solid {self.THEME["accent_secondary"]};
|
|
378
|
+
}}
|
|
379
|
+
|
|
380
|
+
td {{
|
|
381
|
+
padding: 12px 15px;
|
|
382
|
+
border-bottom: 1px solid {self.THEME["accent_primary"]};
|
|
383
|
+
color: {self.THEME["text_primary"]};
|
|
384
|
+
}}
|
|
385
|
+
|
|
386
|
+
tr:hover {{
|
|
387
|
+
background-color: {self.THEME["accent_primary"]};
|
|
388
|
+
}}
|
|
389
|
+
|
|
390
|
+
/* Insights */
|
|
391
|
+
.insights {{
|
|
392
|
+
background-color: {self.THEME["bg_secondary"]};
|
|
393
|
+
border: 1px solid {self.THEME["accent_primary"]};
|
|
394
|
+
border-radius: 8px;
|
|
395
|
+
padding: 25px;
|
|
396
|
+
margin-bottom: 40px;
|
|
397
|
+
}}
|
|
398
|
+
|
|
399
|
+
.insights h2 {{
|
|
400
|
+
font-size: 1.5em;
|
|
401
|
+
margin-bottom: 20px;
|
|
402
|
+
color: {self.THEME["text_primary"]};
|
|
403
|
+
}}
|
|
404
|
+
|
|
405
|
+
.insight {{
|
|
406
|
+
margin-bottom: 20px;
|
|
407
|
+
padding: 15px;
|
|
408
|
+
background-color: {self.THEME["bg_primary"]};
|
|
409
|
+
border-left: 4px solid {self.THEME["accent_secondary"]};
|
|
410
|
+
border-radius: 4px;
|
|
411
|
+
}}
|
|
412
|
+
|
|
413
|
+
.insight-title {{
|
|
414
|
+
font-weight: 600;
|
|
415
|
+
color: {self.THEME["accent_secondary"]};
|
|
416
|
+
margin-bottom: 8px;
|
|
417
|
+
}}
|
|
418
|
+
|
|
419
|
+
.insight-text {{
|
|
420
|
+
color: {self.THEME["text_secondary"]};
|
|
421
|
+
line-height: 1.6;
|
|
422
|
+
}}
|
|
423
|
+
|
|
424
|
+
/* Footer */
|
|
425
|
+
.footer {{
|
|
426
|
+
text-align: center;
|
|
427
|
+
padding-top: 20px;
|
|
428
|
+
border-top: 1px solid {self.THEME["accent_primary"]};
|
|
429
|
+
color: {self.THEME["text_secondary"]};
|
|
430
|
+
font-size: 0.85em;
|
|
431
|
+
}}
|
|
432
|
+
|
|
433
|
+
/* Responsive */
|
|
434
|
+
@media (max-width: 768px) {{
|
|
435
|
+
.charts {{
|
|
436
|
+
grid-template-columns: 1fr;
|
|
437
|
+
}}
|
|
438
|
+
|
|
439
|
+
.header h1 {{
|
|
440
|
+
font-size: 1.8em;
|
|
441
|
+
}}
|
|
442
|
+
|
|
443
|
+
.card-value {{
|
|
444
|
+
font-size: 2em;
|
|
445
|
+
}}
|
|
446
|
+
|
|
447
|
+
table {{
|
|
448
|
+
font-size: 0.9em;
|
|
449
|
+
}}
|
|
450
|
+
|
|
451
|
+
th, td {{
|
|
452
|
+
padding: 10px;
|
|
453
|
+
}}
|
|
454
|
+
}}
|
|
455
|
+
</style>"""
|
|
456
|
+
|
|
457
|
+
def _html_body_open(self) -> str:
|
|
458
|
+
"""Generate opening body tag."""
|
|
459
|
+
return "<body>"
|
|
460
|
+
|
|
461
|
+
def _html_body_close(self) -> str:
|
|
462
|
+
"""Generate closing body tag."""
|
|
463
|
+
return "</body></html>"
|
|
464
|
+
|
|
465
|
+
def _html_header_section(self, violation_summary: SessionViolationSummary) -> str:
|
|
466
|
+
"""Generate dashboard header section."""
|
|
467
|
+
generated_at = self.generated_at.strftime("%Y-%m-%d %H:%M:%S")
|
|
468
|
+
circuit_status = (
|
|
469
|
+
"YES - Circuit Breaker Triggered"
|
|
470
|
+
if violation_summary.circuit_breaker_triggered
|
|
471
|
+
else "No"
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
return f"""<div class="container">
|
|
475
|
+
<div class="header">
|
|
476
|
+
<h1>Cost Attribution Dashboard</h1>
|
|
477
|
+
<p class="subtitle">Phase 1 MVP - HtmlGraph Delegation Cost Analysis</p>
|
|
478
|
+
<div class="header-meta">
|
|
479
|
+
<span>Session ID: <strong>{violation_summary.session_id}</strong></span>
|
|
480
|
+
<span>Generated: <strong>{generated_at}</strong></span>
|
|
481
|
+
<span>Circuit Breaker: <strong class="status-{"error" if violation_summary.circuit_breaker_triggered else "good"}">{circuit_status}</strong></span>
|
|
482
|
+
</div>
|
|
483
|
+
</div>
|
|
484
|
+
"""
|
|
485
|
+
|
|
486
|
+
def _html_summary_cards(self, violation_summary: SessionViolationSummary) -> str:
|
|
487
|
+
"""Generate summary metric cards."""
|
|
488
|
+
metrics = self.generate_summary_metrics(violation_summary)
|
|
489
|
+
|
|
490
|
+
compliance_status = (
|
|
491
|
+
"status-good"
|
|
492
|
+
if metrics["compliance_rate"] >= 0.8
|
|
493
|
+
else "status-warning"
|
|
494
|
+
if metrics["compliance_rate"] >= 0.5
|
|
495
|
+
else "status-error"
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
return f"""<div class="cards">
|
|
499
|
+
<div class="card">
|
|
500
|
+
<div class="card-label">Total Violations</div>
|
|
501
|
+
<div class="card-value">{metrics["total_violations"]}</div>
|
|
502
|
+
<div class="card-meta">Direct execution violations detected</div>
|
|
503
|
+
</div>
|
|
504
|
+
|
|
505
|
+
<div class="card">
|
|
506
|
+
<div class="card-label">Total Waste</div>
|
|
507
|
+
<div class="card-value">{metrics["total_waste_tokens"]:,}<span class="card-unit"> tokens</span></div>
|
|
508
|
+
<div class="card-meta">Avg: {metrics["avg_waste_per_violation"]:,} per violation</div>
|
|
509
|
+
</div>
|
|
510
|
+
|
|
511
|
+
<div class="card">
|
|
512
|
+
<div class="card-label">Compliance Rate</div>
|
|
513
|
+
<div class="card-value"><span class="{compliance_status}">{metrics["compliance_rate"]:.0%}</span></div>
|
|
514
|
+
<div class="card-meta">Delegation adherence across session</div>
|
|
515
|
+
</div>
|
|
516
|
+
|
|
517
|
+
<div class="card">
|
|
518
|
+
<div class="card-label">Potential Savings</div>
|
|
519
|
+
<div class="card-value">{metrics["savings_potential"]:,}<span class="card-unit"> tokens</span></div>
|
|
520
|
+
<div class="card-meta"><span class="status-good">{metrics["savings_percentage"]:.0f}% reduction</span></div>
|
|
521
|
+
</div>
|
|
522
|
+
</div>
|
|
523
|
+
"""
|
|
524
|
+
|
|
525
|
+
def _html_charts_section(self, violation_summary: SessionViolationSummary) -> str:
|
|
526
|
+
"""Generate charts section with visualizations."""
|
|
527
|
+
breakdown_by_type = self.generate_cost_breakdown_by_type(violation_summary)
|
|
528
|
+
breakdown_by_tool = self.generate_cost_breakdown_by_tool(violation_summary)
|
|
529
|
+
|
|
530
|
+
# Prepare data for charts
|
|
531
|
+
type_labels = list(breakdown_by_type.keys())
|
|
532
|
+
type_data = list(breakdown_by_type.values())
|
|
533
|
+
|
|
534
|
+
tool_labels = list(breakdown_by_tool.keys())
|
|
535
|
+
tool_data = list(breakdown_by_tool.values())
|
|
536
|
+
|
|
537
|
+
# Embed data as JSON for JavaScript
|
|
538
|
+
type_data_json = json.dumps(type_labels)
|
|
539
|
+
type_values_json = json.dumps(type_data)
|
|
540
|
+
tool_data_json = json.dumps(tool_labels)
|
|
541
|
+
tool_values_json = json.dumps(tool_data)
|
|
542
|
+
|
|
543
|
+
return f"""<div class="charts">
|
|
544
|
+
<div class="chart-container">
|
|
545
|
+
<div class="chart-title">Cost by Violation Type</div>
|
|
546
|
+
<canvas id="chartByType" class="chart-canvas"></canvas>
|
|
547
|
+
</div>
|
|
548
|
+
|
|
549
|
+
<div class="chart-container">
|
|
550
|
+
<div class="chart-title">Cost by Tool</div>
|
|
551
|
+
<canvas id="chartByTool" class="chart-canvas"></canvas>
|
|
552
|
+
</div>
|
|
553
|
+
</div>
|
|
554
|
+
|
|
555
|
+
<script>
|
|
556
|
+
// Chart.js configuration
|
|
557
|
+
const chartConfig = {{
|
|
558
|
+
type_labels: {type_data_json},
|
|
559
|
+
type_data: {type_values_json},
|
|
560
|
+
tool_labels: {tool_data_json},
|
|
561
|
+
tool_data: {tool_values_json}
|
|
562
|
+
}};
|
|
563
|
+
</script>
|
|
564
|
+
"""
|
|
565
|
+
|
|
566
|
+
def _html_violations_table(self, violation_summary: SessionViolationSummary) -> str:
|
|
567
|
+
"""Generate detailed violations table."""
|
|
568
|
+
table_rows = ""
|
|
569
|
+
|
|
570
|
+
for i, violation in enumerate(violation_summary.violations, 1):
|
|
571
|
+
vtype = str(violation.violation_type)
|
|
572
|
+
warning_indicator = "⚠️" if violation.was_warned else ""
|
|
573
|
+
|
|
574
|
+
table_rows += f""" <tr>
|
|
575
|
+
<td>{i}</td>
|
|
576
|
+
<td>{violation.timestamp.strftime("%H:%M:%S")}</td>
|
|
577
|
+
<td><strong>{violation.tool}</strong></td>
|
|
578
|
+
<td>{vtype}</td>
|
|
579
|
+
<td>{violation.actual_cost_tokens:,}</td>
|
|
580
|
+
<td>{violation.optimal_cost_tokens:,}</td>
|
|
581
|
+
<td><span class="status-error">{violation.waste_tokens:,}</span></td>
|
|
582
|
+
<td>{violation.should_have_delegated_to}</td>
|
|
583
|
+
<td>{warning_indicator}</td>
|
|
584
|
+
</tr>
|
|
585
|
+
"""
|
|
586
|
+
|
|
587
|
+
return f"""<div class="table-section">
|
|
588
|
+
<h2>Violation Details</h2>
|
|
589
|
+
<div class="table-wrapper">
|
|
590
|
+
<table>
|
|
591
|
+
<thead>
|
|
592
|
+
<tr>
|
|
593
|
+
<th>#</th>
|
|
594
|
+
<th>Time</th>
|
|
595
|
+
<th>Tool</th>
|
|
596
|
+
<th>Violation Type</th>
|
|
597
|
+
<th>Actual Cost</th>
|
|
598
|
+
<th>Optimal Cost</th>
|
|
599
|
+
<th>Waste</th>
|
|
600
|
+
<th>Should Delegate To</th>
|
|
601
|
+
<th>Warned</th>
|
|
602
|
+
</tr>
|
|
603
|
+
</thead>
|
|
604
|
+
<tbody>
|
|
605
|
+
{table_rows} </tbody>
|
|
606
|
+
</table>
|
|
607
|
+
</div>
|
|
608
|
+
</div>
|
|
609
|
+
"""
|
|
610
|
+
|
|
611
|
+
def _html_insights_section(self, violation_summary: SessionViolationSummary) -> str:
|
|
612
|
+
"""Generate insights and recommendations."""
|
|
613
|
+
metrics = self.generate_summary_metrics(violation_summary)
|
|
614
|
+
breakdown_by_type = self.generate_cost_breakdown_by_type(violation_summary)
|
|
615
|
+
|
|
616
|
+
insights = []
|
|
617
|
+
|
|
618
|
+
# Insight 1: Compliance
|
|
619
|
+
if metrics["compliance_rate"] >= 0.8:
|
|
620
|
+
insights.append(
|
|
621
|
+
(
|
|
622
|
+
"Excellent Delegation Compliance",
|
|
623
|
+
"Your delegation adherence is excellent. Continue following the delegation "
|
|
624
|
+
"patterns you've established.",
|
|
625
|
+
)
|
|
626
|
+
)
|
|
627
|
+
elif metrics["compliance_rate"] >= 0.5:
|
|
628
|
+
insights.append(
|
|
629
|
+
(
|
|
630
|
+
"Moderate Compliance",
|
|
631
|
+
f"Improve delegation by {(1 - metrics['compliance_rate']) * 100:.0f}%. Focus on "
|
|
632
|
+
"delegating exploration and implementation work.",
|
|
633
|
+
)
|
|
634
|
+
)
|
|
635
|
+
else:
|
|
636
|
+
insights.append(
|
|
637
|
+
(
|
|
638
|
+
"Low Delegation Compliance",
|
|
639
|
+
"Significant opportunity for improvement. Consider using Task() and spawn_* "
|
|
640
|
+
"functions for major work categories.",
|
|
641
|
+
)
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
# Insight 2: Savings potential
|
|
645
|
+
if metrics["savings_potential"] > 0:
|
|
646
|
+
insights.append(
|
|
647
|
+
(
|
|
648
|
+
"Significant Savings Opportunity",
|
|
649
|
+
f"By properly delegating all violations, you could save approximately "
|
|
650
|
+
f"{metrics['savings_potential']:,} tokens ({metrics['savings_percentage']:.0f}% reduction).",
|
|
651
|
+
)
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
# Insight 3: Most costly violation type
|
|
655
|
+
if breakdown_by_type:
|
|
656
|
+
top_violation = max(breakdown_by_type.items(), key=lambda x: x[1])
|
|
657
|
+
insights.append(
|
|
658
|
+
(
|
|
659
|
+
f"Highest Cost: {top_violation[0]}",
|
|
660
|
+
f"This violation type accounts for {top_violation[1]:,} tokens. "
|
|
661
|
+
"Consider this area for immediate improvement.",
|
|
662
|
+
)
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
# Insight 4: Circuit breaker
|
|
666
|
+
if violation_summary.circuit_breaker_triggered:
|
|
667
|
+
insights.append(
|
|
668
|
+
(
|
|
669
|
+
"Circuit Breaker Triggered",
|
|
670
|
+
"You have exceeded 3 violations in this session. Please adopt proper delegation "
|
|
671
|
+
"practices to prevent future circuit breaker activation.",
|
|
672
|
+
)
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
insights_html = ""
|
|
676
|
+
for title, text in insights:
|
|
677
|
+
insights_html += f""" <div class="insight">
|
|
678
|
+
<div class="insight-title">{title}</div>
|
|
679
|
+
<div class="insight-text">{text}</div>
|
|
680
|
+
</div>
|
|
681
|
+
"""
|
|
682
|
+
|
|
683
|
+
return f"""<div class="insights">
|
|
684
|
+
<h2>Insights & Recommendations</h2>
|
|
685
|
+
{insights_html} </div>
|
|
686
|
+
"""
|
|
687
|
+
|
|
688
|
+
def _html_footer_section(self) -> str:
|
|
689
|
+
"""Generate footer section."""
|
|
690
|
+
return """ <div class="footer">
|
|
691
|
+
<p>HtmlGraph Cost Attribution Dashboard | Phase 1 MVP | <a href="https://code.claude.com">Claude Code</a></p>
|
|
692
|
+
</div>
|
|
693
|
+
</div>"""
|
|
694
|
+
|
|
695
|
+
def _html_scripts(self) -> str:
|
|
696
|
+
"""Generate JavaScript for interactive charts."""
|
|
697
|
+
return """<script>
|
|
698
|
+
// Initialize charts when DOM is ready
|
|
699
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
700
|
+
const config = window.chartConfig || {
|
|
701
|
+
type_labels: [],
|
|
702
|
+
type_data: [],
|
|
703
|
+
tool_labels: [],
|
|
704
|
+
tool_data: []
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
// Chart.js configuration
|
|
708
|
+
const chartOptions = {
|
|
709
|
+
responsive: true,
|
|
710
|
+
maintainAspectRatio: true,
|
|
711
|
+
plugins: {
|
|
712
|
+
legend: {
|
|
713
|
+
labels: {
|
|
714
|
+
color: '#e0e0e0',
|
|
715
|
+
font: { size: 12 }
|
|
716
|
+
}
|
|
717
|
+
},
|
|
718
|
+
tooltip: {
|
|
719
|
+
backgroundColor: 'rgba(26, 26, 46, 0.8)',
|
|
720
|
+
titleColor: '#e0e0e0',
|
|
721
|
+
bodyColor: '#b0b0b0',
|
|
722
|
+
borderColor: '#0f3460',
|
|
723
|
+
borderWidth: 1
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
scales: {
|
|
727
|
+
y: {
|
|
728
|
+
ticks: { color: '#b0b0b0' },
|
|
729
|
+
grid: { color: '#16213e' }
|
|
730
|
+
},
|
|
731
|
+
x: {
|
|
732
|
+
ticks: { color: '#b0b0b0' },
|
|
733
|
+
grid: { color: '#16213e' }
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
// Pie chart - Cost by Violation Type
|
|
739
|
+
if (config.type_labels.length > 0) {
|
|
740
|
+
const ctxType = document.getElementById('chartByType');
|
|
741
|
+
if (ctxType) {
|
|
742
|
+
new Chart(ctxType, {
|
|
743
|
+
type: 'doughnut',
|
|
744
|
+
data: {
|
|
745
|
+
labels: config.type_labels,
|
|
746
|
+
datasets: [{
|
|
747
|
+
label: 'Waste Tokens',
|
|
748
|
+
data: config.type_data,
|
|
749
|
+
backgroundColor: [
|
|
750
|
+
'#e94560',
|
|
751
|
+
'#ff9800',
|
|
752
|
+
'#ff6f00',
|
|
753
|
+
'#d84315',
|
|
754
|
+
'#c62828'
|
|
755
|
+
],
|
|
756
|
+
borderColor: '#1a1a2e',
|
|
757
|
+
borderWidth: 2
|
|
758
|
+
}]
|
|
759
|
+
},
|
|
760
|
+
options: {
|
|
761
|
+
...chartOptions,
|
|
762
|
+
plugins: {
|
|
763
|
+
...chartOptions.plugins,
|
|
764
|
+
legend: {
|
|
765
|
+
...chartOptions.plugins.legend,
|
|
766
|
+
position: 'bottom'
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Bar chart - Cost by Tool
|
|
775
|
+
if (config.tool_labels.length > 0) {
|
|
776
|
+
const ctxTool = document.getElementById('chartByTool');
|
|
777
|
+
if (ctxTool) {
|
|
778
|
+
new Chart(ctxTool, {
|
|
779
|
+
type: 'bar',
|
|
780
|
+
data: {
|
|
781
|
+
labels: config.tool_labels,
|
|
782
|
+
datasets: [{
|
|
783
|
+
label: 'Waste Tokens',
|
|
784
|
+
data: config.tool_data,
|
|
785
|
+
backgroundColor: '#0f3460',
|
|
786
|
+
borderColor: '#e94560',
|
|
787
|
+
borderWidth: 2,
|
|
788
|
+
borderRadius: 4
|
|
789
|
+
}]
|
|
790
|
+
},
|
|
791
|
+
options: {
|
|
792
|
+
...chartOptions,
|
|
793
|
+
indexAxis: 'y',
|
|
794
|
+
plugins: {
|
|
795
|
+
...chartOptions.plugins,
|
|
796
|
+
legend: {
|
|
797
|
+
...chartOptions.plugins.legend,
|
|
798
|
+
display: true
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
</script>"""
|
|
807
|
+
|
|
808
|
+
def _create_empty_summary(self) -> SessionViolationSummary:
|
|
809
|
+
"""Create an empty violation summary for dashboard initialization."""
|
|
810
|
+
return SessionViolationSummary(
|
|
811
|
+
session_id="demo-session",
|
|
812
|
+
total_violations=0,
|
|
813
|
+
violations_by_type={},
|
|
814
|
+
total_waste_tokens=0,
|
|
815
|
+
circuit_breaker_triggered=False,
|
|
816
|
+
compliance_rate=1.0,
|
|
817
|
+
violations=[],
|
|
818
|
+
)
|