Files
boehmitools-training/android/docs/API_CONTRACT.md
T
2026-07-24 21:37:03 +02:00

8.6 KiB

API-Vertrag boehmitools Training

Stand: aus dem vollständigen boehmitools-Quellstand und den Plugins trainingsplan/trainingstracker 2.0 abgeleitet. Die Plugins werden durch die Host-App unter /plugins/<plugin-id> gemountet. Alle Pfade unten sind relativ zur konfigurierbaren Serverbasis, standardmäßig https://tools.d-razz.de/.

Authentifizierung

Die Host-App schützt alle Pfade außer exakt /api/health optional mit HTTP Basic Auth. Ist APP_PASSWORD gesetzt, muss der Client Authorization: Basic <base64(username:password)> senden. Der Standard-Benutzername des Servers ist admin, sofern APP_USERNAME nicht gesetzt wurde.

Fehlerantwort bei fehlender oder falscher Anmeldung:

{"detail":"Anmeldung erforderlich."}

Status: 401 Unauthorized, Header WWW-Authenticate: Basic realm="boehmitools".

Host-Verbindungstest

GET /api/health

Ohne Authentifizierung erreichbar. Antwortfelder laut Backend:

{
  "status": "ok",
  "version": "1.0.0",
  "plugins": {"trainingsplan":"ok","trainingstracker":"ok"},
  "tandoor_configured": false,
  "openai_configured": true,
  "authentication_enabled": true,
  "jobs_running": 0
}

Status: 200. Die App prüft danach zusätzlich den authentifizierten Tracker-Health-Endpunkt.

Trainingstracker

Basis: /plugins/trainingstracker

GET /api/health

Liefert ok, Server-Datenpfade, Verfügbarkeit von OpenAI und die unterstützten Vertragsversionen:

  • plan_schema
  • training_contract
  • tracker_schema
  • result_data
  • prompt
  • analysis_schema
  • normalization

Status: 200, oder hostweit 401.

GET /api/plans

Antwort:

{
  "plans": [{
    "id": "phase-1.json",
    "filename": "phase-1.json",
    "name": "Phase 1",
    "title": "…",
    "subtitle": "…",
    "weeks": 8,
    "days": 6,
    "tracked": true,
    "start_date": "",
    "completed_sessions": 5,
    "source_changed": false,
    "plan_id": "phase-1",
    "published_revision": 1,
    "tracker_revision": 7
  }],
  "selected": "phase-1.json"
}

Status: 200.

POST /api/plans/{plan_id}/select

Kein Request-Body erforderlich. Antwort: {"ok":true,"selected":"phase-1.json"}.

Status: 200; 404 mit {"error":"Trainingsplan nicht gefunden"}.

GET /api/plans/{plan_id}

Antwort:

{
  "plan": { /* normalisierte veröffentlichte Planfassung */ },
  "tracker": { /* Tracker-Schema 7 */ },
  "analysis_catalog": { /* Wochen- und Gesamtstatus */ },
  "equivalence_guide": { /* Variantencluster und Fallback-FAQ */ },
  "capabilities": {"openai": true}
}

plan enthält ausschließlich vom Backend erzeugte Felder wie source_file, source_hash, plan_id, schema_version, contract_version, compatibility, published_revision, published_at, name, title, subtitle, weeks, days, phases, phase_by_week, stages, exercise_catalog, front, training_format.

tracker enthält mindestens version, revision, source_file, source_hash, profile, progressions, sessions, week_statuses, created_at, updated_at; für die GET-Antwort werden zusätzlich source_changed, analysis_cache und analysis_state zusammengesetzt.

Status: 200; 404; 400 bei nicht lesbarem Plan.

PUT /api/plans/{plan_id}/tracker

Vollständiges Speichern, primär für Kompatibilität. Request entweder direkt Trackerobjekt oder:

{"expected_revision":7,"tracker":{}}

Antwort:

{
  "ok": true,
  "updated_at": "…",
  "revision": 8,
  "analysis_catalog": {},
  "analysis_state": {}
}

Status: 200; 400 für ungültige Nutzdaten; 409 bei Revisionskonflikt; 413 über 5 MiB.

PATCH /api/plans/{plan_id}/tracker/session

Der von der App verwendete Autosave-Vertrag:

{
  "expected_revision": 7,
  "profile": {"start_date":"","display_name":"","plan_notes":""},
  "week_statuses": {"1":{"status":"open"}},
  "session_key": "w01-d01",
  "session": {
    "status": "in_progress",
    "plan_id": "phase-1",
    "plan_revision": 1,
    "items": {}
  }
}

profile, week_statuses und session sind jeweils optional. Ein Patch ersetzt die betreffende Session vollständig, nicht einzelne Felder innerhalb der Session.

Erlaubte Sessionstatus: planned, in_progress, stopped, completed.

Erlaubte Übungsstatus: planned, completed, partial, skipped. Bei skipped kann skip_reason gespeichert werden. Neue strukturierte Ergebnisse nutzen result_data Version 2:

{
  "version": 2,
  "mode": "reps|seconds|minutes",
  "laterality": "bilateral|unilateral",
  "sides_mode": "same|separate",
  "sets": 3,
  "weight_kg": 8.0,
  "values": [8,7,6],
  "left_values": [8,7,6],
  "right_values": [8,7,6]
}

Das Backend akzeptiert maximal 20 Sätze, Werte von 0 bis 100000 und genau ein gemeinsames Gewicht. Alte Freitextfelder result, note, progression bleiben erhalten.

Erfolg wie beim PUT. Konfliktantwort:

{
  "error":"Die Session wurde in einem anderen Tab oder Gerät geändert.",
  "code":"revision_conflict",
  "current_revision":8
}

Status: 409. Die App darf dann nicht automatisch gegen Revision 8 überschreiben.

POST /api/plans/{plan_id}/analysis

Request Woche:

{"scope":"week","week":1}

Request Gesamtanalyse:

{"scope":"overall"}

Unveränderte, bereits aktuelle Analyse: 200 mit cached:true, selection, analysis_state, analysis_cache, analysis_catalog.

Neuer Hintergrundjob: 202 Accepted mit cached:false, job, analysis_state, analysis_catalog.

Fehler: 400 bei fehlender OpenAI-Konfiguration, ungültiger Woche oder fehlenden Daten. 409 wenn bereits eine Analyse läuft. 404 bei unbekanntem Plan.

GET /api/plans/{plan_id}/analysis/status

Antwort enthält analysis_state, analysis_cache, analysis_catalog, updated_at. Die App pollt nur bei analysis_state.status == "running".

Status: 200; 400; 404.

GET /api/plans/{plan_id}/tracker/export

Liefert die Sessiondatei als application/json; erzeugt bei Bedarf einen leeren Tracker. Status 200 oder 404.

Trainingsplan-Editor

Basis: /plugins/trainingsplan

GET /api/plans

Antwort: plans mit id, name, active, revision, published_revision, has_unpublished_changes, plus active.

POST /api/plans

Request:

{"name":"Neue Phase","from":"optionale-plan-id","kind":"training"}

kind kann technisch auch recipe sein; die Android-App erstellt ausschließlich Trainingspläne. Antwort ist der öffentliche Wrapper mit id, plan_id, name, revision, published_revision, updated_at, published_at, has_unpublished_changes, tracked_sessions, config.

GET /api/plans/{pid}

Öffentlicher Wrapper des Entwurfs. Status 200 oder 404.

POST /api/plans/{pid}/select

Setzt den aktiven Plan. Status 200 oder 404.

POST /api/plans/{pid}

Speichert den Entwurf:

{"config":{},"expected_revision":4}

Antwort: ok, öffentlicher Wrapper, validation mit errors und warnings.

Konflikt: 409 mit code:"revision_conflict" und current_revision.

POST /api/plans/{pid}/validate

Request {"config":{}}. Antwort: errors, warnings, normalisierte config, diff. diff enthält Zähler und stabile-ID-basierte Änderungen für days, rotations, exercises, progression_steps, außerdem changes, total_changes, training_format_changed, plan_meta_changed, tracked_sessions.

POST /api/plans/{pid}/publish

Request {"expected_revision":4}. Erfolgsantwort: ok, Wrapper, diff, validation.

Status 400 bei Validierungsfehlern; 409 bei Revisionskonflikt.

POST /api/plans/{pid}/rename

Request {"name":"…"}. Antwort id, name, revision. Status 400 bei leerem Namen, 404 bei unbekanntem Plan.

DELETE /api/plans/{pid}

Antwort {"ok":true,"active":"…"}. Status 400, wenn der letzte Plan gelöscht werden soll; 404 bei unbekanntem Plan.

GET /api/plans/{pid}/proposals

Liefert die Vorschlagsdatei unverändert oder {}. Vorschläge enthalten unter anderem id, status, scope_key, analysis_type, week, target, target_label, target_exercise_id, target_progression_id, target_step_id, suggested_change, reason, condition, manual_step, analysis_created_at.

POST /api/plans/{pid}/proposals/{proposal_id}

Request {"status":"open|accepted|rejected|reviewed"}. Antwort ok, proposals. Ungültiger Status: 400.

Weitere vorhandene Endpunkte

GET /api/config, POST /api/config, GET /api/default, POST /api/reset, GET /api/plans/{pid}/export, POST /api/plans/import, POST /api/pdf existieren. Die native App verwendet für den Kernbetrieb die planbezogenen Endpunkte; sie bearbeitet niemals Serverdateien direkt.