Anhang B — Rechenkern
Dieser Anhang druckt den Code ab, der sämtliche Zahlen des Statik-Dossiers erzeugt. Er läuft bei jedem Build und unverändert im Browser (Statik-Explorer). Lesereihenfolge: Eingabewerte in der Datenquelle, daraus abgeleitete Masse, dann Formeln und Nachweise; die Tests halten die Goldwerte fest, gegen die jede Änderung geprüft wird. Prüfsummen (SHA-256) identifizieren jede Datei eindeutig — die Kurzform steht auch im Dokument selbst.
| Nr. | Datei | Inhalt | Zeilen | SHA-256 (Anfang) |
|---|---|---|---|---|
| B.1 | projekt.toml | Datenquelle — alle Eingabewerte (Masse, Material, Lasten, Befestigung, Bedingungen) | 344 | 320399cb755f79f5 |
| B.2 | src/statik/geometrie.ts | Abgeleitete Geometrie aus den Primärmassen | 58 | 85ff6ffd1334a2b8 |
| B.3 | src/statik/rechenkern.ts | Rechenkern — Lasten, Seilmodell, Nachweise, Verbindungsmittel, Mengen | 317 | 19e0f3cdf03da4cb |
| B.4 | kit/src/lib/raster.ts | Rasterpositionen (Kit) — Schrauben- und Augplattenreihen | 29 | 86083242cdb1a8f0 |
| B.5 | src/statik/rechenkern.test.ts | Tests — Goldwerte und Grenzfälle | 188 | 307f55aba0b05eb2 |
| Gesamt | Prüfsumme über alle Dateien | 936 | 29ccb2a75bbf5999 |
Ausführen: npm test prüft den Rechenkern gegen die Goldwerte, npm run build erzeugt Dokumente, Abbildungen und PDFs neu. Alle Resultate des aktuellen Builds: results.json. Gesamt-Prüfsumme 29ccb2a75bbf599930124eebdc2627aba6607234b9057d7b12c5580490def614.
B.1 projekt.toml
Datenquelle — alle Eingabewerte (Masse, Material, Lasten, Befestigung, Bedingungen) · 344 Zeilen · SHA-256 320399cb755f79f5d96baf753a14c0885ea900f10b24f8d901139c07dd77cbd7
# ============================================================================
# Hochsitz mit Netz — DIE Datenquelle des Projekts.
# Alles, was in Dokumenten, 3D-Modell, Explorer und Stückliste als Zahl oder
# Text erscheint, kommt von hier oder wird im Rechenkern daraus abgeleitet.
# Masse in cm (Geometrie), Kräfte in kN, Spannungen in N/mm², Gewichte in kg.
# Platzhalter in Texten: {g.luecke_cm}, {r.eta|pct}, {p.gelaender.anker_anforderung_kn}
# ============================================================================
[projekt]
kuerzel = "hochsitz"
titel = "Hochsitz mit Netz"
untertitel = "Kletternetz zwischen den Hochsitzen im Effinger"
ort = "Effinger Bern"
traeger = "YOLU"
kicker = "YOLU · Effinger Bern · Jugendprojekt"
lead = "Ein geknüpftes Kletternetz zwischen den beiden Hochsitzen im Effinger — gemeinsam geplant, transparent gerechnet und im Workshop selbst gebaut. Hier ist das ganze Projekt: zum Drehen, Nachrechnen, Prüfen und Bauen."
verfasst = "Projektteam «Hochsitz mit Netz» (YOLU), rechnerische Aufbereitung mit transparentem Rechenkern"
stand = 2026-09-01
eckdaten = [
"Workshop {p.termine.workshop|datumLang}",
"Rahmen {g.rahmen_x_cm} × {g.rahmen_z_cm} cm · Netz ≈ {g.netz_m2|m2}",
"Netzebene {g.ok_rahmen_cm} cm",
]
fusszeile = "Erstellt vom Projektteam (YOLU) mit Unterstützung von Claude."
[termine]
workshop = 2026-09-04
[modell]
titel = "3D-Modell"
kategorie = "Modell"
beschreibung = "Drehen, zoomen, Ebenen ein- und ausblenden — bis zur einzelnen Schraube"
[explorer]
titel = "Statik-Explorer"
kategorie = "Werkzeug"
beschreibung = "Wie stark biegt sich das Netz mit drei Personen durch? Schieberegler für Personen, Knüpf-Durchhang und Randabstand — alle Kräfte und Nachweise rechnen live mit."
# ---------------------------------------------------------------------------
# Dokumente: Version, Stand, Status und Änderungsverzeichnis je Dokument.
# Hauptversion steigt bei Lasten/Geometrie/Nachweisen/Bedingungen (neue Prüfung),
# Nebenversion bei redaktionellen Klarstellungen.
# Konvention seit 3.0: alle Dokumente tragen dieselbe Versionsnummer wie das Statik-Dossier;
# springt das Dossier, ziehen die anderen mit (Eintrag «an das Dossier angeglichen»).
# ---------------------------------------------------------------------------
[dokumente.statik-dossier]
titel = "Statik-Dossier"
kategorie = "Dokument"
beschreibung = "Das massgebende Dokument zur Prüfung: Lasten, Nachweise, Schraubenplan, Bedingungen {B} — mit offenem Rechenkern und Prüf-Checkliste für die Fachperson."
version = "3.0"
stand = 2026-09-02
status = "zur-pruefung"
aenderungen = [
{ version = "3.0", datum = 2026-09-02, was = "Holz-Bemessungswerte korrigiert (Hinweis der prüfenden Fachperson): Nutzlasten durch Personen zählen nach EC5 als mittlere Einwirkungsdauer, k_mod = 0.8 statt 0.9 (kurz), Nutzungsklasse 1/2 (im Gebäude). f_m,d damit 14.8 statt 16.6 N/mm²; Ausnutzung des Längsbalkens 86 statt 76 % (Varianten Knüpfmass 0/5/10 cm: 86/85/84 %) — Nachweis erfüllt, aber neu im gelben Bereich (80–100 %, kleine Reserve). Auflagerpressung (7 statt 6 %) und Blattschulter (unter 5 %) bleiben unkritisch; alle übrigen Nachweise unverändert. Ampel-Schwellen (gelb ab 80 %, rot ab 100 %) neu als Daten in projekt.toml, Legende unter der Nachweistabelle; die Aussage «alle Nachweise erfüllt» gilt bis 100 % Ausnutzung, nicht nur bei Grün. Schraube S2 auf Hinweis des Schreiners weiter nach vorne: Kopf 3.2 statt 4 cm hinter der Frontkante (= 4d Randabstand, näher geht nicht); sie kreuzt die Blattfuge bei z = 6.7 statt 7.5 cm und tritt bei z = 10.1 statt 10.9 cm in die Deckplatte ein — mehr Reserve, damit sie bei 30° sicher im unteren Blatt des Längsbalkens bleibt. Länge, Neigung und Spitzenlage unverändert." },
{ version = "2.1", datum = 2026-09-02, was = "Anhang B neu als Prüfsummen-Tabelle (Datei, Zeilen, SHA-256); der vollständige Quelltext — Datenquelle, Geometrie, Rechenkern, Raster, Tests — erscheint als separater Anhang B (eigenes PDF und Webseite /quelltext/) mit Syntaxhervorhebung und Zeilennummern. Das Dossier-PDF wird dadurch sechs Seiten kürzer. Inhaltlich unverändert." },
{ version = "2.0", datum = 2026-09-01, was = "Neuaufbau aus einer Datenquelle (projekt.toml) mit Rechenkern in TypeScript; ersetzt Version 1.3 vom 31.08.2026. Annahmen, Lasten und Nachweise inhaltlich unverändert; Ausnutzungen neu direkt aus dem Rechenkern gerundet (Augplatte 23 statt 24 %). Gebrauchslage 1 Person neu mit symmetrischer Lastaufteilung (H längs 1.63 statt 1.53 kN) und korrekter lichter Höhe ab Knüpfebene 208 cm (186 statt 181 cm). Höhenbezug auf OK Podest vereinheitlicht (OK Deckplatte 200): Geländerseil in Feldmitte 90 statt ~95 cm über OK Rahmen; Kabinendach +12 statt +17 cm über Deckplatte (B5) — vor Ort nachmessen. Verbindungsmittel vereinfacht: zwei Schrauben je Ecke (S1 senkrecht, S2 8×180 um 30° geneigt, Kopf 4 cm hinter der Kante, damit sie die Blattfuge kreuzt und das Blatt des Längsbalkens durchläuft; Köpfe bündig, nicht versenkt) und zwei zusätzliche je Querbalken statt drei je Ecke und alle 50 cm; Ringdruck formschlüssig über die Blattschultern; Abhebekraft aus dem Kippmoment des Längsbalkens hergeleitet; neuer Nachweis Verschieben des Rahmens; geometrische Prüfung der Schraubenlage im Rechenkern. Augplatten (Decksplatten) neu verteilt: Reihen beginnen in den Ecken mit zwei Platten je Ecke (eine je Balkenseite, Randseil in zwei Mal 45° umgelenkt), Mindestabstand 30 cm am Netzrand und 60 cm am Geländer: 50 statt 67 Platten. Stahlsäule als doppelter H-Träger; Geländer-Deckenvariante mit vertikalen Randseilen an den Netzenden." },
]
[dokumente.bauanleitung]
titel = "Bauanleitung Workshop"
kurz = "Bauanleitung"
kategorie = "Workshop"
untertitel = "Hochsitz mit Netz · Effinger · {p.termine.workshop|datumLang}"
beschreibung = "Rollen, Zuschnitt, Schrauben- und Augplattenplan, Ablauf mit Kontrollpunkten und Materialliste für den Bau-Workshop — inklusive der wichtigsten Regel: zuerst vor Ort messen, dann sägen."
version = "3.0"
stand = 2026-09-02
status = "entwurf"
grundlage = "Grundlage: Statik-Dossier v{p.dokumente.statik-dossier.version} (massgebend)"
aenderungen = [
{ version = "3.0", datum = 2026-09-02, was = "Schraubenplan: Kopf von S2 neu 3.2 statt 4 cm hinter der Frontkante (Hinweis Schreiner); Zeichnungen und 3D-Ansichten folgen der Datenquelle. Versionsnummer an das Statik-Dossier angeglichen — ab jetzt tragen alle Dokumente dieselbe Nummer." },
{ version = "2.1", datum = 2026-09-02, was = "Für den Workshop eigenständig lesbar: Schraubenplan, Augplattenplan und die zugehörigen Abbildungen aus dem Dossier eingebettet (dieselbe Datenquelle), Ablauf in sechs Schritte mit Kontrollpunkten gegliedert, Materialliste als Anhang. Inhaltlich unverändert." },
{ version = "2.0", datum = 2026-09-01, was = "Neuaufbau aus der Datenquelle; ersetzt Version 1.1 vom 30.08.2026. An das Statik-Dossier angeglichen: Augplatten statt Ösen, Geländer-Deckenvariante ohne Abhängungen, Pfosten nur noch als Variante." },
]
[dokumente.betriebskonzept]
titel = "Betriebs- und Wartungskonzept"
kurz = "Betriebskonzept"
kategorie = "Betrieb"
untertitel = "Hochsitz mit Netz · Effinger"
beschreibung = "Nutzungsregeln, Aushang für die Aufstiege, Inspektionsplan mit Protokoll und das Erstabnahme-Protokoll für die Freigabe."
version = "3.0"
stand = 2026-09-02
status = "entwurf"
grundlage = "Tritt in Kraft mit Freigabe des Statik-Dossiers + Erstabnahme"
aenderungen = [
{ version = "3.0", datum = 2026-09-02, was = "Versionsnummer an das Statik-Dossier angeglichen — ab jetzt tragen alle Dokumente dieselbe Nummer. Inhaltlich unverändert." },
{ version = "2.0", datum = 2026-09-01, was = "Neuaufbau aus der Datenquelle; ersetzt Version 1.0 vom 30.08.2026. Erstabnahme-Protokoll an das Statik-Dossier angeglichen (Augplatten, Deckenvariante)." },
]
# ---------------------------------------------------------------------------
# Geometrie (cm). Höhenbezug: OK Podest = 0. Achsen wie im 3D-Modell:
# x = Breite (links → rechts), y = Höhe, z = Tiefe (Rückwand → Front).
# ---------------------------------------------------------------------------
[geometrie]
kasten_links_tiefe_cm = 130 # Kasten links, x von 0 bis 130
luecke_cm = 328 # freie Spannweite zwischen den Kästen
kasten_rechts_tiefe_cm = 70 # Kasten rechts
kasten_tiefe_cm = 235 # Tiefe der Kästen (Rückwand → Front) = Querbalkenlänge
wand_cm = 5 # Dreischichtplatten der Kästen (Wände und Deckplatte)
ok_deckplatte_cm = 200 # Oberkante Deckplatte ab Podest
balken_cm = 12 # Balkenquerschnitt 12 × 12
raum_hoehe_cm = 350 # Decke ab Podest
podest_cm = 5 # Holzpodest, auf dem die Kästen stehen
lattenwand_hoehe_cm = 104 # Lattenwand hinten, ab Deckplatte
netz_seillage_cm = 4 # Knüpfebene liegt so viel unter OK Rahmen
netz_durchhang_modell_cm = 16 # dargestellter Durchhang im 3D-Modell (unbelastet, dekorativ)
rohlaenge_balken_cm = 400 # Ausgangslänge der Balken
[geometrie.kabine]
anzahl = 2
breite_cm = 100
tiefe_cm = 100
hoehe_cm = 217
# ---------------------------------------------------------------------------
# Material
# ---------------------------------------------------------------------------
[holz]
art = "Fichte/Tanne"
sortierung = "C24"
f_mk = 24.0 # Biegung, charakteristisch
f_vk = 4.0 # Schub
f_c0k = 21.0 # Druck parallel
f_c90k = 2.5 # Druck quer
rho_kn_m3 = 4.2 # Eigengewicht C24 (~420 kg/m³)
k_mod = 0.8 # mittlere Einwirkungsdauer (Nutzlast durch Personen), Nutzungsklasse 1/2 (im Gebäude); bis v2.1 fälschlich 0.9 (kurz)
gamma_m = 1.3 # Vollholz
[netz]
seil = "Paracord 550"
durchmesser_mm = 4
ea_kn = 8 # Dehnsteifigkeit je Seil (Literatur-/Erfahrungswert, B3 kalibrieren)
n_wirksam = 10 # wirksame Seile im Lastband
bruchlast_kn = 2.4
zulaessig_kn = 0.6 # abgemindert für Knoten und Dauerhaftigkeit
masche_cm = 10
eigengewicht_kg = 15
[netz.randseil]
name = "Tendon PRO WORK 11 mm"
norm = "EN 1891 A"
mbl_kn = 33
[augplatte]
name = "Augplatte 50×40 V2A (Decksplatte)"
kurz = "Augplatte 50×40"
werkstoff = "Edelstahl V2A AISI 304, geschlossener Bügel"
schrauben = "4× Linsenkopfschrauben 5×50"
bruch_kn = 16.2
sicherheitsfaktor = 3
pruefbericht = "210528-1"
pruefbericht_quelle = "Drahtseile24, 31.05.2021"
pruef_holz = "Kiefer"
# ---------------------------------------------------------------------------
# Lasten und Bemessungsszenario
# ---------------------------------------------------------------------------
[lasten]
erwachsene_kg = 85
jugendliche_kg = 55
kind_kg = 30
stossfaktor_kind = 2.5 # springende Kinder
gamma_q = 1.5
lastband_m = 1.2 # wirksame Lastbandbreite
knuepfmass_cm = 0 # Durchhang ohne Last (Knüpfmass) im Bemessungsfall
ungleichverteilung = 1.3 # +30 % auf die Kraft je Randbefestigung
rahmenanteil_kn = 0.3 # Anteil Rahmen-Eigengewicht je Auflagerecke
lichte_hoehe_min_cm = 170
probebelastung_kg = 80
probebelastung_soll_cm = [20, 25]
[lasten.szenario]
erwachsene = 1
jugendliche = 1
kinder = 1
[ampel] # Farbstufen der Nachweise (Dossier-Tabelle, Explorer), Ausnutzung η = Einwirkung / Widerstand
gelb_ab = 0.80 # ab hier gelb: erfüllt, aber kleine Reserve
rot_ab = 1.00 # ab hier rot: Nachweis nicht erfüllt
[gelaender]
variante = "decke" # decke | pfosten
holmlast_kn_m = 0.8 # SIA 358
einzellast_kn = 1.5 # angesetzter Punktstoss
auslenkung_m = 0.30 # seitliche Auslenkung des weichen Seils
auslenkung_weich_m = 0.50
seil_laenge_m = 5.16
anker_y_cm = 344 # bestehende Vorhangseil-Anker nahe Decke
mitte_y_cm = 302 # Seil in Feldmitte
anker_anforderung_kn = 10
schlupf_breite_cm = 50
schlupf_hoehe_cm = 75
leiter_erste_sprosse_cm = 55
pfosten_hoehe_cm = 104
bruestung_en1176_cm = 70
# ---------------------------------------------------------------------------
# Befestigung
# ---------------------------------------------------------------------------
[befestigung]
netzrand_abstand_cm = 30 # MINDESTabstand der Augplatten am Netzrand; Reihen beginnen in den Ecken (zwei Platten je Ecke, eine je Balkenseite), gleichmässig verteilt
gelaender_abstand_cm = 60 # Mindestabstand der Augplatten am Geländer (kleine Lasten; Feinverteilung vor Ort)
kastenwand_zul_nmm2 = 2.0 # knickbegrenzt, konservativ
[befestigung.ecke]
s1 = "VG 8×160" # senkrecht durchs Blatt, in die Deckplatte
s2 = "VG 8×180" # geneigt, kreuzt die Blattfuge, durchläuft das Blatt des Längsbalkens, endet in der Deckplatte
neigung_grad = 30 # Neigung von S2 in Tiefenrichtung zum Kasteninneren
vorbohren_mm = 5
abstand_min_cm = 3.2 # 4d
kopf_versenkt_cm = 0.0 # Köpfe bündig, NICHT versenken: die Längen sind auf die 5-cm-Deckplatte abgestimmt (S1 endet 1 cm über deren Unterseite)
# Lage der Schraubenköpfe im 12×12-Blatt je Ecke: [x ab kastenseitiger Balkenkante, z ab Frontkante] in cm
# S2 muss so weit vorne starten, dass sie unterhalb der Blattfuge noch im Blatt des Längsbalkens (z < 12) läuft: prüft r.verbindung.
# Kopf S2 bei 3.2 cm = 4d: näher an die Kante geht nicht ohne den Mindestrandabstand zu verletzen (Hinweis Schreiner: möglichst weit vorne)
positionen = { s1 = [4.0, 8.5], s2 = [8.0, 3.2] }
[befestigung.querbalken]
schraube = "VG 8×160"
anzahl = 2 # zusätzliche Schrauben je Querbalken, gleichmässig zwischen den Ecken
[befestigung.widerstand] # Richtwerte EC5, C24, vorgebohrt — von der Fachperson zu bestätigen
s1_axial_kn = 1.9 # S1 axial: 8 mm, ~3.5 cm Gewinde in der 5-cm-Dreischichtplatte (f_ax,k ≈ 10 N/mm²) — massgebend fürs Abheben
schraube_abscheren_kn = 2.0 # VG 8 mm, Abscheren in der Deckplatte (Verschieben des Rahmens)
gelaender_anteil_rahmen = 0.5 # Anteil der Geländer-Linienlast, der über die untere Plattenreihe in den Rahmen geht (konservativ)
# ---------------------------------------------------------------------------
# Quelltext-Anhang (Anhang B des Statik-Dossiers). Im Dossier steht nur die Prüfsummen-
# Tabelle; der vollständige Code erscheint auf /quelltext/ und als separates PDF.
# Pfade relativ zum Projektordner; Reihenfolge = Lesereihenfolge.
# ---------------------------------------------------------------------------
[quelltext]
dokument = "statik-dossier"
anhang = "B"
titel = "Rechenkern"
dateien = [
{ pfad = "projekt.toml", titel = "Datenquelle — alle Eingabewerte (Masse, Material, Lasten, Befestigung, Bedingungen)" },
{ pfad = "src/statik/geometrie.ts", titel = "Abgeleitete Geometrie aus den Primärmassen" },
{ pfad = "src/statik/rechenkern.ts", titel = "Rechenkern — Lasten, Seilmodell, Nachweise, Verbindungsmittel, Mengen" },
{ pfad = "../../kit/src/lib/raster.ts", titel = "Rasterpositionen (Kit) — Schrauben- und Augplattenreihen" },
{ pfad = "src/statik/rechenkern.test.ts", titel = "Tests — Goldwerte und Grenzfälle" },
]
# ---------------------------------------------------------------------------
# Bedingungen (Kapitel 9). Nummerierung stabil halten, nie stillschweigend ändern.
# ---------------------------------------------------------------------------
[[bedingungen]]
nr = "B1"
text = "Deckenanker: Tragfähigkeit der bestehenden Vorhangseil-Verankerung (geschweisste Säulenklammer, Gewinde im Wandträger) für ≥ {p.gelaender.anker_anforderung_kn} kN je Ende verifizieren (Probezug). Kein Bohren in den brandschutzummantelten Stahlträger ohne Freigabe von Eigentümer und Brandschutz-Fachfirma. Fällt B1 negativ aus → Pfostenvariante (Kap. 8)"
verantwortung = "Projektteam + Fachperson"
[[bedingungen]]
nr = "B2"
text = "Anker Rückwand oben für das Geländer links (Verbundanker im Mauerwerk «nicht extrem stabil» — Probezug); Eck-Augbolzen an Säule/Wand als durchgesteckte Maschinenschrauben ausführen"
verantwortung = "Projektteam"
[[bedingungen]]
nr = "B3"
text = "Probebelastung nach dem Knüpfen (1 Person mittig, Durchhang messen, Soll {p.lasten.probebelastung_soll_cm.0}–{p.lasten.probebelastung_soll_cm.1} cm); Abweichung > 30 % → Rückmeldung an Rechenkern"
verantwortung = "Projektteam + Fachperson"
[[bedingungen]]
nr = "B4"
text = "Fallschutz Kletterwand: keine losen Matten (Stolperfallen). Stattdessen: Tritt-Griffe so setzen, dass die Füsse max. ~1 m über Boden stehen; optional flache Fallschutzplatten (30–45 mm, angeschrägte Ränder, kritische Fallhöhe ≥ 1 m) oder tiefe Polster-Sitzlandschaft als Möbel im Fallraum"
verantwortung = "Projektteam"
[[bedingungen]]
nr = "B5"
text = "Seitlicher Rand links durch Telefonkabinen (Dach +{g.kabine_ueberstand_cm} cm über Deckplatte): als Absturzschutz-Ersatz durch Fachperson beurteilen; Kabinenposition sichern/markieren"
verantwortung = "Fachperson"
[[bedingungen]]
nr = "B6"
text = "Betriebskonzept: Nutzungsregeln, max. Personen, Inspektionsplan mit Protokoll, verantwortliche Person"
verantwortung = "Projektteam"
[[bedingungen]]
nr = "B7"
text = "Holzqualität der vorhandenen Balken vor Einbau sichten (grosse Äste/Risse aussortieren; Annahme {p.holz.sortierung})"
verantwortung = "Zimmerleute im Team"
# ---------------------------------------------------------------------------
# Material- und Einkaufsliste (Anhang A). menge_aus verweist auf r.mengen.<key>.
# ---------------------------------------------------------------------------
[[material]]
pos = "VG-Schraube 8×160, Torx, vorbohren Ø 5"
menge_aus = "schrauben_8x160"
bemerkung = "{r.mengen.ecken}× Ecken (S1) + {r.mengen.querbalken_schrauben}× Querbalken; Reserve dazu"
gruppe = "befestigung"
[[material]]
pos = "VG-Schraube 8×180, Torx"
menge_aus = "schrauben_8x180"
bemerkung = "{r.mengen.ecken}× Ecken (S2, {p.befestigung.ecke.neigung_grad}° geneigt); Reserve dazu"
gruppe = "befestigung"
[[material]]
pos = "Augplatte 50×40 V2A (Decksplatte) inkl. 4× Linsenkopf 5×50"
menge_aus = "augplatten"
bemerkung = "Netzrand {r.mengen.augplatten_netzrand} (zwei je Ecke, Abstand {r.mengen.rand_abstand_x_cm|z1} längs / {r.mengen.rand_abstand_z_cm|z1} quer) + Geländer {r.mengen.augplatten_gelaender} ({r.mengen.gelaender_abstand_front_cm|z0} cm); Reserve dazu"
gruppe = "befestigung"
[[material]]
pos = "Wantenspanner M12 (Auge–Auge)"
menge = 2
bemerkung = "Nachspannen der Geländerseile"
gruppe = "befestigung"
[[material]]
pos = "Augbolzen M10 durchgesteckt / Verbundanker"
menge = 3
bemerkung = "Ecklösungen Säule/Wand, Anker Rückwand oben (B2)"
gruppe = "befestigung"
[[material]]
pos = "Augbolzen M12 durchgesteckt, grosse U-Scheiben"
menge = 4
bemerkung = "Seilenden an Pfosten (nur Pfostenvariante)"
gruppe = "variante"
[[material]]
pos = "Trägerklammer/Balkenklemme (Säule H-Profil)"
menge = 2
bemerkung = "Pfosten links an Stahlsäule, ohne Bohren (nur Pfostenvariante)"
gruppe = "variante"
[[material]]
pos = "Verbundanker M10 mit Siebhülse"
menge = 2
bemerkung = "Wand rechts (oder Verschraubung in Kastenwand)"
gruppe = "befestigung"
[[material]]
pos = "Holzbohrer Ø 5, Torx-Bits T40, Zollstock, Wasserwaage"
einheit = ""
bemerkung = "Werkzeug Workshop"
gruppe = "werkzeug"
[[material]]
pos = "Fallschutzplatten 30–45 mm, angeschrägt (optional, B4)"
menge = 2.5
einheit = "m²"
bemerkung = "flach begehbar statt loser Matten; Alternative: Polster-Sitzlandschaft"
gruppe = "optional"B.2 src/statik/geometrie.ts
Abgeleitete Geometrie aus den Primärmassen · 58 Zeilen · SHA-256 85ff6ffd1334a2b8bd31bcbd7e7a83a5a88cc896a33f0dff976608c79480428c
/**
* Abgeleitete Geometrie — alles, was aus den Primärmassen in projekt.toml folgt.
* Wird von Rechenkern, 3D-Szene, Zeichnungen und Dokumenten gemeinsam benutzt.
* Koordinaten: x Breite (0 = linke Aussenkante Kasten links), y Höhe (0 = OK Podest), z Tiefe (0 = Rückwand).
*/
import type { Hochsitz } from '../schema';
export function ableiten(p: Hochsitz) {
const G = p.geometrie;
const b = G.balken_cm;
const x_l0 = 0;
const x_l1 = G.kasten_links_tiefe_cm;
const x_r0 = x_l1 + G.luecke_cm;
const x_r1 = x_r0 + G.kasten_rechts_tiefe_cm;
const rahmen_x0 = x_l1 - b;
const rahmen_x1 = x_r0 + b;
const ok_rahmen_cm = G.ok_deckplatte_cm + b;
const netzfeld_x_cm = G.luecke_cm;
const netzfeld_z_cm = G.kasten_tiefe_cm - 2 * b;
return {
b,
x_l0, x_l1, x_r0, x_r1, rahmen_x0, rahmen_x1,
breite_total_cm: x_r1,
luecke_cm: G.luecke_cm,
tiefe_cm: G.kasten_tiefe_cm,
wand_cm: G.wand_cm,
rahmen_x_cm: G.luecke_cm + 2 * b,
rahmen_z_cm: G.kasten_tiefe_cm,
netzfeld_x_cm,
netzfeld_z_cm,
netz_m2: (netzfeld_x_cm * netzfeld_z_cm) / 1e4,
ok_deckplatte_cm: G.ok_deckplatte_cm,
ok_rahmen_cm,
knuepfebene_cm: ok_rahmen_cm - G.netz_seillage_cm,
kopffreiheit_cm: G.raum_hoehe_cm - ok_rahmen_cm,
raum_hoehe_cm: G.raum_hoehe_cm,
kastenwand_hoehe_cm: G.ok_deckplatte_cm - G.wand_cm,
lattenwand_ok_cm: G.ok_deckplatte_cm + G.lattenwand_hoehe_cm,
auflage_cm: b,
blatt_cm: b / 2,
// Kabinen stehen neben dem Podest auf dem Rohboden (podest_cm unter OK Podest); Dachhöhe über OK Podest = hoehe − podest
kabine_ok_cm: G.kabine.hoehe_cm - G.podest_cm,
kabine_ueberstand_cm: G.kabine.hoehe_cm - G.podest_cm - G.ok_deckplatte_cm,
balken_rest_laengs_cm: G.rohlaenge_balken_cm - (G.luecke_cm + 2 * b),
balken_rest_quer_cm: G.rohlaenge_balken_cm - G.kasten_tiefe_cm,
verlaengerung_links_cm: rahmen_x0,
verlaengerung_rechts_cm: x_r1 - rahmen_x1,
// Querschnittswerte (mm)
W_mm3: (b * b * b) / 6 * 1e3,
A_mm2: b * b * 1e2,
auflager_linie_mm2: b * G.wand_cm * 1e2,
blatt_schulter_mm2: b * (b / 2) * 1e2,
gelaender_seil_mitte_ueber_rahmen_cm: p.gelaender.mitte_y_cm - ok_rahmen_cm,
restspalt_decke_cm: G.raum_hoehe_cm - p.gelaender.mitte_y_cm,
lichte_hoehe_unter_seil_cm: p.gelaender.mitte_y_cm,
};
}
export type Geometrie = ReturnType<typeof ableiten>;B.3 src/statik/rechenkern.ts
Rechenkern — Lasten, Seilmodell, Nachweise, Verbindungsmittel, Mengen · 317 Zeilen · SHA-256 19e0f3cdf03da4cb97e03cd988a388e207342b269a7e37e946ff3f69ecc23587
/**
* Rechenkern Statik «Hochsitz mit Netz» — die einzige Implementierung.
* Läuft im Build (Dossier-Tabellen, Nachweise, Stückliste) und im Browser (Statik-Explorer)
* und wird im Dossier-Anhang B wörtlich abgedruckt.
*
* Basis: SN EN 1995-1-1 (EC5) / SIA 260/261/265-Logik, SIA 358 für das Geländer.
* Einheiten: Längen m (Geometrie kommt in cm und wird umgerechnet), Kräfte kN, Spannungen N/mm².
*/
import type { Hochsitz } from '../schema';
import type { Geometrie } from './geometrie';
import { laengeAusBezeichnung, rasterMitEcken } from '@yolu/bauprojekt-kit/lib/raster';
export interface Szenario {
erwachsene: number;
jugendliche: number;
kinder: number;
/** Knüpfmass = Durchhang ohne Last, cm */
knuepfmass_cm: number;
/** MINDESTabstand der Augplatten am Netzrand, cm (Reihen beginnen in den Ecken, gleichmässig) */
randabstand_cm: number;
}
/** Bemessungswerte des Holzes nach EC5: f_d = f_k · k_mod / γ_M */
export function holzwerte(p: Hochsitz) {
const k = p.holz.k_mod / p.holz.gamma_m;
return { f_md: p.holz.f_mk * k, f_vd: p.holz.f_vk * k, f_c0d: p.holz.f_c0k * k, f_c90d: p.holz.f_c90k * k };
}
/** Effektiver Plattenabstand bei Mindestabstand `min` auf der Länge `L` (Platten in beiden Ecken). */
export function abstandEffektiv(L: number, min: number): number {
const pos = rasterMitEcken(L, min);
return pos.length > 1 ? pos[1] - pos[0] : L;
}
/**
* Gekoppeltes Seilmodell mit Dehnung: beide Seilrichtungen (quer über Lx, längs über Lz) teilen sich
* EINE Mittendurchbiegung f. Je Richtung: Seillänge s(f) = 2·√((L/2)² + f²), Dehnkraft
* T = EA·(s/s0 − 1), Horizontalzug H = T/√(1 + (2f/L)²), getragene Last P = 4·H·f/L.
* Gesucht ist f mit P_quer(f) + P_längs(f) = P (Bisektion).
*/
export function seilmodell(P: number, f0: number, Lx: number, Lz: number, EA: number, beta = 0.5) {
const EAx = EA * 2 * beta;
const EAz = EA * 2 * (1 - beta);
const s0z = 2 * Math.hypot(Lz / 2, f0);
const s0x = 2 * Math.hypot(Lx / 2, f0);
const stuetz = (f: number) => {
const sz = 2 * Math.hypot(Lz / 2, f);
const sx = 2 * Math.hypot(Lx / 2, f);
const Tz = Math.max(0, sz / s0z - 1) * EAz;
const Tx = Math.max(0, sx / s0x - 1) * EAx;
const Hz = Tz / Math.hypot(1, (2 * f) / Lz);
const Hx = Tx / Math.hypot(1, (2 * f) / Lx);
return { Pz: (4 * Hz * f) / Lz, Px: (4 * Hx * f) / Lx, Hz, Hx, Tz, Tx };
};
if (P <= 0) return { f: f0, ...stuetz(f0), Pz: 0, Px: 0, Hz: 0, Hx: 0, Tz: 0, Tx: 0 };
let lo = f0 + 1e-4;
let hi = 1.6;
let f = hi;
for (let i = 0; i < 80; i++) {
f = (lo + hi) / 2;
const r = stuetz(f);
if (r.Pz + r.Px < P) lo = f;
else hi = f;
}
return { f, ...stuetz(f) };
}
/** Ein Lastszenario vollständig durchrechnen. */
export function bemessung(p: Hochsitz, g: Geometrie, sz: Szenario) {
const L = p.lasten;
const Lx = g.netzfeld_x_cm / 100; // quer: Spannweite der Lücke, zieht an den Querbalken
const Lz = g.netzfeld_z_cm / 100; // längs: zwischen den Längsbalken, zieht an den Längsbalken
const EA = p.netz.ea_kn * p.netz.n_wirksam;
const w = L.lastband_m;
const hw = holzwerte(p);
// Lasten: Personen mittig konzentriert, Kinder springend mit Stossfaktor φ
const Qk = (L.erwachsene_kg * sz.erwachsene + L.jugendliche_kg * sz.jugendliche) / 100 + (L.kind_kg / 100) * sz.kinder * L.stossfaktor_kind;
const Pd = L.gamma_q * Qk;
const kg = L.erwachsene_kg * sz.erwachsene + L.jugendliche_kg * sz.jugendliche + L.kind_kg * sz.kinder;
const s = seilmodell(Pd, sz.knuepfmass_cm / 100, Lx, Lz, EA);
const Hz = s.Hz; // Horizontalzug auf je einen Längsbalken (gesamt)
const Hx = s.Hx; // Horizontalzug auf je einen Querbalken (voll aufliegend)
// Längsbalken: zweiachsige Biegung — Horizontalzug als Bandlast mittig, Vertikalanteil als Punktlast
const Mh = Hz * (Lx / 4 - w / 8);
const Vz = s.Pz / 2;
const Mv = (Vz * Lx) / 4;
const sigma = ((Mh + Mv) * 1e6) / g.W_mm3;
const eta = sigma / hw.f_md;
// Ecken / Auflager: der Ringdruck C geht formschlüssig über die Blattschulter (12 × 6 cm)
const C = Hz / 2; // Ringdruck in die Überblattungs-Schulter je Ecke (= Schub am Auflager)
const V = Hz / 2;
const Rv = Vz + L.rahmenanteil_kn; // Auflagerkraft vertikal je Ecke
const sigma_c90 = (Rv * 1e3) / g.A_mm2;
const sigma_wand = (Rv * 1e3) / g.auflager_linie_mm2;
const sigma_schulter = (C * 1e3) / g.blatt_schulter_mm2;
// Randbefestigung (Augplatten): effektiver Abstand bei Mindestabstand sz.randabstand_cm auf der längeren Seite
const rand_abstand_eff_cm = Math.max(abstandEffektiv(g.netzfeld_x_cm, sz.randabstand_cm), abstandEffektiv(g.netzfeld_z_cm, sz.randabstand_cm));
const Fkn = (Math.max(Hz, Hx) / w) * (rand_abstand_eff_cm / 100) * L.ungleichverteilung;
const Tstrang = Math.max(s.Tz, s.Tx) / p.netz.n_wirksam;
const f_zul_platte = p.augplatte.bruch_kn / p.augplatte.sicherheitsfaktor;
return {
sz, kg, Qk, Pd, f_cm: s.f * 100, Hz, Hx, Hz_line: Hz / w, Hx_line: Hx / w, Pz: s.Pz, Px: s.Px,
Mh, Mv, Vz, sigma, eta, C, V, Rv, sigma_c90, sigma_wand, sigma_schulter,
rand_abstand_eff_cm, Fkn, Tstrang, f_zul_platte, eta_platte: Fkn / f_zul_platte, eta_strang: Tstrang / p.netz.zulaessig_kn,
licht_cm: g.knuepfebene_cm - s.f * 100,
...hw,
};
}
export type Bemessung = ReturnType<typeof bemessung>;
/** Geländerseil, Deckenvariante: frei hängendes Seil, seitliche Auslenkung unter Holmlast (SIA 358). */
export function gelaender(p: Hochsitz) {
const G = p.gelaender;
const qd = G.holmlast_kn_m * p.lasten.gamma_q;
return {
qd,
T_punkt: (G.einzellast_kn * G.seil_laenge_m) / (4 * G.auslenkung_m),
T_linie: (qd * G.seil_laenge_m ** 2) / (8 * G.auslenkung_m),
T_linie_weich: (qd * G.seil_laenge_m ** 2) / (8 * G.auslenkung_weich_m),
/** horizontaler Stoss, der konservativ über die untere Plattenreihe in den Rahmen geht */
F_rahmen: p.befestigung.widerstand.gelaender_anteil_rahmen * qd * G.seil_laenge_m,
};
}
/**
* Verbindungsmittel an der Ecke — Geometrie nachgerechnet, damit Text, Zeichnung und 3D nicht auseinanderlaufen.
* Koordinaten wie im Schraubenplan: x ab kastenseitiger Kante des Querbalkens, z ab Front-/Rückkante, y ab OK Podest.
* Das Blatt (Überblattung) ist b × b gross; Blattfuge auf OK Rahmen − b/2. Unter der Fuge liegt das Blatt des
* Längsbalkens nur für z < b, dahinter (z > b) der untere Teil des Querbalkens (Schulter bei z = b).
* S1: senkrecht durchs Blatt in die Deckplatte (Fugensicherung, Abheben, Dübel gegen Verschieben).
* S2: um `neigung_grad` in Tiefenrichtung zum Kasteninneren geneigt — sie muss die Blattfuge kreuzen, danach bis zur
* Deckplatte im Blatt des Längsbalkens bleiben (z < b) und in der Deckplatte enden, damit im Kasten keine Spitze austritt.
*/
export function verbindung(p: Hochsitz, g: Geometrie) {
const E = p.befestigung.ecke;
const b = g.b;
const d_cm = 0.8; // Schraubendurchmesser 8 mm
const s1_laenge_cm = laengeAusBezeichnung(E.s1, 16);
const s2_laenge_cm = laengeAusBezeichnung(E.s2, 18);
const neig = (E.neigung_grad * Math.PI) / 180;
const kopf_y = g.ok_rahmen_cm - E.kopf_versenkt_cm;
const fuge_y = g.ok_rahmen_cm - b / 2;
const ok_deck = g.ok_deckplatte_cm;
const uk_deck = ok_deck - g.wand_cm;
// S1 senkrecht
// Schraubenlänge gilt ab Kopfoberkante (Senkkopf); Kopf sitzt auf kopf_y
const s1_spitze_y_cm = kopf_y - s1_laenge_cm;
const s1_in_deck_cm = ok_deck - s1_spitze_y_cm;
// S2 geneigt: z-Lage an Fuge, Deckplatte und Spitze
const frei = s2_laenge_cm;
const zBei = (y: number) => E.positionen.s2[1] + (kopf_y - y) * Math.tan(neig);
const s2_fuge_z_cm = zBei(fuge_y);
const s2_deck_z_cm = zBei(ok_deck);
const s2_spitze_y_cm = kopf_y - frei * Math.cos(neig);
const s2_spitze_z_cm = zBei(s2_spitze_y_cm);
const s2_in_quer_cm = (kopf_y - fuge_y) / Math.cos(neig);
const s2_in_laengs_cm = (fuge_y - ok_deck) / Math.cos(neig);
const s2_in_deck_cm = (ok_deck - s2_spitze_y_cm) / Math.cos(neig);
const rand = 4 * d_cm; // Mindestabstand Kopf zu Kanten 4d
const [s1x, s1z] = E.positionen.s1;
const [s2x, s2z] = E.positionen.s2;
const imBlatt = (x: number, z: number) => x >= rand && x <= b - rand && z >= rand && z <= b - rand;
return {
s1_laenge_cm, s2_laenge_cm, kopf_y, fuge_y, ok_deck, uk_deck,
s1_spitze_y_cm, s1_in_deck_cm, s1_in_deckplatte: s1_spitze_y_cm > uk_deck + 0.5 && s1_spitze_y_cm < ok_deck,
s2_fuge_z_cm, s2_deck_z_cm, s2_spitze_y_cm, s2_spitze_z_cm, s2_in_quer_cm, s2_in_laengs_cm, s2_in_deck_cm,
/** kreuzt die Fuge im Blatt und bleibt bis zur Deckplatte im Blatt des Längsbalkens */
s2_im_blatt: s2_fuge_z_cm > rand && s2_deck_z_cm < b - 0.5,
s2_in_deckplatte: s2_spitze_y_cm > uk_deck + 0.5 && s2_spitze_y_cm < ok_deck,
s2_reserve_cm: s2_spitze_y_cm - uk_deck,
kopfabstand_cm: Math.hypot(s1x - s2x, s1z - s2z),
randabstaende_ok: imBlatt(s1x, s1z) && imBlatt(s2x, s2z) && Math.hypot(s1x - s2x, s1z - s2z) >= rand,
/** EC5-Mindestabstände für axial beanspruchte Schrauben (Tab. 8.6: a1,CG = 10d) sind im 12-cm-Blatt nicht einhaltbar — Fachperson */
ec5_endabstand_10d_cm: 10 * d_cm,
ec5_endabstand_ok: Math.min(s1z, s2z, s1x, s2x) >= 10 * d_cm,
};
}
/**
* Abheben des Längsbalken-Endes: Der Netzzug wirkt auf Plattenhöhe (b/2 über der Deckplatte) und will den Balken
* um seine innere Unterkante kippen. Stabilisierend wirkt das Eigengewicht; der Rest muss an den zwei Enden
* durch S1 (und den aufliegenden Querbalken) gehalten werden. Rückgabe: Haltekraft je Ecke in kN.
*/
export function abheben(p: Hochsitz, g: Geometrie, Hz: number) {
const bm = g.b / 100;
const G_balken = p.holz.rho_kn_m3 * bm * bm * (g.rahmen_x_cm / 100);
const M_kipp = Hz * (bm / 2);
const M_stab = G_balken * (bm / 2);
return { G_balken, M_kipp, M_stab, F_ecke: Math.max(0, (M_kipp - M_stab) / bm / 2) };
}
/** Mengen für Stückliste, 3D-Modell und Zeichnungen — aus denselben Rasterfunktionen. */
export function mengen(p: Hochsitz, g: Geometrie) {
const B = p.befestigung;
const ecken = 4;
// Querbalken: `anzahl` zusätzliche Schrauben je Balken, gleichmässig zwischen den Ecken
const nQ = B.querbalken.anzahl;
const querbalkenPositionen = Array.from({ length: nQ }, (_, i) => +((g.tiefe_cm * (i + 1)) / (nQ + 1)).toFixed(1));
// Augplatten Netzrand: alle vier Reihen beginnen in den Ecken → zwei Platten je Ecke (eine je Balkenseite),
// das Randseil wird dort in zwei Mal 45° umgelenkt und die Umlenkkraft auf zwei Platten verteilt
const randXalle = rasterMitEcken(g.netzfeld_x_cm, B.netzrand_abstand_cm);
const randZalle = rasterMitEcken(g.netzfeld_z_cm, B.netzrand_abstand_cm);
const randZ = randZalle;
// Geländer: Frontreihe mit beiden Enden (Säule, Wand); linke Reihe ohne das vordere Ende (Eckplatte der Frontreihe)
const gelaenderFront = rasterMitEcken(g.breite_total_cm, B.gelaender_abstand_cm);
const gelaenderLinks = rasterMitEcken(g.tiefe_cm, B.gelaender_abstand_cm).slice(0, -1);
const augplatten_netzrand = 2 * randXalle.length + 2 * randZ.length;
const augplatten_gelaender = gelaenderFront.length + gelaenderLinks.length;
const augplatten = augplatten_netzrand + augplatten_gelaender;
return {
ecken,
querbalken_positionen_cm: querbalkenPositionen,
querbalken_schrauben: 2 * nQ,
schrauben_8x160: ecken + 2 * nQ,
schrauben_8x180: ecken,
schrauben_deckplatte: ecken + 2 * nQ, // senkrecht in die Deckplatte (S1 + Querbalken) — gegen Verschieben
rand_x_cm: randXalle,
rand_z_cm: randZ,
rand_abstand_x_cm: randXalle[1] - randXalle[0],
rand_abstand_z_cm: randZalle[1] - randZalle[0],
gelaender_front_cm: gelaenderFront,
gelaender_links_cm: gelaenderLinks,
gelaender_abstand_front_cm: gelaenderFront[1] - gelaenderFront[0],
gelaender_abstand_links_cm: gelaenderLinks.length > 1 ? gelaenderLinks[1] - gelaenderLinks[0] : g.tiefe_cm,
augplatten_netzrand,
augplatten_gelaender,
augplatten,
augplatten_je_ecke: 2,
};
}
export interface Nachweis {
id: string;
titel: string;
einwirkung: string;
widerstand: string;
eta: number | null;
status: 'ok' | 'warn' | 'crit' | 'bedingung';
hinweis?: string;
}
const z = (v: number, d = 2) => (v + 1e-9).toFixed(d); // Epsilon gegen Binär-Abrundung (3.2249999… → 3.23)
/** Ampel eines Nachweises aus der Ausnutzung; Schwellen aus projekt.toml [ampel] (auch im Explorer). */
export const ampelStatus = (eta: number, a: Hochsitz['ampel']): 'ok' | 'warn' | 'crit' =>
eta < a.gelb_ab ? 'ok' : eta <= a.rot_ab ? 'warn' : 'crit';
/** Alle Resultate für Dossier, Startseite und Stückliste: Standardszenario, Varianten, Nachweise. */
export function rechne(p: Hochsitz, g: Geometrie) {
const L = p.lasten;
const std: Szenario = { ...L.szenario, knuepfmass_cm: L.knuepfmass_cm, randabstand_cm: p.befestigung.netzrand_abstand_cm };
const b = bemessung(p, g, std);
const varianten = [0, 5, 10].map((f0) => bemessung(p, g, { ...std, knuepfmass_cm: f0 }));
// Gebrauchslage: 1 Person, charakteristisch (γ = 1), ohne Stossfaktor
const gebrauch = (() => {
const s = seilmodell(L.erwachsene_kg / 100, 0, g.netzfeld_x_cm / 100, g.netzfeld_z_cm / 100, p.netz.ea_kn * p.netz.n_wirksam);
return { P: L.erwachsene_kg / 100, f_cm: s.f * 100, Hz: s.Hz, licht_cm: g.knuepfebene_cm - s.f * 100 };
})();
const gl = gelaender(p);
const m = mengen(p, g);
const vb = verbindung(p, g);
const ab = abheben(p, g, b.Hz);
const W = p.befestigung.widerstand;
const R_verschieben = m.schrauben_deckplatte * W.schraube_abscheren_kn;
const ampel = (eta: number) => ampelStatus(eta, p.ampel);
const nachweise: Nachweis[] = [
{ id: 'biegung', titel: 'Längsbalken, Biegung zweiachsig',
einwirkung: `σ = ${z(b.sigma, 1)} N/mm² (M<sub>h</sub> ${z(b.Mh)} + M<sub>v</sub> ${z(b.Mv)} kNm)`,
widerstand: `f<sub>m,d</sub> = ${z(b.f_md, 1)} N/mm²`, eta: b.eta, status: ampel(b.eta) },
{ id: 'auflager', titel: `Auflagerpressung (${g.b}×${g.b} cm)`,
einwirkung: `σ<sub>c,90</sub> = ${z(b.sigma_c90)} N/mm²`, widerstand: `f<sub>c,90,d</sub> = ${z(b.f_c90d, 1)} N/mm²`,
eta: b.sigma_c90 / b.f_c90d, status: ampel(b.sigma_c90 / b.f_c90d) },
{ id: 'kastenwand', titel: `Kastenwand ${g.wand_cm} cm unter Auflagerecke`,
einwirkung: `${z(b.sigma_wand)} N/mm² (R<sub>v</sub> ≈ ${z(b.Rv, 1)} kN, Linienlager ${g.b} × ${g.wand_cm} cm)`,
widerstand: `~${z(p.befestigung.kastenwand_zul_nmm2, 0)} N/mm² (knickbegrenzt, H ${g.kastenwand_hoehe_cm} cm)`,
eta: b.sigma_wand / p.befestigung.kastenwand_zul_nmm2, status: ampel(b.sigma_wand / p.befestigung.kastenwand_zul_nmm2) },
{ id: 'schulter', titel: 'Ringdruck / Schub an der Blattschulter (formschlüssig)',
einwirkung: `C = V = ${z(b.C)} kN je Ecke auf ${g.blatt_schulter_mm2 / 100} cm² → ${z(b.sigma_schulter)} N/mm²`,
widerstand: `f<sub>c,0,d</sub> = ${z(b.f_c0d, 1)} N/mm²; S2 (${p.befestigung.ecke.s2}, ${p.befestigung.ecke.neigung_grad}°) als Redundanz`,
eta: b.sigma_schulter / b.f_c0d, status: 'ok' },
{ id: 'verschieben', titel: 'Rahmen auf Deckplatte, Verschieben (Geländer-Stoss über untere Plattenreihe)',
einwirkung: `F ≈ ${z(gl.F_rahmen, 1)} kN (${z(W.gelaender_anteil_rahmen * 100, 0)} % der Holm-Linienlast ${z(gl.qd, 1)} kN/m × ${z(p.gelaender.seil_laenge_m)} m)`,
widerstand: `${m.schrauben_deckplatte} Schrauben senkrecht in die Deckplatte, Abscheren R<sub>d</sub> ≈ ${m.schrauben_deckplatte} × ${z(W.schraube_abscheren_kn, 1)} = ${z(R_verschieben, 0)} kN, plus Reibung`,
eta: gl.F_rahmen / R_verschieben, status: ampel(gl.F_rahmen / R_verschieben) },
{ id: 'abheben', titel: 'Abheben / Kippen des Längsbalken-Endes',
einwirkung: `F = ${z(ab.F_ecke)} kN je Ecke (Netzzug ${z(b.Hz)} kN auf Plattenhöhe ${g.b / 2} cm, Kippen um die innere Unterkante, abzüglich Eigengewicht ${z(ab.G_balken)} kN)`,
widerstand: `S1 axial in der ${g.wand_cm}-cm-Deckplatte (${z(vb.s1_in_deck_cm, 1)} cm Gewinde): R<sub>d</sub> ≈ ${z(W.s1_axial_kn, 1)} kN, dazu Auflast des Querbalkens`,
eta: ab.F_ecke / W.s1_axial_kn, status: ampel(ab.F_ecke / W.s1_axial_kn) },
{ id: 'randbefestigung', titel: `Netz-Randbefestigung (${p.augplatte.kurz}, e = ${z(b.rand_abstand_eff_cm, 1)} cm)`,
einwirkung: `F = ${z(b.Fkn)} kN je Punkt`,
widerstand: `zulässig ${z(b.f_zul_platte, 1)} kN = Bruch ${z(p.augplatte.bruch_kn, 1)} kN / ${p.augplatte.sicherheitsfaktor} (Prüfbericht ${p.augplatte.pruefbericht})`,
eta: b.eta_platte, status: ampel(b.eta_platte) },
{ id: 'strang', titel: `${p.netz.seil}-Strang im Lastband`,
einwirkung: `T = ${z(b.Tstrang)} kN`, widerstand: `zulässig ≈ ${z(p.netz.zulaessig_kn, 1)} kN (inkl. Knoten)`,
eta: b.eta_strang, status: ampel(b.eta_strang) },
{ id: 'deckenanker', titel: 'Geländer-Deckenanker (Hängeseil)',
einwirkung: `Punktstoss ≈ ${z(gl.T_punkt, 1)} kN · Linienlast konservativ ≈ ${z(gl.T_linie, 0)} kN`,
widerstand: `bestehende Vorhangseil-Anker — Anforderung ≥ ${p.gelaender.anker_anforderung_kn} kN je Ende`,
eta: null, status: 'bedingung', hinweis: 'B1' },
];
return {
std: b, varianten, gebrauch, gelaender: gl, mengen: m, verbindung: vb, abheben: ab, nachweise,
// erfüllt = Ausnutzung ≤ rot_ab (grün oder gelb); gelb heisst «erfüllt mit kleiner Reserve», nicht «nicht erfüllt»
alleErfuellt: nachweise.every((n) => n.status !== 'crit') && vb.s2_in_deckplatte && vb.s1_in_deckplatte && vb.s2_im_blatt && vb.randabstaende_ok,
max_personen: L.szenario.erwachsene + L.szenario.jugendliche + L.szenario.kinder,
holz: holzwerte(p),
EA: p.netz.ea_kn * p.netz.n_wirksam,
};
}
export type Resultate = ReturnType<typeof rechne>;B.4 kit/src/lib/raster.ts
Rasterpositionen (Kit) — Schrauben- und Augplattenreihen · 29 Zeilen · SHA-256 86083242cdb1a8f0899e4f24d035a0b76766259d3624577bff3f11ad3c1f97fa
/**
* Gleichmässige Positionen entlang einer Länge: Sollabstand `abstand` bleibt exakt erhalten,
* die Gruppe wird zentriert, an beiden Enden bleibt mindestens `rand` frei.
* Wird für Schraubenreihen, Augplatten-Raster und Stücklisten genutzt, damit 3D-Modell,
* Zeichnung und Mengen dieselben Punkte haben («alle 50 cm» heisst dann auch 50 cm).
*/
export function raster(laenge: number, abstand: number, rand: number): number[] {
const nutz = laenge - 2 * rand;
if (nutz <= 0 || abstand <= 0) return [laenge / 2];
const n = Math.floor(nutz / abstand + 1e-9) + 1;
const start = (laenge - (n - 1) * abstand) / 2;
return Array.from({ length: n }, (_, i) => +(start + i * abstand).toFixed(2));
}
/**
* Positionen, die in BEIDEN Ecken beginnen und den Mindestabstand einhalten: n = ⌊L/min⌋ + 1 Punkte,
* gleichmässig verteilt (effektiver Abstand L/(n−1) ≥ min). Für Augplatten-Reihen «ganz in den Ecken beginnend».
*/
export function rasterMitEcken(laenge: number, minAbstand: number): number[] {
if (laenge <= 0) return [0];
const n = Math.max(2, Math.floor(laenge / minAbstand + 1e-9) + 1);
return Array.from({ length: n }, (_, i) => +((i * laenge) / (n - 1)).toFixed(2));
}
/** Schraubenlänge aus der Bezeichnung «VG 8×180» in cm (Fallback, wenn nicht lesbar). */
export function laengeAusBezeichnung(bezeichnung: string, fallback: number): number {
const m = /[×x]\s*(\d+)/.exec(bezeichnung);
return m ? Number(m[1]) / 10 : fallback;
}B.5 src/statik/rechenkern.test.ts
Tests — Goldwerte und Grenzfälle · 188 Zeilen · SHA-256 307f55aba0b05eb20bd241ec050dd6e54feea25d79831d92d85e229ecfa85cf0
/**
* Goldene Werte aus dem geprüften Dossier v1.3 (31.08.2026) und Grenzfälle; Holz-Ausnutzungen seit v3.0 mit k_mod = 0.8.
* Ändert sich der Rechenkern absichtlich, müssen diese Werte bewusst angepasst werden.
*/
import { describe, expect, it } from 'vitest';
import { readFileSync } from 'node:fs';
import { parseProjekt } from '@yolu/bauprojekt-kit/daten';
import { rasterMitEcken } from '@yolu/bauprojekt-kit/lib/raster';
import { HochsitzSchema } from '../schema';
import { ableiten } from './geometrie';
import { bemessung, rechne, seilmodell } from './rechenkern';
const p = parseProjekt(readFileSync(new URL('../../projekt.toml', import.meta.url), 'utf8'), HochsitzSchema);
const g = ableiten(p);
const r = rechne(p, g);
describe('Geometrie', () => {
it('leitet die bekannten Masse ab', () => {
expect(g.rahmen_x_cm).toBe(352);
expect(g.netzfeld_z_cm).toBe(211);
expect(g.ok_rahmen_cm).toBe(212);
expect(g.knuepfebene_cm).toBe(208);
expect(g.kopffreiheit_cm).toBe(138);
expect(g.netz_m2).toBeCloseTo(6.92, 2);
expect(g.W_mm3).toBeCloseTo(288e3, 0);
expect(g.kabine_ok_cm).toBe(212); // Kabinen 217 auf dem Rohboden, 5 cm unter OK Podest
expect(g.kabine_ueberstand_cm).toBe(12); // v1.3 nannte +17 — abgeleitet aus dem alten 3D-Modell mit OK Deckplatte 195
});
});
describe('Bemessungsszenario 1E+1J+1K, Kind springend (Dossier v1.3, Kap. 3–5)', () => {
const b = r.std;
it('Lasten', () => {
expect(b.Qk).toBeCloseTo(2.15, 2);
expect(b.Pd).toBeCloseTo(3.225, 2);
});
it('Seilmodell', () => {
expect(Math.round(b.f_cm)).toBe(34);
expect(b.Hz).toBeCloseTo(3.89, 2);
expect(b.Hx).toBeCloseTo(1.68, 2);
expect(b.Tstrang).toBeCloseTo(0.41, 2);
expect(Math.round(b.licht_cm)).toBe(174);
});
it('Längsbalken', () => {
expect(b.Mh).toBeCloseTo(2.61, 2);
expect(b.Mv).toBeCloseTo(1.03, 2);
expect(b.sigma).toBeCloseTo(12.7, 1);
expect(Math.round(b.eta * 100)).toBe(86); // v3.0: k_mod 0.8 (mittel) statt 0.9 → f_m,d 14.8; bis v2.1 76 %
});
it('Auflager und Rand', () => {
expect(b.C).toBeCloseTo(1.95, 2);
expect(b.Rv).toBeCloseTo(1.56, 2);
expect(b.sigma_c90).toBeCloseTo(0.11, 2);
expect(b.sigma_wand).toBeCloseTo(0.26, 2);
// Augplatten ab den Ecken mit Mindestabstand 30 → effektiv 32.8 cm längs; v1.3 rechnete mit fix 30 cm (1.27 kN, 24 %)
expect(b.rand_abstand_eff_cm).toBeCloseTo(32.8, 1);
expect(b.Fkn).toBeCloseTo(1.38, 2);
expect(Math.round(b.eta_platte * 100)).toBe(26);
});
it('Varianten Knüpfmass 0/5/10 cm', () => {
expect(r.varianten.map((v) => Math.round(v.f_cm))).toEqual([34, 34, 35]);
expect(r.varianten.map((v) => +v.Hz.toFixed(2))).toEqual([3.89, 3.86, 3.77]);
expect(r.varianten.map((v) => Math.round(v.eta * 100))).toEqual([86, 85, 84]); // bis v2.1 (k_mod 0.9): 76/76/74
});
it('Gebrauchslast 1 Person', () => {
// v1.3 rechnete diesen Fall mit β = 0.55 (H = 1.53 kN) und einer veralteten Knüpfebene (lichte Höhe 181 cm).
// Neu konsistent symmetrisch (β = 0.5) und mit Knüpfebene 208 cm → dokumentiert im Änderungsverzeichnis 2.0.
expect(Math.round(r.gebrauch.f_cm)).toBe(22);
expect(r.gebrauch.Hz).toBeCloseTo(1.63, 1);
expect(Math.round(r.gebrauch.licht_cm)).toBe(186);
});
it('Geländer-Deckenanker und Stoss in den Rahmen', () => {
expect(r.gelaender.T_punkt).toBeCloseTo(6.45, 1);
expect(r.gelaender.T_linie).toBeCloseTo(13.3, 1);
expect(r.gelaender.T_linie_weich).toBeCloseTo(8.0, 1);
expect(r.gelaender.F_rahmen).toBeCloseTo(3.1, 1);
});
it('alle rechnerischen Nachweise erfüllt', () => {
expect(r.alleErfuellt).toBe(true);
const v = r.nachweise.find((n) => n.id === 'verschieben')!;
expect(v.eta!).toBeLessThan(0.3);
});
});
describe('Verbindungsmittel Ecke', () => {
it('Längen aus der Bezeichnung', () => {
expect(r.verbindung.s1_laenge_cm).toBe(16);
expect(r.verbindung.s2_laenge_cm).toBe(18);
});
it('S1 und S2 enden in der Deckplatte, nicht im Kasten', () => {
const v = r.verbindung;
expect(v.s1_in_deckplatte).toBe(true);
expect(v.s1_in_deck_cm).toBeCloseTo(4.0, 1);
expect(v.s2_in_deckplatte).toBe(true);
expect(v.s2_spitze_y_cm).toBeCloseTo(196.4, 1);
expect(v.s2_reserve_cm).toBeGreaterThan(1);
});
it('S2 kreuzt die Blattfuge und bleibt bis zur Deckplatte im Blatt des Längsbalkens', () => {
const v = r.verbindung;
expect(v.s2_fuge_z_cm).toBeCloseTo(6.7, 1); // v3.0: Kopf 3.2 statt 4 cm hinter der Kante (bis v2.1: 7.5)
expect(v.s2_deck_z_cm).toBeCloseTo(10.1, 1); // bis v2.1: 10.9
expect(v.s2_deck_z_cm).toBeLessThan(g.b);
expect(v.s2_im_blatt).toBe(true);
expect(v.s2_in_laengs_cm).toBeCloseTo(6.9, 1);
expect(v.s2_in_quer_cm).toBeCloseTo(6.9, 1);
expect(v.s2_in_deck_cm).toBeCloseTo(4.1, 1);
});
it('die frühere Kopflage z = 8.5 hätte das Blatt des Längsbalkens verfehlt', () => {
const p2 = structuredClone(p);
p2.befestigung.ecke.positionen.s2 = [8, 8.5];
expect(rechne(p2, g).verbindung.s2_im_blatt).toBe(false);
});
it('Kopfabstand und Randabstände ≥ 4d', () => {
expect(r.verbindung.kopfabstand_cm).toBeGreaterThanOrEqual(p.befestigung.ecke.abstand_min_cm);
expect(r.verbindung.randabstaende_ok).toBe(true);
});
it('Abheben aus dem Kippmoment hergeleitet, S1 hält', () => {
expect(r.abheben.F_ecke).toBeCloseTo(0.9, 1);
expect(r.abheben.F_ecke / p.befestigung.widerstand.s1_axial_kn).toBeLessThan(0.6);
});
it('ein 8×200 bei 30° würde unter der Deckplatte austreten (Grund für 8×180)', () => {
const p2 = structuredClone(p);
p2.befestigung.ecke.s2 = 'VG 8×200';
expect(rechne(p2, g).verbindung.s2_in_deckplatte).toBe(false);
});
});
describe('Mengen', () => {
it('Schrauben: zwei je Ecke plus zwei je Querbalken', () => {
expect(r.mengen.querbalken_positionen_cm).toEqual([78.3, 156.7]);
expect(r.mengen.schrauben_8x160).toBe(8);
expect(r.mengen.schrauben_8x180).toBe(4);
expect(r.mengen.schrauben_deckplatte).toBe(8);
});
it('Augplatten: alle Reihen ab den Ecken (zwei Platten je Ecke), Mindestabstand eingehalten', () => {
expect(r.mengen.rand_x_cm[0]).toBe(0);
expect(r.mengen.rand_x_cm.at(-1)).toBe(g.netzfeld_x_cm);
expect(r.mengen.rand_x_cm).toHaveLength(11);
expect(r.mengen.rand_abstand_x_cm).toBeGreaterThanOrEqual(p.befestigung.netzrand_abstand_cm);
expect(r.mengen.rand_abstand_z_cm).toBeGreaterThanOrEqual(p.befestigung.netzrand_abstand_cm);
expect(r.mengen.rand_z_cm[0]).toBe(0);
expect(r.mengen.rand_z_cm.at(-1)).toBe(g.netzfeld_z_cm);
expect(r.mengen.rand_z_cm).toHaveLength(8); // Querbalken-Reihe ebenfalls mit beiden Eckplatten
expect(r.mengen.augplatten_netzrand).toBe(38);
expect(r.mengen.augplatten_gelaender).toBe(12);
expect(r.mengen.augplatten).toBe(50);
});
it('rasterMitEcken', () => {
expect(rasterMitEcken(328, 30)).toHaveLength(11);
expect(rasterMitEcken(328, 30)[1]).toBeCloseTo(32.8, 1);
expect(rasterMitEcken(60, 30)).toEqual([0, 30, 60]);
expect(rasterMitEcken(50, 30)).toEqual([0, 50]);
});
});
describe('Grenzfälle', () => {
it('ohne Last kein Zug, Durchhang = Knüpfmass', () => {
const s = seilmodell(0, 0.1, 3.28, 2.11, 80);
expect(s.Hz).toBe(0);
expect(s.f).toBeCloseTo(0.1);
});
it('mehr Last → mehr Durchhang, mehr Zug', () => {
const a = seilmodell(1, 0, 3.28, 2.11, 80);
const b = seilmodell(3, 0, 3.28, 2.11, 80);
expect(b.f).toBeGreaterThan(a.f);
expect(b.Hz).toBeGreaterThan(a.Hz);
});
it('steiferes Netz → weniger Durchhang', () => {
const weich = seilmodell(3, 0, 3.28, 2.11, 40);
const steif = seilmodell(3, 0, 3.28, 2.11, 160);
expect(steif.f).toBeLessThan(weich.f);
});
it('Gleichgewicht: getragene Last = aufgebrachte Last', () => {
const s = seilmodell(3.225, 0, 3.28, 2.11, 80);
expect(s.Pz + s.Px).toBeCloseTo(3.225, 3);
});
it('Slackline-Kontrolle: H ≈ P·L/(4f) je Richtung', () => {
const s = seilmodell(3.225, 0, 3.28, 2.11, 80);
expect(s.Hz).toBeCloseTo((s.Pz * 2.11) / (4 * s.f), 3);
expect(s.Hx).toBeCloseTo((s.Px * 3.28) / (4 * s.f), 3);
});
it('Netz unbelastet im Bemessungspfad', () => {
const b = bemessung(p, g, { erwachsene: 0, jugendliche: 0, kinder: 0, knuepfmass_cm: 5, randabstand_cm: 30 });
expect(b.Pd).toBe(0);
expect(b.eta).toBe(0);
expect(b.f_cm).toBeCloseTo(5);
});
});Anhang B zum Statik-Dossier «Hochsitz mit Netz» · Version 3.0 · 02.09.2026 · Zur Prüfung· Build adba937 vom 02.09.2026 · Erstellt vom Projektteam (YOLU) mit Unterstützung von Claude.