diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 663d384..cba237a 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -221,6 +221,7 @@ model User { knowledgeEntryVersions KnowledgeEntryVersion[] @relation("EntryVersionAuthor") llmProviders LlmProviderInstance[] @relation("UserLlmProviders") federatedIdentities FederatedIdentity[] + llmUsageLogs LlmUsageLog[] @relation("UserLlmUsageLogs") @@map("users") } @@ -272,6 +273,7 @@ model Workspace { federationConnections FederationConnection[] federationMessages FederationMessage[] federationEventSubscriptions FederationEventSubscription[] + llmUsageLogs LlmUsageLog[] @@index([ownerId]) @@map("workspaces") @@ -1036,6 +1038,7 @@ model LlmProviderInstance { user User? @relation("UserLlmProviders", fields: [userId], references: [id], onDelete: Cascade) personalities Personality[] @relation("PersonalityLlmProvider") workspaceLlmSettings WorkspaceLlmSettings[] @relation("WorkspaceLlmProvider") + llmUsageLogs LlmUsageLog[] @relation("LlmUsageLogs") @@index([userId]) @@index([providerType]) @@ -1383,3 +1386,53 @@ model FederationEventSubscription { @@index([workspaceId, isActive]) @@map("federation_event_subscriptions") } + +// ============================================ +// LLM USAGE TRACKING MODULE +// ============================================ + +model LlmUsageLog { + id String @id @default(uuid()) @db.Uuid + workspaceId String @map("workspace_id") @db.Uuid + userId String @map("user_id") @db.Uuid + + // LLM provider and model info + provider String @db.VarChar(50) + model String @db.VarChar(100) + providerInstanceId String? @map("provider_instance_id") @db.Uuid + + // Token usage + promptTokens Int @default(0) @map("prompt_tokens") + completionTokens Int @default(0) @map("completion_tokens") + totalTokens Int @default(0) @map("total_tokens") + + // Optional cost (in cents for precision) + costCents Float? @map("cost_cents") + + // Task type for routing analytics + taskType String? @map("task_type") @db.VarChar(50) + + // Optional reference to conversation/session + conversationId String? @map("conversation_id") @db.Uuid + + // Duration in milliseconds + durationMs Int? @map("duration_ms") + + // Timestamp + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz + + // Relations + workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade) + user User @relation("UserLlmUsageLogs", fields: [userId], references: [id], onDelete: Cascade) + llmProviderInstance LlmProviderInstance? @relation("LlmUsageLogs", fields: [providerInstanceId], references: [id], onDelete: SetNull) + + @@index([workspaceId]) + @@index([workspaceId, createdAt]) + @@index([userId]) + @@index([provider]) + @@index([model]) + @@index([providerInstanceId]) + @@index([taskType]) + @@index([conversationId]) + @@map("llm_usage_logs") +} diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index 285471a..efa050a 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -22,6 +22,7 @@ import { KnowledgeModule } from "./knowledge/knowledge.module"; import { UsersModule } from "./users/users.module"; import { WebSocketModule } from "./websocket/websocket.module"; import { LlmModule } from "./llm/llm.module"; +import { LlmUsageModule } from "./llm-usage/llm-usage.module"; import { BrainModule } from "./brain/brain.module"; import { CronModule } from "./cron/cron.module"; import { AgentTasksModule } from "./agent-tasks/agent-tasks.module"; @@ -80,6 +81,7 @@ import { FederationModule } from "./federation/federation.module"; UsersModule, WebSocketModule, LlmModule, + LlmUsageModule, BrainModule, CronModule, AgentTasksModule, diff --git a/apps/api/src/llm-usage/dto/index.ts b/apps/api/src/llm-usage/dto/index.ts new file mode 100644 index 0000000..aa13edc --- /dev/null +++ b/apps/api/src/llm-usage/dto/index.ts @@ -0,0 +1,2 @@ +export * from "./track-usage.dto"; +export * from "./usage-analytics.dto"; diff --git a/apps/api/src/llm-usage/dto/track-usage.dto.ts b/apps/api/src/llm-usage/dto/track-usage.dto.ts new file mode 100644 index 0000000..d23d7f2 --- /dev/null +++ b/apps/api/src/llm-usage/dto/track-usage.dto.ts @@ -0,0 +1,49 @@ +import { IsString, IsInt, IsOptional, IsNumber, Min, IsUUID } from "class-validator"; + +export class TrackUsageDto { + @IsUUID() + workspaceId!: string; + + @IsUUID() + userId!: string; + + @IsString() + provider!: string; + + @IsString() + model!: string; + + @IsOptional() + @IsUUID() + providerInstanceId?: string; + + @IsInt() + @Min(0) + promptTokens!: number; + + @IsInt() + @Min(0) + completionTokens!: number; + + @IsInt() + @Min(0) + totalTokens!: number; + + @IsOptional() + @IsNumber() + @Min(0) + costCents?: number; + + @IsOptional() + @IsString() + taskType?: string; + + @IsOptional() + @IsUUID() + conversationId?: string; + + @IsOptional() + @IsInt() + @Min(0) + durationMs?: number; +} diff --git a/apps/api/src/llm-usage/dto/usage-analytics.dto.ts b/apps/api/src/llm-usage/dto/usage-analytics.dto.ts new file mode 100644 index 0000000..9e878e8 --- /dev/null +++ b/apps/api/src/llm-usage/dto/usage-analytics.dto.ts @@ -0,0 +1,69 @@ +import { IsOptional, IsDateString, IsUUID, IsString } from "class-validator"; + +export class UsageAnalyticsQueryDto { + @IsOptional() + @IsUUID() + workspaceId?: string; + + @IsOptional() + @IsString() + provider?: string; + + @IsOptional() + @IsString() + model?: string; + + @IsOptional() + @IsUUID() + userId?: string; + + @IsOptional() + @IsDateString() + startDate?: string; + + @IsOptional() + @IsDateString() + endDate?: string; +} + +export interface UsageAnalyticsResponseDto { + totalCalls: number; + totalPromptTokens: number; + totalCompletionTokens: number; + totalTokens: number; + totalCostCents: number; + averageDurationMs: number; + byProvider: ProviderUsageDto[]; + byModel: ModelUsageDto[]; + byTaskType: TaskTypeUsageDto[]; +} + +export interface ProviderUsageDto { + provider: string; + calls: number; + promptTokens: number; + completionTokens: number; + totalTokens: number; + costCents: number; + averageDurationMs: number; +} + +export interface ModelUsageDto { + model: string; + calls: number; + promptTokens: number; + completionTokens: number; + totalTokens: number; + costCents: number; + averageDurationMs: number; +} + +export interface TaskTypeUsageDto { + taskType: string; + calls: number; + promptTokens: number; + completionTokens: number; + totalTokens: number; + costCents: number; + averageDurationMs: number; +} diff --git a/apps/api/src/llm-usage/llm-usage.controller.spec.ts b/apps/api/src/llm-usage/llm-usage.controller.spec.ts new file mode 100644 index 0000000..d928003 --- /dev/null +++ b/apps/api/src/llm-usage/llm-usage.controller.spec.ts @@ -0,0 +1,210 @@ +import { describe, it, expect, beforeEach, vi } from "vitest"; +import { Test, TestingModule } from "@nestjs/testing"; +import { LlmUsageController } from "./llm-usage.controller"; +import { LlmUsageService } from "./llm-usage.service"; +import type { UsageAnalyticsQueryDto } from "./dto"; + +describe("LlmUsageController", () => { + let controller: LlmUsageController; + let service: LlmUsageService; + + const mockLlmUsageService = { + getUsageAnalytics: vi.fn(), + getUsageByWorkspace: vi.fn(), + getUsageByProvider: vi.fn(), + getUsageByModel: vi.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [LlmUsageController], + providers: [ + { + provide: LlmUsageService, + useValue: mockLlmUsageService, + }, + ], + }).compile(); + + controller = module.get(LlmUsageController); + service = module.get(LlmUsageService); + + vi.clearAllMocks(); + }); + + it("should be defined", () => { + expect(controller).toBeDefined(); + }); + + describe("getAnalytics", () => { + it("should return usage analytics", async () => { + const query: UsageAnalyticsQueryDto = { + workspaceId: "workspace-123", + }; + + const expectedAnalytics = { + totalCalls: 10, + totalPromptTokens: 1000, + totalCompletionTokens: 500, + totalTokens: 1500, + totalCostCents: 1.5, + averageDurationMs: 1200, + byProvider: [ + { + provider: "ollama", + calls: 10, + promptTokens: 1000, + completionTokens: 500, + totalTokens: 1500, + costCents: 1.5, + averageDurationMs: 1200, + }, + ], + byModel: [ + { + model: "llama3.2", + calls: 10, + promptTokens: 1000, + completionTokens: 500, + totalTokens: 1500, + costCents: 1.5, + averageDurationMs: 1200, + }, + ], + byTaskType: [ + { + taskType: "chat", + calls: 10, + promptTokens: 1000, + completionTokens: 500, + totalTokens: 1500, + costCents: 1.5, + averageDurationMs: 1200, + }, + ], + }; + + mockLlmUsageService.getUsageAnalytics.mockResolvedValue(expectedAnalytics); + + const result = await controller.getAnalytics(query); + + expect(result).toEqual({ + data: expectedAnalytics, + }); + expect(service.getUsageAnalytics).toHaveBeenCalledWith(query); + }); + + it("should pass all query parameters to service", async () => { + const query: UsageAnalyticsQueryDto = { + workspaceId: "workspace-123", + provider: "ollama", + model: "llama3.2", + userId: "user-456", + startDate: "2024-01-01T00:00:00Z", + endDate: "2024-01-31T23:59:59Z", + }; + + mockLlmUsageService.getUsageAnalytics.mockResolvedValue({ + totalCalls: 0, + totalPromptTokens: 0, + totalCompletionTokens: 0, + totalTokens: 0, + totalCostCents: 0, + averageDurationMs: 0, + byProvider: [], + byModel: [], + byTaskType: [], + }); + + await controller.getAnalytics(query); + + expect(service.getUsageAnalytics).toHaveBeenCalledWith(query); + }); + }); + + describe("getUsageByWorkspace", () => { + it("should return usage logs for a workspace", async () => { + const workspaceId = "workspace-123"; + const expectedLogs = [ + { + id: "log-1", + workspaceId, + userId: "user-1", + provider: "ollama", + model: "llama3.2", + promptTokens: 100, + completionTokens: 50, + totalTokens: 150, + createdAt: new Date(), + }, + ]; + + mockLlmUsageService.getUsageByWorkspace.mockResolvedValue(expectedLogs); + + const result = await controller.getUsageByWorkspace(workspaceId); + + expect(result).toEqual({ + data: expectedLogs, + }); + expect(service.getUsageByWorkspace).toHaveBeenCalledWith(workspaceId); + }); + }); + + describe("getUsageByProvider", () => { + it("should return usage logs for a provider", async () => { + const workspaceId = "workspace-123"; + const provider = "ollama"; + const expectedLogs = [ + { + id: "log-1", + workspaceId, + userId: "user-1", + provider, + model: "llama3.2", + promptTokens: 100, + completionTokens: 50, + totalTokens: 150, + createdAt: new Date(), + }, + ]; + + mockLlmUsageService.getUsageByProvider.mockResolvedValue(expectedLogs); + + const result = await controller.getUsageByProvider(workspaceId, provider); + + expect(result).toEqual({ + data: expectedLogs, + }); + expect(service.getUsageByProvider).toHaveBeenCalledWith(workspaceId, provider); + }); + }); + + describe("getUsageByModel", () => { + it("should return usage logs for a model", async () => { + const workspaceId = "workspace-123"; + const model = "llama3.2"; + const expectedLogs = [ + { + id: "log-1", + workspaceId, + userId: "user-1", + provider: "ollama", + model, + promptTokens: 100, + completionTokens: 50, + totalTokens: 150, + createdAt: new Date(), + }, + ]; + + mockLlmUsageService.getUsageByModel.mockResolvedValue(expectedLogs); + + const result = await controller.getUsageByModel(workspaceId, model); + + expect(result).toEqual({ + data: expectedLogs, + }); + expect(service.getUsageByModel).toHaveBeenCalledWith(workspaceId, model); + }); + }); +}); diff --git a/apps/api/src/llm-usage/llm-usage.controller.ts b/apps/api/src/llm-usage/llm-usage.controller.ts new file mode 100644 index 0000000..5c58e96 --- /dev/null +++ b/apps/api/src/llm-usage/llm-usage.controller.ts @@ -0,0 +1,68 @@ +import { Controller, Get, Param, Query } from "@nestjs/common"; +import { LlmUsageService } from "./llm-usage.service"; +import type { UsageAnalyticsQueryDto } from "./dto"; + +/** + * LLM Usage Controller + * + * Provides endpoints for querying LLM usage analytics and logs. + * All endpoints return data in the standard API response format. + */ +@Controller("llm-usage") +export class LlmUsageController { + constructor(private readonly llmUsageService: LlmUsageService) {} + + /** + * Get aggregated usage analytics. + * Supports filtering by workspace, provider, model, user, and date range. + * + * @param query - Analytics query parameters + * @returns Aggregated usage analytics + */ + @Get("analytics") + async getAnalytics(@Query() query: UsageAnalyticsQueryDto) { + const data = await this.llmUsageService.getUsageAnalytics(query); + return { data }; + } + + /** + * Get all usage logs for a specific workspace. + * + * @param workspaceId - Workspace UUID + * @returns Array of usage logs + */ + @Get("by-workspace/:workspaceId") + async getUsageByWorkspace(@Param("workspaceId") workspaceId: string) { + const data = await this.llmUsageService.getUsageByWorkspace(workspaceId); + return { data }; + } + + /** + * Get usage logs for a specific provider within a workspace. + * + * @param workspaceId - Workspace UUID + * @param provider - Provider name + * @returns Array of usage logs + */ + @Get("by-workspace/:workspaceId/provider/:provider") + async getUsageByProvider( + @Param("workspaceId") workspaceId: string, + @Param("provider") provider: string + ) { + const data = await this.llmUsageService.getUsageByProvider(workspaceId, provider); + return { data }; + } + + /** + * Get usage logs for a specific model within a workspace. + * + * @param workspaceId - Workspace UUID + * @param model - Model name + * @returns Array of usage logs + */ + @Get("by-workspace/:workspaceId/model/:model") + async getUsageByModel(@Param("workspaceId") workspaceId: string, @Param("model") model: string) { + const data = await this.llmUsageService.getUsageByModel(workspaceId, model); + return { data }; + } +} diff --git a/apps/api/src/llm-usage/llm-usage.module.ts b/apps/api/src/llm-usage/llm-usage.module.ts new file mode 100644 index 0000000..e713910 --- /dev/null +++ b/apps/api/src/llm-usage/llm-usage.module.ts @@ -0,0 +1,12 @@ +import { Module } from "@nestjs/common"; +import { LlmUsageService } from "./llm-usage.service"; +import { LlmUsageController } from "./llm-usage.controller"; +import { PrismaModule } from "../prisma/prisma.module"; + +@Module({ + imports: [PrismaModule], + controllers: [LlmUsageController], + providers: [LlmUsageService], + exports: [LlmUsageService], +}) +export class LlmUsageModule {} diff --git a/apps/api/src/llm-usage/llm-usage.service.spec.ts b/apps/api/src/llm-usage/llm-usage.service.spec.ts new file mode 100644 index 0000000..475200f --- /dev/null +++ b/apps/api/src/llm-usage/llm-usage.service.spec.ts @@ -0,0 +1,374 @@ +import { describe, it, expect, beforeEach, vi } from "vitest"; +import { Test, TestingModule } from "@nestjs/testing"; +import { LlmUsageService } from "./llm-usage.service"; +import { PrismaService } from "../prisma/prisma.service"; +import { TrackUsageDto, UsageAnalyticsQueryDto } from "./dto"; + +describe("LlmUsageService", () => { + let service: LlmUsageService; + let prisma: PrismaService; + + const mockPrismaService = { + llmUsageLog: { + create: vi.fn(), + findMany: vi.fn(), + groupBy: vi.fn(), + aggregate: vi.fn(), + }, + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + LlmUsageService, + { + provide: PrismaService, + useValue: mockPrismaService, + }, + ], + }).compile(); + + service = module.get(LlmUsageService); + prisma = module.get(PrismaService); + + vi.clearAllMocks(); + }); + + it("should be defined", () => { + expect(service).toBeDefined(); + }); + + describe("trackUsage", () => { + it("should create a new usage log entry", async () => { + const trackUsageDto: TrackUsageDto = { + workspaceId: "workspace-123", + userId: "user-456", + provider: "ollama", + model: "llama3.2", + promptTokens: 100, + completionTokens: 50, + totalTokens: 150, + costCents: 0.15, + taskType: "chat", + durationMs: 1500, + }; + + const expectedResult = { + id: "usage-789", + ...trackUsageDto, + createdAt: new Date(), + }; + + mockPrismaService.llmUsageLog.create.mockResolvedValue(expectedResult); + + const result = await service.trackUsage(trackUsageDto); + + expect(result).toEqual(expectedResult); + expect(mockPrismaService.llmUsageLog.create).toHaveBeenCalledWith({ + data: trackUsageDto, + }); + }); + + it("should handle optional fields correctly", async () => { + const trackUsageDto: TrackUsageDto = { + workspaceId: "workspace-123", + userId: "user-456", + provider: "ollama", + model: "llama3.2", + promptTokens: 100, + completionTokens: 50, + totalTokens: 150, + }; + + const expectedResult = { + id: "usage-789", + ...trackUsageDto, + costCents: null, + taskType: null, + durationMs: null, + providerInstanceId: null, + conversationId: null, + createdAt: new Date(), + }; + + mockPrismaService.llmUsageLog.create.mockResolvedValue(expectedResult); + + const result = await service.trackUsage(trackUsageDto); + + expect(result).toEqual(expectedResult); + }); + + it("should throw error when database operation fails", async () => { + const trackUsageDto: TrackUsageDto = { + workspaceId: "workspace-123", + userId: "user-456", + provider: "ollama", + model: "llama3.2", + promptTokens: 100, + completionTokens: 50, + totalTokens: 150, + }; + + mockPrismaService.llmUsageLog.create.mockRejectedValue(new Error("Database error")); + + await expect(service.trackUsage(trackUsageDto)).rejects.toThrow("Database error"); + }); + }); + + describe("getUsageAnalytics", () => { + it("should return aggregated usage analytics", async () => { + const query: UsageAnalyticsQueryDto = { + workspaceId: "workspace-123", + }; + + const mockUsageLogs = [ + { + provider: "ollama", + model: "llama3.2", + taskType: "chat", + promptTokens: 100, + completionTokens: 50, + totalTokens: 150, + costCents: 0.15, + durationMs: 1500, + }, + { + provider: "ollama", + model: "llama3.2", + taskType: "embed", + promptTokens: 200, + completionTokens: 0, + totalTokens: 200, + costCents: 0.1, + durationMs: 500, + }, + ]; + + mockPrismaService.llmUsageLog.findMany.mockResolvedValue(mockUsageLogs); + + const result = await service.getUsageAnalytics(query); + + expect(result.totalCalls).toBe(2); + expect(result.totalPromptTokens).toBe(300); + expect(result.totalCompletionTokens).toBe(50); + expect(result.totalTokens).toBe(350); + expect(result.totalCostCents).toBe(0.25); + expect(result.averageDurationMs).toBe(1000); + expect(result.byProvider).toHaveLength(1); + expect(result.byModel).toHaveLength(1); + expect(result.byTaskType).toHaveLength(2); + }); + + it("should filter by date range", async () => { + const query: UsageAnalyticsQueryDto = { + workspaceId: "workspace-123", + startDate: "2024-01-01T00:00:00Z", + endDate: "2024-01-31T23:59:59Z", + }; + + mockPrismaService.llmUsageLog.findMany.mockResolvedValue([]); + + await service.getUsageAnalytics(query); + + expect(mockPrismaService.llmUsageLog.findMany).toHaveBeenCalledWith({ + where: { + workspaceId: "workspace-123", + createdAt: { + gte: new Date("2024-01-01T00:00:00Z"), + lte: new Date("2024-01-31T23:59:59Z"), + }, + }, + }); + }); + + it("should filter by provider", async () => { + const query: UsageAnalyticsQueryDto = { + workspaceId: "workspace-123", + provider: "ollama", + }; + + mockPrismaService.llmUsageLog.findMany.mockResolvedValue([]); + + await service.getUsageAnalytics(query); + + expect(mockPrismaService.llmUsageLog.findMany).toHaveBeenCalledWith({ + where: { + workspaceId: "workspace-123", + provider: "ollama", + }, + }); + }); + + it("should filter by model", async () => { + const query: UsageAnalyticsQueryDto = { + workspaceId: "workspace-123", + model: "llama3.2", + }; + + mockPrismaService.llmUsageLog.findMany.mockResolvedValue([]); + + await service.getUsageAnalytics(query); + + expect(mockPrismaService.llmUsageLog.findMany).toHaveBeenCalledWith({ + where: { + workspaceId: "workspace-123", + model: "llama3.2", + }, + }); + }); + + it("should filter by userId", async () => { + const query: UsageAnalyticsQueryDto = { + workspaceId: "workspace-123", + userId: "user-456", + }; + + mockPrismaService.llmUsageLog.findMany.mockResolvedValue([]); + + await service.getUsageAnalytics(query); + + expect(mockPrismaService.llmUsageLog.findMany).toHaveBeenCalledWith({ + where: { + workspaceId: "workspace-123", + userId: "user-456", + }, + }); + }); + + it("should handle empty results", async () => { + const query: UsageAnalyticsQueryDto = { + workspaceId: "workspace-123", + }; + + mockPrismaService.llmUsageLog.findMany.mockResolvedValue([]); + + const result = await service.getUsageAnalytics(query); + + expect(result.totalCalls).toBe(0); + expect(result.totalPromptTokens).toBe(0); + expect(result.totalCompletionTokens).toBe(0); + expect(result.totalTokens).toBe(0); + expect(result.totalCostCents).toBe(0); + expect(result.averageDurationMs).toBe(0); + expect(result.byProvider).toEqual([]); + expect(result.byModel).toEqual([]); + expect(result.byTaskType).toEqual([]); + }); + + it("should handle null values in aggregation", async () => { + const query: UsageAnalyticsQueryDto = { + workspaceId: "workspace-123", + }; + + const mockUsageLogs = [ + { + provider: "ollama", + model: "llama3.2", + taskType: null, + promptTokens: 100, + completionTokens: 50, + totalTokens: 150, + costCents: null, + durationMs: null, + }, + ]; + + mockPrismaService.llmUsageLog.findMany.mockResolvedValue(mockUsageLogs); + + const result = await service.getUsageAnalytics(query); + + expect(result.totalCalls).toBe(1); + expect(result.totalCostCents).toBe(0); + expect(result.averageDurationMs).toBe(0); + }); + }); + + describe("getUsageByWorkspace", () => { + it("should return usage logs for a specific workspace", async () => { + const workspaceId = "workspace-123"; + const mockLogs = [ + { + id: "log-1", + workspaceId, + userId: "user-1", + provider: "ollama", + model: "llama3.2", + promptTokens: 100, + completionTokens: 50, + totalTokens: 150, + createdAt: new Date(), + }, + ]; + + mockPrismaService.llmUsageLog.findMany.mockResolvedValue(mockLogs); + + const result = await service.getUsageByWorkspace(workspaceId); + + expect(result).toEqual(mockLogs); + expect(mockPrismaService.llmUsageLog.findMany).toHaveBeenCalledWith({ + where: { workspaceId }, + orderBy: { createdAt: "desc" }, + }); + }); + }); + + describe("getUsageByProvider", () => { + it("should return usage logs for a specific provider", async () => { + const workspaceId = "workspace-123"; + const provider = "ollama"; + const mockLogs = [ + { + id: "log-1", + workspaceId, + userId: "user-1", + provider, + model: "llama3.2", + promptTokens: 100, + completionTokens: 50, + totalTokens: 150, + createdAt: new Date(), + }, + ]; + + mockPrismaService.llmUsageLog.findMany.mockResolvedValue(mockLogs); + + const result = await service.getUsageByProvider(workspaceId, provider); + + expect(result).toEqual(mockLogs); + expect(mockPrismaService.llmUsageLog.findMany).toHaveBeenCalledWith({ + where: { workspaceId, provider }, + orderBy: { createdAt: "desc" }, + }); + }); + }); + + describe("getUsageByModel", () => { + it("should return usage logs for a specific model", async () => { + const workspaceId = "workspace-123"; + const model = "llama3.2"; + const mockLogs = [ + { + id: "log-1", + workspaceId, + userId: "user-1", + provider: "ollama", + model, + promptTokens: 100, + completionTokens: 50, + totalTokens: 150, + createdAt: new Date(), + }, + ]; + + mockPrismaService.llmUsageLog.findMany.mockResolvedValue(mockLogs); + + const result = await service.getUsageByModel(workspaceId, model); + + expect(result).toEqual(mockLogs); + expect(mockPrismaService.llmUsageLog.findMany).toHaveBeenCalledWith({ + where: { workspaceId, model }, + orderBy: { createdAt: "desc" }, + }); + }); + }); +}); diff --git a/apps/api/src/llm-usage/llm-usage.service.ts b/apps/api/src/llm-usage/llm-usage.service.ts new file mode 100644 index 0000000..e6d1e93 --- /dev/null +++ b/apps/api/src/llm-usage/llm-usage.service.ts @@ -0,0 +1,224 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { PrismaService } from "../prisma/prisma.service"; +import type { + TrackUsageDto, + UsageAnalyticsQueryDto, + UsageAnalyticsResponseDto, + ProviderUsageDto, + ModelUsageDto, + TaskTypeUsageDto, +} from "./dto"; + +/** + * LLM Usage Service + * + * Tracks and analyzes LLM usage across workspaces, providers, and models. + * Provides analytics for cost tracking, token usage, and performance metrics. + */ +@Injectable() +export class LlmUsageService { + private readonly logger = new Logger(LlmUsageService.name); + + constructor(private readonly prisma: PrismaService) {} + + /** + * Track a single LLM usage event. + * Records token counts, cost, duration, and metadata. + * + * @param dto - Usage tracking data + * @returns The created usage log entry + */ + async trackUsage(dto: TrackUsageDto) { + this.logger.debug( + `Tracking usage: ${dto.provider}/${dto.model} - ${String(dto.totalTokens)} tokens` + ); + + return this.prisma.llmUsageLog.create({ + data: dto, + }); + } + + /** + * Get aggregated usage analytics based on query filters. + * Supports filtering by workspace, provider, model, user, and date range. + * + * @param query - Analytics query filters + * @returns Aggregated usage analytics + */ + async getUsageAnalytics(query: UsageAnalyticsQueryDto): Promise { + const where: Record = {}; + + if (query.workspaceId) { + where.workspaceId = query.workspaceId; + } + if (query.provider) { + where.provider = query.provider; + } + if (query.model) { + where.model = query.model; + } + if (query.userId) { + where.userId = query.userId; + } + if (query.startDate || query.endDate) { + where.createdAt = {}; + if (query.startDate) { + (where.createdAt as Record).gte = new Date(query.startDate); + } + if (query.endDate) { + (where.createdAt as Record).lte = new Date(query.endDate); + } + } + + const usageLogs = await this.prisma.llmUsageLog.findMany({ where }); + + // Aggregate totals + const totalCalls = usageLogs.length; + const totalPromptTokens = usageLogs.reduce((sum, log) => sum + log.promptTokens, 0); + const totalCompletionTokens = usageLogs.reduce((sum, log) => sum + log.completionTokens, 0); + const totalTokens = usageLogs.reduce((sum, log) => sum + log.totalTokens, 0); + const totalCostCents = usageLogs.reduce((sum, log) => sum + (log.costCents ?? 0), 0); + + const durations = usageLogs.map((log) => log.durationMs).filter((d): d is number => d !== null); + const averageDurationMs = + durations.length > 0 ? durations.reduce((sum, d) => sum + d, 0) / durations.length : 0; + + // Group by provider + const byProviderMap = new Map(); + for (const log of usageLogs) { + const existing = byProviderMap.get(log.provider); + if (existing) { + existing.calls += 1; + existing.promptTokens += log.promptTokens; + existing.completionTokens += log.completionTokens; + existing.totalTokens += log.totalTokens; + existing.costCents += log.costCents ?? 0; + if (log.durationMs) { + const count = existing.calls === 1 ? 1 : existing.calls - 1; + existing.averageDurationMs = + (existing.averageDurationMs * (count - 1) + log.durationMs) / count; + } + } else { + byProviderMap.set(log.provider, { + provider: log.provider, + calls: 1, + promptTokens: log.promptTokens, + completionTokens: log.completionTokens, + totalTokens: log.totalTokens, + costCents: log.costCents ?? 0, + averageDurationMs: log.durationMs ?? 0, + }); + } + } + + // Group by model + const byModelMap = new Map(); + for (const log of usageLogs) { + const existing = byModelMap.get(log.model); + if (existing) { + existing.calls += 1; + existing.promptTokens += log.promptTokens; + existing.completionTokens += log.completionTokens; + existing.totalTokens += log.totalTokens; + existing.costCents += log.costCents ?? 0; + if (log.durationMs) { + const count = existing.calls === 1 ? 1 : existing.calls - 1; + existing.averageDurationMs = + (existing.averageDurationMs * (count - 1) + log.durationMs) / count; + } + } else { + byModelMap.set(log.model, { + model: log.model, + calls: 1, + promptTokens: log.promptTokens, + completionTokens: log.completionTokens, + totalTokens: log.totalTokens, + costCents: log.costCents ?? 0, + averageDurationMs: log.durationMs ?? 0, + }); + } + } + + // Group by task type + const byTaskTypeMap = new Map(); + for (const log of usageLogs) { + const taskType = log.taskType ?? "unknown"; + const existing = byTaskTypeMap.get(taskType); + if (existing) { + existing.calls += 1; + existing.promptTokens += log.promptTokens; + existing.completionTokens += log.completionTokens; + existing.totalTokens += log.totalTokens; + existing.costCents += log.costCents ?? 0; + if (log.durationMs) { + const count = existing.calls === 1 ? 1 : existing.calls - 1; + existing.averageDurationMs = + (existing.averageDurationMs * (count - 1) + log.durationMs) / count; + } + } else { + byTaskTypeMap.set(taskType, { + taskType, + calls: 1, + promptTokens: log.promptTokens, + completionTokens: log.completionTokens, + totalTokens: log.totalTokens, + costCents: log.costCents ?? 0, + averageDurationMs: log.durationMs ?? 0, + }); + } + } + + return { + totalCalls, + totalPromptTokens, + totalCompletionTokens, + totalTokens, + totalCostCents, + averageDurationMs, + byProvider: Array.from(byProviderMap.values()), + byModel: Array.from(byModelMap.values()), + byTaskType: Array.from(byTaskTypeMap.values()), + }; + } + + /** + * Get all usage logs for a specific workspace. + * + * @param workspaceId - Workspace UUID + * @returns Array of usage logs + */ + async getUsageByWorkspace(workspaceId: string) { + return this.prisma.llmUsageLog.findMany({ + where: { workspaceId }, + orderBy: { createdAt: "desc" }, + }); + } + + /** + * Get usage logs for a specific provider within a workspace. + * + * @param workspaceId - Workspace UUID + * @param provider - Provider name + * @returns Array of usage logs + */ + async getUsageByProvider(workspaceId: string, provider: string) { + return this.prisma.llmUsageLog.findMany({ + where: { workspaceId, provider }, + orderBy: { createdAt: "desc" }, + }); + } + + /** + * Get usage logs for a specific model within a workspace. + * + * @param workspaceId - Workspace UUID + * @param model - Model name + * @returns Array of usage logs + */ + async getUsageByModel(workspaceId: string, model: string) { + return this.prisma.llmUsageLog.findMany({ + where: { workspaceId, model }, + orderBy: { createdAt: "desc" }, + }); + } +} diff --git a/apps/api/src/llm/llm.module.ts b/apps/api/src/llm/llm.module.ts index 57640b3..3d2fc4a 100644 --- a/apps/api/src/llm/llm.module.ts +++ b/apps/api/src/llm/llm.module.ts @@ -4,9 +4,10 @@ import { LlmProviderAdminController } from "./llm-provider-admin.controller"; import { LlmService } from "./llm.service"; import { LlmManagerService } from "./llm-manager.service"; import { PrismaModule } from "../prisma/prisma.module"; +import { LlmUsageModule } from "../llm-usage/llm-usage.module"; @Module({ - imports: [PrismaModule], + imports: [PrismaModule, LlmUsageModule], controllers: [LlmController, LlmProviderAdminController], providers: [LlmService, LlmManagerService], exports: [LlmService, LlmManagerService], diff --git a/apps/coordinator/coverage.json b/apps/coordinator/coverage.json index 004e9ab..173406c 100644 --- a/apps/coordinator/coverage.json +++ b/apps/coordinator/coverage.json @@ -1,8 +1,8 @@ { "meta": { "format": 3, - "version": "7.13.2", - "timestamp": "2026-02-01T18:23:40.086042", + "version": "7.13.3", + "timestamp": "2026-02-04T11:35:08.447905", "branch_coverage": false, "show_contexts": false }, @@ -247,15 +247,15 @@ "executed_lines": [], "summary": { "covered_lines": 0, - "num_statements": 13, + "num_statements": 15, "percent_covered": 0.0, "percent_covered_display": "0", - "missing_lines": 13, + "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, - "missing_lines": [3, 6, 9, 18, 24, 25, 28, 31, 32, 33, 36, 38, 42], + "missing_lines": [3, 6, 9, 17, 18, 21, 24, 25, 28, 31, 32, 33, 36, 38, 42], "excluded_lines": [], "functions": { "get_settings": { @@ -278,15 +278,15 @@ "executed_lines": [], "summary": { "covered_lines": 0, - "num_statements": 12, + "num_statements": 14, "percent_covered": 0.0, "percent_covered_display": "0", - "missing_lines": 12, + "missing_lines": 14, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, - "missing_lines": [3, 6, 9, 18, 24, 25, 28, 31, 32, 33, 36, 42], + "missing_lines": [3, 6, 9, 17, 18, 21, 24, 25, 28, 31, 32, 33, 36, 42], "excluded_lines": [], "start_line": 1 } @@ -312,120 +312,41 @@ "executed_lines": [], "summary": { "covered_lines": 0, - "num_statements": 13, + "num_statements": 15, "percent_covered": 0.0, "percent_covered_display": "0", - "missing_lines": 13, + "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, - "missing_lines": [3, 6, 9, 18, 24, 25, 28, 31, 32, 33, 36, 38, 42], + "missing_lines": [3, 6, 9, 17, 18, 21, 24, 25, 28, 31, 32, 33, 36, 38, 42], "excluded_lines": [], "start_line": 1 } } }, - "src/context_monitor.py": { + "src/context_compaction.py": { "executed_lines": [], "summary": { "covered_lines": 0, - "num_statements": 50, + "num_statements": 62, "percent_covered": 0.0, "percent_covered_display": "0", - "missing_lines": 50, + "missing_lines": 62, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, "missing_lines": [ - 3, 4, 5, 6, 7, 9, 11, 14, 23, 24, 26, 33, 34, 35, 36, 38, 50, 51, 58, 59, 61, 63, 72, 74, - 75, 78, 79, 80, 83, 85, 86, 88, 97, 99, 111, 112, 116, 117, 118, 119, 120, 121, 125, 126, - 127, 128, 130, 132, 138, 139 + 11, 12, 13, 15, 18, 19, 34, 35, 36, 37, 38, 39, 40, 41, 42, 44, 46, 47, 54, 55, 69, 70, 71, + 72, 73, 74, 75, 76, 78, 80, 81, 89, 97, 107, 113, 115, 127, 129, 130, 132, 133, 135, 144, + 146, 148, 149, 150, 151, 153, 159, 162, 165, 166, 167, 168, 171, 172, 176, 182, 193, 194, + 195 ], "excluded_lines": [], "functions": { - "ContextMonitor.__init__": { - "executed_lines": [], - "summary": { - "covered_lines": 0, - "num_statements": 4, - "percent_covered": 0.0, - "percent_covered_display": "0", - "missing_lines": 4, - "excluded_lines": 0, - "percent_statements_covered": 0.0, - "percent_statements_covered_display": "0" - }, - "missing_lines": [33, 34, 35, 36], - "excluded_lines": [], - "start_line": 26 - }, - "ContextMonitor.get_context_usage": { - "executed_lines": [], - "summary": { - "covered_lines": 0, - "num_statements": 5, - "percent_covered": 0.0, - "percent_covered_display": "0", - "missing_lines": 5, - "excluded_lines": 0, - "percent_statements_covered": 0.0, - "percent_statements_covered_display": "0" - }, - "missing_lines": [50, 51, 58, 59, 61], - "excluded_lines": [], - "start_line": 38 - }, - "ContextMonitor.determine_action": { - "executed_lines": [], - "summary": { - "covered_lines": 0, - "num_statements": 9, - "percent_covered": 0.0, - "percent_covered_display": "0", - "missing_lines": 9, - "excluded_lines": 0, - "percent_statements_covered": 0.0, - "percent_statements_covered_display": "0" - }, - "missing_lines": [72, 74, 75, 78, 79, 80, 83, 85, 86], - "excluded_lines": [], - "start_line": 63 - }, - "ContextMonitor.get_usage_history": { - "executed_lines": [], - "summary": { - "covered_lines": 0, - "num_statements": 1, - "percent_covered": 0.0, - "percent_covered_display": "0", - "missing_lines": 1, - "excluded_lines": 0, - "percent_statements_covered": 0.0, - "percent_statements_covered_display": "0" - }, - "missing_lines": [97], - "excluded_lines": [], - "start_line": 88 - }, - "ContextMonitor.start_monitoring": { - "executed_lines": [], - "summary": { - "covered_lines": 0, - "num_statements": 13, - "percent_covered": 0.0, - "percent_covered_display": "0", - "missing_lines": 13, - "excluded_lines": 0, - "percent_statements_covered": 0.0, - "percent_statements_covered_display": "0" - }, - "missing_lines": [111, 112, 116, 117, 118, 119, 120, 121, 125, 126, 127, 128, 130], - "excluded_lines": [], - "start_line": 99 - }, - "ContextMonitor.stop_monitoring": { + "CompactionResult.__repr__": { "executed_lines": [], "summary": { "covered_lines": 0, @@ -437,85 +358,191 @@ "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, - "missing_lines": [138, 139], + "missing_lines": [46, 47], "excluded_lines": [], - "start_line": 132 + "start_line": 44 }, - "": { + "SessionRotation.__repr__": { "executed_lines": [], "summary": { "covered_lines": 0, - "num_statements": 16, + "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", - "missing_lines": 16, + "missing_lines": 2, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, - "missing_lines": [3, 4, 5, 6, 7, 9, 11, 14, 23, 24, 26, 38, 63, 88, 99, 132], + "missing_lines": [80, 81], "excluded_lines": [], - "start_line": 1 - } - }, - "classes": { - "ContextMonitor": { + "start_line": 78 + }, + "ContextCompactor.__init__": { "executed_lines": [], "summary": { "covered_lines": 0, - "num_statements": 34, + "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", - "missing_lines": 34, + "missing_lines": 1, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [113], + "excluded_lines": [], + "start_line": 107 + }, + "ContextCompactor.request_summary": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 5, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 5, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [127, 129, 130, 132, 133], + "excluded_lines": [], + "start_line": 115 + }, + "ContextCompactor.compact": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 20, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 20, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, "missing_lines": [ - 33, 34, 35, 36, 50, 51, 58, 59, 61, 72, 74, 75, 78, 79, 80, 83, 85, 86, 97, 111, 112, - 116, 117, 118, 119, 120, 121, 125, 126, 127, 128, 130, 138, 139 + 144, 146, 148, 149, 150, 151, 153, 159, 162, 165, 166, 167, 168, 171, 172, 176, 182, + 193, 194, 195 ], "excluded_lines": [], - "start_line": 14 + "start_line": 135 }, "": { "executed_lines": [], "summary": { "covered_lines": 0, - "num_statements": 16, + "num_statements": 32, "percent_covered": 0.0, "percent_covered_display": "0", - "missing_lines": 16, + "missing_lines": 32, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, - "missing_lines": [3, 4, 5, 6, 7, 9, 11, 14, 23, 24, 26, 38, 63, 88, 99, 132], + "missing_lines": [ + 11, 12, 13, 15, 18, 19, 34, 35, 36, 37, 38, 39, 40, 41, 42, 44, 54, 55, 69, 70, 71, 72, + 73, 74, 75, 76, 78, 89, 97, 107, 115, 135 + ], + "excluded_lines": [], + "start_line": 1 + } + }, + "classes": { + "CompactionResult": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 2, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 2, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [46, 47], + "excluded_lines": [], + "start_line": 19 + }, + "SessionRotation": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 2, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 2, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [80, 81], + "excluded_lines": [], + "start_line": 55 + }, + "ContextCompactor": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 26, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 26, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [ + 113, 127, 129, 130, 132, 133, 144, 146, 148, 149, 150, 151, 153, 159, 162, 165, 166, + 167, 168, 171, 172, 176, 182, 193, 194, 195 + ], + "excluded_lines": [], + "start_line": 89 + }, + "": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 32, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 32, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [ + 11, 12, 13, 15, 18, 19, 34, 35, 36, 37, 38, 39, 40, 41, 42, 44, 54, 55, 69, 70, 71, 72, + 73, 74, 75, 76, 78, 89, 97, 107, 115, 135 + ], "excluded_lines": [], "start_line": 1 } } }, - "src/coordinator.py": { + "src/context_monitor.py": { "executed_lines": [], "summary": { "covered_lines": 0, - "num_statements": 63, + "num_statements": 75, "percent_covered": 0.0, "percent_covered_display": "0", - "missing_lines": 63, + "missing_lines": 75, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, "missing_lines": [ - 3, 4, 5, 7, 9, 12, 23, 34, 35, 36, 37, 38, 40, 41, 47, 49, 50, 56, 58, 64, 66, 71, 72, 73, - 75, 76, 77, 78, 79, 80, 84, 85, 90, 91, 93, 96, 97, 99, 105, 106, 107, 108, 110, 120, 122, - 123, 124, 126, 133, 136, 137, 139, 141, 142, 144, 146, 147, 150, 152, 164, 171, 179, 181 + 3, 4, 5, 6, 7, 9, 10, 12, 15, 24, 25, 27, 34, 35, 36, 37, 38, 40, 52, 53, 60, 61, 63, 65, + 74, 76, 77, 80, 81, 82, 85, 87, 88, 90, 99, 101, 113, 114, 118, 119, 120, 121, 122, 123, + 127, 128, 129, 130, 132, 134, 140, 141, 143, 155, 156, 158, 159, 164, 166, 168, 193, 198, + 200, 201, 202, 204, 210, 211, 214, 218, 220, 225, 235, 236, 237 ], "excluded_lines": [], "functions": { - "Coordinator.__init__": { + "ContextMonitor.__init__": { "executed_lines": [], "summary": { "covered_lines": 0, @@ -529,7 +556,223 @@ }, "missing_lines": [34, 35, 36, 37, 38], "excluded_lines": [], - "start_line": 23 + "start_line": 27 + }, + "ContextMonitor.get_context_usage": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 5, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 5, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [52, 53, 60, 61, 63], + "excluded_lines": [], + "start_line": 40 + }, + "ContextMonitor.determine_action": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 9, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 9, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [74, 76, 77, 80, 81, 82, 85, 87, 88], + "excluded_lines": [], + "start_line": 65 + }, + "ContextMonitor.get_usage_history": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 1, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 1, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [99], + "excluded_lines": [], + "start_line": 90 + }, + "ContextMonitor.start_monitoring": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 13, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 13, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [113, 114, 118, 119, 120, 121, 122, 123, 127, 128, 129, 130, 132], + "excluded_lines": [], + "start_line": 101 + }, + "ContextMonitor.stop_monitoring": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 2, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 2, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [140, 141], + "excluded_lines": [], + "start_line": 134 + }, + "ContextMonitor.trigger_compaction": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 6, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 6, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [155, 156, 158, 159, 164, 166], + "excluded_lines": [], + "start_line": 143 + }, + "ContextMonitor.trigger_rotation": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 15, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 15, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [ + 193, 198, 200, 201, 202, 204, 210, 211, 214, 218, 220, 225, 235, 236, 237 + ], + "excluded_lines": [], + "start_line": 168 + }, + "": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 19, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 19, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [ + 3, 4, 5, 6, 7, 9, 10, 12, 15, 24, 25, 27, 40, 65, 90, 101, 134, 143, 168 + ], + "excluded_lines": [], + "start_line": 1 + } + }, + "classes": { + "ContextMonitor": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 56, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 56, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [ + 34, 35, 36, 37, 38, 52, 53, 60, 61, 63, 74, 76, 77, 80, 81, 82, 85, 87, 88, 99, 113, + 114, 118, 119, 120, 121, 122, 123, 127, 128, 129, 130, 132, 140, 141, 155, 156, 158, + 159, 164, 166, 193, 198, 200, 201, 202, 204, 210, 211, 214, 218, 220, 225, 235, 236, 237 + ], + "excluded_lines": [], + "start_line": 15 + }, + "": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 19, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 19, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [ + 3, 4, 5, 6, 7, 9, 10, 12, 15, 24, 25, 27, 40, 65, 90, 101, 134, 143, 168 + ], + "excluded_lines": [], + "start_line": 1 + } + } + }, + "src/coordinator.py": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 183, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 183, + "excluded_lines": 2, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [ + 3, 4, 5, 7, 8, 9, 10, 11, 16, 19, 30, 41, 42, 43, 44, 45, 47, 48, 54, 56, 57, 63, 65, 71, + 73, 78, 79, 80, 82, 83, 84, 85, 86, 87, 91, 92, 97, 98, 100, 103, 104, 106, 112, 113, 114, + 115, 117, 127, 129, 130, 131, 133, 140, 143, 144, 146, 148, 149, 151, 153, 154, 157, 159, + 171, 178, 186, 188, 191, 202, 219, 220, 221, 222, 223, 224, 225, 226, 229, 230, 231, 233, + 234, 240, 242, 243, 249, 251, 252, 258, 260, 261, 267, 269, 270, 276, 278, 284, 286, 291, + 292, 293, 295, 296, 297, 298, 299, 300, 304, 305, 310, 311, 313, 316, 317, 319, 325, 326, + 327, 328, 330, 344, 346, 347, 348, 350, 358, 359, 362, 363, 370, 372, 374, 375, 376, 379, + 382, 384, 386, 387, 388, 393, 394, 396, 397, 400, 402, 414, 421, 423, 425, 434, 435, 437, + 438, 439, 440, 442, 443, 444, 445, 447, 456, 458, 459, 461, 462, 464, 467, 471, 473, 474, + 476, 477, 488, 497, 499, 500, 501, 507, 508, 509, 511, 512 + ], + "excluded_lines": [13, 14], + "functions": { + "Coordinator.__init__": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 5, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 5, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [41, 42, 43, 44, 45], + "excluded_lines": [], + "start_line": 30 }, "Coordinator.is_running": { "executed_lines": [], @@ -543,9 +786,9 @@ "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, - "missing_lines": [47], + "missing_lines": [54], "excluded_lines": [], - "start_line": 41 + "start_line": 48 }, "Coordinator.active_agents": { "executed_lines": [], @@ -559,9 +802,9 @@ "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, - "missing_lines": [56], + "missing_lines": [63], "excluded_lines": [], - "start_line": 50 + "start_line": 57 }, "Coordinator.get_active_agent_count": { "executed_lines": [], @@ -575,9 +818,9 @@ "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, - "missing_lines": [64], + "missing_lines": [71], "excluded_lines": [], - "start_line": 58 + "start_line": 65 }, "Coordinator.start": { "executed_lines": [], @@ -591,9 +834,9 @@ "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, - "missing_lines": [71, 72, 73, 75, 76, 77, 78, 79, 80, 84, 85, 90, 91, 93, 96, 97], + "missing_lines": [78, 79, 80, 82, 83, 84, 85, 86, 87, 91, 92, 97, 98, 100, 103, 104], "excluded_lines": [], - "start_line": 66 + "start_line": 73 }, "Coordinator.stop": { "executed_lines": [], @@ -607,9 +850,9 @@ "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, - "missing_lines": [105, 106, 107, 108], + "missing_lines": [112, 113, 114, 115], "excluded_lines": [], - "start_line": 99 + "start_line": 106 }, "Coordinator.process_queue": { "executed_lines": [], @@ -624,10 +867,10 @@ "percent_statements_covered_display": "0" }, "missing_lines": [ - 120, 122, 123, 124, 126, 133, 136, 137, 139, 141, 142, 144, 146, 147, 150 + 127, 129, 130, 131, 133, 140, 143, 144, 146, 148, 149, 151, 153, 154, 157 ], "excluded_lines": [], - "start_line": 110 + "start_line": 117 }, "Coordinator.spawn_agent": { "executed_lines": [], @@ -641,11 +884,123 @@ "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, - "missing_lines": [164, 171, 179, 181], + "missing_lines": [171, 178, 186, 188], "excluded_lines": [], - "start_line": 152 + "start_line": 159 }, - "": { + "OrchestrationLoop.__init__": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 11, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 11, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [219, 220, 221, 222, 223, 224, 225, 226, 229, 230, 231], + "excluded_lines": [], + "start_line": 202 + }, + "OrchestrationLoop.is_running": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 1, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 1, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [240], + "excluded_lines": [], + "start_line": 234 + }, + "OrchestrationLoop.active_agents": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 1, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 1, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [249], + "excluded_lines": [], + "start_line": 243 + }, + "OrchestrationLoop.processed_count": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 1, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 1, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [258], + "excluded_lines": [], + "start_line": 252 + }, + "OrchestrationLoop.success_count": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 1, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 1, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [267], + "excluded_lines": [], + "start_line": 261 + }, + "OrchestrationLoop.rejection_count": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 1, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 1, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [276], + "excluded_lines": [], + "start_line": 270 + }, + "OrchestrationLoop.get_active_agent_count": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 1, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 1, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [284], + "excluded_lines": [], + "start_line": 278 + }, + "OrchestrationLoop.start": { "executed_lines": [], "summary": { "covered_lines": 0, @@ -657,8 +1012,128 @@ "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, - "missing_lines": [3, 4, 5, 7, 9, 12, 23, 40, 41, 49, 50, 58, 66, 99, 110, 152], + "missing_lines": [ + 291, 292, 293, 295, 296, 297, 298, 299, 300, 304, 305, 310, 311, 313, 316, 317 + ], "excluded_lines": [], + "start_line": 286 + }, + "OrchestrationLoop.stop": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 4, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 4, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [325, 326, 327, 328], + "excluded_lines": [], + "start_line": 319 + }, + "OrchestrationLoop.process_next_issue": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 25, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 25, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [ + 344, 346, 347, 348, 350, 358, 359, 362, 363, 370, 372, 374, 375, 376, 379, 382, 384, + 386, 387, 388, 393, 394, 396, 397, 400 + ], + "excluded_lines": [], + "start_line": 330 + }, + "OrchestrationLoop._spawn_agent": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 3, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 3, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [414, 421, 423], + "excluded_lines": [], + "start_line": 402 + }, + "OrchestrationLoop._check_context": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 10, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 10, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [434, 435, 437, 438, 439, 440, 442, 443, 444, 445], + "excluded_lines": [], + "start_line": 425 + }, + "OrchestrationLoop._verify_quality": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 12, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 12, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [456, 458, 459, 461, 462, 464, 467, 471, 473, 474, 476, 477], + "excluded_lines": [], + "start_line": 447 + }, + "OrchestrationLoop._handle_rejection": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 9, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 9, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [497, 499, 500, 501, 507, 508, 509, 511, 512], + "excluded_lines": [], + "start_line": 488 + }, + "": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 40, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 40, + "excluded_lines": 2, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [ + 3, 4, 5, 7, 8, 9, 10, 11, 16, 19, 30, 47, 48, 56, 57, 65, 73, 106, 117, 159, 191, 202, + 233, 234, 242, 243, 251, 252, 260, 261, 269, 270, 278, 286, 319, 330, 402, 425, 447, 488 + ], + "excluded_lines": [13, 14], "start_line": 1 } }, @@ -676,26 +1151,144 @@ "percent_statements_covered_display": "0" }, "missing_lines": [ - 34, 35, 36, 37, 38, 47, 56, 64, 71, 72, 73, 75, 76, 77, 78, 79, 80, 84, 85, 90, 91, 93, - 96, 97, 105, 106, 107, 108, 120, 122, 123, 124, 126, 133, 136, 137, 139, 141, 142, 144, - 146, 147, 150, 164, 171, 179, 181 + 41, 42, 43, 44, 45, 54, 63, 71, 78, 79, 80, 82, 83, 84, 85, 86, 87, 91, 92, 97, 98, 100, + 103, 104, 112, 113, 114, 115, 127, 129, 130, 131, 133, 140, 143, 144, 146, 148, 149, + 151, 153, 154, 157, 171, 178, 186, 188 ], "excluded_lines": [], - "start_line": 12 + "start_line": 19 + }, + "OrchestrationLoop": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 96, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 96, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [ + 219, 220, 221, 222, 223, 224, 225, 226, 229, 230, 231, 240, 249, 258, 267, 276, 284, + 291, 292, 293, 295, 296, 297, 298, 299, 300, 304, 305, 310, 311, 313, 316, 317, 325, + 326, 327, 328, 344, 346, 347, 348, 350, 358, 359, 362, 363, 370, 372, 374, 375, 376, + 379, 382, 384, 386, 387, 388, 393, 394, 396, 397, 400, 414, 421, 423, 434, 435, 437, + 438, 439, 440, 442, 443, 444, 445, 456, 458, 459, 461, 462, 464, 467, 471, 473, 474, + 476, 477, 497, 499, 500, 501, 507, 508, 509, 511, 512 + ], + "excluded_lines": [], + "start_line": 191 }, "": { "executed_lines": [], "summary": { "covered_lines": 0, - "num_statements": 16, + "num_statements": 40, "percent_covered": 0.0, "percent_covered_display": "0", - "missing_lines": 16, + "missing_lines": 40, + "excluded_lines": 2, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [ + 3, 4, 5, 7, 8, 9, 10, 11, 16, 19, 30, 47, 48, 56, 57, 65, 73, 106, 117, 159, 191, 202, + 233, 234, 242, 243, 251, 252, 260, 261, 269, 270, 278, 286, 319, 330, 402, 425, 447, 488 + ], + "excluded_lines": [13, 14], + "start_line": 1 + } + } + }, + "src/forced_continuation.py": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 39, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 39, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [ + 3, 6, 17, 29, 30, 36, 43, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 66, 67, 68, 69, 70, 71, + 72, 74, 77, 85, 86, 96, 97, 107, 108, 118, 119, 120, 121, 123, 135, 144 + ], + "excluded_lines": [], + "functions": { + "ForcedContinuationService.generate_prompt": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 36, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 36, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, - "missing_lines": [3, 4, 5, 7, 9, 12, 23, 40, 41, 49, 50, 58, 66, 99, 110, 152], + "missing_lines": [ + 29, 30, 36, 43, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 66, 67, 68, 69, 70, 71, 72, 74, + 77, 85, 86, 96, 97, 107, 108, 118, 119, 120, 121, 123, 135, 144 + ], + "excluded_lines": [], + "start_line": 17 + }, + "": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 3, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 3, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [3, 6, 17], + "excluded_lines": [], + "start_line": 1 + } + }, + "classes": { + "ForcedContinuationService": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 36, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 36, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [ + 29, 30, 36, 43, 51, 52, 53, 56, 57, 58, 60, 61, 62, 63, 66, 67, 68, 69, 70, 71, 72, 74, + 77, 85, 86, 96, 97, 107, 108, 118, 119, 120, 121, 123, 135, 144 + ], + "excluded_lines": [], + "start_line": 6 + }, + "": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 3, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 3, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [3, 6, 17], "excluded_lines": [], "start_line": 1 } @@ -753,10 +1346,10 @@ } }, "src/gates/build_gate.py": { - "executed_lines": [3, 4, 6, 9, 16, 22, 23, 30, 31, 41, 51, 52, 58, 59, 65, 66], + "executed_lines": [3, 5, 8, 15, 21, 22, 29, 30, 40, 50, 51, 57, 58, 64, 65], "summary": { - "covered_lines": 16, - "num_statements": 16, + "covered_lines": 15, + "num_statements": 15, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, @@ -768,7 +1361,7 @@ "excluded_lines": [], "functions": { "BuildGate.check": { - "executed_lines": [22, 23, 30, 31, 41, 51, 52, 58, 59, 65, 66], + "executed_lines": [21, 22, 29, 30, 40, 50, 51, 57, 58, 64, 65], "summary": { "covered_lines": 11, "num_statements": 11, @@ -781,13 +1374,13 @@ }, "missing_lines": [], "excluded_lines": [], - "start_line": 16 + "start_line": 15 }, "": { - "executed_lines": [3, 4, 6, 9, 16], + "executed_lines": [3, 5, 8, 15], "summary": { - "covered_lines": 5, - "num_statements": 5, + "covered_lines": 4, + "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, @@ -802,7 +1395,7 @@ }, "classes": { "BuildGate": { - "executed_lines": [22, 23, 30, 31, 41, 51, 52, 58, 59, 65, 66], + "executed_lines": [21, 22, 29, 30, 40, 50, 51, 57, 58, 64, 65], "summary": { "covered_lines": 11, "num_statements": 11, @@ -815,13 +1408,13 @@ }, "missing_lines": [], "excluded_lines": [], - "start_line": 9 + "start_line": 8 }, "": { - "executed_lines": [3, 4, 6, 9, 16], + "executed_lines": [3, 5, 8, 15], "summary": { - "covered_lines": 5, - "num_statements": 5, + "covered_lines": 4, + "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, @@ -837,24 +1430,25 @@ }, "src/gates/coverage_gate.py": { "executed_lines": [ - 3, 4, 5, 7, 10, 16, 18, 24, 26, 34, 35, 37, 39, 40, 52, 53, 65, 77, 78, 84, 85, 91, 92, 98, - 104, 105, 106, 112, 114, 125, 126, 127, 128, 129, 130, 131, 134 + 3, 4, 5, 7, 10, 16, 18, 24, 26, 41, 42, 44, 46, 47, 59, 60, 75, 90, 91, 97, 98, 104, 105, + 111, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 129, 140, 141, 142, 143, 144, + 145, 146, 147, 148, 149 ], "summary": { - "covered_lines": 37, - "num_statements": 44, - "percent_covered": 84.0909090909091, - "percent_covered_display": "84", - "missing_lines": 7, + "covered_lines": 46, + "num_statements": 46, + "percent_covered": 100.0, + "percent_covered_display": "100", + "missing_lines": 0, "excluded_lines": 0, - "percent_statements_covered": 84.0909090909091, - "percent_statements_covered_display": "84" + "percent_statements_covered": 100.0, + "percent_statements_covered_display": "100" }, - "missing_lines": [107, 108, 109, 110, 111, 132, 133], + "missing_lines": [], "excluded_lines": [], "functions": { "CoverageGate.check": { - "executed_lines": [24, 26, 34, 35, 37, 39, 40, 52, 53, 65, 77, 78, 84, 85, 91, 92], + "executed_lines": [24, 26, 41, 42, 44, 46, 47, 59, 60, 75, 90, 91, 97, 98, 104, 105], "summary": { "covered_lines": 16, "num_statements": 16, @@ -870,39 +1464,39 @@ "start_line": 18 }, "CoverageGate._extract_coverage_from_json": { - "executed_lines": [104, 105, 106, 112], + "executed_lines": [117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127], "summary": { - "covered_lines": 4, - "num_statements": 9, - "percent_covered": 44.44444444444444, - "percent_covered_display": "44", - "missing_lines": 5, + "covered_lines": 11, + "num_statements": 11, + "percent_covered": 100.0, + "percent_covered_display": "100", + "missing_lines": 0, "excluded_lines": 0, - "percent_statements_covered": 44.44444444444444, - "percent_statements_covered_display": "44" + "percent_statements_covered": 100.0, + "percent_statements_covered_display": "100" }, - "missing_lines": [107, 108, 109, 110, 111], + "missing_lines": [], "excluded_lines": [], - "start_line": 98 + "start_line": 111 }, "CoverageGate._extract_coverage_from_output": { - "executed_lines": [125, 126, 127, 128, 129, 130, 131, 134], + "executed_lines": [140, 141, 142, 143, 144, 145, 146, 147, 148, 149], "summary": { - "covered_lines": 8, + "covered_lines": 10, "num_statements": 10, - "percent_covered": 80.0, - "percent_covered_display": "80", - "missing_lines": 2, + "percent_covered": 100.0, + "percent_covered_display": "100", + "missing_lines": 0, "excluded_lines": 0, - "percent_statements_covered": 80.0, - "percent_statements_covered_display": "80" + "percent_statements_covered": 100.0, + "percent_statements_covered_display": "100" }, - "missing_lines": [132, 133], + "missing_lines": [], "excluded_lines": [], - "start_line": 114 + "start_line": 129 }, "": { - "executed_lines": [3, 4, 5, 7, 10, 16, 18, 98, 114], + "executed_lines": [3, 4, 5, 7, 10, 16, 18, 111, 129], "summary": { "covered_lines": 9, "num_statements": 9, @@ -921,25 +1515,25 @@ "classes": { "CoverageGate": { "executed_lines": [ - 24, 26, 34, 35, 37, 39, 40, 52, 53, 65, 77, 78, 84, 85, 91, 92, 104, 105, 106, 112, 125, - 126, 127, 128, 129, 130, 131, 134 + 24, 26, 41, 42, 44, 46, 47, 59, 60, 75, 90, 91, 97, 98, 104, 105, 117, 118, 119, 120, + 121, 122, 123, 124, 125, 126, 127, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149 ], "summary": { - "covered_lines": 28, - "num_statements": 35, - "percent_covered": 80.0, - "percent_covered_display": "80", - "missing_lines": 7, + "covered_lines": 37, + "num_statements": 37, + "percent_covered": 100.0, + "percent_covered_display": "100", + "missing_lines": 0, "excluded_lines": 0, - "percent_statements_covered": 80.0, - "percent_statements_covered_display": "80" + "percent_statements_covered": 100.0, + "percent_statements_covered_display": "100" }, - "missing_lines": [107, 108, 109, 110, 111, 132, 133], + "missing_lines": [], "excluded_lines": [], "start_line": 10 }, "": { - "executed_lines": [3, 4, 5, 7, 10, 16, 18, 98, 114], + "executed_lines": [3, 4, 5, 7, 10, 16, 18, 111, 129], "summary": { "covered_lines": 9, "num_statements": 9, @@ -1225,10 +1819,10 @@ "executed_lines": [], "summary": { "covered_lines": 0, - "num_statements": 65, + "num_statements": 67, "percent_covered": 0.0, "percent_covered_display": "0", - "missing_lines": 65, + "missing_lines": 67, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" @@ -1236,8 +1830,8 @@ "missing_lines": [ 3, 4, 5, 6, 7, 8, 10, 11, 13, 14, 15, 16, 20, 22, 23, 31, 32, 35, 36, 39, 45, 48, 49, 60, 61, 62, 63, 66, 67, 68, 71, 72, 76, 82, 83, 85, 87, 90, 93, 94, 95, 96, 97, 98, 99, 100, - 101, 102, 104, 108, 116, 121, 122, 125, 126, 132, 133, 135, 136, 137, 139, 148, 151, 152, - 154 + 101, 102, 104, 108, 116, 119, 120, 121, 122, 125, 126, 132, 133, 135, 136, 137, 139, 148, + 151, 152, 154 ], "excluded_lines": [], "functions": { @@ -1312,17 +1906,17 @@ "executed_lines": [], "summary": { "covered_lines": 0, - "num_statements": 30, + "num_statements": 32, "percent_covered": 0.0, "percent_covered_display": "0", - "missing_lines": 30, + "missing_lines": 32, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, "missing_lines": [ - 3, 4, 5, 6, 7, 8, 10, 11, 13, 14, 15, 16, 20, 31, 32, 35, 36, 39, 48, 49, 108, 116, 121, - 122, 125, 126, 148, 151, 152, 154 + 3, 4, 5, 6, 7, 8, 10, 11, 13, 14, 15, 16, 20, 31, 32, 35, 36, 39, 48, 49, 108, 116, 119, + 120, 121, 122, 125, 126, 148, 151, 152, 154 ], "excluded_lines": [], "start_line": 1 @@ -1349,10 +1943,10 @@ "executed_lines": [], "summary": { "covered_lines": 0, - "num_statements": 65, + "num_statements": 67, "percent_covered": 0.0, "percent_covered_display": "0", - "missing_lines": 65, + "missing_lines": 67, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" @@ -1360,8 +1954,151 @@ "missing_lines": [ 3, 4, 5, 6, 7, 8, 10, 11, 13, 14, 15, 16, 20, 22, 23, 31, 32, 35, 36, 39, 45, 48, 49, 60, 61, 62, 63, 66, 67, 68, 71, 72, 76, 82, 83, 85, 87, 90, 93, 94, 95, 96, 97, 98, 99, - 100, 101, 102, 104, 108, 116, 121, 122, 125, 126, 132, 133, 135, 136, 137, 139, 148, - 151, 152, 154 + 100, 101, 102, 104, 108, 116, 119, 120, 121, 122, 125, 126, 132, 133, 135, 136, 137, + 139, 148, 151, 152, 154 + ], + "excluded_lines": [], + "start_line": 1 + } + } + }, + "src/metrics.py": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 49, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 49, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [ + 12, 13, 15, 16, 19, 20, 35, 36, 37, 38, 39, 40, 41, 42, 43, 45, 51, 63, 69, 77, 83, 85, 114, + 115, 116, 118, 119, 121, 123, 126, 139, 140, 141, 144, 145, 148, 149, 150, 151, 152, 154, + 155, 156, 157, 158, 159, 160, 162, 166 + ], + "excluded_lines": [], + "functions": { + "SuccessMetrics.to_dict": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 1, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 1, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [51], + "excluded_lines": [], + "start_line": 45 + }, + "SuccessMetrics.validate_targets": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 1, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 1, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [69], + "excluded_lines": [], + "start_line": 63 + }, + "SuccessMetrics.format_report": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 9, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 9, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [83, 85, 114, 115, 116, 118, 119, 121, 123], + "excluded_lines": [], + "start_line": 77 + }, + "generate_metrics_from_orchestrator": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 19, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 19, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [ + 139, 140, 141, 144, 145, 148, 149, 150, 151, 152, 154, 155, 156, 157, 158, 159, 160, + 162, 166 + ], + "excluded_lines": [], + "start_line": 126 + }, + "": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 19, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 19, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [ + 12, 13, 15, 16, 19, 20, 35, 36, 37, 38, 39, 40, 41, 42, 43, 45, 63, 77, 126 + ], + "excluded_lines": [], + "start_line": 1 + } + }, + "classes": { + "SuccessMetrics": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 11, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 11, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [51, 69, 83, 85, 114, 115, 116, 118, 119, 121, 123], + "excluded_lines": [], + "start_line": 20 + }, + "": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 38, + "percent_covered": 0.0, + "percent_covered_display": "0", + "missing_lines": 38, + "excluded_lines": 0, + "percent_statements_covered": 0.0, + "percent_statements_covered_display": "0" + }, + "missing_lines": [ + 12, 13, 15, 16, 19, 20, 35, 36, 37, 38, 39, 40, 41, 42, 43, 45, 63, 77, 126, 139, 140, + 141, 144, 145, 148, 149, 150, 151, 152, 154, 155, 156, 157, 158, 159, 160, 162, 166 ], "excluded_lines": [], "start_line": 1 @@ -1795,24 +2532,162 @@ } } }, + "src/quality_orchestrator.py": { + "executed_lines": [ + 3, 4, 6, 8, 9, 10, 11, 12, 15, 23, 24, 29, 38, 55, 56, 57, 58, 60, 74, 75, 76, 77, 80, 89, + 90, 92, 93, 95, 100, 101, 111, 113, 115, 130, 131, 132, 135, 139, 140, 142, 143, 145, 153, + 161, 162, 163 + ], + "summary": { + "covered_lines": 46, + "num_statements": 53, + "percent_covered": 86.79245283018868, + "percent_covered_display": "87", + "missing_lines": 7, + "excluded_lines": 0, + "percent_statements_covered": 86.79245283018868, + "percent_statements_covered_display": "87" + }, + "missing_lines": [104, 136, 144, 155, 156, 158, 164], + "excluded_lines": [], + "functions": { + "QualityOrchestrator.__init__": { + "executed_lines": [55, 56, 57, 58], + "summary": { + "covered_lines": 4, + "num_statements": 4, + "percent_covered": 100.0, + "percent_covered_display": "100", + "missing_lines": 0, + "excluded_lines": 0, + "percent_statements_covered": 100.0, + "percent_statements_covered_display": "100" + }, + "missing_lines": [], + "excluded_lines": [], + "start_line": 38 + }, + "QualityOrchestrator.verify_completion": { + "executed_lines": [74, 75, 76, 77, 80, 89, 90, 92, 93, 95, 100, 101, 111, 113], + "summary": { + "covered_lines": 14, + "num_statements": 15, + "percent_covered": 93.33333333333333, + "percent_covered_display": "93", + "missing_lines": 1, + "excluded_lines": 0, + "percent_statements_covered": 93.33333333333333, + "percent_statements_covered_display": "93" + }, + "missing_lines": [104], + "excluded_lines": [], + "start_line": 60 + }, + "QualityOrchestrator._run_gate_async": { + "executed_lines": [130, 131, 132, 135, 139, 140, 142, 143, 145, 153, 161, 162, 163], + "summary": { + "covered_lines": 13, + "num_statements": 19, + "percent_covered": 68.42105263157895, + "percent_covered_display": "68", + "missing_lines": 6, + "excluded_lines": 0, + "percent_statements_covered": 68.42105263157895, + "percent_statements_covered_display": "68" + }, + "missing_lines": [136, 144, 155, 156, 158, 164], + "excluded_lines": [], + "start_line": 115 + }, + "": { + "executed_lines": [3, 4, 6, 8, 9, 10, 11, 12, 15, 23, 24, 29, 38, 60, 115], + "summary": { + "covered_lines": 15, + "num_statements": 15, + "percent_covered": 100.0, + "percent_covered_display": "100", + "missing_lines": 0, + "excluded_lines": 0, + "percent_statements_covered": 100.0, + "percent_statements_covered_display": "100" + }, + "missing_lines": [], + "excluded_lines": [], + "start_line": 1 + } + }, + "classes": { + "VerificationResult": { + "executed_lines": [], + "summary": { + "covered_lines": 0, + "num_statements": 0, + "percent_covered": 100.0, + "percent_covered_display": "100", + "missing_lines": 0, + "excluded_lines": 0, + "percent_statements_covered": 100.0, + "percent_statements_covered_display": "100" + }, + "missing_lines": [], + "excluded_lines": [], + "start_line": 15 + }, + "QualityOrchestrator": { + "executed_lines": [ + 55, 56, 57, 58, 74, 75, 76, 77, 80, 89, 90, 92, 93, 95, 100, 101, 111, 113, 130, 131, + 132, 135, 139, 140, 142, 143, 145, 153, 161, 162, 163 + ], + "summary": { + "covered_lines": 31, + "num_statements": 38, + "percent_covered": 81.57894736842105, + "percent_covered_display": "82", + "missing_lines": 7, + "excluded_lines": 0, + "percent_statements_covered": 81.57894736842105, + "percent_statements_covered_display": "82" + }, + "missing_lines": [104, 136, 144, 155, 156, 158, 164], + "excluded_lines": [], + "start_line": 29 + }, + "": { + "executed_lines": [3, 4, 6, 8, 9, 10, 11, 12, 15, 23, 24, 29, 38, 60, 115], + "summary": { + "covered_lines": 15, + "num_statements": 15, + "percent_covered": 100.0, + "percent_covered_display": "100", + "missing_lines": 0, + "excluded_lines": 0, + "percent_statements_covered": 100.0, + "percent_statements_covered_display": "100" + }, + "missing_lines": [], + "excluded_lines": [], + "start_line": 1 + } + } + }, "src/queue.py": { "executed_lines": [], "summary": { "covered_lines": 0, - "num_statements": 85, + "num_statements": 87, "percent_covered": 0.0, "percent_covered_display": "0", - "missing_lines": 85, + "missing_lines": 87, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, "missing_lines": [ - 3, 4, 5, 6, 7, 9, 12, 15, 16, 17, 20, 21, 26, 27, 29, 32, 34, 40, 47, 48, 57, 65, 68, 74, - 75, 76, 78, 85, 89, 90, 91, 93, 99, 100, 101, 102, 104, 110, 116, 119, 122, 123, 124, 127, - 128, 130, 136, 137, 138, 139, 141, 147, 148, 149, 151, 160, 162, 168, 170, 176, 178, 184, - 186, 192, 199, 201, 202, 205, 208, 210, 212, 214, 215, 217, 219, 220, 222, 223, 224, 226, - 227, 228, 231, 232, 234 + 3, 4, 5, 6, 7, 9, 12, 15, 16, 17, 20, 21, 24, 25, 26, 27, 29, 32, 34, 40, 47, 48, 57, 65, + 68, 74, 75, 76, 78, 85, 89, 90, 91, 93, 99, 100, 101, 102, 104, 110, 116, 119, 122, 123, + 124, 127, 128, 130, 136, 137, 138, 139, 141, 147, 148, 149, 151, 160, 162, 168, 170, 176, + 178, 184, 186, 192, 199, 201, 202, 205, 208, 210, 212, 214, 215, 217, 219, 220, 222, 223, + 224, 226, 227, 228, 231, 232, 234 ], "excluded_lines": [], "functions": { @@ -2076,17 +2951,17 @@ "executed_lines": [], "summary": { "covered_lines": 0, - "num_statements": 32, + "num_statements": 34, "percent_covered": 0.0, "percent_covered_display": "0", - "missing_lines": 32, + "missing_lines": 34, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, "missing_lines": [ - 3, 4, 5, 6, 7, 9, 12, 15, 16, 17, 20, 21, 26, 27, 29, 34, 47, 48, 65, 68, 78, 93, 104, - 130, 141, 151, 162, 170, 178, 186, 210, 217 + 3, 4, 5, 6, 7, 9, 12, 15, 16, 17, 20, 21, 24, 25, 26, 27, 29, 34, 47, 48, 65, 68, 78, + 93, 104, 130, 141, 151, 162, 170, 178, 186, 210, 217 ], "excluded_lines": [], "start_line": 1 @@ -2149,17 +3024,17 @@ "executed_lines": [], "summary": { "covered_lines": 0, - "num_statements": 32, + "num_statements": 34, "percent_covered": 0.0, "percent_covered_display": "0", - "missing_lines": 32, + "missing_lines": 34, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, "missing_lines": [ - 3, 4, 5, 6, 7, 9, 12, 15, 16, 17, 20, 21, 26, 27, 29, 34, 47, 48, 65, 68, 78, 93, 104, - 130, 141, 151, 162, 170, 178, 186, 210, 217 + 3, 4, 5, 6, 7, 9, 12, 15, 16, 17, 20, 21, 24, 25, 26, 27, 29, 34, 47, 48, 65, 68, 78, + 93, 104, 130, 141, 151, 162, 170, 178, 186, 210, 217 ], "excluded_lines": [], "start_line": 1 @@ -2237,15 +3112,15 @@ "executed_lines": [], "summary": { "covered_lines": 0, - "num_statements": 14, + "num_statements": 15, "percent_covered": 0.0, "percent_covered_display": "0", - "missing_lines": 14, + "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, - "missing_lines": [7, 9, 13, 22, 23, 32, 35, 54, 55, 58, 61, 64, 65, 74], + "missing_lines": [7, 9, 13, 22, 23, 31, 32, 35, 54, 55, 58, 61, 64, 65, 74], "excluded_lines": [], "functions": { "validate_fifty_percent_rule": { @@ -2268,15 +3143,15 @@ "executed_lines": [], "summary": { "covered_lines": 0, - "num_statements": 7, + "num_statements": 8, "percent_covered": 0.0, "percent_covered_display": "0", - "missing_lines": 7, + "missing_lines": 8, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, - "missing_lines": [7, 9, 13, 22, 23, 32, 35], + "missing_lines": [7, 9, 13, 22, 23, 31, 32, 35], "excluded_lines": [], "start_line": 1 } @@ -2302,15 +3177,15 @@ "executed_lines": [], "summary": { "covered_lines": 0, - "num_statements": 14, + "num_statements": 15, "percent_covered": 0.0, "percent_covered_display": "0", - "missing_lines": 14, + "missing_lines": 15, "excluded_lines": 0, "percent_statements_covered": 0.0, "percent_statements_covered_display": "0" }, - "missing_lines": [7, 9, 13, 22, 23, 32, 35, 54, 55, 58, 61, 64, 65, 74], + "missing_lines": [7, 9, 13, 22, 23, 31, 32, 35, 54, 55, 58, 61, 64, 65, 74], "excluded_lines": [], "start_line": 1 } @@ -2474,13 +3349,13 @@ } }, "totals": { - "covered_lines": 98, - "num_statements": 589, - "percent_covered": 16.6383701188455, - "percent_covered_display": "17", - "missing_lines": 491, - "excluded_lines": 2, - "percent_statements_covered": 16.6383701188455, - "percent_statements_covered_display": "17" + "covered_lines": 152, + "num_statements": 945, + "percent_covered": 16.084656084656086, + "percent_covered_display": "16", + "missing_lines": 793, + "excluded_lines": 4, + "percent_statements_covered": 16.084656084656086, + "percent_statements_covered_display": "16" } }