forge_engine/lab/
archive.rs1use crate::config::ForgeConfig;
2use crate::error::ForgeResult;
3use crate::lab::evaluate::ScoreVector;
4use crate::runtime::novelty;
5use crate::runtime::patch::types::StructuredPatch;
6use crate::store::ForgeStore;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum ArchiveUpdate {
11 Inserted,
12 Replaced,
13 BelowGate,
14 NoChange,
15}
16
17pub fn compute_cell_key(
19 score_vector: &ScoreVector,
20 patch: &StructuredPatch,
21 config: &ForgeConfig,
22) -> String {
23 let novelty_bin = compute_novelty_bin(score_vector.novelty, config);
24 let stability_bin = compute_stability_bin(score_vector.stability, config);
25 let tags = novelty::extract_strategy_tags(patch);
26 let approach_family = novelty::determine_approach_family(&tags);
27
28 format!("{novelty_bin}:{stability_bin}:{approach_family}")
29}
30
31fn compute_novelty_bin(novelty: f64, config: &ForgeConfig) -> String {
33 for bin in &config.lab.archive.novelty_bins {
34 if novelty >= bin.lo && novelty < bin.hi {
35 return bin.name.clone();
36 }
37 }
38 if let Some(last) = config.lab.archive.novelty_bins.last() {
40 if (novelty - last.hi).abs() < f64::EPSILON {
41 return last.name.clone();
42 }
43 }
44 "low".to_string()
45}
46
47fn compute_stability_bin(_stability: f64, _config: &ForgeConfig) -> &'static str {
49 "stable"
51}
52
53pub fn compute_score_summary(scores: &ScoreVector) -> f64 {
57 0.70 * scores.correctness + 0.20 * scores.novelty + 0.10 * scores.stability
60}
61
62pub fn archive_insert(
64 store: &ForgeStore,
65 candidate_id: &str,
66 scores: &ScoreVector,
67 patch: &StructuredPatch,
68 config: &ForgeConfig,
69 cea_fingerprint: Option<&str>,
70) -> ForgeResult<ArchiveUpdate> {
71 if scores.correctness < config.lab.archive.correctness_gate {
73 return Ok(ArchiveUpdate::BelowGate);
74 }
75
76 let cell_key = compute_cell_key(scores, patch, config);
77 let score_summary = compute_score_summary(scores);
78
79 let existing = store.get_archive_cell(&cell_key)?;
81
82 match existing {
83 Some(cell) => {
84 let existing_summary: f64 =
85 serde_json::from_str(&cell.score_summary_json).unwrap_or(0.0);
86
87 if score_summary > existing_summary {
88 let summary_json = serde_json::to_string(&score_summary)?;
89 store.upsert_archive_cell(
90 &cell_key,
91 candidate_id,
92 &summary_json,
93 cea_fingerprint,
94 )?;
95 Ok(ArchiveUpdate::Replaced)
96 } else {
97 Ok(ArchiveUpdate::NoChange)
98 }
99 }
100 None => {
101 let summary_json = serde_json::to_string(&score_summary)?;
102 store.upsert_archive_cell(&cell_key, candidate_id, &summary_json, cea_fingerprint)?;
103 Ok(ArchiveUpdate::Inserted)
104 }
105 }
106}