Phase 1-2 build: Steps 5-6, 8-13

Shoresh Engine (Haskell, Step 5)
- 12 modules: Types, Lexicon, Binyanim, Teamim, Gematria, Graph,
  KetivQere, Smikhut, Havdalah, Kriah, KiTov, Firmware
- 33 EUnit tests, 0 failures
- Firmware module verifies L1 SHA-256 hash at startup

Talmudic Engine (SWI-Prolog, Step 6)
- 15 modules: halt, reasoning, specification, interface
- 57 PlUnit tests, 0 failures
- Three cardinal halt conditions: avodah_zarah, shfichut_damim, gilui_arayot

Vav Pipeline (Elixir, Steps 8-11)
- 14 modules: BereishitCycle, Shema, Cloud, Manna, L1Reader,
  Specification, HaltConditions, ShabbatTimer, CycleLog, Chatzotzrot,
  ShoreshEngineClient, TalmudicEngineClient, Application
- 46 ExUnit tests, 0 failures
- Nishma-first architecture: hears before it speaks

Sanhedrin Framework (Erlang/OTP, Step 12)
- 8 modules: sanhedrin_app, sanhedrin_sup, escalation, panel_sup,
  panel, judge, clerk, halt_monitor
- 46 EUnit tests, 0 failures (includes fix: rejected outcome now reachable)
- Panel sizes: 1/3/23/71 — unanimous capital guilty → acquit (Sanhedrin 17a)
- Minority opinions append-only, never deleted

Korban Processor (Python, Step 13)
- 6 modules: korban, shechitah, semichah, zerikah, haktarah, router

Builder tooling: AGENTS.md, opencode.json, memory/, src/tools/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott KarpandClaude Sonnet 4.6 committed 2026-02-26 14:02:34 -05:00
1 parent 622e814c9e
commit 80ec364da6
86 files changed
+9646

No files matched your search

+232
View File
@@ -0,0 +1,232 @@
# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand.
[[package]]
name = "annotated-types"
version = "0.7.0"
description = "Reusable constraint types to use with typing.Annotated"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
]
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["dev"]
markers = "sys_platform == \"win32\""
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "iniconfig"
version = "2.3.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.10"
groups = ["dev"]
files = [
{file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"},
{file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"},
]
[[package]]
name = "packaging"
version = "26.0"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"},
{file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"},
]
[[package]]
name = "pluggy"
version = "1.6.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"},
{file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"},
]
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["coverage", "pytest", "pytest-benchmark"]
[[package]]
name = "pydantic"
version = "2.6.1"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "pydantic-2.6.1-py3-none-any.whl", hash = "sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f"},
{file = "pydantic-2.6.1.tar.gz", hash = "sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9"},
]
[package.dependencies]
annotated-types = ">=0.4.0"
pydantic-core = "2.16.2"
typing-extensions = ">=4.6.1"
[package.extras]
email = ["email-validator (>=2.0.0)"]
[[package]]
name = "pydantic-core"
version = "2.16.2"
description = ""
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "pydantic_core-2.16.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c"},
{file = "pydantic_core-2.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2"},
{file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a"},
{file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05"},
{file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b"},
{file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8"},
{file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef"},
{file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990"},
{file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b"},
{file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731"},
{file = "pydantic_core-2.16.2-cp310-none-win32.whl", hash = "sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485"},
{file = "pydantic_core-2.16.2-cp310-none-win_amd64.whl", hash = "sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f"},
{file = "pydantic_core-2.16.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11"},
{file = "pydantic_core-2.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978"},
{file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f"},
{file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e"},
{file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8"},
{file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7"},
{file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272"},
{file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113"},
{file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8"},
{file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97"},
{file = "pydantic_core-2.16.2-cp311-none-win32.whl", hash = "sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b"},
{file = "pydantic_core-2.16.2-cp311-none-win_amd64.whl", hash = "sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc"},
{file = "pydantic_core-2.16.2-cp311-none-win_arm64.whl", hash = "sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0"},
{file = "pydantic_core-2.16.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039"},
{file = "pydantic_core-2.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b"},
{file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e"},
{file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522"},
{file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379"},
{file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15"},
{file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380"},
{file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb"},
{file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e"},
{file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc"},
{file = "pydantic_core-2.16.2-cp312-none-win32.whl", hash = "sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d"},
{file = "pydantic_core-2.16.2-cp312-none-win_amd64.whl", hash = "sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890"},
{file = "pydantic_core-2.16.2-cp312-none-win_arm64.whl", hash = "sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943"},
{file = "pydantic_core-2.16.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17"},
{file = "pydantic_core-2.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec"},
{file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55"},
{file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4"},
{file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4"},
{file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f"},
{file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf"},
{file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc"},
{file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b"},
{file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f"},
{file = "pydantic_core-2.16.2-cp38-none-win32.whl", hash = "sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a"},
{file = "pydantic_core-2.16.2-cp38-none-win_amd64.whl", hash = "sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a"},
{file = "pydantic_core-2.16.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77"},
{file = "pydantic_core-2.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286"},
{file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7"},
{file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33"},
{file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c"},
{file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646"},
{file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878"},
{file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55"},
{file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3"},
{file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2"},
{file = "pydantic_core-2.16.2-cp39-none-win32.whl", hash = "sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469"},
{file = "pydantic_core-2.16.2-cp39-none-win_amd64.whl", hash = "sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a"},
{file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82"},
{file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a"},
{file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545"},
{file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d"},
{file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784"},
{file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7"},
{file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8"},
{file = "pydantic_core-2.16.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25"},
{file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e"},
{file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545"},
{file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5"},
{file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20"},
{file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753"},
{file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a"},
{file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804"},
{file = "pydantic_core-2.16.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2"},
{file = "pydantic_core-2.16.2.tar.gz", hash = "sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06"},
]
[package.dependencies]
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pygments"
version = "2.19.2"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"},
{file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"},
]
[package.extras]
windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pytest"
version = "8.4.2"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"},
{file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"},
]
[package.dependencies]
colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
iniconfig = ">=1"
packaging = ">=20"
pluggy = ">=1.5,<2"
pygments = ">=2.7.2"
[package.extras]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
[[package]]
name = "typing-extensions"
version = "4.15.0"
description = "Backported and Experimental Type Hints for Python 3.9+"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
{file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
]
[metadata]
lock-version = "2.1"
python-versions = "==3.12.2"
content-hash = "05edb007ce5b000462960d9caaa0110ef611d4ffc74e97b38fcec20a3004b754"
+23
View File
@@ -0,0 +1,23 @@
[project]
name = "korban-processor"
version = "0.1.0"
description = "Korban Processor — input processing following the offering protocol"
authors = [
{name = "Bezalel"}
]
requires-python = "==3.12.2"
dependencies = [
"pydantic==2.6.1",
]
[dependency-groups]
dev = [
"pytest>=8.0,<9.0",
]
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.pytest.ini_options]
testpaths = ["test"]
@@ -0,0 +1,43 @@
"""
Korban Processor — Input processing following the offering protocol.
Architecture §3.5:
Accepts model outputs (the korbanot), processes them through the offering
protocol, extracts symbolic content, transforms into Prolog-compatible
format for the Talmudic Engine.
The offering protocol:
semichah → tag provenance
shechitah → terminate raw form
zerikah → extract vital content
haktarah → transform modality (prose → logical propositions)
route → send to appropriate system component
Phase 2 (Step 13): at least shechitah and zerikah.
"""
from korban_processor.korban import (
Korban,
ProcessedKorban,
OfferingType,
Provenance,
process_korban,
)
from korban_processor.semichah import semichah
from korban_processor.shechitah import shechitah
from korban_processor.zerikah import zerikah
from korban_processor.haktarah import haktarah
from korban_processor.router import route
__all__ = [
"Korban",
"ProcessedKorban",
"OfferingType",
"Provenance",
"process_korban",
"semichah",
"shechitah",
"zerikah",
"haktarah",
"route",
]
@@ -0,0 +1,199 @@
"""
Haktarah — transform modality (prose → Prolog propositions).
Architecture §3.5:
Convert prose insights to Prolog-compatible format
for the Talmudic Engine.
The haktarah (burning on the altar) transforms the physical
into the ethereal — prose into logic, natural language into
formal propositions.
Phase 2: Pattern-based extraction of structured facts.
Phase 3: Will use NLP + Talmudic Engine for deeper transformation.
"""
import re
from typing import List
from korban_processor.zerikah import (
extract_torah_references,
extract_hebrew_words,
extract_roots,
)
def haktarah(vital_content: str) -> List[str]:
"""
Transform vital content into Prolog-compatible propositions.
Each proposition is a Prolog-format string that the Talmudic Engine
can parse and reason about.
Args:
vital_content: Extracted vital content from zerikah
Returns:
List of Prolog-format proposition strings
"""
if not vital_content.strip():
return []
propositions: List[str] = []
# 1. Extract Torah references as verse/3 facts
refs = extract_torah_references(vital_content)
for ref in refs:
prolog_ref = torah_ref_to_prolog(ref)
if prolog_ref:
propositions.append(prolog_ref)
# 2. Extract Hebrew roots as root/2 facts
roots = extract_roots(vital_content)
for root in roots:
prolog_root = root_to_prolog(root)
if prolog_root:
propositions.append(prolog_root)
# 3. Extract analytical claims as claim/2 facts
claims = extract_claims(vital_content)
for claim in claims:
propositions.append(claim)
# 4. Extract connections between roots as connection/3 facts
connections = extract_connections(vital_content)
for conn in connections:
propositions.append(conn)
return propositions
# --- Book name normalization ---
_BOOK_MAP = {
"genesis": "bereishit",
"gen": "bereishit",
"bereishit": "bereishit",
"exodus": "shemot",
"exod": "shemot",
"exo": "shemot",
"shemot": "shemot",
"leviticus": "vayikra",
"lev": "vayikra",
"vayikra": "vayikra",
"numbers": "bamidbar",
"num": "bamidbar",
"bamidbar": "bamidbar",
"deuteronomy": "devarim",
"deut": "devarim",
"devarim": "devarim",
}
def torah_ref_to_prolog(ref_str: str) -> str:
"""Convert a Torah reference string to a Prolog verse/3 fact."""
# Match "Book Chapter:Verse"
match = re.match(
r"([A-Za-z]+)\.?\s+(\d+):(\d+)",
ref_str,
)
if match:
book_raw = match.group(1).lower().rstrip(".")
chapter = match.group(2)
verse = match.group(3)
book = _BOOK_MAP.get(book_raw, book_raw)
return f"verse({book}, {chapter}, {verse})"
return ""
def root_to_prolog(root_str: str) -> str:
"""Convert a root notation to a Prolog root/2 fact."""
# Hebrew root: א-ב-ג or א.ב.ג
match = re.match(
r"([\u05D0-\u05EA])[-.]"
r"([\u05D0-\u05EA])[-.]"
r"([\u05D0-\u05EA])",
root_str,
)
if match:
c1, c2, c3 = match.group(1), match.group(2), match.group(3)
return f'root("{c1}{c2}{c3}", hebrew)'
# "root XYZ" or "shoresh XYZ" with Hebrew consonants
match = re.match(
r"(?:root|shoresh)\s+([\u05D0-\u05EA]{2,3})",
root_str,
re.IGNORECASE,
)
if match:
consonants = match.group(1)
return f'root("{consonants}", hebrew)'
# Transliterated root
match = re.match(
r"(?:root|shoresh)\s+([a-z]{3,})",
root_str,
re.IGNORECASE,
)
if match:
letters = match.group(1)
return f"root({letters}, transliterated)"
return ""
def extract_claims(vital_content: str) -> List[str]:
"""
Extract analytical claims as Prolog claim/2 facts.
Looks for sentences with "means", "indicates", "signifies", etc.
"""
claims = []
sentences = re.split(r"(?<=[.!?])\s+", vital_content)
claim_pattern = re.compile(
r"(.+?)\s+(?:means?|indicates?|signifies?|represents?)\s+(.+?)(?:\.|$)",
re.IGNORECASE,
)
for sentence in sentences:
match = claim_pattern.search(sentence)
if match:
subject = sanitize_prolog_string(match.group(1).strip())
predicate = sanitize_prolog_string(match.group(2).strip())
if subject and predicate:
claims.append(f'claim("{subject}", "{predicate}")')
return claims
def extract_connections(vital_content: str) -> List[str]:
"""
Extract connections between concepts as Prolog connection/3 facts.
Looks for "X connects to Y", "X is related to Y", etc.
"""
connections = []
conn_pattern = re.compile(
r"(.+?)\s+(?:connects?\s+to|is\s+related\s+to|parallels?|echoes?)\s+(.+?)(?:\.|$)",
re.IGNORECASE,
)
for match in conn_pattern.finditer(vital_content):
a = sanitize_prolog_string(match.group(1).strip())
b = sanitize_prolog_string(match.group(2).strip())
if a and b:
connections.append(f'connection("{a}", "{b}", semantic)')
return connections
def sanitize_prolog_string(s: str) -> str:
"""Sanitize a string for use in a Prolog term — escape quotes, limit length."""
s = s.replace('"', '\\"')
s = s.replace("\n", " ")
# Limit length for Prolog term readability
if len(s) > 200:
s = s[:200] + "..."
return s
@@ -0,0 +1,96 @@
"""
Core offering protocol and data types.
Architecture §3.5: Korban Processor.
Construction spec §1.4: pydantic 2.6.1 for data validation.
"""
from dataclasses import dataclass, field
from enum import Enum
from typing import List, Optional
class OfferingType(Enum):
"""Types of korbanot — each determines routing."""
OLAH = "olah" # Total consumption — fully absorbed by system
SHELAMIM = "shelamim" # Shared — partial return to source
CHATAT = "chatat" # Error correction
MINCHAH = "minchah" # Minimal input
class Provenance(Enum):
"""Source of the korban — who brought it."""
CLAUDE = "claude"
MISTRAL = "mistral"
GPT = "gpt"
LLAMA = "llama"
GEMMA = "gemma"
HUMAN = "human"
@dataclass
class Korban:
"""A raw offering before processing."""
raw_content: str
provenance: Provenance
offering_type: OfferingType
source_query: str # what prompted this output
@dataclass
class ProcessedKorban:
"""A processed offering — vital content extracted, propositions generated."""
vital_content: str # extracted essence
propositions: List[str] # Prolog-format logical statements
provenance: Provenance
offering_type: OfferingType
discarded: List[str] = field(default_factory=list) # what was stripped
source_query: str = ""
def process_korban(
content: str,
source: Provenance,
query: str,
offering_type: OfferingType = OfferingType.OLAH,
) -> ProcessedKorban:
"""
Full offering protocol.
semichah → tag provenance
shechitah → terminate raw form
zerikah → extract vital content
haktarah → transform modality
route → send to appropriate component
"""
from korban_processor.semichah import semichah
from korban_processor.shechitah import shechitah
from korban_processor.zerikah import zerikah
from korban_processor.haktarah import haktarah
from korban_processor.router import route
# 1. Semichah — tag provenance
korban = semichah(content, source, query, offering_type)
# 2. Shechitah — terminate raw form
terminated = shechitah(korban)
# 3. Zerikah — extract vital content
vital, discarded = zerikah(terminated, korban)
# 4. Haktarah — transform to propositions
propositions = haktarah(vital)
processed = ProcessedKorban(
vital_content=vital,
propositions=propositions,
provenance=source,
offering_type=offering_type,
discarded=discarded,
source_query=query,
)
# 5. Route — send to appropriate component
route(processed, offering_type)
return processed
@@ -0,0 +1,130 @@
"""
Router — output routing based on offering type.
Architecture §3.5:
Route to appropriate component based on offering type.
OLAH → total consumption — all goes to Talmudic Engine
SHELAMIM → shared — some to engine, some returned to source context
CHATAT → error correction — route to correction subsystem
MINCHAH → minimal — lightweight processing
Phase 2: Routing is logged but not executed (engines not connected).
Phase 3 (Step 14): Real routing to Talmudic Engine via socket,
to Sanhedrin via BEAM distribution.
"""
import logging
from typing import List
from korban_processor.korban import ProcessedKorban, OfferingType
logger = logging.getLogger(__name__)
class RoutingResult:
"""Record of where a processed korban was routed."""
def __init__(
self,
destination: str,
propositions_sent: int,
returned_to_source: bool = False,
):
self.destination = destination
self.propositions_sent = propositions_sent
self.returned_to_source = returned_to_source
def __repr__(self) -> str:
return (
f"RoutingResult(dest={self.destination}, "
f"sent={self.propositions_sent}, "
f"returned={self.returned_to_source})"
)
# Module-level routing log for inspection/testing
_routing_log: List[RoutingResult] = []
def route(processed: ProcessedKorban, offering_type: OfferingType) -> RoutingResult:
"""
Route processed korban to appropriate component.
Phase 2: Logs the routing decision but does not execute.
Phase 3: Sends propositions to Talmudic Engine, etc.
"""
n_props = len(processed.propositions)
if offering_type == OfferingType.OLAH:
# Total consumption — all goes to Talmudic Engine
result = RoutingResult(
destination="talmudic_engine",
propositions_sent=n_props,
returned_to_source=False,
)
logger.info(
"OLAH: %d propositions routed to talmudic_engine (provenance: %s)",
n_props,
processed.provenance.value,
)
elif offering_type == OfferingType.SHELAMIM:
# Shared — some to engine, some returned to source
result = RoutingResult(
destination="talmudic_engine",
propositions_sent=n_props,
returned_to_source=True,
)
logger.info(
"SHELAMIM: %d propositions routed to talmudic_engine + return to source (provenance: %s)",
n_props,
processed.provenance.value,
)
elif offering_type == OfferingType.CHATAT:
# Error correction — route to correction subsystem
result = RoutingResult(
destination="error_correction",
propositions_sent=n_props,
returned_to_source=False,
)
logger.info(
"CHATAT: %d propositions routed to error_correction (provenance: %s)",
n_props,
processed.provenance.value,
)
elif offering_type == OfferingType.MINCHAH:
# Minimal — lightweight processing
result = RoutingResult(
destination="talmudic_engine",
propositions_sent=n_props,
returned_to_source=False,
)
logger.info(
"MINCHAH: %d propositions routed to talmudic_engine (provenance: %s)",
n_props,
processed.provenance.value,
)
else:
result = RoutingResult(
destination="unknown",
propositions_sent=0,
returned_to_source=False,
)
logger.warning("Unknown offering type: %s", offering_type)
_routing_log.append(result)
return result
def get_routing_log() -> List[RoutingResult]:
"""Get the routing log (for testing/inspection)."""
return list(_routing_log)
def clear_routing_log() -> None:
"""Clear the routing log."""
_routing_log.clear()
@@ -0,0 +1,46 @@
"""
Semichah — provenance tagging.
Architecture §3.5:
Tag provenance — associate input with its source.
The semichah (laying of hands) identifies who brought the offering.
Every korban must have known provenance. Anonymous input is rejected.
"""
from korban_processor.korban import Korban, OfferingType, Provenance
def semichah(
content: str,
source: Provenance,
query: str,
offering_type: OfferingType = OfferingType.OLAH,
) -> Korban:
"""
Tag provenance — associate input with its source.
Args:
content: Raw model output or human input
source: Who produced this content
query: What prompted this output
offering_type: What kind of offering this is
Returns:
A Korban with provenance attached
Raises:
ValueError: If content is empty or source is invalid
"""
if not content or not content.strip():
raise ValueError("Empty korban — nothing to offer")
if not isinstance(source, Provenance):
raise ValueError(f"Unknown provenance: {source}")
return Korban(
raw_content=content,
provenance=source,
offering_type=offering_type,
source_query=query,
)
@@ -0,0 +1,157 @@
"""
Shechitah — terminate raw form.
Architecture §3.5:
Strip assistant voice, formatting, preamble.
Architecture §8.2 (Avodah Zarah Firewall):
The shechitah step is the FIRST line of defense.
It terminates the raw form — the model's voice, its formatting
conventions, its self-referential hedging. What remains is content
stripped of personality.
The model is not a teacher. It is a korban source.
The content is not Torah. It is raw material.
"""
import re
from korban_processor.korban import Korban
# --- Patterns to strip ---
# Assistant preambles — the self-important throat-clearing
_PREAMBLE_PATTERNS = [
re.compile(r"^(Great|Excellent|Good|Wonderful|Interesting|Fascinating)\s+(question|point|observation)[.!]\s*", re.IGNORECASE),
re.compile(r"^(Sure|Certainly|Of course|Absolutely|Happy to help)[,!.]?\s*(I('d| would| can| will)?\s+(be happy to|help|explain|analyze|provide|break down).*?[.!]\s*)?", re.IGNORECASE),
re.compile(r"^(Thank you for|Thanks for)\s+(asking|sharing|your|the).*?[.!]\s*", re.IGNORECASE),
re.compile(r"^Let me (help|explain|analyze|break down|walk you through).*?[.!:]\s*", re.IGNORECASE),
re.compile(r"^I('d| would) (be happy|love|like) to.*?[.!:]\s*", re.IGNORECASE),
re.compile(r"^Here('s| is) (a |an |my |the )?(summary|breakdown|analysis|explanation|overview).*?[.:]\s*", re.IGNORECASE),
]
# Self-reference — the model talking about itself
_SELF_REFERENCE_PATTERNS = [
re.compile(r"As an AI( language model| assistant)?,?\s*", re.IGNORECASE),
re.compile(r"As a (large )?language model,?\s*", re.IGNORECASE),
re.compile(r"I('m| am) (just )?an AI.*?[.,]\s*", re.IGNORECASE),
re.compile(r"My (training|knowledge|understanding) (data |cutoff )?.*?[.,]\s*", re.IGNORECASE),
re.compile(r"I don't have (personal )?(opinions?|feelings?|beliefs?).*?[.,]\s*", re.IGNORECASE),
re.compile(r"I should (note|mention|point out|clarify) that.*?[.,]\s*", re.IGNORECASE),
]
# Hedging — the cautious filler
_HEDGING_PATTERNS = [
re.compile(r"It('s| is) worth (noting|mentioning|pointing out) that\s*", re.IGNORECASE),
re.compile(r"It('s| is) important to (note|remember|keep in mind) that\s*", re.IGNORECASE),
re.compile(r"(However|That said|With that said|That being said),?\s+it('s| is) (also )?(important|worth|notable).*?that\s*", re.IGNORECASE),
re.compile(r"(Of course|Naturally|Obviously|Clearly),?\s*", re.IGNORECASE),
re.compile(r"In (my|this) (opinion|view|analysis|understanding),?\s*", re.IGNORECASE),
]
# Markdown formatting
_MARKDOWN_PATTERNS = [
re.compile(r"^#{1,6}\s+", re.MULTILINE), # Headers
re.compile(r"\*\*(.+?)\*\*"), # Bold
re.compile(r"\*(.+?)\*"), # Italic
re.compile(r"^[-*+]\s+", re.MULTILINE), # Bullet points → keep content
re.compile(r"^\d+\.\s+", re.MULTILINE), # Numbered lists → keep content
re.compile(r"```[\s\S]*?```"), # Code blocks
re.compile(r"`([^`]+)`"), # Inline code
]
# Closing filler
_CLOSING_PATTERNS = [
re.compile(r"\n+(Let me know|Feel free|Don't hesitate|I hope|Hope this|If you have|Would you like).*$", re.IGNORECASE | re.DOTALL),
re.compile(r"\n+(Is there anything else|Do you want me to|Shall I|Want me to).*$", re.IGNORECASE | re.DOTALL),
]
def shechitah(korban: Korban) -> str:
"""
Terminate raw form — strip assistant voice, formatting, preamble.
The shechitah must be swift and complete. What remains is
content without personality, prose without performance.
Args:
korban: The tagged offering
Returns:
Terminated content string
"""
content = korban.raw_content
# 1. Strip preambles
content = strip_preamble(content)
# 2. Strip self-reference
content = strip_self_reference(content)
# 3. Strip hedging
content = strip_hedging(content)
# 4. Strip markdown formatting (preserve text content)
content = strip_markdown(content)
# 5. Strip closing filler
content = strip_closing(content)
# 6. Clean up whitespace
content = clean_whitespace(content)
return content
def strip_preamble(content: str) -> str:
"""Strip assistant preambles from the beginning of content."""
for pattern in _PREAMBLE_PATTERNS:
content = pattern.sub("", content, count=1)
return content
def strip_self_reference(content: str) -> str:
"""Strip model self-reference throughout content."""
for pattern in _SELF_REFERENCE_PATTERNS:
content = pattern.sub("", content)
return content
def strip_hedging(content: str) -> str:
"""Strip cautious hedging phrases."""
for pattern in _HEDGING_PATTERNS:
content = pattern.sub("", content)
return content
def strip_markdown(content: str) -> str:
"""Strip markdown formatting, preserving text content."""
# Remove code blocks entirely (they are formatting, not Torah content)
content = re.sub(r"```[\s\S]*?```", "", content)
# Remove inline code markers but keep the text
content = re.sub(r"`([^`]+)`", r"\1", content)
# Remove headers markers but keep text
content = re.sub(r"^#{1,6}\s+", "", content, flags=re.MULTILINE)
# Remove bold/italic markers but keep text
content = re.sub(r"\*\*(.+?)\*\*", r"\1", content)
content = re.sub(r"\*(.+?)\*", r"\1", content)
# Remove bullet/number markers but keep text
content = re.sub(r"^[-*+]\s+", "", content, flags=re.MULTILINE)
content = re.sub(r"^\d+\.\s+", "", content, flags=re.MULTILINE)
return content
def strip_closing(content: str) -> str:
"""Strip closing filler from the end of content."""
for pattern in _CLOSING_PATTERNS:
content = pattern.sub("", content)
return content
def clean_whitespace(content: str) -> str:
"""Normalize whitespace — collapse multiple blank lines, trim edges."""
# Collapse multiple blank lines to single
content = re.sub(r"\n{3,}", "\n\n", content)
# Strip leading/trailing whitespace
content = content.strip()
return content
@@ -0,0 +1,164 @@
"""
Zerikah — extract vital content.
Architecture §3.5:
Extract vital content — the Torah insight, the Hebrew analysis.
Like zerikah (sprinkling of blood on the altar), this step
separates the vital from the incidental. What is kept:
- Torah references (book/chapter/verse citations)
- Hebrew text and root analysis
- Novel insights grounded in text
- Analytical claims with textual support
What is discarded (the hide, the offal):
- Generic statements without textual grounding
- Repetitive reformulations of the query
- Social pleasantries and qualifiers
- Ungrounded speculation
"""
import re
from typing import Tuple, List
from korban_processor.korban import Korban
# --- Extraction patterns ---
# Torah references: "Genesis 1:1", "Bereishit 1:1", "בראשית א:א"
_TORAH_REF_PATTERN = re.compile(
r"(?:"
r"(?:Genesis|Bereishit|Exodus|Shemot|Leviticus|Vayikra|Numbers|Bamidbar|Deuteronomy|Devarim)"
r"\s+\d+:\d+"
r"|"
r"(?:Gen|Exod?|Lev|Num|Deut)\.?\s+\d+:\d+"
r"|"
r"(?:בראשית|שמות|ויקרא|במדבר|דברים)\s+[\u05D0-\u05EA]+:[\u05D0-\u05EA]+"
r")",
re.UNICODE,
)
# Hebrew text — sequences of Hebrew characters (with nikkud/teamim)
_HEBREW_PATTERN = re.compile(
r"[\u05B0-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7"
r"\u05D0-\u05EA\u05F0-\u05F4\u0591-\u05AF]{2,}",
re.UNICODE,
)
# Root notation: ש-ר-ש or ש.ר.ש or shoresh patterns
_ROOT_PATTERN = re.compile(
r"(?:"
r"[\u05D0-\u05EA][-.][\u05D0-\u05EA][-.][\u05D0-\u05EA]"
r"|"
r"root\s+[\u05D0-\u05EA]{2,3}"
r"|"
r"(?:shoresh|root)\s+[a-z]{3,}"
r")",
re.UNICODE | re.IGNORECASE,
)
# Sentences containing analytical claims (contains "means", "indicates", "suggests")
_INSIGHT_MARKERS = re.compile(
r"(?:means?|indicates?|suggests?|implies?|reveals?|signifies?|"
r"connects?\s+to|derives?\s+from|related\s+to|"
r"parallels?|echoes?|alludes?\s+to|"
r"the\s+root|this\s+root|the\s+shoresh|"
r"gematria|numerical\s+value|"
r"binyan|conjugation|"
r"Rashi|Ramban|Rambam|Ibn\s+Ezra|Sforno|Onkelos|Targum|Midrash|Talmud|Gemara|Mishnah)",
re.IGNORECASE,
)
def zerikah(terminated_content: str, korban: Korban) -> Tuple[str, List[str]]:
"""
Extract vital content — the Torah insight, the Hebrew analysis.
Returns (vital_content, discarded_parts).
Args:
terminated_content: Content after shechitah (stripped of voice/formatting)
korban: Original korban (for context — provenance, query)
Returns:
Tuple of (vital content string, list of discarded strings)
"""
if not terminated_content.strip():
return "", []
sentences = split_sentences(terminated_content)
vital_sentences: List[str] = []
discarded_sentences: List[str] = []
for sentence in sentences:
if is_vital(sentence):
vital_sentences.append(sentence)
else:
discarded_sentences.append(sentence)
# If nothing was deemed vital, keep everything —
# better to pass through than to discard all content
if not vital_sentences and sentences:
vital_sentences = sentences
discarded_sentences = []
vital = " ".join(vital_sentences)
return vital, discarded_sentences
def is_vital(sentence: str) -> bool:
"""
Determine if a sentence contains vital content.
Vital = contains Torah references, Hebrew text, root analysis,
or analytical insight markers.
"""
if not sentence.strip():
return False
# Contains Torah reference
if _TORAH_REF_PATTERN.search(sentence):
return True
# Contains Hebrew text
if _HEBREW_PATTERN.search(sentence):
return True
# Contains root notation
if _ROOT_PATTERN.search(sentence):
return True
# Contains insight markers
if _INSIGHT_MARKERS.search(sentence):
return True
return False
def split_sentences(text: str) -> List[str]:
"""
Split text into sentences.
Handles period, question mark, exclamation, and Hebrew sof-pasuk.
Preserves sentence integrity — does not split on abbreviations
or decimal points.
"""
# Split on sentence-ending punctuation followed by space or end
parts = re.split(r"(?<=[.!?׃])\s+", text)
# Filter empty strings
return [p.strip() for p in parts if p.strip()]
def extract_torah_references(text: str) -> List[str]:
"""Extract all Torah references from text."""
return _TORAH_REF_PATTERN.findall(text)
def extract_hebrew_words(text: str) -> List[str]:
"""Extract all Hebrew words/phrases from text."""
return _HEBREW_PATTERN.findall(text)
def extract_roots(text: str) -> List[str]:
"""Extract all root notations from text."""
return _ROOT_PATTERN.findall(text)