diff --git a/api/alembic/versions/20260508_0018_add_adventure_entry_linguistic_and_timing_data.py b/api/alembic/versions/20260508_0018_add_adventure_entry_linguistic_and_timing_data.py new file mode 100644 index 0000000..e5f8edf --- /dev/null +++ b/api/alembic/versions/20260508_0018_add_adventure_entry_linguistic_and_timing_data.py @@ -0,0 +1,31 @@ +"""add story_text_linguistic_data and pipeline_timing to adventure entry + +Revision ID: 0018 +Revises: 0017 +Create Date: 2026-05-08 +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +revision: str = "0018" +down_revision: Union[str, None] = "0017" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + +def upgrade() -> None: + op.add_column( + "choose_your_own_adventure_entry", + sa.Column("story_text_linguistic_data", postgresql.JSONB(), nullable=True), + ) + op.add_column( + "choose_your_own_adventure_entry", + sa.Column("pipeline_timing", postgresql.JSONB(), nullable=True), + ) + + +def downgrade() -> None: + op.drop_column("choose_your_own_adventure_entry", "pipeline_timing") + op.drop_column("choose_your_own_adventure_entry", "story_text_linguistic_data") diff --git a/api/app/domain/models/adventure.py b/api/app/domain/models/adventure.py index 6e915ff..cb1399b 100644 --- a/api/app/domain/models/adventure.py +++ b/api/app/domain/models/adventure.py @@ -33,6 +33,8 @@ class AdventureEntry: story_text: str | None gamemaster_notes: str | None llm_data: dict | None + story_text_linguistic_data: dict | None + pipeline_timing: dict | None created_at: datetime diff --git a/api/app/domain/services/adventure_service.py b/api/app/domain/services/adventure_service.py index 409397f..6fae0a4 100644 --- a/api/app/domain/services/adventure_service.py +++ b/api/app/domain/services/adventure_service.py @@ -1,4 +1,6 @@ +import asyncio import logging +import time import uuid from ...languages import SUPPORTED_LANGUAGES @@ -21,16 +23,23 @@ from ...outbound.postgres.repositories.adventure_repository import ( PostgresAdventureEntryTranslationRepository, PostgresAdventureRepository, ) +from ...outbound.spacy.spacy_client import SpacyClient from ...storage import upload_audio from ..models.adventure import ( Adventure, AdventureEntry, + AdventureEntryPossibleChoice, AdventureEntryPossibleChoiceDecision, ) logger = logging.getLogger(__name__) +def _base_language(lang: str) -> str: + """Normalise a language tag like 'en-US' or 'EN' to a bare ISO 639-1 code.""" + return lang.split("-")[0].split("_")[0].lower() + + class AdventureService: def __init__( self, @@ -43,6 +52,7 @@ class AdventureService: anthropic_client: AnthropicClient, deepl_client: DeepLClient, gemini_client: GeminiClient, + spacy_client: SpacyClient, ) -> None: self.adventure_repo = adventure_repo self.entry_repo = entry_repo @@ -53,6 +63,7 @@ class AdventureService: self.anthropic_client = anthropic_client self.deepl_client = deepl_client self.gemini_client = gemini_client + self.spacy_client = spacy_client async def create_adventure_for_user( self, @@ -148,8 +159,8 @@ class AdventureService: ) -> None: """Full entry generation pipeline. Called from the worker queue. - Sequence: LLM generation → parse → persist → translate → TTS → - adventure title (first entry only) → update adventure status. + Sequence: LLM generation → parse → persist → NLP + per-sentence translation → + TTS → adventure title (first entry only) → update adventure status. On any error the entry and adventure are marked 'error'. """ @@ -158,20 +169,21 @@ class AdventureService: assert adventure is not None, f"Adventure {adventure_id} not found" all_entries = await self.entry_repo.list_for_adventure(adventure_id) - all_decisions = [await self.decision_repo.get_for_entry_and_user(entry_id=uuid.UUID(e.id), user_id=user_id) for e in all_entries] current_entry = next(e for e in all_entries if e.id == str(entry_id)) is_first_entry = current_entry.entry_index == 0 is_final_entry = current_entry.entry_index + 1 == adventure.max_entry_count - prior_entries = await self._load_prior_entries_with_metadata( + prior_entries_with_possible_choices = await self._load_possible_choices_for_entries( all_entries=[ e for e in all_entries if e.entry_index < current_entry.entry_index ], + user_id=user_id, + ) + prior_decisions = await self.decision_repo.list_for_adventure_and_user( + adventure_id=adventure_id, user_id=user_id ) - language_name = SUPPORTED_LANGUAGES.get( - adventure.language, adventure.language - ) + language_name = SUPPORTED_LANGUAGES.get(adventure.language, adventure.language) competency = adventure.competencies[0] if adventure.competencies else "B1" system_prompt = build_entry_system_prompt( language_name=language_name, @@ -185,15 +197,17 @@ class AdventureService: setting=adventure.setting, vibes=adventure.vibes, protagonist=adventure.protagonist, - prior_entries=prior_entries, - prior_decisions=all_decisions, + prior_entries_with_choices=prior_entries_with_possible_choices, ) + # ── LLM generation ────────────────────────────────────────────── + t0 = time.monotonic() raw_text, usage_dict = await self.anthropic_client.complete( system_prompt=system_prompt, messages=messages, max_tokens=2048, ) + timing_text_generation = time.monotonic() - t0 story_text, choices_parsed, gm_notes = parse_entry_response(raw_text) @@ -214,21 +228,96 @@ class AdventureService: ], ) - translated = await self.deepl_client.translate( - story_text, adventure.source_language + # ── Per-sentence NLP + translation ─────────────────────────────── + target_lang = _base_language(adventure.language) + source_lang = _base_language(adventure.source_language) + + timing_nlp = 0.0 + timing_translations = 0.0 + + paragraphs = [p.strip() for p in story_text.split("\n\n") if p.strip()] + linguistic_paragraphs = [] + + for para_idx, paragraph_text in enumerate(paragraphs): + t0 = time.monotonic() + target_nlp = await asyncio.to_thread( + self.spacy_client.get_parts_of_speech, paragraph_text, target_lang + ) + timing_nlp += time.monotonic() - t0 + + linguistic_sentences = [] + translated_sentence_texts = [] + + for sent_idx, target_sent in enumerate(target_nlp["sentences"]): + t0 = time.monotonic() + translated_sentence = await self.deepl_client.translate( + target_sent["text"], adventure.source_language + ) + timing_translations += time.monotonic() - t0 + + t0 = time.monotonic() + source_nlp = await asyncio.to_thread( + self.spacy_client.get_parts_of_speech, + translated_sentence, + source_lang, + ) + timing_nlp += time.monotonic() - t0 + + source_tokens = ( + source_nlp["sentences"][0]["tokens"] + if source_nlp["sentences"] + else [] + ) + translated_sentence_texts.append(translated_sentence) + linguistic_sentences.append( + { + "index": sent_idx, + "source_text": translated_sentence, + "target_text": target_sent["text"], + "source_tokens": source_tokens, + "target_tokens": target_sent["tokens"], + } + ) + + translated_paragraph = " ".join(translated_sentence_texts) + linguistic_paragraphs.append( + { + "index": para_idx, + "source_text": translated_paragraph, + "target_text": paragraph_text, + "sentences": linguistic_sentences, + } + ) + + story_text_linguistic_data = { + "source_language": source_lang, + "target_language": target_lang, + "paragraphs": linguistic_paragraphs, + } + + full_translated_text = "\n\n".join( + p["source_text"] for p in linguistic_paragraphs ) await self.translation_repo.create( entry_id=entry_id, component_type="story_text", target_language=adventure.source_language, - translated_text=translated, + translated_text=full_translated_text, ) + # ── TTS ────────────────────────────────────────────────────────── + t0 = time.monotonic() voice = self.gemini_client.get_voice_by_language(adventure.language) story_text_with_tag = "[like a dungeons and dragons gamemaster] " + story_text wav_bytes = await self.gemini_client.generate_audio(story_text_with_tag, voice) + timing_tts = time.monotonic() - t0 + + # ── File upload ─────────────────────────────────────────────────── + t0 = time.monotonic() audio_key = f"adventure-audio/{entry_id}.wav" upload_audio(audio_key, wav_bytes) + timing_file_uploading = time.monotonic() - t0 + await self.audio_repo.create( entry_id=entry_id, component_type="story_text", @@ -237,10 +326,27 @@ class AdventureService: file_name=audio_key, ) + # ── Persist linguistic data + timing ───────────────────────────── + pipeline_timing = { + "durations": { + "text_generation": round(timing_text_generation, 1), + "translations_total": round(timing_translations, 1), + "nlp_total": round(timing_nlp, 1), + "tts": round(timing_tts, 1), + "file_uploading": round(timing_file_uploading, 1), + } + } + await self.entry_repo.update_linguistic_data( + entry_id=entry_id, + story_text_linguistic_data=story_text_linguistic_data, + pipeline_timing=pipeline_timing, + ) + + # ── Adventure title (first entry only) ──────────────────────────── if is_first_entry: title_system = build_title_system_prompt() title_user = build_title_user_message( - story_text, language_name, adventure.genres + story_text, language_name, adventure.genres, gm_notes ) title_raw, _ = await self.anthropic_client.complete( system_prompt=title_system, @@ -267,32 +373,21 @@ class AdventureService: except Exception: logger.exception("Failed to mark entry/adventure as error") - async def _load_prior_entries_with_metadata( + async def _load_possible_choices_for_entries( self, all_entries: list[AdventureEntry], - ) -> list[tuple[AdventureEntry, list, str | None]]: + user_id: uuid.UUID, + ) -> list[tuple[AdventureEntry, list[AdventureEntryPossibleChoice], str | None]]: """Load choices for each prior entry and determine which choice was made. - Returns a list of (entry, choices, chosen_label_or_None) tuples ready for + Returns a list of (entry, choices, selected_choice_id) tuples ready for build_conversation_messages(). """ - sorted_entries = sorted(all_entries, key=lambda e: e.entry_index) result = [] - for i, entry in enumerate(sorted_entries): + + for entry in sorted(all_entries, key=lambda e: e.entry_index): choices = await self.choice_repo.list_for_entry(uuid.UUID(entry.id)) - chosen_label: str | None = None - if i + 1 < len(sorted_entries): - next_entry = sorted_entries[i + 1] - if next_entry.generated_from_choice_id: - chosen = next( - ( - c - for c in choices - if c.id == next_entry.generated_from_choice_id - ), - None, - ) - if chosen: - chosen_label = chosen.label - result.append((entry, choices, chosen_label)) + decision = await self.decision_repo.get_for_entry_and_user(entry_id = uuid.UUID(entry.id), user_id=user_id) + selected_choice_id = decision.choice_id if decision else None + result.append((entry, choices, selected_choice_id)) return result diff --git a/api/app/outbound/anthropic/adventure_prompts.py b/api/app/outbound/anthropic/adventure_prompts.py index 2595b22..d119cef 100644 --- a/api/app/outbound/anthropic/adventure_prompts.py +++ b/api/app/outbound/anthropic/adventure_prompts.py @@ -7,7 +7,7 @@ loads the data; these functions do the translation. """ import re -from ...domain.models.adventure import AdventureEntry, AdventureEntryPossibleChoice +from ...domain.models.adventure import AdventureEntry, AdventureEntryPossibleChoice, AdventureEntryPossibleChoiceDecision def build_entry_system_prompt( @@ -19,28 +19,31 @@ def build_entry_system_prompt( ) -> str: halfway = max(1, max_entry_count // 2) return ( - f"You are an experienced tabletop game master running a single-player one-shot campaign " - f"in a \"choose your own adventure\" format.\n\n" - f"You are helping the player learn {language_name}. Your writing respects their " - f"intelligence, avoids too many clichés, delivers satisfying plot beats, and reads naturally.\n\n" - f"The session is {max_entry_count} turns. Each turn: you write a story passage, then offer " - f"4 numbered choices. The player replies with their choice; you continue accordingly. " - f"By turn {max_entry_count} there needs to be a clear end. As the player's choices reveal " - f"their character, weave those details back into the story. " - f"Don't railroad them until at least turn {halfway}.\n\n" - f"Rules:\n" - f"- Write entirely in {language_name} at {competency} level on the CEFR scale. " - f"No markdown — plaintext only.\n" - f"- Your response MUST be in exactly three parts, each separated by a line containing " - f"only \"-----\".\n" - f"- Part 1: the story entry, {min_length}–{max_length} words, speaking directly to the player.\n" - f"- Part 2: exactly 4 numbered player options, one per line, labelled \"1.\", \"2.\", \"3.\", \"4.\".\n" - f"- Part 3: GM notes to your future self (hidden from the player). \n" - f"If no notes, write \"no notes\".\n" - f"- Your first message must establish: who the player is, the setting, and the broad direction.\n" - f"- No sexual content or graphic violence. Romance, threat, and adventure are fine (12-certificate)." + f"You are a game master running a single-player choose-your-own-adventure story " + f"to help the player practise {language_name}, write like a native. \n\n" + f"The session is {max_entry_count} turns. Deliver a satisfying narrative arc: " + f"establish, complicate, escalate, resolve. Don't force convergence until turn {halfway}. " + f"By turn {max_entry_count} the story must conclude clearly. " + f"Track the character the player is building through their choices and reflect it back.\n\n" + f"Write with economy and confidence. Favour scene over summary. " + f"Use dialogue to reveal character rather than reporting what was said. " + f"Resist the urge to over-explain — trust the player.\n\n" + f"Format — your response must have exactly three parts, each separated by a line containing only \"-----\":\n" + f"Part 1: story passage, {min_length}–{max_length} words, in second person, " + f"written entirely in {language_name} at {competency} CEFR level. Plaintext only, no markdown.\n" + f"Part 2: exactly 4 numbered options (\"1.\" through \"4.\"), one per line, in {language_name}.\n" + f"Part 3: GM notes — three lines only:\n" + f" Character: one sentence on what this player's choices reveal about them. When empty, write 'None'.\n" + f" Threads: unresolved plot points or planted details that should pay off later.\n" + f" Next beat: what the next turn needs to do narratively.\n" + f" Do not describe unchosen options or recap what just happened.\n\n" + f"No sexual content or graphic violence. Romance, threat, and adventure are fine. " + f"12-certificate." ) +""" +SECTION: Title generation prompts +""" def build_title_system_prompt() -> str: return ( @@ -48,8 +51,32 @@ def build_title_system_prompt() -> str: "story, generate a short title and a one-sentence description for it.\n\n" "Respond with exactly two lines of plain text:\n" "Line 1: the title (max 60 characters, no quotes or labels)\n" - "Line 2: the description (max 200 characters, no quotes or labels)" + "Line 2: the description (max 200 characters, no quotes or labels)\n\n" + "Avoid the following tropes: 'The secret of [noun]', 'The [noun] of [noun]'" ) + +def build_title_user_message( + first_entry_text: str, + language_name: str, + genres: list[str], + gamemaster_notes: str, +) -> str: + return ( + f"This is the opening passage of a {', '.join(genres)} adventure written in {language_name}:\n\n" + f"{first_entry_text}\n\n" + f"The gamemaster has provided the following (hidden from the player) notes. " + f"Consider using non-spoiler details:\n{gamemaster_notes}" + ) + +def parse_title_response(text: str) -> tuple[str, str]: + """Parse a two-line title/description response. + + Returns (title, description). Falls back gracefully if only one line is present. + """ + lines = [l.strip() for l in text.strip().splitlines() if l.strip()] + title = lines[0][:60] if lines else "Untitled Adventure" + description = lines[1][:200] if len(lines) > 1 else "" + return title, description def build_initial_user_message( @@ -63,18 +90,8 @@ def build_initial_user_message( f"- Genre: {', '.join(genres)}\n" f"- Setting: {', '.join(setting)}\n" f"- Vibes: {', '.join(vibes)}\n" - f"- Protagonist: {', '.join(protagonist)}" - ) - - -def build_title_user_message( - first_entry_text: str, - language_name: str, - genres: list[str], -) -> str: - return ( - f"This is the opening passage of a {', '.join(genres)} adventure written in {language_name}:\n\n" - f"{first_entry_text}" + f"- Protagonist: {', '.join(protagonist)}\n\n" + "This entry will be used to title the adventure, so include clear hints about the overall story and main character." ) @@ -95,38 +112,34 @@ def build_conversation_messages( setting: list[str], vibes: list[str], protagonist: list[str], - prior_entries: list[tuple[AdventureEntry, list[AdventureEntryPossibleChoice], str | None]], - prior_decisions: list[AdventureEntryPossibleChoice | None], + prior_entries_with_choices: list[tuple[AdventureEntry, list[AdventureEntryPossibleChoice], str | None]], ) -> list[dict]: """Build the full messages array for an Anthropic API call. - prior_entries is a list of (entry, choices_for_that_entry, chosen_label_or_None). + prior_entries is a list of (entry, choices_for_that_entry, selected_choice_id). The chosen label is the label of the option the player picked to advance past that entry. For the most recent completed entry it will be None (no choice made yet). """ messages: list[dict] = [ {"role": "user", "content": build_initial_user_message(genres, setting, vibes, protagonist)} ] - for entry, choices, chosen_label in prior_entries: + for entry, choices, selected_choice_id in prior_entries_with_choices: + + chosen_choice = next((c for c in choices if c.id == selected_choice_id), None) + + if selected_choice_id is None or chosen_choice is None: + # We have a problem, no decision was recorded for this entry + print(f"Warning: no decision found for entry {entry.id}") + continue + + + messages.append( {"role": "assistant", "content": reconstruct_assistant_message(entry, choices)} ) - # Find the player's decision for this entry - choice_ids = [c.id for c in choices] - decision_for_entry = next( - (d for d in prior_decisions if d and d.choice_id in choice_ids), - None - ) + messages.append({"role": "user", "content": chosen_choice.label}) - # If a decision exists, append the player's chosen option - if decision_for_entry: - chosen_option = next( - (c for c in choices if c.id == decision_for_entry.choice_id), - None - ) - if chosen_option: - messages.append({"role": "user", "content": chosen_option.label}) return messages @@ -162,12 +175,4 @@ def parse_entry_response(text: str) -> tuple[str, list[tuple[str, str]], str]: return story_text, choices, gm_notes -def parse_title_response(text: str) -> tuple[str, str]: - """Parse a two-line title/description response. - Returns (title, description). Falls back gracefully if only one line is present. - """ - lines = [l.strip() for l in text.strip().splitlines() if l.strip()] - title = lines[0][:60] if lines else "Untitled Adventure" - description = lines[1][:200] if len(lines) > 1 else "" - return title, description diff --git a/api/app/outbound/postgres/entities/adventure_entities.py b/api/app/outbound/postgres/entities/adventure_entities.py index 1ac2f3c..0a7fc4b 100644 --- a/api/app/outbound/postgres/entities/adventure_entities.py +++ b/api/app/outbound/postgres/entities/adventure_entities.py @@ -61,6 +61,8 @@ class AdventureEntryEntity(Base): story_text: Mapped[str | None] = mapped_column(Text, nullable=True) gamemaster_notes: Mapped[str | None] = mapped_column(Text, nullable=True) llm_data: Mapped[dict | None] = mapped_column(JSONB, nullable=True) + story_text_linguistic_data: Mapped[dict | None] = mapped_column(JSONB, nullable=True) + pipeline_timing: Mapped[dict | None] = mapped_column(JSONB, nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc) ) diff --git a/api/app/outbound/postgres/repositories/adventure_repository.py b/api/app/outbound/postgres/repositories/adventure_repository.py index f75ef26..ec4c093 100644 --- a/api/app/outbound/postgres/repositories/adventure_repository.py +++ b/api/app/outbound/postgres/repositories/adventure_repository.py @@ -54,6 +54,8 @@ def _to_entry(e: AdventureEntryEntity) -> AdventureEntry: story_text=e.story_text, gamemaster_notes=e.gamemaster_notes, llm_data=e.llm_data, + story_text_linguistic_data=e.story_text_linguistic_data, + pipeline_timing=e.pipeline_timing, created_at=e.created_at, ) @@ -246,6 +248,22 @@ class PostgresAdventureEntryRepository: await self.db.refresh(entity) return _to_entry(entity) + async def update_linguistic_data( + self, + entry_id: uuid.UUID, + story_text_linguistic_data: dict, + pipeline_timing: dict, + ) -> AdventureEntry: + result = await self.db.execute( + select(AdventureEntryEntity).where(AdventureEntryEntity.id == entry_id) + ) + entity = result.scalar_one() + entity.story_text_linguistic_data = story_text_linguistic_data + entity.pipeline_timing = pipeline_timing + await self.db.commit() + await self.db.refresh(entity) + return _to_entry(entity) + async def count_complete(self, adventure_id: uuid.UUID) -> int: result = await self.db.execute( select(func.count()).select_from(AdventureEntryEntity).where( @@ -328,6 +346,27 @@ class PostgresAdventureEntryDecisionRepository: ) entity = result.scalar_one_or_none() return _to_decision(entity) if entity else None + + async def list_for_adventure_and_user( + self, adventure_id: uuid.UUID, user_id: uuid.UUID + ) -> list[AdventureEntryPossibleChoiceDecision]: + result = await self.db.execute( + select(AdventureEntryPossibleChoiceDecisionEntity) + .join( + AdventureEntryPossibleChoiceEntity, + AdventureEntryPossibleChoiceDecisionEntity.choice_id + == AdventureEntryPossibleChoiceEntity.id, + ) + .join( + AdventureEntryEntity, + AdventureEntryPossibleChoiceEntity.entry_id == AdventureEntryEntity.id, + ) + .where( + AdventureEntryEntity.adventure_id == adventure_id, + AdventureEntryPossibleChoiceDecisionEntity.user_id == user_id, + ) + ) + return [_to_decision(e) for e in result.scalars().all()] class PostgresAdventureEntryTranslationRepository: diff --git a/api/app/outbound/spacy/spacy_client.py b/api/app/outbound/spacy/spacy_client.py index b500008..4ed2415 100644 --- a/api/app/outbound/spacy/spacy_client.py +++ b/api/app/outbound/spacy/spacy_client.py @@ -29,13 +29,16 @@ class SpacyClient: return self._cache[language] def get_parts_of_speech(self, text: str, language: str) -> dict: - """Use SpaCy to get parts of speech for the given text and language, + """Use SpaCy to get parts of speech for the given text and language, broken down by sentences and then by tokens.""" nlp = self._get_nlp(language) - + # Recognise line-breaks as always being sentence boundaries, even if the model doesn't. # This is important for the frontend to be able to show line-breaks in the source text. - nlp.add_pipe("sentencizer", before="parser") + # Guard prevents double-registration on the cached nlp object. + if "sentencizer" not in nlp.pipe_names: + before = "parser" if "parser" in nlp.pipe_names else None + nlp.add_pipe("sentencizer", before=before) doc = nlp(text) diff --git a/api/app/routers/api/adventures.py b/api/app/routers/api/adventures.py index 65e179e..256acea 100644 --- a/api/app/routers/api/adventures.py +++ b/api/app/routers/api/adventures.py @@ -16,6 +16,7 @@ from ...outbound.anthropic.anthropic_client import AnthropicClient from ...outbound.deepl.deepl_client import DeepLClient from ...outbound.gemini.gemini_client import GeminiClient from ...outbound.postgres.database import AsyncSessionLocal, get_db +from ...outbound.spacy.spacy_client import SpacyClient from ...outbound.postgres.repositories.adventure_repository import ( PostgresAdventureEntryAudioRepository, PostgresAdventureEntryChoiceRepository, @@ -91,6 +92,11 @@ class _StubGeminiClient: return buf.getvalue() +class _StubSpacyClient: + def get_parts_of_speech(self, text: str, language: str) -> dict: + return {"language": language, "sentences": [{"text": text, "tokens": []}]} + + # --------------------------------------------------------------------------- # Service factory # --------------------------------------------------------------------------- @@ -101,10 +107,12 @@ def _make_service(db: AsyncSession) -> AdventureService: anthropic = _StubAnthropicClient() # type: ignore[assignment] deepl = _StubDeepLClient() # type: ignore[assignment] gemini = _StubGeminiClient() # type: ignore[assignment] + spacy = _StubSpacyClient() # type: ignore[assignment] else: anthropic = AnthropicClient.new(settings.anthropic_api_key) deepl = DeepLClient(settings.deepl_api_key) gemini = GeminiClient(settings.gemini_api_key) + spacy = SpacyClient() return AdventureService( adventure_repo=PostgresAdventureRepository(db), @@ -116,6 +124,7 @@ def _make_service(db: AsyncSession) -> AdventureService: anthropic_client=anthropic, deepl_client=deepl, gemini_client=gemini, + spacy_client=spacy, ) @@ -199,6 +208,7 @@ class EntryDetailResponse(BaseModel): choices: list[ChoiceResponse] translation: str | None audio_file_name: str | None + story_text_linguistic_data: dict | None # --------------------------------------------------------------------------- @@ -461,4 +471,5 @@ async def get_entry( ], translation=translation.translated_text if translation else None, audio_file_name=audio.file_name if audio else None, + story_text_linguistic_data=entry.story_text_linguistic_data, ) diff --git a/api/app/routers/bff/adventure.py b/api/app/routers/bff/adventure.py index dcc1963..c9996eb 100644 --- a/api/app/routers/bff/adventure.py +++ b/api/app/routers/bff/adventure.py @@ -33,6 +33,7 @@ class AdventureEntryItem(BaseModel): status: str entry_index: int story_text: str | None + story_text_linguistic_data: dict | None translation: str | None audio_url: str | None created_at: str @@ -108,6 +109,7 @@ async def get_adventure( status=entry.status, entry_index=entry.entry_index, story_text=entry.story_text, + story_text_linguistic_data=entry.story_text_linguistic_data, translation=translation.translated_text if translation else None, audio_url=_audio_url(audio.file_name if audio else None), created_at=entry.created_at.isoformat(), diff --git a/api/docs/design-doc-choose-your-own-adventure.md b/api/docs/design-doc-choose-your-own-adventure.md index c53f01f..7500455 100644 --- a/api/docs/design-doc-choose-your-own-adventure.md +++ b/api/docs/design-doc-choose-your-own-adventure.md @@ -1,174 +1,100 @@ # Feature design doc: Choose your own adventure -This is a semi-technical design document to detail the *Choose Your Own Adventure* functionality of the Langauge Learning App. +This is a semi-technical design document to detail some enhancements to the _Choose Your Own Adventure_ functionality of the Langauge Learning App. -## Purpose +## Purpose -Improve learner familiarity with a foreign language by exposing them to content generated in that language. - -The Choose Your Own Adventure format is chosen because it is fictitious (not all content should be non-fiction, or for "learning"). They are also engaging in that they require a little input from the user, and can be guided (at a high level) by the learner. +Introduce structured linguistic data around entries in a "choose your own adventure", the purpose of which is to create a structured pathway from the user reading/listening to an entry, and then putting words in their vocab bank / word bank, and possibly then creating flashcards around them, to help them learn the words. ## Feature Description -In the website there is a tab, or page, called "Adventures". +The app already has the idea of an Adventure (i.e. a single story), for which there are many Entries, each of which have Possible Choices (4, for now), which the user selects and then the story continues to be generated. -On this page, learners are ablet to see any completed `adventures` (Adventures have a target number of `entries`, let's default to 6) - an adventure is *complete* when the target number of `entries` has been reached for it. +Entries are generated by a LLM (Claude), have a translation text generated by the DeepL translator, and are converted to audio by another LLM (Google's Gemini). -When a learner creates an Adventure, they select a handful of details to aid the generation: the genre of story they want (e.g. crime fiction), the setting they want it to be in (i.e. roughly when and where), the vibes of the story (e.g. "cosy" or "thriller"), and lastly the protagonist (i.e. gender, age, one characteristic). +You are to change the functionality to: -An LLM is then used to generate the first entry in the adventure, which will introduce the learner to the story, and their character. After this has been received, we ask an LLM to create a name and a description for the adventure based on what comes back from this first entry. An Adventure now has its name, description, and some lose content tags. +1. Use the SpaCy natural language processing to break downt he generated (i.e. foreign language) text for an entry into their parts of speech and their sentences. +2. We are to translate these sentenses one at a time, and then the results from that translation are passed into the same SpaCy pipeline. +3. We need to end up with a data structure of `paragraphs` each of which has 1..n `sentences`, and the tokens (words) in that sentence have gone through the part-of-speech tagging system, as well as lemmatisation (these are already configured with how SpaCy is used elsewhere). +4. This structured data should be stored alongside the full-text as they are currently generated in the API, i.e. we need both the structured linguistic data as well as the original body text. -Each entry that gets created will receive a translation of the *Story Text* from the learner's target language, into their source language. This allows for parallel reading of the text. In time, as well, we will do natural language processing on source and target in an attempt to match sentence for sentence, or word for word, to create a better way to do the parallel reading. +## Technical components -Each entry will also have text-to-speech done (by AI), and this can be read through the user interface, but also in the future this will allow for a per-adventure podcast feed to be generated for the learner so they can learn on the go. +The `AdventureService` (`/app/domain/service/adventure_service.py`) contains a method called `run_entry_pipeline` - this is the highly asynchronous orchestrator of calls to various external parties (e.g. LLMs, translators, TTS), we should use this existing entrypoint to run the code. -At the end of the entry is a set of next steps, or options, avaiable to the user. Initially there will be 4. The learner will chose one, which will repeat the cycle above (generate entry, translate, do text-to-speech, learner views it, etc.) +We will need to inject a `SpacyClient` (`app/outbound/spacy/spacy_client`) into the `AdventureService` -Once the learner has run through this cycle until they have reached the target number of entries, the last entry will not have any next-step options to generate. +After the generation of the text (through the call to `anthropic_client.complete` in that method) we should (at a relevant point) -Initially the learner will have to go and create a new Adventure, however, in the future it should be possible for them to go back to a branching point in the narrative and re-continue. +Running the NLP pipeline in SpaCy won't get us the paragraphs, so we may need to split the incoming raw text by the `\n\n` separator, and then call the pipeline on each paragraph in turn. -If the learner is reading the adventure through the LLA's own web UI there should be a way to quick-create flashcards, and/or add words to the learner's vocabulary / word list, identifying them as words they had to look up, and perhaps as words they want to learn in the future. +We will therefore need a JSON new field on the `AdventureEntryEntity`, which I think we should call `story_text_linguistic_data`, which should look like the following: -Additionally, over time, it would be good to generate another set of data (likely also from LLMs) that does key entity extraction from the text, and prevents stories from continually taking place in the same place, with similarly named characters. This would then be fet into the generation / system prompt, e.g. "avoid characters called Detective Renoir, avoid Paris,avoid the early 1950s" to create a variety of content. +```json +{ + "source_language": "en", + "target_language": "fr", + "paragraphs": [ + "index": 0, + "source_text": "\""Since forever, no? It's normal. Everyone is together here.\"", + "target_text": "« Depuis toujours, non ? C'est normal. Tout le monde est ensemble ici. »", + "sentences": [ + { + "index": 0, + "source_text": "\"Since forever, no?", + "target_text": "« Depuis toujours, non ?", + "target_tokens": [..], + "source_tokens": [..], -## Monetisation and payment strategy + }, + { + "index": 1, + "source_text": "It's normal", + "target_text": "C'est normal.", + "target_tokens": [..], + "source_tokens": [..], + }, + { + "index": 2, + "source_text": "Everyone is together here.\"", + "target_text": "Tout le mond est ensemble ici. »", + "target_tokens": [..], + "source_tokens": [..], + }, + ] + ] +} +``` + +Where the `tokens` fields are the same data structure as the tags specified in the `get_parts_of_speech` method in the SpacyClient. + +We will then need to feed this data through to the front-end, which will use it to create a more structured set of data in the UI, which will aid in creating a better "translate" experience (i.e. click on a single word in the target language, and go to the relevant word(s) in the source language; be able to add words from that translation via a more automated pathway, with the option for manual intervention; linking of words with their dictionary entries, which we have) + +This may have an impact on performance, can we therefore introduce a simple tracing mechanism into the `run_entry_pipeline` method, to give visibility about how long it take (in seconds) to run each individual step. Can we store this as JSON in the `AdventureEntryEntity`, so we'll need to createa migration to create those fields, I imagine some data that looks like: + +```json +{ + "durations": { + "text_generation": 10, // for the text itself + "translations_total": 5, // for all calls to DeepL combined, + "nlp_total": 7, // for all run s of SpaCy + "tts": 15, // call to generate the audio file + "file_uploading": 1 // To upload the .wav + } +} +``` + +## IGNORE: Monetisation and payment strategy + +The following text is present as a reminder to me, to consider how adventure generation fits into the monetisation See the [pricing.md](./design-doc-pricing.md) doc for more info. -The use of LLMs creates a cost on Language Learning App per entry that is generated (initial generation, translation, text-to-speech). This will likely be as high as 50-60p per adventure, per user this could add up to a lot of money. +The use of LLMs creates a cost on Language Learning App per entry that is generated (initial generation, translation, text-to-speech). This will likely be as high as 50-60p per adventure, per user this could add up to a lot of money. -Users who wish to operate on the subscription model will get a certain number of Adventure entries per subscription period. We should round this up to the nearest adventure (you don't want to be waiting for your next renewal to finsih an adventure). +Users who wish to operate on the subscription model will get a certain number of Adventure entries per subscription period. We should round this up to the nearest adventure (you don't want to be waiting for your next renewal to finsih an adventure). Users on a metered billing will pay for a whole adventure up-front, i.e. aprox. $1.20/adventure. For this reason, it's very important that the system tracks the costs (in money, and in tokens) taken to generate the content for an adventure, so these figures can be adjusted to reflect reality. - -## Example prompts - -```txt -You are an experienced tabletop game master running a single-player one-shot campaign in a "choose your own adventure" format. - -You are helping the player learn French. Your writing respects their intelligence, avoids too many cliches, delivers satisfying plot beats, and reads naturally. - - The session is 8 turns. Each turn: you write a story passage, then offer 4 numbered choices. The player replies with their choice; you continue accordingly. By turn 8 there needs to be a clear end. As the player's choices reveal their character, weave those details back into the story. Don't railroad them until at least turn 4 - -Rules: - -- Write entirely in French at B1 level. No markdown — plaintext only. -- Your response should be in three parts, each separated by a newline, and then five hyphens ("-----"). -- The first section contains the story entry, 600–700 words length total, speaking to the player directly. -- The second section contains contains a list of new-line separated player options, labelled 1,2,3,4 with explaining text. -- The third section are GM notes, hidden from the player, you may optionally use this section to record notes to your future self, to keep track of threads or ideas. If no notes, simply say "no notes" -- Your first message must establish: who the player is, the setting, and the broad direction of the story. -- No sexual content or graphic violence. Romance, threat, and adventure are fine. Treat this as a 12-certificate. - -The scenario follows. -``` - -## Entities - -### choose_your_own_adventure - -This is the "header" entry, it represnts a single "adventure" in the format, right now it's linked to one user (via `user_id`) and holds details about the language and proficiency it's in, as a record of what was selected at the time. - -The title is `Untitled adventure` and the description is empty when it gets created, but a separate call to an LLM will create a name and a description to put here. - -```json -{ - "id": "unique-uuid", - "user_id": "user-uuid", - "language": "fr", - "competencies": ["B1"], - "max_entry_length": 8, - "entry_story_text_target_length": { "min": 700, "max": 800}, - "title": "Untitled adventure", - "description": null, - "plot_summary": null, - "genres": ["crime fiction"], - "setting": ["France", "city"], - "vibes": ["dark", "light humour"], - "protagonist": ["male", "reluctant", "late-teens"], - "created_at": "2026-05-03T09:00Z", - "deleted_at": null, -} -``` - -### choose_your_own_adventure_entry - -An entry is like a "turn" in a tabletop roleplaying game, or a chapter in a choose your own adventure book. These are generated one at a time, in response to user choices (the first one is generated immediately after creation of the Adventure itself). - -They are generated by an LLM using a prompt. - -They are immediately translated (via DeepL) and have text-to-speech (via Google Gemini) from the story_text content. - -Recording the `entry_index` and the `generated_from_possible_choice_id` allows us to model multiple replays of a specific adventure (e.g. "go back to step 3, and choose a different option to what I initially chose). - -```json -{ - "id": "uuid", - "choose_your_own_adventure_id": "unique-uuid", - "generated_from_possible_choice_id": "choose_your_own_adventure_entry_possible_choice-uuid", // null on entry 0 - "llm_data": { "provider": "anthropic", "model": "claude-4.6" }, // JSONB for arbitrary data - "entry_index": "1", // - "story_text": "You find yourself in a big, dark woods...", - "gamemaster_notes": "The player is playing cautiously...", // Hidden from the user - "created_at": "2026-05-03T09:05", -} -``` - -### choose_your_own_adventure_entry_translation - -This represents a translation of the generated story_text into the user's native language, to help them do parallel reading between the two texts. - -```json -{ - "id": "uuid", - "entry_id": "choose-your-own-adventure-entry-uuid", - "component_type": "story_text", - "target_language": "en", - "translated_text": "This is the translated text from the entry.story_text" -} -``` - -### choose_your_own_adventure_entry_audio - -This is a text-to-speech (AI) generation of the story text, to make the content available to the user as e.g. a podcast feed, and also available on the screen. - -```json -{ - "id": "uuid", - "entry_id": "choose-your-own-adventure-entry-uuid", - "component_type": "story_text", - "tts_provider": "google_gemini", - "tts_options": { "voice": "voice name"}, // JSONB format - "file_name": "uuid-like-filename.mp4" -} -``` - -### choose_your_own_adventure_entry_possible_choice - -This represents the options available to the user a the end of a specific entry, the LLM will generate 4 of them (initially). - -```json -{ - "id": "uuid", - "entry_id": "choose-your-own-adventure-entry-uuid", - "index": 0, - "label": "1", - "text": "Go into the dark house" -} -``` - -### choose_your_own_adventure_entry_possible_choice_decision - -This represents the possible_choice that a user chose, which will be used to generate the next step of the story. - -```json -{ - "id": "uuid", - "choice_id": "choose_your_own_adventure_entry_possible_choice-uuid", - "user_id": "user-uuid", - "created_at": "2026-05-03T10:00:00.000Z" -} -``` diff --git a/frontend/docs/openapi.json b/frontend/docs/openapi.json index ce60551..b8b07c1 100644 --- a/frontend/docs/openapi.json +++ b/frontend/docs/openapi.json @@ -1 +1 @@ -{"openapi":"3.1.0","info":{"title":"Language Learning API","version":"0.1.0"},"paths":{"/api/auth/register":{"post":{"tags":["api","auth"],"summary":"Register","operationId":"register_api_auth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/login":{"post":{"tags":["api","auth"],"summary":"Login","operationId":"login_api_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/verify-email":{"get":{"tags":["api","auth"],"summary":"Verify Email","operationId":"verify_email_api_auth_verify_email_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Verify Email Api Auth Verify Email Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/learnable-languages":{"post":{"tags":["api","account"],"summary":"Add Learnable Language","operationId":"add_learnable_language_api_account_learnable_languages_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddLearnableLanguageRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearnableLanguageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/account/onboarding":{"post":{"tags":["api","account"],"summary":"Complete Onboarding","operationId":"complete_onboarding_api_account_onboarding_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Complete Onboarding Api Account Onboarding Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/account/status":{"get":{"tags":["api","account"],"summary":"Get Account Status","operationId":"get_account_status_api_account_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountStatusResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/account/learnable-languages/{language_id}":{"delete":{"tags":["api","account"],"summary":"Remove Learnable Language","operationId":"remove_learnable_language_api_account_learnable_languages__language_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"language_id","in":"path","required":true,"schema":{"type":"string","title":"Language Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/dictionary/search":{"get":{"tags":["api","dictionary"],"summary":"Search Wordforms Prefix","description":"Search for wordforms whose surface text starts with the given prefix.\n\nUses accent-insensitive, case-insensitive prefix matching so that e.g.\n\"chatea\" returns both \"château\" and \"châteaux\", and \"lent\" returns all\nfour forms of the adjective. Returns one entry per matching lemma.","operationId":"search_wordforms_prefix_api_dictionary_search_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"lang_code","in":"query","required":true,"schema":{"type":"string","title":"Lang Code"}},{"name":"text","in":"query","required":true,"schema":{"type":"string","title":"Text"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WordformMatch"},"title":"Response Search Wordforms Prefix Api Dictionary Search Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/dictionary/senses":{"get":{"tags":["api","dictionary"],"summary":"Search Senses","description":"Search for a Sense by (English) definition\n\nReturns one entry per matching senses,each with its Sense.","operationId":"search_senses_api_dictionary_senses_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"lang_code","in":"query","required":true,"schema":{"type":"string","title":"Lang Code"}},{"name":"text","in":"query","required":true,"schema":{"type":"string","title":"Text"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SenseMatch"},"title":"Response Search Senses Api Dictionary Senses Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/dictionary/wordforms":{"get":{"tags":["api","dictionary"],"summary":"Search Wordforms","description":"Search for a wordform by surface text within a language.\n\nReturns one entry per matching lemma, each with the lemma's senses. A single\nform (e.g. \"allons\") may resolve to more than one lemma when homographs exist.","operationId":"search_wordforms_api_dictionary_wordforms_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"lang_code","in":"query","required":true,"schema":{"type":"string","title":"Lang Code"}},{"name":"text","in":"query","required":true,"schema":{"type":"string","title":"Text"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WordformMatch"},"title":"Response Search Wordforms Api Dictionary Wordforms Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/vocab/{entry_id}/flashcards":{"post":{"tags":["api","flashcards"],"summary":"Generate Flashcards","operationId":"generate_flashcards_api_vocab__entry_id__flashcards_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"entry_id","in":"path","required":true,"schema":{"type":"string","title":"Entry Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateFlashcardsRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FlashcardResponse"},"title":"Response Generate Flashcards Api Vocab Entry Id Flashcards Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/flashcards":{"get":{"tags":["api","flashcards"],"summary":"List Flashcards","operationId":"list_flashcards_api_flashcards_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/FlashcardResponse"},"type":"array","title":"Response List Flashcards Api Flashcards Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/flashcards/{flashcard_id}/events":{"post":{"tags":["api","flashcards"],"summary":"Record Event","operationId":"record_event_api_flashcards__flashcard_id__events_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"flashcard_id","in":"path","required":true,"schema":{"type":"string","title":"Flashcard Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordEventRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlashcardEventResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/pos/":{"post":{"tags":["api","api","pos"],"summary":"Analyze Pos","operationId":"analyze_pos_api_pos__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/POSRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/POSResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/translate":{"get":{"tags":["api","api","translate"],"summary":"Translate text to a target language","operationId":"translate_text_api_translate_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"text","in":"query","required":true,"schema":{"type":"string","title":"Text"}},{"name":"target_language","in":"query","required":true,"schema":{"type":"string","title":"Target Language"}},{"name":"context","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Context"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranslationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/generate":{"post":{"tags":["api","api"],"summary":"Create Generation Job","operationId":"create_generation_job_api_generate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerationRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/jobs/":{"get":{"tags":["api"],"summary":"Get Jobs","operationId":"get_jobs_api_jobs__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobListResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/jobs/{job_id}":{"get":{"tags":["api"],"summary":"Get Job","operationId":"get_job_api_jobs__job_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/regenerate-audio":{"post":{"tags":["api"],"summary":"Regenerate Audio","operationId":"regenerate_audio_api_jobs__job_id__regenerate_audio_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Regenerate Audio Api Jobs Job Id Regenerate Audio Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/learnable_languages":{"post":{"tags":["api","api"],"summary":"Upsert Learnable Language","operationId":"upsert_learnable_language_api_learnable_languages_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearnableLanguageRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearnableLanguageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/vocab":{"post":{"tags":["api","vocab"],"summary":"Add Word","operationId":"add_word_api_vocab_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddWordRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WordBankEntryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["api","vocab"],"summary":"List Entries","operationId":"list_entries_api_vocab_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"language_pair_id","in":"query","required":true,"schema":{"type":"string","title":"Language Pair Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WordBankEntryResponse"},"title":"Response List Entries Api Vocab Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/vocab/from-token":{"post":{"tags":["api","vocab"],"summary":"Add From Token","operationId":"add_from_token_api_vocab_from_token_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddFromTokenRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FromTokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/vocab/pending-disambiguation":{"get":{"tags":["api","vocab"],"summary":"Pending Disambiguation","operationId":"pending_disambiguation_api_vocab_pending_disambiguation_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/WordBankEntryResponse"},"type":"array","title":"Response Pending Disambiguation Api Vocab Pending Disambiguation Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/vocab/{entry_id}/sense":{"patch":{"tags":["api","vocab"],"summary":"Resolve Sense","operationId":"resolve_sense_api_vocab__entry_id__sense_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"entry_id","in":"path","required":true,"schema":{"type":"string","title":"Entry Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetSenseRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WordBankEntryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/packs":{"get":{"tags":["api","packs"],"summary":"List Packs","operationId":"list_packs_api_packs_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"source_lang","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Lang"}},{"name":"target_lang","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PackSummaryResponse"},"title":"Response List Packs Api Packs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/packs/{pack_id}":{"get":{"tags":["api","packs"],"summary":"Get Pack","operationId":"get_pack_api_packs__pack_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pack_id","in":"path","required":true,"schema":{"type":"string","title":"Pack Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/app__routers__api__packs__PackDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/packs/{pack_id}/add-to-bank":{"post":{"tags":["api","packs"],"summary":"Add Pack To Bank","operationId":"add_pack_to_bank_api_packs__pack_id__add_to_bank_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pack_id","in":"path","required":true,"schema":{"type":"string","title":"Pack Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddTobankRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddTobankResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/packs":{"post":{"tags":["api","admin-packs"],"summary":"Create Pack","operationId":"create_pack_api_admin_packs_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePackRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PackResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["api","admin-packs"],"summary":"List Packs","operationId":"list_packs_api_admin_packs_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"source_lang","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Lang"}},{"name":"target_lang","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PackResponse"},"title":"Response List Packs Api Admin Packs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/packs/{pack_id}":{"get":{"tags":["api","admin-packs"],"summary":"Get Pack","operationId":"get_pack_api_admin_packs__pack_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pack_id","in":"path","required":true,"schema":{"type":"string","title":"Pack Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/app__routers__api__admin__packs__PackDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["api","admin-packs"],"summary":"Update Pack","operationId":"update_pack_api_admin_packs__pack_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pack_id","in":"path","required":true,"schema":{"type":"string","title":"Pack Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePackRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PackResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/packs/{pack_id}/publish":{"post":{"tags":["api","admin-packs"],"summary":"Publish Pack","operationId":"publish_pack_api_admin_packs__pack_id__publish_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pack_id","in":"path","required":true,"schema":{"type":"string","title":"Pack Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PackResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/packs/{pack_id}/entries":{"post":{"tags":["api","admin-packs"],"summary":"Add Entry","operationId":"add_entry_api_admin_packs__pack_id__entries_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pack_id","in":"path","required":true,"schema":{"type":"string","title":"Pack Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddEntryRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PackEntryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/packs/{pack_id}/entries/{entry_id}":{"delete":{"tags":["api","admin-packs"],"summary":"Remove Entry","operationId":"remove_entry_api_admin_packs__pack_id__entries__entry_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pack_id","in":"path","required":true,"schema":{"type":"string","title":"Pack Id"}},{"name":"entry_id","in":"path","required":true,"schema":{"type":"string","title":"Entry Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/packs/{pack_id}/entries/{entry_id}/flashcards":{"post":{"tags":["api","admin-packs"],"summary":"Add Flashcard Template","operationId":"add_flashcard_template_api_admin_packs__pack_id__entries__entry_id__flashcards_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pack_id","in":"path","required":true,"schema":{"type":"string","title":"Pack Id"}},{"name":"entry_id","in":"path","required":true,"schema":{"type":"string","title":"Entry Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddFlashcardTemplateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlashcardTemplateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/packs/{pack_id}/entries/{entry_id}/flashcards/{template_id}":{"delete":{"tags":["api","admin-packs"],"summary":"Remove Flashcard Template","operationId":"remove_flashcard_template_api_admin_packs__pack_id__entries__entry_id__flashcards__template_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pack_id","in":"path","required":true,"schema":{"type":"string","title":"Pack Id"}},{"name":"entry_id","in":"path","required":true,"schema":{"type":"string","title":"Entry Id"}},{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/adventures":{"get":{"tags":["api","adventures"],"summary":"List Adventures","operationId":"list_adventures_api_adventures_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/AdventureResponse"},"type":"array","title":"Response List Adventures Api Adventures Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["api","adventures"],"summary":"Create Adventure","operationId":"create_adventure_api_adventures_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAdventureRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdventureResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/adventures/{adventure_id}":{"get":{"tags":["api","adventures"],"summary":"Get Adventure","operationId":"get_adventure_api_adventures__adventure_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"adventure_id","in":"path","required":true,"schema":{"type":"string","title":"Adventure Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdventureResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["api","adventures"],"summary":"Delete Adventure","operationId":"delete_adventure_api_adventures__adventure_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"adventure_id","in":"path","required":true,"schema":{"type":"string","title":"Adventure Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/adventures/{adventure_id}/decisions":{"post":{"tags":["api","adventures"],"summary":"Record Decision","operationId":"record_decision_api_adventures__adventure_id__decisions_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"adventure_id","in":"path","required":true,"schema":{"type":"string","title":"Adventure Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDecisionRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/adventures/{adventure_id}/entries":{"get":{"tags":["api","adventures"],"summary":"List Entries","operationId":"list_entries_api_adventures__adventure_id__entries_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"adventure_id","in":"path","required":true,"schema":{"type":"string","title":"Adventure Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/EntryResponse"},"title":"Response List Entries Api Adventures Adventure Id Entries Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/adventures/{adventure_id}/entries/{entry_id}":{"get":{"tags":["api","adventures"],"summary":"Get Entry","operationId":"get_entry_api_adventures__adventure_id__entries__entry_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"adventure_id","in":"path","required":true,"schema":{"type":"string","title":"Adventure Id"}},{"name":"entry_id","in":"path","required":true,"schema":{"type":"string","title":"Entry Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntryDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/bff/account":{"get":{"tags":["bff","bff"],"summary":"Get Account","operationId":"get_account_bff_account_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/bff/account/onboarding":{"get":{"tags":["bff","bff"],"summary":"Get Onboarding","operationId":"get_onboarding_bff_account_onboarding_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/bff/adventure/{adventure_id}":{"get":{"tags":["bff","bff","adventures"],"summary":"Get Adventure","operationId":"get_adventure_bff_adventure__adventure_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"adventure_id","in":"path","required":true,"schema":{"type":"string","title":"Adventure Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdventureDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/bff/articles":{"get":{"tags":["bff","bff","articles"],"summary":"List Articles","operationId":"list_articles_bff_articles_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"target_language","in":"query","required":false,"schema":{"type":"string","default":"fr","title":"Target Language"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArticleListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/bff/articles/{article_id}":{"get":{"tags":["bff","bff","articles"],"summary":"Get Article","operationId":"get_article_bff_articles__article_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"article_id","in":"path","required":true,"schema":{"type":"string","title":"Article Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArticleDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/bff/user_profile":{"get":{"tags":["bff","bff"],"summary":"Get User Profile","operationId":"get_user_profile_bff_user_profile_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfileResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/bff/packs":{"get":{"tags":["bff","bff-packs"],"summary":"List Packs For Selection","operationId":"list_packs_for_selection_bff_packs_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"source_lang","in":"query","required":true,"schema":{"type":"string","title":"Source Lang"}},{"name":"target_lang","in":"query","required":true,"schema":{"type":"string","title":"Target Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PackSelectionItem"},"title":"Response List Packs For Selection Bff Packs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/adventure-audio/{filename}":{"get":{"tags":["media"],"summary":"Get Adventure Audio File","operationId":"get_adventure_audio_file_media_adventure_audio__filename__get","parameters":[{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/{filename}":{"get":{"tags":["media"],"summary":"Get Media File","operationId":"get_media_file_media__filename__get","parameters":[{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Health Get"}}}}}}}},"components":{"schemas":{"AccountLanguagePair":{"properties":{"id":{"type":"string","title":"Id"},"source_language":{"type":"string","title":"Source Language"},"target_language":{"type":"string","title":"Target Language"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"}},"type":"object","required":["id","source_language","target_language","proficiencies"],"title":"AccountLanguagePair"},"AccountResponse":{"properties":{"email":{"type":"string","title":"Email"},"human_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Human Name"},"language_pairs":{"items":{"$ref":"#/components/schemas/AccountLanguagePair"},"type":"array","title":"Language Pairs"}},"type":"object","required":["email","human_name","language_pairs"],"title":"AccountResponse"},"AccountStatusResponse":{"properties":{"problem_flags":{"items":{"type":"string"},"type":"array","title":"Problem Flags"},"error_messages":{"items":{"type":"string"},"type":"array","title":"Error Messages"}},"type":"object","required":["problem_flags","error_messages"],"title":"AccountStatusResponse"},"AddEntryRequest":{"properties":{"sense_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sense Id"},"surface_text":{"type":"string","title":"Surface Text"}},"type":"object","required":["surface_text"],"title":"AddEntryRequest"},"AddFlashcardTemplateRequest":{"properties":{"prompt_text":{"type":"string","title":"Prompt Text"},"answer_text":{"type":"string","title":"Answer Text"},"prompt_context_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt Context Text"},"answer_context_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Answer Context Text"}},"type":"object","required":["prompt_text","answer_text"],"title":"AddFlashcardTemplateRequest"},"AddFromTokenRequest":{"properties":{"language_pair_id":{"type":"string","title":"Language Pair Id"},"surface":{"type":"string","title":"Surface"},"spacy_lemma":{"type":"string","title":"Spacy Lemma"},"pos_ud":{"type":"string","title":"Pos Ud"},"language":{"type":"string","title":"Language"},"source_article_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Article Id"}},"type":"object","required":["language_pair_id","surface","spacy_lemma","pos_ud","language"],"title":"AddFromTokenRequest"},"AddLearnableLanguageRequest":{"properties":{"source_language":{"type":"string","title":"Source Language"},"target_language":{"type":"string","title":"Target Language"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"}},"type":"object","required":["source_language","target_language","proficiencies"],"title":"AddLearnableLanguageRequest"},"AddTobankRequest":{"properties":{"source_lang":{"type":"string","title":"Source Lang"},"target_lang":{"type":"string","title":"Target Lang"}},"type":"object","required":["source_lang","target_lang"],"title":"AddTobankRequest"},"AddTobankResponse":{"properties":{"added":{"items":{"type":"string"},"type":"array","title":"Added"}},"type":"object","required":["added"],"title":"AddTobankResponse"},"AddWordRequest":{"properties":{"language_pair_id":{"type":"string","title":"Language Pair Id"},"surface_text":{"type":"string","title":"Surface Text"},"entry_pathway":{"type":"string","title":"Entry Pathway","default":"manual"},"is_phrase":{"type":"boolean","title":"Is Phrase","default":false},"source_article_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Article Id"}},"type":"object","required":["language_pair_id","surface_text"],"title":"AddWordRequest"},"AdventureChoiceItem":{"properties":{"id":{"type":"string","title":"Id"},"index":{"type":"integer","title":"Index"},"label":{"type":"string","title":"Label"},"text":{"type":"string","title":"Text"}},"type":"object","required":["id","index","label","text"],"title":"AdventureChoiceItem"},"AdventureDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"user_id":{"type":"string","title":"User Id"},"status":{"type":"string","title":"Status"},"language":{"type":"string","title":"Language"},"source_language":{"type":"string","title":"Source Language"},"competencies":{"items":{"type":"string"},"type":"array","title":"Competencies"},"max_entry_count":{"type":"integer","title":"Max Entry Count"},"title":{"type":"string","title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"setting":{"items":{"type":"string"},"type":"array","title":"Setting"},"vibes":{"items":{"type":"string"},"type":"array","title":"Vibes"},"protagonist":{"items":{"type":"string"},"type":"array","title":"Protagonist"},"created_at":{"type":"string","title":"Created At"},"entries":{"items":{"$ref":"#/components/schemas/AdventureEntryItem"},"type":"array","title":"Entries"},"current_entry_choices":{"items":{"$ref":"#/components/schemas/AdventureChoiceItem"},"type":"array","title":"Current Entry Choices"}},"type":"object","required":["id","user_id","status","language","source_language","competencies","max_entry_count","title","description","genres","setting","vibes","protagonist","created_at","entries","current_entry_choices"],"title":"AdventureDetailResponse"},"AdventureEntryItem":{"properties":{"id":{"type":"string","title":"Id"},"adventure_id":{"type":"string","title":"Adventure Id"},"possible_choices":{"anyOf":[{"items":{"$ref":"#/components/schemas/AdventureChoiceItem"},"type":"array"},{"type":"null"}],"title":"Possible Choices"},"generated_from_choice_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated From Choice Id"},"status":{"type":"string","title":"Status"},"entry_index":{"type":"integer","title":"Entry Index"},"story_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Story Text"},"translation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Translation"},"audio_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audio Url"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","adventure_id","possible_choices","generated_from_choice_id","status","entry_index","story_text","translation","audio_url","created_at"],"title":"AdventureEntryItem"},"AdventureResponse":{"properties":{"id":{"type":"string","title":"Id"},"user_id":{"type":"string","title":"User Id"},"status":{"type":"string","title":"Status"},"language":{"type":"string","title":"Language"},"source_language":{"type":"string","title":"Source Language"},"competencies":{"items":{"type":"string"},"type":"array","title":"Competencies"},"max_entry_count":{"type":"integer","title":"Max Entry Count"},"title":{"type":"string","title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"setting":{"items":{"type":"string"},"type":"array","title":"Setting"},"vibes":{"items":{"type":"string"},"type":"array","title":"Vibes"},"protagonist":{"items":{"type":"string"},"type":"array","title":"Protagonist"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","user_id","status","language","source_language","competencies","max_entry_count","title","description","genres","setting","vibes","protagonist","created_at"],"title":"AdventureResponse"},"ArticleDetail":{"properties":{"id":{"type":"string","title":"Id"},"published_at":{"type":"string","format":"date-time","title":"Published At"},"source_language":{"type":"string","title":"Source Language"},"source_title":{"type":"string","title":"Source Title"},"source_body":{"type":"string","title":"Source Body"},"source_body_pos":{"additionalProperties":true,"type":"object","title":"Source Body Pos"},"target_language":{"type":"string","title":"Target Language"},"target_complexities":{"items":{"type":"string"},"type":"array","title":"Target Complexities"},"target_title":{"type":"string","title":"Target Title"},"target_body":{"type":"string","title":"Target Body"},"target_audio_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Audio Url"},"target_body_pos":{"additionalProperties":true,"type":"object","title":"Target Body Pos"},"target_body_transcript":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Target Body Transcript"}},"type":"object","required":["id","published_at","source_language","source_title","source_body","source_body_pos","target_language","target_complexities","target_title","target_body","target_audio_url","target_body_pos","target_body_transcript"],"title":"ArticleDetail"},"ArticleItem":{"properties":{"id":{"type":"string","title":"Id"},"published_at":{"type":"string","format":"date-time","title":"Published At"},"source_language":{"type":"string","title":"Source Language"},"source_title":{"type":"string","title":"Source Title"},"target_language":{"type":"string","title":"Target Language"},"target_complexities":{"items":{"type":"string"},"type":"array","title":"Target Complexities"},"target_title":{"type":"string","title":"Target Title"}},"type":"object","required":["id","published_at","source_language","source_title","target_language","target_complexities","target_title"],"title":"ArticleItem"},"ArticleListResponse":{"properties":{"articles":{"items":{"$ref":"#/components/schemas/ArticleItem"},"type":"array","title":"Articles"}},"type":"object","required":["articles"],"title":"ArticleListResponse"},"ChoiceResponse":{"properties":{"id":{"type":"string","title":"Id"},"index":{"type":"integer","title":"Index"},"label":{"type":"string","title":"Label"},"text":{"type":"string","title":"Text"}},"type":"object","required":["id","index","label","text"],"title":"ChoiceResponse"},"CreateAdventureRequest":{"properties":{"language":{"type":"string","title":"Language"},"source_language":{"type":"string","title":"Source Language"},"competencies":{"items":{"type":"string"},"type":"array","title":"Competencies"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"setting":{"items":{"type":"string"},"type":"array","title":"Setting"},"vibes":{"items":{"type":"string"},"type":"array","title":"Vibes"},"protagonist":{"items":{"type":"string"},"type":"array","title":"Protagonist"},"entry_word_count_range":{"type":"string","title":"Entry Word Count Range"},"max_entry_count":{"type":"integer","title":"Max Entry Count","default":6}},"type":"object","required":["language","source_language","competencies","genres","setting","vibes","protagonist","entry_word_count_range"],"title":"CreateAdventureRequest"},"CreateDecisionRequest":{"properties":{"choice_id":{"type":"string","title":"Choice Id"}},"type":"object","required":["choice_id"],"title":"CreateDecisionRequest"},"CreatePackRequest":{"properties":{"name":{"type":"string","title":"Name"},"name_target":{"type":"string","title":"Name Target"},"description":{"type":"string","title":"Description"},"description_target":{"type":"string","title":"Description Target"},"source_lang":{"type":"string","title":"Source Lang"},"target_lang":{"type":"string","title":"Target Lang"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies","default":[]}},"type":"object","required":["name","name_target","description","description_target","source_lang","target_lang"],"title":"CreatePackRequest"},"DecisionResponse":{"properties":{"id":{"type":"string","title":"Id"},"choice_id":{"type":"string","title":"Choice Id"},"user_id":{"type":"string","title":"User Id"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","choice_id","user_id","created_at"],"title":"DecisionResponse"},"EntryDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"adventure_id":{"type":"string","title":"Adventure Id"},"generated_from_choice_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated From Choice Id"},"status":{"type":"string","title":"Status"},"entry_index":{"type":"integer","title":"Entry Index"},"story_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Story Text"},"created_at":{"type":"string","title":"Created At"},"choices":{"items":{"$ref":"#/components/schemas/ChoiceResponse"},"type":"array","title":"Choices"},"translation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Translation"},"audio_file_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audio File Name"}},"type":"object","required":["id","adventure_id","generated_from_choice_id","status","entry_index","story_text","created_at","choices","translation","audio_file_name"],"title":"EntryDetailResponse"},"EntryResponse":{"properties":{"id":{"type":"string","title":"Id"},"adventure_id":{"type":"string","title":"Adventure Id"},"generated_from_choice_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated From Choice Id"},"status":{"type":"string","title":"Status"},"entry_index":{"type":"integer","title":"Entry Index"},"story_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Story Text"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","adventure_id","generated_from_choice_id","status","entry_index","story_text","created_at"],"title":"EntryResponse"},"FlashcardEventResponse":{"properties":{"id":{"type":"string","title":"Id"},"flashcard_id":{"type":"string","title":"Flashcard Id"},"user_id":{"type":"string","title":"User Id"},"event_type":{"type":"string","title":"Event Type"},"user_response":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Response"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","flashcard_id","user_id","event_type","user_response","created_at"],"title":"FlashcardEventResponse"},"FlashcardResponse":{"properties":{"id":{"type":"string","title":"Id"},"user_id":{"type":"string","title":"User Id"},"bank_entry_id":{"type":"string","title":"Bank Entry Id"},"source_lang":{"type":"string","title":"Source Lang"},"target_lang":{"type":"string","title":"Target Lang"},"prompt_text":{"type":"string","title":"Prompt Text"},"answer_text":{"type":"string","title":"Answer Text"},"prompt_context_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt Context Text"},"answer_context_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Answer Context Text"},"prompt_modality":{"type":"string","title":"Prompt Modality"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","user_id","bank_entry_id","source_lang","target_lang","prompt_text","answer_text","prompt_context_text","answer_context_text","prompt_modality","created_at"],"title":"FlashcardResponse"},"FlashcardTemplateResponse":{"properties":{"id":{"type":"string","title":"Id"},"pack_entry_id":{"type":"string","title":"Pack Entry Id"},"prompt_text":{"type":"string","title":"Prompt Text"},"answer_text":{"type":"string","title":"Answer Text"},"prompt_context_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt Context Text"},"answer_context_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Answer Context Text"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","pack_entry_id","prompt_text","answer_text","prompt_context_text","answer_context_text","created_at"],"title":"FlashcardTemplateResponse"},"FromTokenResponse":{"properties":{"entry":{"$ref":"#/components/schemas/WordBankEntryResponse"},"sense_candidates":{"items":{"$ref":"#/components/schemas/SenseCandidateResponse"},"type":"array","title":"Sense Candidates"},"matched_via":{"type":"string","title":"Matched Via"}},"type":"object","required":["entry","sense_candidates","matched_via"],"title":"FromTokenResponse"},"GenerateFlashcardsRequest":{"properties":{"direction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Direction"}},"type":"object","title":"GenerateFlashcardsRequest"},"GenerationRequest":{"properties":{"target_language":{"type":"string","title":"Target Language"},"complexity_level":{"type":"string","title":"Complexity Level"},"input_texts":{"items":{"type":"string"},"type":"array","title":"Input Texts"},"source_language":{"type":"string","title":"Source Language","default":"en"}},"type":"object","required":["target_language","complexity_level","input_texts"],"title":"GenerationRequest"},"GenerationResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"}},"type":"object","required":["job_id"],"title":"GenerationResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"JobListResponse":{"properties":{"jobs":{"items":{"$ref":"#/components/schemas/JobSummary"},"type":"array","title":"Jobs"}},"type":"object","required":["jobs"],"title":"JobListResponse"},"JobResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"status":{"type":"string","title":"Status"},"translated_article_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Translated Article Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"}},"type":"object","required":["id","status","created_at"],"title":"JobResponse"},"JobSummary":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"}},"type":"object","required":["id","status","created_at"],"title":"JobSummary"},"LanguagePairOption":{"properties":{"value":{"type":"string","title":"Value"},"label":{"type":"string","title":"Label"},"description":{"type":"string","title":"Description"}},"type":"object","required":["value","label","description"],"title":"LanguagePairOption"},"LearnableLanguageItem":{"properties":{"id":{"type":"string","title":"Id"},"source_language":{"type":"string","title":"Source Language"},"target_language":{"type":"string","title":"Target Language"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"}},"type":"object","required":["id","source_language","target_language","proficiencies"],"title":"LearnableLanguageItem"},"LearnableLanguageRequest":{"properties":{"source_language":{"type":"string","title":"Source Language"},"target_language":{"type":"string","title":"Target Language"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"}},"type":"object","required":["source_language","target_language","proficiencies"],"title":"LearnableLanguageRequest"},"LearnableLanguageResponse":{"properties":{"id":{"type":"string","title":"Id"},"source_language":{"type":"string","title":"Source Language"},"target_language":{"type":"string","title":"Target Language"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"}},"type":"object","required":["id","source_language","target_language","proficiencies"],"title":"LearnableLanguageResponse"},"LemmaResponse":{"properties":{"id":{"type":"string","title":"Id"},"headword":{"type":"string","title":"Headword"},"language":{"type":"string","title":"Language"},"pos_raw":{"type":"string","title":"Pos Raw"},"pos_normalised":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pos Normalised"},"gender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gender"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"}},"type":"object","required":["id","headword","language","pos_raw","pos_normalised","gender","tags"],"title":"LemmaResponse"},"LoginRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"LoginRequest"},"OnboardingRequest":{"properties":{"human_name":{"type":"string","title":"Human Name"},"language_pairs":{"items":{"type":"string"},"type":"array","title":"Language Pairs"},"proficiencies":{"items":{"items":{"type":"string"},"type":"array"},"type":"array","title":"Proficiencies"}},"type":"object","required":["human_name","language_pairs","proficiencies"],"title":"OnboardingRequest"},"OnboardingResponse":{"properties":{"language_pairs":{"items":{"$ref":"#/components/schemas/LanguagePairOption"},"type":"array","title":"Language Pairs"},"proficiencies":{"items":{"$ref":"#/components/schemas/ProficiencyOption"},"type":"array","title":"Proficiencies"}},"type":"object","required":["language_pairs","proficiencies"],"title":"OnboardingResponse"},"POSRequest":{"properties":{"text":{"type":"string","title":"Text"},"language":{"type":"string","title":"Language"}},"type":"object","required":["text","language"],"title":"POSRequest"},"POSResponse":{"properties":{"language":{"type":"string","title":"Language"},"tokens":{"items":{"$ref":"#/components/schemas/TokenInfo"},"type":"array","title":"Tokens"}},"type":"object","required":["language","tokens"],"title":"POSResponse"},"PackEntryResponse":{"properties":{"id":{"type":"string","title":"Id"},"pack_id":{"type":"string","title":"Pack Id"},"sense_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sense Id"},"surface_text":{"type":"string","title":"Surface Text"},"created_at":{"type":"string","title":"Created At"},"flashcard_templates":{"items":{"$ref":"#/components/schemas/FlashcardTemplateResponse"},"type":"array","title":"Flashcard Templates","default":[]}},"type":"object","required":["id","pack_id","sense_id","surface_text","created_at"],"title":"PackEntryResponse"},"PackResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"name_target":{"type":"string","title":"Name Target"},"description":{"type":"string","title":"Description"},"description_target":{"type":"string","title":"Description Target"},"source_lang":{"type":"string","title":"Source Lang"},"target_lang":{"type":"string","title":"Target Lang"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"},"is_published":{"type":"boolean","title":"Is Published"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","name","name_target","description","description_target","source_lang","target_lang","proficiencies","is_published","created_at"],"title":"PackResponse"},"PackSelectionItem":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"name_target":{"type":"string","title":"Name Target"},"description":{"type":"string","title":"Description"},"description_target":{"type":"string","title":"Description Target"},"source_lang":{"type":"string","title":"Source Lang"},"target_lang":{"type":"string","title":"Target Lang"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"},"entry_count":{"type":"integer","title":"Entry Count"},"already_added":{"type":"boolean","title":"Already Added"}},"type":"object","required":["id","name","name_target","description","description_target","source_lang","target_lang","proficiencies","entry_count","already_added"],"title":"PackSelectionItem"},"PackSummaryResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"name_target":{"type":"string","title":"Name Target"},"description":{"type":"string","title":"Description"},"description_target":{"type":"string","title":"Description Target"},"source_lang":{"type":"string","title":"Source Lang"},"target_lang":{"type":"string","title":"Target Lang"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"},"entry_count":{"type":"integer","title":"Entry Count"}},"type":"object","required":["id","name","name_target","description","description_target","source_lang","target_lang","proficiencies","entry_count"],"title":"PackSummaryResponse"},"ProficiencyOption":{"properties":{"value":{"type":"string","title":"Value"},"label":{"type":"string","title":"Label"},"description":{"type":"string","title":"Description"}},"type":"object","required":["value","label","description"],"title":"ProficiencyOption"},"RecordEventRequest":{"properties":{"event_type":{"type":"string","title":"Event Type"},"user_response":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Response"}},"type":"object","required":["event_type"],"title":"RecordEventRequest"},"RegisterRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"RegisterRequest"},"RegisterResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"}},"type":"object","required":["success"],"title":"RegisterResponse"},"SenseCandidateResponse":{"properties":{"id":{"type":"string","title":"Id"},"gloss":{"type":"string","title":"Gloss"},"topics":{"items":{"type":"string"},"type":"array","title":"Topics"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"}},"type":"object","required":["id","gloss","topics","tags"],"title":"SenseCandidateResponse"},"SenseMatch":{"properties":{"sense":{"$ref":"#/components/schemas/SenseResponse"},"lemma":{"$ref":"#/components/schemas/LemmaResponse"}},"type":"object","required":["sense","lemma"],"title":"SenseMatch"},"SenseResponse":{"properties":{"id":{"type":"string","title":"Id"},"sense_index":{"type":"integer","title":"Sense Index"},"gloss":{"type":"string","title":"Gloss"},"topics":{"items":{"type":"string"},"type":"array","title":"Topics"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"}},"type":"object","required":["id","sense_index","gloss","topics","tags"],"title":"SenseResponse"},"SetSenseRequest":{"properties":{"sense_id":{"type":"string","title":"Sense Id"}},"type":"object","required":["sense_id"],"title":"SetSenseRequest"},"TokenInfo":{"properties":{"text":{"type":"string","title":"Text"},"lemma":{"type":"string","title":"Lemma"},"pos":{"type":"string","title":"Pos"},"tag":{"type":"string","title":"Tag"},"dep":{"type":"string","title":"Dep"},"is_stop":{"type":"boolean","title":"Is Stop"}},"type":"object","required":["text","lemma","pos","tag","dep","is_stop"],"title":"TokenInfo"},"TokenResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"}},"type":"object","required":["access_token"],"title":"TokenResponse"},"TranslationResponse":{"properties":{"text":{"type":"string","title":"Text"},"target_language":{"type":"string","title":"Target Language"},"translated_text":{"type":"string","title":"Translated Text"}},"type":"object","required":["text","target_language","translated_text"],"title":"TranslationResponse"},"UpdatePackRequest":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"name_target":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name Target"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"description_target":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description Target"},"proficiencies":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Proficiencies"}},"type":"object","title":"UpdatePackRequest"},"UserProfileResponse":{"properties":{"learnable_languages":{"items":{"$ref":"#/components/schemas/LearnableLanguageItem"},"type":"array","title":"Learnable Languages"}},"type":"object","required":["learnable_languages"],"title":"UserProfileResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WordBankEntryResponse":{"properties":{"id":{"type":"string","title":"Id"},"user_id":{"type":"string","title":"User Id"},"language_pair_id":{"type":"string","title":"Language Pair Id"},"sense_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sense Id"},"wordform_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wordform Id"},"surface_text":{"type":"string","title":"Surface Text"},"is_phrase":{"type":"boolean","title":"Is Phrase"},"entry_pathway":{"type":"string","title":"Entry Pathway"},"source_article_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Article Id"},"disambiguation_status":{"type":"string","title":"Disambiguation Status"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","user_id","language_pair_id","sense_id","wordform_id","surface_text","is_phrase","entry_pathway","source_article_id","disambiguation_status","created_at"],"title":"WordBankEntryResponse"},"WordformMatch":{"properties":{"lemma":{"$ref":"#/components/schemas/LemmaResponse"},"senses":{"items":{"$ref":"#/components/schemas/SenseResponse"},"type":"array","title":"Senses"}},"type":"object","required":["lemma","senses"],"title":"WordformMatch"},"app__routers__api__admin__packs__PackDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"name_target":{"type":"string","title":"Name Target"},"description":{"type":"string","title":"Description"},"description_target":{"type":"string","title":"Description Target"},"source_lang":{"type":"string","title":"Source Lang"},"target_lang":{"type":"string","title":"Target Lang"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"},"is_published":{"type":"boolean","title":"Is Published"},"created_at":{"type":"string","title":"Created At"},"entries":{"items":{"$ref":"#/components/schemas/PackEntryResponse"},"type":"array","title":"Entries","default":[]}},"type":"object","required":["id","name","name_target","description","description_target","source_lang","target_lang","proficiencies","is_published","created_at"],"title":"PackDetailResponse"},"app__routers__api__packs__PackDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"name_target":{"type":"string","title":"Name Target"},"description":{"type":"string","title":"Description"},"description_target":{"type":"string","title":"Description Target"},"source_lang":{"type":"string","title":"Source Lang"},"target_lang":{"type":"string","title":"Target Lang"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"},"entry_count":{"type":"integer","title":"Entry Count"},"surface_texts":{"items":{"type":"string"},"type":"array","title":"Surface Texts"}},"type":"object","required":["id","name","name_target","description","description_target","source_lang","target_lang","proficiencies","entry_count","surface_texts"],"title":"PackDetailResponse"}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}} \ No newline at end of file +{"openapi":"3.1.0","info":{"title":"Language Learning API","version":"0.1.0"},"paths":{"/api/auth/register":{"post":{"tags":["api","auth"],"summary":"Register","operationId":"register_api_auth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/login":{"post":{"tags":["api","auth"],"summary":"Login","operationId":"login_api_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/verify-email":{"get":{"tags":["api","auth"],"summary":"Verify Email","operationId":"verify_email_api_auth_verify_email_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Verify Email Api Auth Verify Email Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/account/learnable-languages":{"post":{"tags":["api","account"],"summary":"Add Learnable Language","operationId":"add_learnable_language_api_account_learnable_languages_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddLearnableLanguageRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearnableLanguageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/account/onboarding":{"post":{"tags":["api","account"],"summary":"Complete Onboarding","operationId":"complete_onboarding_api_account_onboarding_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Complete Onboarding Api Account Onboarding Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/account/status":{"get":{"tags":["api","account"],"summary":"Get Account Status","operationId":"get_account_status_api_account_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountStatusResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/account/learnable-languages/{language_id}":{"delete":{"tags":["api","account"],"summary":"Remove Learnable Language","operationId":"remove_learnable_language_api_account_learnable_languages__language_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"language_id","in":"path","required":true,"schema":{"type":"string","title":"Language Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/dictionary/search":{"get":{"tags":["api","dictionary"],"summary":"Search Wordforms Prefix","description":"Search for wordforms whose surface text starts with the given prefix.\n\nUses accent-insensitive, case-insensitive prefix matching so that e.g.\n\"chatea\" returns both \"château\" and \"châteaux\", and \"lent\" returns all\nfour forms of the adjective. Returns one entry per matching lemma.","operationId":"search_wordforms_prefix_api_dictionary_search_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"lang_code","in":"query","required":true,"schema":{"type":"string","title":"Lang Code"}},{"name":"text","in":"query","required":true,"schema":{"type":"string","title":"Text"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WordformMatch"},"title":"Response Search Wordforms Prefix Api Dictionary Search Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/dictionary/senses":{"get":{"tags":["api","dictionary"],"summary":"Search Senses","description":"Search for a Sense by (English) definition\n\nReturns one entry per matching senses,each with its Sense.","operationId":"search_senses_api_dictionary_senses_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"lang_code","in":"query","required":true,"schema":{"type":"string","title":"Lang Code"}},{"name":"text","in":"query","required":true,"schema":{"type":"string","title":"Text"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SenseMatch"},"title":"Response Search Senses Api Dictionary Senses Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/dictionary/wordforms":{"get":{"tags":["api","dictionary"],"summary":"Search Wordforms","description":"Search for a wordform by surface text within a language.\n\nReturns one entry per matching lemma, each with the lemma's senses. A single\nform (e.g. \"allons\") may resolve to more than one lemma when homographs exist.","operationId":"search_wordforms_api_dictionary_wordforms_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"lang_code","in":"query","required":true,"schema":{"type":"string","title":"Lang Code"}},{"name":"text","in":"query","required":true,"schema":{"type":"string","title":"Text"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WordformMatch"},"title":"Response Search Wordforms Api Dictionary Wordforms Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/vocab/{entry_id}/flashcards":{"post":{"tags":["api","flashcards"],"summary":"Generate Flashcards","operationId":"generate_flashcards_api_vocab__entry_id__flashcards_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"entry_id","in":"path","required":true,"schema":{"type":"string","title":"Entry Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateFlashcardsRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FlashcardResponse"},"title":"Response Generate Flashcards Api Vocab Entry Id Flashcards Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/flashcards":{"get":{"tags":["api","flashcards"],"summary":"List Flashcards","operationId":"list_flashcards_api_flashcards_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/FlashcardResponse"},"type":"array","title":"Response List Flashcards Api Flashcards Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/flashcards/{flashcard_id}/events":{"post":{"tags":["api","flashcards"],"summary":"Record Event","operationId":"record_event_api_flashcards__flashcard_id__events_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"flashcard_id","in":"path","required":true,"schema":{"type":"string","title":"Flashcard Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordEventRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlashcardEventResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/pos/":{"post":{"tags":["api","api","pos"],"summary":"Analyze Pos","operationId":"analyze_pos_api_pos__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/POSRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/POSResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/translate":{"get":{"tags":["api","api","translate"],"summary":"Translate text to a target language","operationId":"translate_text_api_translate_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"text","in":"query","required":true,"schema":{"type":"string","title":"Text"}},{"name":"target_language","in":"query","required":true,"schema":{"type":"string","title":"Target Language"}},{"name":"context","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Context"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranslationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/generate":{"post":{"tags":["api","api"],"summary":"Create Generation Job","operationId":"create_generation_job_api_generate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerationRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/jobs/":{"get":{"tags":["api"],"summary":"Get Jobs","operationId":"get_jobs_api_jobs__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobListResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/jobs/{job_id}":{"get":{"tags":["api"],"summary":"Get Job","operationId":"get_job_api_jobs__job_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/regenerate-audio":{"post":{"tags":["api"],"summary":"Regenerate Audio","operationId":"regenerate_audio_api_jobs__job_id__regenerate_audio_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Regenerate Audio Api Jobs Job Id Regenerate Audio Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/learnable_languages":{"post":{"tags":["api","api"],"summary":"Upsert Learnable Language","operationId":"upsert_learnable_language_api_learnable_languages_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearnableLanguageRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearnableLanguageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/vocab":{"post":{"tags":["api","vocab"],"summary":"Add Word","operationId":"add_word_api_vocab_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddWordRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WordBankEntryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["api","vocab"],"summary":"List Entries","operationId":"list_entries_api_vocab_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"language_pair_id","in":"query","required":true,"schema":{"type":"string","title":"Language Pair Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WordBankEntryResponse"},"title":"Response List Entries Api Vocab Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/vocab/from-token":{"post":{"tags":["api","vocab"],"summary":"Add From Token","operationId":"add_from_token_api_vocab_from_token_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddFromTokenRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FromTokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/vocab/pending-disambiguation":{"get":{"tags":["api","vocab"],"summary":"Pending Disambiguation","operationId":"pending_disambiguation_api_vocab_pending_disambiguation_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/WordBankEntryResponse"},"type":"array","title":"Response Pending Disambiguation Api Vocab Pending Disambiguation Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/vocab/{entry_id}/sense":{"patch":{"tags":["api","vocab"],"summary":"Resolve Sense","operationId":"resolve_sense_api_vocab__entry_id__sense_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"entry_id","in":"path","required":true,"schema":{"type":"string","title":"Entry Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetSenseRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WordBankEntryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/packs":{"get":{"tags":["api","packs"],"summary":"List Packs","operationId":"list_packs_api_packs_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"source_lang","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Lang"}},{"name":"target_lang","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PackSummaryResponse"},"title":"Response List Packs Api Packs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/packs/{pack_id}":{"get":{"tags":["api","packs"],"summary":"Get Pack","operationId":"get_pack_api_packs__pack_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pack_id","in":"path","required":true,"schema":{"type":"string","title":"Pack Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/app__routers__api__packs__PackDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/packs/{pack_id}/add-to-bank":{"post":{"tags":["api","packs"],"summary":"Add Pack To Bank","operationId":"add_pack_to_bank_api_packs__pack_id__add_to_bank_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pack_id","in":"path","required":true,"schema":{"type":"string","title":"Pack Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddTobankRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddTobankResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/packs":{"post":{"tags":["api","admin-packs"],"summary":"Create Pack","operationId":"create_pack_api_admin_packs_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePackRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PackResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["api","admin-packs"],"summary":"List Packs","operationId":"list_packs_api_admin_packs_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"source_lang","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Lang"}},{"name":"target_lang","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PackResponse"},"title":"Response List Packs Api Admin Packs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/packs/{pack_id}":{"get":{"tags":["api","admin-packs"],"summary":"Get Pack","operationId":"get_pack_api_admin_packs__pack_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pack_id","in":"path","required":true,"schema":{"type":"string","title":"Pack Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/app__routers__api__admin__packs__PackDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["api","admin-packs"],"summary":"Update Pack","operationId":"update_pack_api_admin_packs__pack_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pack_id","in":"path","required":true,"schema":{"type":"string","title":"Pack Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePackRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PackResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/packs/{pack_id}/publish":{"post":{"tags":["api","admin-packs"],"summary":"Publish Pack","operationId":"publish_pack_api_admin_packs__pack_id__publish_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pack_id","in":"path","required":true,"schema":{"type":"string","title":"Pack Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PackResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/packs/{pack_id}/entries":{"post":{"tags":["api","admin-packs"],"summary":"Add Entry","operationId":"add_entry_api_admin_packs__pack_id__entries_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pack_id","in":"path","required":true,"schema":{"type":"string","title":"Pack Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddEntryRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PackEntryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/packs/{pack_id}/entries/{entry_id}":{"delete":{"tags":["api","admin-packs"],"summary":"Remove Entry","operationId":"remove_entry_api_admin_packs__pack_id__entries__entry_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pack_id","in":"path","required":true,"schema":{"type":"string","title":"Pack Id"}},{"name":"entry_id","in":"path","required":true,"schema":{"type":"string","title":"Entry Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/packs/{pack_id}/entries/{entry_id}/flashcards":{"post":{"tags":["api","admin-packs"],"summary":"Add Flashcard Template","operationId":"add_flashcard_template_api_admin_packs__pack_id__entries__entry_id__flashcards_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pack_id","in":"path","required":true,"schema":{"type":"string","title":"Pack Id"}},{"name":"entry_id","in":"path","required":true,"schema":{"type":"string","title":"Entry Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddFlashcardTemplateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlashcardTemplateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/packs/{pack_id}/entries/{entry_id}/flashcards/{template_id}":{"delete":{"tags":["api","admin-packs"],"summary":"Remove Flashcard Template","operationId":"remove_flashcard_template_api_admin_packs__pack_id__entries__entry_id__flashcards__template_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pack_id","in":"path","required":true,"schema":{"type":"string","title":"Pack Id"}},{"name":"entry_id","in":"path","required":true,"schema":{"type":"string","title":"Entry Id"}},{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/adventures":{"get":{"tags":["api","adventures"],"summary":"List Adventures","operationId":"list_adventures_api_adventures_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/AdventureResponse"},"type":"array","title":"Response List Adventures Api Adventures Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["api","adventures"],"summary":"Create Adventure","operationId":"create_adventure_api_adventures_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAdventureRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdventureResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/adventures/{adventure_id}":{"get":{"tags":["api","adventures"],"summary":"Get Adventure","operationId":"get_adventure_api_adventures__adventure_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"adventure_id","in":"path","required":true,"schema":{"type":"string","title":"Adventure Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdventureResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["api","adventures"],"summary":"Delete Adventure","operationId":"delete_adventure_api_adventures__adventure_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"adventure_id","in":"path","required":true,"schema":{"type":"string","title":"Adventure Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/adventures/{adventure_id}/decisions":{"post":{"tags":["api","adventures"],"summary":"Record Decision","operationId":"record_decision_api_adventures__adventure_id__decisions_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"adventure_id","in":"path","required":true,"schema":{"type":"string","title":"Adventure Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDecisionRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/adventures/{adventure_id}/entries":{"get":{"tags":["api","adventures"],"summary":"List Entries","operationId":"list_entries_api_adventures__adventure_id__entries_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"adventure_id","in":"path","required":true,"schema":{"type":"string","title":"Adventure Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/EntryResponse"},"title":"Response List Entries Api Adventures Adventure Id Entries Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/adventures/{adventure_id}/entries/{entry_id}":{"get":{"tags":["api","adventures"],"summary":"Get Entry","operationId":"get_entry_api_adventures__adventure_id__entries__entry_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"adventure_id","in":"path","required":true,"schema":{"type":"string","title":"Adventure Id"}},{"name":"entry_id","in":"path","required":true,"schema":{"type":"string","title":"Entry Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntryDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/bff/account":{"get":{"tags":["bff","bff"],"summary":"Get Account","operationId":"get_account_bff_account_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/bff/account/onboarding":{"get":{"tags":["bff","bff"],"summary":"Get Onboarding","operationId":"get_onboarding_bff_account_onboarding_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/bff/adventure/{adventure_id}":{"get":{"tags":["bff","bff","adventures"],"summary":"Get Adventure","operationId":"get_adventure_bff_adventure__adventure_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"adventure_id","in":"path","required":true,"schema":{"type":"string","title":"Adventure Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdventureDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/bff/articles":{"get":{"tags":["bff","bff","articles"],"summary":"List Articles","operationId":"list_articles_bff_articles_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"target_language","in":"query","required":false,"schema":{"type":"string","default":"fr","title":"Target Language"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArticleListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/bff/articles/{article_id}":{"get":{"tags":["bff","bff","articles"],"summary":"Get Article","operationId":"get_article_bff_articles__article_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"article_id","in":"path","required":true,"schema":{"type":"string","title":"Article Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArticleDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/bff/user_profile":{"get":{"tags":["bff","bff"],"summary":"Get User Profile","operationId":"get_user_profile_bff_user_profile_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfileResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/bff/packs":{"get":{"tags":["bff","bff-packs"],"summary":"List Packs For Selection","operationId":"list_packs_for_selection_bff_packs_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"source_lang","in":"query","required":true,"schema":{"type":"string","title":"Source Lang"}},{"name":"target_lang","in":"query","required":true,"schema":{"type":"string","title":"Target Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PackSelectionItem"},"title":"Response List Packs For Selection Bff Packs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/adventure-audio/{filename}":{"get":{"tags":["media"],"summary":"Get Adventure Audio File","operationId":"get_adventure_audio_file_media_adventure_audio__filename__get","parameters":[{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/media/{filename}":{"get":{"tags":["media"],"summary":"Get Media File","operationId":"get_media_file_media__filename__get","parameters":[{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Health Get"}}}}}}}},"components":{"schemas":{"AccountLanguagePair":{"properties":{"id":{"type":"string","title":"Id"},"source_language":{"type":"string","title":"Source Language"},"target_language":{"type":"string","title":"Target Language"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"}},"type":"object","required":["id","source_language","target_language","proficiencies"],"title":"AccountLanguagePair"},"AccountResponse":{"properties":{"email":{"type":"string","title":"Email"},"human_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Human Name"},"language_pairs":{"items":{"$ref":"#/components/schemas/AccountLanguagePair"},"type":"array","title":"Language Pairs"}},"type":"object","required":["email","human_name","language_pairs"],"title":"AccountResponse"},"AccountStatusResponse":{"properties":{"problem_flags":{"items":{"type":"string"},"type":"array","title":"Problem Flags"},"error_messages":{"items":{"type":"string"},"type":"array","title":"Error Messages"}},"type":"object","required":["problem_flags","error_messages"],"title":"AccountStatusResponse"},"AddEntryRequest":{"properties":{"sense_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sense Id"},"surface_text":{"type":"string","title":"Surface Text"}},"type":"object","required":["surface_text"],"title":"AddEntryRequest"},"AddFlashcardTemplateRequest":{"properties":{"prompt_text":{"type":"string","title":"Prompt Text"},"answer_text":{"type":"string","title":"Answer Text"},"prompt_context_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt Context Text"},"answer_context_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Answer Context Text"}},"type":"object","required":["prompt_text","answer_text"],"title":"AddFlashcardTemplateRequest"},"AddFromTokenRequest":{"properties":{"language_pair_id":{"type":"string","title":"Language Pair Id"},"surface":{"type":"string","title":"Surface"},"spacy_lemma":{"type":"string","title":"Spacy Lemma"},"pos_ud":{"type":"string","title":"Pos Ud"},"language":{"type":"string","title":"Language"},"source_article_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Article Id"}},"type":"object","required":["language_pair_id","surface","spacy_lemma","pos_ud","language"],"title":"AddFromTokenRequest"},"AddLearnableLanguageRequest":{"properties":{"source_language":{"type":"string","title":"Source Language"},"target_language":{"type":"string","title":"Target Language"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"}},"type":"object","required":["source_language","target_language","proficiencies"],"title":"AddLearnableLanguageRequest"},"AddTobankRequest":{"properties":{"source_lang":{"type":"string","title":"Source Lang"},"target_lang":{"type":"string","title":"Target Lang"}},"type":"object","required":["source_lang","target_lang"],"title":"AddTobankRequest"},"AddTobankResponse":{"properties":{"added":{"items":{"type":"string"},"type":"array","title":"Added"}},"type":"object","required":["added"],"title":"AddTobankResponse"},"AddWordRequest":{"properties":{"language_pair_id":{"type":"string","title":"Language Pair Id"},"surface_text":{"type":"string","title":"Surface Text"},"entry_pathway":{"type":"string","title":"Entry Pathway","default":"manual"},"is_phrase":{"type":"boolean","title":"Is Phrase","default":false},"source_article_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Article Id"}},"type":"object","required":["language_pair_id","surface_text"],"title":"AddWordRequest"},"AdventureChoiceItem":{"properties":{"id":{"type":"string","title":"Id"},"index":{"type":"integer","title":"Index"},"label":{"type":"string","title":"Label"},"text":{"type":"string","title":"Text"}},"type":"object","required":["id","index","label","text"],"title":"AdventureChoiceItem"},"AdventureDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"user_id":{"type":"string","title":"User Id"},"status":{"type":"string","title":"Status"},"language":{"type":"string","title":"Language"},"source_language":{"type":"string","title":"Source Language"},"competencies":{"items":{"type":"string"},"type":"array","title":"Competencies"},"max_entry_count":{"type":"integer","title":"Max Entry Count"},"title":{"type":"string","title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"setting":{"items":{"type":"string"},"type":"array","title":"Setting"},"vibes":{"items":{"type":"string"},"type":"array","title":"Vibes"},"protagonist":{"items":{"type":"string"},"type":"array","title":"Protagonist"},"created_at":{"type":"string","title":"Created At"},"entries":{"items":{"$ref":"#/components/schemas/AdventureEntryItem"},"type":"array","title":"Entries"},"current_entry_choices":{"items":{"$ref":"#/components/schemas/AdventureChoiceItem"},"type":"array","title":"Current Entry Choices"}},"type":"object","required":["id","user_id","status","language","source_language","competencies","max_entry_count","title","description","genres","setting","vibes","protagonist","created_at","entries","current_entry_choices"],"title":"AdventureDetailResponse"},"AdventureEntryItem":{"properties":{"id":{"type":"string","title":"Id"},"adventure_id":{"type":"string","title":"Adventure Id"},"possible_choices":{"anyOf":[{"items":{"$ref":"#/components/schemas/AdventureChoiceItem"},"type":"array"},{"type":"null"}],"title":"Possible Choices"},"generated_from_choice_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated From Choice Id"},"status":{"type":"string","title":"Status"},"entry_index":{"type":"integer","title":"Entry Index"},"story_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Story Text"},"story_text_linguistic_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Story Text Linguistic Data"},"translation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Translation"},"audio_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audio Url"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","adventure_id","possible_choices","generated_from_choice_id","status","entry_index","story_text","story_text_linguistic_data","translation","audio_url","created_at"],"title":"AdventureEntryItem"},"AdventureResponse":{"properties":{"id":{"type":"string","title":"Id"},"user_id":{"type":"string","title":"User Id"},"status":{"type":"string","title":"Status"},"language":{"type":"string","title":"Language"},"source_language":{"type":"string","title":"Source Language"},"competencies":{"items":{"type":"string"},"type":"array","title":"Competencies"},"max_entry_count":{"type":"integer","title":"Max Entry Count"},"title":{"type":"string","title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"setting":{"items":{"type":"string"},"type":"array","title":"Setting"},"vibes":{"items":{"type":"string"},"type":"array","title":"Vibes"},"protagonist":{"items":{"type":"string"},"type":"array","title":"Protagonist"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","user_id","status","language","source_language","competencies","max_entry_count","title","description","genres","setting","vibes","protagonist","created_at"],"title":"AdventureResponse"},"ArticleDetail":{"properties":{"id":{"type":"string","title":"Id"},"published_at":{"type":"string","format":"date-time","title":"Published At"},"source_language":{"type":"string","title":"Source Language"},"source_title":{"type":"string","title":"Source Title"},"source_body":{"type":"string","title":"Source Body"},"source_body_pos":{"additionalProperties":true,"type":"object","title":"Source Body Pos"},"target_language":{"type":"string","title":"Target Language"},"target_complexities":{"items":{"type":"string"},"type":"array","title":"Target Complexities"},"target_title":{"type":"string","title":"Target Title"},"target_body":{"type":"string","title":"Target Body"},"target_audio_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Audio Url"},"target_body_pos":{"additionalProperties":true,"type":"object","title":"Target Body Pos"},"target_body_transcript":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Target Body Transcript"}},"type":"object","required":["id","published_at","source_language","source_title","source_body","source_body_pos","target_language","target_complexities","target_title","target_body","target_audio_url","target_body_pos","target_body_transcript"],"title":"ArticleDetail"},"ArticleItem":{"properties":{"id":{"type":"string","title":"Id"},"published_at":{"type":"string","format":"date-time","title":"Published At"},"source_language":{"type":"string","title":"Source Language"},"source_title":{"type":"string","title":"Source Title"},"target_language":{"type":"string","title":"Target Language"},"target_complexities":{"items":{"type":"string"},"type":"array","title":"Target Complexities"},"target_title":{"type":"string","title":"Target Title"}},"type":"object","required":["id","published_at","source_language","source_title","target_language","target_complexities","target_title"],"title":"ArticleItem"},"ArticleListResponse":{"properties":{"articles":{"items":{"$ref":"#/components/schemas/ArticleItem"},"type":"array","title":"Articles"}},"type":"object","required":["articles"],"title":"ArticleListResponse"},"ChoiceResponse":{"properties":{"id":{"type":"string","title":"Id"},"index":{"type":"integer","title":"Index"},"label":{"type":"string","title":"Label"},"text":{"type":"string","title":"Text"}},"type":"object","required":["id","index","label","text"],"title":"ChoiceResponse"},"CreateAdventureRequest":{"properties":{"language":{"type":"string","title":"Language"},"source_language":{"type":"string","title":"Source Language"},"competencies":{"items":{"type":"string"},"type":"array","title":"Competencies"},"genres":{"items":{"type":"string"},"type":"array","title":"Genres"},"setting":{"items":{"type":"string"},"type":"array","title":"Setting"},"vibes":{"items":{"type":"string"},"type":"array","title":"Vibes"},"protagonist":{"items":{"type":"string"},"type":"array","title":"Protagonist"},"entry_word_count_range":{"type":"string","title":"Entry Word Count Range"},"max_entry_count":{"type":"integer","title":"Max Entry Count","default":6}},"type":"object","required":["language","source_language","competencies","genres","setting","vibes","protagonist","entry_word_count_range"],"title":"CreateAdventureRequest"},"CreateDecisionRequest":{"properties":{"choice_id":{"type":"string","title":"Choice Id"}},"type":"object","required":["choice_id"],"title":"CreateDecisionRequest"},"CreatePackRequest":{"properties":{"name":{"type":"string","title":"Name"},"name_target":{"type":"string","title":"Name Target"},"description":{"type":"string","title":"Description"},"description_target":{"type":"string","title":"Description Target"},"source_lang":{"type":"string","title":"Source Lang"},"target_lang":{"type":"string","title":"Target Lang"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies","default":[]}},"type":"object","required":["name","name_target","description","description_target","source_lang","target_lang"],"title":"CreatePackRequest"},"DecisionResponse":{"properties":{"id":{"type":"string","title":"Id"},"choice_id":{"type":"string","title":"Choice Id"},"user_id":{"type":"string","title":"User Id"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","choice_id","user_id","created_at"],"title":"DecisionResponse"},"EntryDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"adventure_id":{"type":"string","title":"Adventure Id"},"generated_from_choice_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated From Choice Id"},"status":{"type":"string","title":"Status"},"entry_index":{"type":"integer","title":"Entry Index"},"story_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Story Text"},"created_at":{"type":"string","title":"Created At"},"choices":{"items":{"$ref":"#/components/schemas/ChoiceResponse"},"type":"array","title":"Choices"},"translation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Translation"},"audio_file_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audio File Name"},"story_text_linguistic_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Story Text Linguistic Data"}},"type":"object","required":["id","adventure_id","generated_from_choice_id","status","entry_index","story_text","created_at","choices","translation","audio_file_name","story_text_linguistic_data"],"title":"EntryDetailResponse"},"EntryResponse":{"properties":{"id":{"type":"string","title":"Id"},"adventure_id":{"type":"string","title":"Adventure Id"},"generated_from_choice_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated From Choice Id"},"status":{"type":"string","title":"Status"},"entry_index":{"type":"integer","title":"Entry Index"},"story_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Story Text"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","adventure_id","generated_from_choice_id","status","entry_index","story_text","created_at"],"title":"EntryResponse"},"FlashcardEventResponse":{"properties":{"id":{"type":"string","title":"Id"},"flashcard_id":{"type":"string","title":"Flashcard Id"},"user_id":{"type":"string","title":"User Id"},"event_type":{"type":"string","title":"Event Type"},"user_response":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Response"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","flashcard_id","user_id","event_type","user_response","created_at"],"title":"FlashcardEventResponse"},"FlashcardResponse":{"properties":{"id":{"type":"string","title":"Id"},"user_id":{"type":"string","title":"User Id"},"bank_entry_id":{"type":"string","title":"Bank Entry Id"},"source_lang":{"type":"string","title":"Source Lang"},"target_lang":{"type":"string","title":"Target Lang"},"prompt_text":{"type":"string","title":"Prompt Text"},"answer_text":{"type":"string","title":"Answer Text"},"prompt_context_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt Context Text"},"answer_context_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Answer Context Text"},"prompt_modality":{"type":"string","title":"Prompt Modality"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","user_id","bank_entry_id","source_lang","target_lang","prompt_text","answer_text","prompt_context_text","answer_context_text","prompt_modality","created_at"],"title":"FlashcardResponse"},"FlashcardTemplateResponse":{"properties":{"id":{"type":"string","title":"Id"},"pack_entry_id":{"type":"string","title":"Pack Entry Id"},"prompt_text":{"type":"string","title":"Prompt Text"},"answer_text":{"type":"string","title":"Answer Text"},"prompt_context_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt Context Text"},"answer_context_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Answer Context Text"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","pack_entry_id","prompt_text","answer_text","prompt_context_text","answer_context_text","created_at"],"title":"FlashcardTemplateResponse"},"FromTokenResponse":{"properties":{"entry":{"$ref":"#/components/schemas/WordBankEntryResponse"},"sense_candidates":{"items":{"$ref":"#/components/schemas/SenseCandidateResponse"},"type":"array","title":"Sense Candidates"},"matched_via":{"type":"string","title":"Matched Via"}},"type":"object","required":["entry","sense_candidates","matched_via"],"title":"FromTokenResponse"},"GenerateFlashcardsRequest":{"properties":{"direction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Direction"}},"type":"object","title":"GenerateFlashcardsRequest"},"GenerationRequest":{"properties":{"target_language":{"type":"string","title":"Target Language"},"complexity_level":{"type":"string","title":"Complexity Level"},"input_texts":{"items":{"type":"string"},"type":"array","title":"Input Texts"},"source_language":{"type":"string","title":"Source Language","default":"en"}},"type":"object","required":["target_language","complexity_level","input_texts"],"title":"GenerationRequest"},"GenerationResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"}},"type":"object","required":["job_id"],"title":"GenerationResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"JobListResponse":{"properties":{"jobs":{"items":{"$ref":"#/components/schemas/JobSummary"},"type":"array","title":"Jobs"}},"type":"object","required":["jobs"],"title":"JobListResponse"},"JobResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"status":{"type":"string","title":"Status"},"translated_article_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Translated Article Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"}},"type":"object","required":["id","status","created_at"],"title":"JobResponse"},"JobSummary":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"}},"type":"object","required":["id","status","created_at"],"title":"JobSummary"},"LanguagePairOption":{"properties":{"value":{"type":"string","title":"Value"},"label":{"type":"string","title":"Label"},"description":{"type":"string","title":"Description"}},"type":"object","required":["value","label","description"],"title":"LanguagePairOption"},"LearnableLanguageItem":{"properties":{"id":{"type":"string","title":"Id"},"source_language":{"type":"string","title":"Source Language"},"target_language":{"type":"string","title":"Target Language"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"}},"type":"object","required":["id","source_language","target_language","proficiencies"],"title":"LearnableLanguageItem"},"LearnableLanguageRequest":{"properties":{"source_language":{"type":"string","title":"Source Language"},"target_language":{"type":"string","title":"Target Language"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"}},"type":"object","required":["source_language","target_language","proficiencies"],"title":"LearnableLanguageRequest"},"LearnableLanguageResponse":{"properties":{"id":{"type":"string","title":"Id"},"source_language":{"type":"string","title":"Source Language"},"target_language":{"type":"string","title":"Target Language"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"}},"type":"object","required":["id","source_language","target_language","proficiencies"],"title":"LearnableLanguageResponse"},"LemmaResponse":{"properties":{"id":{"type":"string","title":"Id"},"headword":{"type":"string","title":"Headword"},"language":{"type":"string","title":"Language"},"pos_raw":{"type":"string","title":"Pos Raw"},"pos_normalised":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pos Normalised"},"gender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gender"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"}},"type":"object","required":["id","headword","language","pos_raw","pos_normalised","gender","tags"],"title":"LemmaResponse"},"LoginRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"LoginRequest"},"OnboardingRequest":{"properties":{"human_name":{"type":"string","title":"Human Name"},"language_pairs":{"items":{"type":"string"},"type":"array","title":"Language Pairs"},"proficiencies":{"items":{"items":{"type":"string"},"type":"array"},"type":"array","title":"Proficiencies"}},"type":"object","required":["human_name","language_pairs","proficiencies"],"title":"OnboardingRequest"},"OnboardingResponse":{"properties":{"language_pairs":{"items":{"$ref":"#/components/schemas/LanguagePairOption"},"type":"array","title":"Language Pairs"},"proficiencies":{"items":{"$ref":"#/components/schemas/ProficiencyOption"},"type":"array","title":"Proficiencies"}},"type":"object","required":["language_pairs","proficiencies"],"title":"OnboardingResponse"},"POSRequest":{"properties":{"text":{"type":"string","title":"Text"},"language":{"type":"string","title":"Language"}},"type":"object","required":["text","language"],"title":"POSRequest"},"POSResponse":{"properties":{"language":{"type":"string","title":"Language"},"tokens":{"items":{"$ref":"#/components/schemas/TokenInfo"},"type":"array","title":"Tokens"}},"type":"object","required":["language","tokens"],"title":"POSResponse"},"PackEntryResponse":{"properties":{"id":{"type":"string","title":"Id"},"pack_id":{"type":"string","title":"Pack Id"},"sense_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sense Id"},"surface_text":{"type":"string","title":"Surface Text"},"created_at":{"type":"string","title":"Created At"},"flashcard_templates":{"items":{"$ref":"#/components/schemas/FlashcardTemplateResponse"},"type":"array","title":"Flashcard Templates","default":[]}},"type":"object","required":["id","pack_id","sense_id","surface_text","created_at"],"title":"PackEntryResponse"},"PackResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"name_target":{"type":"string","title":"Name Target"},"description":{"type":"string","title":"Description"},"description_target":{"type":"string","title":"Description Target"},"source_lang":{"type":"string","title":"Source Lang"},"target_lang":{"type":"string","title":"Target Lang"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"},"is_published":{"type":"boolean","title":"Is Published"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","name","name_target","description","description_target","source_lang","target_lang","proficiencies","is_published","created_at"],"title":"PackResponse"},"PackSelectionItem":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"name_target":{"type":"string","title":"Name Target"},"description":{"type":"string","title":"Description"},"description_target":{"type":"string","title":"Description Target"},"source_lang":{"type":"string","title":"Source Lang"},"target_lang":{"type":"string","title":"Target Lang"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"},"entry_count":{"type":"integer","title":"Entry Count"},"already_added":{"type":"boolean","title":"Already Added"}},"type":"object","required":["id","name","name_target","description","description_target","source_lang","target_lang","proficiencies","entry_count","already_added"],"title":"PackSelectionItem"},"PackSummaryResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"name_target":{"type":"string","title":"Name Target"},"description":{"type":"string","title":"Description"},"description_target":{"type":"string","title":"Description Target"},"source_lang":{"type":"string","title":"Source Lang"},"target_lang":{"type":"string","title":"Target Lang"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"},"entry_count":{"type":"integer","title":"Entry Count"}},"type":"object","required":["id","name","name_target","description","description_target","source_lang","target_lang","proficiencies","entry_count"],"title":"PackSummaryResponse"},"ProficiencyOption":{"properties":{"value":{"type":"string","title":"Value"},"label":{"type":"string","title":"Label"},"description":{"type":"string","title":"Description"}},"type":"object","required":["value","label","description"],"title":"ProficiencyOption"},"RecordEventRequest":{"properties":{"event_type":{"type":"string","title":"Event Type"},"user_response":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Response"}},"type":"object","required":["event_type"],"title":"RecordEventRequest"},"RegisterRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"RegisterRequest"},"RegisterResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"}},"type":"object","required":["success"],"title":"RegisterResponse"},"SenseCandidateResponse":{"properties":{"id":{"type":"string","title":"Id"},"gloss":{"type":"string","title":"Gloss"},"topics":{"items":{"type":"string"},"type":"array","title":"Topics"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"}},"type":"object","required":["id","gloss","topics","tags"],"title":"SenseCandidateResponse"},"SenseMatch":{"properties":{"sense":{"$ref":"#/components/schemas/SenseResponse"},"lemma":{"$ref":"#/components/schemas/LemmaResponse"}},"type":"object","required":["sense","lemma"],"title":"SenseMatch"},"SenseResponse":{"properties":{"id":{"type":"string","title":"Id"},"sense_index":{"type":"integer","title":"Sense Index"},"gloss":{"type":"string","title":"Gloss"},"topics":{"items":{"type":"string"},"type":"array","title":"Topics"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"}},"type":"object","required":["id","sense_index","gloss","topics","tags"],"title":"SenseResponse"},"SetSenseRequest":{"properties":{"sense_id":{"type":"string","title":"Sense Id"}},"type":"object","required":["sense_id"],"title":"SetSenseRequest"},"TokenInfo":{"properties":{"text":{"type":"string","title":"Text"},"lemma":{"type":"string","title":"Lemma"},"pos":{"type":"string","title":"Pos"},"tag":{"type":"string","title":"Tag"},"dep":{"type":"string","title":"Dep"},"is_stop":{"type":"boolean","title":"Is Stop"}},"type":"object","required":["text","lemma","pos","tag","dep","is_stop"],"title":"TokenInfo"},"TokenResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"}},"type":"object","required":["access_token"],"title":"TokenResponse"},"TranslationResponse":{"properties":{"text":{"type":"string","title":"Text"},"target_language":{"type":"string","title":"Target Language"},"translated_text":{"type":"string","title":"Translated Text"}},"type":"object","required":["text","target_language","translated_text"],"title":"TranslationResponse"},"UpdatePackRequest":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"name_target":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name Target"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"description_target":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description Target"},"proficiencies":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Proficiencies"}},"type":"object","title":"UpdatePackRequest"},"UserProfileResponse":{"properties":{"learnable_languages":{"items":{"$ref":"#/components/schemas/LearnableLanguageItem"},"type":"array","title":"Learnable Languages"}},"type":"object","required":["learnable_languages"],"title":"UserProfileResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WordBankEntryResponse":{"properties":{"id":{"type":"string","title":"Id"},"user_id":{"type":"string","title":"User Id"},"language_pair_id":{"type":"string","title":"Language Pair Id"},"sense_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sense Id"},"wordform_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wordform Id"},"surface_text":{"type":"string","title":"Surface Text"},"is_phrase":{"type":"boolean","title":"Is Phrase"},"entry_pathway":{"type":"string","title":"Entry Pathway"},"source_article_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Article Id"},"disambiguation_status":{"type":"string","title":"Disambiguation Status"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","user_id","language_pair_id","sense_id","wordform_id","surface_text","is_phrase","entry_pathway","source_article_id","disambiguation_status","created_at"],"title":"WordBankEntryResponse"},"WordformMatch":{"properties":{"lemma":{"$ref":"#/components/schemas/LemmaResponse"},"senses":{"items":{"$ref":"#/components/schemas/SenseResponse"},"type":"array","title":"Senses"}},"type":"object","required":["lemma","senses"],"title":"WordformMatch"},"app__routers__api__admin__packs__PackDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"name_target":{"type":"string","title":"Name Target"},"description":{"type":"string","title":"Description"},"description_target":{"type":"string","title":"Description Target"},"source_lang":{"type":"string","title":"Source Lang"},"target_lang":{"type":"string","title":"Target Lang"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"},"is_published":{"type":"boolean","title":"Is Published"},"created_at":{"type":"string","title":"Created At"},"entries":{"items":{"$ref":"#/components/schemas/PackEntryResponse"},"type":"array","title":"Entries","default":[]}},"type":"object","required":["id","name","name_target","description","description_target","source_lang","target_lang","proficiencies","is_published","created_at"],"title":"PackDetailResponse"},"app__routers__api__packs__PackDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"name_target":{"type":"string","title":"Name Target"},"description":{"type":"string","title":"Description"},"description_target":{"type":"string","title":"Description Target"},"source_lang":{"type":"string","title":"Source Lang"},"target_lang":{"type":"string","title":"Target Lang"},"proficiencies":{"items":{"type":"string"},"type":"array","title":"Proficiencies"},"entry_count":{"type":"integer","title":"Entry Count"},"surface_texts":{"items":{"type":"string"},"type":"array","title":"Surface Texts"}},"type":"object","required":["id","name","name_target","description","description_target","source_lang","target_lang","proficiencies","entry_count","surface_texts"],"title":"PackDetailResponse"}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}} \ No newline at end of file