@zaip-ai/cli 1.0.8 → 1.3.1
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 +3 -3
- package/dist/services/agent-adapters.js.map +1 -1
- package/dist/services/agent-adapters.test.js +7 -1
- package/dist/services/agent-adapters.test.js.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.d.ts.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.js +89 -7
- package/dist/services/codex-pre-tool-hook-template.js.map +1 -1
- package/dist/services/codex-rules.d.ts +1 -1
- package/dist/services/codex-rules.d.ts.map +1 -1
- package/dist/services/codex-rules.js +7 -0
- package/dist/services/codex-rules.js.map +1 -1
- package/dist/services/install-agents-template.d.ts.map +1 -1
- package/dist/services/install-agents-template.js +1 -0
- package/dist/services/install-agents-template.js.map +1 -1
- package/dist/services/logger.test.js +2 -2
- package/dist/services/runtime-template.test.js +31 -7
- package/dist/services/runtime-template.test.js.map +1 -1
- package/dist/services/template-manifest.d.ts.map +1 -1
- package/dist/services/template-manifest.js +12 -3
- 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/agents/catalog.yaml +7 -7
- package/templates/.agents/config/clients/codex/agents/zaip-agent-backend.toml +2 -2
- package/templates/.agents/config/clients/codex/agents/zaip-agent-designer.toml +2 -2
- package/templates/.agents/config/clients/codex/agents/zaip-agent-finalizer.toml +1 -1
- package/templates/.agents/config/clients/codex/agents/zaip-agent-frontend.toml +8 -8
- 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 +457 -173
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-Z60df2r-.js → CostCalculationGuidePage-ovxurkIk.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/DashboardHelpDialog-BgVgrfhQ.js +2310 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-Coc6pA-1.js → PricingSettingsDialog-vSvvOHQp.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-zH758vfs.js +3 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-DLn-Mz2M.js +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-CaKwQP43.css → TraceabilityWorkspace-DvvavDLY.css} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-DKwP1stV.js +68 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-PoJA2PI1.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-DFBuc_qp.js → icons-vendor-B_lDDHn1.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-B31GMG5O.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-CZQlsLV9.js +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-CCQOHdVI.js → react-vendor-B3TJrD4O.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-pC2_0J8x.js → traceability-7f1j5wx3.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +7 -7
- package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
- package/templates/.agents/dashboard/apps/frontend/playwright.dom.config.ts +32 -0
- package/templates/.agents/dashboard/apps/frontend/src/App.tsx +199 -140
- package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +0 -21
- package/templates/.agents/dashboard/apps/frontend/src/DashboardAlternateContent.tsx +20 -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.dom.e2e.ts +56 -0
- package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.test.ts +21 -2
- package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.tsx +89 -19
- 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 +49 -3
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +29 -10
- package/templates/.agents/dashboard/apps/frontend/src/operations-period-render.test.ts +4 -4
- package/templates/.agents/dashboard/apps/frontend/src/overview-status-page.test.ts +4 -2
- 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 +47 -6
- package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +59 -9
- package/templates/.agents/dashboard/apps/frontend/src/sqlite-page-export.test.ts +32 -0
- package/templates/.agents/dashboard/apps/frontend/src/sqlite-page-export.ts +21 -0
- package/templates/.agents/dashboard/apps/frontend/src/styles.css +19 -0
- package/templates/.agents/dashboard/apps/frontend/src/traceability-task-pagination.test.ts +32 -0
- package/templates/.agents/dashboard/apps/frontend/src/traceability-task-pagination.ts +36 -0
- package/templates/.agents/dashboard/apps/frontend/src/traceability-workspace.css +6 -2
- package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.test.ts +137 -23
- package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +229 -35
- 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/use-app-trace-documents.ts +7 -1
- package/templates/.agents/dashboard/apps/frontend/src/use-app-work-list-page.test.ts +163 -0
- package/templates/.agents/dashboard/apps/frontend/src/use-app-work-list-page.ts +136 -0
- package/templates/.agents/dashboard/apps/frontend/src/use-operations-detail-export.ts +76 -0
- package/templates/.agents/dashboard/apps/frontend/src/work-list-load-dedup.test.ts +23 -0
- package/templates/.agents/dashboard/apps/frontend/src/work-list-load-dedup.ts +27 -0
- package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.test.ts +39 -15
- 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/CostDeveloperAttributionPanel.tsx +60 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostDeveloperAttributionTable.tsx +191 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.loading.test.ts +14 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.run-page.test.ts +18 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.tsx +18 -3
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostModelsAuditTable.tsx +1 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +75 -44
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorLoadErrors.tsx +35 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +162 -40
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +221 -79
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTableLoading.test.ts +132 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTableLoading.tsx +70 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +203 -155
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +164 -125
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-copy.ts +2 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-views.ts +3 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor.css +5 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-page-range.ts +35 -0
- 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 +32 -9
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.test.ts +37 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.ts +131 -9
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +34 -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 +46 -26
- package/templates/.agents/dashboard/modules/operations/src/react/DeveloperAttributionPanel.test.ts +54 -0
- package/templates/.agents/dashboard/modules/operations/src/react/DeveloperAttributionPanel.tsx +247 -0
- package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.tsx +31 -11
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsMainContent.tsx +39 -13
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.helpers.tsx +107 -2
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.macro-metrics.test.ts +55 -0
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.test.ts +23 -29
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +211 -184
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverviewTopPanel.tsx +127 -0
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +60 -51
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageNavigation.tsx +1 -4
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageOverview.tsx +3 -0
- package/templates/.agents/dashboard/modules/operations/src/react/PagedWorkList.test.tsx +43 -0
- package/templates/.agents/dashboard/modules/operations/src/react/PagedWorkList.tsx +181 -0
- 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 +16 -6
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.tsx +14 -4
- package/templates/.agents/dashboard/modules/operations/src/react/conversation-rows.ts +1 -2
- package/templates/.agents/dashboard/modules/operations/src/react/developer-identities.test.ts +18 -0
- package/templates/.agents/dashboard/modules/operations/src/react/developer-identities.ts +30 -0
- package/templates/.agents/dashboard/modules/operations/src/react/execution-metrics.ts +1 -0
- package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +393 -4
- package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +484 -61
- package/templates/.agents/dashboard/modules/operations/src/react/export/export-layout.test.ts +23 -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/index.ts +1 -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-coverage-toast.test.ts +4 -3
- package/templates/.agents/dashboard/modules/operations/src/react/operations-date-filters.css +1 -1
- 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 +22 -12
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview.css +4 -0
- 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 +53 -6
- package/templates/.agents/dashboard/modules/operations/src/react/operations-view-order.test.ts +2 -4
- package/templates/.agents/dashboard/modules/operations/src/react/period-summary.test.ts +82 -6
- package/templates/.agents/dashboard/modules/operations/src/react/period-summary.ts +1 -0
- 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/metric-coverage.ts +1 -0
- 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 +533 -338
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +506 -311
- package/templates/.agents/dashboard/packages/local-runtime/gitignore.template +2 -0
- 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 +8 -3
- 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 +141 -33
- package/templates/.agents/dashboard/packages/local-runtime/server.mjs +533 -338
- 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 +89 -16
- package/templates/.agents/dashboard/packages/local-runtime/src/server-board.ts +33 -16
- package/templates/.agents/dashboard/packages/local-runtime/src/server-classification-page.test.ts +87 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-period-macro-metric.test.ts +44 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-period-macro-metric.ts +125 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-refresh.test.ts +66 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-request-authority.test.ts +61 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-request-authority.ts +18 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-run-projections.test.ts +42 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-refresh.ts +97 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot.test.ts +22 -5
- package/templates/.agents/dashboard/packages/local-runtime/src/server-sqlite-task-trace.ts +91 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +7 -3
- package/templates/.agents/dashboard/packages/local-runtime/src/server.test-fixtures.ts +36 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +230 -132
- package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +224 -182
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-artifact-counts.ts +28 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.test.ts +63 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.ts +109 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-aggregates.ts +497 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-fast-page-parity.test-support.ts +65 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-page.test.ts +176 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-page.ts +119 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-projection.ts +41 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-page.test.ts +16 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-page.ts +29 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-first-fold-cache.ts +355 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-first-fold-persistence.ts +121 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-first-fold-timing.ts +27 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-metric-contract.ts +16 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-operations-projection.ts +15 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-operations-work-list.test.ts +223 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-operations-work-list.ts +304 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-cache.test-support.ts +23 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-first-fold-types.ts +89 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider-fixture.ts +227 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider.test.ts +691 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.ts +81 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +1655 -567
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +2168 -1268
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-cache.ts +129 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-install.test.ts +26 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-install.ts +28 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-seed.test.ts +116 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.mjs +146 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.test.mjs +165 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-refresh.mjs +140 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +255 -289
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +94 -88
- package/templates/.agents/dashboard/packages/ui/package.json +1 -1
- package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +5 -2
- package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
- package/templates/.agents/dashboard/tests/e2e/playwright.config.ts +4 -1
- package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-loading.spec.ts +142 -0
- package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-pagination.spec.ts +232 -0
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard-performance-matrix.spec.ts +743 -0
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +189 -214
- package/templates/.agents/dashboard/zaip-dashboard-windows.exe +0 -0
- 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 +6 -6
- 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 +3 -3
- package/templates/.agents/requirements/kanban-dashboard.md +10 -2
- 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 +4 -4
- package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
- package/templates/.agents/rules/dashboard-ui-consistency.md +14 -0
- package/templates/.agents/rules/frontend-lite.md +4 -4
- package/templates/.agents/rules/run-lifecycle-closure.md +4 -4
- package/templates/.agents/runtime/VERSION +1 -1
- package/templates/.agents/runtime/bin/zaip.mjs +1142 -1041
- 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/criar-pasta-obsidian/scripts/generate-obsidian-vault.test.mjs +20 -16
- package/templates/.agents/skills/zaip-aios/SKILL.md +1 -1
- package/templates/.agents/skills/zaip-skill-agent-catalog/SKILL.md +22 -0
- package/templates/.agents/skills/zaip-skill-agent-catalog/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-author-skills/SKILL.md +3 -2
- package/templates/.agents/skills/zaip-skill-author-skills/scripts/audit-skill-catalog.mjs +49 -0
- package/templates/.agents/skills/zaip-skill-author-skills/scripts/author-skill-scripts.test.mjs +27 -0
- package/templates/.agents/skills/zaip-skill-author-skills/scripts/run-skill-tests.mjs +37 -0
- package/templates/.agents/skills/zaip-skill-backend/SKILL.md +1 -0
- 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-change/SKILL.md +1 -1
- package/templates/.agents/skills/zaip-skill-change/references/observable-evidence.md +83 -0
- package/templates/.agents/skills/zaip-skill-dashboard-diagnostics/SKILL.md +22 -0
- package/templates/.agents/skills/zaip-skill-dashboard-diagnostics/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-enforce-architecture/SKILL.md +2 -0
- package/templates/.agents/skills/zaip-skill-execution/SKILL.md +1 -1
- package/templates/.agents/skills/zaip-skill-frontend/SKILL.md +4 -3
- package/templates/.agents/skills/zaip-skill-frontend/agents/openai.yaml +2 -2
- package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +10 -10
- package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +6 -0
- package/templates/.agents/skills/zaip-skill-lifecycle-closure/SKILL.md +27 -0
- package/templates/.agents/skills/zaip-skill-lifecycle-closure/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +6 -0
- package/templates/.agents/skills/zaip-skill-prd/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.mjs +111 -104
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-explicit-recovery.mjs +223 -0
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.mjs +55 -9
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.test.mjs +145 -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 +82 -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-release-qualification/SKILL.md +20 -0
- package/templates/.agents/skills/zaip-skill-release-qualification/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-review/SKILL.md +5 -0
- package/templates/.agents/skills/zaip-skill-rules/SKILL.md +1 -1
- package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +6 -0
- package/templates/.agents/skills/zaip-skill-spec/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-sqlite-read-model/SKILL.md +22 -90
- package/templates/.agents/skills/zaip-skill-sqlite-read-model/scripts/ensure-node.mjs +28 -5
- package/templates/.agents/zaip-aios/INDEX.md +57 -22
- package/templates/.agents/zaip-aios/README.md +24 -13
- package/templates/.agents/zaip-aios/VERSION +1 -1
- package/templates/.agents/zaip-aios/guides/02-requirements.md +5 -0
- package/templates/.agents/zaip-aios/guides/06-execucao.md +13 -4
- package/templates/.agents/zaip-aios/guides/07-revisao-e-validacao.md +7 -0
- package/templates/.agents/zaip-aios/guides/10-seguranca.md +23 -0
- package/templates/.agents/zaip-aios/guides/11-observabilidade-e-custos.md +6 -6
- package/templates/.agents/zaip-aios/guides/13-boas-praticas.md +3 -0
- package/templates/.agents/zaip-aios/guides/16-lifecycle-e-reconciliacao.md +12 -3
- package/templates/.agents/zaip-aios/guides/17-como-o-custo-e-calculado.md +5 -5
- package/templates/.agents/zaip-aios/guides/18-catalogo-de-agentes.md +76 -0
- package/templates/.agents/zaip-aios/guides/19-assurance-e-autonomia.md +95 -0
- package/templates/.agents/zaip-aios/guides/20-instalacao-detalhada.md +366 -0
- package/templates/.agents/zaip-aios/guides/21-telemetria-monitor-custos.md +46 -0
- package/templates/.agents/zaip-aios/guides/22-fluxos-e-roteamento.md +74 -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 +19 -0
- package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +111 -0
- package/templates/.agents/zaip-aios/guides/27-telemetria-git-native.md +172 -0
- package/templates/.agents/zaip-aios/guides/28-lifecycle-completo.md +149 -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 +756 -235
- 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-BZ5WdaMN.css +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
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DK_enB5Z.css +0 -1
- package/templates/.agents/dashboard/apps/frontend/src/use-idle-cost-prefetch.ts +0 -45
|
@@ -11,26 +11,78 @@ import {
|
|
|
11
11
|
type PersistedTokenUsage,
|
|
12
12
|
type PricingCatalog,
|
|
13
13
|
pricingCatalogSchema,
|
|
14
|
+
resolveHistoricalDeveloperAttribution,
|
|
14
15
|
} from "@zaip-ai/costs";
|
|
16
|
+
import type { CostSummarySource } from "@zaip-ai/dashboard-cost-monitor/server";
|
|
17
|
+
import { allocateSharedCreationValue } from "@zaip-ai/lifecycle-manifests";
|
|
15
18
|
import {
|
|
16
19
|
foldRunV2Records,
|
|
17
20
|
parseRunV2JsonLines,
|
|
18
21
|
type RunV2Fold,
|
|
19
22
|
type UsageRecordedV2,
|
|
20
23
|
} from "../../../../../packages/decisions/src/index.js";
|
|
24
|
+
import { artifactStructuralRowsSql, developerArtifactCountsCte } from "./sqlite-artifact-counts.js";
|
|
25
|
+
import {
|
|
26
|
+
costDimensionFactDateFilter,
|
|
27
|
+
isDateOnlyCostPage,
|
|
28
|
+
readDateOnlyCostDimensionPage,
|
|
29
|
+
} from "./sqlite-cost-agent-page.js";
|
|
30
|
+
import { buildCostDimensionAggregates } from "./sqlite-cost-aggregates.js";
|
|
31
|
+
import { type CostGroupKind, readCostGroupPage } from "./sqlite-cost-group-page.js";
|
|
32
|
+
import { materializeCostGroupDailyProjection } from "./sqlite-cost-group-projection.js";
|
|
33
|
+
import { filterCostInvocationProjection } from "./sqlite-cost-invocation-page.js";
|
|
34
|
+
import {
|
|
35
|
+
readFirstFoldAllTime,
|
|
36
|
+
readFirstFoldBoardAllTime,
|
|
37
|
+
readFirstFoldMonthlyQuick,
|
|
38
|
+
readFirstFoldTopTasksAllTime,
|
|
39
|
+
readFirstFoldViewport,
|
|
40
|
+
} from "./sqlite-first-fold-cache.js";
|
|
41
|
+
import {
|
|
42
|
+
hasRequiredFirstFoldPayloads,
|
|
43
|
+
persistFirstFoldPayloads,
|
|
44
|
+
} from "./sqlite-first-fold-persistence.js";
|
|
45
|
+
import { measureFirstFold } from "./sqlite-first-fold-timing.js";
|
|
46
|
+
import { materializeOperationsWorkProjection } from "./sqlite-operations-projection.js";
|
|
47
|
+
import {
|
|
48
|
+
attachOperationsWorkListDevelopers,
|
|
49
|
+
operationsWorkListPayloadPage,
|
|
50
|
+
operationsWorkListSqlPage,
|
|
51
|
+
operationsWorkListSqlRows,
|
|
52
|
+
operationsWorkRowsInRange,
|
|
53
|
+
} from "./sqlite-operations-work-list.js";
|
|
54
|
+
import {
|
|
55
|
+
installLegacyViewAliases,
|
|
56
|
+
materializeSnapshot,
|
|
57
|
+
mostRecentCompatibleSnapshot,
|
|
58
|
+
pruneSnapshotCache,
|
|
59
|
+
readSnapshotFileCount,
|
|
60
|
+
} from "./sqlite-snapshot-cache.js";
|
|
61
|
+
|
|
62
|
+
export {
|
|
63
|
+
type SqliteReadModelSourceCandidate,
|
|
64
|
+
sourceFingerprint,
|
|
65
|
+
} from "./sqlite-read-model-source-fingerprint.js";
|
|
66
|
+
|
|
67
|
+
import { sourceFingerprint } from "./sqlite-read-model-source-fingerprint.js";
|
|
21
68
|
|
|
22
69
|
const dbRelativePath = ".local/dashboard-sqlite/read-model.sqlite";
|
|
23
|
-
const textExtensions = new Set([".json", ".jsonl", ".md", ".yaml", ".yml", ".txt"]);
|
|
24
70
|
// Schema 58 aligns Operations invocation/day facts with the same factual
|
|
25
71
|
// source day used by tokens, Cost Monitor and attributed Tempo. Seed reuse is
|
|
26
72
|
// safe only when both source and this schema version match.
|
|
27
|
-
|
|
73
|
+
// Allocation rows are materialized after attributed-time reconciliation; a
|
|
74
|
+
// prior seed contains the pre-reconciliation payload and is incompatible.
|
|
75
|
+
const readModelSchemaVersion = "70";
|
|
28
76
|
const requiredPersistedTables = [
|
|
29
77
|
"zaip_aios_activities",
|
|
30
78
|
"zaip_dim_llm_model",
|
|
31
79
|
"zaip_dim_provider",
|
|
32
80
|
"zaip_dim_zaip_agent",
|
|
33
81
|
"cost_invocation_dimension_facts",
|
|
82
|
+
"cost_group_daily_projection",
|
|
83
|
+
"cost_group_entity_projection",
|
|
84
|
+
"developer_cost_projection",
|
|
85
|
+
"operations_work_projection",
|
|
34
86
|
] as const;
|
|
35
87
|
const requiredIndexes = [
|
|
36
88
|
"zaip_activities_agent_run",
|
|
@@ -48,6 +100,9 @@ const requiredIndexes = [
|
|
|
48
100
|
"cost_dimension_facts_day_model",
|
|
49
101
|
"cost_dimension_facts_day_provider",
|
|
50
102
|
"cost_dimension_facts_invocation",
|
|
103
|
+
"cost_group_daily_projection_lookup",
|
|
104
|
+
"cost_group_entity_projection_lookup",
|
|
105
|
+
"developer_cost_projection_lookup",
|
|
51
106
|
] as const;
|
|
52
107
|
const requiredViews = [
|
|
53
108
|
"zaip_vw_read_model_metadata",
|
|
@@ -59,26 +114,6 @@ const requiredViews = [
|
|
|
59
114
|
] as const;
|
|
60
115
|
const MAX_RECONCILED_RUN_INTERVAL_MS = 2 * 3_600_000;
|
|
61
116
|
|
|
62
|
-
type CostDimensionAggregateRow = {
|
|
63
|
-
key: string;
|
|
64
|
-
label: string;
|
|
65
|
-
invocation_count: number;
|
|
66
|
-
task_count: number;
|
|
67
|
-
total_duration: number | null;
|
|
68
|
-
source_event_count: number | null;
|
|
69
|
-
total_tokens: number | null;
|
|
70
|
-
total_cost_usd: number | null;
|
|
71
|
-
total_cost_brl: number | null;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
function observedDimensionValue(value: unknown): string | null {
|
|
75
|
-
if (typeof value !== "string") return null;
|
|
76
|
-
const normalized = value.trim();
|
|
77
|
-
return normalized.length > 0 && !["unavailable", "unknown", "mixed"].includes(normalized.toLowerCase())
|
|
78
|
-
? normalized
|
|
79
|
-
: null;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
117
|
function saoPauloCurrentMonth(): { from: string; to: string } {
|
|
83
118
|
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
84
119
|
timeZone: "America/Sao_Paulo",
|
|
@@ -91,161 +126,7 @@ function saoPauloCurrentMonth(): { from: string; to: string } {
|
|
|
91
126
|
return { from: `${value.year}-${value.month}-01`, to: day };
|
|
92
127
|
}
|
|
93
128
|
|
|
94
|
-
/** Small, seed-time-only aggregates for the unfiltered Models/Providers tables. */
|
|
95
|
-
function buildCostDimensionAggregates(
|
|
96
|
-
database: DatabaseSync,
|
|
97
|
-
): Record<"model" | "provider", CostDimensionAggregateRow[]> {
|
|
98
|
-
const dimensions = ["model", "provider"] as const;
|
|
99
|
-
const allowedProviders = new Set(
|
|
100
|
-
(database.prepare("SELECT provider FROM zaip_dim_provider").all() as Array<{ provider: string }>)
|
|
101
|
-
.map((row) => row.provider),
|
|
102
|
-
);
|
|
103
|
-
const activities = database.prepare(`SELECT activity_row_id, agent_invocation_id, task_id, duration_ms,
|
|
104
|
-
source_event_count, provider, model, total_tokens, cost_usd, cost_brl
|
|
105
|
-
FROM zaip_aios_activities WHERE payload_type IS NULL`).all() as Array<Record<string, unknown>>;
|
|
106
|
-
const segments = database.prepare(`SELECT agent_invocation_id, provider, model, total_tokens, cost_usd, cost_brl
|
|
107
|
-
FROM invocation_usage_segments`).all() as Array<Record<string, unknown>>;
|
|
108
|
-
const activityByInvocation = new Map(
|
|
109
|
-
activities
|
|
110
|
-
.filter((row) => typeof row.agent_invocation_id === "string")
|
|
111
|
-
.map((row) => [String(row.agent_invocation_id), row] as const),
|
|
112
|
-
);
|
|
113
|
-
const rowsByDimension = new Map<"model" | "provider", Map<string, CostDimensionAggregateRow>>(
|
|
114
|
-
dimensions.map((dimension) => [dimension, new Map()]),
|
|
115
|
-
);
|
|
116
|
-
const ensure = (dimension: "model" | "provider", key: string) => {
|
|
117
|
-
const rows = rowsByDimension.get(dimension)!;
|
|
118
|
-
const current = rows.get(key);
|
|
119
|
-
if (current !== undefined) return current;
|
|
120
|
-
const created: CostDimensionAggregateRow = {
|
|
121
|
-
key, label: key, invocation_count: 0, task_count: 0, total_duration: null,
|
|
122
|
-
source_event_count: null, total_tokens: null, total_cost_usd: null, total_cost_brl: null,
|
|
123
|
-
};
|
|
124
|
-
rows.set(key, created);
|
|
125
|
-
return created;
|
|
126
|
-
};
|
|
127
|
-
const add = (target: CostDimensionAggregateRow, field: "total_tokens" | "total_cost_usd" | "total_cost_brl", value: unknown) => {
|
|
128
|
-
if (typeof value === "number") target[field] = (target[field] ?? 0) + value;
|
|
129
|
-
};
|
|
130
|
-
const segmentsByInvocation = new Map<string, Array<Record<string, unknown>>>();
|
|
131
|
-
for (const segment of segments) {
|
|
132
|
-
const id = String(segment.agent_invocation_id ?? "");
|
|
133
|
-
if (id.length === 0) continue;
|
|
134
|
-
const invocationSegments = segmentsByInvocation.get(id);
|
|
135
|
-
if (invocationSegments === undefined) segmentsByInvocation.set(id, [segment]);
|
|
136
|
-
else invocationSegments.push(segment);
|
|
137
|
-
}
|
|
138
|
-
for (const [id, invocationSegments] of segmentsByInvocation) {
|
|
139
|
-
const activity = activityByInvocation.get(id);
|
|
140
|
-
if (activity === undefined) continue;
|
|
141
|
-
for (const dimension of dimensions) {
|
|
142
|
-
const financialSegments = invocationSegments.filter(
|
|
143
|
-
(segment) =>
|
|
144
|
-
observedDimensionValue(segment[dimension]) !== null &&
|
|
145
|
-
observedDimensionValue(segment.provider) !== null &&
|
|
146
|
-
allowedProviders.has(observedDimensionValue(segment.provider)!) &&
|
|
147
|
-
(typeof segment.total_tokens === "number" ||
|
|
148
|
-
typeof segment.cost_usd === "number" ||
|
|
149
|
-
typeof segment.cost_brl === "number"),
|
|
150
|
-
);
|
|
151
|
-
const keys = [...new Set(financialSegments.map((row) => observedDimensionValue(row[dimension])!))];
|
|
152
|
-
if (keys.length === 0) {
|
|
153
|
-
// Keep auditable, dimensionless invocation facts visible so Models
|
|
154
|
-
// and Providers reconcile to the Cost KPI instead of silently
|
|
155
|
-
// dropping them.
|
|
156
|
-
const target = ensure(dimension, "unavailable");
|
|
157
|
-
target.invocation_count += 1;
|
|
158
|
-
if (typeof activity.duration_ms === "number") target.total_duration = (target.total_duration ?? 0) + activity.duration_ms;
|
|
159
|
-
if (typeof activity.source_event_count === "number") target.source_event_count = (target.source_event_count ?? 0) + activity.source_event_count;
|
|
160
|
-
add(target, "total_tokens", activity.total_tokens);
|
|
161
|
-
add(target, "total_cost_usd", activity.cost_usd);
|
|
162
|
-
add(target, "total_cost_brl", activity.cost_brl);
|
|
163
|
-
}
|
|
164
|
-
for (const segment of financialSegments) {
|
|
165
|
-
const target = ensure(dimension, observedDimensionValue(segment[dimension])!);
|
|
166
|
-
add(target, "total_tokens", segment.total_tokens);
|
|
167
|
-
add(target, "total_cost_usd", segment.cost_usd);
|
|
168
|
-
add(target, "total_cost_brl", segment.cost_brl);
|
|
169
|
-
}
|
|
170
|
-
// An invocation's elapsed time is attributable only when its observed
|
|
171
|
-
// financial segments agree on one dimension. Do not fabricate a
|
|
172
|
-
// mixed bucket or split wall-clock time across models/providers.
|
|
173
|
-
if (keys.length === 1) {
|
|
174
|
-
const target = ensure(dimension, keys[0]!);
|
|
175
|
-
target.invocation_count += 1;
|
|
176
|
-
if (typeof activity.duration_ms === "number") target.total_duration = (target.total_duration ?? 0) + activity.duration_ms;
|
|
177
|
-
if (typeof activity.source_event_count === "number") target.source_event_count = (target.source_event_count ?? 0) + activity.source_event_count;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
const taskKeys = new Map<"model" | "provider", Map<string, Set<string>>>(
|
|
182
|
-
dimensions.map((dimension) => [dimension, new Map()]),
|
|
183
|
-
);
|
|
184
|
-
for (const [id, invocationSegments] of segmentsByInvocation) {
|
|
185
|
-
const taskId = activityByInvocation.get(id)?.task_id;
|
|
186
|
-
if (typeof taskId !== "string") continue;
|
|
187
|
-
for (const dimension of dimensions) {
|
|
188
|
-
const byTask = taskKeys.get(dimension)!;
|
|
189
|
-
const keys = byTask.get(taskId) ?? new Set<string>();
|
|
190
|
-
for (const segment of invocationSegments) {
|
|
191
|
-
const key = observedDimensionValue(segment[dimension]);
|
|
192
|
-
const provider = observedDimensionValue(segment.provider);
|
|
193
|
-
const hasFinancialFact =
|
|
194
|
-
typeof segment.total_tokens === "number" ||
|
|
195
|
-
typeof segment.cost_usd === "number" ||
|
|
196
|
-
typeof segment.cost_brl === "number";
|
|
197
|
-
if (key !== null && provider !== null && allowedProviders.has(provider) && hasFinancialFact)
|
|
198
|
-
keys.add(key);
|
|
199
|
-
}
|
|
200
|
-
byTask.set(taskId, keys);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
for (const dimension of dimensions)
|
|
204
|
-
for (const keys of taskKeys.get(dimension)!.values())
|
|
205
|
-
for (const key of keys) ensure(dimension, key).task_count += 1;
|
|
206
|
-
for (const activity of activities) {
|
|
207
|
-
if (typeof activity.agent_invocation_id === "string") continue;
|
|
208
|
-
const hasFinancialFact =
|
|
209
|
-
typeof activity.total_tokens === "number" ||
|
|
210
|
-
typeof activity.cost_usd === "number" ||
|
|
211
|
-
typeof activity.cost_brl === "number";
|
|
212
|
-
if (!hasFinancialFact) continue;
|
|
213
|
-
const provider = observedDimensionValue(activity.provider);
|
|
214
|
-
if (provider === null || !allowedProviders.has(provider)) continue;
|
|
215
|
-
for (const dimension of dimensions) {
|
|
216
|
-
const key = observedDimensionValue(activity[dimension]) ?? "unavailable";
|
|
217
|
-
const target = ensure(dimension, key);
|
|
218
|
-
target.invocation_count += 1;
|
|
219
|
-
if (typeof activity.task_id === "string") target.task_count += 1;
|
|
220
|
-
if (typeof activity.duration_ms === "number") target.total_duration = (target.total_duration ?? 0) + activity.duration_ms;
|
|
221
|
-
if (typeof activity.source_event_count === "number") target.source_event_count = (target.source_event_count ?? 0) + activity.source_event_count;
|
|
222
|
-
add(target, "total_tokens", activity.total_tokens);
|
|
223
|
-
add(target, "total_cost_usd", activity.cost_usd);
|
|
224
|
-
add(target, "total_cost_brl", activity.cost_brl);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return Object.fromEntries(dimensions.map((dimension) => [dimension, [...rowsByDimension.get(dimension)!.values()].sort(
|
|
228
|
-
(left, right) => (right.total_cost_brl ?? -1) - (left.total_cost_brl ?? -1) || left.key.localeCompare(right.key),
|
|
229
|
-
)])) as Record<"model" | "provider", CostDimensionAggregateRow[]>;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
129
|
/** Only canonical inputs consumed by the fact or its direct dimensions. */
|
|
233
|
-
function isDashboardCanonicalSource(source: string): boolean {
|
|
234
|
-
if (
|
|
235
|
-
source === ".agents/config/project.yaml" ||
|
|
236
|
-
source === ".agents/config/framework.lock.yaml" ||
|
|
237
|
-
source === ".agents/config/agents/catalog.yaml" ||
|
|
238
|
-
source === ".agents/config/clients.yaml"
|
|
239
|
-
)
|
|
240
|
-
return true;
|
|
241
|
-
if (source.startsWith(".agents/config/pricing/")) return true;
|
|
242
|
-
if (source.startsWith(".agents/records/runs/") && source.endsWith(".jsonl")) return true;
|
|
243
|
-
if (/^\.agents\/records\/(tasks|specs|prds)\/.*\.md$/.test(source)) return true;
|
|
244
|
-
if (/^\.agents\/records\/executions\/.*\/execution\.md$/.test(source)) return true;
|
|
245
|
-
return /^\.agents\/records\/changes\/.*\/(change\.md|decisions\/.*\.md|artifacts\/(evidence|validations)\/.*\.md)$/.test(
|
|
246
|
-
source,
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
130
|
|
|
250
131
|
type AgentProfile = {
|
|
251
132
|
catalogId: string;
|
|
@@ -260,15 +141,21 @@ type CanonicalEntity = {
|
|
|
260
141
|
status: string | null;
|
|
261
142
|
stage: string | null;
|
|
262
143
|
risk: string | null;
|
|
144
|
+
specId: string | null;
|
|
145
|
+
changeId: string | null;
|
|
263
146
|
occurredAt: string | null;
|
|
147
|
+
creationRunId: string | null;
|
|
148
|
+
completedRunId: string | null;
|
|
264
149
|
};
|
|
265
150
|
type CanonicalArtifact = {
|
|
266
151
|
kind: "evidence" | "validation";
|
|
267
152
|
id: string;
|
|
268
153
|
occurredAt: string;
|
|
269
154
|
occurredDaySp: string;
|
|
155
|
+
taskId: string | null;
|
|
156
|
+
changeId: string | null;
|
|
157
|
+
runId: string | null;
|
|
270
158
|
};
|
|
271
|
-
|
|
272
159
|
type CanonicalExecution = {
|
|
273
160
|
id: string;
|
|
274
161
|
status: string | null;
|
|
@@ -279,7 +166,6 @@ type CanonicalExecution = {
|
|
|
279
166
|
finishedAt: string | null;
|
|
280
167
|
sourcePath: string;
|
|
281
168
|
};
|
|
282
|
-
|
|
283
169
|
type CanonicalChange = {
|
|
284
170
|
id: string;
|
|
285
171
|
title: string;
|
|
@@ -296,7 +182,6 @@ type CanonicalChange = {
|
|
|
296
182
|
createdAt: string;
|
|
297
183
|
sourcePath: string;
|
|
298
184
|
};
|
|
299
|
-
|
|
300
185
|
type CanonicalDecision = {
|
|
301
186
|
id: string;
|
|
302
187
|
title: string;
|
|
@@ -306,7 +191,6 @@ type CanonicalDecision = {
|
|
|
306
191
|
createdAt: string;
|
|
307
192
|
sourcePath: string;
|
|
308
193
|
};
|
|
309
|
-
|
|
310
194
|
function frontMatterRecord(content: string): Record<string, unknown> | null {
|
|
311
195
|
const source = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content)?.[1];
|
|
312
196
|
if (source === undefined) return null;
|
|
@@ -336,9 +220,7 @@ function stringValues(value: unknown): string[] {
|
|
|
336
220
|
: [];
|
|
337
221
|
}
|
|
338
222
|
|
|
339
|
-
function readCanonicalLifecycleRecords(
|
|
340
|
-
rows: Array<{ path: string; content: string }>,
|
|
341
|
-
): {
|
|
223
|
+
function readCanonicalLifecycleRecords(rows: Array<{ path: string; content: string }>): {
|
|
342
224
|
executions: CanonicalExecution[];
|
|
343
225
|
changes: CanonicalChange[];
|
|
344
226
|
decisions: CanonicalDecision[];
|
|
@@ -462,7 +344,11 @@ function readCanonicalEntities(
|
|
|
462
344
|
status: scalar("status"),
|
|
463
345
|
stage: scalar("stage"),
|
|
464
346
|
risk: scalar("risk"),
|
|
347
|
+
specId: scalar("spec_id"),
|
|
348
|
+
changeId: scalar("change_id"),
|
|
465
349
|
occurredAt: scalar("created_at") ?? scalar("started_at") ?? scalar("recorded_at"),
|
|
350
|
+
creationRunId: scalar("creation_run_id"),
|
|
351
|
+
completedRunId: scalar("completed_run_id"),
|
|
466
352
|
});
|
|
467
353
|
}
|
|
468
354
|
return entities;
|
|
@@ -490,7 +376,9 @@ function readAgentProfiles(
|
|
|
490
376
|
// host records used `zaip-` forms of deprecated IDs. All of these are
|
|
491
377
|
// explicit identities from the catalog; no UI fallback is involved.
|
|
492
378
|
const identities = new Set([agent, catalogId]);
|
|
493
|
-
for (const alias of block.matchAll(
|
|
379
|
+
for (const alias of block.matchAll(
|
|
380
|
+
/^ {8}- kind:\s*(?:id|codex_name|codex_task_name)\s*\r?\n {10}value:\s*([^\s#]+)/gm,
|
|
381
|
+
)) {
|
|
494
382
|
const value = alias[1];
|
|
495
383
|
if (value !== undefined) identities.add(value);
|
|
496
384
|
}
|
|
@@ -586,7 +474,9 @@ type ResolvedSourceInterval = { start: string; end: string; duration: number };
|
|
|
586
474
|
* happened. It wins over Run finalization elapsed time, which may include
|
|
587
475
|
* orchestration/capture lag or be recorded much later.
|
|
588
476
|
*/
|
|
589
|
-
function resolvedSourceInterval(
|
|
477
|
+
function resolvedSourceInterval(
|
|
478
|
+
records: readonly UsageRecordedV2[],
|
|
479
|
+
): ResolvedSourceInterval | null {
|
|
590
480
|
const intervals = records.flatMap((record) => {
|
|
591
481
|
const start = record.source_started_at;
|
|
592
482
|
const end = record.source_finished_at;
|
|
@@ -677,7 +567,13 @@ function resolveDurations(
|
|
|
677
567
|
}
|
|
678
568
|
// Tokens and lifecycle timestamps are not a time source. Absence stays
|
|
679
569
|
// absent: consumers must display unavailable rather than manufacture time.
|
|
680
|
-
return {
|
|
570
|
+
return {
|
|
571
|
+
source: null,
|
|
572
|
+
effective: null,
|
|
573
|
+
provenance: null,
|
|
574
|
+
sourceStartedAt: null,
|
|
575
|
+
sourceFinishedAt: null,
|
|
576
|
+
};
|
|
681
577
|
}
|
|
682
578
|
|
|
683
579
|
function latestFxRate(rows: Array<{ path: string; content: string }>): FxRate | null {
|
|
@@ -857,7 +753,9 @@ function usagePricingAudit(
|
|
|
857
753
|
const effectiveBrl =
|
|
858
754
|
record.cost.brl ??
|
|
859
755
|
actual?.estimated_brl ??
|
|
860
|
-
(effectiveUsd !== null && fxRate !== null
|
|
756
|
+
(effectiveUsd !== null && fxRate !== null
|
|
757
|
+
? Math.round(effectiveUsd * fxRate.rate * 1_000_000) / 1_000_000
|
|
758
|
+
: null);
|
|
861
759
|
return {
|
|
862
760
|
provider,
|
|
863
761
|
model,
|
|
@@ -946,8 +844,8 @@ export function allocateAttributedTokenActiveMs(intervals: readonly TemporalInte
|
|
|
946
844
|
boundaries.add(interval.startMs);
|
|
947
845
|
boundaries.add(interval.endMs);
|
|
948
846
|
for (let cursor = interval.startMs; cursor < interval.endMs; ) {
|
|
949
|
-
const nextDay =
|
|
950
|
-
86_400_000;
|
|
847
|
+
const nextDay =
|
|
848
|
+
Date.parse(`${saoPauloDay(new Date(cursor).toISOString())}T03:00:00.000Z`) + 86_400_000;
|
|
951
849
|
if (nextDay >= interval.endMs) break;
|
|
952
850
|
boundaries.add(nextDay);
|
|
953
851
|
cursor = nextDay;
|
|
@@ -958,7 +856,9 @@ export function allocateAttributedTokenActiveMs(intervals: readonly TemporalInte
|
|
|
958
856
|
for (let index = 0; index < points.length - 1; index += 1) {
|
|
959
857
|
const startMs = points[index]!;
|
|
960
858
|
const endMs = points[index + 1]!;
|
|
961
|
-
const active = intervals.filter(
|
|
859
|
+
const active = intervals.filter(
|
|
860
|
+
(interval) => interval.startMs < endMs && interval.endMs > startMs,
|
|
861
|
+
);
|
|
962
862
|
if (active.length === 0 || endMs <= startMs) continue;
|
|
963
863
|
const share = (endMs - startMs) / active.length;
|
|
964
864
|
const day = saoPauloDay(new Date(startMs).toISOString());
|
|
@@ -982,34 +882,21 @@ function costMonitorFilterDay(value: string | null | undefined): string | null {
|
|
|
982
882
|
return /^\d{4}-\d{2}-\d{2}$/.test(value) ? value : saoPauloDay(value);
|
|
983
883
|
}
|
|
984
884
|
|
|
985
|
-
function
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
.formatToParts(new Date(utcGuess))
|
|
994
|
-
.find((part) => part.type === "timeZoneName")?.value ?? "GMT";
|
|
995
|
-
const match = /^GMT([+-])(\d{2}):(\d{2})$/.exec(offset);
|
|
996
|
-
if (match === null) return utcGuess;
|
|
997
|
-
const offsetMs =
|
|
998
|
-
(Number(match[2]) * 60 + Number(match[3])) * 60_000 * (match[1] === "+" ? 1 : -1);
|
|
999
|
-
return utcGuess - offsetMs;
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
function nextSaoPauloDay(day: string): string {
|
|
1003
|
-
const [year, month, date] = day.split("-").map(Number);
|
|
1004
|
-
const next = new Date(Date.UTC(year!, month! - 1, date! + 1));
|
|
1005
|
-
return next.toISOString().slice(0, 10);
|
|
885
|
+
function sumKnownDeveloperMetric(
|
|
886
|
+
rows: readonly Record<string, unknown>[],
|
|
887
|
+
field: string,
|
|
888
|
+
): number | null {
|
|
889
|
+
const values = rows
|
|
890
|
+
.map((row) => row[field])
|
|
891
|
+
.filter((value): value is number => typeof value === "number" && Number.isFinite(value));
|
|
892
|
+
return values.length === 0 ? null : values.reduce((sum, value) => sum + value, 0);
|
|
1006
893
|
}
|
|
1007
894
|
|
|
1008
895
|
/** Extracts the small artifact projection while the canonical file is already being read. */
|
|
1009
896
|
function readCanonicalArtifact(source: string, content: string): CanonicalArtifact | null {
|
|
1010
897
|
const kind = source.includes("/artifacts/evidence/")
|
|
1011
898
|
? "evidence"
|
|
1012
|
-
: source.includes("/artifacts/validations/")
|
|
899
|
+
: source.includes("/artifacts/validations/") || source.includes("/artifacts/tests/")
|
|
1013
900
|
? "validation"
|
|
1014
901
|
: null;
|
|
1015
902
|
if (kind === null) return null;
|
|
@@ -1028,6 +915,9 @@ function readCanonicalArtifact(source: string, content: string): CanonicalArtifa
|
|
|
1028
915
|
id: scalar("id") ?? source.slice(source.lastIndexOf("/") + 1, -3),
|
|
1029
916
|
occurredAt,
|
|
1030
917
|
occurredDaySp: saoPauloDay(occurredAt),
|
|
918
|
+
taskId: scalar("task_id"),
|
|
919
|
+
changeId: scalar("change_id"),
|
|
920
|
+
runId: scalar("run_id"),
|
|
1031
921
|
};
|
|
1032
922
|
}
|
|
1033
923
|
|
|
@@ -1035,6 +925,7 @@ export function sqliteReadModelPath(workspaceRoot: string): string {
|
|
|
1035
925
|
return resolve(process.env.ZAIP_SQLITE_DB_PATH ?? resolve(workspaceRoot, dbRelativePath));
|
|
1036
926
|
}
|
|
1037
927
|
|
|
928
|
+
/** Atomically installs a worker-built immutable generation after its schema is validated. */
|
|
1038
929
|
/**
|
|
1039
930
|
* Refuses stale or incomplete SQLite generations before they are served. This
|
|
1040
931
|
* is intentionally independent of source fingerprints: a matching cache key
|
|
@@ -1048,36 +939,54 @@ export function validateSqliteReadModelSchema(path: string): {
|
|
|
1048
939
|
} {
|
|
1049
940
|
const database = new DatabaseSync(path, { readOnly: true });
|
|
1050
941
|
try {
|
|
1051
|
-
const integrity = database.prepare("PRAGMA integrity_check").get() as {
|
|
942
|
+
const integrity = database.prepare("PRAGMA integrity_check").get() as {
|
|
943
|
+
integrity_check?: string;
|
|
944
|
+
};
|
|
1052
945
|
if (integrity.integrity_check !== "ok")
|
|
1053
|
-
throw new Error(
|
|
946
|
+
throw new Error(
|
|
947
|
+
`SQLite integrity check failed for ${path}: ${integrity.integrity_check ?? "unknown"}`,
|
|
948
|
+
);
|
|
1054
949
|
const objects = database
|
|
1055
950
|
.prepare("SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view', 'index')")
|
|
1056
951
|
.all() as Array<{ name: string; type: "table" | "view" | "index" }>;
|
|
1057
|
-
const tables = objects
|
|
952
|
+
const tables = objects
|
|
953
|
+
.filter((object) => object.type === "table")
|
|
954
|
+
.map((object) => object.name)
|
|
955
|
+
.sort();
|
|
1058
956
|
const expectedTables = [...requiredPersistedTables].sort();
|
|
1059
|
-
if (
|
|
1060
|
-
|
|
957
|
+
if (
|
|
958
|
+
tables.length !== expectedTables.length ||
|
|
959
|
+
tables.some((table, index) => table !== expectedTables[index])
|
|
960
|
+
)
|
|
961
|
+
throw new Error(
|
|
962
|
+
`SQLite schema ${readModelSchemaVersion} requires the complete persisted table contract; found ${tables.join(", ") || "none"}`,
|
|
963
|
+
);
|
|
1061
964
|
const names = new Set(objects.map((object) => object.name));
|
|
1062
965
|
const missingViews = requiredViews.filter((view) => !names.has(view));
|
|
1063
|
-
if (missingViews.length > 0)
|
|
966
|
+
if (missingViews.length > 0)
|
|
967
|
+
throw new Error(`SQLite schema is missing required views: ${missingViews.join(", ")}`);
|
|
1064
968
|
const views = objects.filter((object) => object.type === "view").map((object) => object.name);
|
|
1065
969
|
if (views.some((view) => !view.startsWith("zaip_vw_")))
|
|
1066
970
|
throw new Error("SQLite persisted views must use the zaip_vw_ namespace");
|
|
1067
971
|
const missingIndexes = requiredIndexes.filter((index) => !names.has(index));
|
|
1068
|
-
if (missingIndexes.length > 0)
|
|
972
|
+
if (missingIndexes.length > 0)
|
|
973
|
+
throw new Error(`SQLite schema is missing required indexes: ${missingIndexes.join(", ")}`);
|
|
1069
974
|
const schemaVersion = (
|
|
1070
975
|
database
|
|
1071
976
|
.prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key = ?")
|
|
1072
977
|
.get("schema_version") as { value?: string } | undefined
|
|
1073
978
|
)?.value;
|
|
1074
979
|
if (schemaVersion !== readModelSchemaVersion)
|
|
1075
|
-
throw new Error(
|
|
980
|
+
throw new Error(
|
|
981
|
+
`SQLite schema version must be ${readModelSchemaVersion}; found ${schemaVersion ?? "missing"}`,
|
|
982
|
+
);
|
|
1076
983
|
return {
|
|
1077
984
|
schemaVersion,
|
|
1078
985
|
tableCount: tables.length,
|
|
1079
986
|
viewCount: views.length,
|
|
1080
|
-
indexCount: objects.filter(
|
|
987
|
+
indexCount: objects.filter(
|
|
988
|
+
(object) => object.type === "index" && !object.name.startsWith("sqlite_"),
|
|
989
|
+
).length,
|
|
1081
990
|
};
|
|
1082
991
|
} finally {
|
|
1083
992
|
database.close();
|
|
@@ -1087,6 +996,7 @@ export function validateSqliteReadModelSchema(path: string): {
|
|
|
1087
996
|
/** Recreates the disposable comparison read model from ZAIP records and configuration. */
|
|
1088
997
|
export async function rebuildSqliteReadModel(
|
|
1089
998
|
workspaceRoot: string,
|
|
999
|
+
options: { forceCanonical?: boolean } = {},
|
|
1090
1000
|
): Promise<{
|
|
1091
1001
|
files: number;
|
|
1092
1002
|
path: string;
|
|
@@ -1099,14 +1009,27 @@ export async function rebuildSqliteReadModel(
|
|
|
1099
1009
|
const sourceSnapshot = await sourceFingerprint(workspaceRoot);
|
|
1100
1010
|
const reportLaunchProgress = (phase: string, percent: number, detail: string) => {
|
|
1101
1011
|
if (process.env.ZAIP_DASHBOARD_LAUNCH_PROGRESS !== "1") return;
|
|
1102
|
-
process.stderr.write(
|
|
1012
|
+
process.stderr.write(
|
|
1013
|
+
`${JSON.stringify({ event: "dashboard_sqlite_rebuild_progress", phase, percent, detail })}\n`,
|
|
1014
|
+
);
|
|
1103
1015
|
};
|
|
1104
1016
|
const { fingerprint } = sourceSnapshot;
|
|
1105
1017
|
const fingerprintMs = Math.round(performance.now() - fingerprintStartedAt);
|
|
1106
|
-
const existingFiles = readSnapshotFileCount(
|
|
1018
|
+
const existingFiles = readSnapshotFileCount(
|
|
1019
|
+
path,
|
|
1020
|
+
fingerprint,
|
|
1021
|
+
readModelSchemaVersion,
|
|
1022
|
+
hasRequiredFirstFoldPayloads,
|
|
1023
|
+
);
|
|
1107
1024
|
if (existingFiles !== null) {
|
|
1108
1025
|
validateSqliteReadModelSchema(path);
|
|
1109
|
-
return {
|
|
1026
|
+
return {
|
|
1027
|
+
files: existingFiles,
|
|
1028
|
+
path,
|
|
1029
|
+
cache: "generation",
|
|
1030
|
+
fingerprint_ms: fingerprintMs,
|
|
1031
|
+
materialize_ms: 0,
|
|
1032
|
+
};
|
|
1110
1033
|
}
|
|
1111
1034
|
|
|
1112
1035
|
// The launcher intentionally gives every live server a private database path
|
|
@@ -1115,9 +1038,19 @@ export async function rebuildSqliteReadModel(
|
|
|
1115
1038
|
// generation path. A normal fresh-port handoff can therefore be warm.
|
|
1116
1039
|
const cachePath = join(
|
|
1117
1040
|
process.env.ZAIP_SQLITE_CACHE_DIR ?? join(dirname(path), "cache"),
|
|
1118
|
-
|
|
1041
|
+
// The cache payload is part of the immutable generation contract. Keep
|
|
1042
|
+
// a separate namespace when that payload changes, so a schema-compatible
|
|
1043
|
+
// older seed cannot bypass the new all-time precomputation.
|
|
1044
|
+
// Keep the immutable name short enough that SQLite can create its
|
|
1045
|
+
// temporary journal beside deep Windows fixture/workspace paths.
|
|
1046
|
+
`v${readModelSchemaVersion}-ff9-${createHash("sha256").update(fingerprint).digest("hex")}.sqlite`,
|
|
1047
|
+
);
|
|
1048
|
+
const cachedFiles = readSnapshotFileCount(
|
|
1049
|
+
cachePath,
|
|
1050
|
+
fingerprint,
|
|
1051
|
+
readModelSchemaVersion,
|
|
1052
|
+
hasRequiredFirstFoldPayloads,
|
|
1119
1053
|
);
|
|
1120
|
-
const cachedFiles = readSnapshotFileCount(cachePath, fingerprint);
|
|
1121
1054
|
if (cachedFiles !== null) {
|
|
1122
1055
|
const materializeStartedAt = performance.now();
|
|
1123
1056
|
await materializeSnapshot(cachePath, path);
|
|
@@ -1135,8 +1068,12 @@ export async function rebuildSqliteReadModel(
|
|
|
1135
1068
|
// reconstruction merely because a record changed since the last snapshot.
|
|
1136
1069
|
// The launcher labels this state as updating and starts an isolated refresh;
|
|
1137
1070
|
// this process still serves only a schema-validated immutable seed.
|
|
1138
|
-
if (process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED === "1") {
|
|
1139
|
-
const fallback = await mostRecentCompatibleSnapshot(
|
|
1071
|
+
if (!options.forceCanonical && process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED === "1") {
|
|
1072
|
+
const fallback = await mostRecentCompatibleSnapshot(
|
|
1073
|
+
dirname(cachePath),
|
|
1074
|
+
readModelSchemaVersion,
|
|
1075
|
+
hasRequiredFirstFoldPayloads,
|
|
1076
|
+
);
|
|
1140
1077
|
if (fallback !== null) {
|
|
1141
1078
|
const materializeStartedAt = performance.now();
|
|
1142
1079
|
await materializeSnapshot(fallback.path, path);
|
|
@@ -1150,19 +1087,23 @@ export async function rebuildSqliteReadModel(
|
|
|
1150
1087
|
}
|
|
1151
1088
|
}
|
|
1152
1089
|
|
|
1153
|
-
reportLaunchProgress(
|
|
1090
|
+
reportLaunchProgress(
|
|
1091
|
+
"source_inventory",
|
|
1092
|
+
52,
|
|
1093
|
+
`Encontradas ${sourceSnapshot.candidates.length} fontes canônicas.`,
|
|
1094
|
+
);
|
|
1154
1095
|
await mkdir(dirname(cachePath), { recursive: true });
|
|
1155
|
-
const temporaryPath =
|
|
1096
|
+
const temporaryPath = join(dirname(cachePath), `.snapshot-${randomUUID()}.tmp`);
|
|
1156
1097
|
const database = new DatabaseSync(temporaryPath);
|
|
1157
1098
|
let completedFiles = 0;
|
|
1158
1099
|
try {
|
|
1159
1100
|
database.exec("PRAGMA journal_mode=DELETE; BEGIN IMMEDIATE;");
|
|
1160
1101
|
try {
|
|
1161
1102
|
database.exec(`
|
|
1162
|
-
CREATE TEMP TABLE files (path TEXT PRIMARY KEY, content TEXT NOT NULL, mtime_ms INTEGER NOT NULL, size_bytes INTEGER NOT NULL, content_sha256 TEXT NOT NULL, artifact_kind TEXT, artifact_id TEXT, artifact_occurred_at TEXT, artifact_occurred_day_sp TEXT);
|
|
1103
|
+
CREATE TEMP TABLE files (path TEXT PRIMARY KEY, content TEXT NOT NULL, mtime_ms INTEGER NOT NULL, size_bytes INTEGER NOT NULL, content_sha256 TEXT NOT NULL, artifact_kind TEXT, artifact_id TEXT, artifact_occurred_at TEXT, artifact_occurred_day_sp TEXT, artifact_task_id TEXT, artifact_change_id TEXT, artifact_run_id TEXT);
|
|
1163
1104
|
CREATE TEMP TABLE read_model_source_state (source_path TEXT PRIMARY KEY, source_hash TEXT NOT NULL, mtime_ms INTEGER NOT NULL, size_bytes INTEGER NOT NULL, source_kind TEXT NOT NULL, last_processed_at TEXT NOT NULL);
|
|
1164
1105
|
CREATE TEMP TABLE read_model_metadata (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
1165
|
-
CREATE TEMP TABLE canonical_work_items (entity_kind TEXT NOT NULL, id TEXT NOT NULL, title TEXT, status TEXT, stage TEXT, risk TEXT, occurred_at TEXT NOT NULL, occurred_day_sp TEXT NOT NULL, PRIMARY KEY(entity_kind, id));
|
|
1106
|
+
CREATE TEMP TABLE canonical_work_items (entity_kind TEXT NOT NULL, id TEXT NOT NULL, title TEXT, status TEXT, stage TEXT, risk TEXT, spec_id TEXT, change_id TEXT, completed_run_id TEXT, occurred_at TEXT NOT NULL, occurred_day_sp TEXT NOT NULL, PRIMARY KEY(entity_kind, id));
|
|
1166
1107
|
CREATE TEMP TABLE canonical_executions (id TEXT PRIMARY KEY, status TEXT, kind TEXT, origin_type TEXT, origin_id TEXT, created_at TEXT NOT NULL, occurred_day_sp TEXT NOT NULL, finished_at TEXT, source_path TEXT NOT NULL);
|
|
1167
1108
|
CREATE TEMP TABLE canonical_changes (id TEXT PRIMARY KEY, title TEXT NOT NULL, status TEXT NOT NULL, risk TEXT NOT NULL, source_type TEXT NOT NULL, source_id TEXT NOT NULL, source_spec_id TEXT, source_prd_id TEXT, requirement_ids_json TEXT NOT NULL, acceptance_ids_json TEXT NOT NULL, task_ids_json TEXT NOT NULL, result TEXT, created_at TEXT NOT NULL, occurred_day_sp TEXT NOT NULL, source_path TEXT NOT NULL);
|
|
1168
1109
|
CREATE TEMP TABLE canonical_decisions (id TEXT PRIMARY KEY, title TEXT NOT NULL, origin TEXT NOT NULL, scope_type TEXT NOT NULL, scope_id TEXT NOT NULL, created_at TEXT NOT NULL, occurred_day_sp TEXT NOT NULL, source_path TEXT NOT NULL);
|
|
@@ -1191,6 +1132,7 @@ export async function rebuildSqliteReadModel(
|
|
|
1191
1132
|
occurred_at TEXT, occurred_day_sp TEXT, execution_kind TEXT, entrypoint TEXT, work_type TEXT, phase TEXT, purpose TEXT, status TEXT, activity_kind TEXT,
|
|
1192
1133
|
zaip_agent TEXT, zaip_agent_persona TEXT, zaip_agent_job TEXT, zaip_agent_role TEXT, agent_catalog_id TEXT, agent_catalog_hash TEXT,
|
|
1193
1134
|
client TEXT, provider TEXT, model TEXT, service_tier TEXT, reasoning_effort TEXT,
|
|
1135
|
+
developer_account_id TEXT, developer_login TEXT, developer_attribution_source TEXT,
|
|
1194
1136
|
source_duration_ms REAL, source_started_at TEXT, source_finished_at TEXT, duration_ms REAL, duration_provenance TEXT, input_tokens REAL, cached_input_tokens REAL, output_tokens REAL, reasoning_tokens REAL, total_tokens REAL, token_source TEXT, usage_event_count INTEGER, llm_call_count INTEGER,
|
|
1195
1137
|
cost_usd REAL, cost_brl REAL, cost_method TEXT, pricing_model TEXT, pricing_source TEXT, fx_rate REAL, source_event_count INTEGER,
|
|
1196
1138
|
root_run_duration REAL, sub_run_duration REAL,
|
|
@@ -1217,7 +1159,7 @@ export async function rebuildSqliteReadModel(
|
|
|
1217
1159
|
PRIMARY KEY(activity_row_id, occurred_day_sp)
|
|
1218
1160
|
);`);
|
|
1219
1161
|
const insert = database.prepare(
|
|
1220
|
-
"INSERT INTO files(path, content, mtime_ms, size_bytes, content_sha256, artifact_kind, artifact_id, artifact_occurred_at, artifact_occurred_day_sp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
1162
|
+
"INSERT INTO files(path, content, mtime_ms, size_bytes, content_sha256, artifact_kind, artifact_id, artifact_occurred_at, artifact_occurred_day_sp, artifact_task_id, artifact_change_id, artifact_run_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
1221
1163
|
);
|
|
1222
1164
|
const insertSourceState = database.prepare(
|
|
1223
1165
|
"INSERT INTO read_model_source_state(source_path, source_hash, mtime_ms, size_bytes, source_kind, last_processed_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
@@ -1225,11 +1167,17 @@ export async function rebuildSqliteReadModel(
|
|
|
1225
1167
|
let files = 0;
|
|
1226
1168
|
const skipped = 0;
|
|
1227
1169
|
const sourceReadBatchSize = 32;
|
|
1228
|
-
for (
|
|
1170
|
+
for (
|
|
1171
|
+
let offset = 0;
|
|
1172
|
+
offset < sourceSnapshot.candidates.length;
|
|
1173
|
+
offset += sourceReadBatchSize
|
|
1174
|
+
) {
|
|
1229
1175
|
const batch = sourceSnapshot.candidates.slice(offset, offset + sourceReadBatchSize);
|
|
1230
1176
|
// Parallel reads reduce cold I/O latency, while the following ordered
|
|
1231
1177
|
// loop retains deterministic SQLite inserts and canonical projections.
|
|
1232
|
-
const contents = await Promise.all(
|
|
1178
|
+
const contents = await Promise.all(
|
|
1179
|
+
batch.map((candidate) => readFile(candidate.path, "utf8")),
|
|
1180
|
+
);
|
|
1233
1181
|
for (let index = 0; index < batch.length; index += 1) {
|
|
1234
1182
|
const candidate = batch[index];
|
|
1235
1183
|
const content = contents[index];
|
|
@@ -1240,7 +1188,7 @@ export async function rebuildSqliteReadModel(
|
|
|
1240
1188
|
? "run"
|
|
1241
1189
|
: source.includes("/artifacts/evidence/")
|
|
1242
1190
|
? "evidence"
|
|
1243
|
-
: source.includes("/artifacts/validations/")
|
|
1191
|
+
: source.includes("/artifacts/validations/") || source.includes("/artifacts/tests/")
|
|
1244
1192
|
? "validation"
|
|
1245
1193
|
: source.startsWith(".agents/config/")
|
|
1246
1194
|
? "config"
|
|
@@ -1256,6 +1204,9 @@ export async function rebuildSqliteReadModel(
|
|
|
1256
1204
|
artifact?.id ?? null,
|
|
1257
1205
|
artifact?.occurredAt ?? null,
|
|
1258
1206
|
artifact?.occurredDaySp ?? null,
|
|
1207
|
+
artifact?.taskId ?? null,
|
|
1208
|
+
artifact?.changeId ?? null,
|
|
1209
|
+
artifact?.runId ?? null,
|
|
1259
1210
|
);
|
|
1260
1211
|
insertSourceState.run(
|
|
1261
1212
|
source,
|
|
@@ -1267,8 +1218,13 @@ export async function rebuildSqliteReadModel(
|
|
|
1267
1218
|
);
|
|
1268
1219
|
files += 1;
|
|
1269
1220
|
}
|
|
1270
|
-
const completedPercent =
|
|
1271
|
-
|
|
1221
|
+
const completedPercent =
|
|
1222
|
+
52 + Math.round(((offset + batch.length) / sourceSnapshot.candidates.length) * 20);
|
|
1223
|
+
reportLaunchProgress(
|
|
1224
|
+
"source_import",
|
|
1225
|
+
completedPercent,
|
|
1226
|
+
`Lidas ${Math.min(offset + batch.length, sourceSnapshot.candidates.length)} de ${sourceSnapshot.candidates.length} fontes.`,
|
|
1227
|
+
);
|
|
1272
1228
|
}
|
|
1273
1229
|
reportLaunchProgress("projection_build", 74, "Organizando fatos e projeções do banco local.");
|
|
1274
1230
|
const canonicalEntities = readCanonicalEntities(
|
|
@@ -1277,7 +1233,7 @@ export async function rebuildSqliteReadModel(
|
|
|
1277
1233
|
.all() as Array<{ path: string; content: string }>,
|
|
1278
1234
|
);
|
|
1279
1235
|
const insertCanonicalWorkItem = database.prepare(
|
|
1280
|
-
"INSERT INTO canonical_work_items(entity_kind, id, title, status, stage, risk, occurred_at, occurred_day_sp) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
1236
|
+
"INSERT INTO canonical_work_items(entity_kind, id, title, status, stage, risk, spec_id, change_id, completed_run_id, occurred_at, occurred_day_sp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
1281
1237
|
);
|
|
1282
1238
|
for (const entity of canonicalEntities.values()) {
|
|
1283
1239
|
if (
|
|
@@ -1293,6 +1249,9 @@ export async function rebuildSqliteReadModel(
|
|
|
1293
1249
|
entity.status,
|
|
1294
1250
|
entity.stage,
|
|
1295
1251
|
entity.risk,
|
|
1252
|
+
entity.specId,
|
|
1253
|
+
entity.changeId,
|
|
1254
|
+
entity.completedRunId,
|
|
1296
1255
|
entity.occurredAt,
|
|
1297
1256
|
saoPauloDay(entity.occurredAt),
|
|
1298
1257
|
);
|
|
@@ -1390,6 +1349,56 @@ export async function rebuildSqliteReadModel(
|
|
|
1390
1349
|
"SELECT path, content, content_sha256 FROM files WHERE path LIKE '.agents/records/runs/%.jsonl' ORDER BY path",
|
|
1391
1350
|
)
|
|
1392
1351
|
.all() as Array<{ path: string; content: string; content_sha256: string }>;
|
|
1352
|
+
// Historical attribution is an append-only, self-verifying overlay. It
|
|
1353
|
+
// never changes a Run stream and therefore participates only after the
|
|
1354
|
+
// native identity carried by the Run itself.
|
|
1355
|
+
const historicalDeveloperManifests = (
|
|
1356
|
+
database
|
|
1357
|
+
.prepare(
|
|
1358
|
+
"SELECT content FROM files WHERE path LIKE '.agents/records/lifecycle/historical-developer-attribution/%.json'",
|
|
1359
|
+
)
|
|
1360
|
+
.all() as Array<{ content: string }>
|
|
1361
|
+
).flatMap((row) => {
|
|
1362
|
+
try {
|
|
1363
|
+
return [JSON.parse(row.content)];
|
|
1364
|
+
} catch {
|
|
1365
|
+
return [];
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
const resolveDeveloper = (row: { path: string; content: string }, identity: unknown) => {
|
|
1369
|
+
const native = recordValue(identity);
|
|
1370
|
+
if (
|
|
1371
|
+
native !== null &&
|
|
1372
|
+
typeof native.account_id === "string" &&
|
|
1373
|
+
typeof native.login_snapshot === "string"
|
|
1374
|
+
)
|
|
1375
|
+
return {
|
|
1376
|
+
accountId: native.account_id,
|
|
1377
|
+
login: native.login_snapshot,
|
|
1378
|
+
source:
|
|
1379
|
+
typeof native.attribution_source === "string" ? native.attribution_source : "native",
|
|
1380
|
+
};
|
|
1381
|
+
for (const candidate of historicalDeveloperManifests) {
|
|
1382
|
+
try {
|
|
1383
|
+
const resolved = resolveHistoricalDeveloperAttribution({
|
|
1384
|
+
manifest: candidate,
|
|
1385
|
+
run_id: String(JSON.parse(row.content.split(/\r?\n/u)[0] ?? "{}").run_id ?? ""),
|
|
1386
|
+
path: row.path,
|
|
1387
|
+
content: row.content,
|
|
1388
|
+
});
|
|
1389
|
+
if (resolved !== null)
|
|
1390
|
+
return {
|
|
1391
|
+
accountId: resolved.account_id,
|
|
1392
|
+
login: resolved.login_snapshot,
|
|
1393
|
+
source: resolved.attribution_source,
|
|
1394
|
+
};
|
|
1395
|
+
} catch {
|
|
1396
|
+
// A corrupt or non-matching historical manifest is not an
|
|
1397
|
+
// attribution source. The consumer remains fail-closed.
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
return null;
|
|
1401
|
+
};
|
|
1393
1402
|
const folds = new Map(
|
|
1394
1403
|
runRows.map((row) => {
|
|
1395
1404
|
const fold = foldRunV2Records(parseRunV2JsonLines(row.content));
|
|
@@ -1462,6 +1471,9 @@ export async function rebuildSqliteReadModel(
|
|
|
1462
1471
|
"model",
|
|
1463
1472
|
"service_tier",
|
|
1464
1473
|
"reasoning_effort",
|
|
1474
|
+
"developer_account_id",
|
|
1475
|
+
"developer_login",
|
|
1476
|
+
"developer_attribution_source",
|
|
1465
1477
|
"source_duration_ms",
|
|
1466
1478
|
"source_started_at",
|
|
1467
1479
|
"source_finished_at",
|
|
@@ -1500,10 +1512,11 @@ export async function rebuildSqliteReadModel(
|
|
|
1500
1512
|
"INSERT INTO canonical_run_index(run_id, occurred_at, occurred_day_sp, task_id, phase, run_role, duration_ms, source_event_count, zaip_agent, provider, model, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
1501
1513
|
);
|
|
1502
1514
|
let unassistedActivityCount = 0;
|
|
1503
|
-
|
|
1515
|
+
const excludedUnknownTokenRunCount = 0;
|
|
1504
1516
|
const temporalIntervals: TemporalInterval[] = [];
|
|
1505
1517
|
for (const { row, fold } of folds.values()) {
|
|
1506
1518
|
const started = fold.run;
|
|
1519
|
+
const developer = resolveDeveloper(row, started.developer_identity ?? null);
|
|
1507
1520
|
const usage = fold.effective_usage_records;
|
|
1508
1521
|
const agentRunId = started.agent_invocation_id ?? null;
|
|
1509
1522
|
const isAgentRun = agentRunId !== null;
|
|
@@ -1550,13 +1563,9 @@ export async function rebuildSqliteReadModel(
|
|
|
1550
1563
|
model,
|
|
1551
1564
|
fold.status,
|
|
1552
1565
|
);
|
|
1553
|
-
//
|
|
1554
|
-
//
|
|
1555
|
-
//
|
|
1556
|
-
if (totalTokens === null) {
|
|
1557
|
-
excludedUnknownTokenRunCount += 1;
|
|
1558
|
-
continue;
|
|
1559
|
-
}
|
|
1566
|
+
// Preserve an attributed Run with absent telemetry as an analytical
|
|
1567
|
+
// fact with NULL measures. This makes coverage visibly unknown rather
|
|
1568
|
+
// than silently converting it to a known zero or erasing its owner.
|
|
1560
1569
|
const profile = agentProfiles.get(zaipAgent) ?? agentProfiles.get(started.agent);
|
|
1561
1570
|
const workItem =
|
|
1562
1571
|
started.task_id === null
|
|
@@ -1645,6 +1654,9 @@ export async function rebuildSqliteReadModel(
|
|
|
1645
1654
|
consolidatedValue(usage.map((record) => record.runtime?.reasoning_effort ?? null)) ??
|
|
1646
1655
|
started.reasoning_effort ??
|
|
1647
1656
|
null,
|
|
1657
|
+
developer_account_id: developer?.accountId ?? null,
|
|
1658
|
+
developer_login: developer?.login ?? null,
|
|
1659
|
+
developer_attribution_source: developer?.source ?? null,
|
|
1648
1660
|
source_duration_ms: durations.source,
|
|
1649
1661
|
source_started_at: durations.sourceStartedAt,
|
|
1650
1662
|
source_finished_at: durations.sourceFinishedAt,
|
|
@@ -1671,9 +1683,15 @@ export async function rebuildSqliteReadModel(
|
|
|
1671
1683
|
payload_json: null,
|
|
1672
1684
|
} satisfies Record<(typeof columns)[number], string | number | null>;
|
|
1673
1685
|
canonicalInsertActivity.run(...columns.map((column) => activity[column] ?? null));
|
|
1674
|
-
if (
|
|
1686
|
+
if (
|
|
1687
|
+
totalTokens !== null &&
|
|
1688
|
+
totalTokens > 0 &&
|
|
1689
|
+
durations.effective !== null &&
|
|
1690
|
+
durations.effective >= 0
|
|
1691
|
+
) {
|
|
1675
1692
|
const startMs = Date.parse(durations.sourceStartedAt ?? started.occurred_at);
|
|
1676
|
-
const endMs =
|
|
1693
|
+
const endMs =
|
|
1694
|
+
Date.parse(durations.sourceFinishedAt ?? "") || startMs + durations.effective;
|
|
1677
1695
|
if (Number.isFinite(startMs) && Number.isFinite(endMs) && endMs > startMs)
|
|
1678
1696
|
temporalIntervals.push({ activityRowId, startMs, endMs });
|
|
1679
1697
|
else if (durations.effective === 0)
|
|
@@ -1726,6 +1744,188 @@ export async function rebuildSqliteReadModel(
|
|
|
1726
1744
|
}
|
|
1727
1745
|
for (const fact of allocateAttributedTokenActiveMs(temporalIntervals))
|
|
1728
1746
|
insertTemporalFact.run(fact.activityRowId, fact.day, fact.value);
|
|
1747
|
+
// A single authoring sub-Run can create several sibling SPECs. Keep the
|
|
1748
|
+
// Run canonical and attach deterministic shares only to the macro read
|
|
1749
|
+
// model. Payload rows stay outside `activities`, preventing global
|
|
1750
|
+
// Run/Cost totals from counting that Run more than once.
|
|
1751
|
+
const creationSpecsByRun = new Map<string, CanonicalEntity[]>();
|
|
1752
|
+
for (const entity of canonicalEntities.values()) {
|
|
1753
|
+
if (entity.kind !== "spec" || entity.creationRunId === null) continue;
|
|
1754
|
+
creationSpecsByRun.set(entity.creationRunId, [
|
|
1755
|
+
...(creationSpecsByRun.get(entity.creationRunId) ?? []),
|
|
1756
|
+
entity,
|
|
1757
|
+
]);
|
|
1758
|
+
}
|
|
1759
|
+
const materializeSharedCreationAllocations = () => {
|
|
1760
|
+
for (const [runId, recipients] of creationSpecsByRun) {
|
|
1761
|
+
if (recipients.length === 0) continue;
|
|
1762
|
+
const source = folds.get(runId);
|
|
1763
|
+
if (source === undefined) continue;
|
|
1764
|
+
const { fold, row } = source;
|
|
1765
|
+
const started = fold.run;
|
|
1766
|
+
const usage = fold.effective_usage_records;
|
|
1767
|
+
const canonicalActivity = database
|
|
1768
|
+
.prepare(
|
|
1769
|
+
"SELECT duration_ms, duration_provenance, source_started_at, source_finished_at FROM zaip_aios_activities WHERE run_id=? AND payload_type IS NULL LIMIT 1",
|
|
1770
|
+
)
|
|
1771
|
+
.get(runId) as
|
|
1772
|
+
| {
|
|
1773
|
+
duration_ms: number | null;
|
|
1774
|
+
duration_provenance: string | null;
|
|
1775
|
+
source_started_at: string | null;
|
|
1776
|
+
source_finished_at: string | null;
|
|
1777
|
+
}
|
|
1778
|
+
| undefined;
|
|
1779
|
+
const cost = aggregateCost(usage, fold, pricingCatalog, fxRate);
|
|
1780
|
+
const recipientIds = recipients.map((recipient) => recipient.id);
|
|
1781
|
+
// The allocator owns the only partition. Read the already-materialized
|
|
1782
|
+
// canonical activity fact after attributed-token-active reconciliation;
|
|
1783
|
+
// never use the wider JSONL session span or a pre-divided value.
|
|
1784
|
+
const sharedCreationDuration = canonicalActivity?.duration_ms ?? null;
|
|
1785
|
+
const allocations = {
|
|
1786
|
+
source_duration_ms: allocateSharedCreationValue(sharedCreationDuration, recipientIds),
|
|
1787
|
+
// Shared creation attribution uses only the observed source interval;
|
|
1788
|
+
// never propagate an effective/legacy aggregate that can include
|
|
1789
|
+
// more than the creation segment.
|
|
1790
|
+
duration_ms: allocateSharedCreationValue(sharedCreationDuration, recipientIds),
|
|
1791
|
+
input_tokens: allocateSharedCreationValue(knownSum(usage, "input"), recipientIds),
|
|
1792
|
+
cached_input_tokens: allocateSharedCreationValue(
|
|
1793
|
+
knownSum(usage, "cached_input"),
|
|
1794
|
+
recipientIds,
|
|
1795
|
+
),
|
|
1796
|
+
output_tokens: allocateSharedCreationValue(knownSum(usage, "output"), recipientIds),
|
|
1797
|
+
reasoning_tokens: allocateSharedCreationValue(
|
|
1798
|
+
knownSum(usage, "reasoning"),
|
|
1799
|
+
recipientIds,
|
|
1800
|
+
),
|
|
1801
|
+
total_tokens: allocateSharedCreationValue(knownSum(usage, "total"), recipientIds),
|
|
1802
|
+
cost_usd: allocateSharedCreationValue(cost.usd, recipientIds),
|
|
1803
|
+
cost_brl: allocateSharedCreationValue(cost.brl, recipientIds),
|
|
1804
|
+
};
|
|
1805
|
+
for (const recipient of recipients) {
|
|
1806
|
+
const attributed =
|
|
1807
|
+
usage.find((record) => record.attributed_spec_id === recipient.id) ?? null;
|
|
1808
|
+
const attributedDuration =
|
|
1809
|
+
attributed?.source_started_at && attributed?.source_finished_at
|
|
1810
|
+
? Math.max(
|
|
1811
|
+
0,
|
|
1812
|
+
Date.parse(attributed.source_finished_at) -
|
|
1813
|
+
Date.parse(attributed.source_started_at),
|
|
1814
|
+
)
|
|
1815
|
+
: null;
|
|
1816
|
+
const activityRowId = `ACTV-${createHash("sha256")
|
|
1817
|
+
.update(`shared-creation:${runId}:${recipient.id}`)
|
|
1818
|
+
.digest("hex")
|
|
1819
|
+
.slice(0, 24)}`;
|
|
1820
|
+
const lastRecord = fold.records.at(-1);
|
|
1821
|
+
const allocation = {
|
|
1822
|
+
activity_row_id: activityRowId,
|
|
1823
|
+
activity_source_kind: "shared_creation_allocation",
|
|
1824
|
+
activity_key: `shared-creation:${runId}:${recipient.id}`,
|
|
1825
|
+
source_path: row.path,
|
|
1826
|
+
source_hash: row.content_sha256,
|
|
1827
|
+
source_occurred_at: started.occurred_at,
|
|
1828
|
+
recorded_at: lastRecord?.occurred_at ?? started.occurred_at,
|
|
1829
|
+
version_activity_id: null,
|
|
1830
|
+
source_last_event_id: lastRecord?.event_id ?? null,
|
|
1831
|
+
source_last_sequence: lastRecord?.sequence ?? null,
|
|
1832
|
+
work_item_kind: "spec",
|
|
1833
|
+
title: recipient.title,
|
|
1834
|
+
work_item_status: recipient.status,
|
|
1835
|
+
work_stage: recipient.stage,
|
|
1836
|
+
risk: recipient.risk,
|
|
1837
|
+
run_role: "shared_creation_allocation",
|
|
1838
|
+
root_run_id: rootRunId(fold),
|
|
1839
|
+
parent_run_id: started.parent_run_id,
|
|
1840
|
+
agent_run_id: null,
|
|
1841
|
+
root_agent_run_id: null,
|
|
1842
|
+
parent_agent_run_id: null,
|
|
1843
|
+
agent_run_role: null,
|
|
1844
|
+
delegation_depth: started.delegation_depth ?? null,
|
|
1845
|
+
agent_invocation_id: null,
|
|
1846
|
+
root_agent_invocation_id: null,
|
|
1847
|
+
parent_agent_invocation_id: null,
|
|
1848
|
+
invocation_kind: null,
|
|
1849
|
+
execution_id: started.execution_id,
|
|
1850
|
+
task_id: null,
|
|
1851
|
+
spec_id: recipient.id,
|
|
1852
|
+
prd_id: null,
|
|
1853
|
+
change_id: null,
|
|
1854
|
+
conversation_id: started.conversation_id,
|
|
1855
|
+
run_id: started.run_id,
|
|
1856
|
+
occurred_at: started.occurred_at,
|
|
1857
|
+
occurred_day_sp: saoPauloDay(started.occurred_at),
|
|
1858
|
+
execution_kind: started.execution_kind ?? null,
|
|
1859
|
+
entrypoint: started.entrypoint,
|
|
1860
|
+
work_type: started.work_type,
|
|
1861
|
+
phase: started.phase,
|
|
1862
|
+
purpose: started.run_purpose ?? null,
|
|
1863
|
+
status: fold.status,
|
|
1864
|
+
activity_kind: "planning",
|
|
1865
|
+
zaip_agent: started.agent_name ?? started.agent,
|
|
1866
|
+
zaip_agent_persona: null,
|
|
1867
|
+
zaip_agent_job: null,
|
|
1868
|
+
zaip_agent_role: null,
|
|
1869
|
+
agent_catalog_id: null,
|
|
1870
|
+
agent_catalog_hash: catalogHash,
|
|
1871
|
+
client: started.client,
|
|
1872
|
+
provider: null,
|
|
1873
|
+
model: null,
|
|
1874
|
+
service_tier: null,
|
|
1875
|
+
reasoning_effort: started.reasoning_effort ?? null,
|
|
1876
|
+
developer_account_id: null,
|
|
1877
|
+
developer_login: null,
|
|
1878
|
+
developer_attribution_source: null,
|
|
1879
|
+
source_duration_ms:
|
|
1880
|
+
attributedDuration ?? allocations.source_duration_ms.get(recipient.id) ?? null,
|
|
1881
|
+
source_started_at:
|
|
1882
|
+
attributed?.source_started_at ?? canonicalActivity?.source_started_at ?? null,
|
|
1883
|
+
source_finished_at:
|
|
1884
|
+
attributed?.source_finished_at ?? canonicalActivity?.source_finished_at ?? null,
|
|
1885
|
+
duration_ms: attributedDuration ?? allocations.duration_ms.get(recipient.id) ?? null,
|
|
1886
|
+
duration_provenance: attributed
|
|
1887
|
+
? "explicit_spec_turn_recovery"
|
|
1888
|
+
: "shared_creation_allocation",
|
|
1889
|
+
input_tokens:
|
|
1890
|
+
attributed?.tokens.input ?? allocations.input_tokens.get(recipient.id) ?? null,
|
|
1891
|
+
cached_input_tokens:
|
|
1892
|
+
attributed?.tokens.cached_input ??
|
|
1893
|
+
allocations.cached_input_tokens.get(recipient.id) ??
|
|
1894
|
+
null,
|
|
1895
|
+
output_tokens:
|
|
1896
|
+
attributed?.tokens.output ?? allocations.output_tokens.get(recipient.id) ?? null,
|
|
1897
|
+
reasoning_tokens:
|
|
1898
|
+
attributed?.tokens.reasoning ??
|
|
1899
|
+
allocations.reasoning_tokens.get(recipient.id) ??
|
|
1900
|
+
null,
|
|
1901
|
+
total_tokens:
|
|
1902
|
+
attributed?.tokens.total ?? allocations.total_tokens.get(recipient.id) ?? null,
|
|
1903
|
+
token_source: consolidatedValue(usage.map((record) => record.tokens.source)),
|
|
1904
|
+
usage_event_count: usage.length,
|
|
1905
|
+
llm_call_count: null,
|
|
1906
|
+
cost_usd: attributed?.cost.usd ?? allocations.cost_usd.get(recipient.id) ?? null,
|
|
1907
|
+
cost_brl: attributed?.cost.brl ?? allocations.cost_brl.get(recipient.id) ?? null,
|
|
1908
|
+
cost_method: cost.method,
|
|
1909
|
+
pricing_model: cost.pricingModel,
|
|
1910
|
+
pricing_source: cost.pricingSource,
|
|
1911
|
+
fx_rate: cost.fxRate,
|
|
1912
|
+
source_event_count: fold.records.length,
|
|
1913
|
+
root_run_duration: null,
|
|
1914
|
+
sub_run_duration: null,
|
|
1915
|
+
payload_type: "shared_creation_allocation",
|
|
1916
|
+
payload_json: JSON.stringify({
|
|
1917
|
+
provenance: attributed
|
|
1918
|
+
? "explicit_spec_turn_recovery"
|
|
1919
|
+
: "shared_creation_allocation",
|
|
1920
|
+
base_duration_provenance: canonicalActivity?.duration_provenance ?? null,
|
|
1921
|
+
creation_run_id: runId,
|
|
1922
|
+
spec_id: recipient.id,
|
|
1923
|
+
}),
|
|
1924
|
+
} satisfies Record<(typeof columns)[number], string | number | null>;
|
|
1925
|
+
canonicalInsertActivity.run(...columns.map((column) => allocation[column] ?? null));
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
};
|
|
1729
1929
|
database.exec(`UPDATE zaip_aios_activities
|
|
1730
1930
|
SET duration_ms = (
|
|
1731
1931
|
SELECT SUM(f.attributed_token_active_ms)
|
|
@@ -1734,7 +1934,9 @@ export async function rebuildSqliteReadModel(
|
|
|
1734
1934
|
),
|
|
1735
1935
|
duration_provenance = CASE
|
|
1736
1936
|
WHEN EXISTS (SELECT 1 FROM attributed_token_active_facts f WHERE f.activity_row_id=zaip_aios_activities.activity_row_id)
|
|
1737
|
-
THEN 'attributed_token_active' ELSE NULL END
|
|
1937
|
+
THEN 'attributed_token_active' ELSE NULL END
|
|
1938
|
+
WHERE payload_type IS NULL;`);
|
|
1939
|
+
materializeSharedCreationAllocations();
|
|
1738
1940
|
const canonicalRunCount = folds.size;
|
|
1739
1941
|
const invocationCount = (
|
|
1740
1942
|
database
|
|
@@ -1815,7 +2017,7 @@ export async function rebuildSqliteReadModel(
|
|
|
1815
2017
|
insertConfiguredModel.run(configuredModel.id);
|
|
1816
2018
|
insertConfiguredProvider.run(configuredModel.provider);
|
|
1817
2019
|
}
|
|
1818
|
-
const costDimensionAggregates = buildCostDimensionAggregates(database);
|
|
2020
|
+
const costDimensionAggregates = buildCostDimensionAggregates(database, agentProfiles);
|
|
1819
2021
|
// The rebuild scratch tables above are intentionally TEMP only. Persist
|
|
1820
2022
|
// their technical inputs as typed payload activities so the on-disk
|
|
1821
2023
|
// model has one fact plus its three declared dimensions, never hidden
|
|
@@ -1828,16 +2030,46 @@ export async function rebuildSqliteReadModel(
|
|
|
1828
2030
|
'TECH-' || lower(hex(randomblob(16))), 'technical_payload', '${payloadType}:' || row_key,
|
|
1829
2031
|
'sqlite://${payloadType}', '', datetime('now'), 'technical', 'technical', '${payloadType}', payload_json
|
|
1830
2032
|
FROM (${selectSql});`);
|
|
1831
|
-
persistPayload(
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
persistPayload(
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
persistPayload(
|
|
1840
|
-
|
|
2033
|
+
persistPayload(
|
|
2034
|
+
"metadata",
|
|
2035
|
+
"SELECT key AS row_key, json_object('key', key, 'value', value) AS payload_json FROM read_model_metadata",
|
|
2036
|
+
);
|
|
2037
|
+
persistPayload(
|
|
2038
|
+
"source_state",
|
|
2039
|
+
"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",
|
|
2040
|
+
);
|
|
2041
|
+
persistPayload(
|
|
2042
|
+
"file",
|
|
2043
|
+
"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, 'artifact_task_id', artifact_task_id, 'artifact_change_id', artifact_change_id, 'artifact_run_id', artifact_run_id) AS payload_json FROM files",
|
|
2044
|
+
);
|
|
2045
|
+
persistPayload(
|
|
2046
|
+
"work_item",
|
|
2047
|
+
"SELECT entity_kind || ':' || id AS row_key, json_object('entity_kind', entity_kind, 'id', id, 'title', title, 'status', status, 'stage', stage, 'risk', risk, 'spec_id', spec_id, 'change_id', change_id, 'completed_run_id', completed_run_id, 'occurred_at', occurred_at, 'occurred_day_sp', occurred_day_sp) AS payload_json FROM canonical_work_items",
|
|
2048
|
+
);
|
|
2049
|
+
persistPayload(
|
|
2050
|
+
"execution",
|
|
2051
|
+
"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",
|
|
2052
|
+
);
|
|
2053
|
+
persistPayload(
|
|
2054
|
+
"change",
|
|
2055
|
+
"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",
|
|
2056
|
+
);
|
|
2057
|
+
persistPayload(
|
|
2058
|
+
"decision",
|
|
2059
|
+
"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",
|
|
2060
|
+
);
|
|
2061
|
+
persistPayload(
|
|
2062
|
+
"run_index",
|
|
2063
|
+
"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",
|
|
2064
|
+
);
|
|
2065
|
+
persistPayload(
|
|
2066
|
+
"usage_segment",
|
|
2067
|
+
"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",
|
|
2068
|
+
);
|
|
2069
|
+
persistPayload(
|
|
2070
|
+
"duration_allocation",
|
|
2071
|
+
"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",
|
|
2072
|
+
);
|
|
1841
2073
|
// Configuration-backed dimensions contain the current selectable
|
|
1842
2074
|
// catalog even when no activity has used an option yet. Historical
|
|
1843
2075
|
// values remain faithfully recorded in the fact without becoming
|
|
@@ -1870,6 +2102,27 @@ export async function rebuildSqliteReadModel(
|
|
|
1870
2102
|
);
|
|
1871
2103
|
}
|
|
1872
2104
|
}
|
|
2105
|
+
for (const [dimension, projection] of Object.entries(costDimensionAggregates.remote)) {
|
|
2106
|
+
for (const row of projection.rows) {
|
|
2107
|
+
insertCostDimensionAggregate.run(
|
|
2108
|
+
`TECH-${randomUUID().toLowerCase()}`,
|
|
2109
|
+
`cost_dimension_aggregate_v1:${dimension}:${row.key}`,
|
|
2110
|
+
`sqlite://cost_dimension_aggregate_v1/${dimension}`,
|
|
2111
|
+
JSON.stringify({ schema_version: 1, dimension, row_kind: "row", ...row }),
|
|
2112
|
+
);
|
|
2113
|
+
}
|
|
2114
|
+
insertCostDimensionAggregate.run(
|
|
2115
|
+
`TECH-${randomUUID().toLowerCase()}`,
|
|
2116
|
+
`cost_dimension_aggregate_v1:${dimension}:__totals__`,
|
|
2117
|
+
`sqlite://cost_dimension_aggregate_v1/${dimension}`,
|
|
2118
|
+
JSON.stringify({
|
|
2119
|
+
schema_version: 1,
|
|
2120
|
+
dimension,
|
|
2121
|
+
row_kind: "totals",
|
|
2122
|
+
...projection.totals,
|
|
2123
|
+
}),
|
|
2124
|
+
);
|
|
2125
|
+
}
|
|
1873
2126
|
const viewSql = `
|
|
1874
2127
|
CREATE INDEX zaip_activities_agent_run ON zaip_aios_activities(activity_source_kind, agent_run_id, occurred_day_sp, occurred_at DESC);
|
|
1875
2128
|
CREATE INDEX zaip_activities_relationships ON zaip_aios_activities(task_id, spec_id, prd_id, execution_id, conversation_id);
|
|
@@ -1877,7 +2130,7 @@ export async function rebuildSqliteReadModel(
|
|
|
1877
2130
|
CREATE INDEX zaip_activities_spec_day ON zaip_aios_activities(payload_type, spec_id, occurred_day_sp) WHERE spec_id IS NOT NULL;
|
|
1878
2131
|
CREATE INDEX zaip_activities_prd_day ON zaip_aios_activities(payload_type, prd_id, occurred_day_sp) WHERE prd_id IS NOT NULL;
|
|
1879
2132
|
CREATE INDEX zaip_activities_agent ON zaip_aios_activities(zaip_agent, zaip_agent_persona, zaip_agent_job, provider, model);
|
|
1880
|
-
CREATE UNIQUE INDEX zaip_activities_run_id ON zaip_aios_activities(run_id);
|
|
2133
|
+
CREATE UNIQUE INDEX zaip_activities_run_id ON zaip_aios_activities(run_id) WHERE payload_type IS NULL;
|
|
1881
2134
|
CREATE INDEX zaip_activities_run_hierarchy ON zaip_aios_activities(root_run_id, parent_run_id, run_role);
|
|
1882
2135
|
CREATE INDEX zaip_activities_period ON zaip_aios_activities(occurred_day_sp, occurred_at DESC);
|
|
1883
2136
|
-- Dimension residuals first restrict the visible period, then group
|
|
@@ -1887,17 +2140,17 @@ export async function rebuildSqliteReadModel(
|
|
|
1887
2140
|
WHERE agent_invocation_id IS NOT NULL;
|
|
1888
2141
|
CREATE INDEX zaip_activities_status ON zaip_aios_activities(activity_source_kind, status, phase);
|
|
1889
2142
|
CREATE INDEX zaip_activities_payload ON zaip_aios_activities(payload_type, activity_key);
|
|
1890
|
-
CREATE VIEW files AS SELECT json_extract(payload_json, '$.path') AS path, json_extract(payload_json, '$.content') AS content, json_extract(payload_json, '$.mtime_ms') AS mtime_ms, json_extract(payload_json, '$.size_bytes') AS size_bytes, json_extract(payload_json, '$.content_sha256') AS content_sha256, json_extract(payload_json, '$.artifact_kind') AS artifact_kind, json_extract(payload_json, '$.artifact_id') AS artifact_id, json_extract(payload_json, '$.artifact_occurred_at') AS artifact_occurred_at, json_extract(payload_json, '$.artifact_occurred_day_sp') AS artifact_occurred_day_sp FROM zaip_aios_activities WHERE payload_type='file';
|
|
2143
|
+
CREATE VIEW files AS SELECT json_extract(payload_json, '$.path') AS path, json_extract(payload_json, '$.content') AS content, json_extract(payload_json, '$.mtime_ms') AS mtime_ms, json_extract(payload_json, '$.size_bytes') AS size_bytes, json_extract(payload_json, '$.content_sha256') AS content_sha256, json_extract(payload_json, '$.artifact_kind') AS artifact_kind, json_extract(payload_json, '$.artifact_id') AS artifact_id, json_extract(payload_json, '$.artifact_occurred_at') AS artifact_occurred_at, json_extract(payload_json, '$.artifact_occurred_day_sp') AS artifact_occurred_day_sp, json_extract(payload_json, '$.artifact_task_id') AS artifact_task_id, json_extract(payload_json, '$.artifact_change_id') AS artifact_change_id, json_extract(payload_json, '$.artifact_run_id') AS artifact_run_id FROM zaip_aios_activities WHERE payload_type='file';
|
|
1891
2144
|
CREATE VIEW read_model_source_state AS SELECT json_extract(payload_json, '$.source_path') AS source_path, json_extract(payload_json, '$.source_hash') AS source_hash, json_extract(payload_json, '$.mtime_ms') AS mtime_ms, json_extract(payload_json, '$.size_bytes') AS size_bytes, json_extract(payload_json, '$.source_kind') AS source_kind, json_extract(payload_json, '$.last_processed_at') AS last_processed_at FROM zaip_aios_activities WHERE payload_type='source_state';
|
|
1892
2145
|
CREATE VIEW read_model_metadata AS SELECT json_extract(payload_json, '$.key') AS key, json_extract(payload_json, '$.value') AS value FROM zaip_aios_activities WHERE payload_type='metadata';
|
|
1893
|
-
CREATE VIEW canonical_work_items AS SELECT json_extract(payload_json, '$.entity_kind') AS entity_kind, json_extract(payload_json, '$.id') AS id, json_extract(payload_json, '$.title') AS title, json_extract(payload_json, '$.status') AS status, json_extract(payload_json, '$.stage') AS stage, json_extract(payload_json, '$.risk') AS risk, json_extract(payload_json, '$.occurred_at') AS occurred_at, json_extract(payload_json, '$.occurred_day_sp') AS occurred_day_sp FROM zaip_aios_activities WHERE payload_type='work_item';
|
|
2146
|
+
CREATE VIEW canonical_work_items AS SELECT json_extract(payload_json, '$.entity_kind') AS entity_kind, json_extract(payload_json, '$.id') AS id, json_extract(payload_json, '$.title') AS title, json_extract(payload_json, '$.status') AS status, json_extract(payload_json, '$.stage') AS stage, json_extract(payload_json, '$.risk') AS risk, json_extract(payload_json, '$.spec_id') AS spec_id, json_extract(payload_json, '$.change_id') AS change_id, json_extract(payload_json, '$.completed_run_id') AS completed_run_id, json_extract(payload_json, '$.occurred_at') AS occurred_at, json_extract(payload_json, '$.occurred_day_sp') AS occurred_day_sp FROM zaip_aios_activities WHERE payload_type='work_item';
|
|
1894
2147
|
CREATE VIEW canonical_executions AS SELECT json_extract(payload_json, '$.id') AS id, json_extract(payload_json, '$.status') AS status, json_extract(payload_json, '$.kind') AS kind, json_extract(payload_json, '$.origin_type') AS origin_type, json_extract(payload_json, '$.origin_id') AS origin_id, json_extract(payload_json, '$.created_at') AS created_at, json_extract(payload_json, '$.occurred_day_sp') AS occurred_day_sp, json_extract(payload_json, '$.finished_at') AS finished_at, json_extract(payload_json, '$.source_path') AS source_path FROM zaip_aios_activities WHERE payload_type='execution';
|
|
1895
2148
|
CREATE VIEW canonical_changes AS SELECT json_extract(payload_json, '$.id') AS id, json_extract(payload_json, '$.title') AS title, json_extract(payload_json, '$.status') AS status, json_extract(payload_json, '$.risk') AS risk, json_extract(payload_json, '$.source_type') AS source_type, json_extract(payload_json, '$.source_id') AS source_id, json_extract(payload_json, '$.source_spec_id') AS source_spec_id, json_extract(payload_json, '$.source_prd_id') AS source_prd_id, json_extract(payload_json, '$.requirement_ids_json') AS requirement_ids_json, json_extract(payload_json, '$.acceptance_ids_json') AS acceptance_ids_json, json_extract(payload_json, '$.task_ids_json') AS task_ids_json, json_extract(payload_json, '$.result') AS result, json_extract(payload_json, '$.created_at') AS created_at, json_extract(payload_json, '$.occurred_day_sp') AS occurred_day_sp, json_extract(payload_json, '$.source_path') AS source_path FROM zaip_aios_activities WHERE payload_type='change';
|
|
1896
2149
|
CREATE VIEW canonical_decisions AS SELECT json_extract(payload_json, '$.id') AS id, json_extract(payload_json, '$.title') AS title, json_extract(payload_json, '$.origin') AS origin, json_extract(payload_json, '$.scope_type') AS scope_type, json_extract(payload_json, '$.scope_id') AS scope_id, json_extract(payload_json, '$.created_at') AS created_at, json_extract(payload_json, '$.occurred_day_sp') AS occurred_day_sp, json_extract(payload_json, '$.source_path') AS source_path FROM zaip_aios_activities WHERE payload_type='decision';
|
|
1897
2150
|
CREATE VIEW canonical_run_index AS SELECT json_extract(payload_json, '$.run_id') AS run_id, json_extract(payload_json, '$.occurred_at') AS occurred_at, json_extract(payload_json, '$.occurred_day_sp') AS occurred_day_sp, json_extract(payload_json, '$.task_id') AS task_id, json_extract(payload_json, '$.phase') AS phase, json_extract(payload_json, '$.run_role') AS run_role, json_extract(payload_json, '$.duration_ms') AS duration_ms, json_extract(payload_json, '$.source_event_count') AS source_event_count, json_extract(payload_json, '$.zaip_agent') AS zaip_agent, json_extract(payload_json, '$.provider') AS provider, json_extract(payload_json, '$.model') AS model, json_extract(payload_json, '$.status') AS status FROM zaip_aios_activities WHERE payload_type='run_index';
|
|
1898
2151
|
CREATE VIEW invocation_usage_segments AS SELECT json_extract(payload_json, '$.agent_invocation_id') AS agent_invocation_id, json_extract(payload_json, '$.provider') AS provider, json_extract(payload_json, '$.model') AS model, json_extract(payload_json, '$.service_tier') AS service_tier, json_extract(payload_json, '$.reasoning_effort') AS reasoning_effort, json_extract(payload_json, '$.input_tokens') AS input_tokens, json_extract(payload_json, '$.cached_input_tokens') AS cached_input_tokens, json_extract(payload_json, '$.uncached_input_tokens') AS uncached_input_tokens, json_extract(payload_json, '$.output_tokens') AS output_tokens, json_extract(payload_json, '$.reasoning_tokens') AS reasoning_tokens, json_extract(payload_json, '$.visible_output_tokens') AS visible_output_tokens, json_extract(payload_json, '$.total_tokens') AS total_tokens, json_extract(payload_json, '$.input_rate_usd_per_million') AS input_rate_usd_per_million, json_extract(payload_json, '$.cached_input_rate_usd_per_million') AS cached_input_rate_usd_per_million, json_extract(payload_json, '$.output_rate_usd_per_million') AS output_rate_usd_per_million, json_extract(payload_json, '$.reasoning_rate_usd_per_million') AS reasoning_rate_usd_per_million, json_extract(payload_json, '$.context_input_multiplier') AS context_input_multiplier, json_extract(payload_json, '$.context_output_multiplier') AS context_output_multiplier, json_extract(payload_json, '$.speed_cost_multiplier') AS speed_cost_multiplier, json_extract(payload_json, '$.pricing_scenario') AS pricing_scenario, json_extract(payload_json, '$.standard_cost_usd') AS standard_cost_usd, json_extract(payload_json, '$.speed_uplift_usd') AS speed_uplift_usd, json_extract(payload_json, '$.input_cost_usd') AS input_cost_usd, json_extract(payload_json, '$.cached_input_cost_usd') AS cached_input_cost_usd, json_extract(payload_json, '$.output_cost_usd') AS output_cost_usd, json_extract(payload_json, '$.reasoning_cost_usd') AS reasoning_cost_usd, json_extract(payload_json, '$.calculated_cost_usd') AS calculated_cost_usd, json_extract(payload_json, '$.recorded_cost_usd') AS recorded_cost_usd, json_extract(payload_json, '$.cost_variance_usd') AS cost_variance_usd, json_extract(payload_json, '$.cost_usd') AS cost_usd, json_extract(payload_json, '$.cost_brl') AS cost_brl, json_extract(payload_json, '$.cost_method') AS cost_method, json_extract(payload_json, '$.pricing_model') AS pricing_model, json_extract(payload_json, '$.pricing_source') AS pricing_source, json_extract(payload_json, '$.fx_rate') AS fx_rate FROM zaip_aios_activities WHERE payload_type='usage_segment';
|
|
1899
2152
|
CREATE VIEW attributed_token_active_facts AS SELECT json_extract(payload_json, '$.activity_row_id') AS activity_row_id, json_extract(payload_json, '$.occurred_day_sp') AS occurred_day_sp, json_extract(payload_json, '$.attributed_token_active_ms') AS attributed_token_active_ms FROM zaip_aios_activities WHERE payload_type='duration_allocation';
|
|
1900
|
-
CREATE VIEW canonical_artifacts AS SELECT artifact_kind, artifact_id, artifact_occurred_at AS occurred_at, artifact_occurred_day_sp AS occurred_day_sp, path AS source_path FROM files WHERE artifact_kind IS NOT NULL AND artifact_occurred_at IS NOT NULL;
|
|
2153
|
+
CREATE VIEW canonical_artifacts AS SELECT artifact_kind, artifact_id, artifact_occurred_at AS occurred_at, artifact_occurred_day_sp AS occurred_day_sp, artifact_task_id AS task_id, artifact_change_id AS change_id, artifact_run_id AS run_id, path AS source_path FROM files WHERE artifact_kind IS NOT NULL AND artifact_occurred_at IS NOT NULL;
|
|
1901
2154
|
CREATE VIEW activities AS
|
|
1902
2155
|
SELECT a.*, f.attributed_token_active_ms
|
|
1903
2156
|
FROM zaip_aios_activities a
|
|
@@ -1906,6 +2159,19 @@ export async function rebuildSqliteReadModel(
|
|
|
1906
2159
|
FROM attributed_token_active_facts GROUP BY activity_row_id
|
|
1907
2160
|
) f USING(activity_row_id)
|
|
1908
2161
|
WHERE a.payload_type IS NULL;
|
|
2162
|
+
-- Macro-only allocations are persisted as technical payloads to keep
|
|
2163
|
+
-- the global Run population unique. SPEC summaries opt into this
|
|
2164
|
+
-- projection explicitly; all other activity and cost views do not.
|
|
2165
|
+
CREATE VIEW shared_creation_allocations AS
|
|
2166
|
+
SELECT a.*, NULL AS attributed_token_active_ms
|
|
2167
|
+
FROM zaip_aios_activities a WHERE payload_type='shared_creation_allocation';
|
|
2168
|
+
CREATE VIEW macro_spec_activities AS
|
|
2169
|
+
SELECT a.* FROM activities a WHERE a.spec_id IS NOT NULL
|
|
2170
|
+
AND NOT EXISTS (
|
|
2171
|
+
SELECT 1 FROM shared_creation_allocations allocation
|
|
2172
|
+
WHERE allocation.spec_id=a.spec_id AND allocation.run_id=a.run_id
|
|
2173
|
+
)
|
|
2174
|
+
UNION ALL SELECT * FROM shared_creation_allocations;
|
|
1909
2175
|
CREATE VIEW attributed_token_active AS
|
|
1910
2176
|
SELECT f.activity_row_id, f.occurred_day_sp, f.attributed_token_active_ms,
|
|
1911
2177
|
a.run_id, a.execution_id, a.task_id, a.spec_id, a.prd_id, a.conversation_id,
|
|
@@ -2041,7 +2307,10 @@ export async function rebuildSqliteReadModel(
|
|
|
2041
2307
|
SUM(sub_run_duration) AS total_sub_run_duration,
|
|
2042
2308
|
SUM(total_tokens) AS total_tokens,
|
|
2043
2309
|
SUM(cost_usd) AS total_cost_usd,
|
|
2044
|
-
SUM(cost_brl) AS total_cost_brl
|
|
2310
|
+
SUM(cost_brl) AS total_cost_brl,
|
|
2311
|
+
COUNT(total_tokens) AS token_known_runs,
|
|
2312
|
+
COUNT(cost_usd) AS usd_known_runs,
|
|
2313
|
+
COUNT(cost_brl) AS brl_known_runs
|
|
2045
2314
|
FROM activities
|
|
2046
2315
|
GROUP BY date(COALESCE(source_started_at, source_occurred_at, occurred_at), '-3 hours')
|
|
2047
2316
|
), cost_monitor_financial_daily AS (
|
|
@@ -2152,7 +2421,10 @@ export async function rebuildSqliteReadModel(
|
|
|
2152
2421
|
o.total_duration AS total_duration,
|
|
2153
2422
|
f.total_tokens,
|
|
2154
2423
|
f.total_cost_usd,
|
|
2155
|
-
f.total_cost_brl
|
|
2424
|
+
f.total_cost_brl,
|
|
2425
|
+
i.token_known_runs,
|
|
2426
|
+
i.usd_known_runs,
|
|
2427
|
+
i.brl_known_runs
|
|
2156
2428
|
FROM days d
|
|
2157
2429
|
LEFT JOIN work_item_daily w USING(day)
|
|
2158
2430
|
LEFT JOIN artifact_daily a USING(day)
|
|
@@ -2181,7 +2453,7 @@ export async function rebuildSqliteReadModel(
|
|
|
2181
2453
|
-- population rather than Operations' broader Run/activity totals.
|
|
2182
2454
|
CREATE VIEW cost_monitor_overview AS SELECT period_kind, period_key, cost_monitor_invocation_count AS total_run_count, cost_monitor_invocation_count AS invocation_count, llm_call_count, source_event_count, cost_monitor_total_duration, total_tokens, total_cost_usd, total_cost_brl FROM overview;
|
|
2183
2455
|
CREATE VIEW task_summary AS SELECT t.id, t.title, t.status, t.risk, t.occurred_at, t.occurred_day_sp, COUNT(i.run_id) AS invocation_count, SUM(i.llm_call_count) AS llm_call_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 tasks t LEFT JOIN activities i ON i.task_id = t.id GROUP BY t.id, t.title, t.status, t.risk, t.occurred_at, t.occurred_day_sp;
|
|
2184
|
-
CREATE VIEW spec_summary AS SELECT s.id, s.title, s.status, s.risk, s.occurred_at, s.occurred_day_sp, COUNT(i.run_id) AS invocation_count, SUM(i.llm_call_count) AS llm_call_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 specs s LEFT JOIN
|
|
2456
|
+
CREATE VIEW spec_summary AS SELECT s.id, s.title, s.status, s.risk, s.occurred_at, s.occurred_day_sp, COUNT(i.run_id) AS invocation_count, SUM(i.llm_call_count) AS llm_call_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 specs s LEFT JOIN macro_spec_activities i ON i.spec_id = s.id GROUP BY s.id, s.title, s.status, s.risk, s.occurred_at, s.occurred_day_sp;
|
|
2185
2457
|
CREATE VIEW prd_summary AS SELECT p.id, p.title, p.status, p.risk, p.occurred_at, p.occurred_day_sp, COUNT(i.run_id) AS invocation_count, SUM(i.llm_call_count) AS llm_call_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 prds p LEFT JOIN activities i ON i.prd_id = p.id GROUP BY p.id, p.title, p.status, p.risk, p.occurred_at, p.occurred_day_sp;
|
|
2186
2458
|
CREATE VIEW cost_monitor_group_daily AS SELECT * FROM cost_monitor_group_daily_cache;
|
|
2187
2459
|
CREATE VIEW vw_overview AS SELECT * FROM overview;
|
|
@@ -2196,7 +2468,10 @@ export async function rebuildSqliteReadModel(
|
|
|
2196
2468
|
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
2469
|
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
2470
|
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
|
-
|
|
2471
|
+
-- The Operations work-list export reads this public union directly.
|
|
2472
|
+
-- Keep the known aggregate facts from each entity summary so its
|
|
2473
|
+
-- snapshot is equivalent to the metrics shown by the work views.
|
|
2474
|
+
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
2475
|
CREATE VIEW vw_cost_group_daily AS SELECT * FROM cost_monitor_group_daily_cache;
|
|
2201
2476
|
-- Narrow temporal Cost surfaces. Do not route the visible Cost
|
|
2202
2477
|
-- tables through the three-grain overview union: it expands every
|
|
@@ -2302,7 +2577,7 @@ export async function rebuildSqliteReadModel(
|
|
|
2302
2577
|
CREATE VIEW vw_cost_models AS WITH usage AS (SELECT model, agent_invocation_id, SUM(total_tokens) AS total_tokens, SUM(cost_usd) AS cost_usd, SUM(cost_brl) AS cost_brl FROM invocation_usage GROUP BY model, agent_invocation_id), calls AS (SELECT i.agent_invocation_id, CASE WHEN (SELECT COUNT(DISTINCT m.model) FROM invocation_usage m WHERE m.agent_invocation_id = i.agent_invocation_id AND m.model IS NOT NULL) = 1 THEN i.llm_call_count END AS llm_call_count FROM invocations i) SELECT u.model, SUM(c.llm_call_count) AS llm_call_count, SUM(u.total_tokens) AS total_tokens, SUM(u.cost_usd) AS cost_usd, SUM(u.cost_brl) AS cost_brl, COUNT(*) AS invocation_count FROM usage u JOIN calls c USING(agent_invocation_id) GROUP BY u.model;
|
|
2303
2578
|
CREATE VIEW vw_cost_providers AS WITH usage AS (SELECT provider, agent_invocation_id, SUM(total_tokens) AS total_tokens, SUM(cost_usd) AS cost_usd, SUM(cost_brl) AS cost_brl FROM invocation_usage GROUP BY provider, agent_invocation_id), calls AS (SELECT i.agent_invocation_id, CASE WHEN (SELECT COUNT(DISTINCT p.provider) FROM invocation_usage p WHERE p.agent_invocation_id = i.agent_invocation_id AND p.provider IS NOT NULL) = 1 THEN i.llm_call_count END AS llm_call_count FROM invocations i) SELECT u.provider, SUM(c.llm_call_count) AS llm_call_count, SUM(u.total_tokens) AS total_tokens, SUM(u.cost_usd) AS cost_usd, SUM(u.cost_brl) AS cost_brl, COUNT(*) AS invocation_count FROM usage u JOIN calls c USING(agent_invocation_id) GROUP BY u.provider;
|
|
2304
2579
|
CREATE VIEW vw_cost_tasks AS SELECT i.task_id, COALESCE(MAX(t.title), i.task_id) AS title, MAX(t.status) AS status, MAX(t.risk) AS risk, MIN(i.occurred_at) AS occurred_at, MIN(i.occurred_day_sp) AS occurred_day_sp, SUM(i.llm_call_count) AS llm_call_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, COUNT(*) AS invocation_count FROM invocations i LEFT JOIN tasks t ON t.id = i.task_id WHERE i.task_id IS NOT NULL GROUP BY i.task_id;
|
|
2305
|
-
CREATE VIEW vw_cost_specs AS SELECT s.id AS spec_id, s.title, s.status, s.risk, COALESCE(MIN(i.occurred_at), s.occurred_at) AS occurred_at, COALESCE(MIN(i.occurred_day_sp), s.occurred_day_sp) AS occurred_day_sp, SUM(i.llm_call_count) AS llm_call_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, COUNT(i.
|
|
2580
|
+
CREATE VIEW vw_cost_specs AS SELECT s.id AS spec_id, s.title, s.status, s.risk, COALESCE(MIN(i.occurred_at), s.occurred_at) AS occurred_at, COALESCE(MIN(i.occurred_day_sp), s.occurred_day_sp) AS occurred_day_sp, SUM(i.llm_call_count) AS llm_call_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, COUNT(i.run_id) AS invocation_count FROM specs s LEFT JOIN macro_spec_activities i ON i.spec_id = s.id GROUP BY s.id, s.title, s.status, s.risk, s.occurred_at, s.occurred_day_sp;
|
|
2306
2581
|
CREATE VIEW vw_cost_prds AS SELECT p.id AS prd_id, p.title, p.status, p.risk, COALESCE(MIN(i.occurred_at), p.occurred_at) AS occurred_at, COALESCE(MIN(i.occurred_day_sp), p.occurred_day_sp) AS occurred_day_sp, SUM(i.llm_call_count) AS llm_call_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, COUNT(i.agent_invocation_id) AS invocation_count FROM prds p LEFT JOIN invocations i ON i.prd_id = p.id GROUP BY p.id, p.title, p.status, p.risk, p.occurred_at, p.occurred_day_sp;
|
|
2307
2582
|
CREATE VIEW vw_cost_conversations AS SELECT i.conversation_id, i.conversation_id AS title, MAX(c.status) AS status, NULL AS risk, MIN(i.occurred_at) AS occurred_at, MIN(i.occurred_day_sp) AS occurred_day_sp, SUM(i.llm_call_count) AS llm_call_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, COUNT(*) AS invocation_count FROM invocations i LEFT JOIN conversations c ON c.id = i.conversation_id WHERE i.conversation_id IS NOT NULL GROUP BY i.conversation_id;
|
|
2308
2583
|
CREATE VIEW vw_operations_work_list AS SELECT * FROM vw_entities;
|
|
@@ -2325,7 +2600,7 @@ export async function rebuildSqliteReadModel(
|
|
|
2325
2600
|
GROUP BY spec_id, prd_id
|
|
2326
2601
|
), entity_activity AS (
|
|
2327
2602
|
SELECT 'task' AS entity_kind, task_id AS entity_id, * FROM activities WHERE task_id IS NOT NULL
|
|
2328
|
-
UNION ALL SELECT 'spec', spec_id, * FROM
|
|
2603
|
+
UNION ALL SELECT 'spec', spec_id, * FROM macro_spec_activities
|
|
2329
2604
|
UNION ALL SELECT 'prd', prd_id, * FROM activities WHERE prd_id IS NOT NULL
|
|
2330
2605
|
UNION ALL
|
|
2331
2606
|
SELECT 'task', l.task_id, a.*
|
|
@@ -2564,7 +2839,7 @@ export async function rebuildSqliteReadModel(
|
|
|
2564
2839
|
SUM(total_tokens) AS total_tokens,
|
|
2565
2840
|
SUM(cost_usd) AS total_cost_usd,
|
|
2566
2841
|
SUM(cost_brl) AS total_cost_brl
|
|
2567
|
-
FROM
|
|
2842
|
+
FROM macro_spec_activities GROUP BY spec_id
|
|
2568
2843
|
)
|
|
2569
2844
|
SELECT COUNT(*) AS entity_count,
|
|
2570
2845
|
SUM(usage.total_run_count) AS total_run_count,
|
|
@@ -2609,7 +2884,7 @@ export async function rebuildSqliteReadModel(
|
|
|
2609
2884
|
SUM(total_tokens) AS total_tokens,
|
|
2610
2885
|
SUM(cost_usd) AS total_cost_usd,
|
|
2611
2886
|
SUM(cost_brl) AS total_cost_brl
|
|
2612
|
-
FROM
|
|
2887
|
+
FROM macro_spec_activities GROUP BY spec_id
|
|
2613
2888
|
), ranked AS (
|
|
2614
2889
|
SELECT s.id AS entity_id, s.title, s.status, usage.total_run_count, usage.llm_call_count,
|
|
2615
2890
|
usage.total_duration_ms, usage.total_tokens, usage.total_cost_usd, usage.total_cost_brl,
|
|
@@ -2740,7 +3015,72 @@ export async function rebuildSqliteReadModel(
|
|
|
2740
3015
|
},
|
|
2741
3016
|
);
|
|
2742
3017
|
database.exec(deduplicatedViewSql);
|
|
2743
|
-
|
|
3018
|
+
// One row per stable GitHub account. Costs are summed from the single
|
|
3019
|
+
// canonical Run fact, while delivery is joined exclusively through the
|
|
3020
|
+
// Task's completed_run_id (never inferred from a branch or latest Run).
|
|
3021
|
+
database.exec(`
|
|
3022
|
+
CREATE TABLE developer_cost_projection AS
|
|
3023
|
+
WITH completed AS (
|
|
3024
|
+
SELECT a.developer_account_id, COUNT(DISTINCT w.id) AS completed_task_count
|
|
3025
|
+
FROM zaip_vw_canonical_work_items w
|
|
3026
|
+
JOIN zaip_vw_activities a ON a.run_id=w.completed_run_id
|
|
3027
|
+
WHERE w.entity_kind='task' AND w.status='completed'
|
|
3028
|
+
AND a.developer_account_id IS NOT NULL
|
|
3029
|
+
GROUP BY a.developer_account_id
|
|
3030
|
+
)
|
|
3031
|
+
SELECT a.developer_account_id AS account_id,
|
|
3032
|
+
MAX(a.developer_login) AS login_snapshot,
|
|
3033
|
+
CASE WHEN COUNT(DISTINCT a.developer_attribution_source)=1 THEN MAX(a.developer_attribution_source) ELSE 'mixed' END AS attribution_source,
|
|
3034
|
+
COUNT(*) AS run_count,
|
|
3035
|
+
SUM(a.source_event_count) AS source_event_count,
|
|
3036
|
+
SUM(a.total_tokens) AS total_tokens,
|
|
3037
|
+
SUM(a.cost_usd) AS total_cost_usd,
|
|
3038
|
+
SUM(a.cost_brl) AS total_cost_brl,
|
|
3039
|
+
COUNT(a.total_tokens) AS token_covered_runs,
|
|
3040
|
+
COUNT(a.cost_usd) AS usd_covered_runs,
|
|
3041
|
+
COUNT(a.cost_brl) AS brl_covered_runs,
|
|
3042
|
+
COALESCE(completed.completed_task_count, 0) AS completed_task_count
|
|
3043
|
+
FROM zaip_vw_activities a LEFT JOIN completed USING(developer_account_id)
|
|
3044
|
+
WHERE a.developer_account_id IS NOT NULL
|
|
3045
|
+
GROUP BY a.developer_account_id
|
|
3046
|
+
ORDER BY total_cost_brl DESC, account_id;
|
|
3047
|
+
CREATE INDEX developer_cost_projection_lookup ON developer_cost_projection(account_id);
|
|
3048
|
+
`);
|
|
3049
|
+
materializeCostGroupDailyProjection(database);
|
|
3050
|
+
materializeOperationsWorkProjection(database);
|
|
3051
|
+
// Persist the complete unfiltered invocation projection while the seed
|
|
3052
|
+
// still owns the compact canonical facts. Runtime pages can then slice
|
|
3053
|
+
// 10/100 rows without expanding the pricing view for every request.
|
|
3054
|
+
const costAgentInvocationProjection = database
|
|
3055
|
+
.prepare(
|
|
3056
|
+
`SELECT * FROM zaip_vw_cost_monitor_agent_invocations
|
|
3057
|
+
ORDER BY occurred_at DESC, agent_invocation_id DESC`,
|
|
3058
|
+
)
|
|
3059
|
+
.all() as Array<Record<string, unknown>>;
|
|
3060
|
+
const insertCostAgentInvocationProjection = database.prepare(
|
|
3061
|
+
`INSERT INTO zaip_aios_activities(
|
|
3062
|
+
activity_row_id, activity_source_kind, activity_key, source_path, source_hash,
|
|
3063
|
+
recorded_at, occurred_day_sp, run_role, root_run_id, payload_type, payload_json
|
|
3064
|
+
) VALUES (?, 'technical_payload', ?, 'sqlite://cost_agent_invocation_projection_v1', '',
|
|
3065
|
+
datetime('now'), ?, 'technical', 'technical', 'cost_agent_invocation_projection_v1', ?)`,
|
|
3066
|
+
);
|
|
3067
|
+
database.exec("BEGIN;");
|
|
3068
|
+
for (const row of costAgentInvocationProjection) {
|
|
3069
|
+
const invocationId = String(row.agent_invocation_id ?? "");
|
|
3070
|
+
if (invocationId.length === 0) continue;
|
|
3071
|
+
insertCostAgentInvocationProjection.run(
|
|
3072
|
+
`TECH-${randomUUID().toLowerCase()}`,
|
|
3073
|
+
`cost_agent_invocation_projection_v1:${invocationId}`,
|
|
3074
|
+
typeof row.occurred_day_sp === "string" ? row.occurred_day_sp : null,
|
|
3075
|
+
JSON.stringify(row),
|
|
3076
|
+
);
|
|
3077
|
+
}
|
|
3078
|
+
database.exec("COMMIT;");
|
|
3079
|
+
reportLaunchProgress(
|
|
3080
|
+
"projection_finalize",
|
|
3081
|
+
80,
|
|
3082
|
+
"Finalizando índices e a cópia local do dashboard.",
|
|
3083
|
+
);
|
|
2744
3084
|
completedFiles = files;
|
|
2745
3085
|
} catch (error) {
|
|
2746
3086
|
database.exec("ROLLBACK;");
|
|
@@ -2749,58 +3089,92 @@ export async function rebuildSqliteReadModel(
|
|
|
2749
3089
|
} finally {
|
|
2750
3090
|
database.close();
|
|
2751
3091
|
}
|
|
2752
|
-
try {
|
|
2753
|
-
await rename(temporaryPath, cachePath);
|
|
2754
|
-
} catch (error: unknown) {
|
|
2755
|
-
// Another launcher may have built the same immutable cache entry while
|
|
2756
|
-
// this process was reading. It is safe only if its fingerprint agrees.
|
|
2757
|
-
if (readSnapshotFileCount(cachePath, fingerprint) === null) {
|
|
2758
|
-
await rm(temporaryPath, { force: true });
|
|
2759
|
-
throw error;
|
|
2760
|
-
}
|
|
2761
|
-
await rm(temporaryPath, { force: true });
|
|
2762
|
-
}
|
|
2763
3092
|
const materializeStartedAt = performance.now();
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
3093
|
+
const timed = <T>(phase: string, action: () => T) =>
|
|
3094
|
+
measureFirstFold(
|
|
3095
|
+
process.env.ZAIP_DASHBOARD_LAUNCH_PROGRESS === "1",
|
|
3096
|
+
reportLaunchProgress,
|
|
3097
|
+
phase,
|
|
3098
|
+
action,
|
|
3099
|
+
);
|
|
3100
|
+
// The shared cache remains private until every expensive first-fold payload
|
|
3101
|
+
// has been committed. A concurrent launcher may freely use `path`, its own
|
|
3102
|
+
// generation, but must never copy a half-published cache seed.
|
|
3103
|
+
await materializeSnapshot(temporaryPath, path);
|
|
2768
3104
|
const firstFoldFilesystem = new SqliteReadModelFileSystem(workspaceRoot);
|
|
2769
|
-
const firstFoldViewport =
|
|
3105
|
+
const firstFoldViewport = timed("first_fold_viewport", () =>
|
|
3106
|
+
firstFoldFilesystem.firstFoldViewportAllTime(),
|
|
3107
|
+
);
|
|
3108
|
+
const firstFoldAllTime = timed("first_fold_all_time", () =>
|
|
3109
|
+
firstFoldFilesystem.firstFoldAllTime(),
|
|
3110
|
+
);
|
|
3111
|
+
const firstFoldAllTimeBoard = timed("first_fold_board", () =>
|
|
3112
|
+
firstFoldFilesystem.firstFoldBoardAllTime(),
|
|
3113
|
+
);
|
|
3114
|
+
const costMonitorSummaryAllTime = timed("first_fold_cost_summary", () =>
|
|
3115
|
+
firstFoldFilesystem.costMonitorSummaryAggregate({ from: null, to: null }),
|
|
3116
|
+
);
|
|
2770
3117
|
const quickMonth = saoPauloCurrentMonth();
|
|
3118
|
+
const costMonitorSummaryCurrentMonth = timed("first_fold_cost_summary_month", () =>
|
|
3119
|
+
firstFoldFilesystem.costMonitorSummaryAggregate(quickMonth),
|
|
3120
|
+
);
|
|
2771
3121
|
const firstFoldMonthlyQuick: SqliteFirstFoldMonthlyQuick = {
|
|
2772
3122
|
from_day: quickMonth.from,
|
|
2773
3123
|
to_day: quickMonth.to,
|
|
2774
|
-
source_latest_day:
|
|
2775
|
-
|
|
3124
|
+
source_latest_day: timed("first_fold_latest_day", () =>
|
|
3125
|
+
firstFoldFilesystem.firstFoldRelevantLatestDay(),
|
|
3126
|
+
),
|
|
3127
|
+
aggregate: timed("first_fold_month_aggregate", () =>
|
|
3128
|
+
firstFoldFilesystem.firstFoldAggregate(quickMonth),
|
|
3129
|
+
),
|
|
2776
3130
|
pages: {
|
|
2777
|
-
task:
|
|
2778
|
-
|
|
2779
|
-
|
|
3131
|
+
task: operationsWorkRowsInRange(
|
|
3132
|
+
firstFoldAllTimeBoard.pages.task,
|
|
3133
|
+
quickMonth.from,
|
|
3134
|
+
quickMonth.to,
|
|
3135
|
+
),
|
|
3136
|
+
spec: operationsWorkRowsInRange(
|
|
3137
|
+
firstFoldAllTimeBoard.pages.spec,
|
|
3138
|
+
quickMonth.from,
|
|
3139
|
+
quickMonth.to,
|
|
3140
|
+
),
|
|
3141
|
+
prd: operationsWorkRowsInRange(
|
|
3142
|
+
firstFoldAllTimeBoard.pages.prd,
|
|
3143
|
+
quickMonth.from,
|
|
3144
|
+
quickMonth.to,
|
|
3145
|
+
),
|
|
2780
3146
|
},
|
|
2781
|
-
top_tasks: firstFoldFilesystem.topTasks(quickMonth),
|
|
3147
|
+
top_tasks: timed("first_fold_month_top_tasks", () => firstFoldFilesystem.topTasks(quickMonth)),
|
|
2782
3148
|
};
|
|
2783
3149
|
firstFoldFilesystem.close();
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
3150
|
+
persistFirstFoldPayloads([temporaryPath, path], {
|
|
3151
|
+
first_fold_viewport_cache: JSON.stringify(firstFoldViewport),
|
|
3152
|
+
first_fold_all_time_cache: JSON.stringify(firstFoldAllTime),
|
|
3153
|
+
first_fold_all_time_board_cache: JSON.stringify(firstFoldAllTimeBoard),
|
|
3154
|
+
cost_monitor_summary_cache: JSON.stringify(costMonitorSummaryAllTime),
|
|
3155
|
+
cost_monitor_summary_month_cache: JSON.stringify({
|
|
3156
|
+
...quickMonth,
|
|
3157
|
+
aggregate: costMonitorSummaryCurrentMonth,
|
|
3158
|
+
}),
|
|
3159
|
+
first_fold_monthly_quick_cache: JSON.stringify(firstFoldMonthlyQuick),
|
|
3160
|
+
});
|
|
3161
|
+
try {
|
|
3162
|
+
await rename(temporaryPath, cachePath);
|
|
3163
|
+
} catch (error: unknown) {
|
|
3164
|
+
// A concurrent builder wins only when it published a complete seed.
|
|
3165
|
+
if (
|
|
3166
|
+
readSnapshotFileCount(
|
|
3167
|
+
cachePath,
|
|
3168
|
+
fingerprint,
|
|
3169
|
+
readModelSchemaVersion,
|
|
3170
|
+
hasRequiredFirstFoldPayloads,
|
|
3171
|
+
) === null
|
|
3172
|
+
) {
|
|
3173
|
+
await rm(temporaryPath, { force: true });
|
|
3174
|
+
throw error;
|
|
3175
|
+
}
|
|
3176
|
+
await rm(temporaryPath, { force: true });
|
|
3177
|
+
await materializeSnapshot(cachePath, path);
|
|
2804
3178
|
}
|
|
2805
3179
|
reportLaunchProgress("snapshot_ready", 86, "A cópia privada do banco local está pronta.");
|
|
2806
3180
|
validateSqliteReadModelSchema(path);
|
|
@@ -2814,153 +3188,6 @@ export async function rebuildSqliteReadModel(
|
|
|
2814
3188
|
};
|
|
2815
3189
|
}
|
|
2816
3190
|
|
|
2817
|
-
async function pruneSnapshotCache(directory: string, activePath: string): Promise<void> {
|
|
2818
|
-
const entries = await readdir(directory, { withFileTypes: true }).catch(() => []);
|
|
2819
|
-
const stale = await Promise.all(
|
|
2820
|
-
entries
|
|
2821
|
-
.filter(
|
|
2822
|
-
(entry) =>
|
|
2823
|
-
entry.isFile() &&
|
|
2824
|
-
entry.name.endsWith(".sqlite") &&
|
|
2825
|
-
resolve(directory, entry.name) !== activePath,
|
|
2826
|
-
)
|
|
2827
|
-
.map(async (entry) => ({
|
|
2828
|
-
path: resolve(directory, entry.name),
|
|
2829
|
-
mtime: (await stat(resolve(directory, entry.name))).mtimeMs,
|
|
2830
|
-
})),
|
|
2831
|
-
);
|
|
2832
|
-
// Keep the most recent prior seed for a concurrent launcher; never remove tmp
|
|
2833
|
-
// files, which may be an in-progress materialization by another process.
|
|
2834
|
-
for (const entry of stale.sort((a, b) => b.mtime - a.mtime).slice(1))
|
|
2835
|
-
await rm(entry.path, { force: true }).catch(() => {});
|
|
2836
|
-
// Interrupted SQLite builds leave only cache-local temporary files. They
|
|
2837
|
-
// cannot be valid immutable seeds and otherwise inflate the launch folder.
|
|
2838
|
-
for (const entry of entries) {
|
|
2839
|
-
if (!entry.isFile() || !entry.name.includes(".sqlite.") || !entry.name.includes(".tmp")) continue;
|
|
2840
|
-
const candidate = resolve(directory, entry.name);
|
|
2841
|
-
const age = Date.now() - (await stat(candidate)).mtimeMs;
|
|
2842
|
-
if (age > 60_000) await rm(candidate, { force: true }).catch(() => {});
|
|
2843
|
-
}
|
|
2844
|
-
}
|
|
2845
|
-
|
|
2846
|
-
function readSnapshotFileCount(path: string, fingerprint: string): number | null {
|
|
2847
|
-
try {
|
|
2848
|
-
const database = new DatabaseSync(path, { readOnly: true });
|
|
2849
|
-
const previous = (
|
|
2850
|
-
database
|
|
2851
|
-
.prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key = ?")
|
|
2852
|
-
.get("source_fingerprint") as { value?: string } | undefined
|
|
2853
|
-
)?.value;
|
|
2854
|
-
const schema = (
|
|
2855
|
-
database
|
|
2856
|
-
.prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key = ?")
|
|
2857
|
-
.get("schema_version") as { value?: string } | undefined
|
|
2858
|
-
)?.value;
|
|
2859
|
-
const files = Number(
|
|
2860
|
-
(
|
|
2861
|
-
database
|
|
2862
|
-
.prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key = ?")
|
|
2863
|
-
.get("source_file_count") as { value?: string } | undefined
|
|
2864
|
-
)?.value ?? 0,
|
|
2865
|
-
);
|
|
2866
|
-
database.close();
|
|
2867
|
-
return previous === fingerprint && schema === readModelSchemaVersion ? files : null;
|
|
2868
|
-
} catch {
|
|
2869
|
-
return null;
|
|
2870
|
-
}
|
|
2871
|
-
}
|
|
2872
|
-
|
|
2873
|
-
async function mostRecentCompatibleSnapshot(directory: string): Promise<{ path: string; files: number } | null> {
|
|
2874
|
-
const entries = await readdir(directory, { withFileTypes: true }).catch(() => []);
|
|
2875
|
-
const candidates = await Promise.all(entries.filter((entry) => entry.isFile() && entry.name.endsWith(".sqlite"))
|
|
2876
|
-
.map(async (entry) => ({ path: join(directory, entry.name), mtime: (await stat(join(directory, entry.name))).mtimeMs })));
|
|
2877
|
-
for (const candidate of candidates.sort((left, right) => right.mtime - left.mtime)) {
|
|
2878
|
-
const files = readCompatibleSnapshotFileCount(candidate.path);
|
|
2879
|
-
if (files !== null) return { path: candidate.path, files };
|
|
2880
|
-
}
|
|
2881
|
-
return null;
|
|
2882
|
-
}
|
|
2883
|
-
|
|
2884
|
-
function readCompatibleSnapshotFileCount(path: string): number | null {
|
|
2885
|
-
try {
|
|
2886
|
-
const database = new DatabaseSync(path, { readOnly: true });
|
|
2887
|
-
const schema = (database.prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key = ?").get("schema_version") as { value?: string } | undefined)?.value;
|
|
2888
|
-
const files = Number((database.prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key = ?").get("source_file_count") as { value?: string } | undefined)?.value ?? 0);
|
|
2889
|
-
database.close();
|
|
2890
|
-
return schema === readModelSchemaVersion ? files : null;
|
|
2891
|
-
} catch { return null; }
|
|
2892
|
-
}
|
|
2893
|
-
|
|
2894
|
-
/**
|
|
2895
|
-
* The persisted schema exposes only zaip_vw_* names. The local runtime keeps
|
|
2896
|
-
* concise historical names as connection-local TEMP aliases while callers are
|
|
2897
|
-
* migrated; they are never written to the database file.
|
|
2898
|
-
*/
|
|
2899
|
-
function installLegacyViewAliases(database: DatabaseSync): void {
|
|
2900
|
-
const views = database
|
|
2901
|
-
.prepare("SELECT name FROM sqlite_master WHERE type = 'view' AND name LIKE 'zaip_vw_%'")
|
|
2902
|
-
.all() as Array<{ name: string }>;
|
|
2903
|
-
for (const { name } of views) {
|
|
2904
|
-
const alias = name.slice("zaip_vw_".length);
|
|
2905
|
-
const quoted = (identifier: string) => `"${identifier.replaceAll('"', '""')}"`;
|
|
2906
|
-
database.exec(`CREATE TEMP VIEW ${quoted(alias)} AS SELECT * FROM main.${quoted(name)}`);
|
|
2907
|
-
}
|
|
2908
|
-
}
|
|
2909
|
-
|
|
2910
|
-
async function materializeSnapshot(source: string, destination: string): Promise<void> {
|
|
2911
|
-
await mkdir(dirname(destination), { recursive: true });
|
|
2912
|
-
const temporaryPath = `${destination}.${process.pid}.${randomUUID()}.tmp`;
|
|
2913
|
-
await copyFile(source, temporaryPath);
|
|
2914
|
-
try {
|
|
2915
|
-
await rename(temporaryPath, destination);
|
|
2916
|
-
} catch (error) {
|
|
2917
|
-
await rm(temporaryPath, { force: true });
|
|
2918
|
-
throw error;
|
|
2919
|
-
}
|
|
2920
|
-
}
|
|
2921
|
-
|
|
2922
|
-
export type SqliteReadModelSourceCandidate = {
|
|
2923
|
-
path: string;
|
|
2924
|
-
source: string;
|
|
2925
|
-
mtimeMs: number;
|
|
2926
|
-
size: number;
|
|
2927
|
-
};
|
|
2928
|
-
|
|
2929
|
-
export async function sourceFingerprint(workspaceRoot: string): Promise<{
|
|
2930
|
-
fingerprint: string;
|
|
2931
|
-
candidates: SqliteReadModelSourceCandidate[];
|
|
2932
|
-
}> {
|
|
2933
|
-
const candidates = (
|
|
2934
|
-
await Promise.all(
|
|
2935
|
-
[".agents/records", ".agents/config"].map((root) => listFiles(resolve(workspaceRoot, root))),
|
|
2936
|
-
)
|
|
2937
|
-
)
|
|
2938
|
-
.flat()
|
|
2939
|
-
.filter(
|
|
2940
|
-
(path) =>
|
|
2941
|
-
textExtensions.has(path.slice(path.lastIndexOf(".")).toLowerCase()) &&
|
|
2942
|
-
isDashboardCanonicalSource(relative(workspaceRoot, path).replaceAll("\\", "/")),
|
|
2943
|
-
)
|
|
2944
|
-
.sort();
|
|
2945
|
-
const candidatesWithMetadata = await Promise.all(
|
|
2946
|
-
candidates.map(async (candidate) => {
|
|
2947
|
-
const metadata = await stat(candidate);
|
|
2948
|
-
return {
|
|
2949
|
-
path: candidate,
|
|
2950
|
-
source: relative(workspaceRoot, candidate).replaceAll("\\", "/"),
|
|
2951
|
-
mtimeMs: Math.trunc(metadata.mtimeMs),
|
|
2952
|
-
size: metadata.size,
|
|
2953
|
-
};
|
|
2954
|
-
}),
|
|
2955
|
-
);
|
|
2956
|
-
return {
|
|
2957
|
-
fingerprint: candidatesWithMetadata
|
|
2958
|
-
.map((candidate) => `${candidate.source}:${candidate.mtimeMs}:${candidate.size}`)
|
|
2959
|
-
.join("|"),
|
|
2960
|
-
candidates: candidatesWithMetadata,
|
|
2961
|
-
};
|
|
2962
|
-
}
|
|
2963
|
-
|
|
2964
3191
|
export type SqliteFirstFoldAggregate = {
|
|
2965
3192
|
days: number;
|
|
2966
3193
|
total_run_count: number;
|
|
@@ -2971,72 +3198,17 @@ export type SqliteFirstFoldAggregate = {
|
|
|
2971
3198
|
cost_brl: number | null;
|
|
2972
3199
|
execution_day_count: number;
|
|
2973
3200
|
};
|
|
3201
|
+
export type {
|
|
3202
|
+
SqliteFirstFoldAllTime,
|
|
3203
|
+
SqliteFirstFoldAllTimeBoard,
|
|
3204
|
+
SqliteFirstFoldMonthlyQuick,
|
|
3205
|
+
} from "./sqlite-read-model-first-fold-types.js";
|
|
2974
3206
|
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
completed_task_count: number;
|
|
2981
|
-
spec_count: number;
|
|
2982
|
-
completed_spec_count: number;
|
|
2983
|
-
prd_count: number;
|
|
2984
|
-
completed_prd_count: number;
|
|
2985
|
-
total_cost_usd: number | null;
|
|
2986
|
-
latest_task_created_at: string | null;
|
|
2987
|
-
};
|
|
2988
|
-
task_status_counts: Record<string, number>;
|
|
2989
|
-
task_risk_counts: Record<string, number>;
|
|
2990
|
-
task_metrics: {
|
|
2991
|
-
average_duration_ms_per_task: number | null;
|
|
2992
|
-
average_tokens_per_task: number | null;
|
|
2993
|
-
average_cost_usd_per_task: number | null;
|
|
2994
|
-
average_cost_brl_per_task: number | null;
|
|
2995
|
-
latest_task_created_at: string | null;
|
|
2996
|
-
};
|
|
2997
|
-
top_tasks: Array<{
|
|
2998
|
-
rank: number;
|
|
2999
|
-
task_id: string;
|
|
3000
|
-
title: string;
|
|
3001
|
-
status: string | null;
|
|
3002
|
-
risk: string | null;
|
|
3003
|
-
total_run_count: number;
|
|
3004
|
-
llm_call_count: number | null;
|
|
3005
|
-
total_duration_ms: number;
|
|
3006
|
-
total_tokens: number | null;
|
|
3007
|
-
total_cost_usd: number | null;
|
|
3008
|
-
total_cost_brl: number | null;
|
|
3009
|
-
duration_share: number;
|
|
3010
|
-
}>;
|
|
3011
|
-
macro_metrics: Record<"specs" | "prds", {
|
|
3012
|
-
status_counts: Record<string, number>;
|
|
3013
|
-
classification_counts: Record<string, number>;
|
|
3014
|
-
entity_count: number;
|
|
3015
|
-
total_run_count: number;
|
|
3016
|
-
total_duration_ms: number | null;
|
|
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;
|
|
3030
|
-
total_run_count: number;
|
|
3031
|
-
llm_call_count: number | null;
|
|
3032
|
-
total_duration_ms: number | null;
|
|
3033
|
-
total_tokens: number | null;
|
|
3034
|
-
total_cost_usd: number | null;
|
|
3035
|
-
total_cost_brl: number | null;
|
|
3036
|
-
duration_share: number;
|
|
3037
|
-
}>;
|
|
3038
|
-
}>;
|
|
3039
|
-
};
|
|
3207
|
+
import type {
|
|
3208
|
+
SqliteFirstFoldAllTime,
|
|
3209
|
+
SqliteFirstFoldAllTimeBoard,
|
|
3210
|
+
SqliteFirstFoldMonthlyQuick,
|
|
3211
|
+
} from "./sqlite-read-model-first-fold-types.js";
|
|
3040
3212
|
|
|
3041
3213
|
/** The only all-time facts needed before the Operations first fold is painted. */
|
|
3042
3214
|
export type SqliteFirstFoldViewport = Pick<
|
|
@@ -3112,11 +3284,19 @@ export const sqliteViewMetadata = {
|
|
|
3112
3284
|
first_fold_task_metrics_all_time: { date: null, id: null, order: "task_count DESC" },
|
|
3113
3285
|
first_fold_top_tasks_all_time: { date: null, id: "task_id", order: "rank" },
|
|
3114
3286
|
first_fold_spec_status_all_time: { date: null, id: "stage", order: "stage" },
|
|
3115
|
-
first_fold_spec_classification_all_time: {
|
|
3287
|
+
first_fold_spec_classification_all_time: {
|
|
3288
|
+
date: null,
|
|
3289
|
+
id: "classification",
|
|
3290
|
+
order: "classification",
|
|
3291
|
+
},
|
|
3116
3292
|
first_fold_spec_metrics_all_time: { date: null, id: null, order: "entity_count DESC" },
|
|
3117
3293
|
first_fold_top_specs_all_time: { date: null, id: "entity_id", order: "rank" },
|
|
3118
3294
|
first_fold_prd_status_all_time: { date: null, id: "stage", order: "stage" },
|
|
3119
|
-
first_fold_prd_classification_all_time: {
|
|
3295
|
+
first_fold_prd_classification_all_time: {
|
|
3296
|
+
date: null,
|
|
3297
|
+
id: "classification",
|
|
3298
|
+
order: "classification",
|
|
3299
|
+
},
|
|
3120
3300
|
first_fold_prd_metrics_all_time: { date: null, id: null, order: "entity_count DESC" },
|
|
3121
3301
|
first_fold_top_prds_all_time: { date: null, id: "entity_id", order: "rank" },
|
|
3122
3302
|
first_fold_task: { date: "occurred_day_sp", id: "id", order: "occurred_at DESC, id DESC" },
|
|
@@ -3142,51 +3322,24 @@ export type SqliteSummaryPage = {
|
|
|
3142
3322
|
* immutable seed is built, rather than competing with the browser after the
|
|
3143
3323
|
* launcher reports ready.
|
|
3144
3324
|
*/
|
|
3145
|
-
export type SqliteFirstFoldMonthlyQuick = {
|
|
3146
|
-
from_day: string;
|
|
3147
|
-
to_day: string;
|
|
3148
|
-
source_latest_day: string | null;
|
|
3149
|
-
aggregate: SqliteFirstFoldAggregate;
|
|
3150
|
-
pages: Record<SqliteSummaryKind, Array<Record<string, unknown>>>;
|
|
3151
|
-
top_tasks: Array<{
|
|
3152
|
-
task_id: string;
|
|
3153
|
-
total_tokens: number | null;
|
|
3154
|
-
cost_brl: number | null;
|
|
3155
|
-
run_count: number;
|
|
3156
|
-
duration_ms: number;
|
|
3157
|
-
}>;
|
|
3158
|
-
};
|
|
3159
|
-
|
|
3160
|
-
async function listFiles(directory: string): Promise<string[]> {
|
|
3161
|
-
const entries = await readdir(directory, { withFileTypes: true }).catch(() => []);
|
|
3162
|
-
const paths = await Promise.all(
|
|
3163
|
-
entries.map(async (entry) => {
|
|
3164
|
-
const candidate = join(directory, entry.name);
|
|
3165
|
-
return entry.isDirectory() ? listFiles(candidate) : entry.isFile() ? [candidate] : [];
|
|
3166
|
-
}),
|
|
3167
|
-
);
|
|
3168
|
-
return paths.flat();
|
|
3169
|
-
}
|
|
3170
|
-
|
|
3171
3325
|
/** Read-only filesystem backed by the regenerated SQLite snapshot. */
|
|
3172
3326
|
export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
3173
3327
|
private readonly executionDurationCache = new Map<string, Map<string, number>>();
|
|
3174
3328
|
private dailyAggregateRowsCache: Array<Record<string, number | string | null>> | null = null;
|
|
3175
3329
|
private firstFoldAllTimeCache: SqliteFirstFoldAllTime | null = null;
|
|
3330
|
+
private firstFoldAllTimeBoardCache: SqliteFirstFoldAllTimeBoard | null = null;
|
|
3176
3331
|
private firstFoldTopTasksAllTimeCache: SqliteFirstFoldAllTime["top_tasks"] | null = null;
|
|
3177
3332
|
private firstFoldViewportCache: SqliteFirstFoldViewport | null = null;
|
|
3178
3333
|
private firstFoldMonthlyQuickCache: SqliteFirstFoldMonthlyQuick | null | undefined = undefined;
|
|
3179
3334
|
private firstFoldViewportTimings: Record<string, number> = {};
|
|
3180
3335
|
private metadataValues: Map<string, string> | null = null;
|
|
3181
3336
|
private readonly unfilteredViewPageCache = new Map<string, Array<Record<string, unknown>>>();
|
|
3337
|
+
private readonly technicalPayloadRowsCache = new Map<string, Array<Record<string, unknown>>>();
|
|
3182
3338
|
private readonly viewPageResultCache = new Map<string, Record<string, unknown>>();
|
|
3183
|
-
//
|
|
3184
|
-
//
|
|
3185
|
-
//
|
|
3186
|
-
|
|
3187
|
-
// view and all filtered paths remain the compatibility seam.
|
|
3188
|
-
private operationsWorkListPageCache: Array<Record<string, unknown>> | null = null;
|
|
3189
|
-
|
|
3339
|
+
// Summary cards and temporal Cost tables consume the same aggregate. Keep
|
|
3340
|
+
// exact filter results inside the immutable generation so a tab switch does
|
|
3341
|
+
// not repeat its multi-second SQL scan.
|
|
3342
|
+
private readonly costMonitorSummaryCache = new Map<string, CostSummarySource>();
|
|
3190
3343
|
private dailyAggregateRows(): Array<Record<string, number | string | null>> {
|
|
3191
3344
|
if (this.dailyAggregateRowsCache === null) {
|
|
3192
3345
|
this.dailyAggregateRowsCache = this.database
|
|
@@ -3196,23 +3349,37 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3196
3349
|
return this.dailyAggregateRowsCache;
|
|
3197
3350
|
}
|
|
3198
3351
|
|
|
3199
|
-
private operationsWorkListRows(): Array<Record<string, unknown>> {
|
|
3200
|
-
this.operationsWorkListPageCache ??= this.database
|
|
3201
|
-
.prepare("SELECT * FROM operations_work_list ORDER BY occurred_at DESC, id DESC")
|
|
3202
|
-
.all() as Array<Record<string, unknown>>;
|
|
3203
|
-
return this.operationsWorkListPageCache;
|
|
3204
|
-
}
|
|
3205
|
-
|
|
3206
3352
|
private unfilteredViewRows(view: SqliteViewName): Array<Record<string, unknown>> {
|
|
3207
3353
|
const cached = this.unfilteredViewPageCache.get(view);
|
|
3208
3354
|
if (cached !== undefined) return cached;
|
|
3209
3355
|
const order = sqliteViewMetadata[view].order;
|
|
3210
|
-
const rows = this.database
|
|
3211
|
-
|
|
3212
|
-
|
|
3356
|
+
const rows = this.database.prepare(`SELECT * FROM ${view} ORDER BY ${order}`).all() as Array<
|
|
3357
|
+
Record<string, unknown>
|
|
3358
|
+
>;
|
|
3213
3359
|
this.unfilteredViewPageCache.set(view, rows);
|
|
3214
3360
|
return rows;
|
|
3215
3361
|
}
|
|
3362
|
+
|
|
3363
|
+
private technicalPayloadRows(payloadType: string): Array<Record<string, unknown>> {
|
|
3364
|
+
const cached = this.technicalPayloadRowsCache.get(payloadType);
|
|
3365
|
+
if (cached !== undefined) return cached;
|
|
3366
|
+
const rows = (
|
|
3367
|
+
this.database
|
|
3368
|
+
.prepare("SELECT payload_json FROM zaip_aios_activities WHERE payload_type=?")
|
|
3369
|
+
.all(payloadType) as Array<{ payload_json: string }>
|
|
3370
|
+
).flatMap(({ payload_json }) => {
|
|
3371
|
+
try {
|
|
3372
|
+
const parsed = JSON.parse(payload_json) as unknown;
|
|
3373
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)
|
|
3374
|
+
? [parsed as Record<string, unknown>]
|
|
3375
|
+
: [];
|
|
3376
|
+
} catch {
|
|
3377
|
+
return [];
|
|
3378
|
+
}
|
|
3379
|
+
});
|
|
3380
|
+
this.technicalPayloadRowsCache.set(payloadType, rows);
|
|
3381
|
+
return rows;
|
|
3382
|
+
}
|
|
3216
3383
|
private database: DatabaseSync;
|
|
3217
3384
|
|
|
3218
3385
|
constructor(root: string) {
|
|
@@ -3287,6 +3454,7 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3287
3454
|
this.executionDurationCache.clear();
|
|
3288
3455
|
this.dailyAggregateRowsCache = null;
|
|
3289
3456
|
this.firstFoldAllTimeCache = null;
|
|
3457
|
+
this.firstFoldAllTimeBoardCache = null;
|
|
3290
3458
|
this.firstFoldTopTasksAllTimeCache = null;
|
|
3291
3459
|
this.firstFoldViewportCache = null;
|
|
3292
3460
|
this.firstFoldMonthlyQuickCache = undefined;
|
|
@@ -3295,6 +3463,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3295
3463
|
// Public pages belong to the opened immutable SQLite generation. Never
|
|
3296
3464
|
// carry them across refresh/rebuild, even when this filesystem instance is
|
|
3297
3465
|
// reused by the launcher.
|
|
3466
|
+
this.unfilteredViewPageCache.clear();
|
|
3467
|
+
this.technicalPayloadRowsCache.clear();
|
|
3468
|
+
this.costMonitorSummaryCache.clear();
|
|
3298
3469
|
this.viewPageResultCache.clear();
|
|
3299
3470
|
}
|
|
3300
3471
|
|
|
@@ -3353,12 +3524,16 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3353
3524
|
const providerValues = (filter.providers ?? []).filter((value) => value !== "unavailable");
|
|
3354
3525
|
const modelValues = (filter.models ?? []).filter((value) => value !== "unavailable");
|
|
3355
3526
|
const requestsUnavailable =
|
|
3356
|
-
filter.providers?.includes("unavailable") === true ||
|
|
3527
|
+
filter.providers?.includes("unavailable") === true ||
|
|
3528
|
+
filter.models?.includes("unavailable") === true;
|
|
3357
3529
|
if (requestsUnavailable) {
|
|
3358
3530
|
const unavailableDimension = (column: "provider" | "model", selected?: readonly string[]) => {
|
|
3359
3531
|
if (selected === undefined || selected.length === 0) return;
|
|
3360
3532
|
const available = selected.filter((value) => value !== "unavailable");
|
|
3361
|
-
const matches =
|
|
3533
|
+
const matches =
|
|
3534
|
+
available.length === 0
|
|
3535
|
+
? "0"
|
|
3536
|
+
: `EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id=a.agent_run_id AND u.${column} IN (${available.map(() => "?").join(",")}))`;
|
|
3362
3537
|
if (available.length > 0) values.push(...available);
|
|
3363
3538
|
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
3539
|
clauses.push(selected.includes("unavailable") ? `(${matches} OR ${missing})` : matches);
|
|
@@ -3377,7 +3552,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3377
3552
|
}
|
|
3378
3553
|
// Provider and model describe one observed usage segment. Independent
|
|
3379
3554
|
// EXISTS clauses would let a crossed provider/model pair match a mixed Run.
|
|
3380
|
-
clauses.push(
|
|
3555
|
+
clauses.push(
|
|
3556
|
+
`EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id=a.agent_run_id AND ${segmentClauses.join(" AND ")})`,
|
|
3557
|
+
);
|
|
3381
3558
|
}
|
|
3382
3559
|
const dimensions = [
|
|
3383
3560
|
["zaip_agent", filter.agents],
|
|
@@ -3415,12 +3592,16 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3415
3592
|
const providerValues = (filter.providers ?? []).filter((value) => value !== "unavailable");
|
|
3416
3593
|
const modelValues = (filter.models ?? []).filter((value) => value !== "unavailable");
|
|
3417
3594
|
const requestsUnavailable =
|
|
3418
|
-
filter.providers?.includes("unavailable") === true ||
|
|
3595
|
+
filter.providers?.includes("unavailable") === true ||
|
|
3596
|
+
filter.models?.includes("unavailable") === true;
|
|
3419
3597
|
if (requestsUnavailable) {
|
|
3420
3598
|
const unavailableDimension = (column: "provider" | "model", selected?: readonly string[]) => {
|
|
3421
3599
|
if (selected === undefined || selected.length === 0) return;
|
|
3422
3600
|
const available = selected.filter((value) => value !== "unavailable");
|
|
3423
|
-
const matches =
|
|
3601
|
+
const matches =
|
|
3602
|
+
available.length === 0
|
|
3603
|
+
? "0"
|
|
3604
|
+
: `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
3605
|
if (available.length > 0) values.push(...available);
|
|
3425
3606
|
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
3607
|
clauses.push(selected.includes("unavailable") ? `(${matches} OR ${missing})` : matches);
|
|
@@ -3437,7 +3618,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3437
3618
|
segmentClauses.push(`u.model IN (${modelValues.map(() => "?").join(",")})`);
|
|
3438
3619
|
values.push(...modelValues);
|
|
3439
3620
|
}
|
|
3440
|
-
clauses.push(
|
|
3621
|
+
clauses.push(
|
|
3622
|
+
`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 ")})`,
|
|
3623
|
+
);
|
|
3441
3624
|
}
|
|
3442
3625
|
const dimensions = [
|
|
3443
3626
|
["zaip_agent", filter.agents],
|
|
@@ -3450,7 +3633,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3450
3633
|
values.push(...selected);
|
|
3451
3634
|
}
|
|
3452
3635
|
const rows = this.database
|
|
3453
|
-
.prepare(
|
|
3636
|
+
.prepare(
|
|
3637
|
+
`SELECT occurred_day_sp AS day, COUNT(*) AS count FROM canonical_run_index WHERE ${clauses.join(" AND ")} GROUP BY occurred_day_sp`,
|
|
3638
|
+
)
|
|
3454
3639
|
.all(...values) as Array<{ day: string; count: number }>;
|
|
3455
3640
|
return new Map(rows.map((row) => [row.day, Number(row.count)]));
|
|
3456
3641
|
}
|
|
@@ -3469,33 +3654,36 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3469
3654
|
days.map((day) => {
|
|
3470
3655
|
const row = rowsByDay.get(day) ?? {};
|
|
3471
3656
|
const totalRuns = Number(row.total_run_count ?? 0);
|
|
3472
|
-
const
|
|
3473
|
-
const
|
|
3657
|
+
const knownTokenRuns = Number(row.token_known_runs ?? 0);
|
|
3658
|
+
const knownCostRuns = Math.min(
|
|
3659
|
+
Number(row.usd_known_runs ?? 0),
|
|
3660
|
+
Number(row.brl_known_runs ?? 0),
|
|
3661
|
+
);
|
|
3474
3662
|
return [
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3663
|
+
day,
|
|
3664
|
+
{
|
|
3665
|
+
prds: Number(row.prd_count ?? 0),
|
|
3666
|
+
specs: Number(row.spec_count ?? 0),
|
|
3667
|
+
tasks: Number(row.task_count ?? 0),
|
|
3668
|
+
validations: Number(row.validation_count ?? 0),
|
|
3669
|
+
evidences: Number(row.evidence_count ?? 0),
|
|
3670
|
+
runs: Number(row.total_run_count ?? 0),
|
|
3671
|
+
usage: {
|
|
3672
|
+
total_tokens: {
|
|
3673
|
+
known_sum: Number(row.total_tokens ?? 0),
|
|
3674
|
+
unknown: Math.max(0, totalRuns - knownTokenRuns),
|
|
3675
|
+
},
|
|
3676
|
+
cost: {
|
|
3677
|
+
known_sum: Number(row.total_cost_brl ?? 0),
|
|
3678
|
+
unknown: Math.max(0, totalRuns - knownCostRuns),
|
|
3679
|
+
},
|
|
3487
3680
|
},
|
|
3488
|
-
|
|
3489
|
-
known_sum:
|
|
3490
|
-
unknown:
|
|
3681
|
+
duration: {
|
|
3682
|
+
known_sum: durations.get(day) ?? 0,
|
|
3683
|
+
unknown: durations.has(day) ? 0 : Number(row.total_run_count ?? 0),
|
|
3491
3684
|
},
|
|
3492
3685
|
},
|
|
3493
|
-
|
|
3494
|
-
known_sum: durations.get(day) ?? 0,
|
|
3495
|
-
unknown: durations.has(day) ? 0 : Number(row.total_run_count ?? 0),
|
|
3496
|
-
},
|
|
3497
|
-
},
|
|
3498
|
-
];
|
|
3686
|
+
];
|
|
3499
3687
|
}),
|
|
3500
3688
|
);
|
|
3501
3689
|
}
|
|
@@ -3533,333 +3721,65 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3533
3721
|
* A later month with no facts in this immutable generation is also an exact
|
|
3534
3722
|
* empty result; every month that may contain a fact continues through SQL.
|
|
3535
3723
|
*/
|
|
3536
|
-
firstFoldMonthlyQuick(filter: {
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
? JSON.parse(persisted.payload_json) as Partial<SqliteFirstFoldMonthlyQuick>
|
|
3544
|
-
: null;
|
|
3545
|
-
this.firstFoldMonthlyQuickCache =
|
|
3546
|
-
candidate !== null &&
|
|
3547
|
-
typeof candidate.from_day === "string" &&
|
|
3548
|
-
typeof candidate.to_day === "string" &&
|
|
3549
|
-
candidate.aggregate !== undefined &&
|
|
3550
|
-
candidate.pages !== undefined &&
|
|
3551
|
-
Array.isArray(candidate.top_tasks)
|
|
3552
|
-
? candidate as SqliteFirstFoldMonthlyQuick
|
|
3553
|
-
: null;
|
|
3554
|
-
} catch {
|
|
3555
|
-
this.firstFoldMonthlyQuickCache = null;
|
|
3556
|
-
}
|
|
3557
|
-
}
|
|
3558
|
-
const cached = this.firstFoldMonthlyQuickCache;
|
|
3559
|
-
if (cached === null) return null;
|
|
3560
|
-
const from = filter.from?.slice(0, 10) ?? null;
|
|
3561
|
-
const to = filter.to?.slice(0, 10) ?? null;
|
|
3562
|
-
const exactRange = from === cached.from_day && to === cached.to_day;
|
|
3563
|
-
const sameMonthLaterDay =
|
|
3564
|
-
from === cached.from_day &&
|
|
3565
|
-
to !== null &&
|
|
3566
|
-
to > cached.to_day &&
|
|
3567
|
-
to.slice(0, 7) === cached.from_day.slice(0, 7);
|
|
3568
|
-
const noUncoveredFacts =
|
|
3569
|
-
cached.source_latest_day === null || cached.source_latest_day <= cached.to_day;
|
|
3570
|
-
// The payload itself is exact for its own range even if the immutable
|
|
3571
|
-
// generation also contains future-dated facts. Only an extension beyond
|
|
3572
|
-
// its end day needs the no-uncovered-facts guard.
|
|
3573
|
-
if (exactRange || (sameMonthLaterDay && noUncoveredFacts)) return cached;
|
|
3574
|
-
|
|
3575
|
-
const requestedMonthIsLater =
|
|
3576
|
-
from !== null &&
|
|
3577
|
-
to !== null &&
|
|
3578
|
-
from.slice(0, 7) >
|
|
3579
|
-
(cached.source_latest_day === null
|
|
3580
|
-
? cached.from_day.slice(0, 7)
|
|
3581
|
-
: cached.source_latest_day.slice(0, 7));
|
|
3582
|
-
if (!requestedMonthIsLater) return null;
|
|
3583
|
-
return {
|
|
3584
|
-
from_day: from,
|
|
3585
|
-
to_day: to,
|
|
3586
|
-
source_latest_day: cached.source_latest_day,
|
|
3587
|
-
aggregate: {
|
|
3588
|
-
days: 0,
|
|
3589
|
-
total_run_count: 0,
|
|
3590
|
-
agent_invocation_count: 0,
|
|
3591
|
-
llm_call_count: null,
|
|
3592
|
-
duration_ms: null,
|
|
3593
|
-
total_tokens: null,
|
|
3594
|
-
cost_brl: null,
|
|
3595
|
-
execution_day_count: 0,
|
|
3596
|
-
},
|
|
3597
|
-
pages: { task: [], spec: [], prd: [] },
|
|
3598
|
-
top_tasks: [],
|
|
3599
|
-
};
|
|
3724
|
+
firstFoldMonthlyQuick(filter: {
|
|
3725
|
+
from: string | null;
|
|
3726
|
+
to: string | null;
|
|
3727
|
+
}): SqliteFirstFoldMonthlyQuick | null {
|
|
3728
|
+
const read = readFirstFoldMonthlyQuick(this.database, this.firstFoldMonthlyQuickCache, filter);
|
|
3729
|
+
this.firstFoldMonthlyQuickCache = read.cache;
|
|
3730
|
+
return read.result;
|
|
3600
3731
|
}
|
|
3601
3732
|
|
|
3602
3733
|
firstFoldRelevantLatestDay(): string | null {
|
|
3603
|
-
const row = this.database
|
|
3734
|
+
const row = this.database
|
|
3735
|
+
.prepare(`SELECT MAX(day) AS day FROM (
|
|
3604
3736
|
SELECT occurred_day_sp AS day FROM activities
|
|
3605
3737
|
UNION ALL SELECT occurred_day_sp AS day FROM canonical_work_items
|
|
3606
3738
|
UNION ALL SELECT occurred_day_sp AS day FROM canonical_artifacts
|
|
3607
3739
|
UNION ALL SELECT occurred_day_sp AS day FROM canonical_run_index
|
|
3608
3740
|
UNION ALL SELECT occurred_day_sp AS day FROM attributed_token_active_facts
|
|
3609
|
-
)`)
|
|
3741
|
+
)`)
|
|
3742
|
+
.get() as { day?: string | null };
|
|
3610
3743
|
return typeof row.day === "string" ? row.day : null;
|
|
3611
3744
|
}
|
|
3612
3745
|
|
|
3613
|
-
/**
|
|
3614
|
-
* Compact all-time first fold. A runtime reads an immutable private SQLite
|
|
3615
|
-
* snapshot, so readiness can warm this object and first paint can reuse the
|
|
3616
|
-
* same coherent source values without rescanning entity rows.
|
|
3617
|
-
*/
|
|
3618
3746
|
firstFoldAllTime(): SqliteFirstFoldAllTime {
|
|
3619
3747
|
if (this.firstFoldAllTimeCache !== null) return this.firstFoldAllTimeCache;
|
|
3620
|
-
|
|
3621
|
-
.
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
const topTasks = this.database
|
|
3633
|
-
.prepare("SELECT * FROM zaip_vw_first_fold_top_tasks_all_time ORDER BY rank")
|
|
3634
|
-
.all() as SqliteFirstFoldAllTime["top_tasks"];
|
|
3635
|
-
const number = (value: unknown, fallback = 0) =>
|
|
3636
|
-
typeof value === "number" ? value : fallback;
|
|
3637
|
-
const nullableNumber = (value: unknown) => (typeof value === "number" ? value : null);
|
|
3638
|
-
const readMacroMetrics = (kind: "spec" | "prd") => {
|
|
3639
|
-
const statusRows = this.database
|
|
3640
|
-
.prepare(`SELECT stage, entity_count FROM zaip_vw_first_fold_${kind}_status_all_time`)
|
|
3641
|
-
.all() as Array<{ stage: string; entity_count: number }>;
|
|
3642
|
-
const classificationRows = this.database
|
|
3643
|
-
.prepare(
|
|
3644
|
-
`SELECT classification, entity_count FROM zaip_vw_first_fold_${kind}_classification_all_time`,
|
|
3645
|
-
)
|
|
3646
|
-
.all() as Array<{ classification: string; entity_count: number }>;
|
|
3647
|
-
const macro = this.database
|
|
3648
|
-
.prepare(`SELECT * FROM zaip_vw_first_fold_${kind}_metrics_all_time`)
|
|
3649
|
-
.get() as Record<string, unknown>;
|
|
3650
|
-
const topEntities = this.database
|
|
3651
|
-
.prepare(`SELECT * FROM zaip_vw_first_fold_top_${kind}s_all_time ORDER BY rank`)
|
|
3652
|
-
.all() as SqliteFirstFoldAllTime["macro_metrics"]["specs"]["top_entities"];
|
|
3748
|
+
this.firstFoldAllTimeCache = readFirstFoldAllTime(
|
|
3749
|
+
this.database,
|
|
3750
|
+
() => this.dailyAggregateRows().length,
|
|
3751
|
+
this.firstFoldViewportAllTime(),
|
|
3752
|
+
);
|
|
3753
|
+
return this.firstFoldAllTimeCache;
|
|
3754
|
+
}
|
|
3755
|
+
|
|
3756
|
+
firstFoldBoardAllTime(): SqliteFirstFoldAllTimeBoard {
|
|
3757
|
+
if (this.firstFoldAllTimeBoardCache !== null) return this.firstFoldAllTimeBoardCache;
|
|
3758
|
+
this.firstFoldAllTimeBoardCache = readFirstFoldBoardAllTime(this.database, () => {
|
|
3759
|
+
const rows = operationsWorkListSqlRows(this.database);
|
|
3653
3760
|
return {
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
),
|
|
3660
|
-
entity_count: number(macro.entity_count),
|
|
3661
|
-
total_run_count: number(macro.total_run_count),
|
|
3662
|
-
total_duration_ms: nullableNumber(macro.total_duration_ms),
|
|
3663
|
-
total_tokens: nullableNumber(macro.total_tokens),
|
|
3664
|
-
total_cost_usd: nullableNumber(macro.total_cost_usd),
|
|
3665
|
-
total_cost_brl: nullableNumber(macro.total_cost_brl),
|
|
3666
|
-
average_duration_ms: nullableNumber(macro.average_duration_ms),
|
|
3667
|
-
average_tokens: nullableNumber(macro.average_tokens),
|
|
3668
|
-
average_cost_usd: nullableNumber(macro.average_cost_usd),
|
|
3669
|
-
average_cost_brl: nullableNumber(macro.average_cost_brl),
|
|
3670
|
-
latest_created_at:
|
|
3671
|
-
typeof macro.latest_created_at === "string" ? macro.latest_created_at : null,
|
|
3672
|
-
top_entities: topEntities,
|
|
3761
|
+
pages: {
|
|
3762
|
+
task: rows.filter((row) => row.kind === "task" || row.kind === "standalone-task"),
|
|
3763
|
+
spec: rows.filter((row) => row.kind === "spec"),
|
|
3764
|
+
prd: rows.filter((row) => row.kind === "prd"),
|
|
3765
|
+
},
|
|
3673
3766
|
};
|
|
3674
|
-
};
|
|
3675
|
-
this.
|
|
3676
|
-
aggregate: {
|
|
3677
|
-
days:
|
|
3678
|
-
typeof row.start_day === "string" && typeof row.end_day === "string"
|
|
3679
|
-
? this.dailyAggregateRows().length
|
|
3680
|
-
: 0,
|
|
3681
|
-
start_day: typeof row.start_day === "string" ? row.start_day : null,
|
|
3682
|
-
end_day: typeof row.end_day === "string" ? row.end_day : null,
|
|
3683
|
-
task_count: number(row.task_count),
|
|
3684
|
-
completed_task_count: number(row.completed_task_count),
|
|
3685
|
-
spec_count: number(row.spec_count),
|
|
3686
|
-
completed_spec_count: number(row.completed_spec_count),
|
|
3687
|
-
prd_count: number(row.prd_count),
|
|
3688
|
-
completed_prd_count: number(row.completed_prd_count),
|
|
3689
|
-
total_run_count: number(row.total_run_count),
|
|
3690
|
-
agent_invocation_count: number(row.agent_invocation_count),
|
|
3691
|
-
llm_call_count: nullableNumber(row.llm_call_count),
|
|
3692
|
-
duration_ms: nullableNumber(row.total_duration_ms),
|
|
3693
|
-
total_tokens: nullableNumber(row.total_tokens),
|
|
3694
|
-
total_cost_usd: nullableNumber(row.total_cost_usd),
|
|
3695
|
-
cost_brl: nullableNumber(row.total_cost_brl),
|
|
3696
|
-
execution_day_count: number(row.execution_day_count),
|
|
3697
|
-
latest_task_created_at:
|
|
3698
|
-
typeof row.latest_task_created_at === "string" ? row.latest_task_created_at : null,
|
|
3699
|
-
},
|
|
3700
|
-
task_status_counts: Object.fromEntries(
|
|
3701
|
-
statusRows.map((status) => [status.status, Number(status.task_count)]),
|
|
3702
|
-
),
|
|
3703
|
-
task_risk_counts: Object.fromEntries(
|
|
3704
|
-
riskRows.map((risk) => [risk.risk, Number(risk.task_count)]),
|
|
3705
|
-
),
|
|
3706
|
-
task_metrics: {
|
|
3707
|
-
average_duration_ms_per_task: nullableNumber(metrics.average_duration_ms_per_task),
|
|
3708
|
-
average_tokens_per_task: nullableNumber(metrics.average_tokens_per_task),
|
|
3709
|
-
average_cost_usd_per_task: nullableNumber(metrics.average_cost_usd_per_task),
|
|
3710
|
-
average_cost_brl_per_task: nullableNumber(metrics.average_cost_brl_per_task),
|
|
3711
|
-
latest_task_created_at:
|
|
3712
|
-
typeof metrics.latest_task_created_at === "string"
|
|
3713
|
-
? metrics.latest_task_created_at
|
|
3714
|
-
: null,
|
|
3715
|
-
},
|
|
3716
|
-
top_tasks: topTasks,
|
|
3717
|
-
macro_metrics: {
|
|
3718
|
-
specs: readMacroMetrics("spec"),
|
|
3719
|
-
prds: readMacroMetrics("prd"),
|
|
3720
|
-
},
|
|
3721
|
-
};
|
|
3722
|
-
return this.firstFoldAllTimeCache;
|
|
3767
|
+
});
|
|
3768
|
+
return this.firstFoldAllTimeBoardCache;
|
|
3723
3769
|
}
|
|
3724
3770
|
|
|
3725
|
-
/**
|
|
3726
|
-
* Narrow all-time ranking for the Operations quick projection. This avoids
|
|
3727
|
-
* loading macro metrics and extended aggregates solely to render Top 5.
|
|
3728
|
-
*/
|
|
3729
3771
|
firstFoldTopTasksAllTime(): SqliteFirstFoldAllTime["top_tasks"] {
|
|
3730
3772
|
if (this.firstFoldTopTasksAllTimeCache !== null) return this.firstFoldTopTasksAllTimeCache;
|
|
3731
|
-
this.firstFoldTopTasksAllTimeCache = this.database
|
|
3732
|
-
.prepare("SELECT * FROM zaip_vw_first_fold_top_tasks_all_time ORDER BY rank")
|
|
3733
|
-
.all() as SqliteFirstFoldAllTime["top_tasks"];
|
|
3773
|
+
this.firstFoldTopTasksAllTimeCache = readFirstFoldTopTasksAllTime(this.database);
|
|
3734
3774
|
return this.firstFoldTopTasksAllTimeCache;
|
|
3735
3775
|
}
|
|
3736
3776
|
|
|
3737
|
-
/**
|
|
3738
|
-
* First-paint subset: six KPI facts plus the status and risk charts. Keep
|
|
3739
|
-
* rankings, task averages and macro views out of readiness and scope=quick.
|
|
3740
|
-
*/
|
|
3741
3777
|
firstFoldViewportAllTime(): SqliteFirstFoldViewport {
|
|
3742
3778
|
if (this.firstFoldViewportCache !== null) return this.firstFoldViewportCache;
|
|
3743
|
-
|
|
3744
|
-
.
|
|
3745
|
-
.
|
|
3746
|
-
|
|
3747
|
-
this.firstFoldViewportCache = JSON.parse(persisted.payload_json) as SqliteFirstFoldViewport;
|
|
3748
|
-
return this.firstFoldViewportCache;
|
|
3749
|
-
}
|
|
3750
|
-
// This is deliberately not the rich overview view: `task_summary` joins
|
|
3751
|
-
// every activity and is appropriate for rankings, but status/risk charts
|
|
3752
|
-
// need only the canonical work-item dimensions. Keep the three viewport
|
|
3753
|
-
// SELECTs independently attributable and free of below-fold joins.
|
|
3754
|
-
const profile = process.env.ZAIP_DASHBOARD_PROFILE_FIRST_FOLD === "1";
|
|
3755
|
-
const timed = <T>(select: string, read: () => T): T => {
|
|
3756
|
-
const startedAt = performance.now();
|
|
3757
|
-
const value = read();
|
|
3758
|
-
if (profile)
|
|
3759
|
-
this.firstFoldViewportTimings[select] = Math.round((performance.now() - startedAt) * 10) / 10;
|
|
3760
|
-
if (profile)
|
|
3761
|
-
console.info(
|
|
3762
|
-
JSON.stringify({
|
|
3763
|
-
event: "dashboard_first_fold_select",
|
|
3764
|
-
select,
|
|
3765
|
-
elapsed_ms: Math.round((performance.now() - startedAt) * 10) / 10,
|
|
3766
|
-
}),
|
|
3767
|
-
);
|
|
3768
|
-
return value;
|
|
3769
|
-
};
|
|
3770
|
-
const row = timed("viewport_overview", () => this.database
|
|
3771
|
-
.prepare(`WITH totals AS (
|
|
3772
|
-
SELECT SUM(CASE WHEN activity_source_kind='agent_run' THEN 1 ELSE 0 END) AS agent_invocation_count,
|
|
3773
|
-
SUM(llm_call_count) AS llm_call_count,
|
|
3774
|
-
SUM(total_tokens) AS total_tokens, SUM(cost_usd) AS total_cost_usd,
|
|
3775
|
-
SUM(cost_brl) AS total_cost_brl FROM activities
|
|
3776
|
-
), run_totals AS (
|
|
3777
|
-
SELECT COUNT(*) AS total_run_count FROM canonical_run_index
|
|
3778
|
-
), duration_totals AS (
|
|
3779
|
-
SELECT ROUND(SUM(attributed_token_active_ms)) AS total_duration_ms,
|
|
3780
|
-
COUNT(DISTINCT occurred_day_sp) AS execution_day_count
|
|
3781
|
-
FROM attributed_token_active_facts
|
|
3782
|
-
), bounds AS (
|
|
3783
|
-
SELECT MIN(day) AS start_day, MAX(day) AS end_day, COUNT(*) AS day_count FROM (
|
|
3784
|
-
SELECT occurred_day_sp AS day FROM activities
|
|
3785
|
-
UNION SELECT occurred_day_sp AS day FROM canonical_work_items
|
|
3786
|
-
UNION SELECT occurred_day_sp AS day FROM canonical_artifacts
|
|
3787
|
-
)
|
|
3788
|
-
), canonical_items AS MATERIALIZED (
|
|
3789
|
-
SELECT json_extract(payload_json, '$.entity_kind') AS entity_kind,
|
|
3790
|
-
json_extract(payload_json, '$.id') AS id,
|
|
3791
|
-
json_extract(payload_json, '$.status') AS status,
|
|
3792
|
-
json_extract(payload_json, '$.risk') AS risk,
|
|
3793
|
-
json_extract(payload_json, '$.occurred_at') AS occurred_at
|
|
3794
|
-
FROM zaip_aios_activities WHERE payload_type='work_item'
|
|
3795
|
-
), items AS MATERIALIZED (
|
|
3796
|
-
SELECT entity_kind, id, status, risk, occurred_at FROM canonical_items
|
|
3797
|
-
UNION ALL SELECT 'task', a.task_id, MAX(a.work_item_status), MAX(a.risk), MIN(a.occurred_at)
|
|
3798
|
-
FROM zaip_aios_activities a WHERE a.payload_type IS NULL AND a.task_id IS NOT NULL
|
|
3799
|
-
AND NOT EXISTS (SELECT 1 FROM canonical_items c WHERE c.entity_kind='task' AND c.id=a.task_id)
|
|
3800
|
-
GROUP BY task_id
|
|
3801
|
-
UNION ALL SELECT 'spec', a.spec_id, MAX(a.work_item_status), MAX(a.risk), MIN(a.occurred_at)
|
|
3802
|
-
FROM zaip_aios_activities a WHERE a.payload_type IS NULL AND a.spec_id IS NOT NULL
|
|
3803
|
-
AND NOT EXISTS (SELECT 1 FROM canonical_items c WHERE c.entity_kind='spec' AND c.id=a.spec_id)
|
|
3804
|
-
GROUP BY spec_id
|
|
3805
|
-
UNION ALL SELECT 'prd', a.prd_id, MAX(a.work_item_status), MAX(a.risk), MIN(a.occurred_at)
|
|
3806
|
-
FROM zaip_aios_activities a WHERE a.payload_type IS NULL AND a.prd_id IS NOT NULL
|
|
3807
|
-
AND NOT EXISTS (SELECT 1 FROM canonical_items c WHERE c.entity_kind='prd' AND c.id=a.prd_id)
|
|
3808
|
-
GROUP BY prd_id
|
|
3809
|
-
), item_totals AS (
|
|
3810
|
-
SELECT
|
|
3811
|
-
SUM(entity_kind='task') AS task_count,
|
|
3812
|
-
SUM(entity_kind='task' AND status='completed') AS completed_task_count,
|
|
3813
|
-
MAX(CASE WHEN entity_kind='task' THEN occurred_at END) AS latest_task_created_at,
|
|
3814
|
-
SUM(entity_kind='spec') AS spec_count,
|
|
3815
|
-
SUM(entity_kind='spec' AND status IN ('completed','delivered')) AS completed_spec_count,
|
|
3816
|
-
SUM(entity_kind='prd') AS prd_count,
|
|
3817
|
-
SUM(entity_kind='prd' AND status IN ('completed','delivered')) AS completed_prd_count
|
|
3818
|
-
FROM items
|
|
3819
|
-
), task_status AS (
|
|
3820
|
-
SELECT json_group_object(status, task_count) AS task_status_json FROM (
|
|
3821
|
-
SELECT COALESCE(status, 'draft') AS status, COUNT(*) AS task_count
|
|
3822
|
-
FROM items WHERE entity_kind='task' GROUP BY COALESCE(status, 'draft')
|
|
3823
|
-
)
|
|
3824
|
-
), task_risk AS (
|
|
3825
|
-
SELECT json_group_object(risk, task_count) AS task_risk_json FROM (
|
|
3826
|
-
SELECT COALESCE(risk, 'standard') AS risk, COUNT(*) AS task_count
|
|
3827
|
-
FROM items WHERE entity_kind='task' GROUP BY COALESCE(risk, 'standard')
|
|
3828
|
-
)
|
|
3829
|
-
) SELECT totals.*, run_totals.*, duration_totals.*, bounds.*, item_totals.*, task_status.*, task_risk.*
|
|
3830
|
-
FROM totals CROSS JOIN run_totals CROSS JOIN duration_totals CROSS JOIN bounds
|
|
3831
|
-
CROSS JOIN item_totals CROSS JOIN task_status CROSS JOIN task_risk`)
|
|
3832
|
-
.get() as Record<string, unknown>);
|
|
3833
|
-
const number = (value: unknown, fallback = 0) =>
|
|
3834
|
-
typeof value === "number" ? value : fallback;
|
|
3835
|
-
const nullableNumber = (value: unknown) => (typeof value === "number" ? value : null);
|
|
3836
|
-
this.firstFoldViewportCache = {
|
|
3837
|
-
aggregate: {
|
|
3838
|
-
days: number(row.day_count),
|
|
3839
|
-
start_day: typeof row.start_day === "string" ? row.start_day : null,
|
|
3840
|
-
end_day: typeof row.end_day === "string" ? row.end_day : null,
|
|
3841
|
-
task_count: number(row.task_count),
|
|
3842
|
-
completed_task_count: number(row.completed_task_count),
|
|
3843
|
-
spec_count: number(row.spec_count),
|
|
3844
|
-
completed_spec_count: number(row.completed_spec_count),
|
|
3845
|
-
prd_count: number(row.prd_count),
|
|
3846
|
-
completed_prd_count: number(row.completed_prd_count),
|
|
3847
|
-
total_run_count: number(row.total_run_count),
|
|
3848
|
-
agent_invocation_count: number(row.agent_invocation_count),
|
|
3849
|
-
llm_call_count: nullableNumber(row.llm_call_count),
|
|
3850
|
-
duration_ms: nullableNumber(row.total_duration_ms),
|
|
3851
|
-
total_tokens: nullableNumber(row.total_tokens),
|
|
3852
|
-
total_cost_usd: nullableNumber(row.total_cost_usd),
|
|
3853
|
-
cost_brl: nullableNumber(row.total_cost_brl),
|
|
3854
|
-
execution_day_count: number(row.execution_day_count),
|
|
3855
|
-
latest_task_created_at:
|
|
3856
|
-
typeof row.latest_task_created_at === "string" ? row.latest_task_created_at : null,
|
|
3857
|
-
},
|
|
3858
|
-
task_status_counts:
|
|
3859
|
-
typeof row.task_status_json === "string" ? JSON.parse(row.task_status_json) : {},
|
|
3860
|
-
task_risk_counts:
|
|
3861
|
-
typeof row.task_risk_json === "string" ? JSON.parse(row.task_risk_json) : {},
|
|
3862
|
-
};
|
|
3779
|
+
this.firstFoldViewportCache = readFirstFoldViewport(
|
|
3780
|
+
this.database,
|
|
3781
|
+
this.firstFoldViewportTimings,
|
|
3782
|
+
);
|
|
3863
3783
|
return this.firstFoldViewportCache;
|
|
3864
3784
|
}
|
|
3865
3785
|
|
|
@@ -3889,7 +3809,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3889
3809
|
AND (? IS NULL OR f.occurred_day_sp >= ?) AND (? IS NULL OR f.occurred_day_sp <= ?)
|
|
3890
3810
|
GROUP BY a.task_id`)
|
|
3891
3811
|
.all(from, from, to, to) as Array<{ task_id: string; duration_ms: number }>;
|
|
3892
|
-
const durationByTask = new Map(
|
|
3812
|
+
const durationByTask = new Map(
|
|
3813
|
+
durationRows.map((row) => [row.task_id, Number(row.duration_ms)]),
|
|
3814
|
+
);
|
|
3893
3815
|
return facts
|
|
3894
3816
|
.map((row) => ({
|
|
3895
3817
|
...row,
|
|
@@ -3935,14 +3857,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3935
3857
|
}>;
|
|
3936
3858
|
const knownDurationRuns = runs.filter(
|
|
3937
3859
|
(run) =>
|
|
3938
|
-
run.run_role === "root" &&
|
|
3939
|
-
run.phase === "execution" &&
|
|
3940
|
-
typeof run.duration_ms === "number",
|
|
3860
|
+
run.run_role === "root" && run.phase === "execution" && typeof run.duration_ms === "number",
|
|
3941
3861
|
).length;
|
|
3942
|
-
const durationByDay = this.executionDurationByDay(
|
|
3943
|
-
{ from: null, to: null },
|
|
3944
|
-
taskId,
|
|
3945
|
-
);
|
|
3862
|
+
const durationByDay = this.executionDurationByDay({ from: null, to: null }, taskId);
|
|
3946
3863
|
const duration =
|
|
3947
3864
|
durationByDay.size === 0
|
|
3948
3865
|
? null
|
|
@@ -4018,11 +3935,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4018
3935
|
const to = filter.to?.slice(0, 10) ?? null;
|
|
4019
3936
|
// Financial periods are source-session periods when available. This keeps
|
|
4020
3937
|
// a retrospective capture on its observed day rather than its write day.
|
|
4021
|
-
const periodDay =
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
`(? IS NULL OR ${periodDay} <= ?)`,
|
|
4025
|
-
];
|
|
3938
|
+
const periodDay =
|
|
3939
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours')";
|
|
3940
|
+
const clauses = [`(? IS NULL OR ${periodDay} >= ?)`, `(? IS NULL OR ${periodDay} <= ?)`];
|
|
4026
3941
|
const values: Array<string | number | null> = [from, from, to, to];
|
|
4027
3942
|
const add = (column: string, items?: readonly string[]) => {
|
|
4028
3943
|
if (items !== undefined && items.length > 0) {
|
|
@@ -4041,7 +3956,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4041
3956
|
const providerValues = (filter.providers ?? []).filter((item) => item !== "unavailable");
|
|
4042
3957
|
const modelValues = (filter.models ?? []).filter((item) => item !== "unavailable");
|
|
4043
3958
|
const requestsUnavailable =
|
|
4044
|
-
filter.providers?.includes("unavailable") === true ||
|
|
3959
|
+
filter.providers?.includes("unavailable") === true ||
|
|
3960
|
+
filter.models?.includes("unavailable") === true;
|
|
4045
3961
|
if (requestsUnavailable) {
|
|
4046
3962
|
// Keep the pre-existing explicit-unavailable behaviour; it describes a
|
|
4047
3963
|
// missing invocation attribute rather than a provider/model pair.
|
|
@@ -4069,7 +3985,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4069
3985
|
segmentClauses.push(`f.model IN (${modelValues.map(() => "?").join(",")})`);
|
|
4070
3986
|
values.push(...modelValues);
|
|
4071
3987
|
}
|
|
4072
|
-
clauses.push(
|
|
3988
|
+
clauses.push(
|
|
3989
|
+
`EXISTS (SELECT 1 FROM invocation_usage f WHERE f.agent_invocation_id = i.agent_invocation_id AND ${segmentClauses.join(" AND ")})`,
|
|
3990
|
+
);
|
|
4073
3991
|
}
|
|
4074
3992
|
return this.database
|
|
4075
3993
|
.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 +4008,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4090
4008
|
*/
|
|
4091
4009
|
costMonitorFactualDurationByDay(
|
|
4092
4010
|
filter: Parameters<SqliteReadModelFileSystem["invocationCostRows"]>[0],
|
|
4093
|
-
financialRows: readonly Record<string, unknown>[] = this.costMonitorFinancialActivityRows(
|
|
4011
|
+
financialRows: readonly Record<string, unknown>[] = this.costMonitorFinancialActivityRows(
|
|
4012
|
+
filter,
|
|
4013
|
+
),
|
|
4094
4014
|
): Map<string, { known_sum: number; eligible_runs: number; known_runs: number }> {
|
|
4095
4015
|
const rows = financialRows;
|
|
4096
4016
|
const activityIds = new Set<string>();
|
|
@@ -4105,7 +4025,10 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4105
4025
|
? row.occurred_at
|
|
4106
4026
|
: null;
|
|
4107
4027
|
if (timestamp === null) continue;
|
|
4108
|
-
const hasFinancialFact =
|
|
4028
|
+
const hasFinancialFact =
|
|
4029
|
+
typeof row.total_tokens === "number" ||
|
|
4030
|
+
typeof row.cost_brl === "number" ||
|
|
4031
|
+
typeof row.cost_usd === "number";
|
|
4109
4032
|
if (!hasFinancialFact) continue;
|
|
4110
4033
|
// SQLite exposes the durable activity key as an integer; normalize it
|
|
4111
4034
|
// before joining it to the JSON-backed temporal fact key.
|
|
@@ -4122,7 +4045,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4122
4045
|
// local session can be refreshed instead of failing its Cost Monitor.
|
|
4123
4046
|
const durationFactTable =
|
|
4124
4047
|
this.database
|
|
4125
|
-
.prepare(
|
|
4048
|
+
.prepare(
|
|
4049
|
+
"SELECT 1 AS present FROM sqlite_master WHERE name = 'attributed_token_active_facts'",
|
|
4050
|
+
)
|
|
4126
4051
|
.get() !== undefined
|
|
4127
4052
|
? "attributed_token_active_facts"
|
|
4128
4053
|
: "zaip_vw_attributed_token_active_facts";
|
|
@@ -4168,7 +4093,10 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4168
4093
|
ids.add(activityId);
|
|
4169
4094
|
unavailableByDay.set(day, ids);
|
|
4170
4095
|
}
|
|
4171
|
-
const byDay = new Map<
|
|
4096
|
+
const byDay = new Map<
|
|
4097
|
+
string,
|
|
4098
|
+
{ known_sum: number; eligible_runs: number; known_runs: number }
|
|
4099
|
+
>();
|
|
4172
4100
|
for (const day of new Set([...factsByDay.keys(), ...unavailableByDay.keys()])) {
|
|
4173
4101
|
const facts = factsByDay.get(day) ?? { known_sum: 0, activityIds: new Set<string>() };
|
|
4174
4102
|
const unavailableIds = unavailableByDay.get(day) ?? new Set<string>();
|
|
@@ -4227,7 +4155,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4227
4155
|
}
|
|
4228
4156
|
|
|
4229
4157
|
/** Cost Monitor's historical financial population, independent of Agent-only views. */
|
|
4230
|
-
costMonitorFinancialActivityRows(
|
|
4158
|
+
costMonitorFinancialActivityRows(
|
|
4159
|
+
filter: Parameters<SqliteReadModelFileSystem["activityCostRows"]>[0],
|
|
4160
|
+
) {
|
|
4231
4161
|
// Activity capture day can differ from the observed source session day.
|
|
4232
4162
|
// Keep that source-session population in SQL: the previous implementation
|
|
4233
4163
|
// read every activity into JS and issued a usage lookup for each filtered
|
|
@@ -4235,7 +4165,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4235
4165
|
const { from, to, providers = [], models = [] } = filter;
|
|
4236
4166
|
const sourceFrom = costMonitorFilterDay(from);
|
|
4237
4167
|
const sourceTo = costMonitorFilterDay(to);
|
|
4238
|
-
const sourceDay =
|
|
4168
|
+
const sourceDay =
|
|
4169
|
+
"date(COALESCE(a.source_started_at, a.source_occurred_at, a.occurred_at), '-3 hours')";
|
|
4239
4170
|
const clauses = [
|
|
4240
4171
|
"(a.total_tokens IS NOT NULL OR a.cost_brl IS NOT NULL OR a.cost_usd IS NOT NULL)",
|
|
4241
4172
|
`(? IS NULL OR ${sourceDay} >= ?)`,
|
|
@@ -4259,15 +4190,25 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4259
4190
|
if (providers.length > 0 || models.length > 0) {
|
|
4260
4191
|
const usageClauses: string[] = [];
|
|
4261
4192
|
if (providers.length > 0) {
|
|
4262
|
-
usageClauses.push(
|
|
4193
|
+
usageClauses.push(
|
|
4194
|
+
`COALESCE(u.provider, 'unavailable') IN (${providers.map(() => "?").join(",")})`,
|
|
4195
|
+
);
|
|
4263
4196
|
}
|
|
4264
4197
|
if (models.length > 0) {
|
|
4265
|
-
usageClauses.push(
|
|
4198
|
+
usageClauses.push(
|
|
4199
|
+
`COALESCE(u.model, 'unavailable') IN (${models.map(() => "?").join(",")})`,
|
|
4200
|
+
);
|
|
4266
4201
|
}
|
|
4267
4202
|
const usageValues = [...providers, ...models];
|
|
4268
4203
|
const directClauses: string[] = [];
|
|
4269
|
-
if (providers.length > 0)
|
|
4270
|
-
|
|
4204
|
+
if (providers.length > 0)
|
|
4205
|
+
directClauses.push(
|
|
4206
|
+
`COALESCE(a.provider, 'unavailable') IN (${providers.map(() => "?").join(",")})`,
|
|
4207
|
+
);
|
|
4208
|
+
if (models.length > 0)
|
|
4209
|
+
directClauses.push(
|
|
4210
|
+
`COALESCE(a.model, 'unavailable') IN (${models.map(() => "?").join(",")})`,
|
|
4211
|
+
);
|
|
4271
4212
|
// Rows without an Agent Invocation keep their activity dimensions;
|
|
4272
4213
|
// Agent rows retain the segment-level provider/model semantics.
|
|
4273
4214
|
clauses.push(
|
|
@@ -4302,13 +4243,283 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4302
4243
|
* per selected day plus conversation/planning/execution and total rows; no
|
|
4303
4244
|
* activity-shaped record is returned to the server or serialized to HTTP.
|
|
4304
4245
|
*/
|
|
4246
|
+
developerCostAggregate(filter: {
|
|
4247
|
+
from: string | null;
|
|
4248
|
+
to: string | null;
|
|
4249
|
+
accountIds?: readonly string[];
|
|
4250
|
+
}) {
|
|
4251
|
+
const from = costMonitorFilterDay(filter.from);
|
|
4252
|
+
const to = costMonitorFilterDay(filter.to);
|
|
4253
|
+
const clauses = [
|
|
4254
|
+
"a.developer_account_id IS NOT NULL",
|
|
4255
|
+
"(? IS NULL OR a.occurred_day_sp >= ?)",
|
|
4256
|
+
"(? IS NULL OR a.occurred_day_sp <= ?)",
|
|
4257
|
+
];
|
|
4258
|
+
const values: Array<string | null> = [from, from, to, to];
|
|
4259
|
+
if (filter.accountIds !== undefined && filter.accountIds.length > 0) {
|
|
4260
|
+
clauses.push(`a.developer_account_id IN (${filter.accountIds.map(() => "?").join(",")})`);
|
|
4261
|
+
values.push(...filter.accountIds);
|
|
4262
|
+
}
|
|
4263
|
+
const where = clauses.join(" AND ");
|
|
4264
|
+
const rows = this.database
|
|
4265
|
+
.prepare(`
|
|
4266
|
+
WITH selected AS (SELECT * FROM zaip_vw_activities a WHERE ${where}), completed AS (
|
|
4267
|
+
SELECT a.developer_account_id, COUNT(DISTINCT w.id) AS completed_task_count
|
|
4268
|
+
FROM zaip_vw_canonical_work_items w JOIN selected a ON a.run_id=w.completed_run_id
|
|
4269
|
+
WHERE w.entity_kind='task' AND w.status='completed' GROUP BY a.developer_account_id
|
|
4270
|
+
) ${developerArtifactCountsCte()}
|
|
4271
|
+
SELECT a.developer_account_id AS account_id, MAX(a.developer_login) AS login_snapshot,
|
|
4272
|
+
CASE WHEN COUNT(DISTINCT a.developer_attribution_source)=1 THEN MAX(a.developer_attribution_source) ELSE 'mixed' END AS attribution_source,
|
|
4273
|
+
COUNT(DISTINCT a.prd_id) AS prd_count, COUNT(DISTINCT a.spec_id) AS spec_count,
|
|
4274
|
+
COUNT(DISTINCT a.task_id) AS task_count, COUNT(*) AS run_count,
|
|
4275
|
+
COALESCE(artifacts.test_count, 0) AS test_count,
|
|
4276
|
+
COALESCE(artifacts.evidence_count, 0) AS evidence_count,
|
|
4277
|
+
SUM(a.attributed_token_active_ms) AS total_duration_ms,
|
|
4278
|
+
COUNT(a.attributed_token_active_ms) AS duration_covered_runs,
|
|
4279
|
+
SUM(a.source_event_count) AS source_event_count,
|
|
4280
|
+
SUM(a.total_tokens) AS total_tokens, SUM(a.cost_usd) AS total_cost_usd, SUM(a.cost_brl) AS total_cost_brl,
|
|
4281
|
+
COUNT(a.total_tokens) AS token_covered_runs, COUNT(a.cost_usd) AS usd_covered_runs, COUNT(a.cost_brl) AS brl_covered_runs,
|
|
4282
|
+
COALESCE(completed.completed_task_count, 0) AS completed_task_count
|
|
4283
|
+
FROM selected a
|
|
4284
|
+
LEFT JOIN completed USING(developer_account_id)
|
|
4285
|
+
LEFT JOIN artifacts USING(developer_account_id)
|
|
4286
|
+
GROUP BY a.developer_account_id ORDER BY total_cost_brl DESC, account_id`)
|
|
4287
|
+
.all(...values, from, from, to, to) as Array<Record<string, unknown>>;
|
|
4288
|
+
// A delivery is factual only when the canonical Task points at the exact
|
|
4289
|
+
// completed Run. Contributors are the distinct attributed GitHub accounts
|
|
4290
|
+
// on that same Run; no timestamp, commit, or deployment inference enters.
|
|
4291
|
+
const delivered = this.database
|
|
4292
|
+
.prepare(`
|
|
4293
|
+
WITH selected AS (SELECT * FROM zaip_vw_activities a WHERE ${where})
|
|
4294
|
+
SELECT selected.developer_account_id AS delivered_by_account_id, w.id, w.title, w.status, w.completed_run_id,
|
|
4295
|
+
GROUP_CONCAT(DISTINCT contributor.developer_account_id || char(31) || COALESCE(contributor.developer_login, '')) AS contributor_pairs
|
|
4296
|
+
FROM zaip_vw_canonical_work_items w
|
|
4297
|
+
JOIN selected ON selected.run_id=w.completed_run_id
|
|
4298
|
+
LEFT JOIN zaip_vw_activities contributor ON contributor.task_id=w.id
|
|
4299
|
+
AND contributor.developer_account_id IS NOT NULL
|
|
4300
|
+
WHERE w.entity_kind='task' AND w.status='completed'
|
|
4301
|
+
GROUP BY selected.developer_account_id, w.id, w.title, w.status, w.completed_run_id
|
|
4302
|
+
ORDER BY w.id`)
|
|
4303
|
+
.all(...values) as Array<Record<string, unknown>>;
|
|
4304
|
+
const deliveriesByAccount = new Map<string, Array<Record<string, unknown>>>();
|
|
4305
|
+
for (const task of delivered) {
|
|
4306
|
+
const accountId = String(task.delivered_by_account_id);
|
|
4307
|
+
const list = deliveriesByAccount.get(accountId) ?? [];
|
|
4308
|
+
list.push({
|
|
4309
|
+
id: task.id,
|
|
4310
|
+
title: task.title,
|
|
4311
|
+
status: task.status,
|
|
4312
|
+
completed_run_id: task.completed_run_id,
|
|
4313
|
+
delivered_by: { account_id: accountId },
|
|
4314
|
+
contributors: String(task.contributor_pairs ?? "")
|
|
4315
|
+
.split(",")
|
|
4316
|
+
.filter(Boolean)
|
|
4317
|
+
.map((pair) => {
|
|
4318
|
+
const [account_id, login_snapshot] = pair.split(String.fromCharCode(31));
|
|
4319
|
+
return { account_id, login_snapshot: login_snapshot || null };
|
|
4320
|
+
}),
|
|
4321
|
+
});
|
|
4322
|
+
deliveriesByAccount.set(accountId, list);
|
|
4323
|
+
}
|
|
4324
|
+
const qualificationForSource: Record<
|
|
4325
|
+
string,
|
|
4326
|
+
"verified" | "cached_fallback" | "human_assigned" | "mixed"
|
|
4327
|
+
> = {
|
|
4328
|
+
verified_local_account: "verified",
|
|
4329
|
+
cached_fallback: "cached_fallback",
|
|
4330
|
+
human_assigned: "human_assigned",
|
|
4331
|
+
mixed: "mixed",
|
|
4332
|
+
};
|
|
4333
|
+
for (const row of rows) {
|
|
4334
|
+
row.completed_tasks = deliveriesByAccount.get(String(row.account_id)) ?? [];
|
|
4335
|
+
row.qualification = qualificationForSource[String(row.attribution_source)] ?? "unqualified";
|
|
4336
|
+
}
|
|
4337
|
+
return {
|
|
4338
|
+
data_source: "sqlite_read_model" as const,
|
|
4339
|
+
filters: { from, to, account_ids: filter.accountIds ?? [] },
|
|
4340
|
+
rows,
|
|
4341
|
+
totals: {
|
|
4342
|
+
run_count: rows.reduce((sum, row) => sum + Number(row.run_count ?? 0), 0),
|
|
4343
|
+
completed_task_count: rows.reduce(
|
|
4344
|
+
(sum, row) => sum + Number(row.completed_task_count ?? 0),
|
|
4345
|
+
0,
|
|
4346
|
+
),
|
|
4347
|
+
total_cost_usd: sumKnownDeveloperMetric(rows, "total_cost_usd"),
|
|
4348
|
+
total_cost_brl: sumKnownDeveloperMetric(rows, "total_cost_brl"),
|
|
4349
|
+
},
|
|
4350
|
+
};
|
|
4351
|
+
}
|
|
4352
|
+
|
|
4353
|
+
/**
|
|
4354
|
+
* Operations-only developer projection. It deliberately keeps the Cost
|
|
4355
|
+
* Monitor aggregate unchanged: status, kind and risk are derived solely
|
|
4356
|
+
* from canonical Task records linked to the developer's observed Task ids.
|
|
4357
|
+
*/
|
|
4358
|
+
operationsDeveloperAggregate(filter: {
|
|
4359
|
+
from: string | null;
|
|
4360
|
+
to: string | null;
|
|
4361
|
+
accountIds?: readonly string[];
|
|
4362
|
+
}) {
|
|
4363
|
+
const from = costMonitorFilterDay(filter.from);
|
|
4364
|
+
const to = costMonitorFilterDay(filter.to);
|
|
4365
|
+
const clauses = [
|
|
4366
|
+
"a.developer_account_id IS NOT NULL",
|
|
4367
|
+
"(? IS NULL OR a.occurred_day_sp >= ?)",
|
|
4368
|
+
"(? IS NULL OR a.occurred_day_sp <= ?)",
|
|
4369
|
+
];
|
|
4370
|
+
const values: Array<string | null> = [from, from, to, to];
|
|
4371
|
+
if (filter.accountIds !== undefined && filter.accountIds.length > 0) {
|
|
4372
|
+
clauses.push(`a.developer_account_id IN (${filter.accountIds.map(() => "?").join(",")})`);
|
|
4373
|
+
values.push(...filter.accountIds);
|
|
4374
|
+
}
|
|
4375
|
+
const where = clauses.join(" AND ");
|
|
4376
|
+
const rows = this.database
|
|
4377
|
+
.prepare(`
|
|
4378
|
+
WITH selected AS (SELECT * FROM zaip_vw_activities a WHERE ${where}),
|
|
4379
|
+
developer_tasks AS (
|
|
4380
|
+
SELECT DISTINCT developer_account_id AS account_id, task_id
|
|
4381
|
+
FROM selected WHERE task_id IS NOT NULL
|
|
4382
|
+
), canonical_task_facts AS (
|
|
4383
|
+
SELECT developer_tasks.account_id, work.status, work.entity_kind, work.risk
|
|
4384
|
+
FROM developer_tasks JOIN zaip_vw_canonical_work_items work
|
|
4385
|
+
ON work.entity_kind='task' AND work.id=developer_tasks.task_id
|
|
4386
|
+
), status_counts AS (
|
|
4387
|
+
SELECT account_id, json_group_object(status, task_count) AS status_counts_json
|
|
4388
|
+
FROM (SELECT account_id, status, COUNT(*) AS task_count FROM canonical_task_facts
|
|
4389
|
+
WHERE status IS NOT NULL GROUP BY account_id, status) GROUP BY account_id
|
|
4390
|
+
), type_counts AS (
|
|
4391
|
+
SELECT account_id, json_group_object(entity_kind, task_count) AS type_counts_json
|
|
4392
|
+
FROM (SELECT account_id, entity_kind, COUNT(*) AS task_count FROM canonical_task_facts
|
|
4393
|
+
WHERE entity_kind IS NOT NULL GROUP BY account_id, entity_kind) GROUP BY account_id
|
|
4394
|
+
), risk_counts AS (
|
|
4395
|
+
SELECT account_id, json_group_object(risk, task_count) AS risk_counts_json
|
|
4396
|
+
FROM (SELECT account_id, risk, COUNT(*) AS task_count FROM canonical_task_facts
|
|
4397
|
+
WHERE risk IS NOT NULL GROUP BY account_id, risk) GROUP BY account_id
|
|
4398
|
+
), completed AS (
|
|
4399
|
+
SELECT account_id, COUNT(*) AS completed_task_count
|
|
4400
|
+
FROM canonical_task_facts WHERE status='completed'
|
|
4401
|
+
GROUP BY account_id
|
|
4402
|
+
)
|
|
4403
|
+
-- task_count is the distinct, observed Task-id population attributed
|
|
4404
|
+
-- to this developer in the selected period. Canonical records do not
|
|
4405
|
+
-- retain a creator identity, so this projection never infers one.
|
|
4406
|
+
SELECT selected.developer_account_id AS account_id, MAX(selected.developer_login) AS login_snapshot,
|
|
4407
|
+
COUNT(DISTINCT selected.task_id) AS task_count,
|
|
4408
|
+
COALESCE(completed.completed_task_count, 0) AS completed_task_count,
|
|
4409
|
+
SUM(selected.attributed_token_active_ms) AS total_duration_ms,
|
|
4410
|
+
SUM(selected.total_tokens) AS total_tokens,
|
|
4411
|
+
SUM(selected.cost_brl) AS total_cost_brl,
|
|
4412
|
+
status_counts.status_counts_json, type_counts.type_counts_json, risk_counts.risk_counts_json
|
|
4413
|
+
FROM selected
|
|
4414
|
+
LEFT JOIN completed ON completed.account_id=selected.developer_account_id
|
|
4415
|
+
LEFT JOIN status_counts ON status_counts.account_id=selected.developer_account_id
|
|
4416
|
+
LEFT JOIN type_counts ON type_counts.account_id=selected.developer_account_id
|
|
4417
|
+
LEFT JOIN risk_counts ON risk_counts.account_id=selected.developer_account_id
|
|
4418
|
+
GROUP BY selected.developer_account_id
|
|
4419
|
+
ORDER BY total_cost_brl DESC, account_id`)
|
|
4420
|
+
.all(...values) as Array<Record<string, unknown>>;
|
|
4421
|
+
const distribution = (value: unknown): Record<string, number> | null => {
|
|
4422
|
+
if (typeof value !== "string") return null;
|
|
4423
|
+
try {
|
|
4424
|
+
const parsed = JSON.parse(value) as Record<string, unknown>;
|
|
4425
|
+
return Object.fromEntries(
|
|
4426
|
+
Object.entries(parsed).flatMap(([key, count]) =>
|
|
4427
|
+
typeof count === "number" && Number.isFinite(count) ? [[key, count]] : [],
|
|
4428
|
+
),
|
|
4429
|
+
);
|
|
4430
|
+
} catch {
|
|
4431
|
+
return null;
|
|
4432
|
+
}
|
|
4433
|
+
};
|
|
4434
|
+
for (const row of rows) {
|
|
4435
|
+
row.status_counts = distribution(row.status_counts_json);
|
|
4436
|
+
row.type_counts = distribution(row.type_counts_json);
|
|
4437
|
+
row.risk_counts = distribution(row.risk_counts_json);
|
|
4438
|
+
delete row.status_counts_json;
|
|
4439
|
+
delete row.type_counts_json;
|
|
4440
|
+
delete row.risk_counts_json;
|
|
4441
|
+
}
|
|
4442
|
+
return {
|
|
4443
|
+
data_source: "sqlite_read_model" as const,
|
|
4444
|
+
filters: { from, to, account_ids: filter.accountIds ?? [] },
|
|
4445
|
+
rows,
|
|
4446
|
+
};
|
|
4447
|
+
}
|
|
4448
|
+
|
|
4305
4449
|
costMonitorSummaryAggregate(
|
|
4306
4450
|
filter: Parameters<SqliteReadModelFileSystem["activityCostRows"]>[0],
|
|
4307
|
-
) {
|
|
4451
|
+
): CostSummarySource {
|
|
4308
4452
|
const { from, to, providers = [], models = [] } = filter;
|
|
4309
4453
|
const sourceFrom = costMonitorFilterDay(from);
|
|
4310
4454
|
const sourceTo = costMonitorFilterDay(to);
|
|
4311
|
-
const
|
|
4455
|
+
const cacheKey = JSON.stringify({ ...filter, from: sourceFrom, to: sourceTo });
|
|
4456
|
+
const generationCached = this.costMonitorSummaryCache.get(cacheKey);
|
|
4457
|
+
if (generationCached !== undefined) return structuredClone(generationCached);
|
|
4458
|
+
const remember = (value: CostSummarySource) => {
|
|
4459
|
+
if (this.costMonitorSummaryCache.size >= 64) {
|
|
4460
|
+
const oldest = this.costMonitorSummaryCache.keys().next().value as string | undefined;
|
|
4461
|
+
if (oldest !== undefined) this.costMonitorSummaryCache.delete(oldest);
|
|
4462
|
+
}
|
|
4463
|
+
this.costMonitorSummaryCache.set(cacheKey, structuredClone(value));
|
|
4464
|
+
return structuredClone(value);
|
|
4465
|
+
};
|
|
4466
|
+
const unfilteredAllTime =
|
|
4467
|
+
sourceFrom === null &&
|
|
4468
|
+
sourceTo === null &&
|
|
4469
|
+
[
|
|
4470
|
+
providers,
|
|
4471
|
+
models,
|
|
4472
|
+
filter.agents,
|
|
4473
|
+
filter.phases,
|
|
4474
|
+
filter.statuses,
|
|
4475
|
+
filter.tasks,
|
|
4476
|
+
filter.specs,
|
|
4477
|
+
filter.prds,
|
|
4478
|
+
filter.executions,
|
|
4479
|
+
filter.changes,
|
|
4480
|
+
].every((values) => values === undefined || values.length === 0);
|
|
4481
|
+
if (unfilteredAllTime) {
|
|
4482
|
+
const cached = this.database
|
|
4483
|
+
.prepare(
|
|
4484
|
+
"SELECT payload_json FROM zaip_aios_activities WHERE payload_type='cost_monitor_summary_cache' ORDER BY recorded_at DESC LIMIT 1",
|
|
4485
|
+
)
|
|
4486
|
+
.get() as { payload_json?: string } | undefined;
|
|
4487
|
+
if (typeof cached?.payload_json === "string")
|
|
4488
|
+
return remember(JSON.parse(cached.payload_json) as CostSummarySource);
|
|
4489
|
+
}
|
|
4490
|
+
if (
|
|
4491
|
+
sourceFrom !== null &&
|
|
4492
|
+
sourceTo !== null &&
|
|
4493
|
+
[
|
|
4494
|
+
providers,
|
|
4495
|
+
models,
|
|
4496
|
+
filter.agents,
|
|
4497
|
+
filter.phases,
|
|
4498
|
+
filter.statuses,
|
|
4499
|
+
filter.tasks,
|
|
4500
|
+
filter.specs,
|
|
4501
|
+
filter.prds,
|
|
4502
|
+
filter.executions,
|
|
4503
|
+
filter.changes,
|
|
4504
|
+
].every((values) => values === undefined || values.length === 0)
|
|
4505
|
+
) {
|
|
4506
|
+
const cached = this.database
|
|
4507
|
+
.prepare(
|
|
4508
|
+
"SELECT payload_json FROM zaip_aios_activities WHERE payload_type='cost_monitor_summary_month_cache' ORDER BY recorded_at DESC LIMIT 1",
|
|
4509
|
+
)
|
|
4510
|
+
.get() as { payload_json?: string } | undefined;
|
|
4511
|
+
if (typeof cached?.payload_json === "string") {
|
|
4512
|
+
const payload = JSON.parse(cached.payload_json) as {
|
|
4513
|
+
from: string;
|
|
4514
|
+
to: string;
|
|
4515
|
+
aggregate: CostSummarySource;
|
|
4516
|
+
};
|
|
4517
|
+
if (payload.from === sourceFrom && payload.to === sourceTo)
|
|
4518
|
+
return remember(payload.aggregate);
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4521
|
+
const sourceDay =
|
|
4522
|
+
"date(COALESCE(a.source_started_at, a.source_occurred_at, a.occurred_at), '-3 hours')";
|
|
4312
4523
|
const clauses = [
|
|
4313
4524
|
"(a.total_tokens IS NOT NULL OR a.cost_brl IS NOT NULL OR a.cost_usd IS NOT NULL)",
|
|
4314
4525
|
`(? IS NULL OR ${sourceDay} >= ?)`,
|
|
@@ -4334,14 +4545,22 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4334
4545
|
const usageValues: string[] = [];
|
|
4335
4546
|
const directValues: string[] = [];
|
|
4336
4547
|
if (providers.length > 0) {
|
|
4337
|
-
usageClauses.push(
|
|
4338
|
-
|
|
4548
|
+
usageClauses.push(
|
|
4549
|
+
`COALESCE(u.provider, 'unavailable') IN (${providers.map(() => "?").join(",")})`,
|
|
4550
|
+
);
|
|
4551
|
+
directClauses.push(
|
|
4552
|
+
`COALESCE(a.provider, 'unavailable') IN (${providers.map(() => "?").join(",")})`,
|
|
4553
|
+
);
|
|
4339
4554
|
usageValues.push(...providers);
|
|
4340
4555
|
directValues.push(...providers);
|
|
4341
4556
|
}
|
|
4342
4557
|
if (models.length > 0) {
|
|
4343
|
-
usageClauses.push(
|
|
4344
|
-
|
|
4558
|
+
usageClauses.push(
|
|
4559
|
+
`COALESCE(u.model, 'unavailable') IN (${models.map(() => "?").join(",")})`,
|
|
4560
|
+
);
|
|
4561
|
+
directClauses.push(
|
|
4562
|
+
`COALESCE(a.model, 'unavailable') IN (${models.map(() => "?").join(",")})`,
|
|
4563
|
+
);
|
|
4345
4564
|
usageValues.push(...models);
|
|
4346
4565
|
directValues.push(...models);
|
|
4347
4566
|
}
|
|
@@ -4387,21 +4606,34 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4387
4606
|
FROM selected GROUP BY day, phase_key ORDER BY day, phase_key`)
|
|
4388
4607
|
.all(...values) as Array<Record<string, unknown>>;
|
|
4389
4608
|
const nullableColumns = [
|
|
4390
|
-
"duration_ms",
|
|
4391
|
-
"
|
|
4609
|
+
"duration_ms",
|
|
4610
|
+
"input_tokens",
|
|
4611
|
+
"cached_input_tokens",
|
|
4612
|
+
"output_tokens",
|
|
4613
|
+
"reasoning_tokens",
|
|
4614
|
+
"total_tokens",
|
|
4615
|
+
"cost_usd",
|
|
4616
|
+
"cost_brl",
|
|
4392
4617
|
] as const;
|
|
4393
4618
|
const additiveColumns = [
|
|
4394
|
-
"eligible_runs",
|
|
4619
|
+
"eligible_runs",
|
|
4620
|
+
"events",
|
|
4621
|
+
"duration_known",
|
|
4622
|
+
"tokens_known",
|
|
4623
|
+
"usd_known",
|
|
4624
|
+
"brl_known",
|
|
4395
4625
|
] as const;
|
|
4396
4626
|
const rollup = (key: string, rows: readonly Record<string, unknown>[]) => {
|
|
4397
4627
|
const result: Record<string, unknown> = { key };
|
|
4398
4628
|
for (const column of additiveColumns)
|
|
4399
|
-
result[column] = rows.reduce(
|
|
4629
|
+
result[column] = rows.reduce(
|
|
4630
|
+
(sum, row) => sum + (typeof row[column] === "number" ? Number(row[column]) : 0),
|
|
4631
|
+
0,
|
|
4632
|
+
);
|
|
4400
4633
|
for (const column of nullableColumns) {
|
|
4401
4634
|
const present = rows.filter((row) => typeof row[column] === "number");
|
|
4402
|
-
result[column] =
|
|
4403
|
-
? null
|
|
4404
|
-
: present.reduce((sum, row) => sum + Number(row[column]), 0);
|
|
4635
|
+
result[column] =
|
|
4636
|
+
present.length === 0 ? null : present.reduce((sum, row) => sum + Number(row[column]), 0);
|
|
4405
4637
|
}
|
|
4406
4638
|
const taskIds = new Set<string>();
|
|
4407
4639
|
for (const row of rows) {
|
|
@@ -4413,10 +4645,18 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4413
4645
|
return result;
|
|
4414
4646
|
};
|
|
4415
4647
|
const phaseKeys = ["conversation", "planning", "execution"] as const;
|
|
4416
|
-
const phaseRows = phaseKeys.map((key) =>
|
|
4648
|
+
const phaseRows = phaseKeys.map((key) =>
|
|
4649
|
+
rollup(
|
|
4650
|
+
key,
|
|
4651
|
+
factRows.filter((row) => row.phase_key === key),
|
|
4652
|
+
),
|
|
4653
|
+
);
|
|
4417
4654
|
phaseRows.push(rollup("total", factRows));
|
|
4418
4655
|
const dailyRows = [...new Set(factRows.map((row) => String(row.day)))].map((day) =>
|
|
4419
|
-
rollup(
|
|
4656
|
+
rollup(
|
|
4657
|
+
day,
|
|
4658
|
+
factRows.filter((row) => String(row.day) === day),
|
|
4659
|
+
),
|
|
4420
4660
|
);
|
|
4421
4661
|
const structuralRows = this.database
|
|
4422
4662
|
.prepare(`WITH canonical_ids AS MATERIALIZED (
|
|
@@ -4449,16 +4689,7 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4449
4689
|
AND (? IS NULL OR day >= ?)
|
|
4450
4690
|
AND (? IS NULL OR day <= ?)
|
|
4451
4691
|
GROUP BY day
|
|
4452
|
-
|
|
4453
|
-
SELECT occurred_day_sp,
|
|
4454
|
-
0, 0, 0,
|
|
4455
|
-
SUM(CASE WHEN artifact_kind='validation' THEN 1 ELSE 0 END),
|
|
4456
|
-
SUM(CASE WHEN artifact_kind='evidence' THEN 1 ELSE 0 END)
|
|
4457
|
-
FROM canonical_artifacts
|
|
4458
|
-
WHERE occurred_day_sp IS NOT NULL
|
|
4459
|
-
AND (? IS NULL OR occurred_day_sp >= ?)
|
|
4460
|
-
AND (? IS NULL OR occurred_day_sp <= ?)
|
|
4461
|
-
GROUP BY occurred_day_sp
|
|
4692
|
+
${artifactStructuralRowsSql()}
|
|
4462
4693
|
)
|
|
4463
4694
|
SELECT day,
|
|
4464
4695
|
SUM(prd_count) AS prd_count,
|
|
@@ -4467,7 +4698,16 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4467
4698
|
SUM(validation_count) AS validation_count,
|
|
4468
4699
|
SUM(evidence_count) AS evidence_count
|
|
4469
4700
|
FROM structural GROUP BY day ORDER BY day`)
|
|
4470
|
-
.all(
|
|
4701
|
+
.all(
|
|
4702
|
+
sourceFrom,
|
|
4703
|
+
sourceFrom,
|
|
4704
|
+
sourceTo,
|
|
4705
|
+
sourceTo,
|
|
4706
|
+
sourceFrom,
|
|
4707
|
+
sourceFrom,
|
|
4708
|
+
sourceTo,
|
|
4709
|
+
sourceTo,
|
|
4710
|
+
) as Array<Record<string, unknown>>;
|
|
4471
4711
|
const number = (value: unknown) => (typeof value === "number" ? value : 0);
|
|
4472
4712
|
const nullable = (value: unknown) => (typeof value === "number" ? value : null);
|
|
4473
4713
|
const metric = (row?: Record<string, unknown>) => {
|
|
@@ -4588,7 +4828,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4588
4828
|
// phases and daily rows. Calling the paginated view here was tempting but
|
|
4589
4829
|
// silently loses multi-value and lineage filters (Task/SPEC/PRD/etc.).
|
|
4590
4830
|
// This remains one bounded SQL aggregate, without transporting Runs.
|
|
4591
|
-
const summaryModelRows = this.database
|
|
4831
|
+
const summaryModelRows = this.database
|
|
4832
|
+
.prepare(`${selected},
|
|
4592
4833
|
segment_totals AS (
|
|
4593
4834
|
SELECT agent_invocation_id, SUM(total_tokens) AS total_tokens,
|
|
4594
4835
|
SUM(cost_usd) AS cost_usd, SUM(cost_brl) AS cost_brl
|
|
@@ -4630,7 +4871,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4630
4871
|
NULL AS total_duration, 0 AS duration_unknown, 0 AS duration_known,
|
|
4631
4872
|
0 AS source_event_count, SUM(total_tokens) AS total_tokens,
|
|
4632
4873
|
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`)
|
|
4874
|
+
FROM components GROUP BY key ORDER BY total_cost_brl DESC, key`)
|
|
4875
|
+
.all(...values) as Array<Record<string, unknown>>;
|
|
4634
4876
|
const model_groups = summaryModelRows.map((row) => {
|
|
4635
4877
|
const duration = nullable(row.total_duration);
|
|
4636
4878
|
const durationUnknown = number(row.duration_unknown);
|
|
@@ -4659,7 +4901,7 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4659
4901
|
coverage: { events: 0, tokens: 0, usd: 0, brl: 0 },
|
|
4660
4902
|
};
|
|
4661
4903
|
});
|
|
4662
|
-
return {
|
|
4904
|
+
return remember({
|
|
4663
4905
|
periods: { daily: periods },
|
|
4664
4906
|
aggregate_usage: {
|
|
4665
4907
|
conversation,
|
|
@@ -4673,7 +4915,7 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4673
4915
|
model_groups,
|
|
4674
4916
|
cost_monitor_daily_duration: durations,
|
|
4675
4917
|
daily_rows: compactDailyRows,
|
|
4676
|
-
};
|
|
4918
|
+
});
|
|
4677
4919
|
}
|
|
4678
4920
|
|
|
4679
4921
|
/** Keyset cursor or LIMIT/OFFSET pagination for materialized work summaries. */
|
|
@@ -4696,7 +4938,11 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4696
4938
|
const from = filter.from?.slice(0, 10) ?? null;
|
|
4697
4939
|
const to = filter.to?.slice(0, 10) ?? null;
|
|
4698
4940
|
const cursorValue = parseSummaryCursor(cursor);
|
|
4699
|
-
const clauses = [
|
|
4941
|
+
const clauses = [
|
|
4942
|
+
"entity_kind = ?",
|
|
4943
|
+
"(? IS NULL OR occurred_day_sp >= ?)",
|
|
4944
|
+
"(? IS NULL OR occurred_day_sp <= ?)",
|
|
4945
|
+
];
|
|
4700
4946
|
const values: Array<string | number | null> = [kind, from, from, to, to];
|
|
4701
4947
|
if (filter.status) {
|
|
4702
4948
|
clauses.push("status = ?");
|
|
@@ -4748,6 +4994,21 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4748
4994
|
};
|
|
4749
4995
|
}
|
|
4750
4996
|
|
|
4997
|
+
/** Canonical page for the Board: keeps telemetry-only historical rows out. */
|
|
4998
|
+
canonicalWorkPage(
|
|
4999
|
+
kind: SqliteSummaryKind,
|
|
5000
|
+
filter: { from: string | null; to: string | null },
|
|
5001
|
+
offset: number,
|
|
5002
|
+
limit: number,
|
|
5003
|
+
) {
|
|
5004
|
+
return this.viewPage("operations_work_list", {
|
|
5005
|
+
...filter,
|
|
5006
|
+
kind: kind === "task" ? "all-tasks" : kind,
|
|
5007
|
+
offset,
|
|
5008
|
+
limit,
|
|
5009
|
+
});
|
|
5010
|
+
}
|
|
5011
|
+
|
|
4751
5012
|
overviewPage(period: "day" | "month" | "year", offset: number, limit: number) {
|
|
4752
5013
|
const total = Number(
|
|
4753
5014
|
(
|
|
@@ -4780,25 +5041,30 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4780
5041
|
};
|
|
4781
5042
|
}
|
|
4782
5043
|
|
|
4783
|
-
/**
|
|
4784
|
-
* Generation-local cache of the complete public page response. It caches
|
|
4785
|
-
* post-enrichment rows and totals, never raw facts, so the daily Operations
|
|
4786
|
-
* duration and Cost Monitor pre-pagination subtotals retain their contracts.
|
|
4787
|
-
*/
|
|
4788
5044
|
viewPage(
|
|
4789
5045
|
view: SqliteViewName,
|
|
4790
5046
|
input: {
|
|
4791
|
-
from?: string | null;
|
|
4792
|
-
|
|
4793
|
-
|
|
5047
|
+
from?: string | null;
|
|
5048
|
+
to?: string | null;
|
|
5049
|
+
query?: string;
|
|
5050
|
+
status?: string;
|
|
5051
|
+
risk?: string;
|
|
5052
|
+
provider?: string;
|
|
5053
|
+
model?: string;
|
|
5054
|
+
agent?: string;
|
|
5055
|
+
phase?: string;
|
|
5056
|
+
origin?: string;
|
|
5057
|
+
workType?: string;
|
|
5058
|
+
priority?: string;
|
|
5059
|
+
kind?: "all" | "all-tasks" | "task" | "standalone-task" | "prd" | "spec";
|
|
5060
|
+
offset?: number;
|
|
5061
|
+
limit?: number;
|
|
4794
5062
|
},
|
|
4795
5063
|
): ReturnType<SqliteReadModelFileSystem["uncachedViewPage"]> {
|
|
4796
|
-
// The key includes every filter and pagination input. The cache is scoped
|
|
4797
|
-
// to the immutable opened SQLite generation and reopen() clears it, so a
|
|
4798
|
-
// launcher refresh cannot expose a page from the previous snapshot.
|
|
4799
5064
|
const key = `${view}:${JSON.stringify(input)}`;
|
|
4800
5065
|
const cached = this.viewPageResultCache.get(key);
|
|
4801
|
-
if (cached !== undefined)
|
|
5066
|
+
if (cached !== undefined)
|
|
5067
|
+
return structuredClone(cached) as ReturnType<SqliteReadModelFileSystem["uncachedViewPage"]>;
|
|
4802
5068
|
const result = this.uncachedViewPage(view, input);
|
|
4803
5069
|
if (this.viewPageResultCache.size >= 256) {
|
|
4804
5070
|
const oldest = this.viewPageResultCache.keys().next().value as string | undefined;
|
|
@@ -4824,10 +5090,151 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4824
5090
|
origin?: string;
|
|
4825
5091
|
workType?: string;
|
|
4826
5092
|
priority?: string;
|
|
5093
|
+
kind?: "all" | "all-tasks" | "task" | "standalone-task" | "prd" | "spec";
|
|
4827
5094
|
offset?: number;
|
|
4828
5095
|
limit?: number;
|
|
4829
5096
|
},
|
|
4830
5097
|
) {
|
|
5098
|
+
const isUnfilteredAllTimeCostPage =
|
|
5099
|
+
!input.from &&
|
|
5100
|
+
!input.to &&
|
|
5101
|
+
!input.query &&
|
|
5102
|
+
!input.status &&
|
|
5103
|
+
!input.risk &&
|
|
5104
|
+
!input.provider &&
|
|
5105
|
+
!input.model &&
|
|
5106
|
+
!input.agent &&
|
|
5107
|
+
!input.phase &&
|
|
5108
|
+
!input.origin &&
|
|
5109
|
+
!input.workType &&
|
|
5110
|
+
!input.priority;
|
|
5111
|
+
const isDateOnlyCostInvocationPage =
|
|
5112
|
+
view === "cost_monitor_agent_invocations" &&
|
|
5113
|
+
!input.query &&
|
|
5114
|
+
!input.status &&
|
|
5115
|
+
!input.risk &&
|
|
5116
|
+
!input.provider &&
|
|
5117
|
+
!input.model &&
|
|
5118
|
+
!input.agent &&
|
|
5119
|
+
!input.phase &&
|
|
5120
|
+
!input.origin &&
|
|
5121
|
+
!input.workType &&
|
|
5122
|
+
!input.priority;
|
|
5123
|
+
if (isDateOnlyCostInvocationPage) {
|
|
5124
|
+
const projection = filterCostInvocationProjection(
|
|
5125
|
+
this.technicalPayloadRows("cost_agent_invocation_projection_v1"),
|
|
5126
|
+
input,
|
|
5127
|
+
);
|
|
5128
|
+
if (projection.length > 0) {
|
|
5129
|
+
const limit = Math.min(200, Math.max(1, input.limit ?? 100));
|
|
5130
|
+
const offset = Math.max(0, input.offset ?? 0);
|
|
5131
|
+
const rows = projection.slice(offset, offset + limit);
|
|
5132
|
+
return {
|
|
5133
|
+
data_source: "sqlite_read_model" as const,
|
|
5134
|
+
view,
|
|
5135
|
+
offset,
|
|
5136
|
+
limit,
|
|
5137
|
+
total_count: projection.length,
|
|
5138
|
+
has_more: offset + rows.length < projection.length,
|
|
5139
|
+
rows,
|
|
5140
|
+
};
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
const remoteAggregateDimensions: Partial<Record<SqliteViewName, string>> = {
|
|
5144
|
+
cost_monitor_zaip_agents: "agent",
|
|
5145
|
+
cost_monitor_executions: "execution",
|
|
5146
|
+
cost_monitor_tasks: "task",
|
|
5147
|
+
cost_monitor_conversations: "conversation",
|
|
5148
|
+
};
|
|
5149
|
+
const remoteAggregateDimension = remoteAggregateDimensions[view];
|
|
5150
|
+
if (isUnfilteredAllTimeCostPage && remoteAggregateDimension !== undefined) {
|
|
5151
|
+
const projection = this.technicalPayloadRows("cost_dimension_aggregate_v1").filter(
|
|
5152
|
+
(row) => row.dimension === remoteAggregateDimension,
|
|
5153
|
+
);
|
|
5154
|
+
const rows = projection
|
|
5155
|
+
.filter((row) => row.row_kind === "row")
|
|
5156
|
+
.map((row) =>
|
|
5157
|
+
view === "cost_monitor_zaip_agents"
|
|
5158
|
+
? {
|
|
5159
|
+
key: row.key,
|
|
5160
|
+
label: row.label,
|
|
5161
|
+
persona: row.persona,
|
|
5162
|
+
job: row.job,
|
|
5163
|
+
latest_started_at: row.latest_started_at,
|
|
5164
|
+
invocation_count: row.invocation_count,
|
|
5165
|
+
task_count: row.task_count,
|
|
5166
|
+
total_duration: row.total_duration,
|
|
5167
|
+
duration_unknown: row.duration_unknown,
|
|
5168
|
+
duration_known: row.duration_known,
|
|
5169
|
+
source_event_count: row.source_event_count,
|
|
5170
|
+
total_tokens: row.total_tokens,
|
|
5171
|
+
total_cost_usd: row.total_cost_usd,
|
|
5172
|
+
total_cost_brl: row.total_cost_brl,
|
|
5173
|
+
}
|
|
5174
|
+
: {
|
|
5175
|
+
id: row.id,
|
|
5176
|
+
key: row.key,
|
|
5177
|
+
title: row.title,
|
|
5178
|
+
status: row.status,
|
|
5179
|
+
risk: row.risk,
|
|
5180
|
+
first_day: row.first_day,
|
|
5181
|
+
last_day: row.last_day,
|
|
5182
|
+
occurred_at: row.occurred_at,
|
|
5183
|
+
invocation_count: row.invocation_count,
|
|
5184
|
+
llm_call_count: row.llm_call_count,
|
|
5185
|
+
total_duration: row.total_duration,
|
|
5186
|
+
total_input_tokens: row.total_input_tokens,
|
|
5187
|
+
total_cached_input_tokens: row.total_cached_input_tokens,
|
|
5188
|
+
total_output_tokens: row.total_output_tokens,
|
|
5189
|
+
total_reasoning_tokens: row.total_reasoning_tokens,
|
|
5190
|
+
total_tokens: row.total_tokens,
|
|
5191
|
+
total_cost_usd: row.total_cost_usd,
|
|
5192
|
+
total_cost_brl: row.total_cost_brl,
|
|
5193
|
+
},
|
|
5194
|
+
)
|
|
5195
|
+
.sort(
|
|
5196
|
+
(left, right) =>
|
|
5197
|
+
Number(right.total_cost_brl ?? -1) - Number(left.total_cost_brl ?? -1) ||
|
|
5198
|
+
String(left.key ?? "").localeCompare(String(right.key ?? "")),
|
|
5199
|
+
) as Array<Record<string, unknown>>;
|
|
5200
|
+
const totals = projection.find((row) => row.row_kind === "totals");
|
|
5201
|
+
if (rows.length > 0 && totals !== undefined) {
|
|
5202
|
+
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
5203
|
+
const offset = Math.max(0, input.offset ?? 0);
|
|
5204
|
+
const page = rows.slice(offset, offset + limit);
|
|
5205
|
+
const publicTotals =
|
|
5206
|
+
view !== "cost_monitor_zaip_agents"
|
|
5207
|
+
? {
|
|
5208
|
+
entity_count: totals.entity_count,
|
|
5209
|
+
invocation_count: totals.invocation_count,
|
|
5210
|
+
total_duration: totals.total_duration,
|
|
5211
|
+
total_tokens: totals.total_tokens,
|
|
5212
|
+
total_cost_usd: totals.total_cost_usd,
|
|
5213
|
+
total_cost_brl: totals.total_cost_brl,
|
|
5214
|
+
}
|
|
5215
|
+
: {
|
|
5216
|
+
invocation_count: totals.invocation_count,
|
|
5217
|
+
task_count: totals.task_count,
|
|
5218
|
+
total_duration: totals.total_duration,
|
|
5219
|
+
duration_unknown: totals.duration_unknown,
|
|
5220
|
+
duration_known: totals.duration_known,
|
|
5221
|
+
source_event_count: totals.source_event_count,
|
|
5222
|
+
total_tokens: totals.total_tokens,
|
|
5223
|
+
total_cost_usd: totals.total_cost_usd,
|
|
5224
|
+
total_cost_brl: totals.total_cost_brl,
|
|
5225
|
+
};
|
|
5226
|
+
return {
|
|
5227
|
+
data_source: "sqlite_read_model" as const,
|
|
5228
|
+
view,
|
|
5229
|
+
offset,
|
|
5230
|
+
limit,
|
|
5231
|
+
total_count: rows.length,
|
|
5232
|
+
has_more: offset + page.length < rows.length,
|
|
5233
|
+
totals: publicTotals,
|
|
5234
|
+
rows: page,
|
|
5235
|
+
};
|
|
5236
|
+
}
|
|
5237
|
+
}
|
|
4831
5238
|
const cacheableUnfilteredViews: readonly SqliteViewName[] = [
|
|
4832
5239
|
"operations_work_kanban",
|
|
4833
5240
|
"operations_executions",
|
|
@@ -4836,9 +5243,18 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4836
5243
|
];
|
|
4837
5244
|
if (
|
|
4838
5245
|
cacheableUnfilteredViews.includes(view) &&
|
|
4839
|
-
!input.from &&
|
|
4840
|
-
!input.
|
|
4841
|
-
!input.
|
|
5246
|
+
!input.from &&
|
|
5247
|
+
!input.to &&
|
|
5248
|
+
!input.query &&
|
|
5249
|
+
!input.status &&
|
|
5250
|
+
!input.risk &&
|
|
5251
|
+
!input.provider &&
|
|
5252
|
+
!input.model &&
|
|
5253
|
+
!input.agent &&
|
|
5254
|
+
!input.phase &&
|
|
5255
|
+
!input.origin &&
|
|
5256
|
+
!input.workType &&
|
|
5257
|
+
!input.priority
|
|
4842
5258
|
) {
|
|
4843
5259
|
const rows = this.unfilteredViewRows(view);
|
|
4844
5260
|
const limit = Math.min(200, Math.max(1, input.limit ?? 100));
|
|
@@ -4854,39 +5270,24 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4854
5270
|
rows: page,
|
|
4855
5271
|
};
|
|
4856
5272
|
}
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
!input.from &&
|
|
4865
|
-
!input.to &&
|
|
4866
|
-
!input.query &&
|
|
4867
|
-
!input.status &&
|
|
4868
|
-
!input.risk
|
|
4869
|
-
) {
|
|
4870
|
-
const rows = this.operationsWorkListRows();
|
|
4871
|
-
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
4872
|
-
const offset = Math.max(0, input.offset ?? 0);
|
|
4873
|
-
const page = rows.slice(offset, offset + limit);
|
|
5273
|
+
if (view === "operations_work_list") {
|
|
5274
|
+
const payload =
|
|
5275
|
+
input.from || input.to
|
|
5276
|
+
? this.firstFoldMonthlyQuick({ from: input.from ?? null, to: input.to ?? null })
|
|
5277
|
+
: this.technicalPayloadRows("first_fold_all_time_board_cache")[0];
|
|
5278
|
+
const cached = operationsWorkListPayloadPage(input, payload);
|
|
5279
|
+
const result = cached ?? operationsWorkListSqlPage(this.database, input);
|
|
4874
5280
|
return {
|
|
4875
5281
|
data_source: "sqlite_read_model" as const,
|
|
4876
5282
|
view,
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
total_count: rows.length,
|
|
4880
|
-
has_more: offset + page.length < rows.length,
|
|
4881
|
-
rows: page,
|
|
5283
|
+
...result,
|
|
5284
|
+
rows: attachOperationsWorkListDevelopers(this.database, result.rows, input),
|
|
4882
5285
|
};
|
|
4883
5286
|
}
|
|
4884
5287
|
if (view === "cost_monitor_agent_invocations") {
|
|
4885
|
-
const sourceDay =
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
`(? IS NULL OR ${sourceDay} <= ?)`,
|
|
4889
|
-
];
|
|
5288
|
+
const sourceDay =
|
|
5289
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours')";
|
|
5290
|
+
const clauses = [`(? IS NULL OR ${sourceDay} >= ?)`, `(? IS NULL OR ${sourceDay} <= ?)`];
|
|
4890
5291
|
const from = costMonitorFilterDay(input.from);
|
|
4891
5292
|
const to = costMonitorFilterDay(input.to);
|
|
4892
5293
|
const values: Array<string | number | null> = [from, from, to, to];
|
|
@@ -4902,7 +5303,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4902
5303
|
const usageSegmentClauses: string[] = [];
|
|
4903
5304
|
if (input.provider) {
|
|
4904
5305
|
if (input.provider === "unavailable") {
|
|
4905
|
-
clauses.push(
|
|
5306
|
+
clauses.push(
|
|
5307
|
+
"((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')))",
|
|
5308
|
+
);
|
|
4906
5309
|
} else {
|
|
4907
5310
|
usageSegmentClauses.push("f.provider=?");
|
|
4908
5311
|
values.push(input.provider);
|
|
@@ -4910,14 +5313,18 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4910
5313
|
}
|
|
4911
5314
|
if (input.model) {
|
|
4912
5315
|
if (input.model === "unavailable") {
|
|
4913
|
-
clauses.push(
|
|
5316
|
+
clauses.push(
|
|
5317
|
+
"((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')))",
|
|
5318
|
+
);
|
|
4914
5319
|
} else {
|
|
4915
5320
|
usageSegmentClauses.push("f.model=?");
|
|
4916
5321
|
values.push(input.model);
|
|
4917
5322
|
}
|
|
4918
5323
|
}
|
|
4919
5324
|
if (usageSegmentClauses.length > 0)
|
|
4920
|
-
clauses.push(
|
|
5325
|
+
clauses.push(
|
|
5326
|
+
`EXISTS (SELECT 1 FROM invocation_usage f WHERE f.agent_invocation_id=i.agent_invocation_id AND ${usageSegmentClauses.join(" AND ")})`,
|
|
5327
|
+
);
|
|
4921
5328
|
if (input.query) {
|
|
4922
5329
|
clauses.push("(i.agent_invocation_id LIKE ? OR i.task_id LIKE ? OR i.execution_id LIKE ?)");
|
|
4923
5330
|
values.push(`%${input.query}%`, `%${input.query}%`, `%${input.query}%`);
|
|
@@ -4925,16 +5332,18 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4925
5332
|
const where = clauses.join(" AND ");
|
|
4926
5333
|
const limit = Math.min(200, Math.max(1, input.limit ?? 100));
|
|
4927
5334
|
const offset = Math.max(0, input.offset ?? 0);
|
|
4928
|
-
|
|
5335
|
+
const total_count = Number(
|
|
4929
5336
|
(
|
|
4930
5337
|
this.database
|
|
4931
|
-
.prepare(
|
|
5338
|
+
.prepare(
|
|
5339
|
+
`SELECT COUNT(*) AS count FROM cost_monitor_agent_invocations i WHERE ${where}`,
|
|
5340
|
+
)
|
|
4932
5341
|
.get(...values) as { count: number }
|
|
4933
5342
|
).count,
|
|
4934
5343
|
);
|
|
4935
|
-
|
|
5344
|
+
const rows = this.database
|
|
4936
5345
|
.prepare(
|
|
4937
|
-
`SELECT i.* FROM
|
|
5346
|
+
`SELECT i.* FROM cost_monitor_agent_invocations i WHERE ${where} ORDER BY i.occurred_at DESC, i.agent_invocation_id DESC LIMIT ? OFFSET ?`,
|
|
4938
5347
|
)
|
|
4939
5348
|
.all(...values, limit, offset) as Array<Record<string, unknown>>;
|
|
4940
5349
|
return {
|
|
@@ -4958,14 +5367,53 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4958
5367
|
cost_monitor_providers: "provider",
|
|
4959
5368
|
};
|
|
4960
5369
|
const filteredCostGroup = filteredCostGroups[view];
|
|
5370
|
+
if (
|
|
5371
|
+
(filteredCostGroup === "agent" ||
|
|
5372
|
+
filteredCostGroup === "model" ||
|
|
5373
|
+
filteredCostGroup === "provider") &&
|
|
5374
|
+
isDateOnlyCostPage(input)
|
|
5375
|
+
) {
|
|
5376
|
+
const from = costMonitorFilterDay(input.from) ?? "0000-01-01";
|
|
5377
|
+
const to = costMonitorFilterDay(input.to) ?? "9999-12-31";
|
|
5378
|
+
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
5379
|
+
const offset = Math.max(0, input.offset ?? 0);
|
|
5380
|
+
const fast = readDateOnlyCostDimensionPage(
|
|
5381
|
+
this.database,
|
|
5382
|
+
filteredCostGroup,
|
|
5383
|
+
from,
|
|
5384
|
+
to,
|
|
5385
|
+
limit,
|
|
5386
|
+
offset,
|
|
5387
|
+
);
|
|
5388
|
+
return {
|
|
5389
|
+
data_source: "sqlite_read_model" as const,
|
|
5390
|
+
view,
|
|
5391
|
+
offset,
|
|
5392
|
+
limit,
|
|
5393
|
+
total_count: fast.total_count,
|
|
5394
|
+
has_more: offset + fast.rows.length < fast.total_count,
|
|
5395
|
+
totals: fast.totals,
|
|
5396
|
+
rows: fast.rows,
|
|
5397
|
+
};
|
|
5398
|
+
}
|
|
4961
5399
|
const isUnfilteredCostDimension =
|
|
4962
5400
|
(filteredCostGroup === "model" || filteredCostGroup === "provider") &&
|
|
4963
|
-
!input.from &&
|
|
4964
|
-
!input.
|
|
4965
|
-
!input.
|
|
5401
|
+
!input.from &&
|
|
5402
|
+
!input.to &&
|
|
5403
|
+
!input.query &&
|
|
5404
|
+
!input.status &&
|
|
5405
|
+
!input.risk &&
|
|
5406
|
+
!input.provider &&
|
|
5407
|
+
!input.model &&
|
|
5408
|
+
!input.agent &&
|
|
5409
|
+
!input.phase &&
|
|
5410
|
+
!input.origin &&
|
|
5411
|
+
!input.workType &&
|
|
5412
|
+
!input.priority;
|
|
4966
5413
|
if (isUnfilteredCostDimension) {
|
|
4967
5414
|
const dimension = filteredCostGroup;
|
|
4968
|
-
const payloadRows = this.database
|
|
5415
|
+
const payloadRows = this.database
|
|
5416
|
+
.prepare(`SELECT
|
|
4969
5417
|
json_extract(payload_json, '$.key') AS key,
|
|
4970
5418
|
json_extract(payload_json, '$.label') AS label,
|
|
4971
5419
|
json_extract(payload_json, '$.invocation_count') AS invocation_count,
|
|
@@ -4978,7 +5426,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4978
5426
|
FROM zaip_aios_activities
|
|
4979
5427
|
WHERE payload_type='cost_dimension_aggregate_v1'
|
|
4980
5428
|
AND json_extract(payload_json, '$.dimension')=?
|
|
4981
|
-
ORDER BY total_cost_brl DESC, key`)
|
|
5429
|
+
ORDER BY total_cost_brl DESC, key`)
|
|
5430
|
+
.all(dimension) as Array<Record<string, unknown>>;
|
|
4982
5431
|
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
4983
5432
|
const offset = Math.max(0, input.offset ?? 0);
|
|
4984
5433
|
const rows = payloadRows.slice(offset, offset + limit);
|
|
@@ -4995,17 +5444,46 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
4995
5444
|
if (filteredCostGroup === "agent") {
|
|
4996
5445
|
const clauses = ["i.zaip_agent IS NOT NULL"];
|
|
4997
5446
|
const values: Array<string | number> = [];
|
|
4998
|
-
if (input.from) {
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5447
|
+
if (input.from) {
|
|
5448
|
+
clauses.push(
|
|
5449
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') >= ?",
|
|
5450
|
+
);
|
|
5451
|
+
values.push(input.from.slice(0, 10));
|
|
5452
|
+
}
|
|
5453
|
+
if (input.to) {
|
|
5454
|
+
clauses.push(
|
|
5455
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') <= ?",
|
|
5456
|
+
);
|
|
5457
|
+
values.push(input.to.slice(0, 10));
|
|
5458
|
+
}
|
|
5459
|
+
if (input.agent) {
|
|
5460
|
+
clauses.push("i.zaip_agent = ?");
|
|
5461
|
+
values.push(input.agent);
|
|
5462
|
+
}
|
|
5463
|
+
if (input.phase) {
|
|
5464
|
+
clauses.push("i.phase = ?");
|
|
5465
|
+
values.push(input.phase);
|
|
5466
|
+
}
|
|
5467
|
+
if (input.status) {
|
|
5468
|
+
clauses.push("i.status = ?");
|
|
5469
|
+
values.push(input.status);
|
|
5470
|
+
}
|
|
5003
5471
|
// This read model does not materialize canonical Task priority. Keep
|
|
5004
5472
|
// the filtered view safe and avoid substituting a different field.
|
|
5005
5473
|
if (input.priority) clauses.push("0");
|
|
5006
|
-
if (input.risk) {
|
|
5474
|
+
if (input.risk) {
|
|
5475
|
+
clauses.push("t.risk = ?");
|
|
5476
|
+
values.push(input.risk);
|
|
5477
|
+
}
|
|
5007
5478
|
if (input.origin) {
|
|
5008
|
-
const origin =
|
|
5479
|
+
const origin =
|
|
5480
|
+
input.origin === "prd"
|
|
5481
|
+
? "i.prd_id IS NOT NULL"
|
|
5482
|
+
: input.origin === "spec"
|
|
5483
|
+
? "i.prd_id IS NULL AND i.spec_id IS NOT NULL"
|
|
5484
|
+
: input.origin === "task"
|
|
5485
|
+
? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL"
|
|
5486
|
+
: "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL";
|
|
5009
5487
|
clauses.push(origin);
|
|
5010
5488
|
}
|
|
5011
5489
|
// SQLite invocation facts do not contain canonical work_type. A work-type
|
|
@@ -5013,33 +5491,82 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5013
5491
|
if (input.workType) clauses.push("0");
|
|
5014
5492
|
const segment: string[] = [];
|
|
5015
5493
|
if (input.provider) {
|
|
5016
|
-
if (input.provider === "unavailable")
|
|
5017
|
-
|
|
5494
|
+
if (input.provider === "unavailable")
|
|
5495
|
+
clauses.push(
|
|
5496
|
+
"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')",
|
|
5497
|
+
);
|
|
5498
|
+
else {
|
|
5499
|
+
segment.push("f.provider = ?");
|
|
5500
|
+
values.push(input.provider);
|
|
5501
|
+
}
|
|
5018
5502
|
}
|
|
5019
5503
|
if (input.model) {
|
|
5020
|
-
if (input.model === "unavailable")
|
|
5021
|
-
|
|
5504
|
+
if (input.model === "unavailable")
|
|
5505
|
+
clauses.push(
|
|
5506
|
+
"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')",
|
|
5507
|
+
);
|
|
5508
|
+
else {
|
|
5509
|
+
segment.push("f.model = ?");
|
|
5510
|
+
values.push(input.model);
|
|
5511
|
+
}
|
|
5022
5512
|
}
|
|
5023
|
-
if (segment.length > 0)
|
|
5513
|
+
if (segment.length > 0)
|
|
5514
|
+
clauses.push(
|
|
5515
|
+
`EXISTS (SELECT 1 FROM invocation_usage f WHERE f.agent_invocation_id=i.agent_invocation_id AND ${segment.join(" AND ")})`,
|
|
5516
|
+
);
|
|
5024
5517
|
if (input.query) {
|
|
5025
5518
|
const query = `%${input.query}%`;
|
|
5026
|
-
clauses.push(
|
|
5519
|
+
clauses.push(
|
|
5520
|
+
"(i.zaip_agent LIKE ? OR i.zaip_agent_persona LIKE ? OR i.zaip_agent_job LIKE ?)",
|
|
5521
|
+
);
|
|
5027
5522
|
values.push(query, query, query);
|
|
5028
5523
|
}
|
|
5029
5524
|
// This is deliberately the same financial fact population as the Cost
|
|
5030
5525
|
// Monitor summary, grouped by the factual activity.zaip_agent. It is
|
|
5031
5526
|
// not an invocation-only approximation.
|
|
5032
|
-
const sourceDay =
|
|
5033
|
-
|
|
5527
|
+
const sourceDay =
|
|
5528
|
+
"date(COALESCE(a.source_started_at, a.source_occurred_at, a.occurred_at), '-3 hours')";
|
|
5529
|
+
const activityClauses = [
|
|
5530
|
+
"a.activity_source_kind <> 'agent_run'",
|
|
5531
|
+
"a.zaip_agent IS NOT NULL",
|
|
5532
|
+
"(a.total_tokens IS NOT NULL OR a.cost_brl IS NOT NULL OR a.cost_usd IS NOT NULL)",
|
|
5533
|
+
];
|
|
5034
5534
|
const activityValues: Array<string | number> = [];
|
|
5035
|
-
if (input.from) {
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
if (input.
|
|
5535
|
+
if (input.from) {
|
|
5536
|
+
activityClauses.push(`${sourceDay} >= ?`);
|
|
5537
|
+
activityValues.push(input.from.slice(0, 10));
|
|
5538
|
+
}
|
|
5539
|
+
if (input.to) {
|
|
5540
|
+
activityClauses.push(`${sourceDay} <= ?`);
|
|
5541
|
+
activityValues.push(input.to.slice(0, 10));
|
|
5542
|
+
}
|
|
5543
|
+
if (input.agent) {
|
|
5544
|
+
activityClauses.push("a.zaip_agent = ?");
|
|
5545
|
+
activityValues.push(input.agent);
|
|
5546
|
+
}
|
|
5547
|
+
if (input.phase) {
|
|
5548
|
+
activityClauses.push("a.phase = ?");
|
|
5549
|
+
activityValues.push(input.phase);
|
|
5550
|
+
}
|
|
5551
|
+
if (input.status) {
|
|
5552
|
+
activityClauses.push("a.status = ?");
|
|
5553
|
+
activityValues.push(input.status);
|
|
5554
|
+
}
|
|
5040
5555
|
if (input.priority || input.workType) activityClauses.push("0");
|
|
5041
|
-
if (input.risk) {
|
|
5042
|
-
|
|
5556
|
+
if (input.risk) {
|
|
5557
|
+
activityClauses.push("activity_task.risk = ?");
|
|
5558
|
+
activityValues.push(input.risk);
|
|
5559
|
+
}
|
|
5560
|
+
if (input.origin)
|
|
5561
|
+
activityClauses.push(
|
|
5562
|
+
input.origin === "prd"
|
|
5563
|
+
? "a.prd_id IS NOT NULL"
|
|
5564
|
+
: input.origin === "spec"
|
|
5565
|
+
? "a.prd_id IS NULL AND a.spec_id IS NOT NULL"
|
|
5566
|
+
: input.origin === "task"
|
|
5567
|
+
? "a.prd_id IS NULL AND a.spec_id IS NULL AND a.task_id IS NOT NULL"
|
|
5568
|
+
: "a.prd_id IS NULL AND a.spec_id IS NULL AND a.task_id IS NULL",
|
|
5569
|
+
);
|
|
5043
5570
|
if (input.provider || input.model) {
|
|
5044
5571
|
const direct: string[] = [];
|
|
5045
5572
|
const usage: string[] = [];
|
|
@@ -5053,30 +5580,86 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5053
5580
|
usage.push("COALESCE(u.model, 'unavailable') = ?");
|
|
5054
5581
|
activityValues.push(input.model);
|
|
5055
5582
|
}
|
|
5056
|
-
activityClauses.push(
|
|
5583
|
+
activityClauses.push(
|
|
5584
|
+
`((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 ")})))`,
|
|
5585
|
+
);
|
|
5057
5586
|
if (input.provider) activityValues.push(input.provider);
|
|
5058
5587
|
if (input.model) activityValues.push(input.model);
|
|
5059
5588
|
}
|
|
5060
|
-
if (input.query) {
|
|
5589
|
+
if (input.query) {
|
|
5590
|
+
const query = `%${input.query}%`;
|
|
5591
|
+
activityClauses.push(
|
|
5592
|
+
"(a.zaip_agent LIKE ? OR a.zaip_agent_persona LIKE ? OR a.zaip_agent_job LIKE ?)",
|
|
5593
|
+
);
|
|
5594
|
+
activityValues.push(query, query, query);
|
|
5595
|
+
}
|
|
5061
5596
|
const where = clauses.join(" AND ");
|
|
5062
|
-
const invocationClauses = ["i.zaip_agent IS NOT NULL"
|
|
5597
|
+
const invocationClauses = ["i.zaip_agent IS NOT NULL"];
|
|
5063
5598
|
const invocationValues: Array<string | number> = [];
|
|
5064
|
-
const invocationSourceDay =
|
|
5065
|
-
|
|
5066
|
-
if (input.
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5599
|
+
const invocationSourceDay =
|
|
5600
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours')";
|
|
5601
|
+
if (input.from) {
|
|
5602
|
+
invocationClauses.push(`${invocationSourceDay} >= ?`);
|
|
5603
|
+
invocationValues.push(input.from.slice(0, 10));
|
|
5604
|
+
// Keep the source-day predicate for historical correctness, while
|
|
5605
|
+
// using the materialized day key to seek the immutable fact first.
|
|
5606
|
+
invocationClauses.push("i.occurred_day_sp >= ?");
|
|
5607
|
+
invocationValues.push(input.from.slice(0, 10));
|
|
5608
|
+
}
|
|
5609
|
+
if (input.to) {
|
|
5610
|
+
invocationClauses.push(`${invocationSourceDay} <= ?`);
|
|
5611
|
+
invocationValues.push(input.to.slice(0, 10));
|
|
5612
|
+
invocationClauses.push("i.occurred_day_sp <= ?");
|
|
5613
|
+
invocationValues.push(input.to.slice(0, 10));
|
|
5614
|
+
}
|
|
5615
|
+
if (input.agent) {
|
|
5616
|
+
invocationClauses.push("i.zaip_agent = ?");
|
|
5617
|
+
invocationValues.push(input.agent);
|
|
5618
|
+
}
|
|
5619
|
+
if (input.phase) {
|
|
5620
|
+
invocationClauses.push("i.phase = ?");
|
|
5621
|
+
invocationValues.push(input.phase);
|
|
5622
|
+
}
|
|
5623
|
+
if (input.status) {
|
|
5624
|
+
invocationClauses.push("i.status = ?");
|
|
5625
|
+
invocationValues.push(input.status);
|
|
5626
|
+
}
|
|
5070
5627
|
if (input.priority || input.workType) invocationClauses.push("0");
|
|
5071
|
-
if (input.risk) {
|
|
5072
|
-
|
|
5628
|
+
if (input.risk) {
|
|
5629
|
+
invocationClauses.push("invocation_task.risk = ?");
|
|
5630
|
+
invocationValues.push(input.risk);
|
|
5631
|
+
}
|
|
5632
|
+
if (input.origin)
|
|
5633
|
+
invocationClauses.push(
|
|
5634
|
+
input.origin === "prd"
|
|
5635
|
+
? "i.prd_id IS NOT NULL"
|
|
5636
|
+
: input.origin === "spec"
|
|
5637
|
+
? "i.prd_id IS NULL AND i.spec_id IS NOT NULL"
|
|
5638
|
+
: input.origin === "task"
|
|
5639
|
+
? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL"
|
|
5640
|
+
: "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL",
|
|
5641
|
+
);
|
|
5073
5642
|
if (input.provider || input.model) {
|
|
5074
5643
|
const segment: string[] = [];
|
|
5075
|
-
if (input.provider) {
|
|
5076
|
-
|
|
5077
|
-
|
|
5644
|
+
if (input.provider) {
|
|
5645
|
+
segment.push("COALESCE(u.provider, 'unavailable') = ?");
|
|
5646
|
+
invocationValues.push(input.provider);
|
|
5647
|
+
}
|
|
5648
|
+
if (input.model) {
|
|
5649
|
+
segment.push("COALESCE(u.model, 'unavailable') = ?");
|
|
5650
|
+
invocationValues.push(input.model);
|
|
5651
|
+
}
|
|
5652
|
+
invocationClauses.push(
|
|
5653
|
+
`EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id=i.agent_invocation_id AND ${segment.join(" AND ")})`,
|
|
5654
|
+
);
|
|
5655
|
+
}
|
|
5656
|
+
if (input.query) {
|
|
5657
|
+
const query = `%${input.query}%`;
|
|
5658
|
+
invocationClauses.push(
|
|
5659
|
+
"(i.zaip_agent LIKE ? OR i.zaip_agent_persona LIKE ? OR i.zaip_agent_job LIKE ?)",
|
|
5660
|
+
);
|
|
5661
|
+
invocationValues.push(query, query, query);
|
|
5078
5662
|
}
|
|
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
5663
|
const groupedFacts = `WITH agent_facts AS (
|
|
5081
5664
|
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
5665
|
i.occurred_at, i.task_id, i.duration_ms, i.source_event_count, i.total_tokens, i.cost_usd, i.cost_brl
|
|
@@ -5090,63 +5673,171 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5090
5673
|
)`;
|
|
5091
5674
|
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
5092
5675
|
const offset = Math.max(0, input.offset ?? 0);
|
|
5093
|
-
const total_count = Number(
|
|
5676
|
+
const total_count = Number(
|
|
5677
|
+
(
|
|
5678
|
+
this.database
|
|
5679
|
+
.prepare(`${groupedFacts} SELECT COUNT(DISTINCT key) AS count FROM agent_facts`)
|
|
5680
|
+
.get(...invocationValues, ...activityValues) as { count: number }
|
|
5681
|
+
).count,
|
|
5682
|
+
);
|
|
5094
5683
|
// Activity facts retain the catalog snapshot used when they were
|
|
5095
5684
|
// materialized. The current dimension is a safe fallback for older
|
|
5096
5685
|
// snapshots that predate those fields: it supplies only catalog display
|
|
5097
5686
|
// metadata, never tokens, cost, duration, or attribution.
|
|
5098
|
-
const totals = this.database
|
|
5099
|
-
|
|
5100
|
-
|
|
5687
|
+
const totals = this.database
|
|
5688
|
+
.prepare(
|
|
5689
|
+
`${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`,
|
|
5690
|
+
)
|
|
5691
|
+
.get(...invocationValues, ...activityValues) as Record<string, unknown>;
|
|
5692
|
+
const rows = this.database
|
|
5693
|
+
.prepare(
|
|
5694
|
+
`${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 ?`,
|
|
5695
|
+
)
|
|
5696
|
+
.all(...invocationValues, ...activityValues, limit, offset) as Array<
|
|
5697
|
+
Record<string, unknown>
|
|
5698
|
+
>;
|
|
5699
|
+
return {
|
|
5700
|
+
data_source: "sqlite_read_model" as const,
|
|
5701
|
+
view,
|
|
5702
|
+
offset,
|
|
5703
|
+
limit,
|
|
5704
|
+
total_count,
|
|
5705
|
+
has_more: offset + rows.length < total_count,
|
|
5706
|
+
totals,
|
|
5707
|
+
rows,
|
|
5708
|
+
};
|
|
5101
5709
|
}
|
|
5102
5710
|
if (filteredCostGroup === "model") {
|
|
5103
5711
|
const clauses = ["1=1"];
|
|
5104
5712
|
const values: Array<string | number> = [];
|
|
5105
|
-
|
|
5106
|
-
if (input.
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5713
|
+
const dimensionDateFilter = costDimensionFactDateFilter(input);
|
|
5714
|
+
if (input.from) {
|
|
5715
|
+
clauses.push(
|
|
5716
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') >= ?",
|
|
5717
|
+
);
|
|
5718
|
+
values.push(input.from.slice(0, 10));
|
|
5719
|
+
}
|
|
5720
|
+
if (input.to) {
|
|
5721
|
+
clauses.push(
|
|
5722
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') <= ?",
|
|
5723
|
+
);
|
|
5724
|
+
values.push(input.to.slice(0, 10));
|
|
5725
|
+
}
|
|
5726
|
+
if (input.agent) {
|
|
5727
|
+
clauses.push("i.zaip_agent = ?");
|
|
5728
|
+
values.push(input.agent);
|
|
5729
|
+
}
|
|
5730
|
+
if (input.phase) {
|
|
5731
|
+
clauses.push("i.phase = ?");
|
|
5732
|
+
values.push(input.phase);
|
|
5733
|
+
}
|
|
5734
|
+
if (input.status) {
|
|
5735
|
+
clauses.push("i.status = ?");
|
|
5736
|
+
values.push(input.status);
|
|
5737
|
+
}
|
|
5110
5738
|
if (input.priority || input.workType) clauses.push("0");
|
|
5111
|
-
if (input.risk) {
|
|
5739
|
+
if (input.risk) {
|
|
5740
|
+
clauses.push("t.risk = ?");
|
|
5741
|
+
values.push(input.risk);
|
|
5742
|
+
}
|
|
5112
5743
|
if (input.origin) {
|
|
5113
|
-
clauses.push(
|
|
5744
|
+
clauses.push(
|
|
5745
|
+
input.origin === "prd"
|
|
5746
|
+
? "i.prd_id IS NOT NULL"
|
|
5747
|
+
: input.origin === "spec"
|
|
5748
|
+
? "i.prd_id IS NULL AND i.spec_id IS NOT NULL"
|
|
5749
|
+
: input.origin === "task"
|
|
5750
|
+
? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL"
|
|
5751
|
+
: "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL",
|
|
5752
|
+
);
|
|
5114
5753
|
}
|
|
5115
5754
|
if (input.provider) {
|
|
5116
|
-
if (input.provider === "unavailable")
|
|
5117
|
-
|
|
5755
|
+
if (input.provider === "unavailable")
|
|
5756
|
+
clauses.push(
|
|
5757
|
+
"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')",
|
|
5758
|
+
);
|
|
5759
|
+
else {
|
|
5760
|
+
clauses.push("s.provider = ?");
|
|
5761
|
+
values.push(input.provider);
|
|
5762
|
+
}
|
|
5118
5763
|
}
|
|
5119
5764
|
if (input.model) {
|
|
5120
5765
|
if (input.model === "unavailable") clauses.push("s.model_key = 'unavailable'");
|
|
5121
|
-
else {
|
|
5766
|
+
else {
|
|
5767
|
+
clauses.push("s.model_key = ?");
|
|
5768
|
+
values.push(input.model);
|
|
5769
|
+
}
|
|
5770
|
+
}
|
|
5771
|
+
if (input.query) {
|
|
5772
|
+
clauses.push("s.model_key LIKE ?");
|
|
5773
|
+
values.push(`%${input.query}%`);
|
|
5122
5774
|
}
|
|
5123
|
-
if (input.query) { clauses.push("s.model_key LIKE ?"); values.push(`%${input.query}%`); }
|
|
5124
5775
|
const where = clauses.join(" AND ");
|
|
5125
5776
|
const activityClauses = ["a.activity_source_kind <> 'agent_run'"];
|
|
5126
5777
|
const activityValues: Array<string | number> = [];
|
|
5127
|
-
if (input.from) {
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
if (input.
|
|
5778
|
+
if (input.from) {
|
|
5779
|
+
activityClauses.push("a.occurred_day_sp >= ?");
|
|
5780
|
+
activityValues.push(input.from.slice(0, 10));
|
|
5781
|
+
}
|
|
5782
|
+
if (input.to) {
|
|
5783
|
+
activityClauses.push("a.occurred_day_sp <= ?");
|
|
5784
|
+
activityValues.push(input.to.slice(0, 10));
|
|
5785
|
+
}
|
|
5786
|
+
if (input.agent) {
|
|
5787
|
+
activityClauses.push("a.zaip_agent = ?");
|
|
5788
|
+
activityValues.push(input.agent);
|
|
5789
|
+
}
|
|
5790
|
+
if (input.phase) {
|
|
5791
|
+
activityClauses.push("a.phase = ?");
|
|
5792
|
+
activityValues.push(input.phase);
|
|
5793
|
+
}
|
|
5794
|
+
if (input.status) {
|
|
5795
|
+
activityClauses.push("a.status = ?");
|
|
5796
|
+
activityValues.push(input.status);
|
|
5797
|
+
}
|
|
5132
5798
|
if (input.priority || input.workType) activityClauses.push("0");
|
|
5133
|
-
if (input.risk) {
|
|
5134
|
-
|
|
5799
|
+
if (input.risk) {
|
|
5800
|
+
activityClauses.push("activity_task.risk = ?");
|
|
5801
|
+
activityValues.push(input.risk);
|
|
5802
|
+
}
|
|
5803
|
+
if (input.origin)
|
|
5804
|
+
activityClauses.push(
|
|
5805
|
+
input.origin === "prd"
|
|
5806
|
+
? "a.prd_id IS NOT NULL"
|
|
5807
|
+
: input.origin === "spec"
|
|
5808
|
+
? "a.prd_id IS NULL AND a.spec_id IS NOT NULL"
|
|
5809
|
+
: input.origin === "task"
|
|
5810
|
+
? "a.prd_id IS NULL AND a.spec_id IS NULL AND a.task_id IS NOT NULL"
|
|
5811
|
+
: "a.prd_id IS NULL AND a.spec_id IS NULL AND a.task_id IS NULL",
|
|
5812
|
+
);
|
|
5135
5813
|
if (input.provider) {
|
|
5136
|
-
if (input.provider === "unavailable")
|
|
5137
|
-
|
|
5814
|
+
if (input.provider === "unavailable")
|
|
5815
|
+
activityClauses.push(
|
|
5816
|
+
"a.provider IS NULL OR trim(a.provider) = '' OR a.provider = 'unavailable'",
|
|
5817
|
+
);
|
|
5818
|
+
else {
|
|
5819
|
+
activityClauses.push("a.provider = ?");
|
|
5820
|
+
activityValues.push(input.provider);
|
|
5821
|
+
}
|
|
5138
5822
|
}
|
|
5139
5823
|
if (input.model) {
|
|
5140
|
-
if (input.model === "unavailable")
|
|
5141
|
-
|
|
5824
|
+
if (input.model === "unavailable")
|
|
5825
|
+
activityClauses.push("a.model IS NULL OR trim(a.model) = '' OR a.model = 'unavailable'");
|
|
5826
|
+
else {
|
|
5827
|
+
activityClauses.push("a.model = ?");
|
|
5828
|
+
activityValues.push(input.model);
|
|
5829
|
+
}
|
|
5830
|
+
}
|
|
5831
|
+
if (input.query) {
|
|
5832
|
+
activityClauses.push("COALESCE(a.model, 'unavailable') LIKE ?");
|
|
5833
|
+
activityValues.push(`%${input.query}%`);
|
|
5142
5834
|
}
|
|
5143
|
-
if (input.query) { activityClauses.push("COALESCE(a.model, 'unavailable') LIKE ?"); activityValues.push(`%${input.query}%`); }
|
|
5144
5835
|
const ctes = `WITH segment_totals AS (
|
|
5145
5836
|
SELECT agent_invocation_id, SUM(input_tokens) AS input_tokens, SUM(cached_input_tokens) AS cached_input_tokens,
|
|
5146
5837
|
SUM(output_tokens) AS output_tokens, SUM(reasoning_tokens) AS reasoning_tokens,
|
|
5147
5838
|
SUM(total_tokens) AS total_tokens, SUM(cost_usd) AS cost_usd, SUM(cost_brl) AS cost_brl
|
|
5148
5839
|
FROM cost_invocation_dimension_facts
|
|
5149
|
-
WHERE total_tokens IS NOT NULL OR cost_usd IS NOT NULL OR cost_brl IS NOT NULL
|
|
5840
|
+
WHERE (total_tokens IS NOT NULL OR cost_usd IS NOT NULL OR cost_brl IS NOT NULL)${dimensionDateFilter}
|
|
5150
5841
|
GROUP BY agent_invocation_id
|
|
5151
5842
|
), segments AS (
|
|
5152
5843
|
SELECT agent_invocation_id,
|
|
@@ -5156,7 +5847,7 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5156
5847
|
SUM(output_tokens) AS output_tokens, SUM(reasoning_tokens) AS reasoning_tokens,
|
|
5157
5848
|
SUM(total_tokens) AS total_tokens, SUM(cost_usd) AS cost_usd, SUM(cost_brl) AS cost_brl
|
|
5158
5849
|
FROM cost_invocation_dimension_facts
|
|
5159
|
-
WHERE total_tokens IS NOT NULL OR cost_usd IS NOT NULL OR cost_brl IS NOT NULL
|
|
5850
|
+
WHERE (total_tokens IS NOT NULL OR cost_usd IS NOT NULL OR cost_brl IS NOT NULL)${dimensionDateFilter}
|
|
5160
5851
|
GROUP BY agent_invocation_id, model_key, provider
|
|
5161
5852
|
UNION ALL
|
|
5162
5853
|
-- Preserve every factual residual from the consolidated invocation:
|
|
@@ -5223,7 +5914,13 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5223
5914
|
)`;
|
|
5224
5915
|
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
5225
5916
|
const offset = Math.max(0, input.offset ?? 0);
|
|
5226
|
-
const total_count = Number(
|
|
5917
|
+
const total_count = Number(
|
|
5918
|
+
(
|
|
5919
|
+
this.database
|
|
5920
|
+
.prepare(`${ctes} ${facts} SELECT COUNT(*) AS count FROM keys`)
|
|
5921
|
+
.get(...values, ...activityValues) as { count: number }
|
|
5922
|
+
).count,
|
|
5923
|
+
);
|
|
5227
5924
|
// Dimension membership is intentionally non-additive: one Task can use
|
|
5228
5925
|
// several concrete models. Expose the distinct population separately so
|
|
5229
5926
|
// a footer never reports the inflated sum of row memberships as total.
|
|
@@ -5244,30 +5941,90 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5244
5941
|
SUM(cost_brl) AS total_cost_brl
|
|
5245
5942
|
FROM filtered_segments`)
|
|
5246
5943
|
.get(...values, ...activityValues) as Record<string, unknown>;
|
|
5247
|
-
const rows = this.database
|
|
5248
|
-
|
|
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, ...activityValues, 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
|
+
};
|
|
5249
5959
|
}
|
|
5250
5960
|
if (filteredCostGroup === "provider") {
|
|
5251
5961
|
const clauses = ["1=1"];
|
|
5252
5962
|
const values: Array<string | number> = [];
|
|
5253
|
-
|
|
5254
|
-
if (input.
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5963
|
+
const dimensionDateFilter = costDimensionFactDateFilter(input);
|
|
5964
|
+
if (input.from) {
|
|
5965
|
+
clauses.push(
|
|
5966
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') >= ?",
|
|
5967
|
+
);
|
|
5968
|
+
values.push(input.from.slice(0, 10));
|
|
5969
|
+
}
|
|
5970
|
+
if (input.to) {
|
|
5971
|
+
clauses.push(
|
|
5972
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') <= ?",
|
|
5973
|
+
);
|
|
5974
|
+
values.push(input.to.slice(0, 10));
|
|
5975
|
+
}
|
|
5976
|
+
if (input.agent) {
|
|
5977
|
+
clauses.push("i.zaip_agent = ?");
|
|
5978
|
+
values.push(input.agent);
|
|
5979
|
+
}
|
|
5980
|
+
if (input.phase) {
|
|
5981
|
+
clauses.push("i.phase = ?");
|
|
5982
|
+
values.push(input.phase);
|
|
5983
|
+
}
|
|
5984
|
+
if (input.status) {
|
|
5985
|
+
clauses.push("i.status = ?");
|
|
5986
|
+
values.push(input.status);
|
|
5987
|
+
}
|
|
5258
5988
|
if (input.priority || input.workType) clauses.push("0");
|
|
5259
|
-
if (input.risk) {
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
if (input.
|
|
5989
|
+
if (input.risk) {
|
|
5990
|
+
clauses.push("t.risk = ?");
|
|
5991
|
+
values.push(input.risk);
|
|
5992
|
+
}
|
|
5993
|
+
if (input.origin)
|
|
5994
|
+
clauses.push(
|
|
5995
|
+
input.origin === "prd"
|
|
5996
|
+
? "i.prd_id IS NOT NULL"
|
|
5997
|
+
: input.origin === "spec"
|
|
5998
|
+
? "i.prd_id IS NULL AND i.spec_id IS NOT NULL"
|
|
5999
|
+
: input.origin === "task"
|
|
6000
|
+
? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL"
|
|
6001
|
+
: "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL",
|
|
6002
|
+
);
|
|
6003
|
+
if (input.provider) {
|
|
6004
|
+
if (input.provider === "unavailable") clauses.push("s.provider_key = 'unavailable'");
|
|
6005
|
+
else {
|
|
6006
|
+
clauses.push("s.provider_key = ?");
|
|
6007
|
+
values.push(input.provider);
|
|
6008
|
+
}
|
|
6009
|
+
}
|
|
6010
|
+
if (input.model) {
|
|
6011
|
+
if (input.model === "unavailable") clauses.push("s.model_key = 'unavailable'");
|
|
6012
|
+
else {
|
|
6013
|
+
clauses.push("s.model_key = ?");
|
|
6014
|
+
values.push(input.model);
|
|
6015
|
+
}
|
|
6016
|
+
}
|
|
6017
|
+
if (input.query) {
|
|
6018
|
+
clauses.push("s.provider_key LIKE ?");
|
|
6019
|
+
values.push(`%${input.query}%`);
|
|
6020
|
+
}
|
|
5264
6021
|
const where = clauses.join(" AND ");
|
|
5265
6022
|
const ctes = `WITH segment_totals AS (
|
|
5266
6023
|
SELECT agent_invocation_id, SUM(input_tokens) AS input_tokens, SUM(cached_input_tokens) AS cached_input_tokens,
|
|
5267
6024
|
SUM(output_tokens) AS output_tokens, SUM(reasoning_tokens) AS reasoning_tokens,
|
|
5268
6025
|
SUM(total_tokens) AS total_tokens, SUM(cost_usd) AS cost_usd, SUM(cost_brl) AS cost_brl
|
|
5269
6026
|
FROM cost_invocation_dimension_facts
|
|
5270
|
-
WHERE total_tokens IS NOT NULL OR cost_usd IS NOT NULL OR cost_brl IS NOT NULL
|
|
6027
|
+
WHERE (total_tokens IS NOT NULL OR cost_usd IS NOT NULL OR cost_brl IS NOT NULL)${dimensionDateFilter}
|
|
5271
6028
|
GROUP BY agent_invocation_id
|
|
5272
6029
|
), segments AS (
|
|
5273
6030
|
SELECT agent_invocation_id,
|
|
@@ -5277,7 +6034,7 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5277
6034
|
SUM(output_tokens) AS output_tokens, SUM(reasoning_tokens) AS reasoning_tokens,
|
|
5278
6035
|
SUM(total_tokens) AS total_tokens, SUM(cost_usd) AS cost_usd, SUM(cost_brl) AS cost_brl
|
|
5279
6036
|
FROM cost_invocation_dimension_facts
|
|
5280
|
-
WHERE total_tokens IS NOT NULL OR cost_usd IS NOT NULL OR cost_brl IS NOT NULL
|
|
6037
|
+
WHERE (total_tokens IS NOT NULL OR cost_usd IS NOT NULL OR cost_brl IS NOT NULL)${dimensionDateFilter}
|
|
5281
6038
|
GROUP BY agent_invocation_id, provider_key, model_key
|
|
5282
6039
|
UNION ALL
|
|
5283
6040
|
-- See the equivalent Models branch: attribute only the segment part;
|
|
@@ -5319,8 +6076,15 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5319
6076
|
SELECT provider_key AS key, COUNT(*) AS task_count
|
|
5320
6077
|
FROM task_providers GROUP BY provider_key
|
|
5321
6078
|
), 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
|
|
6079
|
+
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
6080
|
+
const offset = Math.max(0, input.offset ?? 0);
|
|
6081
|
+
const total_count = Number(
|
|
6082
|
+
(
|
|
6083
|
+
this.database
|
|
6084
|
+
.prepare(`${ctes} ${facts} SELECT COUNT(*) AS count FROM keys`)
|
|
6085
|
+
.get(...values) as { count: number }
|
|
6086
|
+
).count,
|
|
6087
|
+
);
|
|
5324
6088
|
const totals = this.database
|
|
5325
6089
|
.prepare(`${ctes} ${facts} SELECT
|
|
5326
6090
|
(SELECT COUNT(*) FROM population_ranked WHERE provider_rank = 1) AS invocation_count,
|
|
@@ -5338,21 +6102,38 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5338
6102
|
SUM(cost_brl) AS total_cost_brl
|
|
5339
6103
|
FROM filtered_segments`)
|
|
5340
6104
|
.get(...values) as Record<string, unknown>;
|
|
5341
|
-
const rows = this.database
|
|
5342
|
-
|
|
6105
|
+
const rows = this.database
|
|
6106
|
+
.prepare(
|
|
6107
|
+
`${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 ?`,
|
|
6108
|
+
)
|
|
6109
|
+
.all(...values, limit, offset) as Array<Record<string, unknown>>;
|
|
6110
|
+
return {
|
|
6111
|
+
data_source: "sqlite_read_model" as const,
|
|
6112
|
+
view,
|
|
6113
|
+
offset,
|
|
6114
|
+
limit,
|
|
6115
|
+
total_count,
|
|
6116
|
+
has_more: offset + rows.length < total_count,
|
|
6117
|
+
totals,
|
|
6118
|
+
rows,
|
|
6119
|
+
};
|
|
5343
6120
|
}
|
|
5344
6121
|
const temporalCostGroup =
|
|
5345
6122
|
filteredCostGroup === "day" || filteredCostGroup === "month" || filteredCostGroup === "year";
|
|
5346
6123
|
const hasDimensionalCostFilter = Boolean(
|
|
5347
|
-
input.provider ||
|
|
6124
|
+
input.provider ||
|
|
6125
|
+
input.model ||
|
|
6126
|
+
input.agent ||
|
|
6127
|
+
input.phase ||
|
|
6128
|
+
input.status ||
|
|
6129
|
+
input.origin ||
|
|
6130
|
+
input.priority ||
|
|
6131
|
+
input.risk ||
|
|
6132
|
+
input.workType,
|
|
5348
6133
|
);
|
|
5349
6134
|
if (filteredCostGroup !== undefined && temporalCostGroup && !hasDimensionalCostFilter) {
|
|
5350
6135
|
const from = costMonitorFilterDay(input.from);
|
|
5351
6136
|
const to = costMonitorFilterDay(input.to);
|
|
5352
|
-
// Cost temporal tables reuse the same aggregate-only projection as the
|
|
5353
|
-
// summary cards. This intentionally bypasses daily_aggregate_cache,
|
|
5354
|
-
// whose recursive all-dashboard view used to expand the complete fact
|
|
5355
|
-
// graph before returning a handful of visible columns.
|
|
5356
6137
|
const aggregate = this.costMonitorSummaryAggregate({ from, to });
|
|
5357
6138
|
const financialRows = aggregate.daily_rows as Array<Record<string, unknown>>;
|
|
5358
6139
|
const financialByDay = new Map(financialRows.map((row) => [String(row.key), row]));
|
|
@@ -5400,12 +6181,14 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5400
6181
|
for (const column of additive) {
|
|
5401
6182
|
const value = financial?.[column];
|
|
5402
6183
|
if (typeof value === "number")
|
|
5403
|
-
target[column] =
|
|
6184
|
+
target[column] =
|
|
6185
|
+
(typeof target[column] === "number" ? Number(target[column]) : 0) + value;
|
|
5404
6186
|
}
|
|
5405
6187
|
const duration = financial?.total_duration;
|
|
5406
6188
|
if (typeof duration === "number") {
|
|
5407
6189
|
target.total_duration =
|
|
5408
|
-
(typeof target.total_duration === "number" ? Number(target.total_duration) : 0) +
|
|
6190
|
+
(typeof target.total_duration === "number" ? Number(target.total_duration) : 0) +
|
|
6191
|
+
duration;
|
|
5409
6192
|
}
|
|
5410
6193
|
if (typeof financial?.duration_unknown === "number" && financial.duration_unknown > 0)
|
|
5411
6194
|
target.duration_incomplete = true;
|
|
@@ -5440,7 +6223,20 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5440
6223
|
.filter((row) => query === null || String(row.key).toLocaleLowerCase().includes(query))
|
|
5441
6224
|
.sort((left, right) => String(right.key).localeCompare(String(left.key)));
|
|
5442
6225
|
const totals = allRows.reduce<Record<string, number | null>>((result, row) => {
|
|
5443
|
-
for (const key of [
|
|
6226
|
+
for (const key of [
|
|
6227
|
+
"prd_count",
|
|
6228
|
+
"spec_count",
|
|
6229
|
+
"task_count",
|
|
6230
|
+
"total_run_count",
|
|
6231
|
+
"validation_count",
|
|
6232
|
+
"evidence_count",
|
|
6233
|
+
"total_duration",
|
|
6234
|
+
"duration_known",
|
|
6235
|
+
"duration_unknown",
|
|
6236
|
+
"total_tokens",
|
|
6237
|
+
"total_cost_usd",
|
|
6238
|
+
"total_cost_brl",
|
|
6239
|
+
] as const) {
|
|
5444
6240
|
const value = row[key];
|
|
5445
6241
|
if (typeof value === "number") result[key] = (result[key] ?? 0) + value;
|
|
5446
6242
|
else if (!(key in result)) result[key] = null;
|
|
@@ -5465,7 +6261,15 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5465
6261
|
if (temporalCostGroup && input.workType) {
|
|
5466
6262
|
// No canonical work_type exists at invocation grain, including for
|
|
5467
6263
|
// duration/Run completion backfills. Do not emit unrelated buckets.
|
|
5468
|
-
return {
|
|
6264
|
+
return {
|
|
6265
|
+
data_source: "sqlite_read_model" as const,
|
|
6266
|
+
view,
|
|
6267
|
+
offset: Math.max(0, input.offset ?? 0),
|
|
6268
|
+
limit: Math.min(200, Math.max(1, input.limit ?? 50)),
|
|
6269
|
+
total_count: 0,
|
|
6270
|
+
has_more: false,
|
|
6271
|
+
rows: [],
|
|
6272
|
+
};
|
|
5469
6273
|
}
|
|
5470
6274
|
const groupExpression =
|
|
5471
6275
|
filteredCostGroup === "day"
|
|
@@ -5481,17 +6285,23 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5481
6285
|
];
|
|
5482
6286
|
const values: Array<string | number> = [];
|
|
5483
6287
|
if (input.from) {
|
|
5484
|
-
clauses.push(
|
|
6288
|
+
clauses.push(
|
|
6289
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') >= ?",
|
|
6290
|
+
);
|
|
5485
6291
|
values.push(costMonitorFilterDay(input.from)!);
|
|
5486
6292
|
}
|
|
5487
6293
|
if (input.to) {
|
|
5488
|
-
clauses.push(
|
|
6294
|
+
clauses.push(
|
|
6295
|
+
"date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') <= ?",
|
|
6296
|
+
);
|
|
5489
6297
|
values.push(costMonitorFilterDay(input.to)!);
|
|
5490
6298
|
}
|
|
5491
6299
|
const usageSegmentClauses: string[] = [];
|
|
5492
6300
|
if (input.provider) {
|
|
5493
6301
|
if (input.provider === "unavailable") {
|
|
5494
|
-
clauses.push(
|
|
6302
|
+
clauses.push(
|
|
6303
|
+
"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')",
|
|
6304
|
+
);
|
|
5495
6305
|
} else {
|
|
5496
6306
|
usageSegmentClauses.push("f.provider=?");
|
|
5497
6307
|
values.push(input.provider);
|
|
@@ -5499,14 +6309,18 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5499
6309
|
}
|
|
5500
6310
|
if (input.model) {
|
|
5501
6311
|
if (input.model === "unavailable") {
|
|
5502
|
-
clauses.push(
|
|
6312
|
+
clauses.push(
|
|
6313
|
+
"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')",
|
|
6314
|
+
);
|
|
5503
6315
|
} else {
|
|
5504
6316
|
usageSegmentClauses.push("f.model=?");
|
|
5505
6317
|
values.push(input.model);
|
|
5506
6318
|
}
|
|
5507
6319
|
}
|
|
5508
6320
|
if (usageSegmentClauses.length > 0)
|
|
5509
|
-
clauses.push(
|
|
6321
|
+
clauses.push(
|
|
6322
|
+
`EXISTS (SELECT 1 FROM invocation_usage f WHERE f.agent_invocation_id=i.agent_invocation_id AND ${usageSegmentClauses.join(" AND ")})`,
|
|
6323
|
+
);
|
|
5510
6324
|
if (input.agent) {
|
|
5511
6325
|
clauses.push("i.zaip_agent = ?");
|
|
5512
6326
|
values.push(input.agent);
|
|
@@ -5602,19 +6416,43 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5602
6416
|
const factualDuration = this.costMonitorFactualDurationByDay(canonicalFilter);
|
|
5603
6417
|
// Lifecycle counts are canonical facts keyed by their own occurrence
|
|
5604
6418
|
// date. They deliberately do not inherit financial dimensions.
|
|
5605
|
-
const hasUnsupportedLifecycleAttribution = Boolean(
|
|
6419
|
+
const hasUnsupportedLifecycleAttribution = Boolean(
|
|
6420
|
+
input.origin || input.priority || input.risk || input.workType,
|
|
6421
|
+
);
|
|
5606
6422
|
const lifecycleRows = hasUnsupportedLifecycleAttribution
|
|
5607
6423
|
? []
|
|
5608
|
-
: this.database
|
|
6424
|
+
: (this.database
|
|
5609
6425
|
.prepare(`SELECT day, prd_count, spec_count, task_count, validation_count, evidence_count
|
|
5610
6426
|
FROM daily_aggregate_cache WHERE (? IS NULL OR day >= ?) AND (? IS NULL OR day <= ?)`)
|
|
5611
|
-
.all(
|
|
6427
|
+
.all(
|
|
6428
|
+
costMonitorFilterDay(input.from),
|
|
6429
|
+
costMonitorFilterDay(input.from),
|
|
6430
|
+
costMonitorFilterDay(input.to),
|
|
6431
|
+
costMonitorFilterDay(input.to),
|
|
6432
|
+
) as Array<Record<string, unknown>>);
|
|
5612
6433
|
const lifecycleByKey = new Map<string, Record<string, number>>();
|
|
5613
6434
|
for (const row of lifecycleRows) {
|
|
5614
6435
|
const day = String(row.day);
|
|
5615
|
-
const key =
|
|
5616
|
-
|
|
5617
|
-
|
|
6436
|
+
const key =
|
|
6437
|
+
filteredCostGroup === "day"
|
|
6438
|
+
? day
|
|
6439
|
+
: filteredCostGroup === "month"
|
|
6440
|
+
? day.slice(0, 7)
|
|
6441
|
+
: day.slice(0, 4);
|
|
6442
|
+
const target = lifecycleByKey.get(key) ?? {
|
|
6443
|
+
prd_count: 0,
|
|
6444
|
+
spec_count: 0,
|
|
6445
|
+
task_count: 0,
|
|
6446
|
+
validation_count: 0,
|
|
6447
|
+
evidence_count: 0,
|
|
6448
|
+
};
|
|
6449
|
+
for (const column of [
|
|
6450
|
+
"prd_count",
|
|
6451
|
+
"spec_count",
|
|
6452
|
+
"task_count",
|
|
6453
|
+
"validation_count",
|
|
6454
|
+
"evidence_count",
|
|
6455
|
+
] as const)
|
|
5618
6456
|
target[column] = (target[column] ?? 0) + Number(row[column] ?? 0);
|
|
5619
6457
|
lifecycleByKey.set(key, target);
|
|
5620
6458
|
}
|
|
@@ -5622,7 +6460,12 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5622
6460
|
const canonicalKeys = new Set(
|
|
5623
6461
|
(input.query
|
|
5624
6462
|
? []
|
|
5625
|
-
: [
|
|
6463
|
+
: [
|
|
6464
|
+
...new Set([
|
|
6465
|
+
...factualDuration.keys(),
|
|
6466
|
+
...lifecycleRows.map((row) => String(row.day)),
|
|
6467
|
+
]),
|
|
6468
|
+
]
|
|
5626
6469
|
).map((day) =>
|
|
5627
6470
|
filteredCostGroup === "day"
|
|
5628
6471
|
? day
|
|
@@ -5653,12 +6496,26 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5653
6496
|
const durationMatches = [...factualDuration].filter(([day]) => dayMatches(day));
|
|
5654
6497
|
return {
|
|
5655
6498
|
...financial,
|
|
5656
|
-
...(lifecycleByKey.get(key) ??
|
|
5657
|
-
|
|
5658
|
-
|
|
6499
|
+
...(lifecycleByKey.get(key) ??
|
|
6500
|
+
(hasUnsupportedLifecycleAttribution
|
|
6501
|
+
? {
|
|
6502
|
+
prd_count: null,
|
|
6503
|
+
spec_count: null,
|
|
6504
|
+
task_count: null,
|
|
6505
|
+
validation_count: null,
|
|
6506
|
+
evidence_count: null,
|
|
6507
|
+
}
|
|
6508
|
+
: {
|
|
6509
|
+
prd_count: 0,
|
|
6510
|
+
spec_count: 0,
|
|
6511
|
+
task_count: 0,
|
|
6512
|
+
validation_count: 0,
|
|
6513
|
+
evidence_count: 0,
|
|
6514
|
+
})),
|
|
5659
6515
|
total_run_count: Number(financial.invocation_count ?? 0),
|
|
5660
6516
|
total_duration:
|
|
5661
|
-
durationMatches.length === 0 ||
|
|
6517
|
+
durationMatches.length === 0 ||
|
|
6518
|
+
durationMatches.some(([, duration]) => duration.eligible_runs !== duration.known_runs)
|
|
5662
6519
|
? null
|
|
5663
6520
|
: durationMatches.reduce((sum, [, duration]) => sum + duration.known_sum, 0),
|
|
5664
6521
|
};
|
|
@@ -5701,8 +6558,13 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5701
6558
|
// or belongs to another period. Financial measures stay nullable.
|
|
5702
6559
|
const isCanonicalMacro = groupKind === "spec" || groupKind === "prd";
|
|
5703
6560
|
const hasInvocationOnlyFilter = Boolean(
|
|
5704
|
-
input.provider ||
|
|
5705
|
-
|
|
6561
|
+
input.provider ||
|
|
6562
|
+
input.model ||
|
|
6563
|
+
input.agent ||
|
|
6564
|
+
input.phase ||
|
|
6565
|
+
input.origin ||
|
|
6566
|
+
input.workType ||
|
|
6567
|
+
input.priority,
|
|
5706
6568
|
);
|
|
5707
6569
|
if (isCanonicalMacro && !hasInvocationOnlyFilter) {
|
|
5708
6570
|
const entityTable = groupKind === "spec" ? "specs" : "prds";
|
|
@@ -5736,7 +6598,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5736
6598
|
entityValues.push(`%${input.query}%`, `%${input.query}%`);
|
|
5737
6599
|
}
|
|
5738
6600
|
const entityWhere = entityClauses.length === 0 ? "1" : entityClauses.join(" AND ");
|
|
5739
|
-
const invocationJoin =
|
|
6601
|
+
const invocationJoin =
|
|
6602
|
+
invocationClauses.length === 0 ? "" : ` AND ${invocationClauses.join(" AND ")}`;
|
|
5740
6603
|
const ctes = `WITH grouped AS (
|
|
5741
6604
|
SELECT entity.id AS group_value, entity.title, entity.status, entity.risk,
|
|
5742
6605
|
COALESCE(MIN(i.occurred_day_sp), entity.occurred_day_sp) AS first_day,
|
|
@@ -5758,42 +6621,135 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5758
6621
|
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
5759
6622
|
const offset = Math.max(0, input.offset ?? 0);
|
|
5760
6623
|
const values = [...invocationValues, ...entityValues];
|
|
5761
|
-
const total_count = Number(
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
6624
|
+
const total_count = Number(
|
|
6625
|
+
(
|
|
6626
|
+
this.database
|
|
6627
|
+
.prepare(`${ctes} SELECT COUNT(*) AS count FROM grouped`)
|
|
6628
|
+
.get(...values) as { count: number }
|
|
6629
|
+
).count,
|
|
6630
|
+
);
|
|
6631
|
+
const totals = this.database
|
|
6632
|
+
.prepare(
|
|
6633
|
+
`${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`,
|
|
6634
|
+
)
|
|
6635
|
+
.get(...values) as Record<string, unknown>;
|
|
6636
|
+
const rows = this.database
|
|
6637
|
+
.prepare(
|
|
6638
|
+
`${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 ?`,
|
|
6639
|
+
)
|
|
6640
|
+
.all(...values, limit, offset) as Array<Record<string, unknown>>;
|
|
6641
|
+
return {
|
|
6642
|
+
data_source: "sqlite_read_model" as const,
|
|
6643
|
+
view,
|
|
6644
|
+
offset,
|
|
6645
|
+
limit,
|
|
6646
|
+
total_count,
|
|
6647
|
+
has_more: offset + rows.length < total_count,
|
|
6648
|
+
totals,
|
|
6649
|
+
rows,
|
|
6650
|
+
};
|
|
5765
6651
|
}
|
|
5766
6652
|
const hasDimensionalFilter = Boolean(
|
|
5767
|
-
input.provider ||
|
|
5768
|
-
|
|
6653
|
+
input.provider ||
|
|
6654
|
+
input.model ||
|
|
6655
|
+
input.agent ||
|
|
6656
|
+
input.phase ||
|
|
6657
|
+
input.status ||
|
|
6658
|
+
input.origin ||
|
|
6659
|
+
input.workType ||
|
|
6660
|
+
input.priority ||
|
|
6661
|
+
input.risk,
|
|
5769
6662
|
);
|
|
5770
6663
|
if (hasDimensionalFilter) {
|
|
5771
6664
|
const column = {
|
|
5772
|
-
task: "i.task_id",
|
|
5773
|
-
|
|
6665
|
+
task: "i.task_id",
|
|
6666
|
+
spec: "i.spec_id",
|
|
6667
|
+
prd: "i.prd_id",
|
|
6668
|
+
execution: "i.execution_id",
|
|
6669
|
+
conversation: "i.conversation_id",
|
|
5774
6670
|
}[groupKind];
|
|
5775
|
-
const entity =
|
|
6671
|
+
const entity =
|
|
6672
|
+
groupKind === "task"
|
|
6673
|
+
? "tasks"
|
|
6674
|
+
: groupKind === "spec"
|
|
6675
|
+
? "specs"
|
|
6676
|
+
: groupKind === "prd"
|
|
6677
|
+
? "prds"
|
|
6678
|
+
: null;
|
|
5776
6679
|
const clauses = [`${column} IS NOT NULL`];
|
|
5777
6680
|
const values: Array<string | number> = [];
|
|
5778
|
-
if (input.from) {
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
if (input.
|
|
5783
|
-
|
|
5784
|
-
|
|
6681
|
+
if (input.from) {
|
|
6682
|
+
clauses.push("i.occurred_day_sp >= ?");
|
|
6683
|
+
values.push(input.from.slice(0, 10));
|
|
6684
|
+
}
|
|
6685
|
+
if (input.to) {
|
|
6686
|
+
clauses.push("i.occurred_day_sp <= ?");
|
|
6687
|
+
values.push(input.to.slice(0, 10));
|
|
6688
|
+
}
|
|
6689
|
+
if (input.agent) {
|
|
6690
|
+
clauses.push("i.zaip_agent = ?");
|
|
6691
|
+
values.push(input.agent);
|
|
6692
|
+
}
|
|
6693
|
+
if (input.phase) {
|
|
6694
|
+
clauses.push("i.phase = ?");
|
|
6695
|
+
values.push(input.phase);
|
|
6696
|
+
}
|
|
6697
|
+
if (input.status) {
|
|
6698
|
+
clauses.push("i.status = ?");
|
|
6699
|
+
values.push(input.status);
|
|
6700
|
+
}
|
|
6701
|
+
if (input.risk) {
|
|
6702
|
+
clauses.push("task_entity.risk = ?");
|
|
6703
|
+
values.push(input.risk);
|
|
6704
|
+
}
|
|
6705
|
+
if (input.origin)
|
|
6706
|
+
clauses.push(
|
|
6707
|
+
input.origin === "prd"
|
|
6708
|
+
? "i.prd_id IS NOT NULL"
|
|
6709
|
+
: input.origin === "spec"
|
|
6710
|
+
? "i.prd_id IS NULL AND i.spec_id IS NOT NULL"
|
|
6711
|
+
: input.origin === "task"
|
|
6712
|
+
? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL"
|
|
6713
|
+
: "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL",
|
|
6714
|
+
);
|
|
5785
6715
|
// Compact lifecycle rows do not materialize canonical work type or Task priority.
|
|
5786
6716
|
// Return a safe empty result instead of substituting another attribution.
|
|
5787
6717
|
if (input.workType || input.priority) clauses.push("0");
|
|
5788
6718
|
const segment: string[] = [];
|
|
5789
|
-
if (input.provider) {
|
|
5790
|
-
|
|
5791
|
-
|
|
6719
|
+
if (input.provider) {
|
|
6720
|
+
if (input.provider === "unavailable")
|
|
6721
|
+
clauses.push(
|
|
6722
|
+
"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')",
|
|
6723
|
+
);
|
|
6724
|
+
else {
|
|
6725
|
+
segment.push("u.provider = ?");
|
|
6726
|
+
values.push(input.provider);
|
|
6727
|
+
}
|
|
6728
|
+
}
|
|
6729
|
+
if (input.model) {
|
|
6730
|
+
if (input.model === "unavailable")
|
|
6731
|
+
clauses.push(
|
|
6732
|
+
"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')",
|
|
6733
|
+
);
|
|
6734
|
+
else {
|
|
6735
|
+
segment.push("u.model = ?");
|
|
6736
|
+
values.push(input.model);
|
|
6737
|
+
}
|
|
6738
|
+
}
|
|
6739
|
+
if (segment.length > 0)
|
|
6740
|
+
clauses.push(
|
|
6741
|
+
`EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id=i.agent_invocation_id AND ${segment.join(" AND ")})`,
|
|
6742
|
+
);
|
|
5792
6743
|
if (input.query) {
|
|
5793
|
-
clauses.push(
|
|
6744
|
+
clauses.push(
|
|
6745
|
+
entity === null
|
|
6746
|
+
? `${column} LIKE ?`
|
|
6747
|
+
: `(${column} LIKE ? OR COALESCE(entity.title, '') LIKE ?)`,
|
|
6748
|
+
);
|
|
5794
6749
|
values.push(`%${input.query}%`, ...(entity === null ? [] : [`%${input.query}%`]));
|
|
5795
6750
|
}
|
|
5796
|
-
const entityJoin =
|
|
6751
|
+
const entityJoin =
|
|
6752
|
+
entity === null ? "" : `LEFT JOIN ${entity} entity ON entity.id=${column}`;
|
|
5797
6753
|
const taskJoin = "LEFT JOIN tasks task_entity ON task_entity.id=i.task_id";
|
|
5798
6754
|
const join = `${entityJoin} ${taskJoin}`;
|
|
5799
6755
|
const where = clauses.join(" AND ");
|
|
@@ -5802,115 +6758,35 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5802
6758
|
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
6759
|
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
5804
6760
|
const offset = Math.max(0, input.offset ?? 0);
|
|
5805
|
-
const total_count = Number(
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
const groupFilter =
|
|
5833
|
-
groupKind === "provider"
|
|
5834
|
-
? input.provider
|
|
5835
|
-
: groupKind === "model"
|
|
5836
|
-
? input.model
|
|
5837
|
-
: groupKind === "zaip_agent"
|
|
5838
|
-
? input.agent
|
|
5839
|
-
: undefined;
|
|
5840
|
-
if (groupFilter) {
|
|
5841
|
-
clauses.push("group_value = ?");
|
|
5842
|
-
values.push(groupFilter);
|
|
6761
|
+
const total_count = Number(
|
|
6762
|
+
(
|
|
6763
|
+
this.database
|
|
6764
|
+
.prepare(`${ctes} SELECT COUNT(*) AS count FROM grouped`)
|
|
6765
|
+
.get(...values) as { count: number }
|
|
6766
|
+
).count,
|
|
6767
|
+
);
|
|
6768
|
+
const totals = this.database
|
|
6769
|
+
.prepare(
|
|
6770
|
+
`${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`,
|
|
6771
|
+
)
|
|
6772
|
+
.get(...values) as Record<string, unknown>;
|
|
6773
|
+
const rows = this.database
|
|
6774
|
+
.prepare(
|
|
6775
|
+
`${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 ?`,
|
|
6776
|
+
)
|
|
6777
|
+
.all(...values, limit, offset) as Array<Record<string, unknown>>;
|
|
6778
|
+
return {
|
|
6779
|
+
data_source: "sqlite_read_model" as const,
|
|
6780
|
+
view,
|
|
6781
|
+
offset,
|
|
6782
|
+
limit,
|
|
6783
|
+
total_count,
|
|
6784
|
+
has_more: offset + rows.length < total_count,
|
|
6785
|
+
totals,
|
|
6786
|
+
rows,
|
|
6787
|
+
};
|
|
5843
6788
|
}
|
|
5844
|
-
|
|
5845
|
-
const total_count = Number(
|
|
5846
|
-
(
|
|
5847
|
-
this.database
|
|
5848
|
-
.prepare(
|
|
5849
|
-
`SELECT COUNT(DISTINCT group_value) AS count FROM cost_monitor_group_daily_cache WHERE ${where}`,
|
|
5850
|
-
)
|
|
5851
|
-
.get(...values) as { count: number }
|
|
5852
|
-
).count,
|
|
5853
|
-
);
|
|
5854
|
-
// This aggregate deliberately precedes LIMIT/OFFSET: a compact page is
|
|
5855
|
-
// only a window into the selected entity population, never its subtotal.
|
|
5856
|
-
// SUM preserves a factual 0 and leaves a wholly unavailable metric NULL.
|
|
5857
|
-
const totals = this.database
|
|
5858
|
-
.prepare(
|
|
5859
|
-
`SELECT COUNT(DISTINCT group_value) AS entity_count,
|
|
5860
|
-
COALESCE(SUM(invocation_count), 0) AS invocation_count,
|
|
5861
|
-
ROUND(SUM(total_duration)) AS total_duration,
|
|
5862
|
-
SUM(total_tokens) AS total_tokens,
|
|
5863
|
-
SUM(total_cost_usd) AS total_cost_usd,
|
|
5864
|
-
SUM(total_cost_brl) AS total_cost_brl
|
|
5865
|
-
FROM cost_monitor_group_daily_cache
|
|
5866
|
-
WHERE ${where}`,
|
|
5867
|
-
)
|
|
5868
|
-
.get(...values) as Record<string, unknown>;
|
|
5869
|
-
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
5870
|
-
const offset = Math.max(0, input.offset ?? 0);
|
|
5871
|
-
const rows = this.database
|
|
5872
|
-
.prepare(
|
|
5873
|
-
`WITH grouped AS (
|
|
5874
|
-
SELECT group_value, MIN(day) AS first_day, MAX(day) AS last_day,
|
|
5875
|
-
SUM(invocation_count) AS invocation_count,
|
|
5876
|
-
SUM(llm_call_count) AS llm_call_count,
|
|
5877
|
-
SUM(total_duration) AS total_duration,
|
|
5878
|
-
SUM(total_input_tokens) AS total_input_tokens,
|
|
5879
|
-
SUM(total_cached_input_tokens) AS total_cached_input_tokens,
|
|
5880
|
-
SUM(total_output_tokens) AS total_output_tokens,
|
|
5881
|
-
SUM(total_reasoning_tokens) AS total_reasoning_tokens,
|
|
5882
|
-
SUM(total_tokens) AS total_tokens,
|
|
5883
|
-
SUM(total_cost_usd) AS total_cost_usd,
|
|
5884
|
-
SUM(total_cost_brl) AS total_cost_brl
|
|
5885
|
-
FROM cost_monitor_group_daily_cache
|
|
5886
|
-
WHERE ${where}
|
|
5887
|
-
GROUP BY group_value
|
|
5888
|
-
)
|
|
5889
|
-
SELECT grouped.group_value AS id, grouped.group_value AS key,
|
|
5890
|
-
COALESCE(${metadata.title}, grouped.group_value) AS title,
|
|
5891
|
-
${metadata.status} AS status, ${metadata.risk} AS risk,
|
|
5892
|
-
grouped.first_day, grouped.last_day,
|
|
5893
|
-
grouped.last_day || 'T12:00:00.000Z' AS occurred_at,
|
|
5894
|
-
grouped.invocation_count, grouped.llm_call_count, grouped.total_duration,
|
|
5895
|
-
grouped.total_input_tokens, grouped.total_cached_input_tokens,
|
|
5896
|
-
grouped.total_output_tokens, grouped.total_reasoning_tokens,
|
|
5897
|
-
grouped.total_tokens, grouped.total_cost_usd, grouped.total_cost_brl
|
|
5898
|
-
FROM grouped
|
|
5899
|
-
LEFT JOIN ${metadata.table} entity ON entity.id = grouped.group_value
|
|
5900
|
-
ORDER BY grouped.total_cost_brl DESC, grouped.group_value
|
|
5901
|
-
LIMIT ? OFFSET ?`,
|
|
5902
|
-
)
|
|
5903
|
-
.all(...values, limit, offset) as Array<Record<string, unknown>>;
|
|
5904
|
-
return {
|
|
5905
|
-
data_source: "sqlite_read_model" as const,
|
|
5906
|
-
view,
|
|
5907
|
-
offset,
|
|
5908
|
-
limit,
|
|
5909
|
-
total_count,
|
|
5910
|
-
has_more: offset + rows.length < total_count,
|
|
5911
|
-
totals,
|
|
5912
|
-
rows,
|
|
5913
|
-
};
|
|
6789
|
+
return readCostGroupPage(this.database, view, groupKind as CostGroupKind, input);
|
|
5914
6790
|
}
|
|
5915
6791
|
const meta = sqliteViewMetadata[view];
|
|
5916
6792
|
const clauses: string[] = [];
|
|
@@ -5918,8 +6794,10 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5918
6794
|
// Period rows use a coarse key (YYYY-MM/YYYY), so a partial daily range
|
|
5919
6795
|
// must intersect start/end days instead of comparing that key directly.
|
|
5920
6796
|
const overlaps =
|
|
5921
|
-
view === "cost_monitor_monthly" ||
|
|
5922
|
-
view === "
|
|
6797
|
+
view === "cost_monitor_monthly" ||
|
|
6798
|
+
view === "cost_monitor_annual" ||
|
|
6799
|
+
view === "operations_monthly" ||
|
|
6800
|
+
view === "operations_annual";
|
|
5923
6801
|
if (overlaps && input.from) {
|
|
5924
6802
|
clauses.push("end_day >= ?");
|
|
5925
6803
|
values.push(input.from.slice(0, 10));
|
|
@@ -6068,8 +6946,13 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
6068
6946
|
const partialFromDay = input.from?.slice(0, 10) ?? null;
|
|
6069
6947
|
const partialToDay = input.to?.slice(0, 10) ?? null;
|
|
6070
6948
|
const dailyRows = partialCoarse
|
|
6071
|
-
? this.database
|
|
6072
|
-
|
|
6949
|
+
? (this.database
|
|
6950
|
+
.prepare(
|
|
6951
|
+
`SELECT * FROM operations_daily WHERE (? IS NULL OR day >= ?) AND (? IS NULL OR day <= ?)`,
|
|
6952
|
+
)
|
|
6953
|
+
.all(partialFromDay, partialFromDay, partialToDay, partialToDay) as Array<
|
|
6954
|
+
Record<string, unknown>
|
|
6955
|
+
>)
|
|
6073
6956
|
: [];
|
|
6074
6957
|
const daily = this.executionDurationByDay({ from: input.from ?? null, to: input.to ?? null });
|
|
6075
6958
|
for (const row of rows) {
|
|
@@ -6079,8 +6962,23 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
6079
6962
|
const matchingDays = matching.map((day) => String(day.day)).sort();
|
|
6080
6963
|
row.start_day = matchingDays[0] ?? null;
|
|
6081
6964
|
row.end_day = matchingDays.at(-1) ?? null;
|
|
6082
|
-
for (const column of [
|
|
6083
|
-
|
|
6965
|
+
for (const column of [
|
|
6966
|
+
"prd_count",
|
|
6967
|
+
"spec_count",
|
|
6968
|
+
"task_count",
|
|
6969
|
+
"validation_count",
|
|
6970
|
+
"evidence_count",
|
|
6971
|
+
"total_run_count",
|
|
6972
|
+
"invocation_count",
|
|
6973
|
+
"llm_call_count",
|
|
6974
|
+
"total_tokens",
|
|
6975
|
+
"total_cost_usd",
|
|
6976
|
+
"total_cost_brl",
|
|
6977
|
+
] as const)
|
|
6978
|
+
row[column] = matching.reduce(
|
|
6979
|
+
(sum, day) => sum + (typeof day[column] === "number" ? Number(day[column]) : 0),
|
|
6980
|
+
0,
|
|
6981
|
+
);
|
|
6084
6982
|
}
|
|
6085
6983
|
row.operations_total_duration = [...daily]
|
|
6086
6984
|
.filter(([day]) => (view === "operations_daily" ? day === key : day.startsWith(key)))
|
|
@@ -6112,10 +7010,12 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
6112
7010
|
} {
|
|
6113
7011
|
if (this.metadataValues === null) {
|
|
6114
7012
|
this.metadataValues = new Map(
|
|
6115
|
-
(
|
|
6116
|
-
key
|
|
6117
|
-
|
|
6118
|
-
|
|
7013
|
+
(
|
|
7014
|
+
this.database.prepare("SELECT key, value FROM read_model_metadata").all() as Array<{
|
|
7015
|
+
key: string;
|
|
7016
|
+
value: string;
|
|
7017
|
+
}>
|
|
7018
|
+
).map((row) => [row.key, row.value]),
|
|
6119
7019
|
);
|
|
6120
7020
|
}
|
|
6121
7021
|
const value = (key: string) => Number(this.metadataValues?.get(key) ?? 0);
|