"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.observeDashboardService = exports.ObserveDashboardService = void 0;
const decimal_js_1 = __importDefault(require("decimal.js"));
const date_fns_1 = require("date-fns");
const logger_1 = __importDefault(require("../../../logger"));
const database_1 = require("../../../database");
const tables_1 = require("../../../database/tables");
const drizzle_orm_1 = require("drizzle-orm");
const clickhouseService_1 = require("../clickhouseService");
const frameworkParser_1 = require("./frameworkParser");
const utilts_1 = require("./utils/utilts");
const aggregatedTraceDagInsert_1 = require("../aggregatedTraceDagInsert");
const ragParser_1 = require("./ragParser");
const lodash_1 = __importDefault(require("lodash"));
class ObserveDashboardService {
    constructor() {
        this.aggDailyColumnsCache = null;
        this.aggHourlyColumnsCache = null;
        this.aggDailyDashboardColumnsCache = null;
        this.getObserveDashboardConfig = async (workspaceId, applicationId, fetchVersions = false) => {
            const db = await (0, database_1.getDb)();
            let type = 'non_agentic';
            if (applicationId) {
                const application = await db.select().from(tables_1.applicationsTable).where((0, drizzle_orm_1.eq)(tables_1.applicationsTable.id, applicationId)).limit(1);
                if (application) {
                    type = application?.[0]?.isAgentic ? 'agentic' : 'non_agentic';
                }
            }
            // const dashboards = await db
            //   .select()
            //   .from(observeDashboardTable)
            //   .where(and(
            //     eq(observeDashboardTable.workspaceId, workspaceId),
            //     eq(observeDashboardTable.type, type)
            //   ))
            //   .limit(1);
            // const dashboard = dashboards[0];
            // if (!dashboard) {
            const defaultDashboards = await db
                .select()
                .from(tables_1.defaultObserveDashboardTable)
                .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.defaultObserveDashboardTable.status, 1), (0, drizzle_orm_1.eq)(tables_1.defaultObserveDashboardTable.type, type)))
                .limit(1);
            const defaultDashboard = defaultDashboards[0];
            if (!defaultDashboard) {
                return null;
            }
            let versions = [];
            if (fetchVersions) {
                const versionRows = await clickhouseService_1.clickHouseService.query(`SELECT version, min(day) AS first_seen
         FROM agg_daily
         WHERE workspaceId = {workspaceId: String}
           AND applicationId = {applicationId: String}
           AND version != ''
         GROUP BY version
         ORDER BY first_seen DESC`, { workspaceId, applicationId });
                versions = (versionRows ?? [])
                    .map((r) => ({
                    version: String(r?.version ?? '').trim(),
                    firstSeen: String(r?.first_seen ?? ''),
                }))
                    .filter((v) => v.version.length > 0)
                    .sort((a, b) => (b.firstSeen < a.firstSeen ? -1 : b.firstSeen > a.firstSeen ? 1 : 0));
                if (defaultDashboard.widgets) {
                    defaultDashboard.widgets = defaultDashboard.widgets.filter((w) => w.showInCompare);
                }
            }
            return {
                id: defaultDashboard.id,
                name: defaultDashboard.name,
                workspaceId: workspaceId,
                description: defaultDashboard.description,
                status: defaultDashboard.status,
                widgets: defaultDashboard.widgets || [],
                positions: defaultDashboard.positions || [],
                ...(versions.length > 0 ? { versions } : {}),
            };
        };
        this.populateObservationDashboardData = async ({ applicationId, traceId, spans, monitoringConfig, workspaceId, application, }) => {
            // Only write agg_daily rows if spans are provided
            if (spans
                && Array.isArray(spans)
                && spans?.length > 0) {
                try {
                    let sessionId = null;
                    for (const span of spans) {
                        if (span?.attributes?.['session.id']) {
                            sessionId = span.attributes?.['session.id'];
                            break;
                        }
                    }
                    await this.writeAggDailyFromSpans({
                        spans: spans,
                        applicationId,
                        workspaceId: workspaceId ?? monitoringConfig?.application?.workspaceId ?? monitoringConfig?.workspaceId ?? '',
                        projectId: monitoringConfig?.projectId ?? monitoringConfig?.application?.projectId ?? '',
                        traceId: traceId ?? undefined,
                        sessionId: sessionId ?? undefined,
                        monitoringConfig,
                        application,
                    });
                }
                catch (error) {
                    this.logger.error(`Error writing agg_daily rows: ${error instanceof Error ? error.message : String(error)}`);
                }
            }
        };
        this.logger = logger_1.default;
    }
    async getAggDailyColumns() {
        if (this.aggDailyColumnsCache)
            return this.aggDailyColumnsCache;
        const db = process.env.CLICKHOUSE_DB_NAME || 'default';
        const rows = await clickhouseService_1.clickHouseService.query(`SELECT name FROM system.columns WHERE database = {db: String} AND table = {table: String}`, { db, table: 'agg_daily' });
        this.aggDailyColumnsCache = new Set((rows ?? []).map((r) => String(r?.name ?? '').trim()).filter(Boolean));
        return this.aggDailyColumnsCache;
    }
    async getAggHourlyColumns() {
        if (this.aggHourlyColumnsCache)
            return this.aggHourlyColumnsCache;
        const db = process.env.CLICKHOUSE_DB_NAME || 'default';
        const rows = await clickhouseService_1.clickHouseService.query(`SELECT name FROM system.columns WHERE database = {db: String} AND table = {table: String}`, { db, table: 'agg_hourly' });
        this.aggHourlyColumnsCache = new Set((rows ?? []).map((r) => String(r?.name ?? '').trim()).filter(Boolean));
        return this.aggHourlyColumnsCache;
    }
    async getAggDailyDashboardColumns() {
        if (this.aggDailyDashboardColumnsCache)
            return this.aggDailyDashboardColumnsCache;
        const db = process.env.CLICKHOUSE_DB_NAME || 'default';
        const rows = await clickhouseService_1.clickHouseService.query(`SELECT name FROM system.columns WHERE database = {db: String} AND table = {table: String}`, { db, table: 'agg_daily_dashboard' });
        this.aggDailyDashboardColumnsCache = new Set((rows ?? []).map((r) => String(r?.name ?? '').trim()).filter(Boolean));
        return this.aggDailyDashboardColumnsCache;
    }
    normalizeObserveSpan(raw) {
        const merged = {
            ...(raw?.resourceAttributes ?? raw?.ResourceAttributes ?? {}),
            ...(raw?.attributes ?? raw?.Attributes ?? {}),
        };
        // Normalize common key spellings to what the observe parsers expect.
        for (const [k, v] of Object.entries(merged)) {
            if (k.startsWith('gen_ai.')) {
                const nk = `genai.${k.slice('gen_ai.'.length)}`;
                if (merged[nk] === undefined)
                    merged[nk] = v;
            }
        }
        const alias = (from, to) => {
            if (merged[to] === undefined && merged[from] !== undefined)
                merged[to] = merged[from];
        };
        alias('gen_ai.system', 'genai.system');
        alias('gen_ai.framework', 'genai.framework');
        alias('gen_ai.workflow.type', 'genai.workflow.type');
        alias('gen_ai.tool.name', 'genai.tool.name');
        alias('gen_ai.usage.total_tokens', 'genai.usage.totaltokens');
        const traceId = String(raw?.traceId ?? raw?.TraceId ?? merged.traceId ?? 'unknown-trace');
        const id = String(raw?.id ?? raw?.SpanId ?? merged.id ?? `${traceId}:${Math.random().toString(16).slice(2)}`);
        const parentSpanIdRaw = raw?.parentSpanId ?? raw?.ParentSpanId ?? merged.parentSpanId ?? null;
        const parentSpanId = parentSpanIdRaw ? String(parentSpanIdRaw) : null;
        const serviceName = String(raw?.serviceName ??
            raw?.ServiceName ??
            (raw?.resourceAttributes ?? raw?.ResourceAttributes)?.['service.name'] ??
            merged?.['service.name'] ??
            'unknown') || 'unknown';
        const timestamp = String(raw?.timestamp ?? raw?.Timestamp ?? merged.timestamp ?? new Date(0).toISOString());
        const duration = utilts_1.FrameworkUtils.safeParseFloat(raw?.duration ?? raw?.Duration ?? merged.duration ?? 0) ?? 0;
        return {
            id,
            traceId,
            parentSpanId,
            name: String(raw?.name ?? raw?.SpanName ?? merged.name ?? 'span'),
            duration,
            timestamp,
            serviceName,
            attributes: merged,
            events: raw?.events ?? raw?.Events,
            resourceAttributes: raw?.resourceAttributes ?? raw?.ResourceAttributes,
        };
    }
    normalizeObserveSpans(spans) {
        const out = [];
        for (const s of spans || []) {
            if (Array.isArray(s)) {
                out.push(...s.map((x) => this.normalizeObserveSpan(x)));
            }
            else {
                out.push(this.normalizeObserveSpan(s));
            }
        }
        return out;
    }
    chString(value) {
        const s = String(value ?? '');
        // ClickHouse string literal escaping (single quotes doubled).
        return `'${s.replace(/'/g, "''")}'`;
    }
    histogramBucketMs(valueMs, bucketSize = 20) {
        if (!Number.isFinite(valueMs) || valueMs <= 0)
            return '0';
        const ms = new decimal_js_1.default(valueMs).toDecimalPlaces(2);
        const lowerBound = ms.div(bucketSize).floor().mul(bucketSize);
        const upperBound = lowerBound.plus(bucketSize);
        return `${lowerBound.toDecimalPlaces(2).toString()}-${upperBound.toDecimalPlaces(2).toString()}`;
    }
    extractTtftMs(span) {
        if (!span)
            return undefined;
        const attrs = span.attributes ?? {};
        const raw = attrs['gen_ai.server.time_to_first_token'] ??
            attrs['genai.server.time_to_first_token'] ??
            attrs['gen_ai.time_to_first_token'] ??
            attrs['genai.time_to_first_token'];
        if (raw === undefined || raw === null)
            return undefined;
        const n = Number.parseFloat(String(raw));
        return Number.isFinite(n) ? n : undefined;
    }
    buildMapSumStateExpr(hist) {
        // const entries = Object.entries(hist).filter(([, v]) => Number.isFinite(v) && v !== 0);
        const entries = Object.entries(hist);
        if (entries.length === 0) {
            return `CAST(map(), 'Map(String, AggregateFunction(sum, UInt64))')`;
        }
        const parts = [];
        for (const [k, v] of entries) {
            parts.push(this.chString(k));
            parts.push(`sumState(toUInt64(${Math.max(0, Math.floor(v))}))`);
        }
        return `map(${parts.join(', ')})`;
    }
    /**
     * Histogram aggregation that *merges correctly* in AggregatingMergeTree:
     * store as AggregateFunction(sumMap, Array(String), Array(UInt64)) via sumMapState(keys, values).
     *
     * This avoids the "only first row wins" behavior of Map(String, AggregateFunction(...)) during merges.
     */
    buildHistogramSumMapStateExpr(hist) {
        const entries = Object.entries(hist).filter(([k, v]) => Boolean(k) && Number.isFinite(v) && v !== 0);
        if (entries.length === 0) {
            return `sumMapState(CAST([], 'Array(String)'), CAST([], 'Array(UInt64)'))`;
        }
        const keys = entries.map(([k]) => this.chString(k)).join(', ');
        const values = entries.map(([, v]) => `toUInt64(${Math.max(0, Math.floor(v))})`).join(', ');
        return `sumMapState([${keys}], [${values}])`;
    }
    buildMetricsMapStateExpr(entries) {
        const rows = Object.entries(entries);
        if (rows.length === 0) {
            return `CAST(map(), 'Map(String, Tuple(AggregateFunction(sum, UInt64), AggregateFunction(sum, UInt64), AggregateFunction(sum, Float64), AggregateFunction(sum, UInt64)))')`;
        }
        const parts = [];
        for (const [k, v] of rows) {
            parts.push(this.chString(k));
            parts.push(`(` +
                `sumState(toUInt64(${Math.max(0, Math.floor(v.pass))})), ` +
                `sumState(toUInt64(${Math.max(0, Math.floor(v.fail))})), ` +
                `sumState(toFloat64(${Number.isFinite(v.scoreSum) ? v.scoreSum : 0})), ` +
                `sumState(toUInt64(${Math.max(0, Math.floor(v.scoreCount))}))` +
                `)`);
        }
        return `map(${parts.join(', ')})`;
    }
    /** Build ClickHouse state expression for vectordb_responseTimeByOperation: map(opName -> (sumState, countState, quantilesState)). */
    buildVectordbResponseTimeByOperationStateExpr(operationName, durationMs) {
        const d = Number.isFinite(durationMs) ? durationMs : 0;
        return `map(${this.chString(operationName)}, (sumState(toFloat64(${d})), sumState(toUInt64(1)), quantilesState(0.6, 0.9, 0.99)(toFloat64(${d}))))`;
    }
    /** Build exprByCol entries for all vectordb_* columns. When vectordbName is empty, use default zero/empty states. */
    buildVectordbExprByCol(vectordb) {
        const emptyHist = () => `sumMapState(CAST([], 'Array(String)'), CAST([], 'Array(UInt64)'))`;
        const emptySumMap = () => `sumMapState(CAST([], 'Array(String)'), CAST([], 'Array(UInt64)'))`;
        const emptyResponseTimeMap = () => `CAST(map(), 'Map(String, Tuple(AggregateFunction(sum, Float64), AggregateFunction(sum, UInt64), AggregateFunction(quantiles(0.6, 0.9, 0.99), Float64)))')`;
        const emptyRtSumMap = () => `sumMapState(CAST([], 'Array(String)'), CAST([], 'Array(Float64)'))`;
        const emptyRtCountMap = () => `sumMapState(CAST([], 'Array(String)'), CAST([], 'Array(UInt64)'))`;
        if (!vectordb || lodash_1.default.isEmpty(vectordb)) {
            return {
                vectordb_name: this.chString(''),
                vectordb_pass_count_state: `sumState(toUInt64(0))`,
                vectordb_fail_count_state: `sumState(toUInt64(0))`,
                vectordb_error_count_state: `sumState(toUInt64(0))`,
                vectordb_retrievalHit_count_state: `sumState(toUInt64(0))`,
                vectordb_retrievalTotal_count_state: `sumState(toUInt64(0))`,
                vectordb_retrievalHit_quantiles_state: `quantilesState(0.6, 0.9, 0.99)(toFloat64(0))`,
                vectordb_retrievalLatency_state: `sumState(toFloat64(0))`,
                vectordb_retrievalLatencyCount_state: `sumState(toUInt64(0))`,
                vectordb_retrievalLatency_quantiles_state: `quantilesState(0.6, 0.9, 0.99)(toFloat64(0))`,
                vectordb_retrievalLatency_hist_sumMap_state: emptyHist(),
                vectordb_queries_count_state: `sumState(toUInt64(0))`,
                vectordb_docsRetrieved_state: `sumState(toUInt64(0))`,
                vectordb_docsRetrievedCount_state: `sumState(toUInt64(0))`,
                vectordb_requestVolumeByOperation_sumMap_state: emptySumMap(),
                vectordb_responseTimeByOperation_state: emptyResponseTimeMap(),
                vectordb_responseTimeByOp_sum_sumMap_state: emptyRtSumMap(),
                vectordb_responseTimeByOp_count_sumMap_state: emptyRtCountMap(),
            };
        }
        const v = vectordb;
        const retrievalHit = Number.isFinite(v.retrievalHitQuantileValue) ? v.retrievalHitQuantileValue : 0;
        const latency = Number.isFinite(v.retrievalLatencyQuantileValue) ? v.retrievalLatencyQuantileValue : 0;
        return {
            vectordb_name: this.chString(v.vectordbName) ?? 'vectordb',
            vectordb_pass_count_state: `sumState(toUInt64(${Math.max(0, Math.floor(v.passCount))}))`,
            vectordb_fail_count_state: `sumState(toUInt64(${Math.max(0, Math.floor(v.failCount))}))`,
            vectordb_error_count_state: `sumState(toUInt64(${Math.max(0, Math.floor(v.errorCount))}))`,
            vectordb_retrievalHit_count_state: `sumState(toUInt64(${Math.max(0, Math.floor(v.retrievalHitCount))}))`,
            vectordb_retrievalTotal_count_state: `sumState(toUInt64(${Math.max(0, Math.floor(v.retrievalTotalCount))}))`,
            vectordb_retrievalHit_quantiles_state: `quantilesState(0.6, 0.9, 0.99)(toFloat64(${retrievalHit}))`,
            vectordb_retrievalLatency_state: `sumState(toFloat64(${v.retrievalLatencySum}))`,
            vectordb_retrievalLatencyCount_state: `sumState(toUInt64(${Math.max(0, Math.floor(v.retrievalLatencyCount))}))`,
            vectordb_retrievalLatency_quantiles_state: `quantilesState(0.6, 0.9, 0.99)(toFloat64(${latency}))`,
            vectordb_retrievalLatency_hist_sumMap_state: Object.keys(v.retrievalLatencyHist ?? {}).length > 0
                ? this.buildHistogramSumMapStateExpr(v.retrievalLatencyHist)
                : emptyHist(),
            vectordb_queries_count_state: `sumState(toUInt64(${Math.max(0, Math.floor(v.queriesCount))}))`,
            vectordb_docsRetrieved_state: `sumState(toUInt64(${Math.max(0, Math.floor(v.docsRetrieved))}))`,
            vectordb_docsRetrievedCount_state: `sumState(toUInt64(${Math.max(0, Math.floor(v.docsRetrievedCount))}))`,
            vectordb_requestVolumeByOperation_sumMap_state: Object.keys(v.requestVolumeByOperation ?? {}).length > 0
                ? this.buildHistogramSumMapStateExpr(v.requestVolumeByOperation)
                : emptySumMap(),
            vectordb_responseTimeByOperation_state: this.buildVectordbResponseTimeByOperationStateExpr(v.responseTimeByOperationOp, v.responseTimeByOperationDurationMs),
            vectordb_responseTimeByOp_sum_sumMap_state: `sumMapState([${this.chString(v.responseTimeByOperationOp)}], [toFloat64(${Number.isFinite(v.responseTimeByOperationDurationMs) ? v.responseTimeByOperationDurationMs : 0})])`,
            vectordb_responseTimeByOp_count_sumMap_state: `sumMapState([${this.chString(v.responseTimeByOperationOp)}], [toUInt64(1)])`,
        };
    }
    buildAggDailyRowSql(params, columns = ObserveDashboardService.AGG_DAILY_KNOWN_COLUMNS) {
        const { day, applicationId, workspaceId, projectId, environment, version, agent, toolName, model, isTrace, isSession, costUsd, durationMs, totalTokensUsed, toolTokensUsed, modelTokensUsed, ttftAvgMs, totalToolRuns, totalModelRuns, toolSampleCount, modelSampleCount, requestDurationHist, ttftHist, metricsMap, pluginsMap, cacheReadTokens = 0, cacheWriteTokens = 0, vectordb, } = params;
        const durationMsU = Math.max(0, Math.floor(durationMs));
        const tokensU = Math.max(0, Math.floor(totalTokensUsed));
        const toolTokensU = Math.max(0, Math.floor(toolTokensUsed));
        const modelTokensU = Math.max(0, Math.floor(modelTokensUsed));
        const ttftU = Math.max(0, Math.floor(ttftAvgMs));
        const costF = Number.isFinite(costUsd) ? costUsd : 0;
        const toolCountU = Math.max(0, Math.floor(toolSampleCount));
        const modelCountU = Math.max(0, Math.floor(modelSampleCount));
        const exprByCol = {
            day: `toDate(${this.chString(day)})`,
            applicationId: this.chString(applicationId),
            workspaceId: this.chString(workspaceId),
            projectId: this.chString(projectId ?? ''),
            environment: this.chString(environment ?? ''),
            version: this.chString(version ?? ''),
            agent: this.chString(agent),
            toolName: this.chString(toolName),
            model: this.chString(model),
            traces_state: `sumState(toUInt64(${isTrace ? 1 : 0}))`,
            sessions_state: `sumState(toUInt64(${isSession ? 1 : 0}))`,
            cost_state: `sumState(toFloat64(${costF}))`,
            costCount_state: `sumState(toUInt64(1))`,
            requestDuration_state: `sumState(toFloat64(${durationMs}))`,
            requestDurationCount_state: `sumState(toUInt64(1))`,
            requestDuration_hist_state: this.buildMapSumStateExpr(requestDurationHist),
            requestDuration_hist_sumMap_state: this.buildHistogramSumMapStateExpr(requestDurationHist),
            tokens_state: `sumState(toUInt64(${tokensU}))`,
            tokensCount_state: `sumState(toUInt64(1))`,
            timeToFirstToken_state: `sumState(toFloat64(${ttftAvgMs}))`,
            timeToFirstToken_quantiles_state: `quantilesState(0.6, 0.9, 0.99)(toFloat64(${ttftAvgMs}))`,
            timeToFirstTokenCount_state: `sumState(toUInt64(1))`,
            timeToFirstToken_hist_state: this.buildMapSumStateExpr(ttftHist),
            timeToFirstToken_hist_sumMap_state: this.buildHistogramSumMapStateExpr(ttftHist),
            cacheReadTokens_state: `sumState(toUInt64(${Math.max(0, Math.floor(cacheReadTokens))}))`,
            cacheWriteTokens_state: `sumState(toUInt64(${Math.max(0, Math.floor(cacheWriteTokens))}))`,
            cacheReadTokensCount_state: `sumState(toUInt64(${cacheReadTokens > 0 ? 1 : 0}))`,
            cacheWriteTokensCount_state: `sumState(toUInt64(${cacheWriteTokens > 0 ? 1 : 0}))`,
            totalRuns_state: `sumState(toUInt64(1))`,
            totalCount_state: `sumState(toUInt64(1))`,
            totalTokensUsed_state: `sumState(toUInt64(${tokensU}))`,
            totalTokensUsed_quantiles_state: `quantilesState(0.6, 0.9, 0.99)(toUInt64(${tokensU}))`,
            totalTokensCount_state: `sumState(toUInt64(1))`,
            totalToolRuns_state: `sumState(toUInt64(${Math.max(0, Math.floor(totalToolRuns))}))`,
            totalToolCount_state: `sumState(toUInt64(${toolCountU}))`,
            totalToolTokensUsed_state: `sumState(toUInt64(${toolTokensU}))`,
            totalToolTokensUsed_quantiles_state: `quantilesState(0.6, 0.9, 0.99)(toUInt64(${toolTokensU}))`,
            totalToolTokensCount_state: `sumState(toUInt64(${toolCountU}))`,
            totalModelRuns_state: `sumState(toUInt64(${Math.max(0, Math.floor(totalModelRuns))}))`,
            totalModelCount_state: `sumState(toUInt64(${modelCountU}))`,
            totalModelTokensUsed_state: `sumState(toUInt64(${modelTokensU}))`,
            totalModelTokensUsed_quantiles_state: `quantilesState(0.6, 0.9, 0.99)(toUInt64(${modelTokensU}))`,
            totalModelTokensCount_state: `sumState(toUInt64(${modelCountU}))`,
            metrics_state: this.buildMetricsMapStateExpr(metricsMap),
            plugins_state: this.buildMetricsMapStateExpr(pluginsMap),
            requestDuration_quantiles_state: `quantilesState(0.6, 0.9, 0.99)(toFloat64(${durationMs}))`,
            ...this.buildVectordbExprByCol(vectordb ?? null),
        };
        const values = columns
            .map((c) => exprByCol[c])
            .filter((v) => typeof v === 'string' && v.length > 0);
        return `(${values.join(', ')})`.replace(/\s+/g, ' ').trim();
    }
    buildAggDailyRowSelectSql(params, columns) {
        // Reuse the same expression mapping as buildAggDailyRowSql, but emit a SELECT row (not VALUES tuple).
        const tuple = this.buildAggDailyRowSql(params, columns);
        // tuple looks like "(expr1, expr2, ...)" -> convert to "SELECT expr1, expr2, ..."
        return `SELECT ${tuple.slice(1, -1)}`.trim();
    }
    buildAggHourlyRowSql(params, columns = ObserveDashboardService.AGG_HOURLY_KNOWN_COLUMNS) {
        const { hour, applicationId, workspaceId, projectId, environment, version, agent, toolName, model, isTrace, isSession, costUsd, durationMs, totalTokensUsed, toolTokensUsed, modelTokensUsed, ttftAvgMs, totalToolRuns, totalModelRuns, toolSampleCount, modelSampleCount, requestDurationHist, ttftHist, metricsMap, pluginsMap, cacheReadTokens = 0, cacheWriteTokens = 0, vectordb, } = params;
        const durationMsU = Math.max(0, Math.floor(durationMs));
        const tokensU = Math.max(0, Math.floor(totalTokensUsed));
        const toolTokensU = Math.max(0, Math.floor(toolTokensUsed));
        const modelTokensU = Math.max(0, Math.floor(modelTokensUsed));
        const ttftU = Math.max(0, Math.floor(ttftAvgMs));
        const costF = Number.isFinite(costUsd) ? costUsd : 0;
        const toolCountU = Math.max(0, Math.floor(toolSampleCount));
        const modelCountU = Math.max(0, Math.floor(modelSampleCount));
        const exprByCol = {
            hour: `toDateTime(${this.chString(hour)})`,
            applicationId: this.chString(applicationId),
            workspaceId: this.chString(workspaceId),
            projectId: this.chString(projectId ?? ''),
            environment: this.chString(environment ?? ''),
            version: this.chString(version ?? ''),
            agent: this.chString(agent),
            toolName: this.chString(toolName),
            model: this.chString(model),
            traces_state: `sumState(toUInt64(${isTrace ? 1 : 0}))`,
            sessions_state: `sumState(toUInt64(${isSession ? 1 : 0}))`,
            cost_state: `sumState(toFloat64(${costF}))`,
            costCount_state: `sumState(toUInt64(1))`,
            requestDuration_state: `sumState(toFloat64(${durationMs}))`,
            requestDurationCount_state: `sumState(toUInt64(1))`,
            requestDuration_hist_state: this.buildMapSumStateExpr(requestDurationHist),
            requestDuration_hist_sumMap_state: this.buildHistogramSumMapStateExpr(requestDurationHist),
            tokens_state: `sumState(toUInt64(${tokensU}))`,
            tokensCount_state: `sumState(toUInt64(1))`,
            timeToFirstToken_state: `sumState(toFloat64(${ttftAvgMs}))`,
            timeToFirstToken_quantiles_state: `quantilesState(0.6, 0.9, 0.99)(toFloat64(${ttftAvgMs}))`,
            timeToFirstTokenCount_state: `sumState(toUInt64(1))`,
            timeToFirstToken_hist_state: this.buildMapSumStateExpr(ttftHist),
            timeToFirstToken_hist_sumMap_state: this.buildHistogramSumMapStateExpr(ttftHist),
            cacheReadTokens_state: `sumState(toUInt64(${Math.max(0, Math.floor(cacheReadTokens))}))`,
            cacheWriteTokens_state: `sumState(toUInt64(${Math.max(0, Math.floor(cacheWriteTokens))}))`,
            cacheReadTokensCount_state: `sumState(toUInt64(${cacheReadTokens > 0 ? 1 : 0}))`,
            cacheWriteTokensCount_state: `sumState(toUInt64(${cacheWriteTokens > 0 ? 1 : 0}))`,
            totalRuns_state: `sumState(toUInt64(1))`,
            totalCount_state: `sumState(toUInt64(1))`,
            totalTokensUsed_state: `sumState(toUInt64(${tokensU}))`,
            totalTokensUsed_quantiles_state: `quantilesState(0.6, 0.9, 0.99)(toUInt64(${tokensU}))`,
            totalTokensCount_state: `sumState(toUInt64(1))`,
            totalToolRuns_state: `sumState(toUInt64(${Math.max(0, Math.floor(totalToolRuns))}))`,
            totalToolCount_state: `sumState(toUInt64(${toolCountU}))`,
            totalToolTokensUsed_state: `sumState(toUInt64(${toolTokensU}))`,
            totalToolTokensUsed_quantiles_state: `quantilesState(0.6, 0.9, 0.99)(toUInt64(${toolTokensU}))`,
            totalToolTokensCount_state: `sumState(toUInt64(${toolCountU}))`,
            totalModelRuns_state: `sumState(toUInt64(${Math.max(0, Math.floor(totalModelRuns))}))`,
            totalModelCount_state: `sumState(toUInt64(${modelCountU}))`,
            totalModelTokensUsed_state: `sumState(toUInt64(${modelTokensU}))`,
            totalModelTokensUsed_quantiles_state: `quantilesState(0.6, 0.9, 0.99)(toUInt64(${modelTokensU}))`,
            totalModelTokensCount_state: `sumState(toUInt64(${modelCountU}))`,
            metrics_state: this.buildMetricsMapStateExpr(metricsMap),
            plugins_state: this.buildMetricsMapStateExpr(pluginsMap),
            requestDuration_quantiles_state: `quantilesState(0.6, 0.9, 0.99)(toFloat64(${durationMs}))`,
            ...this.buildVectordbExprByCol(vectordb ?? null),
        };
        const values = columns
            .map((c) => exprByCol[c])
            .filter((v) => typeof v === 'string' && v.length > 0);
        return `(${values.join(', ')})`.replace(/\s+/g, ' ').trim();
    }
    buildAggHourlyRowSelectSql(params, columns) {
        const tuple = this.buildAggHourlyRowSql(params, columns);
        return `SELECT ${tuple.slice(1, -1)}`.trim();
    }
    buildAggDailyDashboardRowSelectSql(params, columns) {
        const exprByCol = {
            day: `toDate('${params.day}')`,
            applicationId: `'${String(params.applicationId).replace(/'/g, "''")}'`,
            workspaceId: `'${String(params.workspaceId).replace(/'/g, "''")}'`,
            projectId: `'${String(params.projectId).replace(/'/g, "''")}'`,
            environment: `'${String(params.environment).replace(/'/g, "''")}'`,
            version: `'${String(params.version).replace(/'/g, "''")}'`,
            totalTraces_state: `sumState(toUInt64(${Math.max(0, Math.floor(params.totalTraces))}))`,
            totalSessions_state: `sumState(toUInt64(${Math.max(0, Math.floor(params.totalSessions))}))`,
            totalTokens_state: `sumState(toUInt64(${Math.max(0, Math.floor(params.totalTokens))}))`,
            totalRequestDuration_state: `sumState(toUInt64(${Math.max(0, Math.floor(params.totalRequestDuration))}))`,
            requestDurationCount_state: `sumState(toUInt64(${Math.max(0, Math.floor(params.requestDurationCount))}))`,
            passedRequests_state: `sumState(toUInt64(${Math.max(0, Math.floor(params.passedRequests))}))`,
            failedRequests_state: `sumState(toUInt64(${Math.max(0, Math.floor(params.failedRequests))}))`,
            totalCost_state: `sumState(toFloat64(${Number.isFinite(params.totalCost) ? params.totalCost : 0}))`,
        };
        const values = columns
            .map((c) => exprByCol[c])
            .filter((v) => typeof v === 'string' && v.length > 0);
        return `SELECT ${values.join(', ')}`.trim();
    }
    async writeAggDailyFromSpans(params) {
        const { spans, applicationId, workspaceId, projectId, traceId, sessionId, monitoringConfig, application } = params;
        if (!Array.isArray(spans) || spans.length === 0) {
            return;
        }
        // ---- System health (trace-level): if ANY event has exception.type => failed request ----
        const hasExceptionType = (val) => {
            const s = String(val ?? '').trim();
            return s.length > 0;
        };
        const parseMaybeJsonArray = (v) => {
            if (Array.isArray(v))
                return v;
            if (typeof v === 'string' && v.trim().startsWith('[')) {
                try {
                    const parsed = JSON.parse(v);
                    return Array.isArray(parsed) ? parsed : undefined;
                }
                catch {
                    return undefined;
                }
            }
            return undefined;
        };
        const traceFailed = (spans ?? []).some((s) => {
            const eventsAttrs = parseMaybeJsonArray(s?.eventsAttributes) ??
                parseMaybeJsonArray(s?.['Events.Attributes']) ??
                undefined;
            if (eventsAttrs && eventsAttrs.some((ea) => hasExceptionType(ea?.['exception.type'])))
                return true;
            const events = parseMaybeJsonArray(s?.events) ??
                parseMaybeJsonArray(s?.Events) ??
                undefined;
            if (events && events.some((e) => hasExceptionType((e?.attributes ?? e)?.['exception.type'])))
                return true;
            return false;
        });
        const normalizedSpans = this.normalizeObserveSpans(spans);
        if (normalizedSpans.length === 0)
            return;
        const parser = new frameworkParser_1.MultiFrameworkParser();
        const parsed = parser.parse(normalizedSpans);
        const ragCalls = (0, ragParser_1.parseRAG)(normalizedSpans);
        let llmModels = parsed?.models ?? [];
        if (parsed?.framework === 'llamaindex') {
            llmModels = parsed?.unScopedModels ?? [];
        }
        const spanById = new Map(normalizedSpans.map((s) => [s.id, s]));
        const roots = normalizedSpans.filter((s) => !s.parentSpanId || !spanById.has(s.parentSpanId));
        const derivedDurationMs = roots.reduce((sum, s) => sum + (Number(s.duration) || 0), 0) / 1000000;
        const totalDurationMs = derivedDurationMs > 0 ? derivedDurationMs : Number(parsed?.metadata?.totalDuration ?? 0);
        const totalCost = Number(parsed?.metadata?.totalCost ?? 0);
        const totalToolRuns = parsed?.tools?.length ?? 0;
        const totalModelRuns = llmModels?.length ?? 0;
        const totalTokensUsed = (llmModels ?? []).reduce((sum, m) => {
            return sum + Number(m?.tokenUsage?.totalTokens ?? 0);
        }, 0);
        // Cache read/write: sum once per unique model id (match frameworkParser id check)
        const seenModelIdsForCache = new Set();
        let totalCacheRead = 0;
        let totalCacheWrite = 0;
        for (const m of llmModels ?? []) {
            if (seenModelIdsForCache.has(m.id))
                continue;
            seenModelIdsForCache.add(m.id);
            totalCacheRead += Number(m?.tokenUsage?.cacheRead ?? 0);
            totalCacheWrite += Number(m?.tokenUsage?.cacheWrite ?? 0);
        }
        const ttftValues = [];
        for (const m of llmModels ?? []) {
            const ttft = this.extractTtftMs(spanById.get(m.spanId));
            if (typeof ttft === 'number' && Number.isFinite(ttft) && ttft >= 0)
                ttftValues.push(ttft);
        }
        const ttftAvgMs = ttftValues.length > 0 ? ttftValues.reduce((a, b) => a + b, 0) / ttftValues.length : 0;
        // Histograms (one sample per trace row)
        const requestDurationHist = {
            [this.histogramBucketMs(totalDurationMs, 20)]: 1,
        };
        const ttftHist = ttftValues.length > 0
            ? { [this.histogramBucketMs(ttftAvgMs, 0.05)]: 1 }
            : { '0-0': 1 };
        // Metrics / plugins maps from evaluation results
        const metricsMap = {};
        const pluginsMap = {};
        const timestamps = normalizedSpans.map((s) => new Date(s.timestamp).getTime()).filter(Number.isFinite);
        const minTs = timestamps.length ? Math.min(...timestamps) : Date.now();
        const day = (0, date_fns_1.format)(new Date(minTs), 'yyyy-MM-dd');
        const hour = (0, date_fns_1.format)(new Date(minTs), 'yyyy-MM-dd HH:00:00');
        const environment = String(parsed?.environment ?? parsed?.metadata?.environment ?? '').trim();
        const version = String(parsed?.version ?? parsed?.metadata?.version ?? '').trim();
        const baseDimsCommon = {
            applicationId,
            workspaceId,
            projectId: projectId ?? '',
            environment,
            version,
            isTrace: !!traceId,
            isSession: !!sessionId,
            metricsMap,
            pluginsMap,
        };
        const rowParamsDaily = [];
        const rowParamsHourly = [];
        // Overall (safe default slice)
        rowParamsDaily.push({
            ...baseDimsCommon,
            day,
            agent: '__all__',
            toolName: '__all__',
            model: '__all__',
            costUsd: totalCost,
            durationMs: totalDurationMs,
            totalTokensUsed,
            toolTokensUsed: 0,
            modelTokensUsed: totalTokensUsed,
            ttftAvgMs,
            totalToolRuns,
            totalModelRuns,
            toolSampleCount: 1,
            modelSampleCount: 1,
            requestDurationHist,
            ttftHist,
            cacheReadTokens: totalCacheRead,
            cacheWriteTokens: totalCacheWrite,
        });
        rowParamsHourly.push({
            ...baseDimsCommon,
            hour,
            agent: '__all__',
            toolName: '__all__',
            model: '__all__',
            costUsd: totalCost,
            durationMs: totalDurationMs,
            totalTokensUsed,
            toolTokensUsed: 0,
            modelTokensUsed: totalTokensUsed,
            ttftAvgMs,
            totalToolRuns,
            totalModelRuns,
            toolSampleCount: 1,
            modelSampleCount: 1,
            requestDurationHist,
            ttftHist,
            cacheReadTokens: totalCacheRead,
            cacheWriteTokens: totalCacheWrite,
        });
        // Per-model rows (model breakdown; cost/tokens attributable per model call)
        for (const m of llmModels ?? []) {
            const durMs = (m.duration ?? 0) / 1000000;
            const ttft = this.extractTtftMs(spanById.get(m.spanId)) ?? 0;
            const cacheRead = Number(m?.tokenUsage?.cacheRead ?? 0);
            const cacheWrite = Number(m?.tokenUsage?.cacheWrite ?? 0);
            rowParamsDaily.push({
                ...baseDimsCommon,
                version,
                day,
                agent: '__all__',
                toolName: '__all__',
                model: String(m.modelName ?? m.id ?? 'unknown'),
                costUsd: Number(m.cost ?? 0),
                durationMs: durMs,
                totalTokensUsed: Number(m?.tokenUsage?.totalTokens ?? 0),
                toolTokensUsed: 0,
                modelTokensUsed: Number(m?.tokenUsage?.totalTokens ?? 0),
                ttftAvgMs: ttft,
                totalToolRuns: 0,
                totalModelRuns: 1,
                toolSampleCount: 0,
                modelSampleCount: 1,
                requestDurationHist: { [this.histogramBucketMs(durMs, 20)]: 1 },
                ttftHist: ttft ? { [this.histogramBucketMs(ttft, 0.05)]: 1 } : { '0-0': 1 },
                cacheReadTokens: cacheRead,
                cacheWriteTokens: cacheWrite,
            });
            rowParamsHourly.push({
                ...baseDimsCommon,
                version,
                hour,
                agent: '__all__',
                toolName: '__all__',
                model: String(m.modelName ?? m.id ?? 'unknown'),
                costUsd: Number(m.cost ?? 0),
                durationMs: durMs,
                totalTokensUsed: Number(m?.tokenUsage?.totalTokens ?? 0),
                toolTokensUsed: 0,
                modelTokensUsed: Number(m?.tokenUsage?.totalTokens ?? 0),
                ttftAvgMs: ttft,
                totalToolRuns: 0,
                totalModelRuns: 1,
                toolSampleCount: 0,
                modelSampleCount: 1,
                requestDurationHist: { [this.histogramBucketMs(durMs, 20)]: 1 },
                ttftHist: ttft ? { [this.histogramBucketMs(ttft, 0.05)]: 1 } : { '0-0': 1 },
                cacheReadTokens: cacheRead,
                cacheWriteTokens: cacheWrite,
            });
        }
        let isAgentic = application?.isAgentic ?? false;
        // Per-tool rows (counts + cumulative durations/tokens from nested model calls)
        for (const t of parsed?.tools ?? []) {
            const durMs = (spanById.get(t.spanId)?.duration ?? 0) / 1000000;
            const cumTokens = Number(t?.cumulativeTokenUsage?.totalTokens ?? 0);
            const toolCacheRead = Number(t?.cumulativeTokenUsage?.cacheRead ?? 0);
            const toolCacheWrite = Number(t?.cumulativeTokenUsage?.cacheWrite ?? 0);
            const tTtft = Array.isArray(t?.ttft) ? t.ttft : [];
            const tTtftAvg = tTtft.length ? tTtft.reduce((a, b) => a + b, 0) / tTtft.length : 0;
            rowParamsDaily.push({
                ...baseDimsCommon,
                version,
                day,
                agent: '__all__',
                toolName: String(t.name ?? t.id ?? 'unknown'),
                model: '__all__',
                costUsd: 0,
                durationMs: durMs,
                totalTokensUsed: cumTokens,
                toolTokensUsed: cumTokens,
                modelTokensUsed: 0,
                ttftAvgMs: tTtftAvg,
                totalToolRuns: 1,
                totalModelRuns: 0,
                toolSampleCount: 1,
                modelSampleCount: 0,
                requestDurationHist: durMs ? { [this.histogramBucketMs(durMs, 20)]: 1 } : { '0-0': 1 },
                ttftHist: tTtftAvg ? { [this.histogramBucketMs(tTtftAvg, 0.05)]: 1 } : { '0-0': 1 },
                cacheReadTokens: toolCacheRead,
                cacheWriteTokens: toolCacheWrite,
            });
            rowParamsHourly.push({
                ...baseDimsCommon,
                version,
                hour,
                agent: '__all__',
                toolName: String(t.name ?? t.id ?? 'unknown'),
                model: '__all__',
                costUsd: 0,
                durationMs: durMs,
                totalTokensUsed: cumTokens,
                toolTokensUsed: cumTokens,
                modelTokensUsed: 0,
                ttftAvgMs: tTtftAvg,
                totalToolRuns: 1,
                totalModelRuns: 0,
                toolSampleCount: 1,
                modelSampleCount: 0,
                requestDurationHist: durMs ? { [this.histogramBucketMs(durMs, 20)]: 1 } : { '0-0': 1 },
                ttftHist: tTtftAvg ? { [this.histogramBucketMs(tTtftAvg, 0.05)]: 1 } : { '0-0': 1 },
                cacheReadTokens: toolCacheRead,
                cacheWriteTokens: toolCacheWrite,
            });
        }
        // Per-agent rows (counts + cumulative durations/tokens from nested model calls)
        for (const a of parsed?.agents ?? []) {
            const durMs = (spanById.get(a.id)?.duration ?? 0) / 1000000;
            const cumTokens = Number(a?.cumulativeTokenUsage?.totalTokens ?? 0);
            const agentCacheRead = Number(a?.cumulativeTokenUsage?.cacheRead ?? 0);
            const agentCacheWrite = Number(a?.cumulativeTokenUsage?.cacheWrite ?? 0);
            const aTtft = Array.isArray(a?.ttft) ? a.ttft : [];
            const aTtftAvg = aTtft.length ? aTtft.reduce((x, y) => x + y, 0) / aTtft.length : 0;
            rowParamsDaily.push({
                ...baseDimsCommon,
                version,
                day,
                agent: String(a.name ?? a.id ?? 'unknown'),
                toolName: '__all__',
                model: '__all__',
                costUsd: 0,
                durationMs: durMs,
                totalTokensUsed: cumTokens,
                toolTokensUsed: 0,
                modelTokensUsed: 0,
                ttftAvgMs: aTtftAvg,
                totalToolRuns: 0,
                totalModelRuns: 0,
                toolSampleCount: 0,
                modelSampleCount: 0,
                requestDurationHist: durMs ? { [this.histogramBucketMs(durMs, 20)]: 1 } : { '0-0': 1 },
                ttftHist: aTtftAvg ? { [this.histogramBucketMs(aTtftAvg, 0.05)]: 1 } : { '0-0': 1 },
                cacheReadTokens: agentCacheRead,
                cacheWriteTokens: agentCacheWrite,
            });
            rowParamsHourly.push({
                ...baseDimsCommon,
                version,
                hour,
                agent: String(a.name ?? a.id ?? 'unknown'),
                toolName: '__all__',
                model: '__all__',
                costUsd: 0,
                durationMs: durMs,
                totalTokensUsed: cumTokens,
                toolTokensUsed: 0,
                modelTokensUsed: 0,
                ttftAvgMs: aTtftAvg,
                totalToolRuns: 0,
                totalModelRuns: 0,
                toolSampleCount: 0,
                modelSampleCount: 0,
                requestDurationHist: durMs ? { [this.histogramBucketMs(durMs, 20)]: 1 } : { '0-0': 1 },
                ttftHist: aTtftAvg ? { [this.histogramBucketMs(aTtftAvg, 0.05)]: 1 } : { '0-0': 1 },
                cacheReadTokens: agentCacheRead,
                cacheWriteTokens: agentCacheWrite,
            });
        }
        // If there are agents or tools, set isAgentic to true
        if ((Array.isArray(parsed?.agents) && parsed?.agents?.length > 0)
            || (Array.isArray(parsed?.tools) && parsed?.tools?.length > 0)) {
            isAgentic = true;
        }
        // If isAgentic is true and the application is not agentic, update the application isAgentic field
        if (isAgentic && !monitoringConfig?.application?.isAgentic) {
            const db = await (0, database_1.getDb)();
            await db.update(tables_1.applicationsTable).set({
                isAgentic: true,
            }).where((0, drizzle_orm_1.eq)(tables_1.applicationsTable.id, applicationId));
            // update inmemory objects
            monitoringConfig.application.isAgentic = true;
            application.isAgentic = true;
        }
        // If vector DB calls were found and the application does not have hasVectorDb set, update it
        if (Array.isArray(ragCalls) && ragCalls.length > 0 && !monitoringConfig?.application?.hasVectorDb) {
            const db = await (0, database_1.getDb)();
            await db.update(tables_1.applicationsTable).set({
                hasVectorDb: true,
            }).where((0, drizzle_orm_1.eq)(tables_1.applicationsTable.id, applicationId));
            monitoringConfig.application.hasVectorDb = true;
            application.hasVectorDb = true;
        }
        // Per-RAG (vectordb) rows: one row per RAG call for agg_daily and agg_hourly
        const zeroedMetrics = {
            costUsd: 0,
            durationMs: 0,
            totalTokensUsed: 0,
            toolTokensUsed: 0,
            modelTokensUsed: 0,
            ttftAvgMs: 0,
            totalToolRuns: 0,
            totalModelRuns: 0,
            toolSampleCount: 0,
            modelSampleCount: 0,
            requestDurationHist: { '0-0': 1 },
            ttftHist: { '0-0': 1 },
            cacheReadTokens: 0,
            cacheWriteTokens: 0,
        };
        for (const call of ragCalls ?? []) {
            const durationMs = Number.isFinite(call.duration) ? call.duration : 0;
            const retrievalLatencyHist = durationMs > 0 ? { [this.histogramBucketMs(durationMs, 0.05)]: 1 } : { '0-0': 1 };
            const vectordb = {
                vectordbName: String(call.systemName ?? '').trim() || 'unknown',
                passCount: call.status === 'passed' ? 1 : 0,
                failCount: call.status === 'failed' ? 1 : 0,
                errorCount: call.status === 'error' ? 1 : 0,
                retrievalHitCount: call.status === 'passed' ? 1 : 0,
                retrievalTotalCount: 1,
                retrievalHitQuantileValue: call.status === 'passed' ? 1 : 0,
                retrievalLatencySum: durationMs,
                retrievalLatencyCount: 1,
                retrievalLatencyQuantileValue: durationMs,
                retrievalLatencyHist,
                queriesCount: 1,
                docsRetrieved: Math.max(0, Math.floor(call.returnedRows ?? 0)),
                docsRetrievedCount: 1,
                requestVolumeByOperation: { [String(call.operationName ?? 'unknown')]: 1 },
                responseTimeByOperationOp: String(call.operationName ?? 'unknown'),
                responseTimeByOperationDurationMs: durationMs,
            };
            rowParamsDaily.push({
                ...baseDimsCommon,
                day,
                agent: '__all__',
                toolName: '__all__',
                model: '__all__',
                ...zeroedMetrics,
                vectordb,
            });
            rowParamsHourly.push({
                ...baseDimsCommon,
                hour,
                agent: '__all__',
                toolName: '__all__',
                model: '__all__',
                ...zeroedMetrics,
                vectordb,
            });
        }
        // ---- agg_daily (schema-aware insert) ----
        const existingDailyCols = await this.getAggDailyColumns();
        const insertDailyCols = ObserveDashboardService.AGG_DAILY_KNOWN_COLUMNS.filter((c) => existingDailyCols.has(c));
        if (insertDailyCols.length > 0 && rowParamsDaily.length > 0) {
            // IMPORTANT: ClickHouse `INSERT ... VALUES (...)` does not allow non-constant expressions for AggregateFunction states.
            // Use `INSERT ... SELECT ... UNION ALL SELECT ...` so we can compute sumState/quantilesState expressions.
            const selectsDaily = rowParamsDaily.map((p) => this.buildAggDailyRowSelectSql(p, insertDailyCols));
            const insertSqlDaily = `INSERT INTO agg_daily (${insertDailyCols.join(', ')}) ${selectsDaily.join(' UNION ALL ')}`;
            this.logger?.debug?.(`[agg_daily] inserting ${rowParamsDaily.length} row(s) with ${insertDailyCols.length} column(s)`);
            await clickhouseService_1.clickHouseService.command(insertSqlDaily);
        }
        // ---- agg_hourly (schema-aware insert) ----
        const existingHourlyCols = await this.getAggHourlyColumns();
        const insertHourlyCols = ObserveDashboardService.AGG_HOURLY_KNOWN_COLUMNS.filter((c) => existingHourlyCols.has(c));
        if (insertHourlyCols.length > 0 && rowParamsHourly.length > 0) {
            const selectsHourly = rowParamsHourly.map((p) => this.buildAggHourlyRowSelectSql(p, insertHourlyCols));
            const insertSqlHourly = `INSERT INTO agg_hourly (${insertHourlyCols.join(', ')}) ${selectsHourly.join(' UNION ALL ')}`;
            this.logger?.debug?.(`[agg_hourly] inserting ${rowParamsHourly.length} row(s) with ${insertHourlyCols.length} column(s)`);
            await clickhouseService_1.clickHouseService.command(insertSqlHourly);
        }
        // ---- agg_daily_dashboard (dashboard-level metrics) ----
        // System health is trace-level: passed iff no exception.type exists in ANY event; failed otherwise.
        const passedRequests = traceFailed ? 0 : 1;
        const failedRequests = traceFailed ? 1 : 0;
        const totalSessions = sessionId ? 1 : 0;
        const requestDurationCount = 1; // one trace = one request
        const dashboardCols = await this.getAggDailyDashboardColumns();
        const dashboardKnownCols = [
            'day',
            'applicationId',
            'workspaceId',
            'projectId',
            'environment',
            'version',
            'totalTraces_state',
            'totalSessions_state',
            'totalTokens_state',
            'totalRequestDuration_state',
            'requestDurationCount_state',
            'passedRequests_state',
            'failedRequests_state',
            'totalCost_state',
        ];
        const insertDashboardCols = dashboardKnownCols.filter((c) => dashboardCols.has(c));
        if (insertDashboardCols.length > 0) {
            const dashboardSelect = this.buildAggDailyDashboardRowSelectSql({
                day,
                applicationId,
                workspaceId,
                projectId,
                environment,
                version,
                totalTraces: 1,
                totalSessions,
                totalTokens: totalTokensUsed,
                totalRequestDuration: totalDurationMs,
                requestDurationCount,
                passedRequests,
                failedRequests,
                totalCost,
            }, insertDashboardCols);
            const insertSqlDashboard = `INSERT INTO agg_daily_dashboard (${insertDashboardCols.join(', ')}) ${dashboardSelect}`;
            this.logger?.debug?.(`[agg_daily_dashboard] inserting 1 row with ${insertDashboardCols.length} column(s)`);
            await clickhouseService_1.clickHouseService.command(insertSqlDashboard);
        }
        if (Array.isArray(spans) && spans.length > 0 && traceId) {
            try {
                await (0, aggregatedTraceDagInsert_1.insertAggregatedTraceDagData)({ spans, traceId, projectId, workspaceId, applicationId }, { clickHouseService: clickhouseService_1.clickHouseService, logger: this.logger });
            }
            catch (error) {
                this.logger.error(`Error inserting DAG data: ${error}`);
            }
        }
    }
}
exports.ObserveDashboardService = ObserveDashboardService;
/**
 * The set of columns this service knows how to populate in `agg_daily`.
 * We dynamically intersect this list with the live ClickHouse schema to avoid
 * hard failures when columns are added/removed.
 */
ObserveDashboardService.AGG_DAILY_KNOWN_COLUMNS = [
    'day',
    'applicationId',
    'workspaceId',
    'projectId',
    'environment',
    'version',
    'agent',
    'toolName',
    'model',
    'traces_state',
    'sessions_state',
    'cost_state',
    'costCount_state',
    'requestDuration_state',
    'requestDurationCount_state',
    'requestDuration_hist_state',
    // NOTE: prefer *_hist_sumMap_state when available; *_hist_state does not merge correctly in AggregatingMergeTree
    'requestDuration_hist_sumMap_state',
    'tokens_state',
    'tokensCount_state',
    'timeToFirstToken_state',
    'timeToFirstToken_quantiles_state',
    'timeToFirstTokenCount_state',
    'timeToFirstToken_hist_state',
    // NOTE: prefer *_hist_sumMap_state when available; *_hist_state does not merge correctly in AggregatingMergeTree
    'timeToFirstToken_hist_sumMap_state',
    'cacheReadTokens_state',
    'cacheWriteTokens_state',
    'cacheReadTokensCount_state',
    'cacheWriteTokensCount_state',
    'totalRuns_state',
    'totalCount_state',
    'totalTokensUsed_state',
    'totalTokensUsed_quantiles_state',
    'totalTokensCount_state',
    'totalToolRuns_state',
    'totalToolCount_state',
    'totalToolTokensUsed_state',
    'totalToolTokensUsed_quantiles_state',
    'totalToolTokensCount_state',
    'totalModelRuns_state',
    'totalModelCount_state',
    'totalModelTokensUsed_state',
    'totalModelTokensUsed_quantiles_state',
    'totalModelTokensCount_state',
    'metrics_state',
    'plugins_state',
    'requestDuration_quantiles_state',
    'vectordb_name',
    'vectordb_pass_count_state',
    'vectordb_fail_count_state',
    'vectordb_error_count_state',
    'vectordb_retrievalHit_count_state',
    'vectordb_retrievalTotal_count_state',
    'vectordb_retrievalHit_quantiles_state',
    'vectordb_retrievalLatency_state',
    'vectordb_retrievalLatencyCount_state',
    'vectordb_retrievalLatency_quantiles_state',
    'vectordb_retrievalLatency_hist_sumMap_state',
    'vectordb_queries_count_state',
    'vectordb_docsRetrieved_state',
    'vectordb_docsRetrievedCount_state',
    'vectordb_requestVolumeByOperation_sumMap_state',
    'vectordb_responseTimeByOperation_state',
    'vectordb_responseTimeByOp_sum_sumMap_state',
    'vectordb_responseTimeByOp_count_sumMap_state',
];
/**
 * Same as AGG_DAILY_KNOWN_COLUMNS, but for `agg_hourly` (time key = `hour` DateTime).
 */
ObserveDashboardService.AGG_HOURLY_KNOWN_COLUMNS = [
    'hour',
    'applicationId',
    'workspaceId',
    'projectId',
    'environment',
    'version',
    'agent',
    'toolName',
    'model',
    'traces_state',
    'sessions_state',
    'cost_state',
    'costCount_state',
    'requestDuration_state',
    'requestDurationCount_state',
    'requestDuration_hist_state',
    // NOTE: prefer *_hist_sumMap_state when available; *_hist_state does not merge correctly in AggregatingMergeTree
    'requestDuration_hist_sumMap_state',
    'tokens_state',
    'tokensCount_state',
    'timeToFirstToken_state',
    'timeToFirstToken_quantiles_state',
    'timeToFirstTokenCount_state',
    'timeToFirstToken_hist_state',
    // NOTE: prefer *_hist_sumMap_state when available; *_hist_state does not merge correctly in AggregatingMergeTree
    'timeToFirstToken_hist_sumMap_state',
    'cacheReadTokens_state',
    'cacheWriteTokens_state',
    'cacheReadTokensCount_state',
    'cacheWriteTokensCount_state',
    'totalRuns_state',
    'totalCount_state',
    'totalTokensUsed_state',
    'totalTokensUsed_quantiles_state',
    'totalTokensCount_state',
    'totalToolRuns_state',
    'totalToolCount_state',
    'totalToolTokensUsed_state',
    'totalToolTokensUsed_quantiles_state',
    'totalToolTokensCount_state',
    'totalModelRuns_state',
    'totalModelCount_state',
    'totalModelTokensUsed_state',
    'totalModelTokensUsed_quantiles_state',
    'totalModelTokensCount_state',
    'metrics_state',
    'plugins_state',
    'requestDuration_quantiles_state',
    'vectordb_name',
    'vectordb_pass_count_state',
    'vectordb_fail_count_state',
    'vectordb_error_count_state',
    'vectordb_retrievalHit_count_state',
    'vectordb_retrievalTotal_count_state',
    'vectordb_retrievalHit_quantiles_state',
    'vectordb_retrievalLatency_state',
    'vectordb_retrievalLatencyCount_state',
    'vectordb_retrievalLatency_quantiles_state',
    'vectordb_retrievalLatency_hist_sumMap_state',
    'vectordb_queries_count_state',
    'vectordb_docsRetrieved_state',
    'vectordb_docsRetrievedCount_state',
    'vectordb_requestVolumeByOperation_sumMap_state',
    'vectordb_responseTimeByOperation_state',
    'vectordb_responseTimeByOp_sum_sumMap_state',
    'vectordb_responseTimeByOp_count_sumMap_state',
];
// Export a singleton instance
exports.observeDashboardService = new ObserveDashboardService();
//# sourceMappingURL=observeDashboard.js.map