Skip to content

Commit b7b068a

Browse files
sangggggclaude
andauthored
refactor: extract rubric data to AIQuantitativeOutput and simplify Analytics (#80)
- Add new AIQuantitativeOutput struct for rubric-based LLM-as-a-judge data - Move rubric_scores and rubric_summary from QualitativeOutput to AIQuantitativeOutput - Remove quantitative_input and qualitative_input from Analytics struct (can be reconstructed from session_id) - Update Analytics::new() to accept AIQuantitativeOutput and Metrics directly - Add migration 014 to restructure analytics table - Update database repository and service layer accordingly - Simplify formatters to use metrics field instead of quantitative_input - Update TypeScript types for frontend compatibility Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7a86214 commit b7b068a

13 files changed

+223
-312
lines changed

.sqlx/query-a86b1bafa09dce47f7cb020c8f3b6545a3472f39fccffb78036495f2249d923e.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.sqlx/query-c8ce3ddd28ebae1dcfe6ca5ce06a47f1e68693f3e6ae699a2ace3a8eb82d9134.json renamed to .sqlx/query-d8afba9d2c421e71f3ec567212228c7962f50fd9fee31e53b8d0cef48d2e6faf.json

Lines changed: 7 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.sqlx/query-d46e2ca1d29a8b793a70a4af7cca1262f80c2581504e807667436a65701311da.json renamed to .sqlx/query-e6df29ab91df154a77f022b2bbd7814145ed9c7aab397815843eab3612462953.json

Lines changed: 7 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.sqlx/query-f3043f35b871a5e6c50ed16fc43f3e868abc56f9e0dbd5c78ae99f2b5e9a74d5.json

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
-- Refactor analytics table: add ai_quantitative_output, remove input columns
2+
-- Migration: 014_add_ai_quantitative_output
3+
-- Description:
4+
-- 1. Add ai_quantitative_output_json column to store rubric-based LLM-as-a-judge evaluation
5+
-- 2. Remove quantitative_input_json and qualitative_input_json columns (can be reconstructed from session_id)
6+
7+
-- SQLite doesn't support DROP COLUMN, so we need to recreate the table
8+
-- Step 1: Create new table with updated schema
9+
CREATE TABLE analytics_new (
10+
id TEXT PRIMARY KEY,
11+
analytics_request_id TEXT NOT NULL,
12+
session_id TEXT,
13+
generated_at TEXT NOT NULL,
14+
15+
-- Consolidated JSON columns (meaningful groups)
16+
scores_json TEXT NOT NULL,
17+
metrics_json TEXT NOT NULL,
18+
19+
-- Analysis output data (already JSON)
20+
-- Note: quantitative_input_json and qualitative_input_json are removed
21+
-- as they can be reconstructed from session_id
22+
qualitative_output_json TEXT NOT NULL,
23+
processed_output_json TEXT NOT NULL,
24+
25+
-- AI-generated quantitative output (rubric-based LLM-as-a-judge evaluation)
26+
ai_quantitative_output_json TEXT NOT NULL DEFAULT '{"rubric_scores":[],"rubric_summary":null}',
27+
28+
-- Metadata
29+
model_used TEXT,
30+
analysis_duration_ms INTEGER,
31+
32+
FOREIGN KEY (analytics_request_id) REFERENCES analytics_requests(id) ON DELETE CASCADE
33+
);
34+
35+
-- Step 2: Migrate existing data
36+
INSERT INTO analytics_new (
37+
id, analytics_request_id, session_id, generated_at,
38+
scores_json, metrics_json,
39+
qualitative_output_json, processed_output_json,
40+
ai_quantitative_output_json,
41+
model_used, analysis_duration_ms
42+
)
43+
SELECT
44+
id, analytics_request_id, session_id, generated_at,
45+
scores_json, metrics_json,
46+
qualitative_output_json, processed_output_json,
47+
'{"rubric_scores":[],"rubric_summary":null}' as ai_quantitative_output_json,
48+
model_used, analysis_duration_ms
49+
FROM analytics;
50+
51+
-- Step 3: Drop old table
52+
DROP TABLE analytics;
53+
54+
-- Step 4: Rename new table
55+
ALTER TABLE analytics_new RENAME TO analytics;
56+
57+
-- Step 5: Recreate indexes
58+
CREATE INDEX idx_analytics_request_id ON analytics(analytics_request_id);
59+
CREATE INDEX idx_analytics_generated_at ON analytics(generated_at);
60+
CREATE INDEX idx_analytics_session_id ON analytics(session_id);

src/database/analytics_repo.rs

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -391,35 +391,32 @@ impl AnalyticsRepository {
391391
serde_json::to_string(&analytics.scores).context("Failed to serialize scores")?;
392392
let metrics_json =
393393
serde_json::to_string(&analytics.metrics).context("Failed to serialize metrics")?;
394-
let quantitative_input_json = serde_json::to_string(&analytics.quantitative_input)
395-
.context("Failed to serialize quantitative_input")?;
396-
let qualitative_input_json = serde_json::to_string(&analytics.qualitative_input)
397-
.context("Failed to serialize qualitative_input")?;
398394
let qualitative_output_json = serde_json::to_string(&analytics.qualitative_output)
399395
.context("Failed to serialize qualitative_output")?;
400396
let processed_output_json = serde_json::to_string(&analytics.processed_output)
401397
.context("Failed to serialize processed_output")?;
398+
let ai_quantitative_output_json = serde_json::to_string(&analytics.ai_quantitative_output)
399+
.context("Failed to serialize ai_quantitative_output")?;
402400

403401
sqlx::query!(
404402
r#"
405403
INSERT INTO analytics (
406404
id, analytics_request_id, session_id, generated_at,
407405
scores_json, metrics_json,
408-
quantitative_input_json, qualitative_input_json,
409406
qualitative_output_json, processed_output_json,
407+
ai_quantitative_output_json,
410408
model_used, analysis_duration_ms
411-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
409+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
412410
"#,
413411
analytics.id,
414412
analytics.analytics_request_id,
415413
analytics.session_id,
416414
generated_at,
417415
scores_json,
418416
metrics_json,
419-
quantitative_input_json,
420-
qualitative_input_json,
421417
qualitative_output_json,
422418
processed_output_json,
419+
ai_quantitative_output_json,
423420
analytics.model_used,
424421
analytics.analysis_duration_ms
425422
)
@@ -436,8 +433,8 @@ impl AnalyticsRepository {
436433
SELECT
437434
id, analytics_request_id, session_id, generated_at,
438435
scores_json, metrics_json,
439-
quantitative_input_json, qualitative_input_json,
440436
qualitative_output_json, processed_output_json,
437+
ai_quantitative_output_json,
441438
model_used, analysis_duration_ms
442439
FROM analytics
443440
WHERE id = ?
@@ -456,18 +453,15 @@ impl AnalyticsRepository {
456453
serde_json::from_str(&row.scores_json).context("Failed to deserialize scores")?;
457454
let metrics: crate::models::Metrics =
458455
serde_json::from_str(&row.metrics_json).context("Failed to deserialize metrics")?;
459-
let quantitative_input: crate::services::analytics::QuantitativeInput =
460-
serde_json::from_str(&row.quantitative_input_json)
461-
.context("Failed to deserialize quantitative_input")?;
462-
let qualitative_input: crate::services::analytics::QualitativeInput =
463-
serde_json::from_str(&row.qualitative_input_json)
464-
.context("Failed to deserialize qualitative_input")?;
465456
let qualitative_output: crate::services::analytics::QualitativeOutput =
466457
serde_json::from_str(&row.qualitative_output_json)
467458
.context("Failed to deserialize qualitative_output")?;
468459
let processed_output: crate::services::analytics::ProcessedQuantitativeOutput =
469460
serde_json::from_str(&row.processed_output_json)
470461
.context("Failed to deserialize processed_output")?;
462+
let ai_quantitative_output: crate::services::analytics::AIQuantitativeOutput =
463+
serde_json::from_str(&row.ai_quantitative_output_json)
464+
.context("Failed to deserialize ai_quantitative_output")?;
471465

472466
// Get session_id from row, or fetch from analytics_requests as fallback
473467
let session_id = if let Some(sid) = row.session_id {
@@ -489,10 +483,9 @@ impl AnalyticsRepository {
489483
generated_at,
490484
scores,
491485
metrics,
492-
quantitative_input,
493-
qualitative_input,
494486
qualitative_output,
495487
processed_output,
488+
ai_quantitative_output,
496489
model_used: row.model_used,
497490
analysis_duration_ms: row.analysis_duration_ms,
498491
}))
@@ -510,8 +503,8 @@ impl AnalyticsRepository {
510503
SELECT
511504
id, analytics_request_id, session_id, generated_at,
512505
scores_json, metrics_json,
513-
quantitative_input_json, qualitative_input_json,
514506
qualitative_output_json, processed_output_json,
507+
ai_quantitative_output_json,
515508
model_used, analysis_duration_ms
516509
FROM analytics
517510
WHERE analytics_request_id = ?
@@ -532,18 +525,15 @@ impl AnalyticsRepository {
532525
serde_json::from_str(&row.scores_json).context("Failed to deserialize scores")?;
533526
let metrics: crate::models::Metrics =
534527
serde_json::from_str(&row.metrics_json).context("Failed to deserialize metrics")?;
535-
let quantitative_input: crate::services::analytics::QuantitativeInput =
536-
serde_json::from_str(&row.quantitative_input_json)
537-
.context("Failed to deserialize quantitative_input")?;
538-
let qualitative_input: crate::services::analytics::QualitativeInput =
539-
serde_json::from_str(&row.qualitative_input_json)
540-
.context("Failed to deserialize qualitative_input")?;
541528
let qualitative_output: crate::services::analytics::QualitativeOutput =
542529
serde_json::from_str(&row.qualitative_output_json)
543530
.context("Failed to deserialize qualitative_output")?;
544531
let processed_output: crate::services::analytics::ProcessedQuantitativeOutput =
545532
serde_json::from_str(&row.processed_output_json)
546533
.context("Failed to deserialize processed_output")?;
534+
let ai_quantitative_output: crate::services::analytics::AIQuantitativeOutput =
535+
serde_json::from_str(&row.ai_quantitative_output_json)
536+
.context("Failed to deserialize ai_quantitative_output")?;
547537

548538
// Get session_id from row, or fetch from analytics_requests as fallback
549539
let session_id = if let Some(sid) = row.session_id {
@@ -565,10 +555,9 @@ impl AnalyticsRepository {
565555
generated_at,
566556
scores,
567557
metrics,
568-
quantitative_input,
569-
qualitative_input,
570558
qualitative_output,
571559
processed_output,
560+
ai_quantitative_output,
572561
model_used: row.model_used,
573562
analysis_duration_ms: row.analysis_duration_ms,
574563
}))

src/models/analytics.rs

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ use uuid::Uuid;
44

55
// Re-export types from services that will be stored as JSON
66
use crate::services::analytics::{
7-
ProcessedQuantitativeOutput, QualitativeInput, QualitativeOutput, QuantitativeInput,
8-
QuantitativeOutput,
7+
AIQuantitativeOutput, ProcessedQuantitativeOutput, QualitativeOutput, QuantitativeOutput,
98
};
109

1110
// =============================================================================
@@ -44,10 +43,12 @@ pub struct Analytics {
4443
pub metrics: Metrics,
4544

4645
// Complex data structures (stored as JSON strings in DB, deserialized here)
47-
pub quantitative_input: QuantitativeInput,
48-
pub qualitative_input: QualitativeInput,
46+
// Note: quantitative_input and qualitative_input are not stored here
47+
// as they can be reconstructed from session_id
4948
pub qualitative_output: QualitativeOutput,
5049
pub processed_output: ProcessedQuantitativeOutput,
50+
/// AI-generated quantitative output from rubric-based LLM-as-a-judge evaluation
51+
pub ai_quantitative_output: AIQuantitativeOutput,
5152

5253
// Metadata
5354
pub model_used: Option<String>,
@@ -59,11 +60,11 @@ impl Analytics {
5960
pub fn new(
6061
analytics_request_id: String,
6162
session_id: String,
62-
quantitative_input: QuantitativeInput,
63-
qualitative_input: QualitativeInput,
6463
quantitative_output: QuantitativeOutput,
6564
qualitative_output: QualitativeOutput,
6665
processed_output: ProcessedQuantitativeOutput,
66+
ai_quantitative_output: AIQuantitativeOutput,
67+
metrics: Metrics,
6768
model_used: Option<String>,
6869
analysis_duration_ms: Option<i64>,
6970
) -> Self {
@@ -80,20 +81,10 @@ impl Analytics {
8081
collaboration: quantitative_output.collaboration_score,
8182
learning: quantitative_output.learning_score,
8283
},
83-
metrics: Metrics {
84-
total_files_modified: quantitative_input.file_changes.total_files_modified,
85-
total_files_read: quantitative_input.file_changes.total_files_read,
86-
lines_added: quantitative_input.file_changes.lines_added,
87-
lines_removed: quantitative_input.file_changes.lines_removed,
88-
total_tokens_used: quantitative_input.token_metrics.total_tokens_used,
89-
session_duration_minutes: quantitative_input
90-
.time_metrics
91-
.total_session_time_minutes,
92-
},
93-
quantitative_input,
94-
qualitative_input,
84+
metrics,
9585
qualitative_output,
9686
processed_output,
87+
ai_quantitative_output,
9788
model_used,
9889
analysis_duration_ms,
9990
}

0 commit comments

Comments
 (0)