RecordedNodeScorer.java

package com.taxonomy.catalog.service;

import com.taxonomy.catalog.model.TaxonomyNode;
import com.taxonomy.dto.SavedAnalysis;

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

/**
 * Scores nodes by looking up pre-computed values from a {@link SavedAnalysis}.
 *
 * <p>This scorer reads scores from a previously recorded analysis (e.g. a
 * mock-score JSON file or an LLM recording export). Nodes not found in the
 * recording receive a score of zero.
 *
 * <p>When combined with {@link IndependentScoring}, this faithfully replays
 * the recorded scores. When combined with {@link BudgetDistribution}, the
 * recorded scores are used as proportional weights and redistributed so that
 * children sum to the parent — useful for regenerating normalised mock data
 * from an existing analysis.
 */
public final class RecordedNodeScorer implements NodeScorer {

    private final Map<String, Integer> recordedScores;

    /**
     * Creates a scorer backed by the given recorded analysis.
     *
     * @param analysis saved analysis with pre-computed scores
     */
    public RecordedNodeScorer(SavedAnalysis analysis) {
        this.recordedScores = analysis.getScores() != null
                ? Map.copyOf(analysis.getScores())
                : Map.of();
    }

    /**
     * Creates a scorer backed by an explicit scores map (defensive copy).
     *
     * @param scores pre-computed node code → score map
     */
    public RecordedNodeScorer(Map<String, Integer> scores) {
        this.recordedScores = scores != null ? Map.copyOf(scores) : Map.of();
    }

    @Override
    public Map<String, Integer> score(String requirementText,
                                      List<TaxonomyNode> nodes,
                                      int parentScore) {
        Map<String, Integer> result = new LinkedHashMap<>();
        for (TaxonomyNode node : nodes) {
            result.put(node.getCode(), recordedScores.getOrDefault(node.getCode(), 0));
        }
        return result;
    }
}