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:
- Clear context predicates: Define the context types
- Explicit dependencies: Rules declare which contexts they apply to
- Default handling: Account for scenarios without explicit context
- Leniency documentation: Record when leniencies are applied
- Comprehensive testing: Test all context combinations
Next Steps¶
Continue with:
- Tutorial 04: Testing Strategies - Comprehensive testing
- 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.