DeterministicNodeScorer.java

package com.taxonomy.catalog.service;

import com.taxonomy.catalog.model.TaxonomyNode;

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

/**
 * Scores nodes using deterministic weights derived from their code hashes.
 *
 * <p>This scorer ignores the requirement text and produces stable,
 * reproducible scores that vary across sibling nodes but are consistent
 * across runs. Each node's weight is {@code Math.floorMod(code.hashCode(), 100) + 1}.
 *
 * <p>Intended for offline mock-score generation (see {@code MockScoreGeneratorIT}),
 * not for live analysis.  When combined with {@link BudgetDistribution}, the
 * resulting scores guarantee that children sum exactly to the parent.
 */
public final class DeterministicNodeScorer implements NodeScorer {

    /** Singleton instance. */
    public static final DeterministicNodeScorer INSTANCE = new DeterministicNodeScorer();

    private DeterministicNodeScorer() {}

    @Override
    public Map<String, Integer> score(String requirementText,
                                      List<TaxonomyNode> nodes,
                                      int parentScore) {
        Map<String, Integer> scores = new LinkedHashMap<>();
        for (TaxonomyNode node : nodes) {
            // Weight range: 1–100, deterministic per code (floorMod avoids negative from Integer.MIN_VALUE)
            int weight = Math.floorMod(node.getCode().hashCode(), 100) + 1;
            scores.put(node.getCode(), weight);
        }
        return scores;
    }
}