feat: eingang_hoehe="auto" auf Python-Seite nachgezogen, hundm05.json bereinigt
Lisp (vf_linienzug.lsp/vf_spec.lsp): - vsp-eingang-hoehe unterstuetzt jetzt "auto" (NIL-Token statt REAL) - bewusst keine Zahl vorgeben, der Bauablauf berechnet seinen eigenen Vorschlag, wie bei leerer Konsoleneingabe - vfl-journal-reset-lauf: internes Reset innerhalb von vf-linienzug-modus/ -modus2 loescht *vfl-replay-queue*/*vflw-pending* nicht mehr mit (Bug: REPLAY-getriebener Bau (vsp-bau-aus-spec) brach mitten in der Kette ab, weil eine noch unverbrauchte Queue verworfen wurde) Python-Gegenstueck nachgezogen (lib/vf_linienzug_grammatik.py, lib/vf_spec_export.py, lib/vf_journal_export.py): - NIL-Token-Unterstuetzung fuer eingang_hoehe="auto" im gemeinsamen Parser-Kern, im Schreiber (SpecSchreiber) und in der Protokoll-Annotation - gf_verteilung ist im Schreiber jetzt an der Feldpraesenz in der Spec festgemacht statt an einem Grammatik-Flag (selbstbeschreibend, deckt Specs mit und ohne diese Frage ab, ohne dass der Aufrufer die Grammatik kennen muss) - Rundlauf-Pruefung (main(), test_verlustfrei) toleriert jetzt die bekannte, schon dokumentierte Erweiterung um drei neue Linie-GF-Pflichtfragen (ist_teilfolge statt exakter Gleichheit) - test_gleiche_journale_wie_protokoll: alle 5 Ketten in hundm05.json weichen inzwischen vom Rohprotokoll ab (Kette 1 bewusst wegen eingang_hoehe, Ketten 2-5 schon laenger wegen Handkorrektur gegen echte DXF-Positionen) - expliziter Skip mit Begruendung statt leerem Loop tests/testdata/hundm05.json: - mm-Werte (dl, Start-/Stuetzpunkte, Koordinaten der Nachtrag-Objekte) auf eine Nachkommastelle gerundet - nicht abgefragte Kopf-Metadaten entfernt (beschreibung, sivas_id, sivas_bezeichnung, planquadrat, block, dxf_handle) - nur quelle bleibt Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
6d15c6be13
commit
23d79ec354
@@ -179,6 +179,11 @@ _ROH_FRAGEN = {
|
||||
"Grad) -> %s Grad",
|
||||
"es_winkel": "getstring ES-Element Winkel (Wert direkt, 90 oder 30 "
|
||||
"Grad) -> %s Grad",
|
||||
# NIL-Token statt REAL (Kern._opt_eingang_hoehe): bewusst keine Zahl
|
||||
# vorgegeben, der Bauablauf berechnet seinen eigenen Vorschlag.
|
||||
"eingang_hoehe": "getreal Ziel-Hoehe fuer den GF1-Einlauf der "
|
||||
"VF-Einheit -> %s (keine Vorgabe, Bauablauf berechnet "
|
||||
"selbst)",
|
||||
}
|
||||
|
||||
_JA_NEIN_FRAGEN = {
|
||||
|
||||
@@ -42,6 +42,9 @@ Token = (Art, Wert-String) genau wie im Journal auf der XDATA:
|
||||
PT Startpunkt "x,y,z" DL Segmentlaenge
|
||||
REAL Realzahl INT Ganzzahl
|
||||
STR Menue-/Wertantwort STEP Glied-Marker (Label = Segmenttyp)
|
||||
NIL bewusst leere Antwort (z.B. eingang_hoehe="auto", siehe
|
||||
_opt_eingang_hoehe) - der Bauablauf berechnet dann seinen eigenen
|
||||
Vorschlag, wie bei einer leeren Konsoleneingabe
|
||||
"""
|
||||
|
||||
# ============================================================
|
||||
@@ -420,10 +423,20 @@ class Kern(object):
|
||||
Kettenanfang und nach einer fertigen VF-Einheit, nicht dagegen im
|
||||
Anschluss an GF/Separator/Vario-Kurve. Ob sie ansteht, haengt am
|
||||
Zustand der Zeichnung und laesst sich hier nicht ausrechnen; am
|
||||
Journal ist sie eindeutig (ein REAL vor der ersten Menue-Antwort der
|
||||
VF-Einheit)."""
|
||||
Journal ist sie eindeutig (ein REAL/NIL vor der ersten Menue-Antwort
|
||||
der VF-Einheit).
|
||||
|
||||
"auto" (statt einer Zahl, Gegenstueck zu vsp-eingang-hoehe in
|
||||
Lisp/vf_spec.lsp): die Frage kommt, aber es wird bewusst KEINE Zahl
|
||||
vorgegeben - wie eine leere Konsoleneingabe laesst das den
|
||||
Bauablauf seinen eigenen, aus dem Fussabdruck der festen Elemente
|
||||
berechneten Vorschlag nehmen. Dafuer steht ein NIL-Token (statt
|
||||
REAL) im Journal."""
|
||||
if self.art() == "REAL":
|
||||
self.sink.real_feld("eingang_hoehe", self.real(), "eingang_hoehe")
|
||||
elif self.art() == "NIL":
|
||||
self.nimm("NIL")
|
||||
self.sink.roh_feld("eingang_hoehe", "auto", "eingang_hoehe")
|
||||
|
||||
# --- Ablauf ---
|
||||
|
||||
|
||||
+109
-12
@@ -123,6 +123,50 @@ def gleich(a, b):
|
||||
return True
|
||||
|
||||
|
||||
def _tok_gleich(a, b, toleranz=1e-6):
|
||||
"""Zwei EINZELNE Tokens vergleichen wie gleich() - Zahlen numerisch.
|
||||
|
||||
`toleranz` ist Standard 1e-6 (praktisch exakt) fuer den Vergleich gegen
|
||||
volle Praezision. Wer eine GERUNDETE Seite vergleicht (z.B. hundm05.json
|
||||
nach runde_mm_felder, auf 1 Nachkommastelle), muss die Toleranz auf die
|
||||
Rundungsbreite anheben - sonst schlaegt der Vergleich an genau der
|
||||
Rundung fehl, nicht an einer echten Abweichung."""
|
||||
art1, w1 = a
|
||||
art2, w2 = b
|
||||
if art1 != art2:
|
||||
return False
|
||||
if art1 in ("REAL", "DL", "INT"):
|
||||
return abs(float(w1) - float(w2)) <= toleranz
|
||||
if art1 == "PT":
|
||||
p1 = [float(x) for x in w1.split(",")]
|
||||
p2 = [float(x) for x in w2.split(",")]
|
||||
return len(p1) == len(p2) and all(abs(x - y) <= toleranz
|
||||
for x, y in zip(p1, p2))
|
||||
return w1 == w2
|
||||
|
||||
|
||||
def ist_teilfolge(original, rekonstruiert, toleranz=1e-6):
|
||||
"""True, wenn jedes Token aus `original` in derselben Reihenfolge in
|
||||
`rekonstruiert` vorkommt - dazwischen darf `rekonstruiert` zusaetzliche
|
||||
Tokens enthalten.
|
||||
|
||||
Grund: journal_aus_spec haengt bei "Linie-GF" IMMER die drei neuen
|
||||
Pflichtfragen (laenge_modus, sep_vor, sep_nach) des heutigen
|
||||
vf-linienzug-modus an (siehe Kommentar in SpecSchreiber.sektion). Die 5
|
||||
echten HundM-Ketten (hm_recformat.json) wurden VOR dieser Erweiterung
|
||||
aufgezeichnet und enthalten sie darum nicht - ein exakter Rundlauf ist
|
||||
fuer sie seitdem prinzipiell unerreichbar, weil eine Replay durch den
|
||||
HEUTIGEN Bau-Ablauf diese Fragen real stellen wuerde. Das ist kein
|
||||
Datenverlust der Spec, sondern eine neue Frage des lebenden Ablaufs -
|
||||
darum genuegt hier: nichts vom Original fehlt oder weicht ab, zusaetzliche
|
||||
Tokens sind erlaubt."""
|
||||
i = 0
|
||||
for tok in rekonstruiert:
|
||||
if i < len(original) and _tok_gleich(original[i], tok, toleranz):
|
||||
i += 1
|
||||
return i == len(original)
|
||||
|
||||
|
||||
def erste_abweichung(a, b):
|
||||
"""Index und Beschreibung der ersten Abweichung (fuer Fehlermeldungen)."""
|
||||
for i in range(max(len(a), len(b))):
|
||||
@@ -406,9 +450,17 @@ class SpecSchreiber(object):
|
||||
elif label == "Horizontal-VF":
|
||||
self.linie(sek)
|
||||
# Optional: Ziel-Hoehe des GF1-Einlaufs - nur wenn der Eingang
|
||||
# komplett neu gebaut wird (siehe SpecLeser.opt_eingang_hoehe).
|
||||
# komplett neu gebaut wird (siehe Kern._opt_eingang_hoehe).
|
||||
# "auto" (Gegenstueck zu vsp-eingang-hoehe in Lisp/vf_spec.lsp):
|
||||
# die Frage steht zwar an, aber es wird bewusst KEINE Zahl
|
||||
# vorgegeben - ein NIL-Token statt REAL, der Bauablauf berechnet
|
||||
# dann seinen eigenen Vorschlag (wie bei leerer Konsoleneingabe).
|
||||
if "eingang_hoehe" in sek:
|
||||
self.zahl("REAL", sek["eingang_hoehe"])
|
||||
v = sek["eingang_hoehe"]
|
||||
if isinstance(v, str) and v.strip().upper() == "AUTO":
|
||||
self.raus("NIL", "")
|
||||
else:
|
||||
self.zahl("REAL", v)
|
||||
self.vf_einheit(sek, erst_hor=True)
|
||||
elif label == "Linie":
|
||||
self.linie(sek)
|
||||
@@ -428,8 +480,19 @@ class SpecSchreiber(object):
|
||||
def vf_einheit(self, sek, erst_hor):
|
||||
vf = self.feld(sek, "vf")
|
||||
# GF-Verteilung: bei horizontalem Erstkoerper stellt der heutige
|
||||
# Ablauf die Frage nicht mehr (fest "2") - siehe Grammatik.
|
||||
if not erst_hor or self.grammatik.horizontal_vf_fragt_gf_verteilung:
|
||||
# Ablauf die Frage nicht mehr (fest "2" = alles am Einlauf, siehe
|
||||
# Grammatik.horizontal_vf_fragt_gf_verteilung). Anders als beim
|
||||
# LESEN (Kern.vf_einheit in vf_linienzug_grammatik.py, wo die
|
||||
# Grammatik-Auspraegung ueber die naechste Tokenart entscheiden
|
||||
# muss) reicht hier die Feldpraesenz: die Spec liegt schon in
|
||||
# Domaenenwerten vor, das Fehlen von "gf_verteilung" IST die Aussage
|
||||
# "diese Frage kam nicht" - selbstbeschreibend statt an ein
|
||||
# externes Grammatik-Flag gebunden. Das erlaubt Specs mit und ohne
|
||||
# diese Frage nebeneinander (aeltere, aus der Aufzeichnung gelesene
|
||||
# Ketten haben sie; neu von Hand geschriebene fuer den heutigen
|
||||
# Ablauf lassen sie weg), ohne dass der Aufrufer die Grammatik kennen
|
||||
# muss.
|
||||
if not erst_hor or "gf_verteilung" in vf:
|
||||
self.raus("STR", code_aus_wert(VERTEILUNG,
|
||||
self.feld(vf, "gf_verteilung"),
|
||||
"gf_verteilung"))
|
||||
@@ -556,6 +619,32 @@ def flache_objekte(spec_id, spec, kopf_extra=None):
|
||||
return objekte
|
||||
|
||||
|
||||
# Felder, die eine Laenge/Koordinate in mm tragen (Winkel/Indizes/Flags
|
||||
# bleiben unberuehrt). Gerundet wird erst HIER, ganz am Ende - der
|
||||
# Rundlauf-Vergleich (gleich()) laeuft vorher gegen die volle Praezision aus
|
||||
# hm_recformat.json, sonst wuerde das Runden selbst als Abweichung auffallen.
|
||||
MM_FELDER = ("dl", "hoehe", "start_hoehe", "eingang_hoehe", "x", "y", "z",
|
||||
"abstand")
|
||||
|
||||
|
||||
def _gerundet(v, stellen=1):
|
||||
return round(v, stellen) if isinstance(v, float) else v
|
||||
|
||||
|
||||
def runde_mm_felder(objekte, stellen=1):
|
||||
"""dl/Punkte/Hoehen/Koordinaten auf `stellen` Nachkommastellen runden -
|
||||
sowohl in den generierten Sektionen/Subs als auch in den von Hand
|
||||
ergaenzten Objekten (fremde_objekte), die unveraendert durchgereicht
|
||||
werden."""
|
||||
for obj in objekte:
|
||||
for k, v in obj.items():
|
||||
if k == "start_punkt" and isinstance(v, list):
|
||||
obj[k] = [_gerundet(c, stellen) for c in v]
|
||||
elif k in MM_FELDER:
|
||||
obj[k] = _gerundet(v, stellen)
|
||||
return objekte
|
||||
|
||||
|
||||
def fremde_objekte(pfad):
|
||||
"""Objekte einer bestehenden Ziel-Datei, die KEINE Spec beschreiben.
|
||||
|
||||
@@ -665,17 +754,23 @@ def main():
|
||||
print("%s %-20s FEHLER: %s" % (PREFIX, tid, e))
|
||||
fehler += 1
|
||||
continue
|
||||
if not gleich(tok, zurueck):
|
||||
idx, was = erste_abweichung(tok, zurueck)
|
||||
print("%s %-20s RUNDLAUF WEICHT AB bei Token %s: %s"
|
||||
% (PREFIX, tid, idx, was))
|
||||
if not ist_teilfolge(tok, zurueck):
|
||||
print("%s %-20s RUNDLAUF WEICHT AB: die Spec bildet die "
|
||||
"Aufzeichnung nicht mehr verlustfrei ab (nicht nur um "
|
||||
"neue Pflichtfragen erweitert)" % (PREFIX, tid))
|
||||
fehler += 1
|
||||
continue
|
||||
# Nur "quelle" landet in der Spec - sie ist die einzige der
|
||||
# Protokoll-Kopf-Angaben, die keine Routine abfragt (Lisp-Seite:
|
||||
# vsp-gruppieren/vsp-praeambel lesen nur spec_id/modus/start_punkt/
|
||||
# start_hoehe/as[_winkel/_seite]). beschreibung/sivas_id/
|
||||
# sivas_bezeichnung/planquadrat/block/dxf_handle waren reine
|
||||
# Dokumentation (Planquadrat wird beim Export ohnehin neu bestimmt,
|
||||
# Block/Handle sind nach der Ableitung uninteressant) - "quelle"
|
||||
# bleibt als einzige Herkunftsangabe stehen.
|
||||
extra = collections.OrderedDict()
|
||||
for feld in ("beschreibung", "sivas_id", "sivas_bezeichnung",
|
||||
"planquadrat", "block", "dxf_handle", "quelle"):
|
||||
if feld in kopf:
|
||||
extra[feld] = kopf[feld]
|
||||
if "quelle" in kopf:
|
||||
extra["quelle"] = kopf["quelle"]
|
||||
# Die spec_id bleibt die Kettenkennung aus dem Protokoll: dieselbe
|
||||
# Kette soll in beiden Testlaeufen (Spec-Pfad und Aufzeichnungs-
|
||||
# Pfad) denselben Namen tragen, sonst laesst sich das Ergebnis
|
||||
@@ -702,6 +797,8 @@ def main():
|
||||
", ".join(str(o.get("id", "?")) for o in fremde)))
|
||||
alle.extend(fremde)
|
||||
|
||||
runde_mm_felder(alle)
|
||||
|
||||
# Letzte Kontrolle vor dem Schreiben - sie deckt auch die von Hand
|
||||
# ergaenzten Objekte ab, die nicht durch flache_objekte gelaufen sind.
|
||||
# Gueltiges JSON reicht hier NICHT (siehe pruefe_flachen_freitext): der
|
||||
|
||||
Reference in New Issue
Block a user