Skip to main content

encounter_argumentation/
value_argument.rs

1//! Scheme-backed value argument resolution.
2//!
3//! Resolves disputes over Schwartz values using the "Argument from Values"
4//! Walton argumentation scheme evaluated through ASPIC+ and Dung preferred
5//! extension semantics.  Falls back to the conviction-gap formula when the
6//! scheme is unavailable or produces an undecided outcome.
7
8use crate::resolver::ArgumentOutcome;
9use argumentation_schemes::CatalogRegistry;
10use encounter::value_argument::ValueArgumentResult;
11
12/// Resolve a value argument using the "Argument from Values" Walton scheme.
13///
14/// Builds an ASPIC+ structured system where:
15/// - The attacker instantiates the scheme with `action = "uphold_<value>"`,
16///   `value = value_at_stake`, and `agent = attacker`.
17/// - The defender's counter-argument is added as a contrary to the attacker's
18///   conclusion.
19/// - Preference ordering is determined by conviction levels (higher conviction
20///   wins the preference ordering).
21///
22/// If the scheme is absent from `registry` or if extension semantics yield an
23/// undecided result, the function falls back to the simple conviction-gap
24/// formula: the character with the higher conviction wins; ties favour the
25/// attacker.
26///
27/// # Returns
28///
29/// A [`ValueArgumentResult`] containing the winner, loser, the value at stake,
30/// how much the loser's value shifts, and the winner's small self-reinforcement.
31pub fn scheme_value_argument(
32    attacker: &str,
33    defender: &str,
34    value_at_stake: &str,
35    attacker_conviction: f64,
36    defender_conviction: f64,
37    defender_openness: f64,
38    registry: &CatalogRegistry,
39) -> ValueArgumentResult {
40    let attacker_wins_by_conviction = attacker_conviction >= defender_conviction;
41
42    // Try scheme-based resolution.
43    let scheme_winner = registry.by_key("argument_from_values").and_then(|scheme| {
44        use argumentation::aspic::StructuredSystem;
45        use argumentation_schemes::aspic::add_scheme_to_system;
46
47        let attacker_bindings = [
48            ("action".into(), format!("uphold_{}", value_at_stake)),
49            ("value".into(), value_at_stake.into()),
50            ("agent".into(), attacker.into()),
51        ]
52        .into_iter()
53        .collect();
54
55        let attacker_instance = scheme.instantiate(&attacker_bindings).ok()?;
56
57        let mut system = StructuredSystem::new();
58        let attacker_rule = add_scheme_to_system(&attacker_instance, &mut system);
59
60        // Defender's counter: assert the contrary of attacker's conclusion.
61        let counter_literal = attacker_instance.conclusion.contrary();
62        system.add_ordinary(counter_literal.clone());
63        let defender_rule = system.add_defeasible_rule(
64            vec![counter_literal],
65            attacker_instance.conclusion.contrary(),
66        );
67
68        // Set preference by conviction level.
69        if attacker_conviction > defender_conviction {
70            let _ = system.prefer_rule(attacker_rule, defender_rule);
71        } else if defender_conviction > attacker_conviction {
72            let _ = system.prefer_rule(defender_rule, attacker_rule);
73        }
74
75        let built = system.build_framework().ok()?;
76        let extensions = built.framework.preferred_extensions().ok()?;
77        if extensions.is_empty() {
78            return None;
79        }
80
81        let ext = &extensions[0];
82        let conclusions = built.conclusions_in(ext);
83        let attacker_survives = conclusions.contains(&attacker_instance.conclusion);
84
85        if attacker_survives {
86            Some(ArgumentOutcome::ProposerWins { survival_rate: 1.0 })
87        } else {
88            Some(ArgumentOutcome::ResponderWins { defeat_rate: 1.0 })
89        }
90    });
91
92    // Determine winner using scheme result, falling back to conviction ordering.
93    let attacker_wins = match scheme_winner {
94        Some(ArgumentOutcome::ProposerWins { .. }) => true,
95        Some(ArgumentOutcome::ResponderWins { .. }) => false,
96        _ => attacker_wins_by_conviction,
97    };
98
99    let (winner, loser) = if attacker_wins {
100        (attacker.to_string(), defender.to_string())
101    } else {
102        (defender.to_string(), attacker.to_string())
103    };
104
105    let conviction_gap = (attacker_conviction - defender_conviction).abs();
106    let loser_value_shift = (conviction_gap * defender_openness).clamp(0.0, 1.0);
107    let winner_value_shift = (conviction_gap * 0.1).clamp(0.0, 0.1);
108
109    ValueArgumentResult {
110        winner,
111        loser,
112        value_at_stake: value_at_stake.into(),
113        loser_value_shift,
114        winner_value_shift,
115    }
116}