#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ test_lager_yaml.py - lib/lager_yaml_export.py (Lagerplanung -> RFQ-Overlay-YAML). Laeuft ohne BricsCAD: die Rohdaten sind von Hand nachgestellt, im Format von LAGER_EXPORT (Lisp/LagerInsert.lsp, lg-export-json). Geprueft wird: 1. levels: Belagshoehen, lichte Hoehe aus dem Geschoss darueber, Haengeebenen (Summe aller Reihen je Schienenhoehe/Warenlaenge, inkl. goh_rack-Bloecke), Belag/Gitterrost nur auf Buehnen. 2. layout: Bereichsparameter, Ganganzahl/-laenge, Huelle. 3. Provenienz: jeder Overlay-Wert hat einen Herkunfts-Eintrag. 4. Abgleich gegen cfg/example_goh_storage.yaml: Abweichungen -> Konflikte, null/fehlend -> gefuellt. 5. CLI schreibt YAML (wieder einlesbar) und Nachweis-HTML. """ import json import os import sys import pytest import yaml sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "lib")) import lager_yaml_export as lye # noqa: E402 BEISPIEL_YAML = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "cfg", "example_goh_storage.yaml") EBENEN_EG = [{"schienenhoehe_mm": 2300, "warenlaenge_mm": 1200}, {"schienenhoehe_mm": 1100, "warenlaenge_mm": 1100}] EBENEN_OG = [{"schienenhoehe_mm": 2800, "warenlaenge_mm": 1200}, {"schienenhoehe_mm": 1400, "warenlaenge_mm": 1200}] def _reihe(gs, laenge, ebenen, tiefe=700): return {"geschoss": gs, "laenge_mm": laenge, "tiefe_mm": tiefe, "haengeebenen": ebenen, "schienen": 1, "quelle": "auto", "bereich": 1} def _gang(gs, laenge, breite=1000, art="gang", belag="spanplatte"): return {"geschoss": gs, "laenge_mm": laenge, "breite_mm": breite, "art": art, "belag": belag, "bereich": 1} @pytest.fixture def raw(): """Zwei Geschosse: Hallenboden (EG) + Buehne auf 3100 mm, je ein 2-Reihen-Modul.""" return { "version": 1, "zeichnung": "test.dwg", "erzeugt": "20261021120000", "einheit": "mm", "land": "DE", "belag_yaml": {"spanplatte": "chipboard", "gitterrost": "grating", "hallenboden": ""}, "reihen_als_gitterrost": True, "geschosse": [ # bewusst nicht nach Hoehe sortiert - der Export sortiert selbst {"nr": 1, "bezeichnung": "Level 1", "hoehe_belag_mm": 3100, "konstr_hoehe_mm": 150, "lichte_hoehe_mm": None, "belag_art": "spanplatte", "personen": 20, "kontur_flaeche_mm2": 60e6, "kontur_bbox": [0, 0, 10000, 6000]}, {"nr": 0, "bezeichnung": "ground floor", "hoehe_belag_mm": 0, "konstr_hoehe_mm": 0, "lichte_hoehe_mm": None, "belag_art": "hallenboden", "personen": 20, "kontur_flaeche_mm2": 60e6, "kontur_bbox": [0, 0, 10000, 6000]}, ], "bereiche": [ {"nr": 1, "geschoss": 0, "gangbreite_mm": 1000, "regaltiefe_mm": 700, "reihen_je_gang": 2, "schienen_je_gang": 1}, {"nr": 2, "geschoss": 1, "gangbreite_mm": 1000, "regaltiefe_mm": 700, "reihen_je_gang": 2, "schienen_je_gang": 1}, ], "reihen": [_reihe(0, 10000, EBENEN_EG), _reihe(0, 10000, EBENEN_EG), _reihe(1, 10000, EBENEN_OG), _reihe(1, 10000, EBENEN_OG)], "gaenge": [_gang(0, 10000), _gang(1, 10000), _gang(1, 700, breite=1200, art="quergang"), _gang(1, 500, breite=1200, art="quergang", belag="gitterrost")], "goh_racks": [{"geschoss": 1, "name": "goh_rack", "laenge_mm": 5000, "ebenen": 2, "tiefe_mm": 700, "haengeebenen": EBENEN_OG, "haengeebenen_quelle": "vorgabe"}], "warnungen": ["Testwarnung"], } def test_levels_sortiert_und_hoehen(raw): overlay, _ = lye.aggregiere(raw) lv = overlay["levels"] assert [l["floor_elevation_m"] for l in lv] == [0.0, 3.1] assert lv[0]["remark"] == "ground floor" # lichte Hoehe EG = 3100 - 150 (Konstruktion darueber) - 0 assert lv[0]["clear_height_m"] == pytest.approx(2.95) # oberstes Geschoss ohne LICHTE_HOEHE_mm -> nicht berechenbar assert "clear_height_m" not in lv[1] def test_haengeebenen_summe_inkl_goh_rack(raw): overlay, _ = lye.aggregiere(raw) eg, og = overlay["levels"] assert eg["hanging_levels"] == [ {"rail_height_m": 2.3, "rack_length_m": 20.0, "goods_length_m": 1.2}, {"rail_height_m": 1.1, "rack_length_m": 20.0, "goods_length_m": 1.1}, ] # 2 Reihen a 10 m + goh_rack 5 m, oberste Schiene zuerst assert [h["rack_length_m"] for h in og["hanging_levels"]] == [25.0, 25.0] assert og["hanging_levels"][0]["rail_height_m"] == 2.8 def test_belag_nur_auf_buehne(raw): overlay, _ = lye.aggregiere(raw) eg, og = overlay["levels"] assert "walkway" not in eg and "grating" not in eg # Belag: Hauptgang 10 m2 + Quergang Spanplatte 0,84 m2 assert og["walkway"]["area_m2"] == pytest.approx(10.8) assert og["walkway"]["spec"] == "chipboard" # Gitterrost: Quergang 0,6 m2 + Reihen 2 x 7 m2 + goh_rack 3,5 m2 assert og["grating"] == pytest.approx(18.1) def test_belag_ohne_gaenge_aus_kontur(raw): raw["gaenge"] = [] overlay, s = lye.aggregiere(raw) og = overlay["levels"][1] # 60 m2 Kontur - (2 x 7 + 3,5) m2 Reihen assert og["walkway"]["area_m2"] == pytest.approx(42.5) assert "Konturflaeche" in s.provenance["levels[1].walkway.area_m2"]["formel"] def test_layout(raw): overlay, _ = lye.aggregiere(raw) lay = overlay["layout"] assert lay["aisle_width_m"] == 1.0 assert lay["rack_rows_per_aisle"] == 2 assert lay["rack_depth_m"] == 0.7 assert lay["trolley_rails_per_aisle"] == 1 assert lay["aisles"] == 1 # Quergaenge zaehlen nicht assert lay["aisle_length_m"] == 10.0 assert lay["block_length_m"] == 10.0 assert lay["block_width_m"] == 6.0 def test_randgang_im_belag_nicht_in_aisles(raw): # Randgang 10 m x 1 m (Spanplatte) im OG: Belag waechst, aisles bleibt raw["gaenge"].append(_gang(1, 10000, art="randgang")) overlay, s = lye.aggregiere(raw) og = overlay["levels"][1] assert og["walkway"]["area_m2"] == pytest.approx(20.8) assert overlay["layout"]["aisles"] == 1 assert overlay["layout"]["aisle_length_m"] == 10.0 assert "Randgaenge G1: 10.0 m" in s.provenance["layout.aisles"]["formel"] def test_unterschiedliche_bereiche_warnen(raw): raw["bereiche"][1]["gangbreite_mm"] = 1200 raw["bereiche"].append(dict(raw["bereiche"][1])) overlay, s = lye.aggregiere(raw) assert overlay["layout"]["aisle_width_m"] == 1.2 assert any("aisle_width_m" in w for w in s.warnungen) def test_provenienz_vollstaendig(raw): ausgabe, _ = lye.baue_ausgabe(raw) # hanging_levels: Provenienz je rack_length_m; Schienenhoehe/Warenlaenge # sind der Gruppenschluessel derselben Zeile und stehen in deren Formel. fehlend = [p for p, _ in lye._blaetter(ausgabe) if p not in ausgabe["_provenance"] and not (".hanging_levels[" in p and not p.endswith(".rack_length_m"))] assert fehlend == [] assert ausgabe["_warnings"][0] == "Testwarnung" def test_abgleich_mit_beispiel_yaml(raw): with open(BEISPIEL_YAML, encoding="utf-8") as f: hand = yaml.safe_load(f) konflikte, fuellt = lye.abgleich(lye.aggregiere(raw)[0], hand) pfade = {k["pfad"]: k for k in konflikte} # Hand 4 Ebenen, Zeichnung 2 assert "levels" in pfade # Hand 1,0 m Gangbreite = Zeichnung -> kein Konflikt assert "layout.aisle_width_m" not in pfade # Hand 4286 m2, Zeichnung 10,8 m2 assert pfade["levels[1].walkway.area_m2"]["hand"] == 4286 assert pfade["levels[1].walkway.area_m2"]["abweichung_prozent"] < -99 # Hand null -> Zeichnung fuellt gef = {f["pfad"] for f in fuellt} assert "layout.block_length_m" in gef assert "levels[1].grating" in gef def test_hole_pfad(): obj = {"levels": [{"a": 1}, {"walkway": {"area_m2": 5}}]} assert lye._hole(obj, "levels[1].walkway.area_m2") == (True, 5) assert lye._hole(obj, "levels[2].walkway") == (False, None) assert lye._hole(obj, "layout.x") == (False, None) def test_cli(raw, tmp_path): rp = tmp_path / "x_lager_raw.json" rp.write_text(json.dumps(raw), encoding="cp1252") yp = tmp_path / "x_drawing.yaml" hp = tmp_path / "x_nachweis.html" rc = lye.main([str(rp), str(yp), "--hand", BEISPIEL_YAML, "--nachweis", str(hp)]) assert rc == 0 daten = yaml.safe_load(yp.read_text(encoding="utf-8")) assert daten["levels"][1]["floor_elevation_m"] == 3.1 assert daten["_conflicts"] assert "levels[0].floor_elevation_m" in daten["_provenance"] assert "Lager-Nachweis" in hp.read_text(encoding="utf-8") def test_cli_fehlende_datei(tmp_path): assert lye.main([str(tmp_path / "fehlt.json"), str(tmp_path / "o.yaml")]) == 2