Curation v2 step 3: Voyage embeddings, cheap signals, candidate telemetry

- embedding.rs: EmbeddingBackend + VoyageBackend, batched bounded-concurrency
  client with its own UsageMeter, f32 BLOB codec, article/interest embedding
  cache keyed by model, dimension and sha256 of the embedded text.
- signals.rs: z-scored interest match, decayed rated-neighbour preference
  with the knn gate, feed affinity with the feed gate, social, text heuristic
  without social terms, mid-rank percentile normalizer, preliminary blend.
- telemetry.rs: candidate_runs writer with §7.5 signals_json, explain and
  near-misses renderers, prune.
- [voyage] and the full [curation.ranking] config with validation.
- CLI: explain, features backfill|prune, generate --skip-embeddings.
- Pipeline: hygiene rows, embed and signals stages before the old prefilter;
  same-date regeneration no longer excludes its own picks.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A1rCLQeKBgnBo3oTgHuTMe
This commit is contained in:
2026-09-02 04:08:45 +00:00
co-authored by Claude Fable 5.1
parent 3a9f4b99e0
commit ea3b141373
13 changed files with 4888 additions and 17 deletions
+39
View File
@@ -321,6 +321,45 @@ impl Db {
Ok(rows.iter().map(|r| r.get::<i64, _>("article_id")).collect())
}
/// Article ids published before this issue date; same-date regeneration is allowed (§8.1).
pub async fn previously_published_ids_before(&self, date: Date) -> Result<Vec<ArticleId>> {
let rows =
sqlx::query("SELECT DISTINCT article_id FROM issue_articles WHERE issue_date < ?")
.bind(date.to_string())
.fetch_all(&self.pool)
.await?;
Ok(rows
.iter()
.map(|row| row.get::<i64, _>("article_id"))
.collect())
}
/// Published article ids first seen at or after `since` (`features backfill`).
pub async fn published_article_ids_since(&self, since: Timestamp) -> Result<Vec<ArticleId>> {
let rows = sqlx::query(
"SELECT DISTINCT ia.article_id FROM issue_articles ia
JOIN articles a ON a.id = ia.article_id
WHERE a.first_seen >= ?
ORDER BY ia.article_id",
)
.bind(fmt_ts(since))
.fetch_all(&self.pool)
.await?;
Ok(rows
.iter()
.map(|row| row.get::<i64, _>("article_id"))
.collect())
}
/// Every article id first seen at or after `since` (`features backfill --all`).
pub async fn article_ids_since(&self, since: Timestamp) -> Result<Vec<ArticleId>> {
let rows = sqlx::query("SELECT id FROM articles WHERE first_seen >= ? ORDER BY id")
.bind(fmt_ts(since))
.fetch_all(&self.pool)
.await?;
Ok(rows.iter().map(|row| row.get::<i64, _>("id")).collect())
}
/// Articles the LLM scored below `threshold` within the last `days` (§3.5).
pub async fn recently_low_scored_ids(
&self,