Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bffa01e0e |
No files matched your search
@@ -157,7 +157,16 @@ vote_from_consultation(Step, {ok, Map}, RootCount) ->
|
|||||||
Status = maps:get(status, Map, <<"teiku">>),
|
Status = maps:get(status, Map, <<"teiku">>),
|
||||||
Text = maps:get(text, Map, <<>>),
|
Text = maps:get(text, Map, <<>>),
|
||||||
Verses = maps:get(verses, Map, []),
|
Verses = maps:get(verses, Map, []),
|
||||||
|
Grounding = maps:get(grounding, Map, []),
|
||||||
case Status of
|
case Status of
|
||||||
|
<<"resolved">> when Verses =:= [], Grounding =:= [] ->
|
||||||
|
%% The engine says "resolved" over an empty trace — no verse,
|
||||||
|
%% no mitzvah. That is a reading that stands on nothing, and
|
||||||
|
%% a vote to accept it would be the calf (avodah_zarah.pl
|
||||||
|
%% Form 2: the uncoupled output). Abstain, and say why.
|
||||||
|
{<<"Teiku — the reading is coupled to no verse and no mitzvah">>,
|
||||||
|
reasoning(Step, <<"resolved on an empty trace; nothing to stand on">>, RootCount, Verses),
|
||||||
|
[], abstain};
|
||||||
<<"resolved">> ->
|
<<"resolved">> ->
|
||||||
{Text,
|
{Text,
|
||||||
reasoning(Step, <<"resolved">>, RootCount, Verses),
|
reasoning(Step, <<"resolved">>, RootCount, Verses),
|
||||||
|
|||||||
+10
-4
@@ -207,15 +207,21 @@ compute_majority_minority([]) ->
|
|||||||
torah_refs => [],
|
torah_refs => [],
|
||||||
vote_count => 0}};
|
vote_count => 0}};
|
||||||
compute_majority_minority(Votes) ->
|
compute_majority_minority(Votes) ->
|
||||||
%% Count accepts vs rejects
|
|
||||||
Accepts = [V || V <- Votes, V#vote.verdict == accept],
|
Accepts = [V || V <- Votes, V#vote.verdict == accept],
|
||||||
Rejects = [V || V <- Votes, V#vote.verdict == reject],
|
Rejects = [V || V <- Votes, V#vote.verdict == reject],
|
||||||
Abstains = [V || V <- Votes, V#vote.verdict == abstain],
|
Abstains = [V || V <- Votes, V#vote.verdict == abstain],
|
||||||
|
NA = length(Accepts), NR = length(Rejects), NB = length(Abstains),
|
||||||
|
|
||||||
|
%% The majority is the verdict with the most votes — abstentions
|
||||||
|
%% count (tablet 55: live, one accept "won" against two
|
||||||
|
%% abstentions, and the reply showed majority 1, minority 2). An
|
||||||
|
%% abstaining plurality, or an accept/reject tie, is teiku
|
||||||
|
%% (determine_outcome reads the majority's verdict).
|
||||||
{MajVotes, MinVotes} =
|
{MajVotes, MinVotes} =
|
||||||
case length(Accepts) >= length(Rejects) of
|
if
|
||||||
true -> {Accepts, Rejects ++ Abstains};
|
NA > NR, NA >= NB -> {Accepts, Rejects ++ Abstains};
|
||||||
false -> {Rejects, Accepts ++ Abstains}
|
NR > NA, NR >= NB -> {Rejects, Accepts ++ Abstains};
|
||||||
|
true -> {Abstains, Accepts ++ Rejects}
|
||||||
end,
|
end,
|
||||||
|
|
||||||
MajSample = case MajVotes of
|
MajSample = case MajVotes of
|
||||||
|
|||||||
@@ -506,6 +506,37 @@ majority_verdict_reject_test() ->
|
|||||||
{Maj, _Min} = panel:compute_majority_minority(Votes),
|
{Maj, _Min} = panel:compute_majority_minority(Votes),
|
||||||
?assertEqual(reject, maps:get(verdict, Maj)).
|
?assertEqual(reject, maps:get(verdict, Maj)).
|
||||||
|
|
||||||
|
%% Tablet 55: abstentions count. One accept against two abstentions is
|
||||||
|
%% not an accepting majority — it is teiku.
|
||||||
|
majority_abstaining_plurality_is_teiku_test() ->
|
||||||
|
Votes = [make_vote(accept), make_vote(abstain), make_vote(abstain)],
|
||||||
|
{Maj, Min} = panel:compute_majority_minority(Votes),
|
||||||
|
?assertEqual(abstain, maps:get(verdict, Maj)),
|
||||||
|
?assertEqual(2, maps:get(vote_count, Maj)),
|
||||||
|
?assertEqual(1, maps:get(vote_count, Min)),
|
||||||
|
?assertEqual(teiku, panel:determine_outcome(Maj, Min, passed)).
|
||||||
|
|
||||||
|
majority_accept_reject_tie_is_teiku_test() ->
|
||||||
|
Votes = [make_vote(accept), make_vote(reject)],
|
||||||
|
{Maj, _Min} = panel:compute_majority_minority(Votes),
|
||||||
|
?assertEqual(abstain, maps:get(verdict, Maj)),
|
||||||
|
?assertEqual(0, maps:get(vote_count, Maj)).
|
||||||
|
|
||||||
|
%% Tablet 55: "resolved" over an empty trace — no verse, no mitzvah —
|
||||||
|
%% is nothing to stand on; the judge abstains rather than accept.
|
||||||
|
judge_resolved_on_empty_trace_abstains_test() ->
|
||||||
|
{Pos, Reason, Refs, Verdict} =
|
||||||
|
judge:vote_from_consultation(interpret,
|
||||||
|
{ok, #{status => <<"resolved">>, text => <<"reading(0,0)">>, verses => [], grounding => []}}, 5),
|
||||||
|
?assertEqual(abstain, Verdict),
|
||||||
|
?assertEqual([], Refs),
|
||||||
|
?assertMatch({_, _}, binary:match(Pos, <<"Teiku">>)),
|
||||||
|
?assertMatch({_, _}, binary:match(Reason, <<"empty trace">>)),
|
||||||
|
%% a mitzvah without a verse is still ground
|
||||||
|
{_, _, _, V2} = judge:vote_from_consultation(interpret,
|
||||||
|
{ok, #{status => <<"resolved">>, text => <<"r">>, verses => [], grounding => [<<"mitzvah(5)">>]}}, 1),
|
||||||
|
?assertEqual(accept, V2).
|
||||||
|
|
||||||
%% ============================================================
|
%% ============================================================
|
||||||
%% determine_outcome tests — accepted, rejected, teiku reachable
|
%% determine_outcome tests — accepted, rejected, teiku reachable
|
||||||
%% ============================================================
|
%% ============================================================
|
||||||
|
|||||||
@@ -970,6 +970,40 @@ defmodule VavPipeline.BereishitCycle do
|
|||||||
|
|
||||||
defp letters_to_hebrew(_), do: nil
|
defp letters_to_hebrew(_), do: nil
|
||||||
|
|
||||||
|
# The inverse: Hebrew consonant → letter name, finals folded to their base.
|
||||||
|
@hebrew_to_name Map.new(@consonant_names, fn {name, heb} -> {heb, name} end)
|
||||||
|
|> Map.merge(%{"ך" => "kaf", "ם" => "mem", "ן" => "nun", "ף" => "pe", "ץ" => "tsadi"})
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
The root as the Talmudic Engine names it — letter names joined by `-`
|
||||||
|
(`json_bridge` `parse_root_name` → `bet_resh_aleph`, the form of its
|
||||||
|
verse facts). From the letter names the Shoresh Engine gives when it
|
||||||
|
has them, else from the Hebrew (the L1 fallback carries only that).
|
||||||
|
Tablet 55: the engine was being sent the Hebrew, which its facts never
|
||||||
|
match — 5 roots in, 0 verses out, and "resolved" over the emptiness.
|
||||||
|
`nil` when the root carries nothing nameable.
|
||||||
|
"""
|
||||||
|
@spec engine_root_name(map()) :: String.t() | nil
|
||||||
|
def engine_root_name(root) when is_map(root) do
|
||||||
|
letters = Map.get(root, :shoresh_letters) || []
|
||||||
|
|
||||||
|
names =
|
||||||
|
if is_list(letters) and letters != [] do
|
||||||
|
Enum.map(letters, &String.downcase(to_string(&1)))
|
||||||
|
else
|
||||||
|
root
|
||||||
|
|> Map.get(:shoresh)
|
||||||
|
|> case do
|
||||||
|
s when is_binary(s) -> s |> String.graphemes() |> Enum.map(&Map.get(@hebrew_to_name, &1))
|
||||||
|
_ -> [nil]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if names != [] and Enum.all?(names, &is_binary/1), do: Enum.join(names, "-"), else: nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def engine_root_name(_), do: nil
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Private helpers
|
# Private helpers
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
@@ -1250,7 +1250,11 @@ defmodule VavPipeline.BigdeiKodesh.Router do
|
|||||||
|
|
||||||
{:ok,
|
{:ok,
|
||||||
%{
|
%{
|
||||||
roots: Enum.map(roots_full, & &1.shoresh),
|
# The engine's verse facts name roots by letter — bet_resh_aleph —
|
||||||
|
# and json_bridge reads "bet-resh-aleph". The cycle's root carries
|
||||||
|
# the letter names beside the Hebrew; the Hebrew never matches
|
||||||
|
# (live, 2026-10-19: 5 roots sent, 0 verses found, "resolved").
|
||||||
|
roots: roots_full |> Enum.map(&VavPipeline.BereishitCycle.engine_root_name/1) |> Enum.reject(&is_nil/1),
|
||||||
roots_full: roots_full,
|
roots_full: roots_full,
|
||||||
torah_ref: {String.to_atom(scope.book), scope.chapter, scope.verse},
|
torah_ref: {String.to_atom(scope.book), scope.chapter, scope.verse},
|
||||||
read_from: Map.get(state, :roots_read_from)
|
read_from: Map.get(state, :roots_read_from)
|
||||||
|
|||||||
@@ -403,8 +403,10 @@ defmodule VavPipeline.BigdeiKodeshTest do
|
|||||||
body = Jason.decode!(conn.resp_body)
|
body = Jason.decode!(conn.resp_body)
|
||||||
assert body["deliberation"]["outcome"] == "teiku"
|
assert body["deliberation"]["outcome"] == "teiku"
|
||||||
assert body["deliberation"]["panel_size"] == 1
|
assert body["deliberation"]["panel_size"] == 1
|
||||||
assert body["deliberation"]["majority"]["votes"] == 0
|
# the one judge abstained: an abstaining majority of one, no dissent
|
||||||
assert body["deliberation"]["minority"]["reasoning"] =~ "no consultation"
|
assert body["deliberation"]["majority"]["votes"] == 1
|
||||||
|
assert body["deliberation"]["majority"]["reasoning"] =~ "no consultation"
|
||||||
|
assert body["deliberation"]["minority"]["votes"] == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
# Tablet 55: with a consultation behind the judges, the dialectic
|
# Tablet 55: with a consultation behind the judges, the dialectic
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
defmodule VavPipeline.Tablet55EngineRootNameTest do
|
||||||
|
@moduledoc """
|
||||||
|
Tablet 55: the Talmudic Engine names roots by letter (bet_resh_aleph,
|
||||||
|
read from "bet-resh-aleph"). The pipeline was sending it the Hebrew,
|
||||||
|
which its verse facts never match — roots in, no verses out, and a
|
||||||
|
"resolved" reading over the emptiness.
|
||||||
|
"""
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
alias VavPipeline.BereishitCycle
|
||||||
|
|
||||||
|
test "from the Shoresh Engine's letter names" do
|
||||||
|
assert BereishitCycle.engine_root_name(%{shoresh: "ברא", shoresh_letters: ["bet", "resh", "aleph"]}) ==
|
||||||
|
"bet-resh-aleph"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "from the Hebrew alone (the L1 fallback), finals folded" do
|
||||||
|
assert BereishitCycle.engine_root_name(%{shoresh: "דבר", shoresh_letters: []}) == "dalet-bet-resh"
|
||||||
|
assert BereishitCycle.engine_root_name(%{shoresh: "מלך"}) == "mem-lamed-kaf"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "nothing nameable → nil, never a Hebrew string the engine cannot read" do
|
||||||
|
assert BereishitCycle.engine_root_name(%{shoresh: nil}) == nil
|
||||||
|
assert BereishitCycle.engine_root_name(%{shoresh: "x1"}) == nil
|
||||||
|
assert BereishitCycle.engine_root_name(%{}) == nil
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in new issue
Block a user