EnrichedImpactService.java

package com.taxonomy.architecture.service;

import com.taxonomy.dto.*;
import com.taxonomy.relations.model.RequirementCoverage;
import com.taxonomy.relations.repository.RequirementCoverageRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;
import com.taxonomy.dto.ChangeImpactView;
import com.taxonomy.dto.EnrichedChangeImpactView;
import com.taxonomy.dto.EnrichedImpactElement;
import com.taxonomy.dto.ImpactElement;

/**
 * Enriches the existing failure/change impact analysis with requirement
 * coverage data. For each affected element, the service looks up which
 * requirements cover it and computes an aggregated risk score.
 */
@Service
public class EnrichedImpactService {

    private static final Logger log = LoggerFactory.getLogger(EnrichedImpactService.class);

    private final ArchitectureGraphQueryService graphQueryService;
    private final RequirementCoverageRepository coverageRepository;

    public EnrichedImpactService(ArchitectureGraphQueryService graphQueryService,
                                 RequirementCoverageRepository coverageRepository) {
        this.graphQueryService = graphQueryService;
        this.coverageRepository = coverageRepository;
    }

    /**
     * Computes failure impact for the given node and enriches results with
     * requirement coverage correlation.
     *
     * @param nodeCode the node that fails or changes
     * @param maxHops  maximum graph hops (1–5, clamped)
     * @return enriched change impact view with requirement correlation
     */
    @Transactional(readOnly = true)
    public EnrichedChangeImpactView findEnrichedFailureImpact(String nodeCode, int maxHops) {
        // Delegate to the existing failure impact analysis
        ChangeImpactView baseView = graphQueryService.findFailureImpact(nodeCode, maxHops);

        EnrichedChangeImpactView enriched = new EnrichedChangeImpactView();
        enriched.setFailedNodeCode(baseView.getFailedNodeCode());
        enriched.setMaxHops(baseView.getMaxHops());
        enriched.setTraversedRelationships(baseView.getTraversedRelationships());
        enriched.setTotalRelationships(baseView.getTotalRelationships());
        enriched.getNotes().addAll(baseView.getNotes());

        // Enrich directly affected elements
        List<EnrichedImpactElement> enrichedDirect = enrichElements(baseView.getDirectlyAffected());
        enriched.setDirectlyAffected(enrichedDirect);

        // Enrich indirectly affected elements
        List<EnrichedImpactElement> enrichedIndirect = enrichElements(baseView.getIndirectlyAffected());
        enriched.setIndirectlyAffected(enrichedIndirect);

        enriched.setTotalAffected(enrichedDirect.size() + enrichedIndirect.size());

        // Collect all distinct affected requirements
        Set<String> allRequirements = new LinkedHashSet<>();
        for (EnrichedImpactElement el : enrichedDirect) {
            allRequirements.addAll(el.getCoveredByRequirements());
        }
        for (EnrichedImpactElement el : enrichedIndirect) {
            allRequirements.addAll(el.getCoveredByRequirements());
        }
        enriched.setAffectedRequirements(new ArrayList<>(allRequirements));

        // Risk score = sum of (requirement count × relevance) for all affected elements
        double risk = 0.0;
        for (EnrichedImpactElement el : enrichedDirect) {
            risk += el.getRequirementCount() * el.getRelevance();
        }
        for (EnrichedImpactElement el : enrichedIndirect) {
            risk += el.getRequirementCount() * el.getRelevance() * 0.5; // indirect = half weight
        }
        enriched.setRiskScore(Math.round(risk * 100.0) / 100.0);

        log.info("Enriched failure impact for {}: {} affected, {} requirements, risk={}",
                nodeCode, enriched.getTotalAffected(),
                enriched.getAffectedRequirements().size(), enriched.getRiskScore());

        return enriched;
    }

    private List<EnrichedImpactElement> enrichElements(List<ImpactElement> elements) {
        List<EnrichedImpactElement> result = new ArrayList<>();
        for (ImpactElement el : elements) {
            EnrichedImpactElement enriched = new EnrichedImpactElement();
            enriched.setNodeCode(el.getNodeCode());
            enriched.setTitle(el.getTitle());
            enriched.setTaxonomySheet(el.getTaxonomySheet());
            enriched.setRelevance(el.getRelevance());
            enriched.setHopDistance(el.getHopDistance());
            enriched.setIncludedBecause(el.getIncludedBecause());

            // Look up requirement coverage for this node
            List<RequirementCoverage> coverages = coverageRepository.findByNodeCode(el.getNodeCode());
            List<String> requirementIds = coverages.stream()
                    .map(RequirementCoverage::getRequirementId)
                    .distinct()
                    .collect(Collectors.toList());
            enriched.setCoveredByRequirements(requirementIds);
            enriched.setRequirementCount(requirementIds.size());

            result.add(enriched);
        }
        return result;
    }
}