diff --git a/brain-bar/Sources/BrainBar/BrainDatabase.swift b/brain-bar/Sources/BrainBar/BrainDatabase.swift index 2a35eeea..fdd0cb29 100644 --- a/brain-bar/Sources/BrainBar/BrainDatabase.swift +++ b/brain-bar/Sources/BrainBar/BrainDatabase.swift @@ -1445,10 +1445,11 @@ final class BrainDatabase: @unchecked Sendable { private func dashboardCounts() throws -> (chunkCount: Int, enrichedChunkCount: Int, pendingEnrichmentCount: Int) { guard let db else { throw DBError.notOpen } + // Pending enrichment is represented by NULL; any persisted status is terminal coverage. let sql = """ SELECT COUNT(*) AS chunk_count, - SUM(CASE WHEN enrich_status = 'success' THEN 1 ELSE 0 END) AS enriched_count, + SUM(CASE WHEN enrich_status IS NOT NULL THEN 1 ELSE 0 END) AS enriched_count, SUM(CASE WHEN enriched_at IS NULL AND enrich_status IS NULL THEN 1 ELSE 0 END) AS pending_enrichment_count FROM chunks """ @@ -4274,6 +4275,7 @@ final class BrainDatabase: @unchecked Sendable { func enrichmentStats() throws -> EnrichmentStatsSummary { guard let db else { throw DBError.notOpen } let enrichedAtEpochSQL = Self.normalizedUnixEpochSQL(for: "enriched_at") + let terminalCoveragePredicate = "enrich_status IS NOT NULL" func queryInt(_ sql: String) throws -> Int { var stmt: OpaquePointer? @@ -4287,9 +4289,9 @@ final class BrainDatabase: @unchecked Sendable { return EnrichmentStatsSummary( totalChunks: try queryInt("SELECT COUNT(*) FROM chunks"), - enriched: try queryInt("SELECT COUNT(*) FROM chunks WHERE enrich_status = 'success'"), + enriched: try queryInt("SELECT COUNT(*) FROM chunks WHERE \(terminalCoveragePredicate)"), unenrichedEligible: try queryInt("SELECT COUNT(*) FROM chunks WHERE enriched_at IS NULL AND enrich_status IS NULL AND char_count >= 50"), - skippedTooShort: try queryInt("SELECT COUNT(*) FROM chunks WHERE (enrich_status IS NOT NULL AND enrich_status != 'success') OR (enrich_status IS NULL AND enriched_at IS NULL AND char_count < 50)"), + skippedTooShort: try queryInt("SELECT COUNT(*) FROM chunks WHERE enrich_status IS NULL AND enriched_at IS NULL AND char_count < 50"), enrichedLast24Hours: try queryInt(""" SELECT COUNT(*) FROM ( @@ -4454,7 +4456,8 @@ final class BrainDatabase: @unchecked Sendable { let totalChunks = try queryInt("SELECT COUNT(*) FROM chunks") let totalEntities = try queryInt("SELECT COUNT(*) FROM kg_entities") let totalRelations = try queryInt("SELECT COUNT(*) FROM kg_relations") - let enrichedChunks = try queryInt("SELECT COUNT(*) FROM chunks WHERE enrich_status = 'success'") + // Pending enrichment is represented by NULL; any persisted status is terminal coverage. + let enrichedChunks = try queryInt("SELECT COUNT(*) FROM chunks WHERE enrich_status IS NOT NULL") let totalProjects = try queryInt("SELECT COUNT(DISTINCT project) FROM chunks") let projects = try queryStrings("SELECT DISTINCT project FROM chunks WHERE project IS NOT NULL AND project != '' ORDER BY project ASC LIMIT 12") let contentTypes = try queryStrings("SELECT DISTINCT content_type FROM chunks WHERE content_type IS NOT NULL AND content_type != '' ORDER BY content_type ASC") diff --git a/brain-bar/Tests/BrainBarTests/DashboardTests.swift b/brain-bar/Tests/BrainBarTests/DashboardTests.swift index 3c77240a..1c28ea22 100644 --- a/brain-bar/Tests/BrainBarTests/DashboardTests.swift +++ b/brain-bar/Tests/BrainBarTests/DashboardTests.swift @@ -52,7 +52,7 @@ final class DashboardTests: XCTestCase { XCTAssertGreaterThan(stats.databaseSizeBytes, 0) } - func testDashboardStatsIgnoresSkippedEnrichmentStatusStrings() throws { + func testDashboardStatsCountsTerminalEnrichmentStatusesAsCovered() throws { try db.insertChunk( id: "dash-success", content: "Successfully enriched chunk", @@ -62,8 +62,16 @@ final class DashboardTests: XCTestCase { importance: 7 ) try db.insertChunk( - id: "dash-skipped", - content: "Legacy skipped duplicate marker", + id: "dash-duplicate", + content: "Terminal duplicate marker", + sessionId: "dashboard", + project: "brainlayer", + contentType: "assistant_text", + importance: 5 + ) + try db.insertChunk( + id: "dash-noise", + content: "Terminal noise marker", sessionId: "dashboard", project: "brainlayer", contentType: "assistant_text", @@ -79,13 +87,20 @@ final class DashboardTests: XCTestCase { UPDATE chunks SET enriched_at = 'skipped:duplicate', enrich_status = 'duplicate' - WHERE id = 'dash-skipped' + WHERE id = 'dash-duplicate' + """) + db.exec(""" + UPDATE chunks + SET enriched_at = NULL, + enrich_status = 'noise' + WHERE id = 'dash-noise' """) let stats = try db.dashboardStats(activityWindowMinutes: 30, bucketCount: 6) - XCTAssertEqual(stats.enrichedChunkCount, 1) + XCTAssertEqual(stats.enrichedChunkCount, 3) XCTAssertEqual(stats.pendingEnrichmentCount, 0) + XCTAssertEqual(stats.enrichmentPercent, 100.0, accuracy: 0.001) XCTAssertEqual(stats.recentEnrichmentBuckets.reduce(0, +), 1) let lastEnrichedAt = try XCTUnwrap(stats.lastEnrichedAt) XCTAssertLessThan(abs(lastEnrichedAt.timeIntervalSinceNow + 300), 10) @@ -102,6 +117,56 @@ final class DashboardTests: XCTestCase { XCTAssertEqual(stats.recentEnrichmentBuckets, [0, 0, 0, 0]) } + func testEnrichmentStatsCountsAnyTerminalStatusAsCovered() throws { + try db.insertChunk( + id: "stats-success", + content: "Successfully enriched stats chunk", + sessionId: "dashboard", + project: "brainlayer", + contentType: "assistant_text", + importance: 7 + ) + try db.insertChunk( + id: "stats-duplicate", + content: "Duplicate terminal stats chunk", + sessionId: "dashboard", + project: "brainlayer", + contentType: "assistant_text", + importance: 5 + ) + try db.insertChunk( + id: "stats-noise", + content: "Noise terminal stats chunk", + sessionId: "dashboard", + project: "brainlayer", + contentType: "assistant_text", + importance: 5 + ) + try db.insertChunk( + id: "stats-pending", + content: "Pending eligible stats chunk with enough text to meet the eligibility threshold", + sessionId: "dashboard", + project: "brainlayer", + contentType: "assistant_text", + importance: 5 + ) + db.exec("UPDATE chunks SET enriched_at = datetime('now'), enrich_status = 'success' WHERE id = 'stats-success'") + db.exec("UPDATE chunks SET enriched_at = 'skipped:duplicate', enrich_status = 'duplicate' WHERE id = 'stats-duplicate'") + db.exec("UPDATE chunks SET enriched_at = NULL, enrich_status = 'noise' WHERE id = 'stats-noise'") + + let summary = try db.enrichmentStats() + + XCTAssertEqual(summary.totalChunks, 4) + XCTAssertEqual(summary.enriched, 3) + XCTAssertEqual(summary.unenrichedEligible, 1) + XCTAssertEqual(summary.skippedTooShort, 0) + XCTAssertEqual( + summary.totalChunks, + summary.enriched + summary.unenrichedEligible + summary.skippedTooShort + ) + XCTAssertEqual(summary.enrichedPercentText, "75.0%") + } + func testDashboardStatsReadsPendingStoreQueueDepthAndOldestEntry() throws { let queuePath = URL(fileURLWithPath: NSTemporaryDirectory()) .appendingPathComponent("pending-stores-dashboard-\(UUID().uuidString).jsonl") diff --git a/brain-bar/Tests/BrainBarTests/DatabaseTests.swift b/brain-bar/Tests/BrainBarTests/DatabaseTests.swift index 122473d8..67ec80ca 100644 --- a/brain-bar/Tests/BrainBarTests/DatabaseTests.swift +++ b/brain-bar/Tests/BrainBarTests/DatabaseTests.swift @@ -977,6 +977,22 @@ final class DatabaseTests: XCTestCase { XCTAssertGreaterThan(total, 0) } + func testRecallStatsCoverageCountsAnyTerminalStatusAsEnriched() throws { + try db.insertChunk(id: "rc-success", content: "Recall stats success", sessionId: "s1", project: "test", contentType: "assistant_text", importance: 5) + try db.insertChunk(id: "rc-duplicate", content: "Recall stats duplicate", sessionId: "s1", project: "test", contentType: "assistant_text", importance: 5) + try db.insertChunk(id: "rc-noise", content: "Recall stats noise", sessionId: "s1", project: "test", contentType: "assistant_text", importance: 5) + try db.insertChunk(id: "rc-pending", content: "Recall stats pending", sessionId: "s1", project: "test", contentType: "assistant_text", importance: 5) + db.exec("UPDATE chunks SET enrich_status = 'success' WHERE id = 'rc-success'") + db.exec("UPDATE chunks SET enrich_status = 'duplicate' WHERE id = 'rc-duplicate'") + db.exec("UPDATE chunks SET enrich_status = 'noise' WHERE id = 'rc-noise'") + + let stats = try db.recallStats() + + XCTAssertEqual(stats["total_chunks"] as? Int, 4) + XCTAssertEqual(stats["enriched_chunks"] as? Int, 3) + XCTAssertEqual(stats["enrichment_pct"] as? Double, 75.0) + } + // MARK: - brain_digest (rule-based entity extraction) func testDigestExtractsEntities() throws {