From 6b3aee458a1beecbcc966680f76528fa0f4bb1a1 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Tue, 31 Mar 2026 09:04:24 +0200 Subject: [PATCH] feat(bisaya-course): expand exercises for shopping, neighborhood visits, conflict resolution, and free speaking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added new exercises in multiple-choice, gap-fill, and situational response formats for categories including 'Einkaufen vertiefen', 'Nachbarschaft & Besuche', 'Rollenspiel - Konflikt und Hilfe', and 'Freies Sprechen - Alltag ohne Stütze'. - Each exercise includes detailed instructions, question data, answer data, and explanations to enhance the learning experience for Bisaya language learners. - Focused on practical scenarios to improve conversational skills and vocabulary retention. --- .../scripts/create-bisaya-course-content.js | 528 ++++++++++++++++++ ...T_PREGNANCY_SCHEDULED_BIRTH_DAEMON_SPEC.md | 178 ++++++ 2 files changed, 706 insertions(+) create mode 100644 docs/FALUKANT_PREGNANCY_SCHEDULED_BIRTH_DAEMON_SPEC.md diff --git a/backend/scripts/create-bisaya-course-content.js b/backend/scripts/create-bisaya-course-content.js index e6e4440..f0d2471 100644 --- a/backend/scripts/create-bisaya-course-content.js +++ b/backend/scripts/create-bisaya-course-content.js @@ -2136,6 +2136,534 @@ const BISAYA_EXERCISES = { }, explanation: 'Die Abschlussprüfung bündelt Weg, Organisation und Hilfe in einer letzten Miniszene.' }) + ], + + 'Einkaufen vertiefen': [ + { + exerciseTypeId: 2, + title: 'Gesamtpreis erfragen', + instruction: 'Wähle die passendste Einkaufsfrage.', + questionData: { + type: 'multiple_choice', + question: 'Du hast mehrere Dinge ausgesucht. Wie fragst du nach dem Gesamtpreis?', + options: ['Pila ni tanan?', 'Kapoy na ka?', 'Asa ang bata?', 'Tabangan tika.'] + }, + answerData: { type: 'multiple_choice', correctAnswer: 0 }, + explanation: '"Pila ni tanan?" fragt nach dem Gesamtpreis.' + }, + { + exerciseTypeId: 1, + title: 'Menge ergänzen', + instruction: 'Fülle die Lücke mit dem passenden Mengenwort.', + questionData: { + type: 'gap_fill', + text: 'Pwede tulo ka{gap}?', + gaps: 1 + }, + answerData: { + type: 'gap_fill', + answers: ['buok'] + }, + explanation: '"buok" wird für zählbare Einzelstücke verwendet.' + }, + withTypeName('situational_response', { + title: 'Einkauf abschließen', + instruction: 'Reagiere passend beim Bezahlen.', + questionData: { + type: 'situational_response', + question: 'Frage nach dem Gesamtpreis und sage dann, dass du es nimmst.', + keywords: ['tanan', 'kuhaon'] + }, + answerData: { + modelAnswer: 'Pila ni tanan? Kuhaon na nako.', + keywords: ['tanan', 'kuhaon'] + }, + explanation: 'Das ist ein sehr alltagsnaher Miniabschluss beim Einkaufen.' + }) + ], + + 'Nachbarschaft & Besuche': [ + { + exerciseTypeId: 2, + title: 'Bei Nachbarn vorbeischauen', + instruction: 'Wähle die passendste Aussage für einen Besuch bei Nachbarn.', + questionData: { + type: 'multiple_choice', + question: 'Wie sagst du: "Wir waren bei den Nachbarn"?', + options: ['Niadto mi sa silingan.', 'Adto ta sa doktor.', 'Magdula ta.', 'Naa koy assignment.'] + }, + answerData: { type: 'multiple_choice', correctAnswer: 0 }, + explanation: '"silingan" bedeutet Nachbar oder Nachbarschaft.' + }, + { + exerciseTypeId: 1, + title: 'Einladung in die Nachbarschaft', + instruction: 'Fülle die Lücke mit dem passenden Besuchswort.', + questionData: { + type: 'gap_fill', + text: '{gap} mo unya.', + gaps: 1 + }, + answerData: { + type: 'gap_fill', + answers: ['Bisita'] + }, + explanation: '"Bisita mo unya." lädt zu einem späteren Besuch ein.' + }, + withTypeName('situational_response', { + title: 'Nachbarschaftsbesuch ankündigen', + instruction: 'Reagiere in zwei kurzen Sätzen.', + questionData: { + type: 'situational_response', + question: 'Sag, dass ihr bei den Nachbarn wart und dass sie später zu Besuch kommen können.', + keywords: ['silingan', 'bisita'] + }, + answerData: { + modelAnswer: 'Niadto mi sa silingan. Bisita mo unya.', + keywords: ['silingan', 'bisita'] + }, + explanation: 'Das verbindet Begegnung und Einladung in einem natürlichen Nachbarschaftskontext.' + }) + ], + + 'Rollenspiel - Konflikt und Hilfe': [ + { + exerciseTypeId: 2, + title: 'Konflikt ruhig eröffnen', + instruction: 'Wähle die höflichste Eröffnung für ein schwieriges Gespräch.', + questionData: { + type: 'multiple_choice', + question: 'Wie leitest du ein Konfliktgespräch ruhig ein?', + options: ['Pwede nato istoryahan?', 'Pila ni tanan?', 'Sulod lang.', 'Nikaon na ka?'] + }, + answerData: { type: 'multiple_choice', correctAnswer: 0 }, + explanation: '"Pwede nato istoryahan?" ist weich und gesprächsorientiert.' + }, + { + exerciseTypeId: 1, + title: 'Hilfe ergänzen', + instruction: 'Fülle die Lücke mit der passenden Hilfeformel.', + questionData: { + type: 'gap_fill', + text: '{gap} tika.', + gaps: 1 + }, + answerData: { + type: 'gap_fill', + answers: ['Tabangan'] + }, + explanation: '"Tabangan tika." bedeutet "Ich helfe dir."' + }, + withTypeName('situational_response', { + title: 'Konflikt und Hilfe verbinden', + instruction: 'Reagiere kurz, höflich und lösungsorientiert.', + questionData: { + type: 'situational_response', + question: 'Bitte darum, das Problem zu besprechen, und biete anschließend Hilfe an.', + keywords: ['istoryahan', 'tabangan'] + }, + answerData: { + modelAnswer: 'Pwede nato istoryahan? Tabangan tika.', + keywords: ['istoryahan', 'tabangan'] + }, + explanation: 'Das Rollenspiel verbindet Deeskalation und konkrete Hilfe.' + }) + ], + + 'Freies Sprechen - Alltag ohne Stütze': [ + { + exerciseTypeId: 2, + title: 'Freies Sprechen strukturieren', + instruction: 'Wähle den Ausdruck, mit dem du eine freie Aussage natürlich einleitest.', + questionData: { + type: 'multiple_choice', + question: 'Welche Form passt gut als Einleitung für freies Sprechen?', + options: ['Sa tinuod...', 'Pila ang plite?', 'Asa ang sakayan?', 'Sulod lang.'] + }, + answerData: { type: 'multiple_choice', correctAnswer: 0 }, + explanation: '"Sa tinuod..." eignet sich gut, um frei in eine Aussage hineinzukommen.' + }, + { + exerciseTypeId: 3, + title: 'Freie Aussage bauen', + instruction: 'Ordne die Wörter zu einer typischen Einleitung für eine freie Alltagsaussage.', + questionData: { + type: 'sentence_building', + question: 'Baue: "Meistens..."', + tokens: ['Kasagaran'] + }, + answerData: { + correct: ['Kasagaran...'] + }, + explanation: 'Diese Einleitungen helfen, im freien Sprechen in Gang zu kommen.' + }, + { + title: 'Alltag frei erzählen', + instruction: 'Sprich ohne deutsche Stütze eine kurze freie Alltagsaussage.', + exerciseTypeId: 8, + questionData: { + type: 'speaking_from_memory', + question: 'Erzähle kurz frei über deinen Alltag und nutze mindestens zwei dieser Einleitungen: Sa tinuod, Kasagaran, Usahay, Apan.', + expectedText: 'Sa tinuod... Kasagaran... Usahay... Apan...', + keywords: ['tinuod', 'kasagaran', 'usahay', 'apan'] + }, + answerData: { + type: 'speaking_from_memory' + }, + explanation: 'Hier geht es nicht mehr um perfekte Vorgabe, sondern um flüssige eigene Produktion.' + } + ], + + 'Langzeitreview - Intensiv I': [ + { + exerciseTypeId: 2, + title: 'Frühe Muster reaktivieren', + instruction: 'Wähle den Ausdruck, der sicher im Langzeitgedächtnis sitzen sollte.', + questionData: { + type: 'multiple_choice', + question: 'Welches frühe Fürsorgemuster musst du sofort wiedererkennen?', + options: ['Nikaon na ka?', 'Asa ang porma?', 'Naa moy appointment?', 'Mubayad ko.'] + }, + answerData: { type: 'multiple_choice', correctAnswer: 0 }, + explanation: 'Das Langzeitreview holt sehr frühe Kernmuster bewusst wieder nach vorn.' + }, + { + exerciseTypeId: 1, + title: 'Frühe Preisfrage ergänzen', + instruction: 'Fülle die Lücke mit dem passenden frühen Kernwort.', + questionData: { + type: 'gap_fill', + text: '{gap} ni?', + gaps: 1 + }, + answerData: { + type: 'gap_fill', + answers: ['Tagpila'] + }, + explanation: '"Tagpila ni?" gehört zu den wichtigsten frühen Alltagsfragen.' + }, + withTypeName('situational_response', { + title: 'Frühe Routinen bündeln', + instruction: 'Reagiere mit zwei sehr frühen Kernmustern.', + questionData: { + type: 'situational_response', + question: 'Begrüße jemanden kurz und frage dann, ob die Person schon gegessen hat.', + keywords: ['kumusta', 'nikaon'] + }, + answerData: { + modelAnswer: 'Kumusta ka? Nikaon na ka?', + keywords: ['kumusta', 'nikaon'] + }, + explanation: 'Das reviewt ganz bewusst sehr frühe, sehr wichtige Sozialmuster.' + }) + ], + + 'Langzeitreview - Intensiv II': [ + { + exerciseTypeId: 2, + title: 'Frühe und späte Themen mischen', + instruction: 'Wähle den Ausdruck, der zum Reaktivieren späterer Alltagsfelder gehört.', + questionData: { + type: 'multiple_choice', + question: 'Welcher Ausdruck gehört klar zu Schule, Gesundheit oder Erledigungen?', + options: ['resibo', 'Kumusta ka?', 'Palangga taka.', 'Sulod lang.'] + }, + answerData: { type: 'multiple_choice', correctAnswer: 0 }, + explanation: '"resibo" steht hier für spätere Erledigungs- und Alltagsblöcke.' + }, + { + exerciseTypeId: 1, + title: 'Gesundheit reaktivieren', + instruction: 'Fülle die Lücke mit dem passenden Wort aus dem Gesundheitsbereich.', + questionData: { + type: 'gap_fill', + text: 'Adto ta sa {gap}.', + gaps: 1 + }, + answerData: { + type: 'gap_fill', + answers: ['doktor'] + }, + explanation: 'Auch späte Alltagsfelder sollen im Langzeitreview schnell wieder greifbar sein.' + }, + withTypeName('situational_response', { + title: 'Späte Themen bündeln', + instruction: 'Reagiere mit zwei kurzen Sätzen.', + questionData: { + type: 'situational_response', + question: 'Sage, dass ihr zum Arzt geht, und erwähne danach ein Dokument oder einen Beleg.', + keywords: ['doktor', 'resibo'] + }, + answerData: { + modelAnswer: 'Adto ta sa doktor. Naa ko resibo.', + keywords: ['doktor', 'resibo'] + }, + explanation: 'Das Langzeitreview mischt bewusst entfernte Themenfelder in einer kurzen Reaktion.' + }) + ], + + 'Hilfe & Unterstützung': [ + { + exerciseTypeId: 2, + title: 'Um Hilfe bitten', + instruction: 'Wähle die passendste Bitte um Unterstützung.', + questionData: { + type: 'multiple_choice', + question: 'Wie fragst du höflich, ob dir jemand helfen kann?', + options: ['Pwede ka motabang?', 'Asa ang sakayan?', 'Naa moy appointment?', 'Sulod lang.'] + }, + answerData: { type: 'multiple_choice', correctAnswer: 0 }, + explanation: '"Pwede ka motabang?" ist eine direkte, natürliche Hilfsbitte.' + }, + { + exerciseTypeId: 1, + title: 'Hilfe anbieten ergänzen', + instruction: 'Fülle die Lücke mit der passenden Hilfsformel.', + questionData: { + type: 'gap_fill', + text: '{gap} tika.', + gaps: 1 + }, + answerData: { + type: 'gap_fill', + answers: ['Tabangan'] + }, + explanation: '"Tabangan tika." bedeutet "Ich helfe dir."' + }, + withTypeName('situational_response', { + title: 'Hilfe erfragen und anbieten', + instruction: 'Reagiere in zwei kurzen Sätzen.', + questionData: { + type: 'situational_response', + question: 'Bitte erst um Hilfe und bedanke dich danach kurz für die Unterstützung.', + keywords: ['tabang', 'salamat'] + }, + answerData: { + modelAnswer: 'Pwede ka motabang? Salamat sa tabang.', + keywords: ['tabang', 'salamat'] + }, + explanation: 'Die Lektion verbindet Bitte und soziale Reaktion zu einem natürlichen Miniablauf.' + }) + ], + + 'Höflich reagieren und ablehnen': [ + { + exerciseTypeId: 2, + title: 'Sanft ablehnen', + instruction: 'Wähle die höflichste weiche Absage.', + questionData: { + type: 'multiple_choice', + question: 'Wie lehnst du etwas freundlich und nicht zu direkt ab?', + options: ['Dili lang sa karon.', 'Tabang!', 'Nikaon na ka?', 'Mubayad ko.'] + }, + answerData: { type: 'multiple_choice', correctAnswer: 0 }, + explanation: '"Dili lang sa karon." klingt deutlich weicher als ein hartes Nein.' + }, + { + exerciseTypeId: 1, + title: 'Später statt jetzt', + instruction: 'Fülle die Lücke mit der passenden weichen Reaktion.', + questionData: { + type: 'gap_fill', + text: '{gap} na lang.', + gaps: 1 + }, + answerData: { + type: 'gap_fill', + answers: ['Sunod'] + }, + explanation: '"Sunod na lang." verschiebt höflich auf später.' + }, + withTypeName('situational_response', { + title: 'Höflich verschieben', + instruction: 'Reagiere freundlich und weich.', + questionData: { + type: 'situational_response', + question: 'Lehne eine Einladung höflich für heute ab und verschiebe sie auf später.', + keywords: ['dili', 'sunod'] + }, + answerData: { + modelAnswer: 'Dili lang sa karon. Sunod na lang.', + keywords: ['dili', 'sunod'] + }, + explanation: 'Das ist genau die weiche soziale Reaktionsform dieser Lektion.' + }) + ], + + 'Feste & Einladungen': [ + { + exerciseTypeId: 2, + title: 'Zur Feier einladen', + instruction: 'Wähle die passende Einladungsformel.', + questionData: { + type: 'multiple_choice', + question: 'Wie sagst du: "Ich lade dich ein"?', + options: ['Giinvite tika.', 'Adto ta sa doktor.', 'Kapoy na ka?', 'Asa imong bag?'] + }, + answerData: { type: 'multiple_choice', correctAnswer: 0 }, + explanation: '"Giinvite tika." ist eine alltagsnahe Einladungsform.' + }, + { + exerciseTypeId: 1, + title: 'Zur Fiesta fragen', + instruction: 'Fülle die Lücke mit dem passenden Wort.', + questionData: { + type: 'gap_fill', + text: 'Moadto ka sa {gap}?', + gaps: 1 + }, + answerData: { + type: 'gap_fill', + answers: ['pista'] + }, + explanation: '"pista" steht hier für Feier oder Fiesta.' + }, + withTypeName('situational_response', { + title: 'Einladung und Treffpunkt', + instruction: 'Reagiere in zwei kurzen Sätzen.', + questionData: { + type: 'situational_response', + question: 'Lade jemanden ein und sage, dass ihr euch dort trefft.', + keywords: ['invite', 'didto'] + }, + answerData: { + modelAnswer: 'Giinvite tika. Magkita ta didto.', + keywords: ['invite', 'didto'] + }, + explanation: 'Die Lektion verbindet Einladung und Verabredung in einer natürlichen Sozialszene.' + }) + ], + + 'Freies Erzählen - Mein Alltag': [ + { + exerciseTypeId: 2, + title: 'Tagesablauf einleiten', + instruction: 'Wähle die passendste Einleitung für einen Tagesablauf.', + questionData: { + type: 'multiple_choice', + question: 'Welche Formulierung passt gut als Start in einen erzählten Tagesablauf?', + options: ['Sa buntag...', 'Pila ni tanan?', 'Asa ang porma?', 'Sulod lang.'] + }, + answerData: { type: 'multiple_choice', correctAnswer: 0 }, + explanation: '"Sa buntag..." eröffnet natürlich einen erzählten Tagesabschnitt.' + }, + { + exerciseTypeId: 1, + title: 'Tagesabschnitt ergänzen', + instruction: 'Fülle die Lücke mit dem passenden Zeitabschnitt.', + questionData: { + type: 'gap_fill', + text: 'Sa {gap}...', + gaps: 1 + }, + answerData: { + type: 'gap_fill', + answers: ['hapon'] + }, + explanation: '"Sa hapon..." ist eine häufige Einleitung für den Nachmittag.' + }, + { + exerciseTypeId: 8, + title: 'Eigenen Alltag erzählen', + instruction: 'Sprich frei über deinen Tagesablauf.', + questionData: { + type: 'speaking_from_memory', + question: 'Erzähle kurz, was du morgens, nachmittags und abends machst.', + expectedText: 'Sa buntag... Sa hapon... Sa gabii...', + keywords: ['buntag', 'hapon', 'gabii'] + }, + answerData: { + type: 'speaking_from_memory' + }, + explanation: 'Hier steht die freie, zusammenhängende Produktion im Vordergrund.' + } + ], + + 'Freies Erzählen - Familie, Sorgen, Pläne': [ + { + exerciseTypeId: 2, + title: 'Sorge ausdrücken', + instruction: 'Wähle die Formulierung, mit der du eine leichte Sorge ausdrückst.', + questionData: { + type: 'multiple_choice', + question: 'Wie sagst du natürlich: "Ich bin etwas besorgt"?', + options: ['Naguol ko gamay.', 'Mubayad ko.', 'Sulod lang.', 'Tagpila ni?'] + }, + answerData: { type: 'multiple_choice', correctAnswer: 0 }, + explanation: '"Naguol ko gamay." drückt eine leichte Sorge oder Niedergeschlagenheit aus.' + }, + { + exerciseTypeId: 1, + title: 'Plan ergänzen', + instruction: 'Fülle die Lücke mit dem passenden Planungswort.', + questionData: { + type: 'gap_fill', + text: 'Aduna koy {gap} unya.', + gaps: 1 + }, + answerData: { + type: 'gap_fill', + answers: ['plano'] + }, + explanation: '"plano" ist hier das Schlüsselwort für einen bevorstehenden Plan.' + }, + { + exerciseTypeId: 8, + title: 'Familie, Sorge und Plan frei verbinden', + instruction: 'Sprich frei in mehreren kurzen Sätzen.', + questionData: { + type: 'speaking_from_memory', + question: 'Erzähle kurz von Familie, einer Sorge und einem Plan für später.', + expectedText: 'Naguol ko gamay. Pero okay ra. Aduna koy plano unya.', + keywords: ['naguol', 'okay', 'plano'] + }, + answerData: { + type: 'speaking_from_memory' + }, + explanation: 'Diese Lektion trainiert freie Verbindung von Gefühl, Familie und Planung.' + } + ], + + 'Kultur, Familie & Sprache langfristig': [ + { + exerciseTypeId: 2, + title: 'Kulturellen Kernbegriff erkennen', + instruction: 'Wähle den Ausdruck, der stark mit respektvollem Umgang verbunden ist.', + questionData: { + type: 'multiple_choice', + question: 'Welcher Ausdruck gehört besonders zum kulturellen Schwerpunkt von Respekt und Rücksicht?', + options: ['respeto', 'plite', 'resibo', 'assignment'] + }, + answerData: { type: 'multiple_choice', correctAnswer: 0 }, + explanation: '"respeto" steht direkt für Respekt im sozialen Umgang.' + }, + { + exerciseTypeId: 2, + title: 'Familienkultur und Sprache', + instruction: 'Wähle den Ausdruck, der besonders mit sozialem Miteinander verbunden ist.', + questionData: { + type: 'multiple_choice', + question: 'Welcher Begriff verweist besonders auf gemeinschaftliches Mitziehen und gutes Miteinander?', + options: ['pakikisama', 'doktor', 'ATM', 'sukli'] + }, + answerData: { type: 'multiple_choice', correctAnswer: 0 }, + explanation: '"pakikisama" ist ein zentraler kultureller Begriff für harmonisches Miteinander.' + }, + { + exerciseTypeId: 8, + title: 'Kulturelle Schlüsselwörter laut festigen', + instruction: 'Sprich die kulturellen Schlüsselwörter laut und bewusst.', + questionData: { + type: 'speaking_from_memory', + question: 'Sprich die Wörter respeto, pakikisama, amping und palihug laut und deutlich.', + expectedText: 'respeto pakikisama amping palihug', + keywords: ['respeto', 'pakikisama', 'amping', 'palihug'] + }, + answerData: { + type: 'speaking_from_memory' + }, + explanation: 'Die Schlusslektion verankert kulturelle Schlüsselwörter bewusst als Langzeitmarker.' + } ] }; diff --git a/docs/FALUKANT_PREGNANCY_SCHEDULED_BIRTH_DAEMON_SPEC.md b/docs/FALUKANT_PREGNANCY_SCHEDULED_BIRTH_DAEMON_SPEC.md new file mode 100644 index 0000000..0ffb600 --- /dev/null +++ b/docs/FALUKANT_PREGNANCY_SCHEDULED_BIRTH_DAEMON_SPEC.md @@ -0,0 +1,178 @@ +# Falukant: Geplante Schwangerschaft & Geburt – Daemon- und Datenbankkonzept + +Dieses Dokument beschreibt das **vereinheitlichte Konzept** für Schwangerschaft, die der **externe Daemon** (`UserCharacterWorker`, C++) verarbeiten soll, sowie die **Datenbank** und die **Abgrenzung zur bestehenden Zufallslogik**. Ziel ist, dass gesetzte Termine (`pregnancy_due_at`) und der gewählte Vater (`pregnancy_father_character_id`) – u. a. aus dem Admin-Tool – **tatsächlich zu Geburten führen**, ohne dass nur die Node-API „Geburt erzwingen“ funktioniert. + +--- + +## 1. Ist-Zustand (Problem) + +### 1.1 Datenbank (bereits vorhanden) + +Auf `falukant_data."character"` existieren (siehe `backend/sql/add_character_pregnancy.sql`): + +| Spalte | Typ | Bedeutung | +|--------|-----|-----------| +| `pregnancy_due_at` | `TIMESTAMPTZ` NULL | Erwarteter Geburtstermin | +| `pregnancy_father_character_id` | `INTEGER` NULL, FK auf `character(id)` | Vater-Charakter | + +Die **Node-Backend**-Logik (Admin, `falukantService`) **liest und schreibt** diese Felder. + +### 1.2 Daemon – aktuelles Verhalten + +In `src/usercharacterworker.cpp` / `src/usercharacterworker.h`: + +- `processPregnancies()` führt **`QUERY_GET_PREGNANCY_CANDIDATES`** aus. +- Diese Query: + - bezieht sich **nur** auf Ehen (`falukant_type.relationship.tr = 'married'`), + - nutzt **keine** `pregnancy_*`-Spalten, + - modelliert Geburten **per Zufallsprozess** (altersabhängige Wahrscheinlichkeit), + - setzt implizit **`character1_id` = Vater** und **`character2_id` = Mutter** (fest verdrahtet, ohne Geschlechts- oder Rollenprüfung). + +**Folge:** Wer im Spiel oder per Admin als „schwanger“ mit Termin und Vater gespeichert wird, **wird vom Daemon nicht erkannt**. Geburten aus diesem Konzept passieren nur, wenn sie **manuell** per Node (z. B. Admin „Geburt erzwingen“) ausgelöst werden. + +**Zusätzlich:** `QUERY_AUTOBATISM` setzt nur `child_relation.name_set` nach einigen Tagen – hat **nichts** mit Schwangerschafts-Spalten zu tun. + +--- + +## 2. Soll-Konzept (Zwei parallele Wege, eine klare Priorität) + +### 2.1 Weg A – **Geplante Schwangerschaft** (DB-gesteuert, neu für den Daemon) + +**Auslöser:** `pregnancy_due_at` ist gesetzt und der Zeitpunkt ist **fällig**. + +**Regeln (Vorschlag):** + +1. **Eine** Mutter ist immer der Datensatz in `character`, auf dem `pregnancy_due_at` und die Schwangerschaft „liegen“. +2. **Vater:** `pregnancy_father_character_id` + - Wenn gesetzt: muss ein existierender Charakter sein, **nicht** `mother.id`. + - Wenn `NULL`: Policy festlegen (siehe Abschnitt 8). +3. **Fälligkeit:** z. B. `date_trunc('day', pregnancy_due_at AT TIME ZONE 'Europe/Berlin') <= current_date` (oder einheitlich **UTC** – wichtig ist **eine** Definition im Team). +4. Nach erfolgreicher Geburt: **`pregnancy_due_at` und `pregnancy_father_character_id` auf NULL** setzen (Schwangerschaft beendet). + +Dieser Weg entspricht dem, was Admin und Spieler erwarten, wenn ein **Termin** existiert. + +### 2.2 Weg B – **Legacy: Zufallsgeburten bei Ehe** (bestehende Query) + +Die Query `QUERY_GET_PREGNANCY_CANDIDATES` modelliert **„natürliche“** Zufallsgeburten ohne `pregnancy_*`-Felder. + +**Entscheidung für die Umsetzung:** + +- **Option B1:** Weg B **abschalten** oder stark reduzieren, wenn Weg A das offizielle Modell ist. +- **Option B2:** Weg B **beibehalten** für Atmosphäre, aber **nur**, wenn **keine** aktive geplante Schwangerschaft auf den betroffenen Charakteren existiert (doppelte Geburten vermeiden). +- **Option B3:** Weg B nur noch **Simulation**, bis Spiel-Logik „Konzeption“ explizit setzt (größerer Umbau). + +**Empfehlung:** Mindestens **B2** oder **B1**, damit keine zwei Geburten pro Tag für dieselbe Ehe aus unterschiedlichen Regeln entstehen. + +--- + +## 3. Datenbank – Erweiterungen (Vorschlag) + +Die Minimalvariante kommt **ohne** neue Spalten aus (nur Daemon-Logik). Für Robustheit und spätere Features sind **optionale** Erweiterungen sinnvoll: + +### 3.1 Pflicht (kein Schema-Zwang, aber empfohlen) + +- **Index** für den Daemon-Tick, z. B.: + +```sql +CREATE INDEX IF NOT EXISTS idx_character_pregnancy_due + ON falukant_data."character" (pregnancy_due_at) + WHERE pregnancy_due_at IS NOT NULL; +``` + +### 3.2 Optional – Metadaten + +| Spalte | Typ | Zweck | +|--------|-----|--------| +| `pregnancy_source` | `TEXT` oder `ENUM` | z. B. `admin`, `gameplay`, `npc` – für Logging und Regeln | +| `pregnancy_conception_at` | `TIMESTAMPTZ` | optional, für Anzeige/Quests | +| `pregnancy_birth_context` | `TEXT` | `marriage` | `lover` – für korrekten `child_relation.birth_context` ohne Heuristik | + +Wenn `pregnancy_birth_context` **nicht** eingeführt wird, leitet der Daemon den Kontext aus der **Beziehung** zwischen Mutter und Vater ab (verheiratet → `marriage`, Liebhaber → `lover`, sonst Default `marriage` oder Policy). + +### 3.3 `child_relation` – Parität mit Node + +Das Sequelize-Modell erwartet u. a. `father_name`, `mother_name`, `legitimacy`, `birth_context`, `public_known`. Die **Daemon-INSERT**-Query (`QUERY_INSERT_CHILD_RELATION`) muss mit der **realen DB** übereinstimmen (Pflichtfelder, Defaults). Falls nötig: + +- INSERT um **Namen** (aus Vordefiniert-Tabellen) und **legitimacy / birth_context / public_known** erweitern, +- oder DB-Defaults / Trigger ergänzen, +- **gleiche Semantik** wie `adminForceFalukantBirth` in `adminService.js` anstreben. + +--- + +## 4. Daemon – Umsetzung (technisch) + +### 4.1 Neue SQL-Query: „fällige geplante Geburten“ + +**Skizze (logisch):** + +- SELECT Mutter-`character` `c` mit: + - `c.pregnancy_due_at IS NOT NULL` + - `c.pregnancy_due_at` fällig (siehe Zeitzone) + - JOIN Vater `c_father` auf `c.pregnancy_father_character_id = c_father.id` **wenn** Vater Pflicht ist +- Pro Zeile: `father_cid`, `mother_cid`, `region_id`, `title_of_nobility` (vom passenden Elternteil), `last_name`, `father_uid`, `mother_uid` analog zur bestehenden Schleife. + +**Wichtig:** Vater/Mutter **nicht** aus `relationship.character1/2` ableiten, sondern aus **expliziten IDs** (`pregnancy_father_character_id` + Zeilen-ID der Mutter). + +### 4.2 Ablauf in `processPregnancies()` + +1. `QUERY_AUTOBATISM` wie bisher (optional, Reihenfolge beachten). +2. **Neu:** Transaktion oder feste Reihenfolge: + - Kandidaten für **geplante Geburt** laden + - **pro Kandidat:** Kind einfügen (`QUERY_INSERT_CHILD` oder gemeinsame Funktion), `child_relation` einfügen, **Schwangerschaft** auf der Mutter **leeren** + - Benachrichtigungen (`children_update`, `falukantUpdateStatus`) wie bei bestehender Schleife +3. **Legacy-Query** nur ausführen, wenn nach Absprache (Option B1–B3). + +**Idempotenz:** Pro Tick darf dieselbe schwangere Zeile **nicht** zweimal gebären. Am sichersten: **UPDATE … RETURNING** oder **DELETE** der Schwangerschaftsdaten in derselben Transaktion wie das Kind. + +### 4.3 Geschlecht der Eltern + +Optional: Plausibilitätsprüfung (`gender` Mutter/Vater) – kann im ersten Schritt weggelassen werden, sollte aber langfristig mit dem Spielregelwerk übereinstimmen. + +--- + +## 5. Backend (Node) – Abstimmung + +- **Zeitzonen:** `pregnancy_due_at` wird in JS als `Date` gesetzt; Daemon muss **dieselbe** Fälligkeitsdefinition nutzen wie die UI („heute“ = 0 Tage). +- **WebSocket:** Nach Daemon-Geburt dieselben Events wie bei Admin-Geburt, damit Clients die Familie aktualisieren. + +--- + +## 6. Frontend + +- Hinweis in der Familien-Ansicht: „Geburt erfolgt automatisch am Termin“ (wenn Daemon aktiv), sonst „nur nach Admin-Aktion“ – je nach Rollout. + +--- + +## 7. Test-Checkliste + +- [ ] Admin: Schwangerschaft mit Termin **heute** und Vater – nach Daemon-Lauf: Kind existiert, Schwangerschaft weg. +- [ ] Kein Vater (`NULL`) – definiertes Verhalten (Fehler loggen / überspringen / NPC-Vater – **Policy**). +- [ ] Doppel-Tick: keine doppelte Geburt. +- [ ] Legacy-Zufallsgeburt: keine Kollision mit aktivem `pregnancy_due_at` auf derselben Mutter. +- [ ] `child_relation` und `character`-Kind konsistent mit Node-Admin-Geburt. + +--- + +## 8. Offene Policy-Fragen (bitte vor Implementierung festlegen) + +1. **Vater `NULL`:** Geburt abbrechen, stillen Vater aus Beziehung erraten, oder festen Platzhalter? +2. **Legacy-Query:** komplett entfernen oder nur noch unter Bedingungen? +3. **Zeitzone** für „Termin ist heute“: Server-UTC, Europe/Berlin, oder Nutzer-TZ? +4. **Liebschaftsgeburten:** nur über `pregnancy_*` + `birth_context`/`pregnancy_birth_context`, nie über reine Ehe-Query? + +--- + +## 9. Referenzdateien im Repo + +| Bereich | Datei | +|---------|--------| +| Daemon | `src/usercharacterworker.cpp` (`processPregnancies`) | +| SQL-Strings | `src/usercharacterworker.h` (`QUERY_GET_PREGNANCY_CANDIDATES`, `QUERY_INSERT_CHILD`, `QUERY_INSERT_CHILD_RELATION`) | +| DB-Spalten Schwangerschaft | `backend/sql/add_character_pregnancy.sql` | +| Modell Character | `backend/models/falukant/data/character.js` | +| Admin / Geburt Node | `backend/services/adminService.js` (`adminForceFalukantPregnancy`, `adminForceFalukantBirth`) | +| Familie API | `backend/services/falukantService.js` (`_getCharacterPregnancyOptional`, `getFamily`) | + +--- + +*Stand: technische Analyse des Repos; zur Abstimmung mit Game-Design und Deployment des Daemons.*