Skip to content

Commit a0f18f6

Browse files
Split monolithic db.rs into domain submodules
Carve the 4161-line src/db.rs into src/db/{runs,search,bookmarks, source_quality,comparisons,batches,run_templates,watchlists, ticker_universe,scanner,llm,prices,thesis,research,scheduled_runs, portfolios}.rs, each extending impl Database for its own tables. The shared mod.rs owns the pool, migrations, and the encode_time / parse_time / collect_rows helpers; RankedInsert and EvidenceNoteInsert are re-exported from search so existing 'use crate::db::RankedInsert' call sites keep compiling. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
1 parent 2bea62b commit a0f18f6

18 files changed

Lines changed: 4357 additions & 4161 deletions

src/db.rs

Lines changed: 0 additions & 4161 deletions
This file was deleted.

src/db/batches.rs

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
//! Batch jobs: run the same question against a set of tickers.
2+
3+
use anyhow::{Context, Result};
4+
use chrono::Utc;
5+
use rusqlite::{params, OptionalExtension, Row};
6+
use uuid::Uuid;
7+
8+
use crate::models::{BatchJob, BatchJobRun, BatchJobRunWithDetails, Run};
9+
10+
use super::{collect_rows, encode_time, parse_time, Database};
11+
12+
impl Database {
13+
pub async fn create_batch_job(&self, name: &str, question_template: &str) -> Result<BatchJob> {
14+
let batch_job = BatchJob {
15+
id: Uuid::new_v4().to_string(),
16+
name: name.to_string(),
17+
question_template: question_template.to_string(),
18+
status: "building".to_string(),
19+
summary: None,
20+
created_at: Utc::now(),
21+
updated_at: Utc::now(),
22+
};
23+
24+
let conn = self.open_connection()?;
25+
conn.execute(
26+
"INSERT INTO batch_jobs (id, name, question_template, status, summary, created_at, updated_at)
27+
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
28+
params![
29+
batch_job.id,
30+
batch_job.name,
31+
batch_job.question_template,
32+
batch_job.status,
33+
batch_job.summary,
34+
encode_time(batch_job.created_at),
35+
encode_time(batch_job.updated_at),
36+
],
37+
)?;
38+
39+
self.get_batch_job(&batch_job.id)
40+
.await?
41+
.context("created batch job missing after insert")
42+
}
43+
44+
pub async fn get_batch_job(&self, batch_job_id: &str) -> Result<Option<BatchJob>> {
45+
let conn = self.open_connection()?;
46+
conn.query_row(
47+
"SELECT * FROM batch_jobs WHERE id = ?1",
48+
[batch_job_id],
49+
map_batch_job,
50+
)
51+
.optional()
52+
.map_err(Into::into)
53+
}
54+
55+
pub async fn list_batch_jobs(&self, limit: i64) -> Result<Vec<BatchJob>> {
56+
let conn = self.open_connection()?;
57+
let mut statement =
58+
conn.prepare("SELECT * FROM batch_jobs ORDER BY created_at DESC LIMIT ?1")?;
59+
let rows = statement.query_map([limit], map_batch_job)?;
60+
collect_rows(rows)
61+
}
62+
63+
pub async fn add_run_to_batch_job(
64+
&self,
65+
batch_job_id: &str,
66+
run_id: &str,
67+
ticker: &str,
68+
sort_order: i64,
69+
) -> Result<BatchJobRun> {
70+
let batch_job_run = BatchJobRun {
71+
id: Uuid::new_v4().to_string(),
72+
batch_job_id: batch_job_id.to_string(),
73+
run_id: run_id.to_string(),
74+
ticker: ticker.to_string(),
75+
sort_order,
76+
created_at: Utc::now(),
77+
};
78+
let conn = self.open_connection()?;
79+
conn.execute(
80+
"INSERT INTO batch_job_runs (id, batch_job_id, run_id, ticker, sort_order, created_at)
81+
VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
82+
params![
83+
batch_job_run.id,
84+
batch_job_run.batch_job_id,
85+
batch_job_run.run_id,
86+
batch_job_run.ticker,
87+
batch_job_run.sort_order,
88+
encode_time(batch_job_run.created_at),
89+
],
90+
)?;
91+
Ok(batch_job_run)
92+
}
93+
94+
pub async fn list_batch_job_runs(
95+
&self,
96+
batch_job_id: &str,
97+
) -> Result<Vec<BatchJobRunWithDetails>> {
98+
let conn = self.open_connection()?;
99+
let mut statement = conn.prepare(
100+
"SELECT
101+
bjr.id AS bjr_id,
102+
bjr.batch_job_id AS bjr_batch_job_id,
103+
bjr.run_id AS bjr_run_id,
104+
bjr.ticker AS bjr_ticker,
105+
bjr.sort_order AS bjr_sort_order,
106+
bjr.created_at AS bjr_created_at,
107+
r.id AS run_id,
108+
r.ticker AS run_ticker,
109+
r.question AS run_question,
110+
r.status AS run_status,
111+
r.created_at AS run_created_at,
112+
r.updated_at AS run_updated_at,
113+
r.final_iteration_number AS run_final_iteration_number,
114+
r.final_memo_markdown AS run_final_memo_markdown,
115+
r.final_memo_html AS run_final_memo_html,
116+
r.summary AS run_summary
117+
FROM batch_job_runs bjr
118+
LEFT JOIN runs r ON bjr.run_id = r.id
119+
WHERE bjr.batch_job_id = ?1
120+
ORDER BY bjr.sort_order ASC, bjr.created_at ASC",
121+
)?;
122+
123+
let rows = statement.query_map([batch_job_id], |row| {
124+
let batch_job_run = BatchJobRun {
125+
id: row.get("bjr_id")?,
126+
batch_job_id: row.get("bjr_batch_job_id")?,
127+
run_id: row.get("bjr_run_id")?,
128+
ticker: row.get("bjr_ticker")?,
129+
sort_order: row.get("bjr_sort_order")?,
130+
created_at: parse_time(row.get("bjr_created_at")?)?,
131+
};
132+
133+
let run_id: Option<String> = row.get("run_id")?;
134+
let run = if run_id.is_some() {
135+
Some(Run {
136+
id: row.get("run_id")?,
137+
ticker: row.get("run_ticker")?,
138+
question: row.get("run_question")?,
139+
status: row.get("run_status")?,
140+
created_at: parse_time(row.get("run_created_at")?)?,
141+
updated_at: parse_time(row.get("run_updated_at")?)?,
142+
final_iteration_number: row.get("run_final_iteration_number")?,
143+
final_memo_markdown: row.get("run_final_memo_markdown")?,
144+
final_memo_html: row.get("run_final_memo_html")?,
145+
summary: row.get("run_summary")?,
146+
})
147+
} else {
148+
None
149+
};
150+
151+
Ok(BatchJobRunWithDetails {
152+
id: batch_job_run.id,
153+
batch_job_id: batch_job_run.batch_job_id,
154+
run_id: batch_job_run.run_id,
155+
ticker: batch_job_run.ticker,
156+
sort_order: batch_job_run.sort_order,
157+
created_at: batch_job_run.created_at,
158+
run,
159+
})
160+
})?;
161+
162+
collect_rows(rows)
163+
}
164+
165+
pub async fn list_batch_job_ids_for_run(&self, run_id: &str) -> Result<Vec<String>> {
166+
let conn = self.open_connection()?;
167+
let mut statement =
168+
conn.prepare("SELECT batch_job_id FROM batch_job_runs WHERE run_id = ?1")?;
169+
let rows = statement.query_map([run_id], |row| row.get::<_, String>(0))?;
170+
collect_rows(rows)
171+
}
172+
173+
pub async fn update_batch_job_status(&self, batch_job_id: &str, status: &str) -> Result<()> {
174+
let conn = self.open_connection()?;
175+
conn.execute(
176+
"UPDATE batch_jobs SET status = ?1, updated_at = ?2 WHERE id = ?3",
177+
params![status, encode_time(Utc::now()), batch_job_id],
178+
)?;
179+
Ok(())
180+
}
181+
182+
pub async fn finalize_batch_job(
183+
&self,
184+
batch_job_id: &str,
185+
status: &str,
186+
summary: Option<&str>,
187+
) -> Result<()> {
188+
let conn = self.open_connection()?;
189+
conn.execute(
190+
"UPDATE batch_jobs SET status = ?1, summary = ?2, updated_at = ?3 WHERE id = ?4",
191+
params![status, summary, encode_time(Utc::now()), batch_job_id],
192+
)?;
193+
Ok(())
194+
}
195+
}
196+
197+
pub(crate) fn map_batch_job(row: &Row<'_>) -> rusqlite::Result<BatchJob> {
198+
Ok(BatchJob {
199+
id: row.get("id")?,
200+
name: row.get("name")?,
201+
question_template: row.get("question_template")?,
202+
status: row.get("status")?,
203+
summary: row.get("summary")?,
204+
created_at: parse_time(row.get("created_at")?)?,
205+
updated_at: parse_time(row.get("updated_at")?)?,
206+
})
207+
}

src/db/bookmarks.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//! Bookmarks - user-authored pointers to runs, iterations, and sources.
2+
3+
use anyhow::{Context, Result};
4+
use chrono::Utc;
5+
use rusqlite::{params, OptionalExtension, Row};
6+
use uuid::Uuid;
7+
8+
use crate::models::Bookmark;
9+
10+
use super::{collect_rows, encode_time, parse_time, Database};
11+
12+
impl Database {
13+
pub async fn upsert_bookmark(
14+
&self,
15+
entity_type: &str,
16+
entity_id: &str,
17+
title: &str,
18+
note: Option<&str>,
19+
target_path: &str,
20+
) -> Result<Bookmark> {
21+
let now = Utc::now();
22+
let id = Uuid::new_v4().to_string();
23+
let conn = self.open_connection()?;
24+
conn.execute(
25+
"INSERT INTO bookmarks (id, entity_type, entity_id, title, note, target_path, created_at, updated_at)
26+
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)
27+
ON CONFLICT(entity_type, entity_id)
28+
DO UPDATE SET
29+
title = excluded.title,
30+
note = excluded.note,
31+
target_path = excluded.target_path,
32+
updated_at = excluded.updated_at",
33+
params![
34+
id,
35+
entity_type,
36+
entity_id,
37+
title,
38+
note,
39+
target_path,
40+
encode_time(now),
41+
encode_time(now),
42+
],
43+
)?;
44+
self.get_bookmark_by_entity(entity_type, entity_id)
45+
.await?
46+
.context("bookmark missing after upsert")
47+
}
48+
49+
pub async fn list_bookmarks(&self, limit: i64) -> Result<Vec<Bookmark>> {
50+
let conn = self.open_connection()?;
51+
let mut statement =
52+
conn.prepare("SELECT * FROM bookmarks ORDER BY updated_at DESC LIMIT ?1")?;
53+
let rows = statement.query_map([limit], map_bookmark)?;
54+
collect_rows(rows)
55+
}
56+
57+
pub async fn delete_bookmark(&self, entity_type: &str, entity_id: &str) -> Result<bool> {
58+
let conn = self.open_connection()?;
59+
let affected = conn.execute(
60+
"DELETE FROM bookmarks WHERE entity_type = ?1 AND entity_id = ?2",
61+
params![entity_type, entity_id],
62+
)?;
63+
Ok(affected > 0)
64+
}
65+
66+
async fn get_bookmark_by_entity(
67+
&self,
68+
entity_type: &str,
69+
entity_id: &str,
70+
) -> Result<Option<Bookmark>> {
71+
let conn = self.open_connection()?;
72+
conn.query_row(
73+
"SELECT * FROM bookmarks WHERE entity_type = ?1 AND entity_id = ?2",
74+
params![entity_type, entity_id],
75+
map_bookmark,
76+
)
77+
.optional()
78+
.map_err(Into::into)
79+
}
80+
}
81+
82+
pub(crate) fn map_bookmark(row: &Row<'_>) -> rusqlite::Result<Bookmark> {
83+
Ok(Bookmark {
84+
id: row.get("id")?,
85+
entity_type: row.get("entity_type")?,
86+
entity_id: row.get("entity_id")?,
87+
title: row.get("title")?,
88+
note: row.get("note")?,
89+
target_path: row.get("target_path")?,
90+
created_at: parse_time(row.get("created_at")?)?,
91+
updated_at: parse_time(row.get("updated_at")?)?,
92+
})
93+
}

0 commit comments

Comments
 (0)