ImpactSelectionStep.java

package com.taxonomy.architecture.pipeline;

import com.taxonomy.architecture.service.ArchitectureImpactSelector;
import com.taxonomy.dto.RequirementRelationshipView;
import org.springframework.stereotype.Service;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * Selects the most semantically valuable nodes for the final impact presentation
 * using a composite scoring formula.
 *
 * <p>Delegates the per-element scoring and selection to
 * {@link ArchitectureImpactSelector}, after first collecting the set of node codes
 * that participate in cross-category relationships (used as a bonus factor).
 *
 * <p><b>Safe extension point</b> — this step may be replaced or augmented to
 * change the impact-selection strategy, provided the {@code selectedForImpact}
 * flag is populated correctly on elements.
 */
@Service
public class ImpactSelectionStep implements ArchitecturePipelineStep {

    /** Stable pipeline step ID. */
    public static final String STEP_ID = "impact-selection";

    private final ArchitectureImpactSelector impactSelector;

    public ImpactSelectionStep(ArchitectureImpactSelector impactSelector) {
        this.impactSelector = impactSelector;
    }

    @Override
    public String id() { return STEP_ID; }

    @Override
    public int order() { return 1000; }

    @Override
    public void apply(ArchitectureViewContext ctx) {
        Set<String> crossCategoryCodes = collectCrossCategoryCodes(ctx.getRelationships());
        impactSelector.selectForImpact(ctx.getElements(), ctx.getScores(), crossCategoryCodes);
    }

    /**
     * Collects node codes that participate in cross-category relationships.
     */
    private static Set<String> collectCrossCategoryCodes(
            List<RequirementRelationshipView> relationships) {
        Set<String> codes = new LinkedHashSet<>();
        for (RequirementRelationshipView rel : relationships) {
            String srcRoot = rootOf(rel.getSourceCode());
            String tgtRoot = rootOf(rel.getTargetCode());
            if (srcRoot != null && tgtRoot != null && !srcRoot.equals(tgtRoot)) {
                codes.add(rel.getSourceCode());
                codes.add(rel.getTargetCode());
            }
        }
        return codes;
    }

    private static String rootOf(String code) {
        if (code == null) return null;
        int dash = code.indexOf('-');
        return dash >= 0 ? code.substring(0, dash) : code;
    }
}