lemma-engine 0.8.14

A language that means business.
Documentation
use lemma::parsing::ast::DateTimeValue;
use lemma::{Engine, LiteralValue, Target};
use std::collections::HashMap;

#[test]
fn discount_multiple_paths_to_same_value() {
    // Use type annotations to make data free variables for inversion
    let code = r#"
        spec shop
        data discount_code: text
        data member_level: text

        rule discount: 0.20
          unless discount_code is "SAVE30" then 0.30
          unless member_level is "platinum" then 0.30
    "#;

    let mut engine = Engine::new();
    engine
        .load(
            code,
            lemma::SourceType::Path(std::sync::Arc::new(std::path::PathBuf::from("test"))),
        )
        .unwrap();

    // Query: "What gives me 30% discount?"
    let now = DateTimeValue::now();
    let solutions = engine
        .invert(
            "shop",
            Some(&now),
            "discount",
            Target::value(LiteralValue::number_from_decimal(
                rust_decimal::Decimal::new(3, 1),
            )),
            HashMap::new(),
        )
        .expect("invert should succeed");

    // Should have solutions
    assert!(!solutions.is_empty(), "Expected at least one solution");

    // Should track discount_code and member_level in domains
    let all_keys: Vec<String> = solutions
        .domains
        .iter()
        .flat_map(|d| d.keys())
        .map(|k| k.to_string())
        .collect();

    assert!(
        all_keys.iter().any(|k| k.contains("discount_code")),
        "discount_code should be in domains: {:?}",
        all_keys
    );
    assert!(
        all_keys.iter().any(|k| k.contains("member_level")),
        "member_level should be in domains: {:?}",
        all_keys
    );
}