fix(bd-zci): map snake_case DB rows to camelCase in getSessionWorkerSummaries

- getSessionWorkerSummaries now maps SQLite snake_case columns to the
  TypeScript camelCase return type, fixing the type mismatch
- Fix getSessionsInRange typo (was getSingsInRange)
- Fix flaky duration test using deterministic timestamps
- Update tests to use camelCase property names

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-04-21 17:28:13 -04:00
parent a6fb5c9289
commit f661430fce
2 changed files with 29 additions and 15 deletions

View file

@ -94,11 +94,13 @@ describe('HistoricalStore', () => {
});
it('should record task completion', () => {
const startedAt = Date.now() - 60000;
const endedAt = startedAt + 60000;
const taskId = store.recordTask({
workerId: 'worker-1',
taskType: 'bead',
startedAt: Date.now() - 60000,
endedAt: Date.now(),
startedAt,
endedAt,
cost: 0.05,
tokensIn: 500,
tokensOut: 200,
@ -511,14 +513,14 @@ describe('HistoricalStore', () => {
const summaries = store.getSessionWorkerSummaries({ sessionId: 'otlp-metric-sess' });
expect(summaries).toHaveLength(1);
const summary = summaries[0];
expect(summary.worker_id).toBe('needle-alpha');
expect(summary.tokens_in).toBe(1500);
expect(summary.tokens_out).toBe(600);
expect(summary.cost_usd).toBeCloseTo(0.084);
expect(summary.beads_completed).toBe(2);
expect(summary.beads_failed).toBe(1);
expect(summary.workerId).toBe('needle-alpha');
expect(summary.tokensIn).toBe(1500);
expect(summary.tokensOut).toBe(600);
expect(summary.costUsd).toBeCloseTo(0.084);
expect(summary.beadsCompleted).toBe(2);
expect(summary.beadsFailed).toBe(1);
expect(summary.errors).toBe(1);
expect(summary.metrics_source).toBe('otlp-metric');
expect(summary.metricsSource).toBe('otlp-metric');
// Verify getAggregatedAnalytics prefers metric-sourced data
store.endSession({
@ -695,9 +697,9 @@ describe('HistoricalStore', () => {
const summaries = store.getSessionWorkerSummaries({ sessionId: 'no-cost-sess' });
expect(summaries).toHaveLength(1);
expect(summaries[0].metrics_source).toBe('otlp-metric');
expect(summaries[0].tokens_in).toBe(800);
expect(summaries[0].cost_usd).toBe(0);
expect(summaries[0].metricsSource).toBe('otlp-metric');
expect(summaries[0].tokensIn).toBe(800);
expect(summaries[0].costUsd).toBe(0);
store.endSession({ workerCount: 1, taskCount: 0, totalCost: 0, totalTokens: 800, metricsSource: 'otlp-metric' });
});

View file

@ -586,7 +586,7 @@ export class HistoricalStore {
query += ' ORDER BY updated_at DESC';
return this.db.prepare(query).all(...params) as Array<{
const rows = this.db.prepare(query).all(...params) as Array<{
id: number;
session_id: string;
worker_id: string;
@ -599,6 +599,18 @@ export class HistoricalStore {
metrics_source: string;
updated_at: number;
}>;
return rows.map(row => ({
sessionId: row.session_id,
workerId: row.worker_id,
tokensIn: row.tokens_in,
tokensOut: row.tokens_out,
costUsd: row.cost_usd,
beadsCompleted: row.beads_completed,
beadsFailed: row.beads_failed,
errors: row.errors,
metricsSource: row.metrics_source,
}));
}
/**
@ -1001,7 +1013,7 @@ export class HistoricalStore {
const { startTime = 0, endTime = Date.now() } = options;
// Get sessions in range
const sessions = this.getSingsInRange(startTime, endTime);
const sessions = this.getSessionsInRange(startTime, endTime);
const sessionIds = sessions.map(s => s.id);
// Fetch metric-sourced worker summaries for these sessions
@ -1210,7 +1222,7 @@ export class HistoricalStore {
/**
* Helper to get sessions in a time range
*/
private getSingsInRange(startTime: number, endTime: number): SessionRecord[] {
private getSessionsInRange(startTime: number, endTime: number): SessionRecord[] {
return this.db.prepare(`
SELECT * FROM sessions
WHERE started_at >= ? AND ended_at <= ?