use std::collections::HashMap;
use crate::analyses::knowledge::trailers;
use crate::facts::FactsDb;
use crate::{CodeLoreError, Options, Result};
pub const DECAY_DAYS: f64 = 220.0;
pub const W_AI_AUTHORED: f64 = 0.3;
pub const W_AI_ASSISTED: f64 = 0.7;
pub const W_REVIEWER: f64 = 0.5;
#[tracing::instrument(name = "knowledge-shares", skip_all)]
pub fn materialize_knowledge_shares(db: &FactsDb, opts: &Options) -> Result<()> {
if db.is_knowledge_shares_built() {
return Ok(());
}
let src = crate::analyses::lineage::source_table(opts);
crate::analyses::lineage::materialize_if_needed(db, opts)?;
let now_anchor = crate::analyses::query::clamped_now_anchor("date");
let human_aliases = crate::analyses::query::HUMAN_ALIASES_CTE;
let base_sql = format!(
"CREATE OR REPLACE TEMP TABLE knowledge_shares AS
WITH anchor AS (SELECT {now_anchor} AS max_d FROM commits),
{human_aliases},
contrib AS (
SELECT c.path,
co.canonical_author AS author,
SUM(
c.loc_added
* CASE co.ai_attribution
WHEN 'ai-authored' THEN {W_AI_AUTHORED}
WHEN 'ai-assisted' THEN {W_AI_ASSISTED}
ELSE 1.0
END
-- GREATEST floors the age at 0: a commit dated after the
-- clamped anchor (clock skew, a future date) reads as the
-- present, never a negative age that would invert the decay.
* EXP(-GREATEST(date_diff('day', co.date, (SELECT max_d FROM anchor)), 0)
/ {DECAY_DAYS})
) AS k
FROM {src} c
JOIN commits co USING (rev)
JOIN human_aliases ha ON ha.raw_name = co.author_name AND ha.raw_email = co.author_email
WHERE c.change_type != 'deleted'
GROUP BY c.path, co.canonical_author
)
SELECT path,
author,
k,
k / NULLIF(SUM(k) OVER (PARTITION BY path), 0) AS k_norm
FROM contrib",
);
db.execute_batch(&base_sql)?;
let reviewer_rows = collect_reviewer_rows(db, src)?;
if !reviewer_rows.is_empty() {
insert_reviewer_rows(db, &reviewer_rows)?;
db.execute_batch(
"CREATE OR REPLACE TEMP TABLE knowledge_shares AS
WITH merged AS (
SELECT path, author, SUM(k) AS k
FROM knowledge_shares
GROUP BY path, author
)
SELECT path,
author,
k,
k / NULLIF(SUM(k) OVER (PARTITION BY path), 0) AS k_norm
FROM merged",
)?;
}
materialize_doe_scores(db, src)?;
db.mark_knowledge_shares_built();
Ok(())
}
struct ReviewerRow {
path: String,
author: String,
k: f64,
}
fn collect_reviewer_rows(db: &FactsDb, src: &str) -> Result<Vec<ReviewerRow>> {
let now_anchor = crate::analyses::query::clamped_now_anchor("date");
let human_aliases = crate::analyses::query::HUMAN_ALIASES_CTE;
let query = format!(
"WITH {human_aliases}
SELECT c.path,
co.message,
c.loc_added
* CASE co.ai_attribution
WHEN 'ai-authored' THEN {W_AI_AUTHORED}
WHEN 'ai-assisted' THEN {W_AI_ASSISTED}
ELSE 1.0
END
* EXP(-GREATEST(date_diff('day', co.date,
(SELECT {now_anchor} FROM commits)), 0)
/ {DECAY_DAYS})
AS k_path
FROM {src} c
JOIN commits co USING (rev)
-- Pair-granular: a human sharing a canonical with a bot keeps
-- their own reviewer-credit contribution counted (see contrib
-- above in materialize_knowledge_shares).
JOIN human_aliases ha ON ha.raw_name = co.author_name AND ha.raw_email = co.author_email
WHERE c.change_type != 'deleted'
AND co.nf <= 10",
);
let mut flat_rows: Vec<(String, String, f64)> = Vec::new();
{
let mut stmt = db
.conn()
.prepare(&query)
.map_err(|e| CodeLoreError::Analysis(format!("prepare reviewer scan: {e}")))?;
let rows = stmt
.query_map([], |r| {
let path: String = r.get(0)?;
let message: String = r.get(1)?;
let k_path: f64 = r.get(2)?;
Ok((path, message, k_path))
})
.map_err(|e| CodeLoreError::Analysis(format!("query reviewer scan: {e}")))?;
for row in rows {
let (path, message, k_path) =
row.map_err(|e| CodeLoreError::Analysis(format!("row reviewer scan: {e}")))?;
flat_rows.push((path, message, k_path));
}
}
if flat_rows.is_empty() {
return Ok(Vec::new());
}
let alias_map = build_alias_map(db)?;
let mut agg: HashMap<(String, String), f64> = HashMap::new();
for (path, message, k_path) in &flat_rows {
let trailer_emails: Vec<String> = {
let mut v = trailers::extract_coauthors(message);
v.extend(trailers::extract_reviewers(message));
v.sort();
v.dedup();
v
};
for email in &trailer_emails {
let Some(canonical) = alias_map.get(email.as_str()) else {
continue; };
*agg.entry((path.clone(), canonical.clone())).or_insert(0.0) += k_path * W_REVIEWER;
}
}
Ok(agg
.into_iter()
.map(|((path, author), k)| ReviewerRow { path, author, k })
.collect())
}
fn build_alias_map(db: &FactsDb) -> Result<HashMap<String, String>> {
let query = format!(
"WITH {human_aliases} \
SELECT raw_email, canonical FROM human_aliases ORDER BY raw_email, canonical",
human_aliases = crate::analyses::query::HUMAN_ALIASES_CTE
);
let mut stmt = db
.conn()
.prepare(&query)
.map_err(|e| CodeLoreError::Analysis(format!("prepare alias_map: {e}")))?;
let rows = stmt
.query_map([], |r| Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?)))
.map_err(|e| CodeLoreError::Analysis(format!("query alias_map: {e}")))?;
let mut map = HashMap::new();
for row in rows {
let (raw, canonical) =
row.map_err(|e| CodeLoreError::Analysis(format!("row alias_map: {e}")))?;
map.insert(raw.to_lowercase(), canonical);
}
Ok(map)
}
fn insert_reviewer_rows(db: &FactsDb, rows: &[ReviewerRow]) -> Result<()> {
let mut stmt = db
.conn()
.prepare("INSERT INTO knowledge_shares (path, author, k, k_norm) VALUES (?, ?, ?, 0.0)")
.map_err(|e| CodeLoreError::Analysis(format!("prepare insert reviewer rows: {e}")))?;
for row in rows {
stmt.execute(duckdb::params![row.path, row.author, row.k])
.map_err(|e| CodeLoreError::Analysis(format!("insert reviewer row: {e}")))?;
}
Ok(())
}
fn materialize_doe_scores(db: &FactsDb, src: &str) -> Result<()> {
let now_anchor = crate::analyses::query::clamped_now_anchor("date");
let human_aliases = crate::analyses::query::HUMAN_ALIASES_CTE;
let sql = format!(
"CREATE OR REPLACE TEMP TABLE doe_scores AS
WITH anchor AS (SELECT {now_anchor} AS max_d FROM commits),
{human_aliases},
-- Who first added each file (fa = 1 if this author created it).
-- Bots excluded pair-granularly: a human sharing a canonical with
-- a bot keeps their own 'added' commits counted (see agg below).
-- DISTINCT: a path deleted and re-added by the same author would
-- otherwise yield duplicate rows here, and the LEFT JOIN below
-- would then duplicate that author's doe_scores row.
first_adders AS (
SELECT DISTINCT c.path,
co.canonical_author AS author
FROM {src} c
JOIN commits co USING (rev)
JOIN human_aliases ha ON ha.raw_name = co.author_name AND ha.raw_email = co.author_email
WHERE c.change_type = 'added'
),
-- HEAD SLOC per path. `complexity_metrics` holds only the HEAD-time
-- scan (its `rev` column carries the actual head SHA, never the
-- literal 'HEAD'), so no rev filter is needed — filtering on
-- rev = 'HEAD' would match zero rows and silently zero the size term.
head_sloc AS (
SELECT path, GREATEST(SUM(sloc), 1) AS size
FROM complexity_metrics
GROUP BY path
),
-- Per author×path aggregates.
agg AS (
SELECT c.path,
co.canonical_author AS author,
SUM(c.loc_added) AS adds,
-- GREATEST floors the age at 0 so a commit dated after the
-- clamped anchor cannot drive LN(1 + num_days) negative.
GREATEST(date_diff('day',
MAX(co.date),
(SELECT max_d FROM anchor)
), 0) AS num_days
FROM {src} c
JOIN commits co USING (rev)
JOIN human_aliases ha ON ha.raw_name = co.author_name AND ha.raw_email = co.author_email
WHERE c.change_type != 'deleted'
GROUP BY c.path, co.canonical_author
),
doe_raw AS (
SELECT agg.path,
agg.author,
5.28223
+ 0.23173 * LN(1.0 + agg.adds)
+ 0.36151 * CASE WHEN fa.author IS NOT NULL THEN 1.0 ELSE 0.0 END
- 0.19421 * LN(1.0 + agg.num_days)
- 0.28761 * LN(COALESCE(hs.size, 1))
AS doe
FROM agg
LEFT JOIN first_adders fa
ON fa.path = agg.path AND fa.author = agg.author
LEFT JOIN head_sloc hs ON hs.path = agg.path
)
SELECT path,
author,
doe,
doe >= 1.0
AND doe >= 0.75 * MAX(doe) OVER (PARTITION BY path)
AS is_expert
FROM doe_raw",
);
db.execute_batch(&sql)
}