Files
awesome-copilot/plugins/chronicle-stats/com.github.copilot/extensions/chronicle-stats/analytics.mjs
T

239 lines
14 KiB
JavaScript

import { calendarDate, shiftDate } from "./time.mjs";
export const COUNTERS = ["aiu", "tokens", "input", "output", "cacheRead", "cacheWrite", "reasoning"];
const FIELDS = {
aiu: "total_nano_aiu", input: "input_tokens", output: "output_tokens",
cacheRead: "cache_read_tokens", cacheWrite: "cache_write_tokens", reasoning: "reasoning_tokens",
};
const iso = value => value == null ? null
: new Date(Math.round((value - 2440587.5) * 86400000)).toISOString();
function metric(value, recordedCalls, completeCalls, totalCalls) {
return { value: value == null ? null : Number(value), recordedCalls, completeCalls,
totalCalls, complete: totalCalls > 0 && completeCalls === totalCalls };
}
function decode(row) {
const requests = Number(row.requests);
const metrics = Object.fromEntries(COUNTERS.map(key => [key, metric(
row[key], Number(row[`${key}Recorded`]), Number(row[`${key}Complete`]), requests,
)]));
metrics.requests = metric(requests, requests, requests, requests);
return {
requests, activeDays: Number(row.activeDays), metrics,
aiu: metrics.aiu.value, tokens: metrics.tokens.value,
tokenComposition: Object.fromEntries(COUNTERS.slice(2).map(key => [key, metrics[key].value])),
avgTtftMs: row.ttft == null ? null : Math.round(row.ttft),
avgDurationMs: row.duration == null ? null : Math.round(row.duration),
first: iso(row.first), last: iso(row.last),
};
}
export function readUsageWindow(db, start, end, now, { details = false, startAt, endAt } = {}) {
const columns = new Set(db.prepare("PRAGMA table_info(assistant_usage_events)")
.all().map(row => row.name));
if (!columns.has("created_at")) {
const metrics = Object.fromEntries([...COUNTERS, "requests"].map(key =>
[key, metric(key === "requests" ? 0 : null, 0, 0, 0)]));
return {
requests: 0, activeDays: 0, aiu: null, tokens: null, metrics, tokenComposition: {},
models: [], effort: [], initiator: [], series: [], sessionGroups: [],
avgTtftMs: null, avgDurationMs: null, first: null, last: null,
coverage: { status: "unavailable", first: null, last: null,
recordingSince: null, missingFields: ["assistant_usage_events.created_at"],
message: "Detailed usage is not recorded in this store. Activity history is still available." },
attribution: { supported: false, matchedCalls: 0, unattributedCalls: 0 },
};
}
const field = name => columns.has(name) ? `u.${name}` : "NULL";
const input = field("input_tokens"), output = field("output_tokens");
const anyTokens = `(${input} IS NOT NULL OR ${output} IS NOT NULL)`;
const bothTokens = `(${input} IS NOT NULL AND ${output} IS NOT NULL)`;
const select = ["COUNT(*) requests", "COUNT(DISTINCT calendar_date(u.created_at)) activeDays",
"MIN(julianday(u.created_at)) first",
"MAX(julianday(u.created_at)) last"];
for (const key of COUNTERS) {
const expr = key === "tokens"
? `CASE WHEN ${anyTokens} THEN COALESCE(${input},0)+COALESCE(${output},0) END`
: field(FIELDS[key]);
const recorded = key === "tokens" ? anyTokens : `${expr} IS NOT NULL`;
const complete = key === "tokens" ? bothTokens : recorded;
select.push(`SUM(${expr})${key === "aiu" ? "/1e9" : ""} ${key}`,
`COUNT(CASE WHEN ${recorded} THEN 1 END) ${key}Recorded`,
`COUNT(CASE WHEN ${complete} THEN 1 END) ${key}Complete`);
}
select.push(`AVG(${field("time_to_first_token_ms")}) ttft`,
`AVG(${field("duration_ms")}) duration`);
const metricSql = select.join(", ");
let predicate = "calendar_date(u.created_at) >= ? AND calendar_date(u.created_at) < ? AND julianday(u.created_at) <= julianday(?)";
const args = [start, end, now.toISOString()];
if (startAt) { predicate += " AND julianday(u.created_at) >= julianday(?)"; args.push(startAt); }
if (endAt) { predicate += " AND julianday(u.created_at) < julianday(?)"; args.push(endAt); }
const from = `FROM assistant_usage_events u WHERE ${predicate}`;
const modelExpr = `COALESCE(NULLIF(${field("model")},''), 'unknown')`;
const row = db.prepare(`SELECT ${metricSql} ${from}`).get(...args);
const summary = decode(row);
const models = db.prepare(`SELECT ${modelExpr} modelKey, ${metricSql} ${from}
GROUP BY modelKey ORDER BY aiu DESC`).all(...args).map(value => ({
model: value.modelKey, reqs: Number(value.requests), ...decode(value),
}));
const distribution = name => columns.has(name) ? db.prepare(`SELECT
COALESCE(NULLIF(u.${name},''), 'unknown') label, COUNT(*) count ${from}
GROUP BY label ORDER BY count DESC`).all(...args) : [];
const global = db.prepare(`SELECT MIN(julianday(created_at)) first,
MAX(julianday(created_at)) last FROM assistant_usage_events
WHERE julianday(created_at) <= julianday(?)`).get(now.toISOString());
const invalid = db.prepare(`SELECT COUNT(*) count FROM assistant_usage_events
WHERE julianday(created_at) IS NULL`).get();
const coverage = {
status: summary.requests ? "available" : "empty", first: summary.first, last: summary.last,
recordingSince: iso(global.first), latestRecorded: iso(global.last),
invalidTimestampCalls: Number(invalid.count),
missingFields: [...Object.values(FIELDS), "model", "session_id",
"reasoning_effort", "initiator", "time_to_first_token_ms", "duration_ms"]
.filter(name => !columns.has(name)),
message: "Local recorded calls only, not account-wide billing. Gaps may mean inactivity or missing recording.",
};
const result = { ...summary, models, effort: distribution("reasoning_effort"),
initiator: distribution("initiator"), coverage };
if (!details) return result;
const daily = db.prepare(`SELECT calendar_date(u.created_at) date, ${metricSql} ${from}
GROUP BY date ORDER BY date`).all(...args).map(value => ({ date: value.date, ...decode(value) }));
const modelDays = db.prepare(`SELECT ${modelExpr} modelKey,
calendar_date(u.created_at) date, ${metricSql} ${from}
GROUP BY modelKey, date ORDER BY date`).all(...args)
.map(value => ({ model: value.modelKey, date: value.date, ...decode(value) }));
let sessionGroups = [];
if (columns.has("session_id")) {
sessionGroups = db.prepare(`SELECT ${modelExpr} modelKey,
s.id sessionKey, MIN(julianday(s.created_at)) started,
MAX(s.repository) repository, ${metricSql}
FROM assistant_usage_events u LEFT JOIN sessions s ON s.id=u.session_id
WHERE ${predicate} GROUP BY modelKey, sessionKey LIMIT 20001`).all(...args);
if (sessionGroups.length > 20000) {
throw Object.assign(new Error("This range is too large for session inspection. Choose a narrower range."),
{ code: "RANGE_TOO_LARGE", statusCode: 413 });
}
sessionGroups = sessionGroups.map(value => ({
model: value.modelKey, sessionId: value.sessionKey,
startedAt: iso(value.started), repository: value.repository || null,
beforeRange: value.started != null && calendarDate(iso(value.started)) < start,
...decode(value),
}));
}
const matchedCalls = sessionGroups.filter(value => value.sessionId != null)
.reduce((sum, value) => sum + value.requests, 0);
return { ...result, daily, modelDays, sessionGroups,
attribution: { supported: columns.has("session_id"), matchedCalls,
unattributedCalls: summary.requests - matchedCalls } };
}
export function combineMetrics(rows) {
const totalCalls = rows.reduce((sum, row) => sum + row.requests, 0);
const metrics = Object.fromEntries(COUNTERS.map(key => {
const values = rows.map(row => row.metrics[key]);
const known = values.filter(value => value.value != null);
return [key, metric(known.length ? known.reduce((sum, value) => sum + value.value, 0) : null,
values.reduce((sum, value) => sum + value.recordedCalls, 0),
values.reduce((sum, value) => sum + value.completeCalls, 0), totalCalls)];
}));
metrics.requests = metric(totalCalls, totalCalls, totalCalls, totalCalls);
return metrics;
}
export function makeSeries(daily, start, end) {
if (!daily.length) return { points: [], granularity: "day" };
const first = start === "0000-01-01" ? daily[0].date : start;
const dayCount = Math.round((Date.parse(`${end}T12:00:00Z`) -
Date.parse(`${first}T12:00:00Z`)) / 86400000);
const granularity = dayCount <= 120 ? "day" : dayCount <= 1825 ? "week" : "month";
const bucket = date => {
if (granularity === "month") return `${date.slice(0, 7)}-01`;
if (granularity === "week") {
return shiftDate(date, -((new Date(`${date}T12:00:00Z`).getUTCDay() + 6) % 7));
}
return date;
};
const groups = new Map();
for (const row of daily) {
const key = bucket(row.date);
if (!groups.has(key)) groups.set(key, []);
groups.get(key).push(row);
}
const points = [];
for (let date = first; date < end;) {
const key = bucket(date);
const metrics = combineMetrics(groups.get(key) || []);
points.push({ date: key, metrics, aiu: metrics.aiu.value,
tokens: metrics.tokens.value, requests: metrics.requests.value });
if (granularity === "month") {
const next = new Date(`${key}T12:00:00Z`);
next.setUTCMonth(next.getUTCMonth() + 1);
date = next.toISOString().slice(0, 10);
} else {
date = shiftDate(key, granularity === "week" ? 7 : 1);
}
}
return { points, granularity };
}
export const PULSE_RULES = Object.freeze({
minimumCalls: 20, minimumActiveDays: 3, minimumAiuBaseline: 1,
minimumChangePct: 20, concentrationShare: 0.7,
});
export function buildPulse(stats, current, previous, comparisonWindow) {
const evidence = [
`${stats.range === "all" ? "All recorded history" : `${stats.range} calendar days`}; snapshot as of ${stats.generatedAt}.`,
`${stats.kpis.requests} recorded model calls. These are not premium requests or a productivity score.`,
stats.usageCoverage.message,
];
if (stats.usageCoverage.recordingSince) {
evidence.push(`Earliest retained usage: ${stats.usageCoverage.recordingSince}. This is not proof of uninterrupted coverage.`);
}
const ready = value => value.requests >= PULSE_RULES.minimumCalls &&
value.activeDays >= PULSE_RULES.minimumActiveDays;
if (comparisonWindow) {
evidence.push(`Comparison excludes partial today: ${comparisonWindow.start} to ${shiftDate(comparisonWindow.end, -1)} versus ${comparisonWindow.previousStart} to ${shiftDate(comparisonWindow.start, -1)}.`);
evidence.push(`Samples: ${current.requests} calls on ${current.activeDays} active recording days versus ${previous.requests} calls on ${previous.activeDays} days. Gates: 20 calls and 3 active days in each period.`);
const retainedBaseline = stats.usageCoverage.recordingSince &&
calendarDate(stats.usageCoverage.recordingSince) <= comparisonWindow.previousStart;
if (!retainedBaseline) evidence.push("Retained usage begins after the previous period starts; a partial baseline cannot support a comparison.");
if (retainedBaseline && ready(current) && ready(previous)) {
const costReady = current.metrics.aiu.complete && previous.metrics.aiu.complete &&
previous.aiu >= PULSE_RULES.minimumAiuBaseline;
const key = costReady ? "aiu" : "requests";
const before = previous[key], after = current[key];
const change = before ? (after - before) / before * 100 : null;
if (change != null && Math.abs(change) >= PULSE_RULES.minimumChangePct - Number.EPSILON * 100) {
const label = key === "aiu" ? "Recorded AIU" : "Recorded model calls";
return { kind: "change",
headline: `${label} ${change > 0 ? "rose" : "fell"} ${Math.abs(change).toFixed(0)}% across comparable days`,
summary: `${current.requests} calls versus ${previous.requests} in the preceding period. This describes recorded consumption, not efficiency or quality.`,
comparison: { key, current: after, previous: before, change, window: comparisonWindow },
evidence: [...evidence, `${label}: ${after} versus ${before}. Percentage gate: at least 20% change; AIU baseline must be at least 1 with complete counters.`] };
}
}
} else {
evidence.push("All-time has no previous-period comparison.");
}
const ranked = stats.models.filter(row => row.aiu != null).slice().sort((a, b) => b.aiu - a.aiu);
if (stats.kpis.requests >= PULSE_RULES.minimumCalls && stats.metrics.aiu.complete &&
ranked.length >= 2 && stats.kpis.aiu > 0 &&
ranked[0].aiu / stats.kpis.aiu >= PULSE_RULES.concentrationShare) {
const share = ranked[0].aiu / stats.kpis.aiu;
return { kind: "concentration", headline: `${ranked[0].model} accounts for ${(share * 100).toFixed(0)}% of recorded AIU`,
driver: { model: ranked[0].model, value: ranked[0].aiu, total: stats.kpis.aiu, share },
summary: "Explore its contributing sessions before deciding whether to change anything. Model mix alone does not establish value or waste.",
evidence: [...evidence, `${ranked[0].aiu} of ${stats.kpis.aiu} AIU. Gates: at least 2 models, 20 calls, complete AIU, and 70% contribution.`] };
}
if (!stats.hasUsage) {
return { kind: "unavailable", headline: "Your activity is here; detailed usage needs a baseline",
summary: "Browse recorded activity or use local Copilot sessions, then refresh. Missing token counters are not zero usage.",
evidence };
}
return { kind: "baseline", headline: "Your recorded usage is taking shape",
summary: `${stats.kpis.requests} model calls in this view. There is not yet a qualifying comparison or multi-model concentration signal.`,
evidence: [...evidence, "No claim passed the conservative evidence gates. Missing counters and sparse or partial periods cannot establish a trend."] };
}