ScoringTraceStep.java

package com.taxonomy.architecture.pipeline;

import com.taxonomy.architecture.service.ScoringTraceSelector;
import com.taxonomy.dto.NodeOrigin;
import com.taxonomy.dto.RequirementElementView;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.LinkedHashMap;

/**
 * Merges the scoring-trace metadata from {@link ScoringTraceSelector} into the
 * main element list.
 *
 * <p>For every anchor node the full path (root → intermediate → anchor) is
 * reconstructed and used to populate the {@code scoringPath} and refine the
 * {@code origin} on elements that are already present in the view.
 * Trace entries that are not yet in the element list are not added — they only
 * provide metadata enrichment for elements that survived propagation.
 *
 * <p><b>Safe extension point</b> — this step may be replaced or augmented to
 * change how scoring-path metadata is populated.
 */
@Service
public class ScoringTraceStep implements ArchitecturePipelineStep {

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

    private final ScoringTraceSelector scoringTraceSelector;

    public ScoringTraceStep(ScoringTraceSelector scoringTraceSelector) {
        this.scoringTraceSelector = scoringTraceSelector;
    }

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

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

    @Override
    public void apply(ArchitectureViewContext ctx) {
        List<RequirementElementView> traceEntries =
                scoringTraceSelector.buildTrace(ctx.getScores(), ctx.getAnchors());
        mergeTraceOrigins(ctx.getElements(), traceEntries);
    }

    /**
     * Updates {@code scoringPath} and {@code origin} in the main element list
     * using the scoring trace produced for the anchor nodes.
     */
    private static void mergeTraceOrigins(List<RequirementElementView> elements,
                                           List<RequirementElementView> traceEntries) {
        Map<String, RequirementElementView> traceByCode = new LinkedHashMap<>();
        for (RequirementElementView te : traceEntries) {
            traceByCode.put(te.getNodeCode(), te);
        }

        for (RequirementElementView el : elements) {
            RequirementElementView traceEntry = traceByCode.get(el.getNodeCode());
            if (traceEntry == null) continue;

            // Propagate scoring path if not already set
            if (el.getScoringPath() == null && traceEntry.getScoringPath() != null) {
                el.setScoringPath(traceEntry.getScoringPath());
            }

            // Upgrade origin from trace if the element does not already have a more
            // specific origin (ENRICHED_LEAF should not be overridden).
            if (el.getOrigin() == null || el.getOrigin() == NodeOrigin.PROPAGATED) {
                el.setOrigin(traceEntry.getOrigin());
            }
        }
    }
}