Skip to content

Tutorial: Complex Conditions

This tutorial teaches you to encode context-sensitive halachic rules where the ruling depends on circumstances like lechatchila vs bedieved (ideal vs after-the-fact), hefsed (financial loss), or shaat hadchak (pressing circumstances).

Duration: 1.5 hours Difficulty: Intermediate Prerequisites: Tutorial 02: Handling Machloket

What You Will Learn

Halachic rulings often vary based on context:

  • Lechatchila (ab initio): What should be done ideally
  • Bedieved (post facto): What to do after the fact
  • Hefsed (financial loss): Leniencies when money is at stake
  • Shaat hadchak (pressing circumstances): Emergency leniencies

This tutorial covers encoding these context-sensitive modifiers in Mistaber.

Understanding Context-Dependent Halacha

The Lechatchila/Bedieved Distinction

Many halachic rulings operate on two levels:

Level Hebrew Meaning Example
Lechatchila לכתחילה Initially, ideally "One should wait 6 hours"
Bedieved בדיעבד After the fact "If one waited less, the food is permitted"

Example: Waiting between meat and dairy - Lechatchila: Wait 6 hours - Bedieved: If one accidentally ate dairy after 3 hours, no repeat eating required

The Hefsed Exception

Financial loss (hefsed) often triggers leniencies:

"In case of hefsed, one may rely on the lenient opinion."

This principle allows: - Relying on minority opinions - Applying bedieved rulings lechatchila - Combining multiple leniencies (tziruf)

The Shaat Hadchak Principle

Pressing circumstances (shaat hadchak) function similarly to hefsed:

"Shaat hadchak is treated like bedieved."

This covers: - Time pressure - Health needs (not sakana level) - Communal necessity

Encoding Context with Modifiers

The Context Predicate

Mistaber uses context predicates to model circumstances:

% === Context Types ===
context_type(lechatchila).   % Ideal circumstances
context_type(bedieved).      % After the fact
context_type(hefsed).        % Financial loss present
context_type(shaat_hadchak). % Pressing circumstances
context_type(normal).        % Standard situation

% === Context in a Scenario ===
% Scenarios declare which context applies
context(Scenario, lechatchila).  % This scenario is lechatchila
context(Scenario, hefsed).       % This scenario involves hefsed

Conditional Rules

Rules can depend on context:

% Lechatchila: Wait full 6 hours
asserts(mechaber, issur(achiila_dairy, Person, d_rabanan)) :-
    ate_meat(Person, MeatType),
    meat_requires_waiting(MeatType),
    not waiting_satisfied(Person),
    context(Scenario, lechatchila).

% Bedieved: If already eaten, food is permitted
asserts(mechaber, heter(food_status, Food)) :-
    accidental_consumption(Person, Food),
    context(Scenario, bedieved).

Example 1: Bitul Ratios in Different Contexts

Bitul (nullification) ratios often vary by context.

The Halacha

Standard bitul: 60:1 ratio required to nullify forbidden substance In hefsed: Some authorities permit relying on lower ratios in cases of financial loss

Encoding Standard Bitul

% ============================================================================
% BITUL SHISHIM: NULLIFICATION RULES
% ============================================================================

% === Base Bitul Ratio ===
rule(r_bitul_shishim).
makor(r_bitul_shishim, sa("yd:98:1")).
madrega(r_bitul_shishim, d_rabanan).
scope(r_bitul_shishim, mechaber).

% Standard bitul requires 60:1 ratio
asserts(mechaber, requires_bitul_ratio(60)) :-
    context(Scenario, lechatchila).

asserts(mechaber, requires_bitul_ratio(60)) :-
    context(Scenario, normal).

% Mixture is batel (nullified) when ratio is met
asserts(mechaber, batel(M)) :-
    mixture(M),
    issur_component(M, I),
    heter_component(M, H),
    ratio(H, I, R),
    requires_bitul_ratio(Required),
    R >= Required.

% If not batel, mixture is forbidden
asserts(mechaber, issur(achiila, M, d_rabanan)) :-
    mixture(M),
    issur_component(M, _),
    not batel(M).

Encoding Hefsed Leniency

% ============================================================================
% HEFSED LENIENCY FOR BITUL
% ============================================================================
% Some authorities permit relying on lower thresholds in hefsed cases.
% This is based on the principle of combining leniencies (tziruf).

rule(r_bitul_hefsed).
makor(r_bitul_hefsed, shach("yd:98:2")).
scope(r_bitul_hefsed, mechaber).

% In hefsed: Can rely on 48:1 per Rav Hai Gaon's opinion
% (Combined with other factors)
asserts(mechaber, batel(M)) :-
    mixture(M),
    issur_component(M, I),
    heter_component(M, H),
    ratio(H, I, R),
    R >= 48,
    context(Scenario, hefsed),
    has_supporting_opinion(M).  % Additional lenient factor required

% Document when hefsed leniency was applied
asserts(mechaber, leniency_applied(M, hefsed_bitul)) :-
    mixture(M),
    batel(M),
    context(Scenario, hefsed),
    ratio(_, _, R),
    R < 60.

Testing Context-Dependent Bitul

"""Tests for context-dependent bitul rules."""
import pytest
from mistaber import query


class TestBitulContexts:
    """Tests for bitul with different contexts."""

    @pytest.fixture
    def mixture_55_ratio(self):
        """Mixture with 55:1 ratio (between 48 and 60)."""
        return """
            mixture(m1).
            issur_component(m1, issur1).
            heter_component(m1, heter1).
            ratio(heter1, issur1, 55).
            has_supporting_opinion(m1).
        """

    @pytest.fixture
    def mixture_70_ratio(self):
        """Mixture with 70:1 ratio (above 60)."""
        return """
            mixture(m1).
            issur_component(m1, issur1).
            heter_component(m1, heter1).
            ratio(heter1, issur1, 70).
        """

    # === Lechatchila Tests ===

    def test_lechatchila_requires_60(self, mixture_55_ratio):
        """Lechatchila: 55:1 is NOT sufficient."""
        scenario = mixture_55_ratio + "context(scenario1, lechatchila)."
        result = query(scenario, world="mechaber")

        assert not result.holds("batel(m1)")
        assert result.holds("issur(achiila, m1, d_rabanan)")

    def test_lechatchila_60_sufficient(self, mixture_70_ratio):
        """Lechatchila: 70:1 (above 60) is sufficient."""
        scenario = mixture_70_ratio + "context(scenario1, lechatchila)."
        result = query(scenario, world="mechaber")

        assert result.holds("batel(m1)")
        assert not result.holds("issur(achiila, m1, d_rabanan)")

    # === Hefsed Tests ===

    def test_hefsed_permits_55(self, mixture_55_ratio):
        """Hefsed: 55:1 is sufficient with supporting opinion."""
        scenario = mixture_55_ratio + "context(scenario1, hefsed)."
        result = query(scenario, world="mechaber")

        assert result.holds("batel(m1)")
        assert result.holds("leniency_applied(m1, hefsed_bitul)")

    def test_hefsed_still_requires_48(self):
        """Hefsed: 40:1 is NOT sufficient (below 48 threshold)."""
        scenario = """
            mixture(m1).
            issur_component(m1, issur1).
            heter_component(m1, heter1).
            ratio(heter1, issur1, 40).
            has_supporting_opinion(m1).
            context(scenario1, hefsed).
        """
        result = query(scenario, world="mechaber")

        assert not result.holds("batel(m1)")

    def test_hefsed_requires_supporting_opinion(self, mixture_55_ratio):
        """Hefsed: 55:1 needs supporting opinion."""
        # Remove has_supporting_opinion
        scenario = """
            mixture(m1).
            issur_component(m1, issur1).
            heter_component(m1, heter1).
            ratio(heter1, issur1, 55).
            context(scenario1, hefsed).
        """
        result = query(scenario, world="mechaber")

        # Without supporting opinion, hefsed leniency doesn't apply
        assert not result.holds("batel(m1)")

Example 2: Lechatchila vs Bedieved Waiting

The waiting period between meat and dairy has lechatchila/bedieved distinctions.

The Halacha

  • Lechatchila: Wait full 6 hours after meat before eating dairy
  • Bedieved: If one mistakenly ate dairy before 6 hours, no additional action required
  • Shaat hadchak: Some permit shortened waiting in pressing circumstances

Encoding Lechatchila/Bedieved

% ============================================================================
% WAITING PERIOD: LECHATCHILA VS BEDIEVED
% ============================================================================

% === Lechatchila: Full waiting required ===
rule(r_waiting_lechatchila).
makor(r_waiting_lechatchila, sa("yd:89:1")).
scope(r_waiting_lechatchila, mechaber).

% Before eating dairy lechatchila: full wait required
asserts(mechaber, issur(achiila_dairy, Person, d_rabanan)) :-
    ate_meat(Person, MeatType),
    meat_requires_waiting(MeatType),
    not waiting_satisfied(Person),
    context(Scenario, lechatchila).

% === Bedieved: Already eaten ===
rule(r_waiting_bedieved).
makor(r_waiting_bedieved, shach("yd:89:6")).
scope(r_waiting_bedieved, mechaber).

% After accidental consumption: food is permitted
% (The person fulfilled their obligation bedieved)
asserts(mechaber, heter(consumed_dairy_status, Person)) :-
    ate_meat(Person, _),
    accidental_dairy_consumption(Person, Dairy),
    context(Scenario, bedieved).

% No need to induce vomiting or other corrective action
asserts(mechaber, no_corrective_action_required(Person)) :-
    accidental_dairy_consumption(Person, _),
    context(Scenario, bedieved).

Encoding Shaat Hadchak Leniency

% ============================================================================
% SHAAT HADCHAK: SHORTENED WAITING
% ============================================================================
% In pressing circumstances, some permit relying on shorter waits.
% This follows "shaat hadchak k'dieved dami" principle.

rule(r_waiting_shaat_hadchak).
makor(r_waiting_shaat_hadchak, darkei_moshe("yd:89")).
scope(r_waiting_shaat_hadchak, rema).

% In shaat hadchak: Ashkenazi custom may permit 3 hours
asserts(rema, heter(achiila_dairy, Person)) :-
    ate_meat(Person, MeatType),
    meat_requires_waiting(MeatType),
    waiting_completed(Person, Hours),
    Hours >= 3,
    context(Scenario, shaat_hadchak).

% Document the leniency
asserts(rema, leniency_applied(Person, shaat_hadchak_waiting)) :-
    ate_meat(Person, _),
    context(Scenario, shaat_hadchak),
    waiting_completed(Person, H),
    H < 6,
    H >= 3.

Example 3: Combined Conditions

Real halachic scenarios often combine multiple conditions.

The Scenario

A mixture has: 1. 50:1 ratio (below 60, above 48) 2. Hefsed is present 3. It's shaat hadchak 4. There's a supporting minority opinion

Encoding Tziruf (Combination of Leniencies)

% ============================================================================
% TZIRUF: COMBINING LENIENCIES
% ============================================================================
% When multiple lenient factors exist, they can be combined.
% "Tziruf" (combination) allows relying on aggregate leniencies.

rule(r_tziruf_leniencies).
makor(r_tziruf_leniencies, pri_megadim("yd:98")).
scope(r_tziruf_leniencies, mechaber).

% Count lenient factors
has_lenient_factor(Scenario, hefsed) :-
    context(Scenario, hefsed).

has_lenient_factor(Scenario, shaat_hadchak) :-
    context(Scenario, shaat_hadchak).

has_lenient_factor(Scenario, minority_opinion) :-
    has_supporting_opinion(Scenario).

has_lenient_factor(Scenario, bedieved) :-
    context(Scenario, bedieved).

% Count factors
lenient_factor_count(Scenario, N) :-
    N = #count { F : has_lenient_factor(Scenario, F) }.

% Tziruf applies when 2+ factors present
tziruf_applies(Scenario) :-
    lenient_factor_count(Scenario, N),
    N >= 2.

% With tziruf: lower thresholds can apply
asserts(mechaber, batel(M)) :-
    mixture(M),
    issur_component(M, I),
    heter_component(M, H),
    ratio(H, I, R),
    R >= 30,           % Lowest threshold with tziruf
    tziruf_applies(Scenario).

Testing Combined Conditions

"""Tests for combined conditional rules."""
import pytest
from mistaber import query


class TestCombinedConditions:
    """Tests for rules with multiple conditions."""

    def test_tziruf_with_two_factors(self):
        """Two lenient factors enable tziruf."""
        scenario = """
            mixture(m1).
            issur_component(m1, i1).
            heter_component(m1, h1).
            ratio(h1, i1, 35).
            context(scenario1, hefsed).
            context(scenario1, shaat_hadchak).
        """
        result = query(scenario, world="mechaber")

        assert result.holds("tziruf_applies(scenario1)")
        assert result.holds("batel(m1)")

    def test_no_tziruf_with_one_factor(self):
        """One lenient factor is not enough for tziruf."""
        scenario = """
            mixture(m1).
            issur_component(m1, i1).
            heter_component(m1, h1).
            ratio(h1, i1, 35).
            context(scenario1, hefsed).
        """
        result = query(scenario, world="mechaber")

        assert not result.holds("tziruf_applies(scenario1)")
        assert not result.holds("batel(m1)")

    def test_tziruf_insufficient_ratio(self):
        """Tziruf doesn't help below minimum threshold."""
        scenario = """
            mixture(m1).
            issur_component(m1, i1).
            heter_component(m1, h1).
            ratio(h1, i1, 20).  % Below 30 minimum
            context(scenario1, hefsed).
            context(scenario1, shaat_hadchak).
            has_supporting_opinion(scenario1).
        """
        result = query(scenario, world="mechaber")

        assert result.holds("tziruf_applies(scenario1)")
        assert not result.holds("batel(m1)")  # Still not enough

Encoding Patterns for Conditions

Pattern 1: Context-Dependent Rule

% Rule with explicit context dependency
asserts(World, ruling(Subject, Result)) :-
    condition(Subject),
    context(Scenario, context_type).

Pattern 2: Context-Based Override

% Base ruling
asserts(mechaber, issur(action, X, madrega)) :-
    base_condition(X),
    context(Scenario, lechatchila).

% Context-specific leniency
asserts(mechaber, heter(action, X)) :-
    base_condition(X),
    context(Scenario, hefsed).

Pattern 3: Graduated Responses

% Different outcomes for different contexts
asserts(mechaber, response(X, strict)) :-
    condition(X),
    context(Scenario, lechatchila).

asserts(mechaber, response(X, moderate)) :-
    condition(X),
    context(Scenario, bedieved).

asserts(mechaber, response(X, lenient)) :-
    condition(X),
    context(Scenario, hefsed).

Pattern 4: Leniency Documentation

% Document when leniencies are applied
asserts(World, leniency_applied(Subject, leniency_type)) :-
    applied_lenient_ruling(Subject),
    context(Scenario, triggering_context).

Testing Context-Dependent Rules

Test Matrix Approach

For context-dependent rules, create a test matrix:

@pytest.mark.parametrize("context,expected_result", [
    ("lechatchila", "issur"),
    ("bedieved", "heter"),
    ("hefsed", "heter"),
    ("shaat_hadchak", "heter"),
    ("normal", "issur"),
])
def test_ruling_by_context(base_scenario, context, expected_result):
    """Test ruling varies by context."""
    scenario = base_scenario + f"context(s1, {context})."
    result = query(scenario, world="mechaber")

    if expected_result == "issur":
        assert result.holds("issur(achiila, m1, _)")
    else:
        assert result.holds("heter(achiila, m1)")

Context Combination Tests

def test_context_combinations():
    """Test that multiple contexts combine correctly."""
    scenarios = [
        # (contexts, expected_tziruf, expected_batel)
        (["hefsed"], False, False),
        (["hefsed", "shaat_hadchak"], True, True),
        (["hefsed", "minority_opinion"], True, True),
        (["bedieved", "hefsed", "shaat_hadchak"], True, True),
    ]

    for contexts, expect_tziruf, expect_batel in scenarios:
        context_decls = ". ".join([
            f"context(s1, {c})" if c != "minority_opinion"
            else "has_supporting_opinion(s1)"
            for c in contexts
        ])
        scenario = base_mixture + context_decls + "."

        result = query(scenario, world="mechaber")

        assert result.holds("tziruf_applies(s1)") == expect_tziruf
        assert result.holds("batel(m1)") == expect_batel

Common Mistakes with Conditions

Mistake 1: Forgetting Default Context

% WRONG - rule only fires with explicit context
asserts(mechaber, issur(achiila, M, d_rabanan)) :-
    is_forbidden_mixture(M),
    context(Scenario, lechatchila).
% What if no context is declared? Rule never fires!

% CORRECT - handle both explicit and default
asserts(mechaber, issur(achiila, M, d_rabanan)) :-
    is_forbidden_mixture(M),
    context(Scenario, lechatchila).

asserts(mechaber, issur(achiila, M, d_rabanan)) :-
    is_forbidden_mixture(M),
    context(Scenario, normal).

asserts(mechaber, issur(achiila, M, d_rabanan)) :-
    is_forbidden_mixture(M),
    not has_any_context(Scenario).  % Default case

Mistake 2: Overlapping Conditions

% WRONG - conditions overlap, rule fires multiple times
asserts(mechaber, heter(X)) :- context(S, hefsed).
asserts(mechaber, heter(X)) :- context(S, shaat_hadchak).
% If both contexts present, multiple hetters asserted

% CORRECT - use disjunction or explicit priority
asserts(mechaber, heter(X)) :-
    context(S, C),
    lenient_context(C).

lenient_context(hefsed).
lenient_context(shaat_hadchak).
lenient_context(bedieved).

Mistake 3: Missing Documentation

% WRONG - leniency applied without documentation
asserts(mechaber, batel(M)) :-
    ratio(_, _, R), R >= 48,
    context(S, hefsed).
% How do we know hefsed leniency was used?

% CORRECT - document applied leniencies
asserts(mechaber, batel(M)) :-
    ratio(_, _, R), R >= 48,
    context(S, hefsed).

asserts(mechaber, leniency_applied(M, hefsed_bitul_48)) :-
    batel(M),
    context(S, hefsed),
    ratio(_, _, R),
    R < 60.  % Normal threshold

Summary

Context-dependent encoding requires:

  1. Clear context predicates: Define the context types
  2. Explicit dependencies: Rules declare which contexts they apply to
  3. Default handling: Account for scenarios without explicit context
  4. Leniency documentation: Record when leniencies are applied
  5. Comprehensive testing: Test all context combinations

Next Steps

Continue with:

  1. Tutorial 04: Testing Strategies - Comprehensive testing
  2. Tutorial 05: Review Process - Navigate reviews

Quick Reference: Context Patterns

% === Context declaration ===
context(Scenario, lechatchila | bedieved | hefsed | shaat_hadchak).

% === Context-dependent rule ===
asserts(World, ruling(X)) :-
    condition(X),
    context(Scenario, required_context).

% === Leniency documentation ===
asserts(World, leniency_applied(X, leniency_name)) :-
    lenient_ruling_applied(X),
    triggering_condition(X).

% === Tziruf counting ===
has_lenient_factor(S, factor_name) :- factor_condition(S).
lenient_factor_count(S, N) :- N = #count { F : has_lenient_factor(S, F) }.
tziruf_applies(S) :- lenient_factor_count(S, N), N >= 2.