Powered by Avantgarde Systems
An advanced AI-driven language learning ecosystem for mastering English, Ukrainian, and Greek β built on enterprise-grade infrastructure with spaced repetition science, real-time multiplayer duels, and LLM-powered vocabulary intelligence.
- What is Lexora?
- Core Features
- System Architecture
- Tech Stack
- Custom Odoo Modules
- Module File Structure
- Database Schema
- Async Event Bus
- Docker Compose Stack
- Repository Layout
- Quick Start
- Developer Tooling
- Documentation
- Implementation Status
- License
Lexora is a full-stack private language learning platform that goes far beyond flashcard apps. It combines a battle-tested SRS engine, synchronous AI translation, PvP word duels with XP progression, a curated Gold Vocabulary of 3000+ words, an interactive Grammar Encyclopedia, and a premium glassmorphism UI β all self-hosted and running entirely on your own infrastructure.
Supported languages: English Β· Ukrainian Β· Greek
Real-time synchronous translation between all three language pairs (en β uk β el)
powered by deep_translator (Google/MyMemory backend). Translate any text and save
the result directly to your personal vocabulary in one click.
Route: /translator
Full SM-2 algorithm implementation. Cards are scheduled at scientifically optimal
intervals based on recall difficulty. Four grade buttons (Again / Hard / Good / Easy)
adjust ease factor and next-review date. Daily practice queue with due-card counter.
Route: /my/practice
Asynchronous word duel system. Challenge other learners or the Lexora Bot, stake XP,
play 10 rounds against each other's vocabulary, and climb the global leaderboard.
XP economy with Streak Freeze, Double XP Booster, and Profile Frame shop items.
Route: /my/arena
- Gold Vocabulary β 3184 most common English words tagged by CEFR level (A1βB2), part of speech, and Ukrainian/Greek translations. Paginated 50/page with one-click "Add to My List" buttons.
- Grammar Encyclopedia β 6 comprehensive sections: All 12 Tenses, Irregular Verbs, Articles, Conditionals, Modal Verbs, Passive Voice & Reported Speech.
Routes: /useful-words Β· /grammar
Printable cheat sheets generated server-side via wkhtmltopdf:
- Personal vocabulary (word + translation + example sentence)
- Gold Vocabulary filtered by CEFR level
- Any Grammar section
Routes: /my/vocabulary/print Β· /useful-words/print?level=A1 Β· /grammar/<slug>/print
Manual entry with automatic language detection, normalization-based deduplication,
Anki .apkg / .txt import with audio extraction, inline translation editing,
LLM enrichment (synonyms, antonyms, example sentences, explanation), audio recording
and TTS generation.
Route: /my/vocabulary
Public language channels and private DMs (built on Odoo Discuss), posts and articles
with moderator review workflow, comments with @mentions, and "Save from Chat /
Save from Post" inline vocabulary capture.
Routes: /posts Β· /my/posts
Six curated conversation scenarios (cafΓ© ordering, job interview, airport check-in,
doctor's visit, hotel check-in, supermarket) powered by the local Qwen2.5 LLM.
In-context grammar corrections, glassmorphism chat UI, and session persistence.
Route: /my/roleplay
110 fill-in-the-blank exercises covering all 12 tenses, conditionals, modal verbs,
articles, and more. CEFR A1βB2 filter, instant green/red feedback, XP rewards.
Route: /my/grammar-practice
Dark animated hero section, glassmorphism cards, Inter + Montserrat typography,
Avantgarde Systems branding, and a fully custom CSS design system (lx-* tokens).
βββββββββββββββββββββββββββββββββββββββββββββββ
β Browser / Client β
β HTTP Β· WebSocket Β· JSON-RPC Β· PDF download β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββ
β :443 / :80
ββββββββββββββββββββββΌβββββββββββββββββββββββββ
β Nginx 1.27 (alpine) β
β SSL termination Β· static files Β· WS proxy β
β /websocket β odoo:8072 β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββ
β :8069 / :8072
ββββββββββββββββββββββΌβββββββββββββββββββββββββ
β Odoo 18 Community (4 workers) β
β β
β βββββββββββββββ ββββββββββββββββββββββ β
β β Portal / β β Odoo Backend / β β
β β Website β β Admin Interface β β
β βββββββββββββββ ββββββββββββββββββββββ β
β β
β ββββββββββββββββββββββββββββββββββββββββ β
β β Custom Modules (12) β β
β β security Β· core Β· words Β· trans Β· β β
β β enrich Β· audio Β· anki Β· chat Β· β β
β β dashboard Β· pvp Β· portal Β· learning β β
β ββββββββββββββββββββββββββββββββββββββββ β
β β
β ββββββββββββββββ ββββββββββββββββββββ β
β β RabbitMQ β β Redis 7 client β β
β β Publisher β β (PvP state) β β
β ββββββββ¬ββββββββ ββββββββββββββββββββ β
βββββββββββΌβββββββββββββββββββββββββββββββββββββ
β AMQP 0-9-1
ββββββββββββββββββββββΌβββββββββββββββββββββββββββ
β RabbitMQ 3 (management UI) β
β Durable queues Β· persistent messages β
ββββ¬βββββββββββββ¬ββββββββββββββββ¬ββββββββββββββββ
β β β β
ββββββββββΌβββ ββββββββΌβββββββ ββββββΌβββββββ ββββββΌβββββββββββ
βTranslationβ βLLM Enrichmt β β Anki β β Audio / TTS β
β FastAPI β β FastAPI β β FastAPI β β FastAPI β
β β β β β β β β
βdeep_trans β βllama-cpp-py β βzstandard β βedge-tts β
βGoogle/ β βQwen2.5-1.5B β βbs4 / zip β βfaster-whisper β
βMyMemory β βGGUF Q4_K_M β βSQLite β βespeak-ng β
β:8001 β β:8002 β β:8003 β β:8004 β
βββββββββββββ βββββββββββββββ βββββββββββββ βββββββββββββββββ
βββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββ
β PostgreSQL 16 β β Redis 7 β
β β β β
β Odoo ORM (business β β PvP ephemeral state only: β
β records, sessions, β β matchmaking queues, round β
β filestore metadata) β β state, reconnect grace TTLs β
β :5432 β β :6379 β
βββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββ
| Principle | Implementation |
|---|---|
| Single system of record | All business data lives in Odoo / PostgreSQL |
| Stateless processors | Worker services (FastAPI) own no persistent state |
| Async by default | Translation, enrichment, Anki import, TTS all via RabbitMQ |
| Sync exception | Roleplay and Translator use direct HTTP (immediate response required) |
| Idempotency | Every async job carries a UUID job_id; workers check terminal state before reprocessing |
| CPU-first | No GPU assumed; LLM and Whisper tuned for AVX-only 8 GiB servers |
| Layer | Technology | Notes |
|---|---|---|
| Application monolith | Odoo 18 Community | 4 prefork workers |
| Database | PostgreSQL 16 | pg_trgm extension for fuzzy search |
| Async message bus | RabbitMQ 3 | Durable queues, persistent messages |
| Ephemeral PvP state | Redis 7 | TTL-keyed battle state; NOT session store |
| Translation service | FastAPI + deep_translator |
Google primary / MyMemory fallback |
| LLM enrichment service | FastAPI + llama-cpp-python |
Qwen2.5-1.5B GGUF Q4_K_M, CPU-only |
| Anki import service | FastAPI + zstandard + beautifulsoup4 |
.apkg (Zstd) + .txt (TSV) |
| Audio / TTS service | FastAPI + edge-tts + faster-whisper |
Microsoft neural voices; Whisper base STT |
| Reverse proxy | Nginx 1.27 | WebSocket pass-through, SSL termination |
| Container orchestration | Docker Compose | Single-host; prod overlay planned |
| PDF generation | wkhtmltopdf 0.12.6 via Odoo QWeb | A4, 2-column print layout |
| Fuzzy search | base_search_fuzzy OCA addon + pg_trgm |
Vocabulary and cross-language lookup |
| Language detection | langdetect 1.0.9 |
Source language prefill; 0.7 confidence threshold |
All modules live under src/addons/. Install order matters β each module declares its
dependencies in __manifest__.py.
language_security
βββ language_core
βββ language_words
β βββ language_translation
β βββ language_enrichment
β βββ language_audio
β βββ language_anki_jobs
βββ language_chat
βββ language_dashboard
βββ language_pvp
βββ language_portal
βββ language_learning
| Module | Key Models | Responsibility |
|---|---|---|
language_security |
β | Security groups, record rules, portal signup hook |
language_core |
language.job.status.mixin |
System params, RabbitMQ publisher/consumer, job mixin |
language_words |
language.entry language.user.profile language.lang language.media.link |
Vocabulary CRUD, dedup, normalization, language detection, sharing |
language_translation |
language.translation |
Translation job lifecycle, translation.* event handling |
language_enrichment |
language.enrichment |
LLM enrichment job lifecycle, enrichment.* event handling |
language_audio |
language.audio |
User recording upload, TTS generation, audio.* event handling |
language_anki_jobs |
language.anki.job |
Anki import job lifecycle, dedup on completion, audio extraction |
language_chat |
discuss.channel (extended) |
Public language channels, DMs, save-from-chat |
language_dashboard |
β | Word of the day cron, popular words, community aggregations |
language_pvp |
language.duel language.duel.line |
PvP duels, Lexora Bot, XP transfer, leaderboard |
language_portal |
language.scenario language.scenario.session language.seeded.word language.grammar.section language.shop.item language.user.item |
All portal routes: vocabulary, translator, roleplay, grammar, shop, PDF export, library |
language_learning |
language.review language.user.profile (extended) language.xp.log |
SM-2 SRS engine, XP/streak/level gamification, leaderboard, dashboard |
Every custom module follows the standard Odoo 18 layout:
src/addons/language_<name>/
βββ __init__.py # post_init_hook / post_update_hook (if needed)
βββ __manifest__.py # module metadata, depends, data file list
β
βββ models/
β βββ __init__.py
β βββ language_<entity>.py # ORM model definition
β βββ ...
β
βββ controllers/
β βββ __init__.py
β βββ portal.py # HTTP routes (Odoo Controller)
β
βββ views/
β βββ <model>_views.xml # backend list/form/search views
β βββ portal_<feature>.xml # QWeb portal templates
β βββ pdf_<feature>.xml # QWeb PDF report templates (language_portal)
β
βββ security/
β βββ ir.model.access.csv # CRUD rights per group
β βββ record_rules.xml # row-level access rules
β
βββ data/
β βββ ir_cron_<name>.xml # scheduled actions
β βββ website_menus.xml # navbar entries
β βββ <seed_data>.xml # XML fixture data (noupdate="1")
β βββ <seed_data>.py # Python seed data (importlib-loaded in hook)
β
βββ static/
β βββ src/css/
β βββ premium_ui.css # custom CSS design system (language_portal)
β
βββ tests/
βββ __init__.py
βββ test_<feature>.py # pytest-style Odoo test cases
| Pattern | Where | Why |
|---|---|---|
importlib.util.spec_from_file_location for seed data |
language_portal/__init__.py |
Absolute import in hook context where relative imports fail |
_inherit = 'language.entry' with new field only |
language_audio, language_enrichment, language_translation |
Adds audio_ids, enrichment_ids, translation_ids to entry without modifying language_words |
"language.xp.log" in request.env.registry guard |
language_portal/controllers/ |
Loose coupling β XP awards degrade gracefully if language_learning is not installed |
QWeb inheritance via xpath with position="after" |
portal_audio.xml, portal_enrichment.xml |
Injects sections into the entry detail page without touching the parent template |
noupdate="0" on scenario XML |
roleplay_scenarios.xml |
Allows prompt updates via --update language_portal without delete/re-insert |
All tables are managed by Odoo ORM. Below are the key tables and relationships.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β language_entry β
βββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββ€
β id β Integer PK β
β type β Selection: word/phrase/sentence/collocation β
β source_text β Char (raw user input) β
β normalized_text β Char (dedup key component, computed on save) β
β source_language β Selection: en/uk/el β
β owner_id β Many2one β res_users β
β is_shared β Boolean (default False) β
β status β Selection: active/archived β
β created_from β Selection: manual/anki_import/copied_from_* β
β copied_from_user_id β Many2one β res_users (nullable) β
β copied_from_entry_id β Many2one β language_entry (nullable) β
β copied_from_post_id β Many2one β language_post (nullable) β
β pvp_eligible β Boolean (computed: has completed translation) β
βββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββ
β One2many β One2many β One2many
βΌ βΌ βΌ
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββββ
βlanguage_trans- β βlanguage_enrich- β β language_audio β
βlation β βment β β β
βββββββββββββββββββ€ ββββββββββββββββββββ€ βββββββββββββββββββββ€
β entry_id β β entry_id β β entry_id β
β target_language β β language β β audio_type β
β translated_text β β synonyms (JSON) β β language β
β job_id (UUID) β β antonyms (JSON) β β attachment_id β
β status β β example_sents β β job_id (UUID) β
β error_message β β explanation β β status β
β β β job_id (UUID) β β tts_engine β
β UNIQUE(entry, β β status β β file_size_bytes β
β target_lang) β β UNIQUE(entry, β β transcription β
β β β language) β β UNIQUE(entry, β
βββββββββββββββββββ ββββββββββββββββββββ β type, language) β
βββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β language_user_profile β
ββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ€
β user_id β Many2one β res_users (UNIQUE) β
β native_language β Selection: en/uk/el β
β default_source_lang β Selection: en/uk/el β
β is_shared_list β Boolean β
β pvp_total_battles β Integer β
β pvp_wins β Integer β
β pvp_losses β Integer β
β pvp_draws β Integer β
β pvp_win_rate β Float (computed) β
β xp_total β Integer (gamification β from language_lrng)β
β current_streak β Integer β
β longest_streak β Integer β
β last_practice_date β Date β
β level β Integer (computed: 1 + floor(sqrt(xp/50)))β
ββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββ
β Many2many
βΌ
βββββββββββββββββββββ ββββββββββββββββββββββββββββββββ
β language_lang β β language_xp_log β
βββββββββββββββββββββ€ ββββββββββββββββββββββββββββββββ€
β code (en/uk/el) β β user_id β
β name (Englishβ¦) β β amount (Integer, +/-) β
βββββββββββββββββββββ β reason (practice/duel_win/β¦) β
β duel_id (soft ref Integer) β
β date β
ββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β language_review β
ββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ€
β entry_id β Many2one β language_entry β
β user_id β Many2one β res_users β
β state β Selection: new/learning/review β
β next_review_date β Date β
β last_review_date β Date β
β repetitions β Integer (n in SM-2) β
β interval β Integer (days until next review) β
β ease_factor β Float (default 2.5, min 1.3, max 3.5) β
β total_reviews β Integer β
β correct_reviews β Integer β
β UNIQUE(user_id, entry_id) β
ββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββ
SM-2 algorithm grades:
| Grade | Button | Effect |
|---|---|---|
| 0 | Again | n=0, interval=1d, EF unchanged, stateβlearning |
| 1 | Hard | n unchanged, intervalΓ1.2, EFβ=0.15 |
| 2 | Good | n+1, interval via SM-2, EF unchanged, stateβreview |
| 3 | Easy | n+1, intervalΓ1.3, EF+=0.15, stateβreview |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β language_duel β
ββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ€
β challenger_id β Many2one β res_users β
β opponent_id β Many2one β res_users (nullable) β
β state β Selection: open/ongoing/finished/cancel β
β winner_id β Many2one β res_users (nullable) β
β xp_staked β Integer (default 10) β
β practice_language β Selection: en/uk/el β
β native_language β Selection: en/uk/el β
β rounds_total β Integer (default 10) β
β challenger_score β Integer β
β opponent_score β Integer β
β start_date β Datetime β
β end_date β Datetime β
ββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββ
β One2many
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β language_duel_line β
ββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ€
β duel_id β Many2one β language_duel (cascade) β
β player_id β Many2one β res_users β
β entry_id β Many2one β language_entry β
β round_number β Integer (1-based) β
β correct β Boolean β
β answer_given β Char β
β time_taken_seconds β Float β
ββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββ
β language_shop_item β β language_user_item β
ββββββββββββββββββββββββ¬βββββββββββββββ€ βββββββββββββββββββ¬ββββββββββββββββββ€
β name β Char β β user_id β Many2oneβusers β
β description β Text ββββββ item_id β Many2oneβitem β
β xp_cost β Integer β β quantity β Integer β
β item_type β Selection: β β activated_at β Datetime β
β β streak_freezeβ β expires_at β Datetime β
β β double_xp β βββββββββββββββββββ΄ββββββββββββββββββ
β β profile_frameβ
β icon β Char (emoji) β
β is_active β Boolean β
ββββββββββββββββββββββββ΄βββββββββββββββ
ββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββ
β language_seeded_word β β language_grammar_section β
ββββββββββββββββββββββββββββββ€ ββββββββββββββββββββββββββββββββββββ€
β word Char β β title Char β
β cefr_level Selection(A1β¦) β β slug Char (unique) β
β pos Char β β category Selection β
β uk_trans Char β β content_html Html β
β el_trans Char β β sequence Integer β
β sort_order Integer β β is_published Boolean β
ββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββ
β language_scenario β β language_scenario_session β
ββββββββββββββββββββββββββββββββ€ ββββββββββββββββββββββββββββββββββββββ€
β name Char β β scenario_id Many2oneβscenario β
β description Char β β user_id Many2oneβres_users β
β icon Char (emoji) ββββββ chat_history Text (JSON array) β
β target_language Selection β β UNIQUE(scenario_id, user_id) β
β initial_prompt Text β ββββββββββββββββββββββββββββββββββββββ
β is_active Boolean β
β sequence Integer β
ββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β language_anki_job β
ββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ€
β user_id β Many2one β res_users β
β filename β Char β
β file_format β Selection: apkg/txt β
β source_language_id β Many2one β language_lang β
β target_language_id β Many2one β language_lang (nullable) β
β field_mapping β Text (JSON: {source: int, translation: int})β
β job_id β Char (UUID, auto-set on create) β
β status β Selection: pending/processing/completed/failedβ
β count_created β Integer β
β count_skipped β Integer β
β count_failed β Integer β
β details_log β Text (JSON: {skipped: [...], failed: [...]})β
β error_message β Text β
ββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββ
Dedup key = normalize(source_text) + source_language + owner_id
normalize():
1. Unicode NFC
2. Lowercase
3. Strip leading/trailing whitespace
4. Collapse internal whitespace to single space
5. Normalize smart quotes/apostrophes/dashes to ASCII
6. Strip trailing sentence-ending punctuation (.!?) β dedup only, not stored
type is NOT in the dedup key.
On collision: skip + report count; never overwrite existing data.
All heavy processing flows through RabbitMQ. Odoo publishes a job and polls result
queues via a 1-minute cron (basic_get drain, ADR-023).
βββββββββββββββββββββββ
β Odoo β
β (publishes job) β
ββββββββββββ¬βββββββββββ
β AMQP publish
βΌ
βββββββββββββββββββββββ
β RabbitMQ queue β
β (durable, persist) β
ββββββββββββ¬βββββββββββ
β basic_consume (prefetch=1)
βΌ
βββββββββββββββββββββββ
β Worker service β
β (FastAPI thread) β
ββββββββββββ¬βββββββββββ
β AMQP publish result
βΌ
βββββββββββββββββββββββ
β Result queue β
ββββββββββββ¬βββββββββββ
β Odoo cron drains (every 1 min)
βΌ
βββββββββββββββββββββββ
β Odoo updates DB β
β status β completedβ
βββββββββββββββββββββββ
| Queue (requested) | Publisher | Queue (result) | Consumer |
|---|---|---|---|
translation.requested |
Odoo | translation.completed / .failed |
Odoo cron |
enrichment.requested |
Odoo | enrichment.completed / .failed |
Odoo cron |
anki.import.requested |
Odoo | anki.import.completed / .failed |
Odoo cron |
audio.generation.requested |
Odoo | audio.generation.completed / .failed |
Odoo cron |
audio.transcription.requested |
Odoo | audio.transcription.completed / .failed |
Odoo cron |
Every message (in both directions) uses this standard envelope:
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"event_type": "translation.requested",
"payload": {
"source_text": "apple",
"source_language": "en",
"target_language": "uk",
"entry_id": 42
}
}Workers ack only after a durable write. On failure, the fail event is published
before the ack. job_id lookup prevents duplicate processing on redelivery.
Two features require an immediate response and bypass the queue:
| Feature | Endpoint | Caller |
|---|---|---|
| AI Translator | POST /translate on translation service |
Odoo portal controller |
| AI Roleplay turn | POST /roleplay on LLM service |
Odoo portal controller |
# Pattern used in both sync features (portal controller side):
resp = requests.post(f"{LLM_SVC}/roleplay", json={...}, timeout=90)
resp.raise_for_status()
data = json.loads(resp.content.decode("utf-8", errors="replace"))docker_compose/
βββ db/ # PostgreSQL 16
β βββ docker-compose.yml # :5432, lexora volume
β
βββ odoo/ # Odoo 18 + observability
β βββ Dockerfile # FROM odoo:18, pip base-requirements
β βββ docker-compose.yml # :8069/:8072, nginx, loki, promtail
β βββ nginx.conf # WebSocket proxy, static files
β
βββ nginx/ # Standalone Nginx (prod overlay)
β βββ Dockerfile # FROM nginx:1.27-alpine
β βββ nginx.conf
β
βββ rabbitmq/ # RabbitMQ 3 management
β βββ docker-compose.yml # :5672 (AMQP), :15672 (UI)
β
βββ redis/ # Redis 7 alpine
β βββ docker-compose.yml # :6379, AOF persistence
β
βββ translation/ # Translation FastAPI worker
β βββ Dockerfile # python:3.11-slim
β βββ docker-compose.yml # :8001, TRANSLATE_* env vars
β
βββ llm/ # LLM Enrichment FastAPI worker
β βββ Dockerfile # python:3.11-slim + build-essential/cmake
β βββ docker-compose.yml # :8002, llm_models volume, LLM_* env vars
β
βββ anki/ # Anki Import FastAPI worker
β βββ Dockerfile # python:3.11-slim
β βββ docker-compose.yml # :8003
β
βββ audio/ # Audio/TTS FastAPI worker
βββ Dockerfile # python:3.11-slim + ffmpeg + espeak-ng
βββ docker-compose.yml # :8004, audio_models volume, TTS_* env vars
| Service | Internal | Host (dev) |
|---|---|---|
| Odoo (via Nginx) | 80 | 5433 |
| Odoo direct | 8069 | β (internal only) |
| Odoo WebSocket | 8072 | β (internal only) |
| PostgreSQL | 5432 | 5432 |
| RabbitMQ AMQP | 5672 | 5672 |
| RabbitMQ Management UI | 15672 | 15672 |
| Redis | 6379 | 6379 |
| Translation service | 8000 | 8001 |
| LLM service | 8000 | 8002 |
| Anki service | 8000 | 8003 |
| Audio service | 8000 | 8004 |
Lexora/
βββ src/
β βββ addons/ # All custom Odoo modules
β β βββ language_security/
β β βββ language_core/
β β βββ language_words/
β β βββ language_translation/
β β βββ language_enrichment/
β β βββ language_audio/
β β βββ language_anki_jobs/
β β βββ language_chat/
β β βββ language_dashboard/
β β βββ language_pvp/
β β βββ language_portal/
β β βββ language_learning/
β β βββ base_search_fuzzy/ # OCA addon (fuzzy vocab search)
β β βββ password_security/ # OCA addon (password policy)
β β βββ web_notify/ # OCA addon (toast notifications)
β β βββ website_require_login/ # OCA addon (auth gate)
β β βββ website_menu_by_user_status/ # OCA addon (conditional nav)
β βββ configs/
β βββ odoo.conf # Odoo configuration (workers=4, etc.)
β
βββ services/ # FastAPI microservices
β βββ translation/
β β βββ main.py # Consumer + /translate sync endpoint
β β βββ requirements.txt
β βββ llm/
β β βββ main.py # Consumer + /enrich + /roleplay endpoints
β β βββ requirements.txt
β βββ anki/
β β βββ main.py # Consumer + .apkg/.txt parsers
β β βββ requirements.txt
β β βββ tests/
β β βββ test_parsers.py # 22 parser unit tests
β βββ audio/
β βββ main.py # Consumer + edge-tts + faster-whisper
β βββ requirements.txt
β
βββ docker_compose/ # Per-service Docker Compose files
β βββ db/ odoo/ nginx/ rabbitmq/
β βββ redis/ translation/ llm/
β βββ anki/ audio/
β βββ pgadmin/ adminer/ # Optional DB tooling
β
βββ requirements/
β βββ base-requirements.txt # Odoo container pip deps (langdetect, redis, etc.)
β βββ dev-requirements.txt # Developer tools (ruff, mypy, bandit, etc.)
β
βββ docs/
β βββ SPEC.md # Product specification
β βββ ARCHITECTURE.md # System design
β βββ PLAN.md # Milestone implementation plan (M0βM21)
β βββ DECISIONS.md # ADR-001 through ADR-028
β βββ TASKS.md # Active task tracker / resume point
β
βββ .github/
β βββ workflows/
β β βββ lint.yml # Ruff + Mypy + Bandit + Hadolint + XMLlint
β β βββ test.yml # FastAPI pytest matrix + Odoo module tests
β β βββ security.yml # pip-audit + TruffleHog + Bandit SARIF
β β βββ docker-build.yml # Build all 5 Docker images
β β βββ pr-check.yml # Conventional Commits + branch name check
β βββ PULL_REQUEST_TEMPLATE.md
β βββ CODEOWNERS
β βββ dependabot.yml
β βββ ISSUE_TEMPLATE/
β βββ bug_report.yml
β βββ feature_request.yml
β
βββ backups/ # pg_restore targets
βββ logs/ # Nginx + app logs
βββ pyproject.toml # Ruff / Mypy / pytest / Bandit config
βββ .pre-commit-config.yaml # pre-commit hooks
βββ .editorconfig # Editor formatting rules
βββ Makefile # Developer shortcuts
βββ env.example # Environment variable template
βββ CLAUDE.md # AI assistant context file
βββ LICENSE # Proprietary β All rights reserved
# 1. Clone and configure
git clone https://github.com/YuriiDorosh/Lexora.git
cd Lexora
cp env.example .env # fill in RABBITMQ_PASS, etc.
# 2. Start the full stack
make up-dev # Odoo + Postgres + RabbitMQ + Redis + Nginx
# + Translation + LLM + Anki + Audio
# 3. Initialize the database (first run only)
# Open http://localhost:5433 and complete the Odoo setup wizard, then:
docker exec odoo odoo --config /etc/odoo/odoo.conf \
-d lexora \
--init language_security,language_core,language_words,language_translation,\
language_enrichment,language_audio,language_anki_jobs,language_chat,\
language_dashboard,language_pvp,language_portal,language_learning \
--stop-after-init
# 4. Access the platform
open http://localhost:5433Health checks:
curl http://localhost:5433/web/health # Odoo ({"status":"pass"})
curl http://localhost:8001/health # Translation service
curl http://localhost:8002/health # LLM service
curl http://localhost:8003/health # Anki service
curl http://localhost:8004/health # Audio service
curl http://localhost:15672 # RabbitMQ management UI (guest/guest)Common Makefile targets:
make up-dev # start everything
make down-dev # stop everything
make logs-odoo # tail Odoo logs
make logs-translation # tail translation service logs
make up-llm-no-cache # rebuild LLM image (after model change)
make load-backup FILE=x.dump # restore PostgreSQL from backup
make ps-dev # list all running dev containersUpdate a single module:
docker exec odoo odoo --config /etc/odoo/odoo.conf \
-d lexora --update language_portal --stop-after-init --no-http
docker restart odoo # reload routes into running workers# Install all dev tools
make install-dev # pip install -r requirements/dev-requirements.txt
# Lint (ruff) β checks all service files and Odoo addons
make lint
# Format (ruff format)
make fmt # apply formatting
make fmt-check # check only (used by CI)
# Type check (mypy β FastAPI services only)
make typecheck
# Security scan (bandit)
make security
# Dependency audit (pip-audit per service)
make audit
# Run all checks in sequence (matches CI)
make check
# pre-commit hooks
make pre-commit-install # install hooks into .git/hooks/
make pre-commit-run # run against all files| Workflow | Trigger | Jobs |
|---|---|---|
lint.yml |
Push to main/feature branches, PRs | Ruff lint + format, Mypy, Bandit, Hadolint, XMLlint |
test.yml |
PRs to main, [test] commits |
FastAPI pytest matrix, Odoo module tests (postgres service) |
security.yml |
Push to main, PRs | pip-audit per service, TruffleHog secrets scan, Bandit SARIF |
docker-build.yml |
Changes to docker_compose/ or services/ | Build all 5 images with GHA cache |
pr-check.yml |
PR opened/edited/synchronized | Conventional Commits title, branch name convention |
Commit convention: type(scope): description
feat(M19): add idioms hub with phrasal verbs
fix(audio): handle edge-tts timeout on slow networks
docs(architecture): update database schema diagram
ci(lint): add DL3059 to hadolint ignore list
| Document | Purpose |
|---|---|
| docs/SPEC.md | Full product specification: domain model, features, privacy, PvP rules |
| docs/ARCHITECTURE.md | System design: services, event catalog, real-time PvP design |
| docs/PLAN.md | Milestone-by-milestone implementation plan (M0βM21) |
| docs/DECISIONS.md | Architecture decision records (ADR-001βADR-028) |
| CLAUDE.md | AI assistant context: build commands, key invariants, module order |
| docs/TASKS.md | Active task tracker β resume point for interrupted sessions |
| Milestone | Feature | Status |
|---|---|---|
| M0 | Infrastructure Foundation | β Complete |
| M1 | Core Module Scaffold + Auth | β Complete |
| M2 | Learning Entries + Dedup | β Complete |
| M3 | Translation Service (RabbitMQ) | β Complete |
| M4 | LLM Enrichment Service | β Complete |
| M4b | Real CPU-only LLM (Qwen2.5-1.5B) | β Complete |
| M4c | Translation / Enrichment split | β Complete |
| M5 | Anki Import (.apkg + .txt) | β Complete |
| M6 | Audio Recording + TTS | β Complete |
| M7 | Posts, Articles, Comments | β Complete |
| M8 | Chat & DMs | β Complete |
| M9 | SRS Core + Dashboards | β Complete |
| M10 | PvP Arena + XP System | β Complete |
| M11 | XP Shop | β Complete |
| M12 | Knowledge Hub | β Complete |
| M13 | PDF Export Suite | β Complete |
| M14 | Premium Visual Identity | β Complete |
| M15 | AI Translator Tool | β Complete |
| M16 | Legal Protection + Documentation | β Complete |
| M17 | AI Situational Roleplay | β Complete |
| M18 | Grammar Pro β Cloze Tests | β Complete |
| M18.5 | Header UI Redesign | π Planned |
| M19 | Natural Speech Hub (Idioms & Phrasal Verbs) | π Planned |
| M20 | Survival Phrasebook (Tourist Kits) | π Planned |
| M21 | Sentence Builder (Syntax Master) | π Planned |
Copyright (c) 2026 Yurii Dorosh & Avantgarde Systems. All rights reserved.
This software is proprietary. Unauthorized copying, modification, distribution, or use is strictly prohibited. See LICENSE for full terms.