Zweiteinheit am Artikel: Bruecke zwischen Stueck, Gramm und Milliliter

Die drei Einheiten-Arten waren bisher strikt getrennt: BASE_OF_KIND bildet
count/weight/volume 1:1 auf Stueck/Gramm/Milliliter ab, ohne jeden Faktor
dazwischen. Zwei Stellen setzten das durch - to_base lehnte artfremde Einheiten
beim Ein-/Auslagern ab, und group_min_context filterte stueckweise gefuehrte
Artikel aus einer Kilogramm-Gruppe stillschweigend heraus. Letzteres war der
Anlass: eine Gruppe "Wurst" in kg sah Bratwuerste in Stueck gar nicht.

Ein Artikel darf jetzt eine Zweiteinheit tragen: "3 Stueck ≙ 250 g". Gespeichert
wird das eingegebene PAAR, nicht der Faktor - wer 3 und 250 eintippt, sieht beim
naechsten Oeffnen genau das wieder. Das hat auch einen rechnerischen Grund:
250 * 3 / 250 ist exakt 3, der Umweg ueber 250/3 ergibt 3,0000000000000004 und
liefe damit gegen die Bestandspruefung beim Auslagern.

Der Artikel bleibt in seiner Basiseinheit gefuehrt; die Bruecke ist reine
Rechnung. Gruppen zaehlen artfremde Artikel jetzt mit ihrem Faktor mit
(GroupMinContext.faktoren), Bestandssummen laufen dafuer je Artikel gewichtet -
weiterhin zwei Abfragen, nur mit GROUP BY. Ein-/Auslagern in der Fremdeinheit
geht, krumme Mengen werden bewusst gebucht statt gerundet: 100 g sind 1,2 Stueck,
und Runden wuerde stumm etwas anderes buchen als angegeben.

WICHTIGE KORREKTUR am urspruenglichen Plan: die Teilmengen-Bedingung in
_gruppen_bedarfe konnte NICHT bleiben. Sie war bisher zugleich ein
Einheiten-Schutz, weil Artikel verschiedener Arten zwangslaeufig disjunkt waren.
Mit der Bruecke gilt sie ploetzlich auch zwischen einer Stueck- und einer
Gramm-Gruppe - und _netted_topups haette einen Bedarf in Stueck von einem in
Gramm abgezogen. Jetzt wird nur noch zwischen Gruppen derselben Basiseinheit
verrechnet.

Open Food Facts: "3 x 80 g" verlor bisher den Multiplikator, weil der Regex den
ersten Zahl-Einheit-Treffer nahm. parse_gebinde liefert jetzt Gesamtmenge UND
Stueckzahl und belegt die Zweiteinheit vor; parse_quantity behaelt seinen
schmalen Vertrag.

18 neue Tests. Dass test_wrong_kind_rejected und
test_einheitenfilter_gilt_auch_fuer_untergruppen unveraendert gruen bleiben, ist
selbst der Beleg: ohne Bruecke aendert sich nichts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-08-16 00:38:13 +02:00
parent f33054c534
commit 9f3225113e
14 changed files with 675 additions and 92 deletions

View File

@@ -1,4 +1,4 @@
from app.off import parse_quantity
from app.off import parse_gebinde, parse_quantity
def test_grams():
@@ -22,3 +22,31 @@ def test_unparsable_is_piece():
assert parse_quantity(None) == ("piece", None)
assert parse_quantity("6 Stück") == ("piece", None)
assert parse_quantity("") == ("piece", None)
def test_multiplikator_wird_erkannt():
"""„3 x 80 g" ist nicht dasselbe wie „240 g": drei zaehlbare Teile."""
assert parse_gebinde("3 x 80 g") == ("gram", 240.0, 3.0, 80.0)
def test_multiplikator_liter():
assert parse_gebinde("6 × 1,5 l") == ("milliliter", 9000.0, 6.0, 1500.0)
def test_multiplikator_mit_a_akzent():
assert parse_gebinde("4 Stück à 125 g") == ("gram", 500.0, 4.0, 125.0)
def test_ohne_multiplikator_unveraendert():
assert parse_gebinde("500 g") == ("gram", 500.0, None, None)
assert parse_quantity("500 g") == ("gram", 500)
def test_zaehlwort_ohne_muster_bleibt_grenze():
"""Bekannte Grenze: „12 Eier à 53 g" faellt auf die Einzelmenge zurueck.
Das Muster kennt nur eine kurze Liste von Zaehlwoertern. Ein generisches
Wort wuerde es zu weit oeffnen lieber eine nicht erkannte Stueckzahl als
eine falsch geratene.
"""
assert parse_gebinde("12 Eier à 53 g").stueck is None