G
YouTube TikTok Games
Skip to content

About

A Django Pokédex with real-time PokéAPI data, an LLM Q&A assistant grounded with RAG (Qdrant + local embeddings), MySQL, Redis caching, Docker Compose, and a fully tested backend (pytest, 100% coverage).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Smart Pokédex

A visual web application built with Django, HTML and CSS that displays Pokémon information via the PokéAPI and lets the user interact with an LLM through OpenRouter to ask questions about the selected Pokémon. Answers are grounded with Retrieval-Augmented Generation (RAG): Pokémon lore is embedded locally and stored in a Qdrant vector database, and the most relevant lore is retrieved and injected into the prompt for every question.

Home page


Table of Contents


Overview

The Smart Pokédex is made up of two main pages:

  • Pokédex (/home/) — a visual list of Pokémon with sprite, types and attribute bars, consuming real-time data from the PokéAPI.
  • Detail (/pokemon/<id>/) — a full profile of the selected Pokémon, with a query area where the user can ask an LLM questions and see the model's generated answer.

Analysis page


Features

  • List of Pokémon with sprite, number, types and mini HP/ATK bars
  • Detail page with a full profile: height, weight, ability, base experience and bars for every stat
  • Query area to send questions to an LLM via OpenRouter
  • Answers are grounded with RAG: each question retrieves the most relevant Pokémon lore from a Qdrant vector database and injects it into the prompt
  • The retrieved lore (Pokémon name + similarity score) is shown alongside the model's answer for transparency
  • The model's answer is displayed on the same page
  • Visual identity inspired by the classic Pokédex, with a CRT terminal aesthetic

Tech Stack

Technology Role
Python 3.x Main language
Django Web framework (views, urls, templates, sessions)
HTML + CSS Interface and visual identity
PokéAPI Source of Pokémon data
OpenRouter API Access to the LLM that answers questions
Python Requests HTTP requests to the external APIs
Django Sessions Passing data between views via POST/redirect/GET
MySQL Relational database (via PyMySQL)
Redis Caching layer for PokéAPI responses
sentence-transformers Local embedding generation (all-MiniLM-L6-v2), no API cost
Qdrant Vector database storing Pokémon lore embeddings for RAG retrieval
Docker + Docker Compose Containerized app, database, cache and vector store for a one-command setup
pytest + pytest-django Automated test suite

Technical Decisions

Split into Django apps

The project is split into apps with distinct responsibilities, following Django's modularization convention. Each app has its own views.py and urls.py, and the main urls.py delegates routes to each app via include().

Shared PokéAPI service layer

Fetching and parsing PokéAPI data lives in a single module (pokedex/services/pokeapi.py), reused by both the home and pokemon_detail apps, instead of being duplicated per view.

API calls happen on the server

Calls to the PokéAPI and OpenRouter are made server-side (in Django views), not in the browser. This avoids exposing the OpenRouter API key on the frontend and centralizes error handling.

POST → Redirect → GET pattern

The model query form follows the PRG pattern (Post/Redirect/Get): the POST is handled by the pokemon_query view, which saves the answer in the Django session and redirects to pokemon_detail via GET. This avoids resubmitting the form on page reload.

Django Sessions for passing data

The LLM's answer is temporarily stored in the Django session and consumed with .pop() in the detail view, guaranteeing it's shown only once and cleared automatically afterwards.

Templates without external JavaScript

The whole interface is built with plain HTML and CSS, with no frontend framework. The only JavaScript present is inline and minimal: a character counter for the textarea and visual feedback on the submit button.

Configuration via environment variables

Secrets (Django SECRET_KEY, OPENROUTER_API_KEY) and environment-dependent settings (DEBUG, ALLOWED_HOSTS, database credentials) are read from a local .env file via python-dotenv, never hardcoded in source.

MySQL via PyMySQL

The project uses MySQL as its relational database, connected through PyMySQL — a pure-Python driver. Unlike mysqlclient (the more common choice), it needs no native build tools to install, so it works the same way on every OS and inside Docker without extra system packages. Database connection settings (DB_NAME, DB_USER, DB_PASSWORD, DB_HOST, DB_PORT) come from the .env file.

Redis cache for PokéAPI responses

The home page originally fetched all 151 Pokémon from the PokéAPI sequentially, one HTTP request at a time, on every single page load — around 18 seconds per load in testing. pokedex/services/pokeapi.py now checks Redis (via Django's built-in cache framework, django.core.cache.backends.redis.RedisCache) before making a request, and caches the parsed result for 24 hours (a Pokémon's base stats/types/sprite essentially never change). After the first load, subsequent loads read entirely from Redis — down to tens of milliseconds. Redis connection settings (REDIS_HOST, REDIS_PORT, REDIS_DB) come from the .env file.

Containerized with Docker Compose

Four services run together: web (this Django app), db (MySQL), redis (Redis) and qdrant (Qdrant). db and redis don't publish ports to the host — only web and qdrant do — qdrant publishes 6333/6334 so its dashboard (http://localhost:6333/dashboard) is browsable outside Docker. web waits for db, redis and qdrant to report healthy (via Compose's depends_on: condition: service_healthy) before starting, and entrypoint.sh runs manage.py migrate automatically on every container start, so the schema is always up to date. Host/port values for DB_HOST/REDIS_HOST/QDRANT_HOST are overridden directly in docker-compose.yml (to the service names db/redis/qdrant) rather than taken from .env, since .env's 127.0.0.1 only makes sense for running the app outside Docker.

RAG: local embeddings + Qdrant, grounding the LLM prompt

The LLM query feature is extended with Retrieval-Augmented Generation instead of relying on the model's own (unreliable, unverifiable) knowledge of Pokémon lore. A one-time management command, python manage.py build_rag_index, fetches species flavor text for Pokémon 1–151 from PokéAPI (pokedex/services/pokeapi.py:fetch_pokemon_species), builds one text document per Pokémon (pokemon_rag/documents.py), embeds each document locally with sentence-transformers (all-MiniLM-L6-v2, 384 dimensions, CPU-only, no API cost), and upserts the vectors into a Qdrant collection (pokemon_rag/services/vector_store.py) keyed by Pokémon id — safe to rerun at any time.

At query time (pokemon_rag/retrieval.py), the user's question is combined with the current Pokémon's name ("{pokemon_name}: {question}") before being embedded and searched — this mirrors how the LLM prompt itself is phrased, so the shared name token biases genuine top-k semantic search toward the right Pokémon's document without a hard pokemon_id filter, which would defeat the point of having a vector database. The retrieved snippets are injected into the OpenRouter prompt (pokemon_query/openrouter.py) as reference context, and also shown in the UI under the model's answer for transparency into what the RAG step retrieved.

Known limitation: the MVP corpus is flavor-text only (no moves/type-matchup data), so a question like "why is it strong against water types" will correctly retrieve that Pokémon's document, but the document won't contain an answer — this is a corpus-scope limitation, not a retrieval bug, and would be addressed by extending the corpus to moves/abilities.

Tests run against SQLite and a local-memory cache, not the real services

pokedex/settings_test.py swaps DATABASES to an in-memory SQLite database and CACHES to Django's LocMemCache, used only when running pytest (wired up in pytest.ini). Since the app has no MySQL-specific SQL and nothing that depends on Redis's actual behavior, this keeps the test suite fast and independent of Docker/MySQL/Redis being up — important both for a quick local feedback loop and for CI. Every PokéAPI and OpenRouter call is mocked (unittest.mock.patch) rather than hitting the real network, so tests can't fail because of an outage or rate limit on either external service.


Project Structure

Smart-Pokedex/
│
├── pokedex/                    # Global project configuration
│   ├── settings.py
│   ├── settings_test.py        # Settings used only by pytest (SQLite + local-memory cache)
│   ├── urls.py                 # Main routing
│   ├── wsgi.py / asgi.py
│   └── services/
│       ├── pokeapi.py          # Shared PokéAPI client
│       └── test_pokeapi.py
│
├── home/                       # Pokédex list app
│   ├── views.py
│   ├── test_views.py
│   └── templates/
│       └── pokedex.html
│
├── pokemon_detail/             # Detail app
│   ├── views.py
│   ├── test_views.py
│   └── templates/
│       └── pokemon_detail.html
│
├── pokemon_query/              # LLM query app
│   ├── views.py
│   ├── test_views.py
│   ├── openrouter.py           # OpenRouter API integration
│   └── test_openrouter.py
│
├── pokemon_rag/                 # RAG app: embeddings, vector store, retrieval
│   ├── documents.py              # Builds one text document per Pokémon
│   ├── test_documents.py
│   ├── retrieval.py               # Query-time embed + search orchestration
│   ├── test_retrieval.py
│   ├── services/
│   │   ├── embeddings.py           # Local sentence-transformers client
│   │   ├── test_embeddings.py
│   │   ├── vector_store.py         # Qdrant client
│   │   └── test_vector_store.py
│   └── management/commands/
│       ├── build_rag_index.py      # Builds/rebuilds the Qdrant corpus
│       └── test_build_rag_index.py
│
├── templates/
│   └── static/
│       └── css/
│           ├── pokedex.css
│           └── pokemon_detail.css
│
├── .env                    # Environment variables (not versioned)
├── .env.example            # Template for .env
├── Dockerfile              # Image for the web service
├── entrypoint.sh           # Runs migrations, then starts the app
├── docker-compose.yml      # Wires up web + MySQL + Redis
├── pytest.ini              # pytest / pytest-django configuration
├── requirements.txt
├── requirements-dev.txt    # requirements.txt + pytest tooling
└── manage.py

Prerequisites

  • Git
  • An account and API key from OpenRouter
  • Internet connection (to consume the PokéAPI and OpenRouter)
  • To run with Docker (recommended): Docker and Docker Compose
  • To run without Docker: Python 3.10+, pip, a running MySQL server, a running Redis server and a running Qdrant instance

Running with Docker

This is the fastest way to get everything running — Django, MySQL, Redis and Qdrant — with a single command, no local Python or database setup required.

1. Clone the repository

git clone https://github.com/GMCavalheri/Smart-Pokedex
cd Smart-Pokedex

2. Configure environment variables

cp .env.example .env

Edit .env and fill in SECRET_KEY, OPENROUTER_API_KEY, and set your own DB_PASSWORD/DB_ROOT_PASSWORD (see Environment Variables). You can leave DB_HOST/REDIS_HOST as-is — docker-compose.yml overrides them automatically for the containers.

3. Build and start everything

docker compose up --build

This builds the app image, starts db (MySQL), redis and qdrant, waits for all three to be healthy, then starts web — which runs migrations automatically before serving.

Open in your browser: http://127.0.0.1:8000/home/

4. Build the RAG corpus (one-time step, run whenever you want to rebuild it)

docker compose exec web python manage.py build_rag_index

This fetches flavor text for Pokémon 1–151 from PokéAPI, embeds it locally, and indexes it into Qdrant — takes a minute or two. You can browse the resulting collection at http://localhost:6333/dashboard#/collections/pokemon_docs.

5. Stop everything

docker compose down

Add -v (docker compose down -v) if you also want to wipe the MySQL data volume.

Made a code change? Rebuild the image so it's picked up:

docker compose up --build

Installation and Setup (without Docker)

1. Clone the repository

git clone https://github.com/GMCavalheri/Smart-Pokedex
cd Smart-Pokedex

2. Create and activate a virtual environment

python -m venv venv

# Linux/macOS
source venv/bin/activate

# Windows
venv\Scripts\activate

3. Install dependencies

pip install -r requirements.txt

4. Configure environment variables

Create a .env file in the project root (see the Environment Variables section). Make sure DB_* points to a running MySQL server with a matching database and user already created, REDIS_* points to a running Redis server, and QDRANT_* points to a running Qdrant instance.

5. Run migrations

python manage.py migrate

6. Build the RAG corpus (one-time step, run whenever you want to rebuild it)

python manage.py build_rag_index

Running Locally (without Docker)

python manage.py runserver

Open in your browser: http://127.0.0.1:8000/home/


Running Tests

The test suite uses pytest + pytest-django, and needs no external services — no MySQL, no Redis, no Qdrant, no Docker. Tests run against an in-memory SQLite database and Django's local-memory cache instead (pokedex/settings_test.py), and every call to the PokéAPI, OpenRouter, the embedding model or Qdrant is mocked, so the suite is fast and fully offline.

1. Install the dev dependencies (adds pytest, pytest-django and pytest-cov on top of requirements.txt)

pip install -r requirements-dev.txt

2. Run the suite

pytest

3. (Optional) Run with a coverage report

pytest --cov=home --cov=pokemon_detail --cov=pokemon_query --cov=pokemon_rag --cov=pokedex/services --cov-report=term-missing

Test files live next to the code they cover, named test_*.py:

  • pokedex/services/test_pokeapi.py — parsing logic, stat percentage math, species flavor-text cleaning, and that a second call for the same Pokémon is served from cache instead of hitting the network again.
  • pokemon_query/test_openrouter.py — prompt building (with and without retrieved context) and the OpenRouter request/response handling.
  • pokemon_rag/test_documents.py, pokemon_rag/test_retrieval.py — corpus document text building, and query-time embed → search → payload-mapping orchestration.
  • pokemon_rag/services/test_embeddings.py, pokemon_rag/services/test_vector_store.py — the sentence-transformers and Qdrant clients, both fully mocked (no real model load, no real Qdrant).
  • pokemon_rag/management/commands/test_build_rag_index.py — the corpus-building management command.
  • home/test_views.py, pokemon_detail/test_views.py, pokemon_query/test_views.py — each view's behavior through Django's test client, including the session-based POST → Redirect → GET flow, the RAG retrieval step, and the error path.

Environment Variables

Copy .env.example to .env and fill in your own values:

cp .env.example .env
SECRET_KEY=your-django-secret-key-here
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1
OPENROUTER_API_KEY=your-openrouter-api-key-here

# MySQL
DB_NAME=pokedex
DB_USER=pokedex_user
DB_PASSWORD=your-db-password
DB_ROOT_PASSWORD=your-db-root-password
DB_HOST=localhost
DB_PORT=3306

# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_DB=0

# Qdrant / RAG
QDRANT_HOST=localhost
QDRANT_PORT=6333
QDRANT_COLLECTION_NAME=pokemon_docs
RAG_EMBEDDING_MODEL=all-MiniLM-L6-v2
RAG_TOP_K=3

Warning: never commit the .env file. It is already listed in .gitignore.

Note: DB_ROOT_PASSWORD is only used by the MySQL container itself (its root superuser account, set up in docker-compose.yml) — Django never uses it. DB_HOST/DB_PORT/REDIS_HOST/REDIS_PORT/QDRANT_HOST here are for running the app without Docker; when running via docker compose, those five are overridden automatically (see Running with Docker).

To generate a Django SECRET_KEY:

python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"

About

A Django Pokédex with real-time PokéAPI data, an LLM Q&A assistant grounded with RAG (Qdrant + local embeddings), MySQL, Redis caching, Docker Compose, and a fully tested backend (pytest, 100% coverage).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages