diff --git a/packages/elf-service/src/knowledge/watch.rs b/packages/elf-service/src/knowledge/watch.rs index 5fe47a59..099cd071 100644 --- a/packages/elf-service/src/knowledge/watch.rs +++ b/packages/elf-service/src/knowledge/watch.rs @@ -2,6 +2,7 @@ mod candidates; mod inputs; mod outcomes; mod outputs; +mod states; mod summary; pub(super) use self::{ @@ -14,10 +15,8 @@ pub(super) use self::{ rebuild_request_from_page, }, outcomes::{blocked_watch_rebuild, successful_watch_rebuild}, - outputs::{ - blocked_outputs, blocked_section_states, candidate_reasons_by_section, rebuild_outputs, - successful_rebuild_state, successful_section_states, - }, + outputs::{blocked_outputs, candidate_reasons_by_section, rebuild_outputs}, + states::{blocked_section_states, successful_rebuild_state, successful_section_states}, summary::{page_operator_summary, watch_operator_summary, watch_rebuild_summary}, }; diff --git a/packages/elf-service/src/knowledge/watch/outputs.rs b/packages/elf-service/src/knowledge/watch/outputs.rs index 371cca0b..16ff84e9 100644 --- a/packages/elf-service/src/knowledge/watch/outputs.rs +++ b/packages/elf-service/src/knowledge/watch/outputs.rs @@ -1,7 +1,7 @@ use crate::knowledge::watch::{ BTreeMap, BTreeSet, KnowledgePageChangedSource, KnowledgePageRebuildOutput, - KnowledgePageSection, KnowledgePageSectionRebuildState, KnowledgePageSectionResponse, - KnowledgePageSourceRef, KnowledgeSourceKind, LintDraft, Uuid, Value, serde_json, + KnowledgePageSection, KnowledgePageSourceRef, KnowledgeSourceKind, LintDraft, Uuid, Value, + serde_json, }; pub(in crate::knowledge) fn rebuild_outputs( @@ -159,107 +159,6 @@ pub(in crate::knowledge) fn conflict_outputs( .collect() } -pub(in crate::knowledge) fn successful_section_states( - before_sections: &[KnowledgePageSection], - rebuilt_sections: &[KnowledgePageSectionResponse], - outputs: &[KnowledgePageRebuildOutput], -) -> Vec { - let output_map = outputs_by_section(outputs); - let before_by_key = before_sections - .iter() - .map(|section| (section.section_key.as_str(), section)) - .collect::>(); - - rebuilt_sections - .iter() - .map(|section| { - let output_types = - output_map.get(section.section_key.as_str()).cloned().unwrap_or_default(); - let lint_finding_types = lint_finding_types_for_outputs(&output_types); - let state = section_state( - before_by_key.get(section.section_key.as_str()).copied(), - section, - &output_types, - ); - - KnowledgePageSectionRebuildState { - section_key: section.section_key.clone(), - heading: section.heading.clone(), - state, - output_types, - lint_finding_types, - } - }) - .collect() -} - -pub(in crate::knowledge) fn blocked_section_states( - sections: &[KnowledgePageSection], - outputs: &[KnowledgePageRebuildOutput], -) -> Vec { - let output_map = outputs_by_section(outputs); - - sections - .iter() - .map(|section| { - let output_types = - output_map.get(section.section_key.as_str()).cloned().unwrap_or_default(); - let lint_finding_types = lint_finding_types_for_outputs(&output_types); - let state = if output_types.iter().any(|kind| kind == "missing_citation") { - "blocked" - } else if output_types.iter().any(|kind| kind == "stale_section") { - "stale" - } else { - "blocked" - }; - - KnowledgePageSectionRebuildState { - section_key: section.section_key.clone(), - heading: section.heading.clone(), - state: state.to_string(), - output_types, - lint_finding_types, - } - }) - .collect() -} - -pub(in crate::knowledge) fn section_state( - before: Option<&KnowledgePageSection>, - after: &KnowledgePageSectionResponse, - output_types: &[String], -) -> String { - if output_types.iter().any(|kind| kind == "missing_citation") { - return "blocked".to_string(); - } - if before.is_some_and(|section| section.content_hash != after.content_hash) - || output_types.iter().any(|kind| kind == "changed_claim" || kind == "conflict") - { - return "changed".to_string(); - } - - if output_types.iter().any(|kind| kind == "stale_section") { - return "stale".to_string(); - } - - "unchanged".to_string() -} - -pub(in crate::knowledge) fn successful_rebuild_state( - diff: Option<&Value>, - outputs: &[KnowledgePageRebuildOutput], -) -> String { - if diff_content_changed(diff) { - return "changed".to_string(); - } - - if outputs.iter().any(|output| output.output_type == "stale_section") { - return "stale".to_string(); - } - - "unchanged".to_string() -} - pub(in crate::knowledge) fn section_lookup( sections: &[KnowledgePageSection], ) -> BTreeMap { @@ -276,13 +175,6 @@ pub(in crate::knowledge) fn diff_section_keys(diff: Option<&Value>, key: &str) - .unwrap_or_default() } -pub(in crate::knowledge) fn diff_content_changed(diff: Option<&Value>) -> bool { - diff.and_then(|value| value.get("content_changed")).and_then(Value::as_bool).unwrap_or(false) - || !diff_section_keys(diff, "added_section_keys").is_empty() - || !diff_section_keys(diff, "removed_section_keys").is_empty() - || !diff_section_keys(diff, "changed_section_keys").is_empty() -} - pub(in crate::knowledge) fn changed_source_set( changed_sources: &[KnowledgePageChangedSource], ) -> BTreeSet<(String, Uuid)> { @@ -303,42 +195,6 @@ pub(in crate::knowledge) fn output_section_keys( .collect() } -pub(in crate::knowledge) fn outputs_by_section( - outputs: &[KnowledgePageRebuildOutput], -) -> BTreeMap<&str, Vec> { - let mut map = BTreeMap::<&str, Vec>::new(); - - for output in outputs { - let Some(section_key) = output.section_key.as_deref() else { - continue; - }; - - map.entry(section_key).or_default().push(output.output_type.clone()); - } - for values in map.values_mut() { - values.sort(); - values.dedup(); - } - - map -} - -pub(in crate::knowledge) fn lint_finding_types_for_outputs(output_types: &[String]) -> Vec { - let mut out = output_types - .iter() - .filter_map(|output_type| match output_type.as_str() { - "stale_section" => Some("stale_source_ref".to_string()), - "missing_citation" => Some("missing_citation".to_string()), - _ => None, - }) - .collect::>(); - - out.sort(); - out.dedup(); - - out -} - pub(in crate::knowledge) fn candidate_reasons_by_section( outputs: &[KnowledgePageRebuildOutput], ) -> BTreeMap<&str, String> { diff --git a/packages/elf-service/src/knowledge/watch/states.rs b/packages/elf-service/src/knowledge/watch/states.rs new file mode 100644 index 00000000..75f1e2aa --- /dev/null +++ b/packages/elf-service/src/knowledge/watch/states.rs @@ -0,0 +1,146 @@ +use crate::knowledge::watch::{ + BTreeMap, KnowledgePageRebuildOutput, KnowledgePageSection, KnowledgePageSectionRebuildState, + KnowledgePageSectionResponse, Value, outputs, +}; + +pub(in crate::knowledge) fn successful_section_states( + before_sections: &[KnowledgePageSection], + rebuilt_sections: &[KnowledgePageSectionResponse], + outputs: &[KnowledgePageRebuildOutput], +) -> Vec { + let output_map = outputs_by_section(outputs); + let before_by_key = before_sections + .iter() + .map(|section| (section.section_key.as_str(), section)) + .collect::>(); + + rebuilt_sections + .iter() + .map(|section| { + let output_types = + output_map.get(section.section_key.as_str()).cloned().unwrap_or_default(); + let lint_finding_types = lint_finding_types_for_outputs(&output_types); + let state = section_state( + before_by_key.get(section.section_key.as_str()).copied(), + section, + &output_types, + ); + + KnowledgePageSectionRebuildState { + section_key: section.section_key.clone(), + heading: section.heading.clone(), + state, + output_types, + lint_finding_types, + } + }) + .collect() +} + +pub(in crate::knowledge) fn blocked_section_states( + sections: &[KnowledgePageSection], + outputs: &[KnowledgePageRebuildOutput], +) -> Vec { + let output_map = outputs_by_section(outputs); + + sections + .iter() + .map(|section| { + let output_types = + output_map.get(section.section_key.as_str()).cloned().unwrap_or_default(); + let lint_finding_types = lint_finding_types_for_outputs(&output_types); + let state = if output_types.iter().any(|kind| kind == "missing_citation") { + "blocked" + } else if output_types.iter().any(|kind| kind == "stale_section") { + "stale" + } else { + "blocked" + }; + + KnowledgePageSectionRebuildState { + section_key: section.section_key.clone(), + heading: section.heading.clone(), + state: state.to_string(), + output_types, + lint_finding_types, + } + }) + .collect() +} + +pub(in crate::knowledge) fn successful_rebuild_state( + diff: Option<&Value>, + outputs: &[KnowledgePageRebuildOutput], +) -> String { + if diff_content_changed(diff) { + return "changed".to_string(); + } + + if outputs.iter().any(|output| output.output_type == "stale_section") { + return "stale".to_string(); + } + + "unchanged".to_string() +} + +fn section_state( + before: Option<&KnowledgePageSection>, + after: &KnowledgePageSectionResponse, + output_types: &[String], +) -> String { + if output_types.iter().any(|kind| kind == "missing_citation") { + return "blocked".to_string(); + } + if before.is_some_and(|section| section.content_hash != after.content_hash) + || output_types.iter().any(|kind| kind == "changed_claim" || kind == "conflict") + { + return "changed".to_string(); + } + + if output_types.iter().any(|kind| kind == "stale_section") { + return "stale".to_string(); + } + + "unchanged".to_string() +} + +fn diff_content_changed(diff: Option<&Value>) -> bool { + diff.and_then(|value| value.get("content_changed")).and_then(Value::as_bool).unwrap_or(false) + || !outputs::diff_section_keys(diff, "added_section_keys").is_empty() + || !outputs::diff_section_keys(diff, "removed_section_keys").is_empty() + || !outputs::diff_section_keys(diff, "changed_section_keys").is_empty() +} + +fn outputs_by_section(outputs: &[KnowledgePageRebuildOutput]) -> BTreeMap<&str, Vec> { + let mut map = BTreeMap::<&str, Vec>::new(); + + for output in outputs { + let Some(section_key) = output.section_key.as_deref() else { + continue; + }; + + map.entry(section_key).or_default().push(output.output_type.clone()); + } + for values in map.values_mut() { + values.sort(); + values.dedup(); + } + + map +} + +fn lint_finding_types_for_outputs(output_types: &[String]) -> Vec { + let mut out = output_types + .iter() + .filter_map(|output_type| match output_type.as_str() { + "stale_section" => Some("stale_source_ref".to_string()), + "missing_citation" => Some("missing_citation".to_string()), + _ => None, + }) + .collect::>(); + + out.sort(); + out.dedup(); + + out +}