use tga::core::db::{
load_candidates, summarize, upsert_pm_work, CheckpointMode, Database, PmWorkRow,
};
use tga::core::pm_work::{self, extract, PmWorkInput};
pub(super) fn backfill_pm_work(db: &mut Database, dry_run: bool) -> anyhow::Result<()> {
let candidates = load_candidates(db.connection())
.map_err(|e| anyhow::anyhow!("pm-work read failed: {e}"))?;
if dry_run {
println!(
"Dry run — would classify {} work item(s) into fact_pm_work \
(formula_version={}). No changes written.",
candidates.len(),
pm_work::FORMULA_VERSION
);
return Ok(());
}
let mut written = 0usize;
for candidate in &candidates {
let fields = extract::extract_fields(candidate.raw_json.as_deref());
let verdict = pm_work::classify(&PmWorkInput {
title: &candidate.title,
description: fields.description.as_deref(),
reporter: fields.reporter.as_deref(),
human_transitioned: candidate.human_transitioned,
});
let row = PmWorkRow {
work_item_id: candidate.id.clone(),
work_item_source: candidate.source.clone(),
pm_name: fields.reporter.clone(),
week_key: fields.created.map(extract::week_key),
verdict,
};
upsert_pm_work(db.connection(), &row)
.map_err(|e| anyhow::anyhow!("pm-work upsert failed for {}: {e}", candidate.id))?;
written += 1;
}
let (meaningful, excluded) =
summarize(db.connection()).map_err(|e| anyhow::anyhow!("pm-work summary failed: {e}"))?;
println!(
"Backfilled fact_pm_work: {written} row(s) written (UPSERT semantics), \
formula_version={}.",
pm_work::FORMULA_VERSION
);
println!(" meaningful: {meaningful}");
for (reason, count) in excluded {
println!(" excluded {reason}: {count}");
}
if let Err(e) = db.wal_checkpoint(CheckpointMode::Truncate) {
tracing::warn!(error = %e, "WAL TRUNCATE checkpoint failed after pm-work backfill");
}
Ok(())
}