Skip to main content

encounter_argumentation/
scoring.rs

1//! Scheme-strength action scoring.
2//!
3//! [`SchemeActionScorer`] wraps an inner [`ActionScorer`] and boosts scores
4//! for actions that have scheme backing in the supplied [`ArgumentKnowledge`].
5//! The boost scales with the scheme's [`SchemeStrength`]:
6//!
7//! | Strength | Multiplier |
8//! |----------|-----------|
9//! | Strong   | 1.00      |
10//! | Moderate | 0.67      |
11//! | Weak     | 0.33      |
12//!
13//! The best (highest) boost across all argument positions for an action is
14//! applied; positions without a matching registry entry are skipped.
15
16use crate::knowledge::{ArgumentKnowledge, ArgumentPosition};
17use argumentation_schemes::CatalogRegistry;
18use argumentation_schemes::types::SchemeStrength;
19use encounter::affordance::CatalogEntry;
20use encounter::scoring::{ActionScorer, ScoredAffordance};
21
22/// An [`ActionScorer`] that boosts scores for scheme-backed actions.
23///
24/// Wraps an inner scorer and, for each scored affordance, looks up argument
25/// positions from [`ArgumentKnowledge`]. If any position maps to a registered
26/// scheme, the affordance score is increased by:
27///
28/// ```text
29/// max_boost * strength_multiplier * preference_weight
30/// ```
31///
32/// The highest boost across all positions wins. The returned list is sorted
33/// descending by final score.
34pub struct SchemeActionScorer<K, S> {
35    knowledge: K,
36    registry: CatalogRegistry,
37    inner: S,
38    max_boost: f64,
39}
40
41impl<K: ArgumentKnowledge, S> SchemeActionScorer<K, S> {
42    /// Create a new `SchemeActionScorer`.
43    ///
44    /// # Parameters
45    /// - `knowledge` – provides per-character argument positions.
46    /// - `registry` – the scheme catalog used to resolve scheme strengths.
47    /// - `inner` – the base scorer whose results are boosted.
48    /// - `max_boost` – the maximum additive score increase for a Strong scheme
49    ///   with `preference_weight = 1.0`.
50    pub fn new(knowledge: K, registry: CatalogRegistry, inner: S, max_boost: f64) -> Self {
51        Self {
52            knowledge,
53            registry,
54            inner,
55            max_boost,
56        }
57    }
58}
59
60impl<K, S, P> ActionScorer<P> for SchemeActionScorer<K, S>
61where
62    K: ArgumentKnowledge,
63    S: ActionScorer<P>,
64    P: Clone,
65{
66    /// Score available actions, boosting those with scheme backing.
67    ///
68    /// Delegates to the inner scorer, then for each result looks up argument
69    /// positions from the knowledge base and applies the best scheme boost.
70    /// Returns results sorted descending by final score.
71    fn score_actions(
72        &self,
73        actor: &str,
74        available: &[CatalogEntry<P>],
75        participants: &[String],
76    ) -> Vec<ScoredAffordance<P>> {
77        let mut scored = self.inner.score_actions(actor, available, participants);
78        for sa in &mut scored {
79            let positions =
80                self.knowledge
81                    .arguments_for_action(actor, &sa.entry.spec.name, &sa.bindings);
82            let boost = best_scheme_boost(&positions, &self.registry, self.max_boost);
83            sa.score += boost;
84        }
85        scored.sort_by(|a, b| {
86            b.score
87                .partial_cmp(&a.score)
88                .unwrap_or(std::cmp::Ordering::Equal)
89        });
90        scored
91    }
92}
93
94/// Returns the multiplier for a given scheme strength.
95fn strength_multiplier(strength: SchemeStrength) -> f64 {
96    match strength {
97        SchemeStrength::Strong => 1.0,
98        SchemeStrength::Moderate => 0.67,
99        SchemeStrength::Weak => 0.33,
100    }
101}
102
103/// Computes the best (highest) scheme boost across all argument positions.
104///
105/// Positions whose `scheme_key` does not match any entry in the registry are
106/// silently skipped.
107fn best_scheme_boost(
108    positions: &[ArgumentPosition],
109    registry: &CatalogRegistry,
110    max_boost: f64,
111) -> f64 {
112    positions
113        .iter()
114        .filter_map(|pos| {
115            let scheme = registry.by_key(&pos.scheme_key)?;
116            let mult = strength_multiplier(scheme.metadata.strength);
117            Some(max_boost * mult * pos.preference_weight)
118        })
119        .fold(0.0_f64, f64::max)
120}