@zaip-ai/cli 1.0.8 → 1.3.0
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.
- package/README.md +1 -1
- package/dist/services/adapters.d.ts +1 -1
- package/dist/services/adapters.js +1 -1
- package/dist/services/agent-adapters.js +2 -2
- package/dist/services/agent-adapters.js.map +1 -1
- package/dist/services/agent-adapters.test.js +5 -0
- package/dist/services/agent-adapters.test.js.map +1 -1
- package/dist/services/logger.test.js +2 -2
- package/dist/services/runtime-template.test.js +6 -6
- package/dist/services/template-manifest.d.ts.map +1 -1
- package/dist/services/template-manifest.js +5 -0
- package/dist/services/template-manifest.js.map +1 -1
- package/package.json +1 -1
- package/templates/.agents/architecture/software-architecture.md +131 -0
- package/templates/.agents/branding/brandbook.docx +0 -0
- package/templates/.agents/branding/brandbook.md +60 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-finalizer.toml +1 -1
- package/templates/.agents/config/clients/codex/agents/zaip-agent-orchestrator.toml +1 -1
- package/templates/.agents/dashboard/.tmp-run-list.json +254 -0
- package/templates/.agents/dashboard/.zaip-managed.json +252 -136
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-Z60df2r-.js → CostCalculationGuidePage-DxR_pgSV.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/DashboardHelpDialog-4jZLTWE_.js +2156 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-Coc6pA-1.js → PricingSettingsDialog-CKcsqb-r.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-BomxqCBU.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-DmmJD2Th.js +3 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-BwtitWa_.js +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-N5Csr0RP.js +68 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-DFBuc_qp.js → icons-vendor-C0fj2uU9.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-C4aIB1PV.js +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-CCQOHdVI.js → react-vendor-BEsJ-FYH.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-pC2_0J8x.js → traceability-B6y6Gw12.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +5 -5
- package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
- package/templates/.agents/dashboard/apps/frontend/src/App.tsx +204 -32
- package/templates/.agents/dashboard/apps/frontend/src/DashboardAlternateContent.tsx +11 -11
- package/templates/.agents/dashboard/apps/frontend/src/ProjectSettingsPage.tsx +14 -7
- package/templates/.agents/dashboard/apps/frontend/src/ProjectStackEditor.tsx +4 -2
- package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.test.ts +3 -1
- package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.tsx +14 -11
- package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +19 -4
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +24 -2
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +22 -1
- package/templates/.agents/dashboard/apps/frontend/src/overview-status-page.test.ts +3 -1
- package/templates/.agents/dashboard/apps/frontend/src/project-settings-page.css +36 -26
- package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +2 -2
- package/templates/.agents/dashboard/apps/frontend/src/snapshot-banner-semantics.test.ts +40 -6
- package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +39 -1
- package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.test.ts +89 -23
- package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +219 -29
- package/templates/.agents/dashboard/apps/frontend/src/use-app-effects.ts +61 -7
- package/templates/.agents/dashboard/apps/frontend/src/use-app-operations-pages.ts +3 -6
- package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.test.ts +2 -2
- package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.ts +3 -4
- package/templates/.agents/dashboard/apps/frontend/vite.config.ts +4 -1
- package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.test.ts +89 -19
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.tsx +31 -16
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.test.ts +43 -14
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.tsx +5 -5
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostModelsAuditTable.tsx +1 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +67 -42
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +115 -39
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +188 -38
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +75 -17
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +147 -58
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor.css +5 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/index.ts +2 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.ts +2 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +18 -7
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.ts +95 -9
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +29 -17
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.test.ts +0 -24
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.test.ts +225 -424
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +24 -15
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-terminal-day.test.ts +6 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/source-size-cost-server/build-cost-monitor-projection-agents.test.ts +31 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/source-size-cost-server/build-cost-summary-projection-duration.test.ts +414 -0
- package/templates/.agents/dashboard/modules/operations/package.json +1 -1
- package/templates/.agents/dashboard/modules/operations/src/contracts.ts +26 -23
- package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.tsx +31 -11
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.test.ts +3 -1
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +51 -21
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +47 -5
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageNavigation.tsx +1 -4
- package/templates/.agents/dashboard/modules/operations/src/react/PeriodSummaryTable.tsx +32 -10
- package/templates/.agents/dashboard/modules/operations/src/react/TaskDetailsDialog.tsx +9 -3
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.tsx +13 -5
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.test.ts +12 -4
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.tsx +1 -4
- package/templates/.agents/dashboard/modules/operations/src/react/conversation-rows.ts +1 -2
- package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +385 -4
- package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +481 -61
- package/templates/.agents/dashboard/modules/operations/src/react/export/export-layout.test.ts +14 -1
- package/templates/.agents/dashboard/modules/operations/src/react/export/xlsx-runtime-e2e.test.ts +248 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.test.ts +4 -4
- package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.ts +6 -4
- package/templates/.agents/dashboard/modules/operations/src/react/operations-global-filters.test.ts +4 -1
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +1 -1
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview.status-cards.test.ts +1 -1
- package/templates/.agents/dashboard/modules/operations/src/react/operations-page-contract.ts +46 -6
- package/templates/.agents/dashboard/modules/operations/src/react/operations-view-order.test.ts +1 -4
- package/templates/.agents/dashboard/modules/operations/src/react/period-summary.test.ts +82 -6
- package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.test.ts +2 -1
- package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.ts +1 -1
- package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.test.ts +19 -7
- package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.ts +4 -1
- package/templates/.agents/dashboard/modules/operations/src/server/quick-daily-operations-projection.ts +11 -5
- package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
- package/templates/.agents/dashboard/packages/branding/package.json +1 -1
- package/templates/.agents/dashboard/packages/branding/src/index.ts +1 -1
- package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
- package/templates/.agents/dashboard/packages/contracts/src/index.ts +1 -0
- package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +152 -149
- package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs.metafile.json +7333 -0
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +149 -146
- package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-dashboard.mjs +15 -7
- package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +44 -13
- package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +5 -2
- package/templates/.agents/dashboard/packages/local-runtime/scripts/source-size-runtime-scripts/sqlite-read-model-fixture.mjs +397 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-read-model.mjs +472 -145
- package/templates/.agents/dashboard/packages/local-runtime/scripts/test-unix-launchers.mjs +15 -3
- package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +129 -31
- package/templates/.agents/dashboard/packages/local-runtime/server.mjs +153 -150
- package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.test.ts +6 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.ts +3 -3
- package/templates/.agents/dashboard/packages/local-runtime/src/server-board.test.ts +55 -16
- package/templates/.agents/dashboard/packages/local-runtime/src/server-board.ts +23 -13
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot.test.ts +19 -4
- package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +7 -3
- package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +132 -9
- package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +126 -60
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-metric-contract.ts +16 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +1861 -350
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +1218 -352
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +119 -42
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +45 -20
- package/templates/.agents/dashboard/packages/ui/package.json +1 -1
- package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +7 -5
- package/templates/.agents/design/.zaip-managed.json +3 -3
- package/templates/.agents/design/apps/catalog/package.json +1 -1
- package/templates/.agents/design/packages/local-runtime/package.json +1 -1
- package/templates/.agents/growth/estrategia-bootstrap-90-dias.md +169 -0
- package/templates/.agents/growth/strategy.md +27 -0
- package/templates/.agents/marketing/estrategia-marketing-zaip-90-dias.md +197 -0
- package/templates/.agents/marketing/strategy.md +29 -0
- package/templates/.agents/requirements/autonomous-assurance.md +1 -1
- package/templates/.agents/requirements/comparative-audit.md +1 -1
- package/templates/.agents/requirements/context-engine.md +1 -1
- package/templates/.agents/requirements/cost-observability.md +1 -1
- package/templates/.agents/requirements/dashboard-launch.md +1 -1
- package/templates/.agents/requirements/data-export.md +1 -1
- package/templates/.agents/requirements/design-system.md +1 -1
- package/templates/.agents/requirements/execution-engine.md +1 -1
- package/templates/.agents/requirements/kanban-dashboard.md +1 -1
- package/templates/.agents/requirements/model-orchestration.md +1 -1
- package/templates/.agents/requirements/public-distribution.md +1 -1
- package/templates/.agents/requirements/software-architecture.md +1 -1
- package/templates/.agents/requirements/strategic-improvements.md +1 -1
- package/templates/.agents/requirements/work-lifecycle.md +1 -1
- package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
- package/templates/.agents/runtime/VERSION +1 -1
- package/templates/.agents/runtime/bin/zaip.mjs +30 -29
- package/templates/.agents/runtime/checksums.sha256 +6 -6
- package/templates/.agents/runtime/manifest.json +1 -1
- package/templates/.agents/runtime/pnpm-lock.sha256 +1 -1
- package/templates/.agents/runtime/provenance.json +5 -5
- package/templates/.agents/runtime/sbom.cdx.json +3 -3
- package/templates/.agents/sales/playbook.md +32 -0
- package/templates/.agents/sales/roteiro-comercial-zaip.md +252 -0
- package/templates/.agents/skills/zaip-aios/SKILL.md +1 -1
- package/templates/.agents/skills/zaip-skill-brand-governance/SKILL.md +9 -2
- package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-framework.md +39 -0
- package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-standards.md +1 -1
- package/templates/.agents/skills/zaip-skill-enforce-architecture/SKILL.md +2 -0
- package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +6 -0
- package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +6 -0
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.mjs +39 -82
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.mjs +13 -8
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.test.mjs +44 -14
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.mjs +19 -9
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.test.mjs +29 -5
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/source-size-telemetry/model-identity-reconciliation.mjs +77 -0
- package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +6 -0
- package/templates/.agents/skills/zaip-skill-sqlite-read-model/scripts/ensure-node.mjs +28 -5
- package/templates/.agents/zaip-aios/INDEX.md +10 -0
- package/templates/.agents/zaip-aios/README.md +2 -1
- package/templates/.agents/zaip-aios/VERSION +1 -1
- package/templates/.agents/zaip-aios/guides/18-catalogo-de-agentes.md +76 -0
- package/templates/.agents/zaip-aios/guides/19-assurance-e-autonomia.md +72 -0
- package/templates/.agents/zaip-aios/guides/20-instalacao-detalhada.md +330 -0
- package/templates/.agents/zaip-aios/guides/21-telemetria-monitor-custos.md +52 -0
- package/templates/.agents/zaip-aios/guides/22-fluxos-e-roteamento.md +69 -0
- package/templates/.agents/zaip-aios/guides/23-orquestracao-de-modelos.md +483 -0
- package/templates/.agents/zaip-aios/guides/24-calculo-de-custos-openai.md +85 -0
- package/templates/.agents/zaip-aios/guides/25-fontes-de-precos-openai.md +18 -0
- package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +44 -0
- package/templates/.agents/zaip-aios/guides/27-telemetria-git-native.md +167 -0
- package/templates/.agents/zaip-aios/guides/28-lifecycle-completo.md +144 -0
- package/templates/.agents/zaip-aios/guides/assets/diagrams/lifecycles-task-spec-prd.png +0 -0
- package/templates/.agents/zaip-aios/guides/assets/diagrams/prd-lifecycle.png +0 -0
- package/templates/.agents/zaip-aios/guides/assets/diagrams/spec-lifecycle.png +0 -0
- package/templates/.agents/zaip-aios/guides/assets/diagrams/task-lifecycle.png +0 -0
- package/templates/template-manifest.json +313 -177
- package/templates/.agents/dashboard/apps/frontend/dist/assets/DashboardHelpDialog-Cs6dzDmW.js +0 -1505
- package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-QE1k9yZB.css +0 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-_Pf5DA_A.js +0 -3
- package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-BVlz3DLZ.js +0 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-Cf9ctPVt.js +0 -68
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-8A4DcNXM.js +0 -2
|
@@ -74,7 +74,8 @@ type CostDimensionAggregateRow = {
|
|
|
74
74
|
function observedDimensionValue(value: unknown): string | null {
|
|
75
75
|
if (typeof value !== "string") return null;
|
|
76
76
|
const normalized = value.trim();
|
|
77
|
-
return normalized.length > 0 &&
|
|
77
|
+
return normalized.length > 0 &&
|
|
78
|
+
!["unavailable", "unknown", "mixed"].includes(normalized.toLowerCase())
|
|
78
79
|
? normalized
|
|
79
80
|
: null;
|
|
80
81
|
}
|
|
@@ -97,14 +98,21 @@ function buildCostDimensionAggregates(
|
|
|
97
98
|
): Record<"model" | "provider", CostDimensionAggregateRow[]> {
|
|
98
99
|
const dimensions = ["model", "provider"] as const;
|
|
99
100
|
const allowedProviders = new Set(
|
|
100
|
-
(
|
|
101
|
-
.
|
|
101
|
+
(
|
|
102
|
+
database.prepare("SELECT provider FROM zaip_dim_provider").all() as Array<{
|
|
103
|
+
provider: string;
|
|
104
|
+
}>
|
|
105
|
+
).map((row) => row.provider),
|
|
102
106
|
);
|
|
103
|
-
const activities = database
|
|
107
|
+
const activities = database
|
|
108
|
+
.prepare(`SELECT activity_row_id, agent_invocation_id, task_id, duration_ms,
|
|
104
109
|
source_event_count, provider, model, total_tokens, cost_usd, cost_brl
|
|
105
|
-
FROM zaip_aios_activities WHERE payload_type IS NULL`)
|
|
106
|
-
|
|
107
|
-
|
|
110
|
+
FROM zaip_aios_activities WHERE payload_type IS NULL`)
|
|
111
|
+
.all() as Array<Record<string, unknown>>;
|
|
112
|
+
const segments = database
|
|
113
|
+
.prepare(`SELECT agent_invocation_id, provider, model, total_tokens, cost_usd, cost_brl
|
|
114
|
+
FROM invocation_usage_segments`)
|
|
115
|
+
.all() as Array<Record<string, unknown>>;
|
|
108
116
|
const activityByInvocation = new Map(
|
|
109
117
|
activities
|
|
110
118
|
.filter((row) => typeof row.agent_invocation_id === "string")
|
|
@@ -118,13 +126,24 @@ function buildCostDimensionAggregates(
|
|
|
118
126
|
const current = rows.get(key);
|
|
119
127
|
if (current !== undefined) return current;
|
|
120
128
|
const created: CostDimensionAggregateRow = {
|
|
121
|
-
key,
|
|
122
|
-
|
|
129
|
+
key,
|
|
130
|
+
label: key,
|
|
131
|
+
invocation_count: 0,
|
|
132
|
+
task_count: 0,
|
|
133
|
+
total_duration: null,
|
|
134
|
+
source_event_count: null,
|
|
135
|
+
total_tokens: null,
|
|
136
|
+
total_cost_usd: null,
|
|
137
|
+
total_cost_brl: null,
|
|
123
138
|
};
|
|
124
139
|
rows.set(key, created);
|
|
125
140
|
return created;
|
|
126
141
|
};
|
|
127
|
-
const add = (
|
|
142
|
+
const add = (
|
|
143
|
+
target: CostDimensionAggregateRow,
|
|
144
|
+
field: "total_tokens" | "total_cost_usd" | "total_cost_brl",
|
|
145
|
+
value: unknown,
|
|
146
|
+
) => {
|
|
128
147
|
if (typeof value === "number") target[field] = (target[field] ?? 0) + value;
|
|
129
148
|
};
|
|
130
149
|
const segmentsByInvocation = new Map<string, Array<Record<string, unknown>>>();
|
|
@@ -148,15 +167,20 @@ function buildCostDimensionAggregates(
|
|
|
148
167
|
typeof segment.cost_usd === "number" ||
|
|
149
168
|
typeof segment.cost_brl === "number"),
|
|
150
169
|
);
|
|
151
|
-
const keys = [
|
|
170
|
+
const keys = [
|
|
171
|
+
...new Set(financialSegments.map((row) => observedDimensionValue(row[dimension])!)),
|
|
172
|
+
];
|
|
152
173
|
if (keys.length === 0) {
|
|
153
174
|
// Keep auditable, dimensionless invocation facts visible so Models
|
|
154
175
|
// and Providers reconcile to the Cost KPI instead of silently
|
|
155
176
|
// dropping them.
|
|
156
177
|
const target = ensure(dimension, "unavailable");
|
|
157
178
|
target.invocation_count += 1;
|
|
158
|
-
if (typeof activity.duration_ms === "number")
|
|
159
|
-
|
|
179
|
+
if (typeof activity.duration_ms === "number")
|
|
180
|
+
target.total_duration = (target.total_duration ?? 0) + activity.duration_ms;
|
|
181
|
+
if (typeof activity.source_event_count === "number")
|
|
182
|
+
target.source_event_count =
|
|
183
|
+
(target.source_event_count ?? 0) + activity.source_event_count;
|
|
160
184
|
add(target, "total_tokens", activity.total_tokens);
|
|
161
185
|
add(target, "total_cost_usd", activity.cost_usd);
|
|
162
186
|
add(target, "total_cost_brl", activity.cost_brl);
|
|
@@ -173,8 +197,11 @@ function buildCostDimensionAggregates(
|
|
|
173
197
|
if (keys.length === 1) {
|
|
174
198
|
const target = ensure(dimension, keys[0]!);
|
|
175
199
|
target.invocation_count += 1;
|
|
176
|
-
if (typeof activity.duration_ms === "number")
|
|
177
|
-
|
|
200
|
+
if (typeof activity.duration_ms === "number")
|
|
201
|
+
target.total_duration = (target.total_duration ?? 0) + activity.duration_ms;
|
|
202
|
+
if (typeof activity.source_event_count === "number")
|
|
203
|
+
target.source_event_count =
|
|
204
|
+
(target.source_event_count ?? 0) + activity.source_event_count;
|
|
178
205
|
}
|
|
179
206
|
}
|
|
180
207
|
}
|
|
@@ -217,16 +244,25 @@ function buildCostDimensionAggregates(
|
|
|
217
244
|
const target = ensure(dimension, key);
|
|
218
245
|
target.invocation_count += 1;
|
|
219
246
|
if (typeof activity.task_id === "string") target.task_count += 1;
|
|
220
|
-
if (typeof activity.duration_ms === "number")
|
|
221
|
-
|
|
247
|
+
if (typeof activity.duration_ms === "number")
|
|
248
|
+
target.total_duration = (target.total_duration ?? 0) + activity.duration_ms;
|
|
249
|
+
if (typeof activity.source_event_count === "number")
|
|
250
|
+
target.source_event_count = (target.source_event_count ?? 0) + activity.source_event_count;
|
|
222
251
|
add(target, "total_tokens", activity.total_tokens);
|
|
223
252
|
add(target, "total_cost_usd", activity.cost_usd);
|
|
224
253
|
add(target, "total_cost_brl", activity.cost_brl);
|
|
225
254
|
}
|
|
226
255
|
}
|
|
227
|
-
return Object.fromEntries(
|
|
228
|
-
(
|
|
229
|
-
|
|
256
|
+
return Object.fromEntries(
|
|
257
|
+
dimensions.map((dimension) => [
|
|
258
|
+
dimension,
|
|
259
|
+
[...rowsByDimension.get(dimension)!.values()].sort(
|
|
260
|
+
(left, right) =>
|
|
261
|
+
(right.total_cost_brl ?? -1) - (left.total_cost_brl ?? -1) ||
|
|
262
|
+
left.key.localeCompare(right.key),
|
|
263
|
+
),
|
|
264
|
+
]),
|
|
265
|
+
) as Record<"model" | "provider", CostDimensionAggregateRow[]>;
|
|
230
266
|
}
|
|
231
267
|
|
|
232
268
|
/** Only canonical inputs consumed by the fact or its direct dimensions. */
|
|
@@ -336,9 +372,7 @@ function stringValues(value: unknown): string[] {
|
|
|
336
372
|
: [];
|
|
337
373
|
}
|
|
338
374
|
|
|
339
|
-
function readCanonicalLifecycleRecords(
|
|
340
|
-
rows: Array<{ path: string; content: string }>,
|
|
341
|
-
): {
|
|
375
|
+
function readCanonicalLifecycleRecords(rows: Array<{ path: string; content: string }>): {
|
|
342
376
|
executions: CanonicalExecution[];
|
|
343
377
|
changes: CanonicalChange[];
|
|
344
378
|
decisions: CanonicalDecision[];
|
|
@@ -490,7 +524,9 @@ function readAgentProfiles(
|
|
|
490
524
|
// host records used `zaip-` forms of deprecated IDs. All of these are
|
|
491
525
|
// explicit identities from the catalog; no UI fallback is involved.
|
|
492
526
|
const identities = new Set([agent, catalogId]);
|
|
493
|
-
for (const alias of block.matchAll(
|
|
527
|
+
for (const alias of block.matchAll(
|
|
528
|
+
/^ {8}- kind:\s*(?:id|codex_name|codex_task_name)\s*\r?\n {10}value:\s*([^\s#]+)/gm,
|
|
529
|
+
)) {
|
|
494
530
|
const value = alias[1];
|
|
495
531
|
if (value !== undefined) identities.add(value);
|
|
496
532
|
}
|
|
@@ -586,7 +622,9 @@ type ResolvedSourceInterval = { start: string; end: string; duration: number };
|
|
|
586
622
|
* happened. It wins over Run finalization elapsed time, which may include
|
|
587
623
|
* orchestration/capture lag or be recorded much later.
|
|
588
624
|
*/
|
|
589
|
-
function resolvedSourceInterval(
|
|
625
|
+
function resolvedSourceInterval(
|
|
626
|
+
records: readonly UsageRecordedV2[],
|
|
627
|
+
): ResolvedSourceInterval | null {
|
|
590
628
|
const intervals = records.flatMap((record) => {
|
|
591
629
|
const start = record.source_started_at;
|
|
592
630
|
const end = record.source_finished_at;
|
|
@@ -677,7 +715,13 @@ function resolveDurations(
|
|
|
677
715
|
}
|
|
678
716
|
// Tokens and lifecycle timestamps are not a time source. Absence stays
|
|
679
717
|
// absent: consumers must display unavailable rather than manufacture time.
|
|
680
|
-
return {
|
|
718
|
+
return {
|
|
719
|
+
source: null,
|
|
720
|
+
effective: null,
|
|
721
|
+
provenance: null,
|
|
722
|
+
sourceStartedAt: null,
|
|
723
|
+
sourceFinishedAt: null,
|
|
724
|
+
};
|
|
681
725
|
}
|
|
682
726
|
|
|
683
727
|
function latestFxRate(rows: Array<{ path: string; content: string }>): FxRate | null {
|
|
@@ -857,7 +901,9 @@ function usagePricingAudit(
|
|
|
857
901
|
const effectiveBrl =
|
|
858
902
|
record.cost.brl ??
|
|
859
903
|
actual?.estimated_brl ??
|
|
860
|
-
(effectiveUsd !== null && fxRate !== null
|
|
904
|
+
(effectiveUsd !== null && fxRate !== null
|
|
905
|
+
? Math.round(effectiveUsd * fxRate.rate * 1_000_000) / 1_000_000
|
|
906
|
+
: null);
|
|
861
907
|
return {
|
|
862
908
|
provider,
|
|
863
909
|
model,
|
|
@@ -946,8 +992,8 @@ export function allocateAttributedTokenActiveMs(intervals: readonly TemporalInte
|
|
|
946
992
|
boundaries.add(interval.startMs);
|
|
947
993
|
boundaries.add(interval.endMs);
|
|
948
994
|
for (let cursor = interval.startMs; cursor < interval.endMs; ) {
|
|
949
|
-
const nextDay =
|
|
950
|
-
86_400_000;
|
|
995
|
+
const nextDay =
|
|
996
|
+
Date.parse(`${saoPauloDay(new Date(cursor).toISOString())}T03:00:00.000Z`) + 86_400_000;
|
|
951
997
|
if (nextDay >= interval.endMs) break;
|
|
952
998
|
boundaries.add(nextDay);
|
|
953
999
|
cursor = nextDay;
|
|
@@ -958,7 +1004,9 @@ export function allocateAttributedTokenActiveMs(intervals: readonly TemporalInte
|
|
|
958
1004
|
for (let index = 0; index < points.length - 1; index += 1) {
|
|
959
1005
|
const startMs = points[index]!;
|
|
960
1006
|
const endMs = points[index + 1]!;
|
|
961
|
-
const active = intervals.filter(
|
|
1007
|
+
const active = intervals.filter(
|
|
1008
|
+
(interval) => interval.startMs < endMs && interval.endMs > startMs,
|
|
1009
|
+
);
|
|
962
1010
|
if (active.length === 0 || endMs <= startMs) continue;
|
|
963
1011
|
const share = (endMs - startMs) / active.length;
|
|
964
1012
|
const day = saoPauloDay(new Date(startMs).toISOString());
|
|
@@ -1048,36 +1096,54 @@ export function validateSqliteReadModelSchema(path: string): {
|
|
|
1048
1096
|
} {
|
|
1049
1097
|
const database = new DatabaseSync(path, { readOnly: true });
|
|
1050
1098
|
try {
|
|
1051
|
-
const integrity = database.prepare("PRAGMA integrity_check").get() as {
|
|
1099
|
+
const integrity = database.prepare("PRAGMA integrity_check").get() as {
|
|
1100
|
+
integrity_check?: string;
|
|
1101
|
+
};
|
|
1052
1102
|
if (integrity.integrity_check !== "ok")
|
|
1053
|
-
throw new Error(
|
|
1103
|
+
throw new Error(
|
|
1104
|
+
`SQLite integrity check failed for ${path}: ${integrity.integrity_check ?? "unknown"}`,
|
|
1105
|
+
);
|
|
1054
1106
|
const objects = database
|
|
1055
1107
|
.prepare("SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view', 'index')")
|
|
1056
1108
|
.all() as Array<{ name: string; type: "table" | "view" | "index" }>;
|
|
1057
|
-
const tables = objects
|
|
1109
|
+
const tables = objects
|
|
1110
|
+
.filter((object) => object.type === "table")
|
|
1111
|
+
.map((object) => object.name)
|
|
1112
|
+
.sort();
|
|
1058
1113
|
const expectedTables = [...requiredPersistedTables].sort();
|
|
1059
|
-
if (
|
|
1060
|
-
|
|
1114
|
+
if (
|
|
1115
|
+
tables.length !== expectedTables.length ||
|
|
1116
|
+
tables.some((table, index) => table !== expectedTables[index])
|
|
1117
|
+
)
|
|
1118
|
+
throw new Error(
|
|
1119
|
+
`SQLite schema ${readModelSchemaVersion} requires exactly five persisted tables; found ${tables.join(", ") || "none"}`,
|
|
1120
|
+
);
|
|
1061
1121
|
const names = new Set(objects.map((object) => object.name));
|
|
1062
1122
|
const missingViews = requiredViews.filter((view) => !names.has(view));
|
|
1063
|
-
if (missingViews.length > 0)
|
|
1123
|
+
if (missingViews.length > 0)
|
|
1124
|
+
throw new Error(`SQLite schema is missing required views: ${missingViews.join(", ")}`);
|
|
1064
1125
|
const views = objects.filter((object) => object.type === "view").map((object) => object.name);
|
|
1065
1126
|
if (views.some((view) => !view.startsWith("zaip_vw_")))
|
|
1066
1127
|
throw new Error("SQLite persisted views must use the zaip_vw_ namespace");
|
|
1067
1128
|
const missingIndexes = requiredIndexes.filter((index) => !names.has(index));
|
|
1068
|
-
if (missingIndexes.length > 0)
|
|
1129
|
+
if (missingIndexes.length > 0)
|
|
1130
|
+
throw new Error(`SQLite schema is missing required indexes: ${missingIndexes.join(", ")}`);
|
|
1069
1131
|
const schemaVersion = (
|
|
1070
1132
|
database
|
|
1071
1133
|
.prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key = ?")
|
|
1072
1134
|
.get("schema_version") as { value?: string } | undefined
|
|
1073
1135
|
)?.value;
|
|
1074
1136
|
if (schemaVersion !== readModelSchemaVersion)
|
|
1075
|
-
throw new Error(
|
|
1137
|
+
throw new Error(
|
|
1138
|
+
`SQLite schema version must be ${readModelSchemaVersion}; found ${schemaVersion ?? "missing"}`,
|
|
1139
|
+
);
|
|
1076
1140
|
return {
|
|
1077
1141
|
schemaVersion,
|
|
1078
1142
|
tableCount: tables.length,
|
|
1079
1143
|
viewCount: views.length,
|
|
1080
|
-
indexCount: objects.filter(
|
|
1144
|
+
indexCount: objects.filter(
|
|
1145
|
+
(object) => object.type === "index" && !object.name.startsWith("sqlite_"),
|
|
1146
|
+
).length,
|
|
1081
1147
|
};
|
|
1082
1148
|
} finally {
|
|
1083
1149
|
database.close();
|
|
@@ -1087,6 +1153,7 @@ export function validateSqliteReadModelSchema(path: string): {
|
|
|
1087
1153
|
/** Recreates the disposable comparison read model from ZAIP records and configuration. */
|
|
1088
1154
|
export async function rebuildSqliteReadModel(
|
|
1089
1155
|
workspaceRoot: string,
|
|
1156
|
+
options: { forceCanonical?: boolean } = {},
|
|
1090
1157
|
): Promise<{
|
|
1091
1158
|
files: number;
|
|
1092
1159
|
path: string;
|
|
@@ -1099,14 +1166,22 @@ export async function rebuildSqliteReadModel(
|
|
|
1099
1166
|
const sourceSnapshot = await sourceFingerprint(workspaceRoot);
|
|
1100
1167
|
const reportLaunchProgress = (phase: string, percent: number, detail: string) => {
|
|
1101
1168
|
if (process.env.ZAIP_DASHBOARD_LAUNCH_PROGRESS !== "1") return;
|
|
1102
|
-
process.stderr.write(
|
|
1169
|
+
process.stderr.write(
|
|
1170
|
+
`${JSON.stringify({ event: "dashboard_sqlite_rebuild_progress", phase, percent, detail })}\n`,
|
|
1171
|
+
);
|
|
1103
1172
|
};
|
|
1104
1173
|
const { fingerprint } = sourceSnapshot;
|
|
1105
1174
|
const fingerprintMs = Math.round(performance.now() - fingerprintStartedAt);
|
|
1106
1175
|
const existingFiles = readSnapshotFileCount(path, fingerprint);
|
|
1107
1176
|
if (existingFiles !== null) {
|
|
1108
1177
|
validateSqliteReadModelSchema(path);
|
|
1109
|
-
return {
|
|
1178
|
+
return {
|
|
1179
|
+
files: existingFiles,
|
|
1180
|
+
path,
|
|
1181
|
+
cache: "generation",
|
|
1182
|
+
fingerprint_ms: fingerprintMs,
|
|
1183
|
+
materialize_ms: 0,
|
|
1184
|
+
};
|
|
1110
1185
|
}
|
|
1111
1186
|
|
|
1112
1187
|
// The launcher intentionally gives every live server a private database path
|
|
@@ -1135,7 +1210,7 @@ export async function rebuildSqliteReadModel(
|
|
|
1135
1210
|
// reconstruction merely because a record changed since the last snapshot.
|
|
1136
1211
|
// The launcher labels this state as updating and starts an isolated refresh;
|
|
1137
1212
|
// this process still serves only a schema-validated immutable seed.
|
|
1138
|
-
if (process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED === "1") {
|
|
1213
|
+
if (!options.forceCanonical && process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED === "1") {
|
|
1139
1214
|
const fallback = await mostRecentCompatibleSnapshot(dirname(cachePath));
|
|
1140
1215
|
if (fallback !== null) {
|
|
1141
1216
|
const materializeStartedAt = performance.now();
|
|
@@ -1150,7 +1225,11 @@ export async function rebuildSqliteReadModel(
|
|
|
1150
1225
|
}
|
|
1151
1226
|
}
|
|
1152
1227
|
|
|
1153
|
-
reportLaunchProgress(
|
|
1228
|
+
reportLaunchProgress(
|
|
1229
|
+
"source_inventory",
|
|
1230
|
+
52,
|
|
1231
|
+
`Encontradas ${sourceSnapshot.candidates.length} fontes canônicas.`,
|
|
1232
|
+
);
|
|
1154
1233
|
await mkdir(dirname(cachePath), { recursive: true });
|
|
1155
1234
|
const temporaryPath = `${cachePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
1156
1235
|
const database = new DatabaseSync(temporaryPath);
|
|
@@ -1225,11 +1304,17 @@ export async function rebuildSqliteReadModel(
|
|
|
1225
1304
|
let files = 0;
|
|
1226
1305
|
const skipped = 0;
|
|
1227
1306
|
const sourceReadBatchSize = 32;
|
|
1228
|
-
for (
|
|
1307
|
+
for (
|
|
1308
|
+
let offset = 0;
|
|
1309
|
+
offset < sourceSnapshot.candidates.length;
|
|
1310
|
+
offset += sourceReadBatchSize
|
|
1311
|
+
) {
|
|
1229
1312
|
const batch = sourceSnapshot.candidates.slice(offset, offset + sourceReadBatchSize);
|
|
1230
1313
|
// Parallel reads reduce cold I/O latency, while the following ordered
|
|
1231
1314
|
// loop retains deterministic SQLite inserts and canonical projections.
|
|
1232
|
-
const contents = await Promise.all(
|
|
1315
|
+
const contents = await Promise.all(
|
|
1316
|
+
batch.map((candidate) => readFile(candidate.path, "utf8")),
|
|
1317
|
+
);
|
|
1233
1318
|
for (let index = 0; index < batch.length; index += 1) {
|
|
1234
1319
|
const candidate = batch[index];
|
|
1235
1320
|
const content = contents[index];
|
|
@@ -1267,8 +1352,13 @@ export async function rebuildSqliteReadModel(
|
|
|
1267
1352
|
);
|
|
1268
1353
|
files += 1;
|
|
1269
1354
|
}
|
|
1270
|
-
const completedPercent =
|
|
1271
|
-
|
|
1355
|
+
const completedPercent =
|
|
1356
|
+
52 + Math.round(((offset + batch.length) / sourceSnapshot.candidates.length) * 20);
|
|
1357
|
+
reportLaunchProgress(
|
|
1358
|
+
"source_import",
|
|
1359
|
+
completedPercent,
|
|
1360
|
+
`Lidas ${Math.min(offset + batch.length, sourceSnapshot.candidates.length)} de ${sourceSnapshot.candidates.length} fontes.`,
|
|
1361
|
+
);
|
|
1272
1362
|
}
|
|
1273
1363
|
reportLaunchProgress("projection_build", 74, "Organizando fatos e projeções do banco local.");
|
|
1274
1364
|
const canonicalEntities = readCanonicalEntities(
|
|
@@ -1673,7 +1763,8 @@ export async function rebuildSqliteReadModel(
|
|
|
1673
1763
|
canonicalInsertActivity.run(...columns.map((column) => activity[column] ?? null));
|
|
1674
1764
|
if (totalTokens > 0 && durations.effective !== null && durations.effective >= 0) {
|
|
1675
1765
|
const startMs = Date.parse(durations.sourceStartedAt ?? started.occurred_at);
|
|
1676
|
-
const endMs =
|
|
1766
|
+
const endMs =
|
|
1767
|
+
Date.parse(durations.sourceFinishedAt ?? "") || startMs + durations.effective;
|
|
1677
1768
|
if (Number.isFinite(startMs) && Number.isFinite(endMs) && endMs > startMs)
|
|
1678
1769
|
temporalIntervals.push({ activityRowId, startMs, endMs });
|
|
1679
1770
|
else if (durations.effective === 0)
|
|
@@ -1828,16 +1919,46 @@ export async function rebuildSqliteReadModel(
|
|
|
1828
1919
|
'TECH-' || lower(hex(randomblob(16))), 'technical_payload', '${payloadType}:' || row_key,
|
|
1829
1920
|
'sqlite://${payloadType}', '', datetime('now'), 'technical', 'technical', '${payloadType}', payload_json
|
|
1830
1921
|
FROM (${selectSql});`);
|
|
1831
|
-
persistPayload(
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
persistPayload(
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
persistPayload(
|
|
1840
|
-
|
|
1922
|
+
persistPayload(
|
|
1923
|
+
"metadata",
|
|
1924
|
+
"SELECT key AS row_key, json_object('key', key, 'value', value) AS payload_json FROM read_model_metadata",
|
|
1925
|
+
);
|
|
1926
|
+
persistPayload(
|
|
1927
|
+
"source_state",
|
|
1928
|
+
"SELECT source_path AS row_key, json_object('source_path', source_path, 'source_hash', source_hash, 'mtime_ms', mtime_ms, 'size_bytes', size_bytes, 'source_kind', source_kind, 'last_processed_at', last_processed_at) AS payload_json FROM read_model_source_state",
|
|
1929
|
+
);
|
|
1930
|
+
persistPayload(
|
|
1931
|
+
"file",
|
|
1932
|
+
"SELECT path AS row_key, json_object('path', path, 'content', content, 'mtime_ms', mtime_ms, 'size_bytes', size_bytes, 'content_sha256', content_sha256, 'artifact_kind', artifact_kind, 'artifact_id', artifact_id, 'artifact_occurred_at', artifact_occurred_at, 'artifact_occurred_day_sp', artifact_occurred_day_sp) AS payload_json FROM files",
|
|
1933
|
+
);
|
|
1934
|
+
persistPayload(
|
|
1935
|
+
"work_item",
|
|
1936
|
+
"SELECT entity_kind || ':' || id AS row_key, json_object('entity_kind', entity_kind, 'id', id, 'title', title, 'status', status, 'stage', stage, 'risk', risk, 'occurred_at', occurred_at, 'occurred_day_sp', occurred_day_sp) AS payload_json FROM canonical_work_items",
|
|
1937
|
+
);
|
|
1938
|
+
persistPayload(
|
|
1939
|
+
"execution",
|
|
1940
|
+
"SELECT id AS row_key, json_object('id', id, 'status', status, 'kind', kind, 'origin_type', origin_type, 'origin_id', origin_id, 'created_at', created_at, 'occurred_day_sp', occurred_day_sp, 'finished_at', finished_at, 'source_path', source_path) AS payload_json FROM canonical_executions",
|
|
1941
|
+
);
|
|
1942
|
+
persistPayload(
|
|
1943
|
+
"change",
|
|
1944
|
+
"SELECT id AS row_key, json_object('id', id, 'title', title, 'status', status, 'risk', risk, 'source_type', source_type, 'source_id', source_id, 'source_spec_id', source_spec_id, 'source_prd_id', source_prd_id, 'requirement_ids_json', requirement_ids_json, 'acceptance_ids_json', acceptance_ids_json, 'task_ids_json', task_ids_json, 'result', result, 'created_at', created_at, 'occurred_day_sp', occurred_day_sp, 'source_path', source_path) AS payload_json FROM canonical_changes",
|
|
1945
|
+
);
|
|
1946
|
+
persistPayload(
|
|
1947
|
+
"decision",
|
|
1948
|
+
"SELECT id AS row_key, json_object('id', id, 'title', title, 'origin', origin, 'scope_type', scope_type, 'scope_id', scope_id, 'created_at', created_at, 'occurred_day_sp', occurred_day_sp, 'source_path', source_path) AS payload_json FROM canonical_decisions",
|
|
1949
|
+
);
|
|
1950
|
+
persistPayload(
|
|
1951
|
+
"run_index",
|
|
1952
|
+
"SELECT run_id AS row_key, json_object('run_id', run_id, 'occurred_at', occurred_at, 'occurred_day_sp', occurred_day_sp, 'task_id', task_id, 'phase', phase, 'run_role', run_role, 'duration_ms', duration_ms, 'source_event_count', source_event_count, 'zaip_agent', zaip_agent, 'provider', provider, 'model', model, 'status', status) AS payload_json FROM canonical_run_index",
|
|
1953
|
+
);
|
|
1954
|
+
persistPayload(
|
|
1955
|
+
"usage_segment",
|
|
1956
|
+
"SELECT agent_invocation_id || ':' || rowid AS row_key, json_object('agent_invocation_id', agent_invocation_id, 'provider', provider, 'model', model, 'service_tier', service_tier, 'reasoning_effort', reasoning_effort, 'input_tokens', input_tokens, 'cached_input_tokens', cached_input_tokens, 'uncached_input_tokens', uncached_input_tokens, 'output_tokens', output_tokens, 'reasoning_tokens', reasoning_tokens, 'visible_output_tokens', visible_output_tokens, 'total_tokens', total_tokens, 'input_rate_usd_per_million', input_rate_usd_per_million, 'cached_input_rate_usd_per_million', cached_input_rate_usd_per_million, 'output_rate_usd_per_million', output_rate_usd_per_million, 'reasoning_rate_usd_per_million', reasoning_rate_usd_per_million, 'context_input_multiplier', context_input_multiplier, 'context_output_multiplier', context_output_multiplier, 'speed_cost_multiplier', speed_cost_multiplier, 'pricing_scenario', pricing_scenario, 'standard_cost_usd', standard_cost_usd, 'speed_uplift_usd', speed_uplift_usd, 'input_cost_usd', input_cost_usd, 'cached_input_cost_usd', cached_input_cost_usd, 'output_cost_usd', output_cost_usd, 'reasoning_cost_usd', reasoning_cost_usd, 'calculated_cost_usd', calculated_cost_usd, 'recorded_cost_usd', recorded_cost_usd, 'cost_variance_usd', cost_variance_usd, 'cost_usd', cost_usd, 'cost_brl', cost_brl, 'cost_method', cost_method, 'pricing_model', pricing_model, 'pricing_source', pricing_source, 'fx_rate', fx_rate) AS payload_json FROM invocation_usage_segments",
|
|
1957
|
+
);
|
|
1958
|
+
persistPayload(
|
|
1959
|
+
"duration_allocation",
|
|
1960
|
+
"SELECT activity_row_id || ':' || occurred_day_sp AS row_key, json_object('activity_row_id', activity_row_id, 'occurred_day_sp', occurred_day_sp, 'attributed_token_active_ms', attributed_token_active_ms) AS payload_json FROM attributed_token_active_facts",
|
|
1961
|
+
);
|
|
1841
1962
|
// Configuration-backed dimensions contain the current selectable
|
|
1842
1963
|
// catalog even when no activity has used an option yet. Historical
|
|
1843
1964
|
// values remain faithfully recorded in the fact without becoming
|
|
@@ -2196,7 +2317,10 @@ export async function rebuildSqliteReadModel(
|
|
|
2196
2317
|
CREATE VIEW vw_prd_summary_daily AS SELECT occurred_day_sp AS day, COUNT(*) AS prd_count, SUM(llm_call_count) AS llm_call_count FROM prd_summary GROUP BY occurred_day_sp;
|
|
2197
2318
|
CREATE VIEW vw_task_status_daily AS SELECT occurred_day_sp AS day, status, COUNT(*) AS task_count FROM task_summary GROUP BY occurred_day_sp, status;
|
|
2198
2319
|
CREATE VIEW vw_task_risk_daily AS SELECT occurred_day_sp AS day, risk, COUNT(*) AS task_count FROM task_summary GROUP BY occurred_day_sp, risk;
|
|
2199
|
-
|
|
2320
|
+
-- The Operations work-list export reads this public union directly.
|
|
2321
|
+
-- Keep the known aggregate facts from each entity summary so its
|
|
2322
|
+
-- snapshot is equivalent to the metrics shown by the work views.
|
|
2323
|
+
CREATE VIEW vw_entities AS SELECT 'task' AS entity_kind, id, title, status, risk, occurred_at, occurred_day_sp, invocation_count, total_duration, total_tokens, total_cost_usd, total_cost_brl FROM task_summary UNION ALL SELECT 'spec', id, title, status, risk, occurred_at, occurred_day_sp, invocation_count, total_duration, total_tokens, total_cost_usd, total_cost_brl FROM spec_summary UNION ALL SELECT 'prd', id, title, status, risk, occurred_at, occurred_day_sp, invocation_count, total_duration, total_tokens, total_cost_usd, total_cost_brl FROM prd_summary;
|
|
2200
2324
|
CREATE VIEW vw_cost_group_daily AS SELECT * FROM cost_monitor_group_daily_cache;
|
|
2201
2325
|
-- Narrow temporal Cost surfaces. Do not route the visible Cost
|
|
2202
2326
|
-- tables through the three-grain overview union: it expands every
|
|
@@ -2740,7 +2864,11 @@ export async function rebuildSqliteReadModel(
|
|
|
2740
2864
|
},
|
|
2741
2865
|
);
|
|
2742
2866
|
database.exec(deduplicatedViewSql);
|
|
2743
|
-
reportLaunchProgress(
|
|
2867
|
+
reportLaunchProgress(
|
|
2868
|
+
"projection_finalize",
|
|
2869
|
+
80,
|
|
2870
|
+
"Finalizando índices e a cópia local do dashboard.",
|
|
2871
|
+
);
|
|
2744
2872
|
completedFiles = files;
|
|
2745
2873
|
} catch (error) {
|
|
2746
2874
|
database.exec("ROLLBACK;");
|
|
@@ -2786,21 +2914,25 @@ export async function rebuildSqliteReadModel(
|
|
|
2786
2914
|
for (const target of [cachePath, path]) {
|
|
2787
2915
|
const targetDatabase = new DatabaseSync(target);
|
|
2788
2916
|
try {
|
|
2789
|
-
targetDatabase
|
|
2917
|
+
targetDatabase
|
|
2918
|
+
.prepare(`INSERT INTO zaip_aios_activities(
|
|
2790
2919
|
activity_row_id, activity_source_kind, activity_key, source_path, source_hash,
|
|
2791
2920
|
recorded_at, run_role, root_run_id, payload_type, payload_json
|
|
2792
2921
|
) VALUES (?, 'technical_payload', 'first_fold_viewport_cache:all_time',
|
|
2793
2922
|
'sqlite://first_fold_viewport_cache/all_time', '', datetime('now'), 'technical', 'technical',
|
|
2794
2923
|
'first_fold_viewport_cache', ?)`)
|
|
2795
2924
|
.run(`TECH-${randomUUID().toLowerCase()}`, firstFoldPayload);
|
|
2796
|
-
targetDatabase
|
|
2925
|
+
targetDatabase
|
|
2926
|
+
.prepare(`INSERT INTO zaip_aios_activities(
|
|
2797
2927
|
activity_row_id, activity_source_kind, activity_key, source_path, source_hash,
|
|
2798
2928
|
recorded_at, run_role, root_run_id, payload_type, payload_json
|
|
2799
2929
|
) VALUES (?, 'technical_payload', 'first_fold_monthly_quick_cache:default',
|
|
2800
2930
|
'sqlite://first_fold_monthly_quick_cache/default', '', datetime('now'), 'technical', 'technical',
|
|
2801
2931
|
'first_fold_monthly_quick_cache', ?)`)
|
|
2802
2932
|
.run(`TECH-${randomUUID().toLowerCase()}`, firstFoldMonthlyQuickPayload);
|
|
2803
|
-
} finally {
|
|
2933
|
+
} finally {
|
|
2934
|
+
targetDatabase.close();
|
|
2935
|
+
}
|
|
2804
2936
|
}
|
|
2805
2937
|
reportLaunchProgress("snapshot_ready", 86, "A cópia privada do banco local está pronta.");
|
|
2806
2938
|
validateSqliteReadModelSchema(path);
|
|
@@ -2836,7 +2968,8 @@ async function pruneSnapshotCache(directory: string, activePath: string): Promis
|
|
|
2836
2968
|
// Interrupted SQLite builds leave only cache-local temporary files. They
|
|
2837
2969
|
// cannot be valid immutable seeds and otherwise inflate the launch folder.
|
|
2838
2970
|
for (const entry of entries) {
|
|
2839
|
-
if (!entry.isFile() || !entry.name.includes(".sqlite.") || !entry.name.includes(".tmp"))
|
|
2971
|
+
if (!entry.isFile() || !entry.name.includes(".sqlite.") || !entry.name.includes(".tmp"))
|
|
2972
|
+
continue;
|
|
2840
2973
|
const candidate = resolve(directory, entry.name);
|
|
2841
2974
|
const age = Date.now() - (await stat(candidate)).mtimeMs;
|
|
2842
2975
|
if (age > 60_000) await rm(candidate, { force: true }).catch(() => {});
|
|
@@ -2870,10 +3003,18 @@ function readSnapshotFileCount(path: string, fingerprint: string): number | null
|
|
|
2870
3003
|
}
|
|
2871
3004
|
}
|
|
2872
3005
|
|
|
2873
|
-
async function mostRecentCompatibleSnapshot(
|
|
3006
|
+
async function mostRecentCompatibleSnapshot(
|
|
3007
|
+
directory: string,
|
|
3008
|
+
): Promise<{ path: string; files: number } | null> {
|
|
2874
3009
|
const entries = await readdir(directory, { withFileTypes: true }).catch(() => []);
|
|
2875
|
-
const candidates = await Promise.all(
|
|
2876
|
-
|
|
3010
|
+
const candidates = await Promise.all(
|
|
3011
|
+
entries
|
|
3012
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".sqlite"))
|
|
3013
|
+
.map(async (entry) => ({
|
|
3014
|
+
path: join(directory, entry.name),
|
|
3015
|
+
mtime: (await stat(join(directory, entry.name))).mtimeMs,
|
|
3016
|
+
})),
|
|
3017
|
+
);
|
|
2877
3018
|
for (const candidate of candidates.sort((left, right) => right.mtime - left.mtime)) {
|
|
2878
3019
|
const files = readCompatibleSnapshotFileCount(candidate.path);
|
|
2879
3020
|
if (files !== null) return { path: candidate.path, files };
|
|
@@ -2884,11 +3025,23 @@ async function mostRecentCompatibleSnapshot(directory: string): Promise<{ path:
|
|
|
2884
3025
|
function readCompatibleSnapshotFileCount(path: string): number | null {
|
|
2885
3026
|
try {
|
|
2886
3027
|
const database = new DatabaseSync(path, { readOnly: true });
|
|
2887
|
-
const schema = (
|
|
2888
|
-
|
|
3028
|
+
const schema = (
|
|
3029
|
+
database
|
|
3030
|
+
.prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key = ?")
|
|
3031
|
+
.get("schema_version") as { value?: string } | undefined
|
|
3032
|
+
)?.value;
|
|
3033
|
+
const files = Number(
|
|
3034
|
+
(
|
|
3035
|
+
database
|
|
3036
|
+
.prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key = ?")
|
|
3037
|
+
.get("source_file_count") as { value?: string } | undefined
|
|
3038
|
+
)?.value ?? 0,
|
|
3039
|
+
);
|
|
2889
3040
|
database.close();
|
|
2890
3041
|
return schema === readModelSchemaVersion ? files : null;
|
|
2891
|
-
} catch {
|
|
3042
|
+
} catch {
|
|
3043
|
+
return null;
|
|
3044
|
+
}
|
|
2892
3045
|
}
|
|
2893
3046
|
|
|
2894
3047
|
/**
|
|
@@ -3008,34 +3161,37 @@ export type SqliteFirstFoldAllTime = {
|
|
|
3008
3161
|
total_cost_brl: number | null;
|
|
3009
3162
|
duration_share: number;
|
|
3010
3163
|
}>;
|
|
3011
|
-
macro_metrics: Record<
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
total_tokens: number | null;
|
|
3018
|
-
total_cost_usd: number | null;
|
|
3019
|
-
total_cost_brl: number | null;
|
|
3020
|
-
average_duration_ms: number | null;
|
|
3021
|
-
average_tokens: number | null;
|
|
3022
|
-
average_cost_usd: number | null;
|
|
3023
|
-
average_cost_brl: number | null;
|
|
3024
|
-
latest_created_at: string | null;
|
|
3025
|
-
top_entities: Array<{
|
|
3026
|
-
rank: number;
|
|
3027
|
-
entity_id: string;
|
|
3028
|
-
title: string;
|
|
3029
|
-
status: string | null;
|
|
3164
|
+
macro_metrics: Record<
|
|
3165
|
+
"specs" | "prds",
|
|
3166
|
+
{
|
|
3167
|
+
status_counts: Record<string, number>;
|
|
3168
|
+
classification_counts: Record<string, number>;
|
|
3169
|
+
entity_count: number;
|
|
3030
3170
|
total_run_count: number;
|
|
3031
|
-
llm_call_count: number | null;
|
|
3032
3171
|
total_duration_ms: number | null;
|
|
3033
3172
|
total_tokens: number | null;
|
|
3034
3173
|
total_cost_usd: number | null;
|
|
3035
3174
|
total_cost_brl: number | null;
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3175
|
+
average_duration_ms: number | null;
|
|
3176
|
+
average_tokens: number | null;
|
|
3177
|
+
average_cost_usd: number | null;
|
|
3178
|
+
average_cost_brl: number | null;
|
|
3179
|
+
latest_created_at: string | null;
|
|
3180
|
+
top_entities: Array<{
|
|
3181
|
+
rank: number;
|
|
3182
|
+
entity_id: string;
|
|
3183
|
+
title: string;
|
|
3184
|
+
status: string | null;
|
|
3185
|
+
total_run_count: number;
|
|
3186
|
+
llm_call_count: number | null;
|
|
3187
|
+
total_duration_ms: number | null;
|
|
3188
|
+
total_tokens: number | null;
|
|
3189
|
+
total_cost_usd: number | null;
|
|
3190
|
+
total_cost_brl: number | null;
|
|
3191
|
+
duration_share: number;
|
|
3192
|
+
}>;
|
|
3193
|
+
}
|
|
3194
|
+
>;
|
|
3039
3195
|
};
|
|
3040
3196
|
|
|
3041
3197
|
/** The only all-time facts needed before the Operations first fold is painted. */
|
|
@@ -3112,11 +3268,19 @@ export const sqliteViewMetadata = {
|
|
|
3112
3268
|
first_fold_task_metrics_all_time: { date: null, id: null, order: "task_count DESC" },
|
|
3113
3269
|
first_fold_top_tasks_all_time: { date: null, id: "task_id", order: "rank" },
|
|
3114
3270
|
first_fold_spec_status_all_time: { date: null, id: "stage", order: "stage" },
|
|
3115
|
-
first_fold_spec_classification_all_time: {
|
|
3271
|
+
first_fold_spec_classification_all_time: {
|
|
3272
|
+
date: null,
|
|
3273
|
+
id: "classification",
|
|
3274
|
+
order: "classification",
|
|
3275
|
+
},
|
|
3116
3276
|
first_fold_spec_metrics_all_time: { date: null, id: null, order: "entity_count DESC" },
|
|
3117
3277
|
first_fold_top_specs_all_time: { date: null, id: "entity_id", order: "rank" },
|
|
3118
3278
|
first_fold_prd_status_all_time: { date: null, id: "stage", order: "stage" },
|
|
3119
|
-
first_fold_prd_classification_all_time: {
|
|
3279
|
+
first_fold_prd_classification_all_time: {
|
|
3280
|
+
date: null,
|
|
3281
|
+
id: "classification",
|
|
3282
|
+
order: "classification",
|
|
3283
|
+
},
|
|
3120
3284
|
first_fold_prd_metrics_all_time: { date: null, id: null, order: "entity_count DESC" },
|
|
3121
3285
|
first_fold_top_prds_all_time: { date: null, id: "entity_id", order: "rank" },
|
|
3122
3286
|
first_fold_task: { date: "occurred_day_sp", id: "id", order: "occurred_at DESC, id DESC" },
|
|
@@ -3207,9 +3371,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3207
3371
|
const cached = this.unfilteredViewPageCache.get(view);
|
|
3208
3372
|
if (cached !== undefined) return cached;
|
|
3209
3373
|
const order = sqliteViewMetadata[view].order;
|
|
3210
|
-
const rows = this.database
|
|
3211
|
-
|
|
3212
|
-
|
|
3374
|
+
const rows = this.database.prepare(`SELECT * FROM ${view} ORDER BY ${order}`).all() as Array<
|
|
3375
|
+
Record<string, unknown>
|
|
3376
|
+
>;
|
|
3213
3377
|
this.unfilteredViewPageCache.set(view, rows);
|
|
3214
3378
|
return rows;
|
|
3215
3379
|
}
|
|
@@ -3353,12 +3517,16 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3353
3517
|
const providerValues = (filter.providers ?? []).filter((value) => value !== "unavailable");
|
|
3354
3518
|
const modelValues = (filter.models ?? []).filter((value) => value !== "unavailable");
|
|
3355
3519
|
const requestsUnavailable =
|
|
3356
|
-
filter.providers?.includes("unavailable") === true ||
|
|
3520
|
+
filter.providers?.includes("unavailable") === true ||
|
|
3521
|
+
filter.models?.includes("unavailable") === true;
|
|
3357
3522
|
if (requestsUnavailable) {
|
|
3358
3523
|
const unavailableDimension = (column: "provider" | "model", selected?: readonly string[]) => {
|
|
3359
3524
|
if (selected === undefined || selected.length === 0) return;
|
|
3360
3525
|
const available = selected.filter((value) => value !== "unavailable");
|
|
3361
|
-
const matches =
|
|
3526
|
+
const matches =
|
|
3527
|
+
available.length === 0
|
|
3528
|
+
? "0"
|
|
3529
|
+
: `EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id=a.agent_run_id AND u.${column} IN (${available.map(() => "?").join(",")}))`;
|
|
3362
3530
|
if (available.length > 0) values.push(...available);
|
|
3363
3531
|
const missing = `NOT EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id=a.agent_run_id AND u.${column} IS NOT NULL AND u.${column} <> 'unavailable')`;
|
|
3364
3532
|
clauses.push(selected.includes("unavailable") ? `(${matches} OR ${missing})` : matches);
|
|
@@ -3377,7 +3545,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3377
3545
|
}
|
|
3378
3546
|
// Provider and model describe one observed usage segment. Independent
|
|
3379
3547
|
// EXISTS clauses would let a crossed provider/model pair match a mixed Run.
|
|
3380
|
-
clauses.push(
|
|
3548
|
+
clauses.push(
|
|
3549
|
+
`EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id=a.agent_run_id AND ${segmentClauses.join(" AND ")})`,
|
|
3550
|
+
);
|
|
3381
3551
|
}
|
|
3382
3552
|
const dimensions = [
|
|
3383
3553
|
["zaip_agent", filter.agents],
|
|
@@ -3415,12 +3585,16 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3415
3585
|
const providerValues = (filter.providers ?? []).filter((value) => value !== "unavailable");
|
|
3416
3586
|
const modelValues = (filter.models ?? []).filter((value) => value !== "unavailable");
|
|
3417
3587
|
const requestsUnavailable =
|
|
3418
|
-
filter.providers?.includes("unavailable") === true ||
|
|
3588
|
+
filter.providers?.includes("unavailable") === true ||
|
|
3589
|
+
filter.models?.includes("unavailable") === true;
|
|
3419
3590
|
if (requestsUnavailable) {
|
|
3420
3591
|
const unavailableDimension = (column: "provider" | "model", selected?: readonly string[]) => {
|
|
3421
3592
|
if (selected === undefined || selected.length === 0) return;
|
|
3422
3593
|
const available = selected.filter((value) => value !== "unavailable");
|
|
3423
|
-
const matches =
|
|
3594
|
+
const matches =
|
|
3595
|
+
available.length === 0
|
|
3596
|
+
? "0"
|
|
3597
|
+
: `EXISTS (SELECT 1 FROM invocations i JOIN invocation_usage u USING(agent_invocation_id) WHERE i.run_id=canonical_run_index.run_id AND u.${column} IN (${available.map(() => "?").join(",")}))`;
|
|
3424
3598
|
if (available.length > 0) values.push(...available);
|
|
3425
3599
|
const missing = `NOT EXISTS (SELECT 1 FROM invocations i JOIN invocation_usage u USING(agent_invocation_id) WHERE i.run_id=canonical_run_index.run_id AND u.${column} IS NOT NULL AND u.${column} <> 'unavailable')`;
|
|
3426
3600
|
clauses.push(selected.includes("unavailable") ? `(${matches} OR ${missing})` : matches);
|
|
@@ -3437,7 +3611,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3437
3611
|
segmentClauses.push(`u.model IN (${modelValues.map(() => "?").join(",")})`);
|
|
3438
3612
|
values.push(...modelValues);
|
|
3439
3613
|
}
|
|
3440
|
-
clauses.push(
|
|
3614
|
+
clauses.push(
|
|
3615
|
+
`EXISTS (SELECT 1 FROM invocations i JOIN invocation_usage u USING(agent_invocation_id) WHERE i.run_id=canonical_run_index.run_id AND ${segmentClauses.join(" AND ")})`,
|
|
3616
|
+
);
|
|
3441
3617
|
}
|
|
3442
3618
|
const dimensions = [
|
|
3443
3619
|
["zaip_agent", filter.agents],
|
|
@@ -3450,7 +3626,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3450
3626
|
values.push(...selected);
|
|
3451
3627
|
}
|
|
3452
3628
|
const rows = this.database
|
|
3453
|
-
.prepare(
|
|
3629
|
+
.prepare(
|
|
3630
|
+
`SELECT occurred_day_sp AS day, COUNT(*) AS count FROM canonical_run_index WHERE ${clauses.join(" AND ")} GROUP BY occurred_day_sp`,
|
|
3631
|
+
)
|
|
3454
3632
|
.all(...values) as Array<{ day: string; count: number }>;
|
|
3455
3633
|
return new Map(rows.map((row) => [row.day, Number(row.count)]));
|
|
3456
3634
|
}
|
|
@@ -3472,30 +3650,30 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3472
3650
|
const knownUsageRuns = Number(row.activity_count ?? 0);
|
|
3473
3651
|
const unknownUsageRuns = Math.max(0, totalRuns - knownUsageRuns);
|
|
3474
3652
|
return [
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3653
|
+
day,
|
|
3654
|
+
{
|
|
3655
|
+
prds: Number(row.prd_count ?? 0),
|
|
3656
|
+
specs: Number(row.spec_count ?? 0),
|
|
3657
|
+
tasks: Number(row.task_count ?? 0),
|
|
3658
|
+
validations: Number(row.validation_count ?? 0),
|
|
3659
|
+
evidences: Number(row.evidence_count ?? 0),
|
|
3660
|
+
runs: Number(row.total_run_count ?? 0),
|
|
3661
|
+
usage: {
|
|
3662
|
+
total_tokens: {
|
|
3663
|
+
known_sum: Number(row.total_tokens ?? 0),
|
|
3664
|
+
unknown: unknownUsageRuns,
|
|
3665
|
+
},
|
|
3666
|
+
cost: {
|
|
3667
|
+
known_sum: Number(row.total_cost_brl ?? 0),
|
|
3668
|
+
unknown: unknownUsageRuns,
|
|
3669
|
+
},
|
|
3487
3670
|
},
|
|
3488
|
-
|
|
3489
|
-
known_sum:
|
|
3490
|
-
unknown:
|
|
3671
|
+
duration: {
|
|
3672
|
+
known_sum: durations.get(day) ?? 0,
|
|
3673
|
+
unknown: durations.has(day) ? 0 : Number(row.total_run_count ?? 0),
|
|
3491
3674
|
},
|
|
3492
3675
|
},
|
|
3493
|
-
|
|
3494
|
-
known_sum: durations.get(day) ?? 0,
|
|
3495
|
-
unknown: durations.has(day) ? 0 : Number(row.total_run_count ?? 0),
|
|
3496
|
-
},
|
|
3497
|
-
},
|
|
3498
|
-
];
|
|
3676
|
+
];
|
|
3499
3677
|
}),
|
|
3500
3678
|
);
|
|
3501
3679
|
}
|
|
@@ -3533,15 +3711,21 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3533
3711
|
* A later month with no facts in this immutable generation is also an exact
|
|
3534
3712
|
* empty result; every month that may contain a fact continues through SQL.
|
|
3535
3713
|
*/
|
|
3536
|
-
firstFoldMonthlyQuick(filter: {
|
|
3714
|
+
firstFoldMonthlyQuick(filter: {
|
|
3715
|
+
from: string | null;
|
|
3716
|
+
to: string | null;
|
|
3717
|
+
}): SqliteFirstFoldMonthlyQuick | null {
|
|
3537
3718
|
if (this.firstFoldMonthlyQuickCache === undefined) {
|
|
3538
3719
|
const persisted = this.database
|
|
3539
|
-
.prepare(
|
|
3720
|
+
.prepare(
|
|
3721
|
+
"SELECT payload_json FROM zaip_aios_activities WHERE payload_type='first_fold_monthly_quick_cache' ORDER BY recorded_at DESC LIMIT 1",
|
|
3722
|
+
)
|
|
3540
3723
|
.get() as { payload_json?: string } | undefined;
|
|
3541
3724
|
try {
|
|
3542
|
-
const candidate =
|
|
3543
|
-
|
|
3544
|
-
|
|
3725
|
+
const candidate =
|
|
3726
|
+
typeof persisted?.payload_json === "string"
|
|
3727
|
+
? (JSON.parse(persisted.payload_json) as Partial<SqliteFirstFoldMonthlyQuick>)
|
|
3728
|
+
: null;
|
|
3545
3729
|
this.firstFoldMonthlyQuickCache =
|
|
3546
3730
|
candidate !== null &&
|
|
3547
3731
|
typeof candidate.from_day === "string" &&
|
|
@@ -3549,7 +3733,7 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3549
3733
|
candidate.aggregate !== undefined &&
|
|
3550
3734
|
candidate.pages !== undefined &&
|
|
3551
3735
|
Array.isArray(candidate.top_tasks)
|
|
3552
|
-
? candidate as SqliteFirstFoldMonthlyQuick
|
|
3736
|
+
? (candidate as SqliteFirstFoldMonthlyQuick)
|
|
3553
3737
|
: null;
|
|
3554
3738
|
} catch {
|
|
3555
3739
|
this.firstFoldMonthlyQuickCache = null;
|
|
@@ -3600,13 +3784,15 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3600
3784
|
}
|
|
3601
3785
|
|
|
3602
3786
|
firstFoldRelevantLatestDay(): string | null {
|
|
3603
|
-
const row = this.database
|
|
3787
|
+
const row = this.database
|
|
3788
|
+
.prepare(`SELECT MAX(day) AS day FROM (
|
|
3604
3789
|
SELECT occurred_day_sp AS day FROM activities
|
|
3605
3790
|
UNION ALL SELECT occurred_day_sp AS day FROM canonical_work_items
|
|
3606
3791
|
UNION ALL SELECT occurred_day_sp AS day FROM canonical_artifacts
|
|
3607
3792
|
UNION ALL SELECT occurred_day_sp AS day FROM canonical_run_index
|
|
3608
3793
|
UNION ALL SELECT occurred_day_sp AS day FROM attributed_token_active_facts
|
|
3609
|
-
)`)
|
|
3794
|
+
)`)
|
|
3795
|
+
.get() as { day?: string | null };
|
|
3610
3796
|
return typeof row.day === "string" ? row.day : null;
|
|
3611
3797
|
}
|
|
3612
3798
|
|
|
@@ -3632,8 +3818,7 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3632
3818
|
const topTasks = this.database
|
|
3633
3819
|
.prepare("SELECT * FROM zaip_vw_first_fold_top_tasks_all_time ORDER BY rank")
|
|
3634
3820
|
.all() as SqliteFirstFoldAllTime["top_tasks"];
|
|
3635
|
-
const number = (value: unknown, fallback = 0) =>
|
|
3636
|
-
typeof value === "number" ? value : fallback;
|
|
3821
|
+
const number = (value: unknown, fallback = 0) => (typeof value === "number" ? value : fallback);
|
|
3637
3822
|
const nullableNumber = (value: unknown) => (typeof value === "number" ? value : null);
|
|
3638
3823
|
const readMacroMetrics = (kind: "spec" | "prd") => {
|
|
3639
3824
|
const statusRows = this.database
|
|
@@ -3741,7 +3926,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3741
3926
|
firstFoldViewportAllTime(): SqliteFirstFoldViewport {
|
|
3742
3927
|
if (this.firstFoldViewportCache !== null) return this.firstFoldViewportCache;
|
|
3743
3928
|
const persisted = this.database
|
|
3744
|
-
.prepare(
|
|
3929
|
+
.prepare(
|
|
3930
|
+
"SELECT payload_json FROM zaip_aios_activities WHERE payload_type='first_fold_viewport_cache' ORDER BY recorded_at DESC LIMIT 1",
|
|
3931
|
+
)
|
|
3745
3932
|
.get() as { payload_json?: string } | undefined;
|
|
3746
3933
|
if (typeof persisted?.payload_json === "string") {
|
|
3747
3934
|
this.firstFoldViewportCache = JSON.parse(persisted.payload_json) as SqliteFirstFoldViewport;
|
|
@@ -3756,7 +3943,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3756
3943
|
const startedAt = performance.now();
|
|
3757
3944
|
const value = read();
|
|
3758
3945
|
if (profile)
|
|
3759
|
-
this.firstFoldViewportTimings[select] =
|
|
3946
|
+
this.firstFoldViewportTimings[select] =
|
|
3947
|
+
Math.round((performance.now() - startedAt) * 10) / 10;
|
|
3760
3948
|
if (profile)
|
|
3761
3949
|
console.info(
|
|
3762
3950
|
JSON.stringify({
|
|
@@ -3767,8 +3955,11 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3767
3955
|
);
|
|
3768
3956
|
return value;
|
|
3769
3957
|
};
|
|
3770
|
-
const row = timed(
|
|
3771
|
-
|
|
3958
|
+
const row = timed(
|
|
3959
|
+
"viewport_overview",
|
|
3960
|
+
() =>
|
|
3961
|
+
this.database
|
|
3962
|
+
.prepare(`WITH totals AS (
|
|
3772
3963
|
SELECT SUM(CASE WHEN activity_source_kind='agent_run' THEN 1 ELSE 0 END) AS agent_invocation_count,
|
|
3773
3964
|
SUM(llm_call_count) AS llm_call_count,
|
|
3774
3965
|
SUM(total_tokens) AS total_tokens, SUM(cost_usd) AS total_cost_usd,
|
|
@@ -3829,9 +4020,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3829
4020
|
) SELECT totals.*, run_totals.*, duration_totals.*, bounds.*, item_totals.*, task_status.*, task_risk.*
|
|
3830
4021
|
FROM totals CROSS JOIN run_totals CROSS JOIN duration_totals CROSS JOIN bounds
|
|
3831
4022
|
CROSS JOIN item_totals CROSS JOIN task_status CROSS JOIN task_risk`)
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
4023
|
+
.get() as Record<string, unknown>,
|
|
4024
|
+
);
|
|
4025
|
+
const number = (value: unknown, fallback = 0) => (typeof value === "number" ? value : fallback);
|
|
3835
4026
|
const nullableNumber = (value: unknown) => (typeof value === "number" ? value : null);
|
|
3836
4027
|
this.firstFoldViewportCache = {
|
|
3837
4028
|
aggregate: {
|
|
@@ -3889,7 +4080,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3889
4080
|
AND (? IS NULL OR f.occurred_day_sp >= ?) AND (? IS NULL OR f.occurred_day_sp <= ?)
|
|
3890
4081
|
GROUP BY a.task_id`)
|
|
3891
4082
|
.all(from, from, to, to) as Array<{ task_id: string; duration_ms: number }>;
|
|
3892
|
-
const durationByTask = new Map(
|
|
4083
|
+
const durationByTask = new Map(
|
|
4084
|
+
durationRows.map((row) => [row.task_id, Number(row.duration_ms)]),
|
|
4085
|
+
);
|
|
3893
4086
|
return facts
|
|
3894
4087
|
.map((row) => ({
|
|
3895
4088
|
...row,
|
|
@@ -3935,14 +4128,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3935
4128
|
}>;
|
|
3936
4129
|
const knownDurationRuns = runs.filter(
|
|
3937
4130
|
(run) =>
|
|
3938
|
-
run.run_role === "root" &&
|
|
3939
|
-
run.phase === "execution" &&
|
|
3940
|
-
typeof run.duration_ms === "number",
|
|
4131
|
+
run.run_role === "root" && run.phase === "execution" && typeof run.duration_ms === "number",
|
|
3941
4132
|
).length;
|
|
3942
|
-
const durationByDay = this.executionDurationByDay(
|
|
3943
|
-
{ from: null, to: null },
|
|
3944
|
-
taskId,
|
|
3945
|
-
);
|
|
4133
|
+
const durationByDay = this.executionDurationByDay({ from: null, to: null }, taskId);
|
|
3946
4134
|
const duration =
|
|
3947
4135
|
durationByDay.size === 0
|
|
3948
4136
|
? null
|
|
@@ -4018,11 +4206,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4018
4206
|
const to = filter.to?.slice(0, 10) ?? null;
|
|
4019
4207
|
// Financial periods are source-session periods when available. This keeps
|
|
4020
4208
|
// a retrospective capture on its observed day rather than its write day.
|
|
4021
|
-
const periodDay =
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
`(? IS NULL OR ${periodDay} <= ?)`,
|
|
4025
|
-
];
|
|
4209
|
+
const periodDay =
|
|
4210
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours')";
|
|
4211
|
+
const clauses = [`(? IS NULL OR ${periodDay} >= ?)`, `(? IS NULL OR ${periodDay} <= ?)`];
|
|
4026
4212
|
const values: Array<string | number | null> = [from, from, to, to];
|
|
4027
4213
|
const add = (column: string, items?: readonly string[]) => {
|
|
4028
4214
|
if (items !== undefined && items.length > 0) {
|
|
@@ -4041,7 +4227,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4041
4227
|
const providerValues = (filter.providers ?? []).filter((item) => item !== "unavailable");
|
|
4042
4228
|
const modelValues = (filter.models ?? []).filter((item) => item !== "unavailable");
|
|
4043
4229
|
const requestsUnavailable =
|
|
4044
|
-
filter.providers?.includes("unavailable") === true ||
|
|
4230
|
+
filter.providers?.includes("unavailable") === true ||
|
|
4231
|
+
filter.models?.includes("unavailable") === true;
|
|
4045
4232
|
if (requestsUnavailable) {
|
|
4046
4233
|
// Keep the pre-existing explicit-unavailable behaviour; it describes a
|
|
4047
4234
|
// missing invocation attribute rather than a provider/model pair.
|
|
@@ -4069,7 +4256,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4069
4256
|
segmentClauses.push(`f.model IN (${modelValues.map(() => "?").join(",")})`);
|
|
4070
4257
|
values.push(...modelValues);
|
|
4071
4258
|
}
|
|
4072
|
-
clauses.push(
|
|
4259
|
+
clauses.push(
|
|
4260
|
+
`EXISTS (SELECT 1 FROM invocation_usage f WHERE f.agent_invocation_id = i.agent_invocation_id AND ${segmentClauses.join(" AND ")})`,
|
|
4261
|
+
);
|
|
4073
4262
|
}
|
|
4074
4263
|
return this.database
|
|
4075
4264
|
.prepare(`SELECT i.*, CASE COUNT(DISTINCT s.provider) WHEN 0 THEN NULL WHEN 1 THEN MIN(s.provider) ELSE 'mixed' END AS provider, CASE COUNT(DISTINCT s.model) WHEN 0 THEN NULL WHEN 1 THEN MIN(s.model) ELSE 'mixed' END AS model,
|
|
@@ -4090,7 +4279,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4090
4279
|
*/
|
|
4091
4280
|
costMonitorFactualDurationByDay(
|
|
4092
4281
|
filter: Parameters<SqliteReadModelFileSystem["invocationCostRows"]>[0],
|
|
4093
|
-
financialRows: readonly Record<string, unknown>[] = this.costMonitorFinancialActivityRows(
|
|
4282
|
+
financialRows: readonly Record<string, unknown>[] = this.costMonitorFinancialActivityRows(
|
|
4283
|
+
filter,
|
|
4284
|
+
),
|
|
4094
4285
|
): Map<string, { known_sum: number; eligible_runs: number; known_runs: number }> {
|
|
4095
4286
|
const rows = financialRows;
|
|
4096
4287
|
const activityIds = new Set<string>();
|
|
@@ -4105,7 +4296,10 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4105
4296
|
? row.occurred_at
|
|
4106
4297
|
: null;
|
|
4107
4298
|
if (timestamp === null) continue;
|
|
4108
|
-
const hasFinancialFact =
|
|
4299
|
+
const hasFinancialFact =
|
|
4300
|
+
typeof row.total_tokens === "number" ||
|
|
4301
|
+
typeof row.cost_brl === "number" ||
|
|
4302
|
+
typeof row.cost_usd === "number";
|
|
4109
4303
|
if (!hasFinancialFact) continue;
|
|
4110
4304
|
// SQLite exposes the durable activity key as an integer; normalize it
|
|
4111
4305
|
// before joining it to the JSON-backed temporal fact key.
|
|
@@ -4122,7 +4316,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4122
4316
|
// local session can be refreshed instead of failing its Cost Monitor.
|
|
4123
4317
|
const durationFactTable =
|
|
4124
4318
|
this.database
|
|
4125
|
-
.prepare(
|
|
4319
|
+
.prepare(
|
|
4320
|
+
"SELECT 1 AS present FROM sqlite_master WHERE name = 'attributed_token_active_facts'",
|
|
4321
|
+
)
|
|
4126
4322
|
.get() !== undefined
|
|
4127
4323
|
? "attributed_token_active_facts"
|
|
4128
4324
|
: "zaip_vw_attributed_token_active_facts";
|
|
@@ -4168,7 +4364,10 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4168
4364
|
ids.add(activityId);
|
|
4169
4365
|
unavailableByDay.set(day, ids);
|
|
4170
4366
|
}
|
|
4171
|
-
const byDay = new Map<
|
|
4367
|
+
const byDay = new Map<
|
|
4368
|
+
string,
|
|
4369
|
+
{ known_sum: number; eligible_runs: number; known_runs: number }
|
|
4370
|
+
>();
|
|
4172
4371
|
for (const day of new Set([...factsByDay.keys(), ...unavailableByDay.keys()])) {
|
|
4173
4372
|
const facts = factsByDay.get(day) ?? { known_sum: 0, activityIds: new Set<string>() };
|
|
4174
4373
|
const unavailableIds = unavailableByDay.get(day) ?? new Set<string>();
|
|
@@ -4227,7 +4426,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4227
4426
|
}
|
|
4228
4427
|
|
|
4229
4428
|
/** Cost Monitor's historical financial population, independent of Agent-only views. */
|
|
4230
|
-
costMonitorFinancialActivityRows(
|
|
4429
|
+
costMonitorFinancialActivityRows(
|
|
4430
|
+
filter: Parameters<SqliteReadModelFileSystem["activityCostRows"]>[0],
|
|
4431
|
+
) {
|
|
4231
4432
|
// Activity capture day can differ from the observed source session day.
|
|
4232
4433
|
// Keep that source-session population in SQL: the previous implementation
|
|
4233
4434
|
// read every activity into JS and issued a usage lookup for each filtered
|
|
@@ -4235,7 +4436,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4235
4436
|
const { from, to, providers = [], models = [] } = filter;
|
|
4236
4437
|
const sourceFrom = costMonitorFilterDay(from);
|
|
4237
4438
|
const sourceTo = costMonitorFilterDay(to);
|
|
4238
|
-
const sourceDay =
|
|
4439
|
+
const sourceDay =
|
|
4440
|
+
"date(COALESCE(a.source_started_at, a.source_occurred_at, a.occurred_at), '-3 hours')";
|
|
4239
4441
|
const clauses = [
|
|
4240
4442
|
"(a.total_tokens IS NOT NULL OR a.cost_brl IS NOT NULL OR a.cost_usd IS NOT NULL)",
|
|
4241
4443
|
`(? IS NULL OR ${sourceDay} >= ?)`,
|
|
@@ -4259,15 +4461,25 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4259
4461
|
if (providers.length > 0 || models.length > 0) {
|
|
4260
4462
|
const usageClauses: string[] = [];
|
|
4261
4463
|
if (providers.length > 0) {
|
|
4262
|
-
usageClauses.push(
|
|
4464
|
+
usageClauses.push(
|
|
4465
|
+
`COALESCE(u.provider, 'unavailable') IN (${providers.map(() => "?").join(",")})`,
|
|
4466
|
+
);
|
|
4263
4467
|
}
|
|
4264
4468
|
if (models.length > 0) {
|
|
4265
|
-
usageClauses.push(
|
|
4469
|
+
usageClauses.push(
|
|
4470
|
+
`COALESCE(u.model, 'unavailable') IN (${models.map(() => "?").join(",")})`,
|
|
4471
|
+
);
|
|
4266
4472
|
}
|
|
4267
4473
|
const usageValues = [...providers, ...models];
|
|
4268
4474
|
const directClauses: string[] = [];
|
|
4269
|
-
if (providers.length > 0)
|
|
4270
|
-
|
|
4475
|
+
if (providers.length > 0)
|
|
4476
|
+
directClauses.push(
|
|
4477
|
+
`COALESCE(a.provider, 'unavailable') IN (${providers.map(() => "?").join(",")})`,
|
|
4478
|
+
);
|
|
4479
|
+
if (models.length > 0)
|
|
4480
|
+
directClauses.push(
|
|
4481
|
+
`COALESCE(a.model, 'unavailable') IN (${models.map(() => "?").join(",")})`,
|
|
4482
|
+
);
|
|
4271
4483
|
// Rows without an Agent Invocation keep their activity dimensions;
|
|
4272
4484
|
// Agent rows retain the segment-level provider/model semantics.
|
|
4273
4485
|
clauses.push(
|
|
@@ -4308,7 +4520,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4308
4520
|
const { from, to, providers = [], models = [] } = filter;
|
|
4309
4521
|
const sourceFrom = costMonitorFilterDay(from);
|
|
4310
4522
|
const sourceTo = costMonitorFilterDay(to);
|
|
4311
|
-
const sourceDay =
|
|
4523
|
+
const sourceDay =
|
|
4524
|
+
"date(COALESCE(a.source_started_at, a.source_occurred_at, a.occurred_at), '-3 hours')";
|
|
4312
4525
|
const clauses = [
|
|
4313
4526
|
"(a.total_tokens IS NOT NULL OR a.cost_brl IS NOT NULL OR a.cost_usd IS NOT NULL)",
|
|
4314
4527
|
`(? IS NULL OR ${sourceDay} >= ?)`,
|
|
@@ -4334,14 +4547,22 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4334
4547
|
const usageValues: string[] = [];
|
|
4335
4548
|
const directValues: string[] = [];
|
|
4336
4549
|
if (providers.length > 0) {
|
|
4337
|
-
usageClauses.push(
|
|
4338
|
-
|
|
4550
|
+
usageClauses.push(
|
|
4551
|
+
`COALESCE(u.provider, 'unavailable') IN (${providers.map(() => "?").join(",")})`,
|
|
4552
|
+
);
|
|
4553
|
+
directClauses.push(
|
|
4554
|
+
`COALESCE(a.provider, 'unavailable') IN (${providers.map(() => "?").join(",")})`,
|
|
4555
|
+
);
|
|
4339
4556
|
usageValues.push(...providers);
|
|
4340
4557
|
directValues.push(...providers);
|
|
4341
4558
|
}
|
|
4342
4559
|
if (models.length > 0) {
|
|
4343
|
-
usageClauses.push(
|
|
4344
|
-
|
|
4560
|
+
usageClauses.push(
|
|
4561
|
+
`COALESCE(u.model, 'unavailable') IN (${models.map(() => "?").join(",")})`,
|
|
4562
|
+
);
|
|
4563
|
+
directClauses.push(
|
|
4564
|
+
`COALESCE(a.model, 'unavailable') IN (${models.map(() => "?").join(",")})`,
|
|
4565
|
+
);
|
|
4345
4566
|
usageValues.push(...models);
|
|
4346
4567
|
directValues.push(...models);
|
|
4347
4568
|
}
|
|
@@ -4387,21 +4608,34 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4387
4608
|
FROM selected GROUP BY day, phase_key ORDER BY day, phase_key`)
|
|
4388
4609
|
.all(...values) as Array<Record<string, unknown>>;
|
|
4389
4610
|
const nullableColumns = [
|
|
4390
|
-
"duration_ms",
|
|
4391
|
-
"
|
|
4611
|
+
"duration_ms",
|
|
4612
|
+
"input_tokens",
|
|
4613
|
+
"cached_input_tokens",
|
|
4614
|
+
"output_tokens",
|
|
4615
|
+
"reasoning_tokens",
|
|
4616
|
+
"total_tokens",
|
|
4617
|
+
"cost_usd",
|
|
4618
|
+
"cost_brl",
|
|
4392
4619
|
] as const;
|
|
4393
4620
|
const additiveColumns = [
|
|
4394
|
-
"eligible_runs",
|
|
4621
|
+
"eligible_runs",
|
|
4622
|
+
"events",
|
|
4623
|
+
"duration_known",
|
|
4624
|
+
"tokens_known",
|
|
4625
|
+
"usd_known",
|
|
4626
|
+
"brl_known",
|
|
4395
4627
|
] as const;
|
|
4396
4628
|
const rollup = (key: string, rows: readonly Record<string, unknown>[]) => {
|
|
4397
4629
|
const result: Record<string, unknown> = { key };
|
|
4398
4630
|
for (const column of additiveColumns)
|
|
4399
|
-
result[column] = rows.reduce(
|
|
4631
|
+
result[column] = rows.reduce(
|
|
4632
|
+
(sum, row) => sum + (typeof row[column] === "number" ? Number(row[column]) : 0),
|
|
4633
|
+
0,
|
|
4634
|
+
);
|
|
4400
4635
|
for (const column of nullableColumns) {
|
|
4401
4636
|
const present = rows.filter((row) => typeof row[column] === "number");
|
|
4402
|
-
result[column] =
|
|
4403
|
-
? null
|
|
4404
|
-
: present.reduce((sum, row) => sum + Number(row[column]), 0);
|
|
4637
|
+
result[column] =
|
|
4638
|
+
present.length === 0 ? null : present.reduce((sum, row) => sum + Number(row[column]), 0);
|
|
4405
4639
|
}
|
|
4406
4640
|
const taskIds = new Set<string>();
|
|
4407
4641
|
for (const row of rows) {
|
|
@@ -4413,10 +4647,18 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4413
4647
|
return result;
|
|
4414
4648
|
};
|
|
4415
4649
|
const phaseKeys = ["conversation", "planning", "execution"] as const;
|
|
4416
|
-
const phaseRows = phaseKeys.map((key) =>
|
|
4650
|
+
const phaseRows = phaseKeys.map((key) =>
|
|
4651
|
+
rollup(
|
|
4652
|
+
key,
|
|
4653
|
+
factRows.filter((row) => row.phase_key === key),
|
|
4654
|
+
),
|
|
4655
|
+
);
|
|
4417
4656
|
phaseRows.push(rollup("total", factRows));
|
|
4418
4657
|
const dailyRows = [...new Set(factRows.map((row) => String(row.day)))].map((day) =>
|
|
4419
|
-
rollup(
|
|
4658
|
+
rollup(
|
|
4659
|
+
day,
|
|
4660
|
+
factRows.filter((row) => String(row.day) === day),
|
|
4661
|
+
),
|
|
4420
4662
|
);
|
|
4421
4663
|
const structuralRows = this.database
|
|
4422
4664
|
.prepare(`WITH canonical_ids AS MATERIALIZED (
|
|
@@ -4467,7 +4709,16 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4467
4709
|
SUM(validation_count) AS validation_count,
|
|
4468
4710
|
SUM(evidence_count) AS evidence_count
|
|
4469
4711
|
FROM structural GROUP BY day ORDER BY day`)
|
|
4470
|
-
.all(
|
|
4712
|
+
.all(
|
|
4713
|
+
sourceFrom,
|
|
4714
|
+
sourceFrom,
|
|
4715
|
+
sourceTo,
|
|
4716
|
+
sourceTo,
|
|
4717
|
+
sourceFrom,
|
|
4718
|
+
sourceFrom,
|
|
4719
|
+
sourceTo,
|
|
4720
|
+
sourceTo,
|
|
4721
|
+
) as Array<Record<string, unknown>>;
|
|
4471
4722
|
const number = (value: unknown) => (typeof value === "number" ? value : 0);
|
|
4472
4723
|
const nullable = (value: unknown) => (typeof value === "number" ? value : null);
|
|
4473
4724
|
const metric = (row?: Record<string, unknown>) => {
|
|
@@ -4588,7 +4839,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4588
4839
|
// phases and daily rows. Calling the paginated view here was tempting but
|
|
4589
4840
|
// silently loses multi-value and lineage filters (Task/SPEC/PRD/etc.).
|
|
4590
4841
|
// This remains one bounded SQL aggregate, without transporting Runs.
|
|
4591
|
-
const summaryModelRows = this.database
|
|
4842
|
+
const summaryModelRows = this.database
|
|
4843
|
+
.prepare(`${selected},
|
|
4592
4844
|
segment_totals AS (
|
|
4593
4845
|
SELECT agent_invocation_id, SUM(total_tokens) AS total_tokens,
|
|
4594
4846
|
SUM(cost_usd) AS cost_usd, SUM(cost_brl) AS cost_brl
|
|
@@ -4630,7 +4882,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4630
4882
|
NULL AS total_duration, 0 AS duration_unknown, 0 AS duration_known,
|
|
4631
4883
|
0 AS source_event_count, SUM(total_tokens) AS total_tokens,
|
|
4632
4884
|
SUM(total_cost_usd) AS total_cost_usd, SUM(total_cost_brl) AS total_cost_brl
|
|
4633
|
-
FROM components GROUP BY key ORDER BY total_cost_brl DESC, key`)
|
|
4885
|
+
FROM components GROUP BY key ORDER BY total_cost_brl DESC, key`)
|
|
4886
|
+
.all(...values) as Array<Record<string, unknown>>;
|
|
4634
4887
|
const model_groups = summaryModelRows.map((row) => {
|
|
4635
4888
|
const duration = nullable(row.total_duration);
|
|
4636
4889
|
const durationUnknown = number(row.duration_unknown);
|
|
@@ -4696,7 +4949,11 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4696
4949
|
const from = filter.from?.slice(0, 10) ?? null;
|
|
4697
4950
|
const to = filter.to?.slice(0, 10) ?? null;
|
|
4698
4951
|
const cursorValue = parseSummaryCursor(cursor);
|
|
4699
|
-
const clauses = [
|
|
4952
|
+
const clauses = [
|
|
4953
|
+
"entity_kind = ?",
|
|
4954
|
+
"(? IS NULL OR occurred_day_sp >= ?)",
|
|
4955
|
+
"(? IS NULL OR occurred_day_sp <= ?)",
|
|
4956
|
+
];
|
|
4700
4957
|
const values: Array<string | number | null> = [kind, from, from, to, to];
|
|
4701
4958
|
if (filter.status) {
|
|
4702
4959
|
clauses.push("status = ?");
|
|
@@ -4788,9 +5045,20 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4788
5045
|
viewPage(
|
|
4789
5046
|
view: SqliteViewName,
|
|
4790
5047
|
input: {
|
|
4791
|
-
from?: string | null;
|
|
4792
|
-
|
|
4793
|
-
|
|
5048
|
+
from?: string | null;
|
|
5049
|
+
to?: string | null;
|
|
5050
|
+
query?: string;
|
|
5051
|
+
status?: string;
|
|
5052
|
+
risk?: string;
|
|
5053
|
+
provider?: string;
|
|
5054
|
+
model?: string;
|
|
5055
|
+
agent?: string;
|
|
5056
|
+
phase?: string;
|
|
5057
|
+
origin?: string;
|
|
5058
|
+
workType?: string;
|
|
5059
|
+
priority?: string;
|
|
5060
|
+
offset?: number;
|
|
5061
|
+
limit?: number;
|
|
4794
5062
|
},
|
|
4795
5063
|
): ReturnType<SqliteReadModelFileSystem["uncachedViewPage"]> {
|
|
4796
5064
|
// The key includes every filter and pagination input. The cache is scoped
|
|
@@ -4798,7 +5066,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4798
5066
|
// launcher refresh cannot expose a page from the previous snapshot.
|
|
4799
5067
|
const key = `${view}:${JSON.stringify(input)}`;
|
|
4800
5068
|
const cached = this.viewPageResultCache.get(key);
|
|
4801
|
-
if (cached !== undefined)
|
|
5069
|
+
if (cached !== undefined)
|
|
5070
|
+
return structuredClone(cached) as ReturnType<SqliteReadModelFileSystem["uncachedViewPage"]>;
|
|
4802
5071
|
const result = this.uncachedViewPage(view, input);
|
|
4803
5072
|
if (this.viewPageResultCache.size >= 256) {
|
|
4804
5073
|
const oldest = this.viewPageResultCache.keys().next().value as string | undefined;
|
|
@@ -4836,9 +5105,18 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4836
5105
|
];
|
|
4837
5106
|
if (
|
|
4838
5107
|
cacheableUnfilteredViews.includes(view) &&
|
|
4839
|
-
!input.from &&
|
|
4840
|
-
!input.
|
|
4841
|
-
!input.
|
|
5108
|
+
!input.from &&
|
|
5109
|
+
!input.to &&
|
|
5110
|
+
!input.query &&
|
|
5111
|
+
!input.status &&
|
|
5112
|
+
!input.risk &&
|
|
5113
|
+
!input.provider &&
|
|
5114
|
+
!input.model &&
|
|
5115
|
+
!input.agent &&
|
|
5116
|
+
!input.phase &&
|
|
5117
|
+
!input.origin &&
|
|
5118
|
+
!input.workType &&
|
|
5119
|
+
!input.priority
|
|
4842
5120
|
) {
|
|
4843
5121
|
const rows = this.unfilteredViewRows(view);
|
|
4844
5122
|
const limit = Math.min(200, Math.max(1, input.limit ?? 100));
|
|
@@ -4882,11 +5160,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4882
5160
|
};
|
|
4883
5161
|
}
|
|
4884
5162
|
if (view === "cost_monitor_agent_invocations") {
|
|
4885
|
-
const sourceDay =
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
`(? IS NULL OR ${sourceDay} <= ?)`,
|
|
4889
|
-
];
|
|
5163
|
+
const sourceDay =
|
|
5164
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours')";
|
|
5165
|
+
const clauses = [`(? IS NULL OR ${sourceDay} >= ?)`, `(? IS NULL OR ${sourceDay} <= ?)`];
|
|
4890
5166
|
const from = costMonitorFilterDay(input.from);
|
|
4891
5167
|
const to = costMonitorFilterDay(input.to);
|
|
4892
5168
|
const values: Array<string | number | null> = [from, from, to, to];
|
|
@@ -4902,7 +5178,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4902
5178
|
const usageSegmentClauses: string[] = [];
|
|
4903
5179
|
if (input.provider) {
|
|
4904
5180
|
if (input.provider === "unavailable") {
|
|
4905
|
-
clauses.push(
|
|
5181
|
+
clauses.push(
|
|
5182
|
+
"((i.agent_invocation_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM invocation_usage known_provider WHERE known_provider.agent_invocation_id=i.agent_invocation_id AND known_provider.provider IS NOT NULL AND known_provider.provider <> 'unavailable')) OR (i.agent_invocation_id IS NULL AND (i.provider IS NULL OR i.provider = 'unavailable')))",
|
|
5183
|
+
);
|
|
4906
5184
|
} else {
|
|
4907
5185
|
usageSegmentClauses.push("f.provider=?");
|
|
4908
5186
|
values.push(input.provider);
|
|
@@ -4910,14 +5188,18 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4910
5188
|
}
|
|
4911
5189
|
if (input.model) {
|
|
4912
5190
|
if (input.model === "unavailable") {
|
|
4913
|
-
clauses.push(
|
|
5191
|
+
clauses.push(
|
|
5192
|
+
"((i.agent_invocation_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM invocation_usage known_model WHERE known_model.agent_invocation_id=i.agent_invocation_id AND known_model.model IS NOT NULL AND known_model.model <> 'unavailable')) OR (i.agent_invocation_id IS NULL AND (i.model IS NULL OR i.model = 'unavailable')))",
|
|
5193
|
+
);
|
|
4914
5194
|
} else {
|
|
4915
5195
|
usageSegmentClauses.push("f.model=?");
|
|
4916
5196
|
values.push(input.model);
|
|
4917
5197
|
}
|
|
4918
5198
|
}
|
|
4919
5199
|
if (usageSegmentClauses.length > 0)
|
|
4920
|
-
clauses.push(
|
|
5200
|
+
clauses.push(
|
|
5201
|
+
`EXISTS (SELECT 1 FROM invocation_usage f WHERE f.agent_invocation_id=i.agent_invocation_id AND ${usageSegmentClauses.join(" AND ")})`,
|
|
5202
|
+
);
|
|
4921
5203
|
if (input.query) {
|
|
4922
5204
|
clauses.push("(i.agent_invocation_id LIKE ? OR i.task_id LIKE ? OR i.execution_id LIKE ?)");
|
|
4923
5205
|
values.push(`%${input.query}%`, `%${input.query}%`, `%${input.query}%`);
|
|
@@ -4925,14 +5207,14 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4925
5207
|
const where = clauses.join(" AND ");
|
|
4926
5208
|
const limit = Math.min(200, Math.max(1, input.limit ?? 100));
|
|
4927
5209
|
const offset = Math.max(0, input.offset ?? 0);
|
|
4928
|
-
|
|
5210
|
+
const total_count = Number(
|
|
4929
5211
|
(
|
|
4930
5212
|
this.database
|
|
4931
5213
|
.prepare(`SELECT COUNT(*) AS count FROM cost_agent_invocations i WHERE ${where}`)
|
|
4932
5214
|
.get(...values) as { count: number }
|
|
4933
5215
|
).count,
|
|
4934
5216
|
);
|
|
4935
|
-
|
|
5217
|
+
const rows = this.database
|
|
4936
5218
|
.prepare(
|
|
4937
5219
|
`SELECT i.* FROM cost_agent_invocations i WHERE ${where} ORDER BY i.occurred_at DESC, i.agent_invocation_id DESC LIMIT ? OFFSET ?`,
|
|
4938
5220
|
)
|
|
@@ -4960,12 +5242,22 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4960
5242
|
const filteredCostGroup = filteredCostGroups[view];
|
|
4961
5243
|
const isUnfilteredCostDimension =
|
|
4962
5244
|
(filteredCostGroup === "model" || filteredCostGroup === "provider") &&
|
|
4963
|
-
!input.from &&
|
|
4964
|
-
!input.
|
|
4965
|
-
!input.
|
|
5245
|
+
!input.from &&
|
|
5246
|
+
!input.to &&
|
|
5247
|
+
!input.query &&
|
|
5248
|
+
!input.status &&
|
|
5249
|
+
!input.risk &&
|
|
5250
|
+
!input.provider &&
|
|
5251
|
+
!input.model &&
|
|
5252
|
+
!input.agent &&
|
|
5253
|
+
!input.phase &&
|
|
5254
|
+
!input.origin &&
|
|
5255
|
+
!input.workType &&
|
|
5256
|
+
!input.priority;
|
|
4966
5257
|
if (isUnfilteredCostDimension) {
|
|
4967
5258
|
const dimension = filteredCostGroup;
|
|
4968
|
-
const payloadRows = this.database
|
|
5259
|
+
const payloadRows = this.database
|
|
5260
|
+
.prepare(`SELECT
|
|
4969
5261
|
json_extract(payload_json, '$.key') AS key,
|
|
4970
5262
|
json_extract(payload_json, '$.label') AS label,
|
|
4971
5263
|
json_extract(payload_json, '$.invocation_count') AS invocation_count,
|
|
@@ -4978,7 +5270,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4978
5270
|
FROM zaip_aios_activities
|
|
4979
5271
|
WHERE payload_type='cost_dimension_aggregate_v1'
|
|
4980
5272
|
AND json_extract(payload_json, '$.dimension')=?
|
|
4981
|
-
ORDER BY total_cost_brl DESC, key`)
|
|
5273
|
+
ORDER BY total_cost_brl DESC, key`)
|
|
5274
|
+
.all(dimension) as Array<Record<string, unknown>>;
|
|
4982
5275
|
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
4983
5276
|
const offset = Math.max(0, input.offset ?? 0);
|
|
4984
5277
|
const rows = payloadRows.slice(offset, offset + limit);
|
|
@@ -4995,17 +5288,46 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4995
5288
|
if (filteredCostGroup === "agent") {
|
|
4996
5289
|
const clauses = ["i.zaip_agent IS NOT NULL"];
|
|
4997
5290
|
const values: Array<string | number> = [];
|
|
4998
|
-
if (input.from) {
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5291
|
+
if (input.from) {
|
|
5292
|
+
clauses.push(
|
|
5293
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') >= ?",
|
|
5294
|
+
);
|
|
5295
|
+
values.push(input.from.slice(0, 10));
|
|
5296
|
+
}
|
|
5297
|
+
if (input.to) {
|
|
5298
|
+
clauses.push(
|
|
5299
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') <= ?",
|
|
5300
|
+
);
|
|
5301
|
+
values.push(input.to.slice(0, 10));
|
|
5302
|
+
}
|
|
5303
|
+
if (input.agent) {
|
|
5304
|
+
clauses.push("i.zaip_agent = ?");
|
|
5305
|
+
values.push(input.agent);
|
|
5306
|
+
}
|
|
5307
|
+
if (input.phase) {
|
|
5308
|
+
clauses.push("i.phase = ?");
|
|
5309
|
+
values.push(input.phase);
|
|
5310
|
+
}
|
|
5311
|
+
if (input.status) {
|
|
5312
|
+
clauses.push("i.status = ?");
|
|
5313
|
+
values.push(input.status);
|
|
5314
|
+
}
|
|
5003
5315
|
// This read model does not materialize canonical Task priority. Keep
|
|
5004
5316
|
// the filtered view safe and avoid substituting a different field.
|
|
5005
5317
|
if (input.priority) clauses.push("0");
|
|
5006
|
-
if (input.risk) {
|
|
5318
|
+
if (input.risk) {
|
|
5319
|
+
clauses.push("t.risk = ?");
|
|
5320
|
+
values.push(input.risk);
|
|
5321
|
+
}
|
|
5007
5322
|
if (input.origin) {
|
|
5008
|
-
const origin =
|
|
5323
|
+
const origin =
|
|
5324
|
+
input.origin === "prd"
|
|
5325
|
+
? "i.prd_id IS NOT NULL"
|
|
5326
|
+
: input.origin === "spec"
|
|
5327
|
+
? "i.prd_id IS NULL AND i.spec_id IS NOT NULL"
|
|
5328
|
+
: input.origin === "task"
|
|
5329
|
+
? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL"
|
|
5330
|
+
: "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL";
|
|
5009
5331
|
clauses.push(origin);
|
|
5010
5332
|
}
|
|
5011
5333
|
// SQLite invocation facts do not contain canonical work_type. A work-type
|
|
@@ -5013,33 +5335,82 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5013
5335
|
if (input.workType) clauses.push("0");
|
|
5014
5336
|
const segment: string[] = [];
|
|
5015
5337
|
if (input.provider) {
|
|
5016
|
-
if (input.provider === "unavailable")
|
|
5017
|
-
|
|
5338
|
+
if (input.provider === "unavailable")
|
|
5339
|
+
clauses.push(
|
|
5340
|
+
"NOT EXISTS (SELECT 1 FROM invocation_usage known_provider WHERE known_provider.agent_invocation_id=i.agent_invocation_id AND known_provider.provider IS NOT NULL AND known_provider.provider <> 'unavailable')",
|
|
5341
|
+
);
|
|
5342
|
+
else {
|
|
5343
|
+
segment.push("f.provider = ?");
|
|
5344
|
+
values.push(input.provider);
|
|
5345
|
+
}
|
|
5018
5346
|
}
|
|
5019
5347
|
if (input.model) {
|
|
5020
|
-
if (input.model === "unavailable")
|
|
5021
|
-
|
|
5348
|
+
if (input.model === "unavailable")
|
|
5349
|
+
clauses.push(
|
|
5350
|
+
"NOT EXISTS (SELECT 1 FROM invocation_usage known_model WHERE known_model.agent_invocation_id=i.agent_invocation_id AND known_model.model IS NOT NULL AND known_model.model <> 'unavailable')",
|
|
5351
|
+
);
|
|
5352
|
+
else {
|
|
5353
|
+
segment.push("f.model = ?");
|
|
5354
|
+
values.push(input.model);
|
|
5355
|
+
}
|
|
5022
5356
|
}
|
|
5023
|
-
if (segment.length > 0)
|
|
5357
|
+
if (segment.length > 0)
|
|
5358
|
+
clauses.push(
|
|
5359
|
+
`EXISTS (SELECT 1 FROM invocation_usage f WHERE f.agent_invocation_id=i.agent_invocation_id AND ${segment.join(" AND ")})`,
|
|
5360
|
+
);
|
|
5024
5361
|
if (input.query) {
|
|
5025
5362
|
const query = `%${input.query}%`;
|
|
5026
|
-
clauses.push(
|
|
5363
|
+
clauses.push(
|
|
5364
|
+
"(i.zaip_agent LIKE ? OR i.zaip_agent_persona LIKE ? OR i.zaip_agent_job LIKE ?)",
|
|
5365
|
+
);
|
|
5027
5366
|
values.push(query, query, query);
|
|
5028
5367
|
}
|
|
5029
5368
|
// This is deliberately the same financial fact population as the Cost
|
|
5030
5369
|
// Monitor summary, grouped by the factual activity.zaip_agent. It is
|
|
5031
5370
|
// not an invocation-only approximation.
|
|
5032
|
-
const sourceDay =
|
|
5033
|
-
|
|
5371
|
+
const sourceDay =
|
|
5372
|
+
"date(COALESCE(a.source_started_at, a.source_occurred_at, a.occurred_at), '-3 hours')";
|
|
5373
|
+
const activityClauses = [
|
|
5374
|
+
"a.activity_source_kind <> 'agent_run'",
|
|
5375
|
+
"a.zaip_agent IS NOT NULL",
|
|
5376
|
+
"(a.total_tokens IS NOT NULL OR a.cost_brl IS NOT NULL OR a.cost_usd IS NOT NULL)",
|
|
5377
|
+
];
|
|
5034
5378
|
const activityValues: Array<string | number> = [];
|
|
5035
|
-
if (input.from) {
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
if (input.
|
|
5379
|
+
if (input.from) {
|
|
5380
|
+
activityClauses.push(`${sourceDay} >= ?`);
|
|
5381
|
+
activityValues.push(input.from.slice(0, 10));
|
|
5382
|
+
}
|
|
5383
|
+
if (input.to) {
|
|
5384
|
+
activityClauses.push(`${sourceDay} <= ?`);
|
|
5385
|
+
activityValues.push(input.to.slice(0, 10));
|
|
5386
|
+
}
|
|
5387
|
+
if (input.agent) {
|
|
5388
|
+
activityClauses.push("a.zaip_agent = ?");
|
|
5389
|
+
activityValues.push(input.agent);
|
|
5390
|
+
}
|
|
5391
|
+
if (input.phase) {
|
|
5392
|
+
activityClauses.push("a.phase = ?");
|
|
5393
|
+
activityValues.push(input.phase);
|
|
5394
|
+
}
|
|
5395
|
+
if (input.status) {
|
|
5396
|
+
activityClauses.push("a.status = ?");
|
|
5397
|
+
activityValues.push(input.status);
|
|
5398
|
+
}
|
|
5040
5399
|
if (input.priority || input.workType) activityClauses.push("0");
|
|
5041
|
-
if (input.risk) {
|
|
5042
|
-
|
|
5400
|
+
if (input.risk) {
|
|
5401
|
+
activityClauses.push("activity_task.risk = ?");
|
|
5402
|
+
activityValues.push(input.risk);
|
|
5403
|
+
}
|
|
5404
|
+
if (input.origin)
|
|
5405
|
+
activityClauses.push(
|
|
5406
|
+
input.origin === "prd"
|
|
5407
|
+
? "a.prd_id IS NOT NULL"
|
|
5408
|
+
: input.origin === "spec"
|
|
5409
|
+
? "a.prd_id IS NULL AND a.spec_id IS NOT NULL"
|
|
5410
|
+
: input.origin === "task"
|
|
5411
|
+
? "a.prd_id IS NULL AND a.spec_id IS NULL AND a.task_id IS NOT NULL"
|
|
5412
|
+
: "a.prd_id IS NULL AND a.spec_id IS NULL AND a.task_id IS NULL",
|
|
5413
|
+
);
|
|
5043
5414
|
if (input.provider || input.model) {
|
|
5044
5415
|
const direct: string[] = [];
|
|
5045
5416
|
const usage: string[] = [];
|
|
@@ -5053,30 +5424,83 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5053
5424
|
usage.push("COALESCE(u.model, 'unavailable') = ?");
|
|
5054
5425
|
activityValues.push(input.model);
|
|
5055
5426
|
}
|
|
5056
|
-
activityClauses.push(
|
|
5427
|
+
activityClauses.push(
|
|
5428
|
+
`((a.agent_run_id IS NULL AND ${direct.join(" AND ")}) OR (a.agent_run_id IS NOT NULL AND EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id = a.agent_run_id AND ${usage.join(" AND ")})))`,
|
|
5429
|
+
);
|
|
5057
5430
|
if (input.provider) activityValues.push(input.provider);
|
|
5058
5431
|
if (input.model) activityValues.push(input.model);
|
|
5059
5432
|
}
|
|
5060
|
-
if (input.query) {
|
|
5433
|
+
if (input.query) {
|
|
5434
|
+
const query = `%${input.query}%`;
|
|
5435
|
+
activityClauses.push(
|
|
5436
|
+
"(a.zaip_agent LIKE ? OR a.zaip_agent_persona LIKE ? OR a.zaip_agent_job LIKE ?)",
|
|
5437
|
+
);
|
|
5438
|
+
activityValues.push(query, query, query);
|
|
5439
|
+
}
|
|
5061
5440
|
const where = clauses.join(" AND ");
|
|
5062
|
-
const invocationClauses = [
|
|
5441
|
+
const invocationClauses = [
|
|
5442
|
+
"i.zaip_agent IS NOT NULL",
|
|
5443
|
+
"(i.total_tokens IS NOT NULL OR i.cost_brl IS NOT NULL OR i.cost_usd IS NOT NULL)",
|
|
5444
|
+
];
|
|
5063
5445
|
const invocationValues: Array<string | number> = [];
|
|
5064
|
-
const invocationSourceDay =
|
|
5065
|
-
|
|
5066
|
-
if (input.
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5446
|
+
const invocationSourceDay =
|
|
5447
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours')";
|
|
5448
|
+
if (input.from) {
|
|
5449
|
+
invocationClauses.push(`${invocationSourceDay} >= ?`);
|
|
5450
|
+
invocationValues.push(input.from.slice(0, 10));
|
|
5451
|
+
}
|
|
5452
|
+
if (input.to) {
|
|
5453
|
+
invocationClauses.push(`${invocationSourceDay} <= ?`);
|
|
5454
|
+
invocationValues.push(input.to.slice(0, 10));
|
|
5455
|
+
}
|
|
5456
|
+
if (input.agent) {
|
|
5457
|
+
invocationClauses.push("i.zaip_agent = ?");
|
|
5458
|
+
invocationValues.push(input.agent);
|
|
5459
|
+
}
|
|
5460
|
+
if (input.phase) {
|
|
5461
|
+
invocationClauses.push("i.phase = ?");
|
|
5462
|
+
invocationValues.push(input.phase);
|
|
5463
|
+
}
|
|
5464
|
+
if (input.status) {
|
|
5465
|
+
invocationClauses.push("i.status = ?");
|
|
5466
|
+
invocationValues.push(input.status);
|
|
5467
|
+
}
|
|
5070
5468
|
if (input.priority || input.workType) invocationClauses.push("0");
|
|
5071
|
-
if (input.risk) {
|
|
5072
|
-
|
|
5469
|
+
if (input.risk) {
|
|
5470
|
+
invocationClauses.push("invocation_task.risk = ?");
|
|
5471
|
+
invocationValues.push(input.risk);
|
|
5472
|
+
}
|
|
5473
|
+
if (input.origin)
|
|
5474
|
+
invocationClauses.push(
|
|
5475
|
+
input.origin === "prd"
|
|
5476
|
+
? "i.prd_id IS NOT NULL"
|
|
5477
|
+
: input.origin === "spec"
|
|
5478
|
+
? "i.prd_id IS NULL AND i.spec_id IS NOT NULL"
|
|
5479
|
+
: input.origin === "task"
|
|
5480
|
+
? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL"
|
|
5481
|
+
: "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL",
|
|
5482
|
+
);
|
|
5073
5483
|
if (input.provider || input.model) {
|
|
5074
5484
|
const segment: string[] = [];
|
|
5075
|
-
if (input.provider) {
|
|
5076
|
-
|
|
5077
|
-
|
|
5485
|
+
if (input.provider) {
|
|
5486
|
+
segment.push("COALESCE(u.provider, 'unavailable') = ?");
|
|
5487
|
+
invocationValues.push(input.provider);
|
|
5488
|
+
}
|
|
5489
|
+
if (input.model) {
|
|
5490
|
+
segment.push("COALESCE(u.model, 'unavailable') = ?");
|
|
5491
|
+
invocationValues.push(input.model);
|
|
5492
|
+
}
|
|
5493
|
+
invocationClauses.push(
|
|
5494
|
+
`EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id=i.agent_invocation_id AND ${segment.join(" AND ")})`,
|
|
5495
|
+
);
|
|
5496
|
+
}
|
|
5497
|
+
if (input.query) {
|
|
5498
|
+
const query = `%${input.query}%`;
|
|
5499
|
+
invocationClauses.push(
|
|
5500
|
+
"(i.zaip_agent LIKE ? OR i.zaip_agent_persona LIKE ? OR i.zaip_agent_job LIKE ?)",
|
|
5501
|
+
);
|
|
5502
|
+
invocationValues.push(query, query, query);
|
|
5078
5503
|
}
|
|
5079
|
-
if (input.query) { const query = `%${input.query}%`; invocationClauses.push("(i.zaip_agent LIKE ? OR i.zaip_agent_persona LIKE ? OR i.zaip_agent_job LIKE ?)"); invocationValues.push(query, query, query); }
|
|
5080
5504
|
const groupedFacts = `WITH agent_facts AS (
|
|
5081
5505
|
SELECT i.zaip_agent AS key, i.zaip_agent_persona AS persona, i.zaip_agent_job AS job, d.persona AS catalog_persona, d.job AS catalog_job,
|
|
5082
5506
|
i.occurred_at, i.task_id, i.duration_ms, i.source_event_count, i.total_tokens, i.cost_usd, i.cost_brl
|
|
@@ -5090,57 +5514,164 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5090
5514
|
)`;
|
|
5091
5515
|
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
5092
5516
|
const offset = Math.max(0, input.offset ?? 0);
|
|
5093
|
-
const total_count = Number(
|
|
5517
|
+
const total_count = Number(
|
|
5518
|
+
(
|
|
5519
|
+
this.database
|
|
5520
|
+
.prepare(`${groupedFacts} SELECT COUNT(DISTINCT key) AS count FROM agent_facts`)
|
|
5521
|
+
.get(...invocationValues, ...activityValues) as { count: number }
|
|
5522
|
+
).count,
|
|
5523
|
+
);
|
|
5094
5524
|
// Activity facts retain the catalog snapshot used when they were
|
|
5095
5525
|
// materialized. The current dimension is a safe fallback for older
|
|
5096
5526
|
// snapshots that predate those fields: it supplies only catalog display
|
|
5097
5527
|
// metadata, never tokens, cost, duration, or attribution.
|
|
5098
|
-
const totals = this.database
|
|
5099
|
-
|
|
5100
|
-
|
|
5528
|
+
const totals = this.database
|
|
5529
|
+
.prepare(
|
|
5530
|
+
`${groupedFacts} SELECT COUNT(*) AS invocation_count, COUNT(DISTINCT task_id) AS task_count, SUM(duration_ms) AS total_duration, SUM(CASE WHEN duration_ms IS NULL THEN 1 ELSE 0 END) AS duration_unknown, SUM(CASE WHEN duration_ms IS NULL THEN 0 ELSE duration_ms END) AS duration_known, SUM(source_event_count) AS source_event_count, SUM(total_tokens) AS total_tokens, SUM(cost_usd) AS total_cost_usd, SUM(cost_brl) AS total_cost_brl FROM agent_facts`,
|
|
5531
|
+
)
|
|
5532
|
+
.get(...invocationValues, ...activityValues) as Record<string, unknown>;
|
|
5533
|
+
const rows = this.database
|
|
5534
|
+
.prepare(
|
|
5535
|
+
`${groupedFacts} SELECT key, key AS label, COALESCE(MAX(persona), MAX(catalog_persona)) AS persona, COALESCE(MAX(job), MAX(catalog_job)) AS job, MAX(occurred_at) AS latest_started_at, COUNT(*) AS invocation_count, COUNT(DISTINCT task_id) AS task_count, SUM(duration_ms) AS total_duration, SUM(CASE WHEN duration_ms IS NULL THEN 1 ELSE 0 END) AS duration_unknown, SUM(CASE WHEN duration_ms IS NULL THEN 0 ELSE duration_ms END) AS duration_known, SUM(source_event_count) AS source_event_count, SUM(total_tokens) AS total_tokens, SUM(cost_usd) AS total_cost_usd, SUM(cost_brl) AS total_cost_brl FROM agent_facts GROUP BY key ORDER BY total_cost_brl DESC, key LIMIT ? OFFSET ?`,
|
|
5536
|
+
)
|
|
5537
|
+
.all(...invocationValues, ...activityValues, limit, offset) as Array<
|
|
5538
|
+
Record<string, unknown>
|
|
5539
|
+
>;
|
|
5540
|
+
return {
|
|
5541
|
+
data_source: "sqlite_read_model" as const,
|
|
5542
|
+
view,
|
|
5543
|
+
offset,
|
|
5544
|
+
limit,
|
|
5545
|
+
total_count,
|
|
5546
|
+
has_more: offset + rows.length < total_count,
|
|
5547
|
+
totals,
|
|
5548
|
+
rows,
|
|
5549
|
+
};
|
|
5101
5550
|
}
|
|
5102
5551
|
if (filteredCostGroup === "model") {
|
|
5103
5552
|
const clauses = ["1=1"];
|
|
5104
5553
|
const values: Array<string | number> = [];
|
|
5105
|
-
if (input.from) {
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5554
|
+
if (input.from) {
|
|
5555
|
+
clauses.push(
|
|
5556
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') >= ?",
|
|
5557
|
+
);
|
|
5558
|
+
values.push(input.from.slice(0, 10));
|
|
5559
|
+
}
|
|
5560
|
+
if (input.to) {
|
|
5561
|
+
clauses.push(
|
|
5562
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') <= ?",
|
|
5563
|
+
);
|
|
5564
|
+
values.push(input.to.slice(0, 10));
|
|
5565
|
+
}
|
|
5566
|
+
if (input.agent) {
|
|
5567
|
+
clauses.push("i.zaip_agent = ?");
|
|
5568
|
+
values.push(input.agent);
|
|
5569
|
+
}
|
|
5570
|
+
if (input.phase) {
|
|
5571
|
+
clauses.push("i.phase = ?");
|
|
5572
|
+
values.push(input.phase);
|
|
5573
|
+
}
|
|
5574
|
+
if (input.status) {
|
|
5575
|
+
clauses.push("i.status = ?");
|
|
5576
|
+
values.push(input.status);
|
|
5577
|
+
}
|
|
5110
5578
|
if (input.priority || input.workType) clauses.push("0");
|
|
5111
|
-
if (input.risk) {
|
|
5579
|
+
if (input.risk) {
|
|
5580
|
+
clauses.push("t.risk = ?");
|
|
5581
|
+
values.push(input.risk);
|
|
5582
|
+
}
|
|
5112
5583
|
if (input.origin) {
|
|
5113
|
-
clauses.push(
|
|
5584
|
+
clauses.push(
|
|
5585
|
+
input.origin === "prd"
|
|
5586
|
+
? "i.prd_id IS NOT NULL"
|
|
5587
|
+
: input.origin === "spec"
|
|
5588
|
+
? "i.prd_id IS NULL AND i.spec_id IS NOT NULL"
|
|
5589
|
+
: input.origin === "task"
|
|
5590
|
+
? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL"
|
|
5591
|
+
: "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL",
|
|
5592
|
+
);
|
|
5114
5593
|
}
|
|
5115
5594
|
if (input.provider) {
|
|
5116
|
-
if (input.provider === "unavailable")
|
|
5117
|
-
|
|
5595
|
+
if (input.provider === "unavailable")
|
|
5596
|
+
clauses.push(
|
|
5597
|
+
"s.provider IS NULL AND NOT EXISTS (SELECT 1 FROM invocation_usage known_provider WHERE known_provider.agent_invocation_id=i.agent_invocation_id AND known_provider.provider IS NOT NULL AND known_provider.provider <> 'unavailable')",
|
|
5598
|
+
);
|
|
5599
|
+
else {
|
|
5600
|
+
clauses.push("s.provider = ?");
|
|
5601
|
+
values.push(input.provider);
|
|
5602
|
+
}
|
|
5118
5603
|
}
|
|
5119
5604
|
if (input.model) {
|
|
5120
5605
|
if (input.model === "unavailable") clauses.push("s.model_key = 'unavailable'");
|
|
5121
|
-
else {
|
|
5606
|
+
else {
|
|
5607
|
+
clauses.push("s.model_key = ?");
|
|
5608
|
+
values.push(input.model);
|
|
5609
|
+
}
|
|
5610
|
+
}
|
|
5611
|
+
if (input.query) {
|
|
5612
|
+
clauses.push("s.model_key LIKE ?");
|
|
5613
|
+
values.push(`%${input.query}%`);
|
|
5122
5614
|
}
|
|
5123
|
-
if (input.query) { clauses.push("s.model_key LIKE ?"); values.push(`%${input.query}%`); }
|
|
5124
5615
|
const where = clauses.join(" AND ");
|
|
5125
5616
|
const activityClauses = ["a.activity_source_kind <> 'agent_run'"];
|
|
5126
5617
|
const activityValues: Array<string | number> = [];
|
|
5127
|
-
if (input.from) {
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
if (input.
|
|
5618
|
+
if (input.from) {
|
|
5619
|
+
activityClauses.push("a.occurred_day_sp >= ?");
|
|
5620
|
+
activityValues.push(input.from.slice(0, 10));
|
|
5621
|
+
}
|
|
5622
|
+
if (input.to) {
|
|
5623
|
+
activityClauses.push("a.occurred_day_sp <= ?");
|
|
5624
|
+
activityValues.push(input.to.slice(0, 10));
|
|
5625
|
+
}
|
|
5626
|
+
if (input.agent) {
|
|
5627
|
+
activityClauses.push("a.zaip_agent = ?");
|
|
5628
|
+
activityValues.push(input.agent);
|
|
5629
|
+
}
|
|
5630
|
+
if (input.phase) {
|
|
5631
|
+
activityClauses.push("a.phase = ?");
|
|
5632
|
+
activityValues.push(input.phase);
|
|
5633
|
+
}
|
|
5634
|
+
if (input.status) {
|
|
5635
|
+
activityClauses.push("a.status = ?");
|
|
5636
|
+
activityValues.push(input.status);
|
|
5637
|
+
}
|
|
5132
5638
|
if (input.priority || input.workType) activityClauses.push("0");
|
|
5133
|
-
if (input.risk) {
|
|
5134
|
-
|
|
5639
|
+
if (input.risk) {
|
|
5640
|
+
activityClauses.push("activity_task.risk = ?");
|
|
5641
|
+
activityValues.push(input.risk);
|
|
5642
|
+
}
|
|
5643
|
+
if (input.origin)
|
|
5644
|
+
activityClauses.push(
|
|
5645
|
+
input.origin === "prd"
|
|
5646
|
+
? "a.prd_id IS NOT NULL"
|
|
5647
|
+
: input.origin === "spec"
|
|
5648
|
+
? "a.prd_id IS NULL AND a.spec_id IS NOT NULL"
|
|
5649
|
+
: input.origin === "task"
|
|
5650
|
+
? "a.prd_id IS NULL AND a.spec_id IS NULL AND a.task_id IS NOT NULL"
|
|
5651
|
+
: "a.prd_id IS NULL AND a.spec_id IS NULL AND a.task_id IS NULL",
|
|
5652
|
+
);
|
|
5135
5653
|
if (input.provider) {
|
|
5136
|
-
if (input.provider === "unavailable")
|
|
5137
|
-
|
|
5654
|
+
if (input.provider === "unavailable")
|
|
5655
|
+
activityClauses.push(
|
|
5656
|
+
"a.provider IS NULL OR trim(a.provider) = '' OR a.provider = 'unavailable'",
|
|
5657
|
+
);
|
|
5658
|
+
else {
|
|
5659
|
+
activityClauses.push("a.provider = ?");
|
|
5660
|
+
activityValues.push(input.provider);
|
|
5661
|
+
}
|
|
5138
5662
|
}
|
|
5139
5663
|
if (input.model) {
|
|
5140
|
-
if (input.model === "unavailable")
|
|
5141
|
-
|
|
5664
|
+
if (input.model === "unavailable")
|
|
5665
|
+
activityClauses.push("a.model IS NULL OR trim(a.model) = '' OR a.model = 'unavailable'");
|
|
5666
|
+
else {
|
|
5667
|
+
activityClauses.push("a.model = ?");
|
|
5668
|
+
activityValues.push(input.model);
|
|
5669
|
+
}
|
|
5670
|
+
}
|
|
5671
|
+
if (input.query) {
|
|
5672
|
+
activityClauses.push("COALESCE(a.model, 'unavailable') LIKE ?");
|
|
5673
|
+
activityValues.push(`%${input.query}%`);
|
|
5142
5674
|
}
|
|
5143
|
-
if (input.query) { activityClauses.push("COALESCE(a.model, 'unavailable') LIKE ?"); activityValues.push(`%${input.query}%`); }
|
|
5144
5675
|
const ctes = `WITH segment_totals AS (
|
|
5145
5676
|
SELECT agent_invocation_id, SUM(input_tokens) AS input_tokens, SUM(cached_input_tokens) AS cached_input_tokens,
|
|
5146
5677
|
SUM(output_tokens) AS output_tokens, SUM(reasoning_tokens) AS reasoning_tokens,
|
|
@@ -5223,7 +5754,13 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5223
5754
|
)`;
|
|
5224
5755
|
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
5225
5756
|
const offset = Math.max(0, input.offset ?? 0);
|
|
5226
|
-
const total_count = Number(
|
|
5757
|
+
const total_count = Number(
|
|
5758
|
+
(
|
|
5759
|
+
this.database
|
|
5760
|
+
.prepare(`${ctes} ${facts} SELECT COUNT(*) AS count FROM keys`)
|
|
5761
|
+
.get(...values, ...activityValues) as { count: number }
|
|
5762
|
+
).count,
|
|
5763
|
+
);
|
|
5227
5764
|
// Dimension membership is intentionally non-additive: one Task can use
|
|
5228
5765
|
// several concrete models. Expose the distinct population separately so
|
|
5229
5766
|
// a footer never reports the inflated sum of row memberships as total.
|
|
@@ -5244,23 +5781,82 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5244
5781
|
SUM(cost_brl) AS total_cost_brl
|
|
5245
5782
|
FROM filtered_segments`)
|
|
5246
5783
|
.get(...values, ...activityValues) as Record<string, unknown>;
|
|
5247
|
-
const rows = this.database
|
|
5248
|
-
|
|
5784
|
+
const rows = this.database
|
|
5785
|
+
.prepare(
|
|
5786
|
+
`${ctes} ${facts} SELECT keys.key, keys.key AS label, COALESCE(population.invocation_count, 0) AS invocation_count, COALESCE(task_population.task_count, 0) AS task_count, population.total_duration, population.source_event_count, financial.total_tokens, financial.total_cost_usd, financial.total_cost_brl FROM keys LEFT JOIN population USING(key) LEFT JOIN task_population USING(key) LEFT JOIN financial USING(key) ORDER BY financial.total_cost_brl DESC, keys.key LIMIT ? OFFSET ?`,
|
|
5787
|
+
)
|
|
5788
|
+
.all(...values, ...activityValues, limit, offset) as Array<Record<string, unknown>>;
|
|
5789
|
+
return {
|
|
5790
|
+
data_source: "sqlite_read_model" as const,
|
|
5791
|
+
view,
|
|
5792
|
+
offset,
|
|
5793
|
+
limit,
|
|
5794
|
+
total_count,
|
|
5795
|
+
has_more: offset + rows.length < total_count,
|
|
5796
|
+
totals,
|
|
5797
|
+
rows,
|
|
5798
|
+
};
|
|
5249
5799
|
}
|
|
5250
5800
|
if (filteredCostGroup === "provider") {
|
|
5251
5801
|
const clauses = ["1=1"];
|
|
5252
5802
|
const values: Array<string | number> = [];
|
|
5253
|
-
if (input.from) {
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5803
|
+
if (input.from) {
|
|
5804
|
+
clauses.push(
|
|
5805
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') >= ?",
|
|
5806
|
+
);
|
|
5807
|
+
values.push(input.from.slice(0, 10));
|
|
5808
|
+
}
|
|
5809
|
+
if (input.to) {
|
|
5810
|
+
clauses.push(
|
|
5811
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') <= ?",
|
|
5812
|
+
);
|
|
5813
|
+
values.push(input.to.slice(0, 10));
|
|
5814
|
+
}
|
|
5815
|
+
if (input.agent) {
|
|
5816
|
+
clauses.push("i.zaip_agent = ?");
|
|
5817
|
+
values.push(input.agent);
|
|
5818
|
+
}
|
|
5819
|
+
if (input.phase) {
|
|
5820
|
+
clauses.push("i.phase = ?");
|
|
5821
|
+
values.push(input.phase);
|
|
5822
|
+
}
|
|
5823
|
+
if (input.status) {
|
|
5824
|
+
clauses.push("i.status = ?");
|
|
5825
|
+
values.push(input.status);
|
|
5826
|
+
}
|
|
5258
5827
|
if (input.priority || input.workType) clauses.push("0");
|
|
5259
|
-
if (input.risk) {
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
if (input.
|
|
5828
|
+
if (input.risk) {
|
|
5829
|
+
clauses.push("t.risk = ?");
|
|
5830
|
+
values.push(input.risk);
|
|
5831
|
+
}
|
|
5832
|
+
if (input.origin)
|
|
5833
|
+
clauses.push(
|
|
5834
|
+
input.origin === "prd"
|
|
5835
|
+
? "i.prd_id IS NOT NULL"
|
|
5836
|
+
: input.origin === "spec"
|
|
5837
|
+
? "i.prd_id IS NULL AND i.spec_id IS NOT NULL"
|
|
5838
|
+
: input.origin === "task"
|
|
5839
|
+
? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL"
|
|
5840
|
+
: "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL",
|
|
5841
|
+
);
|
|
5842
|
+
if (input.provider) {
|
|
5843
|
+
if (input.provider === "unavailable") clauses.push("s.provider_key = 'unavailable'");
|
|
5844
|
+
else {
|
|
5845
|
+
clauses.push("s.provider_key = ?");
|
|
5846
|
+
values.push(input.provider);
|
|
5847
|
+
}
|
|
5848
|
+
}
|
|
5849
|
+
if (input.model) {
|
|
5850
|
+
if (input.model === "unavailable") clauses.push("s.model_key = 'unavailable'");
|
|
5851
|
+
else {
|
|
5852
|
+
clauses.push("s.model_key = ?");
|
|
5853
|
+
values.push(input.model);
|
|
5854
|
+
}
|
|
5855
|
+
}
|
|
5856
|
+
if (input.query) {
|
|
5857
|
+
clauses.push("s.provider_key LIKE ?");
|
|
5858
|
+
values.push(`%${input.query}%`);
|
|
5859
|
+
}
|
|
5264
5860
|
const where = clauses.join(" AND ");
|
|
5265
5861
|
const ctes = `WITH segment_totals AS (
|
|
5266
5862
|
SELECT agent_invocation_id, SUM(input_tokens) AS input_tokens, SUM(cached_input_tokens) AS cached_input_tokens,
|
|
@@ -5319,8 +5915,15 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5319
5915
|
SELECT provider_key AS key, COUNT(*) AS task_count
|
|
5320
5916
|
FROM task_providers GROUP BY provider_key
|
|
5321
5917
|
), keys AS (SELECT key FROM financial UNION SELECT key FROM population UNION SELECT key FROM task_population)`;
|
|
5322
|
-
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
5323
|
-
const
|
|
5918
|
+
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
5919
|
+
const offset = Math.max(0, input.offset ?? 0);
|
|
5920
|
+
const total_count = Number(
|
|
5921
|
+
(
|
|
5922
|
+
this.database
|
|
5923
|
+
.prepare(`${ctes} ${facts} SELECT COUNT(*) AS count FROM keys`)
|
|
5924
|
+
.get(...values) as { count: number }
|
|
5925
|
+
).count,
|
|
5926
|
+
);
|
|
5324
5927
|
const totals = this.database
|
|
5325
5928
|
.prepare(`${ctes} ${facts} SELECT
|
|
5326
5929
|
(SELECT COUNT(*) FROM population_ranked WHERE provider_rank = 1) AS invocation_count,
|
|
@@ -5338,13 +5941,34 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5338
5941
|
SUM(cost_brl) AS total_cost_brl
|
|
5339
5942
|
FROM filtered_segments`)
|
|
5340
5943
|
.get(...values) as Record<string, unknown>;
|
|
5341
|
-
const rows = this.database
|
|
5342
|
-
|
|
5944
|
+
const rows = this.database
|
|
5945
|
+
.prepare(
|
|
5946
|
+
`${ctes} ${facts} SELECT keys.key, keys.key AS label, COALESCE(population.invocation_count,0) AS invocation_count, COALESCE(task_population.task_count,0) AS task_count, population.total_duration, population.source_event_count, financial.total_tokens, financial.total_cost_usd, financial.total_cost_brl FROM keys LEFT JOIN population USING(key) LEFT JOIN task_population USING(key) LEFT JOIN financial USING(key) ORDER BY financial.total_cost_brl DESC, keys.key LIMIT ? OFFSET ?`,
|
|
5947
|
+
)
|
|
5948
|
+
.all(...values, limit, offset) as Array<Record<string, unknown>>;
|
|
5949
|
+
return {
|
|
5950
|
+
data_source: "sqlite_read_model" as const,
|
|
5951
|
+
view,
|
|
5952
|
+
offset,
|
|
5953
|
+
limit,
|
|
5954
|
+
total_count,
|
|
5955
|
+
has_more: offset + rows.length < total_count,
|
|
5956
|
+
totals,
|
|
5957
|
+
rows,
|
|
5958
|
+
};
|
|
5343
5959
|
}
|
|
5344
5960
|
const temporalCostGroup =
|
|
5345
5961
|
filteredCostGroup === "day" || filteredCostGroup === "month" || filteredCostGroup === "year";
|
|
5346
5962
|
const hasDimensionalCostFilter = Boolean(
|
|
5347
|
-
input.provider ||
|
|
5963
|
+
input.provider ||
|
|
5964
|
+
input.model ||
|
|
5965
|
+
input.agent ||
|
|
5966
|
+
input.phase ||
|
|
5967
|
+
input.status ||
|
|
5968
|
+
input.origin ||
|
|
5969
|
+
input.priority ||
|
|
5970
|
+
input.risk ||
|
|
5971
|
+
input.workType,
|
|
5348
5972
|
);
|
|
5349
5973
|
if (filteredCostGroup !== undefined && temporalCostGroup && !hasDimensionalCostFilter) {
|
|
5350
5974
|
const from = costMonitorFilterDay(input.from);
|
|
@@ -5400,12 +6024,14 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5400
6024
|
for (const column of additive) {
|
|
5401
6025
|
const value = financial?.[column];
|
|
5402
6026
|
if (typeof value === "number")
|
|
5403
|
-
target[column] =
|
|
6027
|
+
target[column] =
|
|
6028
|
+
(typeof target[column] === "number" ? Number(target[column]) : 0) + value;
|
|
5404
6029
|
}
|
|
5405
6030
|
const duration = financial?.total_duration;
|
|
5406
6031
|
if (typeof duration === "number") {
|
|
5407
6032
|
target.total_duration =
|
|
5408
|
-
(typeof target.total_duration === "number" ? Number(target.total_duration) : 0) +
|
|
6033
|
+
(typeof target.total_duration === "number" ? Number(target.total_duration) : 0) +
|
|
6034
|
+
duration;
|
|
5409
6035
|
}
|
|
5410
6036
|
if (typeof financial?.duration_unknown === "number" && financial.duration_unknown > 0)
|
|
5411
6037
|
target.duration_incomplete = true;
|
|
@@ -5440,7 +6066,20 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5440
6066
|
.filter((row) => query === null || String(row.key).toLocaleLowerCase().includes(query))
|
|
5441
6067
|
.sort((left, right) => String(right.key).localeCompare(String(left.key)));
|
|
5442
6068
|
const totals = allRows.reduce<Record<string, number | null>>((result, row) => {
|
|
5443
|
-
for (const key of [
|
|
6069
|
+
for (const key of [
|
|
6070
|
+
"prd_count",
|
|
6071
|
+
"spec_count",
|
|
6072
|
+
"task_count",
|
|
6073
|
+
"total_run_count",
|
|
6074
|
+
"validation_count",
|
|
6075
|
+
"evidence_count",
|
|
6076
|
+
"total_duration",
|
|
6077
|
+
"duration_known",
|
|
6078
|
+
"duration_unknown",
|
|
6079
|
+
"total_tokens",
|
|
6080
|
+
"total_cost_usd",
|
|
6081
|
+
"total_cost_brl",
|
|
6082
|
+
] as const) {
|
|
5444
6083
|
const value = row[key];
|
|
5445
6084
|
if (typeof value === "number") result[key] = (result[key] ?? 0) + value;
|
|
5446
6085
|
else if (!(key in result)) result[key] = null;
|
|
@@ -5465,7 +6104,15 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5465
6104
|
if (temporalCostGroup && input.workType) {
|
|
5466
6105
|
// No canonical work_type exists at invocation grain, including for
|
|
5467
6106
|
// duration/Run completion backfills. Do not emit unrelated buckets.
|
|
5468
|
-
return {
|
|
6107
|
+
return {
|
|
6108
|
+
data_source: "sqlite_read_model" as const,
|
|
6109
|
+
view,
|
|
6110
|
+
offset: Math.max(0, input.offset ?? 0),
|
|
6111
|
+
limit: Math.min(200, Math.max(1, input.limit ?? 50)),
|
|
6112
|
+
total_count: 0,
|
|
6113
|
+
has_more: false,
|
|
6114
|
+
rows: [],
|
|
6115
|
+
};
|
|
5469
6116
|
}
|
|
5470
6117
|
const groupExpression =
|
|
5471
6118
|
filteredCostGroup === "day"
|
|
@@ -5481,17 +6128,23 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5481
6128
|
];
|
|
5482
6129
|
const values: Array<string | number> = [];
|
|
5483
6130
|
if (input.from) {
|
|
5484
|
-
clauses.push(
|
|
6131
|
+
clauses.push(
|
|
6132
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') >= ?",
|
|
6133
|
+
);
|
|
5485
6134
|
values.push(costMonitorFilterDay(input.from)!);
|
|
5486
6135
|
}
|
|
5487
6136
|
if (input.to) {
|
|
5488
|
-
clauses.push(
|
|
6137
|
+
clauses.push(
|
|
6138
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') <= ?",
|
|
6139
|
+
);
|
|
5489
6140
|
values.push(costMonitorFilterDay(input.to)!);
|
|
5490
6141
|
}
|
|
5491
6142
|
const usageSegmentClauses: string[] = [];
|
|
5492
6143
|
if (input.provider) {
|
|
5493
6144
|
if (input.provider === "unavailable") {
|
|
5494
|
-
clauses.push(
|
|
6145
|
+
clauses.push(
|
|
6146
|
+
"NOT EXISTS (SELECT 1 FROM invocation_usage known_provider WHERE known_provider.agent_invocation_id=i.agent_invocation_id AND known_provider.provider IS NOT NULL AND known_provider.provider <> 'unavailable')",
|
|
6147
|
+
);
|
|
5495
6148
|
} else {
|
|
5496
6149
|
usageSegmentClauses.push("f.provider=?");
|
|
5497
6150
|
values.push(input.provider);
|
|
@@ -5499,14 +6152,18 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5499
6152
|
}
|
|
5500
6153
|
if (input.model) {
|
|
5501
6154
|
if (input.model === "unavailable") {
|
|
5502
|
-
clauses.push(
|
|
6155
|
+
clauses.push(
|
|
6156
|
+
"NOT EXISTS (SELECT 1 FROM invocation_usage known_model WHERE known_model.agent_invocation_id=i.agent_invocation_id AND known_model.model IS NOT NULL AND known_model.model <> 'unavailable')",
|
|
6157
|
+
);
|
|
5503
6158
|
} else {
|
|
5504
6159
|
usageSegmentClauses.push("f.model=?");
|
|
5505
6160
|
values.push(input.model);
|
|
5506
6161
|
}
|
|
5507
6162
|
}
|
|
5508
6163
|
if (usageSegmentClauses.length > 0)
|
|
5509
|
-
clauses.push(
|
|
6164
|
+
clauses.push(
|
|
6165
|
+
`EXISTS (SELECT 1 FROM invocation_usage f WHERE f.agent_invocation_id=i.agent_invocation_id AND ${usageSegmentClauses.join(" AND ")})`,
|
|
6166
|
+
);
|
|
5510
6167
|
if (input.agent) {
|
|
5511
6168
|
clauses.push("i.zaip_agent = ?");
|
|
5512
6169
|
values.push(input.agent);
|
|
@@ -5602,19 +6259,43 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5602
6259
|
const factualDuration = this.costMonitorFactualDurationByDay(canonicalFilter);
|
|
5603
6260
|
// Lifecycle counts are canonical facts keyed by their own occurrence
|
|
5604
6261
|
// date. They deliberately do not inherit financial dimensions.
|
|
5605
|
-
const hasUnsupportedLifecycleAttribution = Boolean(
|
|
6262
|
+
const hasUnsupportedLifecycleAttribution = Boolean(
|
|
6263
|
+
input.origin || input.priority || input.risk || input.workType,
|
|
6264
|
+
);
|
|
5606
6265
|
const lifecycleRows = hasUnsupportedLifecycleAttribution
|
|
5607
6266
|
? []
|
|
5608
|
-
: this.database
|
|
6267
|
+
: (this.database
|
|
5609
6268
|
.prepare(`SELECT day, prd_count, spec_count, task_count, validation_count, evidence_count
|
|
5610
6269
|
FROM daily_aggregate_cache WHERE (? IS NULL OR day >= ?) AND (? IS NULL OR day <= ?)`)
|
|
5611
|
-
.all(
|
|
6270
|
+
.all(
|
|
6271
|
+
costMonitorFilterDay(input.from),
|
|
6272
|
+
costMonitorFilterDay(input.from),
|
|
6273
|
+
costMonitorFilterDay(input.to),
|
|
6274
|
+
costMonitorFilterDay(input.to),
|
|
6275
|
+
) as Array<Record<string, unknown>>);
|
|
5612
6276
|
const lifecycleByKey = new Map<string, Record<string, number>>();
|
|
5613
6277
|
for (const row of lifecycleRows) {
|
|
5614
6278
|
const day = String(row.day);
|
|
5615
|
-
const key =
|
|
5616
|
-
|
|
5617
|
-
|
|
6279
|
+
const key =
|
|
6280
|
+
filteredCostGroup === "day"
|
|
6281
|
+
? day
|
|
6282
|
+
: filteredCostGroup === "month"
|
|
6283
|
+
? day.slice(0, 7)
|
|
6284
|
+
: day.slice(0, 4);
|
|
6285
|
+
const target = lifecycleByKey.get(key) ?? {
|
|
6286
|
+
prd_count: 0,
|
|
6287
|
+
spec_count: 0,
|
|
6288
|
+
task_count: 0,
|
|
6289
|
+
validation_count: 0,
|
|
6290
|
+
evidence_count: 0,
|
|
6291
|
+
};
|
|
6292
|
+
for (const column of [
|
|
6293
|
+
"prd_count",
|
|
6294
|
+
"spec_count",
|
|
6295
|
+
"task_count",
|
|
6296
|
+
"validation_count",
|
|
6297
|
+
"evidence_count",
|
|
6298
|
+
] as const)
|
|
5618
6299
|
target[column] = (target[column] ?? 0) + Number(row[column] ?? 0);
|
|
5619
6300
|
lifecycleByKey.set(key, target);
|
|
5620
6301
|
}
|
|
@@ -5622,7 +6303,12 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5622
6303
|
const canonicalKeys = new Set(
|
|
5623
6304
|
(input.query
|
|
5624
6305
|
? []
|
|
5625
|
-
: [
|
|
6306
|
+
: [
|
|
6307
|
+
...new Set([
|
|
6308
|
+
...factualDuration.keys(),
|
|
6309
|
+
...lifecycleRows.map((row) => String(row.day)),
|
|
6310
|
+
]),
|
|
6311
|
+
]
|
|
5626
6312
|
).map((day) =>
|
|
5627
6313
|
filteredCostGroup === "day"
|
|
5628
6314
|
? day
|
|
@@ -5653,12 +6339,26 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5653
6339
|
const durationMatches = [...factualDuration].filter(([day]) => dayMatches(day));
|
|
5654
6340
|
return {
|
|
5655
6341
|
...financial,
|
|
5656
|
-
...(lifecycleByKey.get(key) ??
|
|
5657
|
-
|
|
5658
|
-
|
|
6342
|
+
...(lifecycleByKey.get(key) ??
|
|
6343
|
+
(hasUnsupportedLifecycleAttribution
|
|
6344
|
+
? {
|
|
6345
|
+
prd_count: null,
|
|
6346
|
+
spec_count: null,
|
|
6347
|
+
task_count: null,
|
|
6348
|
+
validation_count: null,
|
|
6349
|
+
evidence_count: null,
|
|
6350
|
+
}
|
|
6351
|
+
: {
|
|
6352
|
+
prd_count: 0,
|
|
6353
|
+
spec_count: 0,
|
|
6354
|
+
task_count: 0,
|
|
6355
|
+
validation_count: 0,
|
|
6356
|
+
evidence_count: 0,
|
|
6357
|
+
})),
|
|
5659
6358
|
total_run_count: Number(financial.invocation_count ?? 0),
|
|
5660
6359
|
total_duration:
|
|
5661
|
-
durationMatches.length === 0 ||
|
|
6360
|
+
durationMatches.length === 0 ||
|
|
6361
|
+
durationMatches.some(([, duration]) => duration.eligible_runs !== duration.known_runs)
|
|
5662
6362
|
? null
|
|
5663
6363
|
: durationMatches.reduce((sum, [, duration]) => sum + duration.known_sum, 0),
|
|
5664
6364
|
};
|
|
@@ -5701,8 +6401,13 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5701
6401
|
// or belongs to another period. Financial measures stay nullable.
|
|
5702
6402
|
const isCanonicalMacro = groupKind === "spec" || groupKind === "prd";
|
|
5703
6403
|
const hasInvocationOnlyFilter = Boolean(
|
|
5704
|
-
input.provider ||
|
|
5705
|
-
|
|
6404
|
+
input.provider ||
|
|
6405
|
+
input.model ||
|
|
6406
|
+
input.agent ||
|
|
6407
|
+
input.phase ||
|
|
6408
|
+
input.origin ||
|
|
6409
|
+
input.workType ||
|
|
6410
|
+
input.priority,
|
|
5706
6411
|
);
|
|
5707
6412
|
if (isCanonicalMacro && !hasInvocationOnlyFilter) {
|
|
5708
6413
|
const entityTable = groupKind === "spec" ? "specs" : "prds";
|
|
@@ -5736,7 +6441,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5736
6441
|
entityValues.push(`%${input.query}%`, `%${input.query}%`);
|
|
5737
6442
|
}
|
|
5738
6443
|
const entityWhere = entityClauses.length === 0 ? "1" : entityClauses.join(" AND ");
|
|
5739
|
-
const invocationJoin =
|
|
6444
|
+
const invocationJoin =
|
|
6445
|
+
invocationClauses.length === 0 ? "" : ` AND ${invocationClauses.join(" AND ")}`;
|
|
5740
6446
|
const ctes = `WITH grouped AS (
|
|
5741
6447
|
SELECT entity.id AS group_value, entity.title, entity.status, entity.risk,
|
|
5742
6448
|
COALESCE(MIN(i.occurred_day_sp), entity.occurred_day_sp) AS first_day,
|
|
@@ -5758,42 +6464,135 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5758
6464
|
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
5759
6465
|
const offset = Math.max(0, input.offset ?? 0);
|
|
5760
6466
|
const values = [...invocationValues, ...entityValues];
|
|
5761
|
-
const total_count = Number(
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
6467
|
+
const total_count = Number(
|
|
6468
|
+
(
|
|
6469
|
+
this.database
|
|
6470
|
+
.prepare(`${ctes} SELECT COUNT(*) AS count FROM grouped`)
|
|
6471
|
+
.get(...values) as { count: number }
|
|
6472
|
+
).count,
|
|
6473
|
+
);
|
|
6474
|
+
const totals = this.database
|
|
6475
|
+
.prepare(
|
|
6476
|
+
`${ctes} SELECT COUNT(*) AS entity_count, COALESCE(SUM(invocation_count), 0) AS invocation_count, ROUND(SUM(total_duration)) AS total_duration, SUM(total_tokens) AS total_tokens, SUM(total_cost_usd) AS total_cost_usd, SUM(total_cost_brl) AS total_cost_brl FROM grouped`,
|
|
6477
|
+
)
|
|
6478
|
+
.get(...values) as Record<string, unknown>;
|
|
6479
|
+
const rows = this.database
|
|
6480
|
+
.prepare(
|
|
6481
|
+
`${ctes} SELECT group_value AS id, group_value AS key, title, status, risk, first_day, last_day, occurred_at, invocation_count, llm_call_count, total_duration, total_input_tokens, total_cached_input_tokens, total_output_tokens, total_reasoning_tokens, total_tokens, total_cost_usd, total_cost_brl FROM grouped ORDER BY total_cost_brl DESC, group_value LIMIT ? OFFSET ?`,
|
|
6482
|
+
)
|
|
6483
|
+
.all(...values, limit, offset) as Array<Record<string, unknown>>;
|
|
6484
|
+
return {
|
|
6485
|
+
data_source: "sqlite_read_model" as const,
|
|
6486
|
+
view,
|
|
6487
|
+
offset,
|
|
6488
|
+
limit,
|
|
6489
|
+
total_count,
|
|
6490
|
+
has_more: offset + rows.length < total_count,
|
|
6491
|
+
totals,
|
|
6492
|
+
rows,
|
|
6493
|
+
};
|
|
5765
6494
|
}
|
|
5766
6495
|
const hasDimensionalFilter = Boolean(
|
|
5767
|
-
input.provider ||
|
|
5768
|
-
|
|
6496
|
+
input.provider ||
|
|
6497
|
+
input.model ||
|
|
6498
|
+
input.agent ||
|
|
6499
|
+
input.phase ||
|
|
6500
|
+
input.status ||
|
|
6501
|
+
input.origin ||
|
|
6502
|
+
input.workType ||
|
|
6503
|
+
input.priority ||
|
|
6504
|
+
input.risk,
|
|
5769
6505
|
);
|
|
5770
6506
|
if (hasDimensionalFilter) {
|
|
5771
6507
|
const column = {
|
|
5772
|
-
task: "i.task_id",
|
|
5773
|
-
|
|
6508
|
+
task: "i.task_id",
|
|
6509
|
+
spec: "i.spec_id",
|
|
6510
|
+
prd: "i.prd_id",
|
|
6511
|
+
execution: "i.execution_id",
|
|
6512
|
+
conversation: "i.conversation_id",
|
|
5774
6513
|
}[groupKind];
|
|
5775
|
-
const entity =
|
|
6514
|
+
const entity =
|
|
6515
|
+
groupKind === "task"
|
|
6516
|
+
? "tasks"
|
|
6517
|
+
: groupKind === "spec"
|
|
6518
|
+
? "specs"
|
|
6519
|
+
: groupKind === "prd"
|
|
6520
|
+
? "prds"
|
|
6521
|
+
: null;
|
|
5776
6522
|
const clauses = [`${column} IS NOT NULL`];
|
|
5777
6523
|
const values: Array<string | number> = [];
|
|
5778
|
-
if (input.from) {
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
if (input.
|
|
5783
|
-
|
|
5784
|
-
|
|
6524
|
+
if (input.from) {
|
|
6525
|
+
clauses.push("i.occurred_day_sp >= ?");
|
|
6526
|
+
values.push(input.from.slice(0, 10));
|
|
6527
|
+
}
|
|
6528
|
+
if (input.to) {
|
|
6529
|
+
clauses.push("i.occurred_day_sp <= ?");
|
|
6530
|
+
values.push(input.to.slice(0, 10));
|
|
6531
|
+
}
|
|
6532
|
+
if (input.agent) {
|
|
6533
|
+
clauses.push("i.zaip_agent = ?");
|
|
6534
|
+
values.push(input.agent);
|
|
6535
|
+
}
|
|
6536
|
+
if (input.phase) {
|
|
6537
|
+
clauses.push("i.phase = ?");
|
|
6538
|
+
values.push(input.phase);
|
|
6539
|
+
}
|
|
6540
|
+
if (input.status) {
|
|
6541
|
+
clauses.push("i.status = ?");
|
|
6542
|
+
values.push(input.status);
|
|
6543
|
+
}
|
|
6544
|
+
if (input.risk) {
|
|
6545
|
+
clauses.push("task_entity.risk = ?");
|
|
6546
|
+
values.push(input.risk);
|
|
6547
|
+
}
|
|
6548
|
+
if (input.origin)
|
|
6549
|
+
clauses.push(
|
|
6550
|
+
input.origin === "prd"
|
|
6551
|
+
? "i.prd_id IS NOT NULL"
|
|
6552
|
+
: input.origin === "spec"
|
|
6553
|
+
? "i.prd_id IS NULL AND i.spec_id IS NOT NULL"
|
|
6554
|
+
: input.origin === "task"
|
|
6555
|
+
? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL"
|
|
6556
|
+
: "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL",
|
|
6557
|
+
);
|
|
5785
6558
|
// Compact lifecycle rows do not materialize canonical work type or Task priority.
|
|
5786
6559
|
// Return a safe empty result instead of substituting another attribution.
|
|
5787
6560
|
if (input.workType || input.priority) clauses.push("0");
|
|
5788
6561
|
const segment: string[] = [];
|
|
5789
|
-
if (input.provider) {
|
|
5790
|
-
|
|
5791
|
-
|
|
6562
|
+
if (input.provider) {
|
|
6563
|
+
if (input.provider === "unavailable")
|
|
6564
|
+
clauses.push(
|
|
6565
|
+
"NOT EXISTS (SELECT 1 FROM invocation_usage known_provider WHERE known_provider.agent_invocation_id=i.agent_invocation_id AND known_provider.provider IS NOT NULL AND known_provider.provider <> 'unavailable')",
|
|
6566
|
+
);
|
|
6567
|
+
else {
|
|
6568
|
+
segment.push("u.provider = ?");
|
|
6569
|
+
values.push(input.provider);
|
|
6570
|
+
}
|
|
6571
|
+
}
|
|
6572
|
+
if (input.model) {
|
|
6573
|
+
if (input.model === "unavailable")
|
|
6574
|
+
clauses.push(
|
|
6575
|
+
"NOT EXISTS (SELECT 1 FROM invocation_usage known_model WHERE known_model.agent_invocation_id=i.agent_invocation_id AND known_model.model IS NOT NULL AND known_model.model <> 'unavailable')",
|
|
6576
|
+
);
|
|
6577
|
+
else {
|
|
6578
|
+
segment.push("u.model = ?");
|
|
6579
|
+
values.push(input.model);
|
|
6580
|
+
}
|
|
6581
|
+
}
|
|
6582
|
+
if (segment.length > 0)
|
|
6583
|
+
clauses.push(
|
|
6584
|
+
`EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id=i.agent_invocation_id AND ${segment.join(" AND ")})`,
|
|
6585
|
+
);
|
|
5792
6586
|
if (input.query) {
|
|
5793
|
-
clauses.push(
|
|
6587
|
+
clauses.push(
|
|
6588
|
+
entity === null
|
|
6589
|
+
? `${column} LIKE ?`
|
|
6590
|
+
: `(${column} LIKE ? OR COALESCE(entity.title, '') LIKE ?)`,
|
|
6591
|
+
);
|
|
5794
6592
|
values.push(`%${input.query}%`, ...(entity === null ? [] : [`%${input.query}%`]));
|
|
5795
6593
|
}
|
|
5796
|
-
const entityJoin =
|
|
6594
|
+
const entityJoin =
|
|
6595
|
+
entity === null ? "" : `LEFT JOIN ${entity} entity ON entity.id=${column}`;
|
|
5797
6596
|
const taskJoin = "LEFT JOIN tasks task_entity ON task_entity.id=i.task_id";
|
|
5798
6597
|
const join = `${entityJoin} ${taskJoin}`;
|
|
5799
6598
|
const where = clauses.join(" AND ");
|
|
@@ -5802,17 +6601,60 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5802
6601
|
const ctes = `WITH grouped AS (SELECT ${column} AS group_value, ${title} AS title, ${status} AS status, MAX(task_entity.risk) AS risk, MAX(i.occurred_at) AS occurred_at, COUNT(*) AS invocation_count, SUM(i.duration_ms) AS total_duration, SUM(i.total_tokens) AS total_tokens, SUM(i.cost_usd) AS total_cost_usd, SUM(i.cost_brl) AS total_cost_brl FROM invocations i ${join} WHERE ${where} GROUP BY ${column})`;
|
|
5803
6602
|
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
5804
6603
|
const offset = Math.max(0, input.offset ?? 0);
|
|
5805
|
-
const total_count = Number(
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
6604
|
+
const total_count = Number(
|
|
6605
|
+
(
|
|
6606
|
+
this.database
|
|
6607
|
+
.prepare(`${ctes} SELECT COUNT(*) AS count FROM grouped`)
|
|
6608
|
+
.get(...values) as { count: number }
|
|
6609
|
+
).count,
|
|
6610
|
+
);
|
|
6611
|
+
const totals = this.database
|
|
6612
|
+
.prepare(
|
|
6613
|
+
`${ctes} SELECT COUNT(*) AS entity_count, COALESCE(SUM(invocation_count), 0) AS invocation_count, ROUND(SUM(total_duration)) AS total_duration, SUM(total_tokens) AS total_tokens, SUM(total_cost_usd) AS total_cost_usd, SUM(total_cost_brl) AS total_cost_brl FROM grouped`,
|
|
6614
|
+
)
|
|
6615
|
+
.get(...values) as Record<string, unknown>;
|
|
6616
|
+
const rows = this.database
|
|
6617
|
+
.prepare(
|
|
6618
|
+
`${ctes} SELECT group_value AS id, group_value AS key, title, status, risk, occurred_at, invocation_count, total_duration, total_tokens, total_cost_usd, total_cost_brl FROM grouped ORDER BY total_cost_brl DESC, group_value LIMIT ? OFFSET ?`,
|
|
6619
|
+
)
|
|
6620
|
+
.all(...values, limit, offset) as Array<Record<string, unknown>>;
|
|
6621
|
+
return {
|
|
6622
|
+
data_source: "sqlite_read_model" as const,
|
|
6623
|
+
view,
|
|
6624
|
+
offset,
|
|
6625
|
+
limit,
|
|
6626
|
+
total_count,
|
|
6627
|
+
has_more: offset + rows.length < total_count,
|
|
6628
|
+
totals,
|
|
6629
|
+
rows,
|
|
6630
|
+
};
|
|
5809
6631
|
}
|
|
5810
6632
|
const entityMetadata = {
|
|
5811
|
-
task: {
|
|
5812
|
-
|
|
6633
|
+
task: {
|
|
6634
|
+
table: "tasks",
|
|
6635
|
+
title: "entity.title",
|
|
6636
|
+
status: "entity.status",
|
|
6637
|
+
risk: "entity.risk",
|
|
6638
|
+
},
|
|
6639
|
+
spec: {
|
|
6640
|
+
table: "specs",
|
|
6641
|
+
title: "entity.title",
|
|
6642
|
+
status: "entity.status",
|
|
6643
|
+
risk: "entity.risk",
|
|
6644
|
+
},
|
|
5813
6645
|
prd: { table: "prds", title: "entity.title", status: "entity.status", risk: "entity.risk" },
|
|
5814
|
-
execution: {
|
|
5815
|
-
|
|
6646
|
+
execution: {
|
|
6647
|
+
table: "executions",
|
|
6648
|
+
title: "grouped.group_value",
|
|
6649
|
+
status: "entity.status",
|
|
6650
|
+
risk: "NULL",
|
|
6651
|
+
},
|
|
6652
|
+
conversation: {
|
|
6653
|
+
table: "conversations",
|
|
6654
|
+
title: "grouped.group_value",
|
|
6655
|
+
status: "entity.status",
|
|
6656
|
+
risk: "NULL",
|
|
6657
|
+
},
|
|
5816
6658
|
} as const;
|
|
5817
6659
|
const metadata = entityMetadata[groupKind as keyof typeof entityMetadata];
|
|
5818
6660
|
const clauses = ["group_kind = ?", "group_value IS NOT NULL"];
|
|
@@ -5918,8 +6760,10 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5918
6760
|
// Period rows use a coarse key (YYYY-MM/YYYY), so a partial daily range
|
|
5919
6761
|
// must intersect start/end days instead of comparing that key directly.
|
|
5920
6762
|
const overlaps =
|
|
5921
|
-
view === "cost_monitor_monthly" ||
|
|
5922
|
-
view === "
|
|
6763
|
+
view === "cost_monitor_monthly" ||
|
|
6764
|
+
view === "cost_monitor_annual" ||
|
|
6765
|
+
view === "operations_monthly" ||
|
|
6766
|
+
view === "operations_annual";
|
|
5923
6767
|
if (overlaps && input.from) {
|
|
5924
6768
|
clauses.push("end_day >= ?");
|
|
5925
6769
|
values.push(input.from.slice(0, 10));
|
|
@@ -6068,8 +6912,13 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
6068
6912
|
const partialFromDay = input.from?.slice(0, 10) ?? null;
|
|
6069
6913
|
const partialToDay = input.to?.slice(0, 10) ?? null;
|
|
6070
6914
|
const dailyRows = partialCoarse
|
|
6071
|
-
? this.database
|
|
6072
|
-
|
|
6915
|
+
? (this.database
|
|
6916
|
+
.prepare(
|
|
6917
|
+
`SELECT * FROM operations_daily WHERE (? IS NULL OR day >= ?) AND (? IS NULL OR day <= ?)`,
|
|
6918
|
+
)
|
|
6919
|
+
.all(partialFromDay, partialFromDay, partialToDay, partialToDay) as Array<
|
|
6920
|
+
Record<string, unknown>
|
|
6921
|
+
>)
|
|
6073
6922
|
: [];
|
|
6074
6923
|
const daily = this.executionDurationByDay({ from: input.from ?? null, to: input.to ?? null });
|
|
6075
6924
|
for (const row of rows) {
|
|
@@ -6079,8 +6928,23 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
6079
6928
|
const matchingDays = matching.map((day) => String(day.day)).sort();
|
|
6080
6929
|
row.start_day = matchingDays[0] ?? null;
|
|
6081
6930
|
row.end_day = matchingDays.at(-1) ?? null;
|
|
6082
|
-
for (const column of [
|
|
6083
|
-
|
|
6931
|
+
for (const column of [
|
|
6932
|
+
"prd_count",
|
|
6933
|
+
"spec_count",
|
|
6934
|
+
"task_count",
|
|
6935
|
+
"validation_count",
|
|
6936
|
+
"evidence_count",
|
|
6937
|
+
"total_run_count",
|
|
6938
|
+
"invocation_count",
|
|
6939
|
+
"llm_call_count",
|
|
6940
|
+
"total_tokens",
|
|
6941
|
+
"total_cost_usd",
|
|
6942
|
+
"total_cost_brl",
|
|
6943
|
+
] as const)
|
|
6944
|
+
row[column] = matching.reduce(
|
|
6945
|
+
(sum, day) => sum + (typeof day[column] === "number" ? Number(day[column]) : 0),
|
|
6946
|
+
0,
|
|
6947
|
+
);
|
|
6084
6948
|
}
|
|
6085
6949
|
row.operations_total_duration = [...daily]
|
|
6086
6950
|
.filter(([day]) => (view === "operations_daily" ? day === key : day.startsWith(key)))
|
|
@@ -6112,10 +6976,12 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
6112
6976
|
} {
|
|
6113
6977
|
if (this.metadataValues === null) {
|
|
6114
6978
|
this.metadataValues = new Map(
|
|
6115
|
-
(
|
|
6116
|
-
key
|
|
6117
|
-
|
|
6118
|
-
|
|
6979
|
+
(
|
|
6980
|
+
this.database.prepare("SELECT key, value FROM read_model_metadata").all() as Array<{
|
|
6981
|
+
key: string;
|
|
6982
|
+
value: string;
|
|
6983
|
+
}>
|
|
6984
|
+
).map((row) => [row.key, row.value]),
|
|
6119
6985
|
);
|
|
6120
6986
|
}
|
|
6121
6987
|
const value = (key: string) => Number(this.metadataValues?.get(key) ?? 0);
|