Curation v2 step 5: deep assessment, utility, diversified shortlist
assess.rs replaces score.rs (DEEP_INSTRUCTIONS, representative sample with [BEGINNING]/[MIDDLE]/[END], facets, cached deep rows with --rescore bypass), rank.rs adds the utility blend over present signals with gate ramps and the leader-clustered shortlist (cap 2 → 3 → uncapped, protected top-N, exploration reserve), editor.rs replaces select.rs with the §13 rendering and utility-ordered fallbacks. ScoredArticle is gone; Candidate is the only flow type. deep_batch_size replaces score_batch_size. Started by Codex (cut off by its usage limit mid-verification) and finished by a Claude agent from docs/plans/curation-v2-briefs/step5.md; reviewed against plan §12–§13. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1rCLQeKBgnBo3oTgHuTMe
This commit is contained in:
+33
-6
@@ -137,8 +137,8 @@ pub struct DeepseekConfig {
|
||||
pub model: String,
|
||||
/// Supply via `DAILY_EPUB_DEEPSEEK__API_KEY`.
|
||||
pub api_key: Option<String>,
|
||||
/// Articles per stage-A scoring request (§3.6).
|
||||
pub score_batch_size: usize,
|
||||
/// Articles per deep-assessment request (§12.1).
|
||||
pub deep_batch_size: usize,
|
||||
/// Articles per first-pass triage request (§10).
|
||||
pub triage_batch_size: usize,
|
||||
pub max_concurrent_requests: usize,
|
||||
@@ -158,7 +158,7 @@ impl Default for DeepseekConfig {
|
||||
base_url: "https://api.deepseek.com/v1".into(),
|
||||
model: "deepseek-v4-flash".into(),
|
||||
api_key: None,
|
||||
score_batch_size: 12,
|
||||
deep_batch_size: 8,
|
||||
triage_batch_size: 25,
|
||||
max_concurrent_requests: 4,
|
||||
score_temperature: 0.3,
|
||||
@@ -602,6 +602,12 @@ impl Config {
|
||||
/// Load config for the CLI: explicit `--config` path, else `./config.toml`
|
||||
/// when it exists, then `DAILY_EPUB_*` env overrides (§3.14).
|
||||
pub fn load(explicit: Option<&Path>) -> Result<Self, ConfigError> {
|
||||
if std::env::var_os("DAILY_EPUB_DEEPSEEK__SCORE_BATCH_SIZE").is_some() {
|
||||
return Err(ConfigError::Invalid(
|
||||
"DAILY_EPUB_DEEPSEEK__SCORE_BATCH_SIZE was removed; use DAILY_EPUB_DEEPSEEK__DEEP_BATCH_SIZE"
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
let (path, require) = match explicit {
|
||||
Some(p) => (Some(p.to_path_buf()), true),
|
||||
None => (Some(PathBuf::from(DEFAULT_CONFIG_FILE)), false),
|
||||
@@ -621,6 +627,17 @@ impl Config {
|
||||
"prefilter_keep was removed; use curation.ranking.deep_keep".into(),
|
||||
));
|
||||
}
|
||||
if raw.lines().any(|line| {
|
||||
let line = line.trim_start();
|
||||
!line.starts_with('#')
|
||||
&& line
|
||||
.strip_prefix("score_batch_size")
|
||||
.is_some_and(|tail| tail.trim_start().starts_with('='))
|
||||
}) {
|
||||
return Err(ConfigError::Invalid(
|
||||
"deepseek.score_batch_size was removed; use deepseek.deep_batch_size".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
let mut config: Config = Self::figment(path.as_deref(), require)?.extract()?;
|
||||
// §1 tells the operator to set `DAILY_EPUB_SECRET`; §3.14 calls the key
|
||||
@@ -649,9 +666,9 @@ impl Config {
|
||||
"curation.max_article_count must be >= target_article_count".into(),
|
||||
));
|
||||
}
|
||||
if self.deepseek.score_batch_size == 0 {
|
||||
if self.deepseek.deep_batch_size == 0 {
|
||||
return Err(ConfigError::Invalid(
|
||||
"deepseek.score_batch_size must be >= 1".into(),
|
||||
"deepseek.deep_batch_size must be >= 1".into(),
|
||||
));
|
||||
}
|
||||
if self.deepseek.triage_batch_size == 0 {
|
||||
@@ -779,6 +796,7 @@ mod tests {
|
||||
assert!(c.world_briefing);
|
||||
assert_eq!(c.deepseek.model, "deepseek-v4-flash");
|
||||
assert_eq!(c.deepseek.triage_batch_size, 25);
|
||||
assert_eq!(c.deepseek.deep_batch_size, 8);
|
||||
assert_eq!(c.curation.recent_rejection_days, 7);
|
||||
assert_eq!(c.curation.recent_rejection_floor, 3.0);
|
||||
assert_eq!(c.profile_path, PathBuf::from("data/profile.md"));
|
||||
@@ -878,6 +896,15 @@ mod tests {
|
||||
assert!(message.contains("curation.ranking.deep_keep"), "{message}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removed_score_batch_size_names_deep_batch_size() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("config.toml");
|
||||
std::fs::write(&path, "[deepseek]\nscore_batch_size = 12\n").unwrap();
|
||||
let error = Config::load(Some(&path)).expect_err("stale key must fail");
|
||||
assert!(error.to_string().contains("deep_batch_size"), "{error}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shipped_example_config_parses() {
|
||||
let example = Path::new(env!("CARGO_MANIFEST_DIR")).join("config.example.toml");
|
||||
@@ -918,7 +945,7 @@ mod tests {
|
||||
c.anthropic.max_concurrent_requests = 0;
|
||||
assert!(c.validate().is_err());
|
||||
let mut c = Config::default();
|
||||
c.deepseek.score_batch_size = 0;
|
||||
c.deepseek.deep_batch_size = 0;
|
||||
assert!(c.validate().is_err());
|
||||
let mut c = Config::default();
|
||||
c.deepseek.triage_batch_size = 0;
|
||||
|
||||
Reference in New Issue
Block a user