Skip to content

Katalog erweitern

Eine Marke ist eine YAML-Datei. Neue Hersteller, neue Flügel und neue Masten brauchen keine Code-Änderung — nur Katalogdaten. Diese Datei erklärt, wie das geht und wie sich das Modell mit realen Sessiondaten nachschärfen lässt.

  • Katalogverzeichnis: backend/pumpfoil/catalog/
  • Vorlage: backend/pumpfoil/catalog/_template.yaml
  • Schema und Loader: backend/pumpfoil/catalog/_schema.py

Dateien mit führendem Unterstrich ignoriert der Loader. Alles andere mit der Endung .yaml wird beim Start eingelesen und validiert.


Neuen Hersteller anlegen

bash
cd backend/pumpfoil/catalog
cp _template.yaml armstrong.yaml

Danach brand, display_name und source setzen. brand ist der Schlüssel in der API und muss eindeutig und kleingeschrieben sein.

yaml
brand: armstrong
display_name: Armstrong Foils
source: armstrongfoils.com

Prüfen, dass die Datei geladen wird:

bash
cd backend
.venv/bin/python -c "from pumpfoil.catalog import load_catalogs; print(sorted(load_catalogs()))"

Der Loader cacht das Ergebnis (lru_cache). Nach Katalogänderungen den uvicorn-Prozess neu starten oder mit --reload laufen lassen.


Welche Felder wirklich nötig sind

Pflicht sind nur id, name, span_mm und area_cm2. Alles Weitere leitet das Schema ab:

mean_chord_mm    = area_cm2 · 100 / span_mm
aspect_ratio     = (span_mm / 10)² / area_cm2
thickness_ratio  = volume_cm3 / (0,685 · area_cm2 · mean_chord_cm)
camber_ratio     aus camber_class

Je mehr echte Herstellerangaben eingetragen sind, desto weniger wird geraten. Nach Wirkung sortiert:

FeldWirkung, wenn es fehlt
volume_cm3t/c fällt auf 0,11 zurück, Reibungswiderstand wird ungenau
mean_chord_mmwird aus Fläche und Spannweite gerechnet, bei stark elliptischen Grundrissen zu grob
mass_gfehlt in der Gesamtmasse, verfälscht Take-off und Pumpleistung
camber_classStandard medium_camber (3 %), verschiebt den Nullauftriebswinkel
cl_maxStandard 1,35, verschiebt Take-off-Speed und Stall-Grenze

Wenn ein Wert geschätzt ist, gehört das ins Feld notes. Der Axis-Katalog macht das konsequent so, damit später erkennbar bleibt, welche Zahl belastbar ist.


Frontwing eintragen

yaml
front_wings:
  - id: hs-1250
    name: HS 1250
    family: HS
    span_mm: 1250
    area_cm2: 1584
    volume_cm3: 1810
    max_chord_mm: 170
    mean_chord_mm: 126.7      # weglassen, wenn nicht veröffentlicht
    mass_g: 1450
    camber_class: high_camber # high | medium | low | symmetric
    cl_max: 1.38
    oswald_e: 0.93
    mount: proprietary
    notes: Masse geschätzt.

Zu den beiden Werten, die eine Einschätzung verlangen:

camber_class bestimmt den Nullauftriebswinkel und damit, wie viel Anstellwinkel ein Flügel für sein Gewicht braucht.

KlasseWölbungtypisch für
high_camber4,5 %Pump-, Dockstart-, Downwind-Flügel mit viel Low-End
medium_camber3,0 %Allround-Wingfoils
low_camber1,8 %Speed- und Race-Flügel
symmetric0 %Rearwings, Masten

oswald_e beschreibt, wie nah die Auftriebsverteilung an der elliptischen liegt. 0,94 für sehr schlanke Hochleistungsflügel, 0,90 bis 0,92 für die Allroundklasse, darunter für stark gepfeilte oder rechteckige Grundrisse.

Beide Werte kann man aus einem einzelnen Sessionvergleich nicht sauber zurückrechnen. Im Zweifel bei den Standardwerten bleiben — der Vergleich zwischen Setups bleibt aussagekräftig, solange man dieselben Annahmen für alle Flügel benutzt.


Rearwing, Fuselage, Mast, Board

yaml
rear_wings:
  - id: stab-330
    name: Stab 330
    span_mm: 330
    area_cm2: 145
    volume_cm3: 42
    chord_mm: 44
    mass_g: 200
    camber_class: low_camber
yaml
fuselages:
  - id: fuse-685
    name: Fuselage 685
    length_mm: 685
    geometry: standard        # standard | advance_20 | advance_plus | fatty
    diameter_mm: 26
    mass_g: 545

geometry verschiebt die Mastachse relativ zum Frontwing-Neutralpunkt: standard 0 mm, advance_20 20 mm nach vorn, advance_plus und fatty 60 mm. Das ändert nicht den Auftrieb, aber die Fußposition und damit, ob ein Setup überhaupt ausbalancierbar ist (trimmable). Für andere Hersteller ist standard die richtige Wahl, solange keine Angabe zur Mastposition vorliegt.

Der Hebelarm zwischen den Neutralpunkten ergibt sich als 92 % der Fuselage-Länge; die Enden tragen Verschraubung und Überstand, nicht Hebelarm.

yaml
masts:
  - id: mast-850
    name: Performance Mast 850
    length_mm: 850
    chord_mm: 122
    thickness_mm: 14.0
    mass_g: 1650

Mastdicke und Chord bestimmen zusammen das Profil-t/c und damit rund 10 bis 15 % des Gesamtwiderstands. Wenn ein Hersteller nur die Dicke angibt, ist eine Chord von 115 bis 130 mm eine brauchbare Annahme für einen modernen Highaspect-Mast.

yaml
boards:
  - id: pump-45
    name: Pump Board 4'8" 45L
    volume_l: 45
    length_cm: 142.0
    width_cm: 46.0          # optional, geht in keine Rechnung ein
    mass_kg: 4.1
    discipline: pump          # pump | wing | downwind | allround
                              # UI-Auswahl: nur discipline=pump;
                              # wing/allround/downwind bleiben im Katalog.
    track_center_from_nose_cm: 65.0   # optional, sonst Heuristik
    track_travel_cm: 28.0
    front_strap_from_nose_cm: 102.0
    rear_strap_from_nose_cm: 60.0

Optional für Pump-Boards: track_center_from_nose_cm, track_travel_cm, front_strap_from_nose_cm, rear_strap_from_nose_cm. Fehlen die Angaben, leitet das Schema Track-Mitte (~46 % der Länge), Travel (~28–30 cm) und Straps aus der Referenz-Stance ab.

Im Flug geht das Board nur über die Masse ein. Volumen und Länge dienen der Darstellung und späteren Take-off-Modellierung, die Breite ist rein informativ und darf fehlen.


Preise & Bezug

Optional auf Marken- und Teilebene. Fehlen die Felder, bleibt die Physik unverändert — die Oberfläche zeigt dann „keine Preise“ bzw. nur den Bezugsweg.

yaml
commerce:
  channel: direct          # direct | partner | mixed
  currency: EUR
  shop_url: https://example.com/shop
  dealer_url: https://example.com/dealers   # partner / mixed
  price_checked: "2026-09-08"
  notes: Shoppreise Stand Herstellerseite.
channelBedeutung
directKauf über Hersteller-Shop; Live-Refresh möglich
partnerNur über Händler; kein Refresh-Button
mixedBeides; Refresh nur für Teile mit product_url

Pro Teil (Frontwing, Rearwing, Fuselage, Mast, Board):

yaml
price: 1299
currency: EUR                              # optional, sonst Marken-currency
product_url: https://example.com/shop/...  # nötig für Live-Refresh
price_checked: "2026-09-08"

Baseline: Preise im YAML committen (Quartals-Review / Saison).
Live: Button „Preise für diesen Hersteller aktualisieren“ holt Shop-Preise nur für hinterlegte product_urls und speichert sie als Session-Overlay im Browser — die YAML-Datei auf dem Server wird nicht überschrieben.


Komponenten über Markengrenzen mischen

Flügel, Fuselage und Mast sind herstellerspezifisch verschraubt und kommen deshalb aus der Leitmarke des Setups. Das Board sitzt über die Tracks auf jedem Mast und ist das einzige Teil, das frei kombinierbar bleibt. Dafür gibt es die qualifizierte Referenz marke:id:

python
build_setup(brand="axis", board_id="gong:strada-hdcc-79")

Ohne Doppelpunkt gilt die Leitmarke. Die Auflösung steckt in split_reference und greift für alle Komponentenarten, auch wenn sie praktisch nur beim Board gebraucht wird.

Eine Marke darf deshalb auch nur Boards führen, so wie gong.yaml. Ihr Flag rig_capable steht dann auf false: die Oberfläche bietet sie nicht als Hersteller an, ihre Boards erscheinen aber im Board-Dropdown unter eigenem Gruppentitel. /catalog liefert dafür neben brands eine flache Liste boards, in der jeder Eintrag seine ref mitbringt.


Änderungen prüfen

bash
cd backend
.venv/bin/python -m pytest                      # Schema und Physik
.venv/bin/python - <<'PY'
from pumpfoil.geometry import build_setup
from pumpfoil.steady import steady_report

setup = build_setup(brand="armstrong", front_wing_id="hs-1250",
                    rear_wing_id="stab-330", fuselage_id="fuse-685",
                    mast_id="mast-850", board_id="pump-45")
report = steady_report(setup)
print("t/c        ", round(setup.front_wing.thickness_ratio, 4))
print("AR         ", round(setup.front_wing.aspect_ratio, 2))
print("Take-off   ", round(report.takeoff_speed * 3.6, 1), "km/h")
print("Beste L/D  ", round(report.best_glide.lift_to_drag, 1),
      "bei", round(report.best_glide.speed * 3.6, 1), "km/h")
PY

Plausibilitätsanker für einen Pumpflügel um 1400 cm² mit 85 kg Fahrer:

Größeerwarteter BereichAlarmzeichen
t/c0,10 bis 0,15unter 0,08 oder über 0,18 → Volumen prüfen
Take-off9 bis 14 km/hunter 8 → Fläche oder cl_max zu optimistisch
Beste Gleitzahl13 bis 20über 25 → Kalibrierung zu günstig

Fällt ein Wert heraus, liegt der Fehler fast immer im Katalog und nicht im Modell: vertauschte Einheiten bei area_cm2 oder ein Volumen in Litern statt cm³.


Kalibrierung mit realen Sessiondaten

Jede Marke hat einen eigenen calibration-Block. Er ist die einzige Stelle, an der Modellergebnisse an die Realität angepasst werden:

yaml
calibration:
  cd_viscous_scale: 0.78    # Reibungsniveau, wichtigster Regler
  cd_interference: 0.06     # Zuschlag für Verbindungsstellen
  lift_slope_scale: 0.92    # Grenzschichtabschlag am Auftriebsanstieg
  notes: >
    Woher die Werte stammen — Datum, Setup, Messgröße.

Was sich womit korrigieren lässt

BeobachtungReglerRichtung
Modell sagt zu hohe Gleitzahl / zu wenig Leistungcd_viscous_scaleerhöhen (0,78 → 0,90)
Modell sagt zu viel Widerstandcd_viscous_scalesenken (0,78 → 0,70)
Take-off passt, Höchstgeschwindigkeit zu hochcd_interferenceerhöhen (0,06 → 0,10)
Take-off zu früh, Rest passtlift_slope_scalesenken (0,92 → 0,88)

Nur einen Faktor auf einmal ändern. cd_viscous_scale wirkt auf alle Bauteile gleichzeitig und ist der einzige Regler, der auch bei einer einzelnen Messgröße eindeutig bestimmbar ist.

Welche Sessiondaten brauchbar sind

Nach Aussagekraft sortiert:

  1. Take-off-Geschwindigkeit aus einem GPS-Log mit 5 bis 10 Hz. Die Geschwindigkeit im Moment des Abhebens, gemittelt über mehrere Starts. Am besten reproduzierbar am Dock, weil die Anfahrt gleichmäßig ist.
  2. Gleitgeschwindigkeit im Glide — die Geschwindigkeit, bei der ein Setup ohne Pumpen am weitesten trägt. Da der Auftrieb im getrimmten Flug immer gleich dem Gewicht ist, fällt der kleinste Widerstand mit der besten Gleitzahl zusammen; die Kennzahl best_glide ist also der richtige Vergleichswert.
  3. Ausrollversuch: nach einem Downwind-Run aufhören zu pumpen und die Geschwindigkeit über der Zeit aufzeichnen. Die Verzögerung dU/dt mal Gesamtmasse ergibt direkt den Widerstand bei dieser Geschwindigkeit. Das ist die einzige Methode, die den Absolutwert misst statt nur einen Betriebspunkt.
  4. Herzfrequenz oder gefühlte Anstrengung: nur für relative Vergleiche zwischen zwei Setups derselben Session brauchbar, nicht für Absolutwerte.

Wichtig: immer Wassertemperatur, Salzgehalt und Fahrergewicht mit Ausrüstung notieren. Ohne diese drei Angaben lässt sich eine Session nicht nachrechnen.

Ablauf

  1. Session fahren, dabei ein Setup und die Bedingungen exakt festhalten.
  2. Dasselbe Setup in der Oberfläche einstellen, Wassertemperatur und Salzgehalt auf die Session setzen.
  3. Modellwert und Messwert vergleichen. Für den Ausrollversuch: die Simulation liefert bei jeder Geschwindigkeit drag_n in Panel E.
  4. cd_viscous_scale so anpassen, dass der Widerstand passt. Der Zusammenhang ist nahezu linear im Reibungsanteil, aber nicht im Gesamtwiderstand — bei niedriger Fahrt dominiert der induzierte Anteil, der vom Faktor unberührt bleibt. Deshalb möglichst bei hoher Fahrt kalibrieren, dort ist der Reibungsanteil groß und der Hebel eindeutig.
  5. Ergebnis in notes dokumentieren, inklusive Datum und Setup.
  6. pytest laufen lassen. Wenn Plausibilitätstests jetzt fehlschlagen, ist die Kalibrierung entweder zu aggressiv oder ein Testgrenzwert war zu eng.

Kalibrierung gegensteuern statt überanpassen

Ein einzelner Messpunkt rechtfertigt keine Änderung um mehr als etwa 15 %. Der Standardwert cd_viscous_scale = 0,78 bildet ab, dass moderne Foilprofile bei Re um 5·10⁵ rund ein Viertel der Lauflänge laminar halten und damit unter der voll turbulenten ITTC-57-Plattenreibung liegen. Wer daraus 0,50 macht, um eine Session zu treffen, hat wahrscheinlich einen Katalogfehler kaschiert.

Und der wichtigste Punkt: Setup-Vergleiche im selben Katalog sind auch unkalibriert belastbar, weil sich systematische Fehler herauskürzen. Die Kalibrierung braucht man nur, wenn Absolutwerte für Widerstand und Leistung stimmen sollen.


Checkliste für einen Pull Request am Katalog

  • [ ] id eindeutig innerhalb der Marke, kleingeschrieben, mit Bindestrichen
  • [ ] Einheiten geprüft: span_mm in mm, area_cm2 in cm², volume_cm3 in cm³
  • [ ] mass_g beziehungsweise mass_kg gesetzt oder als geschätzt markiert
  • [ ] Geschätzte Werte in notes gekennzeichnet
  • [ ] source zeigt auf die Datenquelle
  • [ ] pytest läuft grün
  • [ ] Take-off und Gleitzahl liegen in den Plausibilitätsbereichen oben

Vergleiche zwischen Setups sind belastbarer als Absolutzahlen.