omena-query 0.3.0

Omena query boundary over CME producer query fragments
Documentation
use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};

const SASS_UNSUPPORTED_LEDGER_PRODUCT_VIEW_JSON: &str =
    include_str!("../data/sass-unsupported-ledger.json");

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CanonicalSassUnsupportedLedgerReportV0 {
    schema_version: String,
    product: String,
    semantic_site_count: usize,
    ledger_semantic_site_count: usize,
    raw_pattern_hit_count: usize,
    non_semantic_pattern_hit_count: usize,
    linked_site_count: usize,
    named_gap_site_count: usize,
    linked_case_count: usize,
    ledger_metadata_valid: bool,
    all_semantic_sites_match_ledger: bool,
    all_sites_linked_or_named_gap: bool,
    all_linked_cases_match_reason_class: bool,
    all_linked_cases_are_imported_sound_bail_cases: bool,
    all_bail_site_ledger_checks_hold: bool,
    records: Vec<OmenaQuerySassUnsupportedLedgerRecordV0>,
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OmenaQuerySassUnsupportedLedgerRecordV0 {
    pub file: String,
    pub ordinal: usize,
    pub reason: String,
    pub current_line: Option<usize>,
    pub ledger_line_hint: usize,
    pub present_in_current_sources: bool,
    pub linked_fixture_ids: Vec<String>,
    pub gap: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OmenaQuerySassUnsupportedCountV0 {
    pub key: String,
    pub count: usize,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OmenaQuerySassUnsupportedLedgerViewV0 {
    pub schema_version: &'static str,
    pub product: &'static str,
    pub source_product: String,
    pub semantic_site_count: usize,
    pub ledger_semantic_site_count: usize,
    pub surface_record_count: usize,
    pub raw_pattern_hit_count: usize,
    pub non_semantic_pattern_hit_count: usize,
    pub linked_site_count: usize,
    pub named_gap_site_count: usize,
    pub linked_case_count: usize,
    pub ledger_metadata_valid: bool,
    pub all_semantic_sites_match_ledger: bool,
    pub all_sites_linked_or_named_gap: bool,
    pub all_linked_cases_match_reason_class: bool,
    pub all_linked_cases_are_imported_sound_bail_cases: bool,
    pub all_bail_site_ledger_checks_hold: bool,
    pub surface_matches_ledger: bool,
    pub summary_view_ready: bool,
    pub file_counts: Vec<OmenaQuerySassUnsupportedCountV0>,
    pub reason_counts: Vec<OmenaQuerySassUnsupportedCountV0>,
    pub records: Vec<OmenaQuerySassUnsupportedLedgerRecordV0>,
}

pub fn summarize_omena_query_sass_unsupported_ledger_view_v0()
-> Result<OmenaQuerySassUnsupportedLedgerViewV0, serde_json::Error> {
    let canonical = serde_json::from_str::<CanonicalSassUnsupportedLedgerReportV0>(
        SASS_UNSUPPORTED_LEDGER_PRODUCT_VIEW_JSON,
    )?;
    Ok(project_sass_unsupported_ledger_view(canonical))
}

fn project_sass_unsupported_ledger_view(
    canonical: CanonicalSassUnsupportedLedgerReportV0,
) -> OmenaQuerySassUnsupportedLedgerViewV0 {
    let file_counts = count_records_by(&canonical.records, |record| record.file.as_str());
    let reason_counts = count_records_by(&canonical.records, |record| record.reason.as_str());
    let surface_record_count = canonical.records.len();
    let surface_matches_ledger = canonical.all_semantic_sites_match_ledger
        && surface_record_count == canonical.semantic_site_count
        && surface_record_count == canonical.ledger_semantic_site_count;
    let summary_view_ready = canonical.all_bail_site_ledger_checks_hold && surface_matches_ledger;
    OmenaQuerySassUnsupportedLedgerViewV0 {
        schema_version: "0",
        product: "omena-query.sass-unsupported-ledger-view",
        source_product: canonical.product,
        semantic_site_count: canonical.semantic_site_count,
        ledger_semantic_site_count: canonical.ledger_semantic_site_count,
        surface_record_count,
        raw_pattern_hit_count: canonical.raw_pattern_hit_count,
        non_semantic_pattern_hit_count: canonical.non_semantic_pattern_hit_count,
        linked_site_count: canonical.linked_site_count,
        named_gap_site_count: canonical.named_gap_site_count,
        linked_case_count: canonical.linked_case_count,
        ledger_metadata_valid: canonical.ledger_metadata_valid,
        all_semantic_sites_match_ledger: canonical.all_semantic_sites_match_ledger,
        all_sites_linked_or_named_gap: canonical.all_sites_linked_or_named_gap,
        all_linked_cases_match_reason_class: canonical.all_linked_cases_match_reason_class,
        all_linked_cases_are_imported_sound_bail_cases: canonical
            .all_linked_cases_are_imported_sound_bail_cases,
        all_bail_site_ledger_checks_hold: canonical.all_bail_site_ledger_checks_hold,
        surface_matches_ledger,
        summary_view_ready,
        file_counts,
        reason_counts,
        records: canonical.records,
    }
}

fn count_records_by(
    records: &[OmenaQuerySassUnsupportedLedgerRecordV0],
    key: impl Fn(&OmenaQuerySassUnsupportedLedgerRecordV0) -> &str,
) -> Vec<OmenaQuerySassUnsupportedCountV0> {
    let mut counts = BTreeMap::<String, usize>::new();
    for record in records {
        *counts.entry(key(record).to_string()).or_default() += 1;
    }
    counts
        .into_iter()
        .map(|(key, count)| OmenaQuerySassUnsupportedCountV0 { key, count })
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn product_view_reuses_the_independent_ledger_projection() -> Result<(), serde_json::Error> {
        let view = summarize_omena_query_sass_unsupported_ledger_view_v0()?;
        assert_eq!(
            view.source_product,
            "omena-diff-test.sass-spec-bail-site-ledger"
        );
        assert_eq!(view.semantic_site_count, 33);
        assert_eq!(view.surface_record_count, 33);
        assert!(view.all_semantic_sites_match_ledger);
        assert!(view.surface_matches_ledger);
        assert!(view.summary_view_ready);
        assert_eq!(
            view.file_counts.iter().map(|row| row.count).sum::<usize>(),
            view.surface_record_count
        );
        assert_eq!(
            view.reason_counts
                .iter()
                .map(|row| row.count)
                .sum::<usize>(),
            view.surface_record_count
        );
        Ok(())
    }

    #[test]
    fn hiding_a_ledger_record_invalidates_the_surface_projection() -> Result<(), serde_json::Error>
    {
        let mut canonical = serde_json::from_str::<CanonicalSassUnsupportedLedgerReportV0>(
            SASS_UNSUPPORTED_LEDGER_PRODUCT_VIEW_JSON,
        )?;
        canonical.records.pop();
        let view = project_sass_unsupported_ledger_view(canonical);
        assert!(!view.surface_matches_ledger);
        assert!(!view.summary_view_ready);
        Ok(())
    }
}