relay-knowledge 1.1.16

Graph-database-based knowledge graph project.
Documentation
use std::collections::{BTreeMap, BTreeSet};

use crate::domain::{CodeImportRecord, CodebaseViewSnapshot};

use super::{
    builder::{SectionRefs, ViewBuilder},
    rules::{architecture_layer, layer_confidence},
};

pub(super) fn derive_architecture_layers(
    builder: &mut ViewBuilder,
    snapshot: &CodebaseViewSnapshot,
) {
    let indexed_paths = snapshot
        .files
        .iter()
        .map(|file| file.path.as_str())
        .collect::<BTreeSet<_>>();
    let mut layer_candidates = BTreeMap::<String, (Vec<&str>, Vec<String>)>::new();
    for file in &snapshot.files {
        let layer = architecture_layer(&file.path);
        let evidence_id = builder.evidence(
            "file",
            &file.path,
            None,
            None,
            None,
            format!("{} file in {layer} layer", file.language_id),
        );
        let (files, evidence_ids) = layer_candidates.entry(layer.to_owned()).or_default();
        files.push(file.path.as_str());
        evidence_ids.push(evidence_id);
    }
    let mut ordered_layers = layer_candidates.into_iter().collect::<Vec<_>>();
    ordered_layers
        .sort_by_cached_key(|(layer, (files, _))| (std::cmp::Reverse(files.len()), layer.clone()));
    let mut layer_files = BTreeMap::<String, Vec<&str>>::new();
    let mut layer_evidence = BTreeMap::<String, Vec<String>>::new();
    if ordered_layers.len() > builder.limit {
        builder.mark_node_budget_truncated();
    }
    for (layer, (files, evidence_ids)) in ordered_layers.into_iter().take(builder.limit) {
        let Some(node_id) = builder.node(
            format!("layer:{layer}"),
            layer.clone(),
            "architecture_layer",
            None,
            layer_confidence(&layer),
            evidence_ids.first().cloned(),
        ) else {
            continue;
        };
        layer_files.insert(node_id.clone(), files);
        layer_evidence.insert(node_id, evidence_ids);
    }
    for import in &snapshot.imports {
        if let Some(target_path) = resolved_indexed_import_target(import, &indexed_paths) {
            let source = format!("layer:{}", architecture_layer(&import.path));
            let target = format!("layer:{}", architecture_layer(target_path));
            let evidence_id = builder.evidence(
                "import",
                &import.path,
                Some(import.module.clone()),
                Some(import.line_range.clone()),
                Some(import.resolution_state.clone()),
                "import edge between architecture layers",
            );
            let source_id = builder.node(
                source.clone(),
                source.trim_start_matches("layer:").to_owned(),
                "architecture_layer",
                None,
                0.74,
                Some(evidence_id.clone()),
            );
            let target_id = builder.node(
                target.clone(),
                target.trim_start_matches("layer:").to_owned(),
                "architecture_layer",
                None,
                0.74,
                Some(evidence_id.clone()),
            );
            if let (Some(source_id), Some(target_id)) = (source_id, target_id) {
                builder.edge(&source_id, &target_id, "imports", 0.72, Some(evidence_id));
            }
        }
    }
    for call in &snapshot.calls {
        if let Some(target_path) = call.callee_path.as_deref() {
            let source = format!("layer:{}", architecture_layer(&call.call.path));
            let target = format!("layer:{}", architecture_layer(target_path));
            let evidence_id = builder.evidence(
                "call",
                &call.call.path,
                call.call.caller_name.clone(),
                Some(call.call.line_range.clone()),
                Some(call.call.resolution_state.clone()),
                format!("call to {}", call.call.callee_name),
            );
            let source_id = builder.node(
                source.clone(),
                source.trim_start_matches("layer:").to_owned(),
                "architecture_layer",
                None,
                0.74,
                Some(evidence_id.clone()),
            );
            let target_id = builder.node(
                target.clone(),
                target.trim_start_matches("layer:").to_owned(),
                "architecture_layer",
                None,
                0.74,
                Some(evidence_id.clone()),
            );
            if let (Some(source_id), Some(target_id)) = (source_id, target_id) {
                builder.edge(&source_id, &target_id, "calls", 0.76, Some(evidence_id));
            }
        }
    }
    let mut ordered_layers = layer_files.into_iter().collect::<Vec<_>>();
    ordered_layers
        .sort_by(|left, right| right.1.len().cmp(&left.1.len()).then(left.0.cmp(&right.0)));
    for (node_id, files) in ordered_layers.into_iter().take(builder.limit) {
        let layer = node_id.trim_start_matches("layer:");
        let evidence_ids = layer_evidence.remove(&node_id).unwrap_or_default();
        builder.section(
            format!("section:{node_id}"),
            format!("{layer} layer"),
            format!(
                "{layer} contains {} indexed file(s) and is derived from path and graph boundary evidence.",
                files.len()
            ),
            layer_confidence(layer),
            SectionRefs {
                node_ids: vec![node_id],
                evidence_ids,
                ..SectionRefs::default()
            },
        );
    }
}

fn resolved_indexed_import_target<'a>(
    import: &'a CodeImportRecord,
    indexed_paths: &BTreeSet<&str>,
) -> Option<&'a str> {
    let target_path = import.target_hint.as_deref()?;
    (import.resolution_state == "resolved" && indexed_paths.contains(target_path))
        .then_some(target_path)
}