pokedo

PokeDo Architecture

Table of Contents


Overview

PokeDo is a gamified task manager that combines productivity tracking with Pokemon collection mechanics. The application follows a layered architecture pattern with clear separation between:

Technology Stack:


Project Structure

pokedo/
├── __init__.py           # Package init with version
├── __main__.py           # Entry point for `python -m pokedo`
├── cli/                  # Presentation Layer
│   ├── app.py            # Main Typer application
│   └── commands/
│       ├── tasks.py      # Task commands
│       ├── pokemon.py    # Pokemon commands
│       ├── wellbeing.py  # Wellbeing commands
│       ├── stats.py      # Stats/profile commands
│       ├── profile.py    # Profile management commands
│       ├── battle.py     # PvP battle CLI commands
│       └── leaderboard.py # Leaderboard CLI commands
│   └── ui/
│       ├── displays.py   # Display helpers (Rich tables, panels)
│       └── menus.py      # Interactive menus
├── tui/                  # TUI Layer (Textual)
│   ├── app.py            # Textual dashboard app
│   ├── screens/          # Screen classes (tasks, etc.)
│   ├── widgets/          # Reusable UI components
│   └── styles/           # Textual CSS styling
├── core/                 # Business Logic Layer
│   ├── auth.py           # Authentication logic (Bcrypt/JWT)
│   ├── battle.py         # PvP battle state machine and turn resolution
│   ├── moves.py          # Move model, 18-type chart, damage formula, natures
│   ├── task.py           # Task model and enums
│   ├── trainer.py        # Trainer model, progression, and PvP stats
│   ├── pokemon.py        # Pokemon and Pokedex models (EV/IV, battle conversion)
│   ├── rewards.py        # Encounter and reward system
│   └── wellbeing.py      # Wellbeing tracking models
├── data/                 # Data Access Layer
│   ├── database.py       # SQLite operations (local)
│   ├── pokeapi.py        # PokeAPI client (async, cached)
│   ├── server_models.py  # Postgres models (ServerUser, BattleRecord, LeaderboardEntry)
│   └── sync.py           # Sync client and change queue
├── server.py             # FastAPI server (auth, battles, leaderboard, sync)
└── utils/                # Utilities
    ├── config.py         # Configuration management
    ├── helpers.py        # Helper functions
    └── sprites.py        # Terminal sprite rendering

Layered Architecture

Presentation Layer (cli/, tui/)

The CLI layer handles all user interaction through the Typer framework.

app.py - Main application entry point

commands/ - Command implementations

ui/ - Display components

TUI Layer (tui/)

Server Layer (server.py)

Handles centralized multiplayer operations, authentication, and synchronization.

server.py - FastAPI Application

data/server_models.py - Server Database Models

Business Logic Layer (core/)

The core layer contains domain models and game logic.

auth.py - Authentication

battle.py - PvP Battle System

class BattleState(BaseModel):
    # Full battle state: teams, active Pokemon, turn history,
    # pending actions, status, winner tracking

class BattleEngine:
    @staticmethod
    def resolve_turn(state: BattleState) -> list[TurnEvent]:
        # Resolves forfeits, switches, then attacks (priority-sorted)
        # Applies damage, status effects, end-of-turn damage
        # Handles mutual KO (draw support), auto-switching

def calculate_elo_change(winner_elo, loser_elo, k=32) -> tuple[int, int]
def compute_rank(elo: int) -> str  # ELO to rank name

moves.py - Move System

class Move(BaseModel):
    # name, type, power, accuracy, pp, category (physical/special/status)
    # priority, drain_percent, recoil_percent, status_effect, secondary_effect

TYPE_CHART: dict[str, dict[str, float]]  # 18x18 effectiveness matrix
NATURE_MODIFIERS: dict[str, dict[str, float]]  # 25 natures

def calculate_damage(...)  # Gen V+ damage formula
def generate_default_moveset(pokemon, level) -> list[Move]

task.py - Task Management

class TaskCategory(Enum):
    WORK, EXERCISE, LEARNING, PERSONAL, HEALTH, CREATIVE

class TaskDifficulty(Enum):
    EASY, MEDIUM, HARD, EPIC

class Task(BaseModel):
    # Properties: is_overdue, xp_reward, stat_affinity, ev_yield

pokemon.py - Pokemon System

class PokemonRarity(Enum):
    COMMON, UNCOMMON, RARE, EPIC, LEGENDARY, MYTHICAL

class Pokemon(BaseModel):
    # Evolution tracking, XP system, happiness
    # EV/IV stats (new)

trainer.py - Player Progression

class TrainerClass(str, Enum):
    ACE_TRAINER, HIKER, SCIENTIST, ...  # Specialization classes

class Trainer(BaseModel):
    # Level calculation, XP management
    # Streak tracking, badge system
    # Inventory management
    # PvP stats: battle_wins, battle_losses, battle_draws
    # elo_rating (starting 1000), pvp_rank (derived from ELO)
    # record_battle(won, elo_delta) -- updates stats and rank

rewards.py - Reward System

class RewardSystem:
    # Encounter probability calculation
    # Catch rate computation
    # Shiny rate with streak bonuses
    # Streak milestone rewards

wellbeing.py - Wellbeing Tracking

class MoodEntry, ExerciseEntry, SleepEntry,
      HydrationEntry, MeditationEntry, JournalEntry

class DailyWellbeing:
    # Aggregate daily wellbeing data
    # Type affinity bonuses

Data Access Layer (data/)

database.py - SQLite Operations

pokeapi.py - External API Client

sync.py - Synchronization Client


Synchronization Layer

This layer enables local-first data handling with cloud synchronization.

Concept:

Change Entity:

class Change(SQLModel):
    id: str (UUID)
    entity_id: str
    entity_type: str (e.g., "task", "pokemon")
    action: str (CREATE, UPDATE, DELETE)
    payload: JSON
    timestamp: datetime
    synced: bool

Sync Process:

  1. queue_change(): Called by CRUD functions in database.py whenever data modifies.
  2. push_changes(): Reads unsynced records, sends to POST /sync.
  3. On 200 OK, marks records as synced=True.

Data Flow

Task Completion Flow

User Input -> CLI Command -> Validate Input -> Database Update -> Queue Change
                                                    |
                                                    v
Display Result <- UI Components <- Reward System <- Calculate Rewards
                                         |
                                         v
                                   Pokemon Encounter
                                         |
                                         v
                                   Catch Attempt
                                         |
                                         v
                                   Update Database -> Queue Change

Pokemon Encounter Flow

1. Task completed
2. Calculate encounter probability
   - Base rate (70%)
   - Difficulty bonus (+5-15%)
   - Streak bonus (+1%/day, max 10%)
3. If encounter:
   a. Determine rarity (weighted by difficulty)
   b. Select Pokemon from rarity pool
   c. Check shiny status (1% + streak bonus)
   d. Calculate catch rate
   e. Apply ball modifiers
   f. Determine success/failure
4. Update database (trainer stats, Pokemon, Pokedex)
5. Display result

Database Schema

Entity Relationship Diagram

┌─────────────┐       ┌─────────────┐
│   trainer   │       │   tasks     │
├─────────────┤       ├─────────────┤
│ id (PK)     │       │ id (PK)     │
│             │       │ trainer_id  │
│ name        │       │ title       │
│ total_xp    │       │ category    │
│ badges      │       │ difficulty  │
│ inventory   │       │ due_date    │
│ streaks     │       │ completed   │
└─────────────┘       └─────────────┘

┌─────────────┐       ┌─────────────┐
│   pokemon   │──────→│   pokedex   │
├─────────────┤       ├─────────────┤
│ id (PK)     │       │ pokedex_id  │
│ trainer_id  │       │ trainer_id  │
│ pokedex_id  │       │ name        │
│ nickname    │       │ type1/type2 │
│ level       │       │ rarity      │
│ is_shiny    │       │ is_caught   │
│ is_active   │       │ times_caught│
└─────────────┘       └─────────────┘

┌──────────────────┐  ┌──────────────────┐
│  mood_entries    │  │ exercise_entries │
├──────────────────┤  ├──────────────────┤
│ id, trainer_id   │  │ id, trainer_id   │
│ date, mood       │  │ date, type       │
│ note, energy     │  │ duration, note   │
└──────────────────┘  └──────────────────┘

┌──────────────────┐  ┌──────────────────┐
│  sleep_entries   │  │hydration_entries │
├──────────────────┤  ├──────────────────┤
│ id, trainer_id   │  │ id, trainer_id   │
│ date, hours      │  │ date, glasses    │
│ quality, note    │  │ note             │
└──────────────────┘  └──────────────────┘

┌──────────────────┐  ┌──────────────────┐
│meditation_entries│  │ journal_entries  │
├──────────────────┤  ├──────────────────┤
│ id, trainer_id   │  │ id, trainer_id   │
│ date, minutes    │  │ date, content    │
│ note             │  │ gratitude_items  │
└──────────────────┘  └──────────────────┘

Server Database (PostgreSQL)

┌──────────────────┐  ┌──────────────────┐
│  server_users    │  │ battle_records   │
├──────────────────┤  ├──────────────────┤
│ id (PK, UUID)    │  │ id (PK, UUID)    │
│ username (unique)│  │ challenger_id FK │
│ hashed_password  │  │ opponent_id FK   │
│ elo_rating       │  │ format           │
│ battle_wins      │  │ status           │
│ battle_losses    │  │ winner_id        │
│ battle_draws     │  │ state_json       │
│ rank             │  │ turn_history     │
│ disabled         │  │ created/updated  │
│ created_at       │  │                  │
└──────────────────┘  └──────────────────┘

Table Definitions

tasks | Column | Type | Description | |——–|——|————-| | id | INTEGER PK | Auto-increment ID | | trainer_id | INTEGER FK | Owning trainer profile | | title | TEXT | Task title | | description | TEXT | Optional description | | category | TEXT | work/exercise/learning/personal/health/creative | | difficulty | TEXT | easy/medium/hard/epic | | priority | TEXT | low/medium/high/urgent | | created_at | TIMESTAMP | Creation time | | due_date | DATE | Optional due date | | completed_at | TIMESTAMP | Completion time | | is_completed | BOOLEAN | Completion status | | is_archived | BOOLEAN | Archive status | | recurrence | TEXT | daily/weekly/monthly/none | | parent_task_id | INTEGER FK | Parent for recurring tasks | | tags | TEXT | JSON array of tags |

pokemon | Column | Type | Description | |——–|——|————-| | id | INTEGER PK | Auto-increment ID | | trainer_id | INTEGER FK | Owning trainer profile | | pokedex_id | INTEGER | National Pokedex number | | name | TEXT | Species name | | nickname | TEXT | User-assigned nickname | | type1, type2 | TEXT | Pokemon types | | level | INTEGER | Current level (1-100) | | xp | INTEGER | Experience points | | happiness | INTEGER | Happiness value | | caught_at | TIMESTAMP | Catch time | | is_shiny | BOOLEAN | Shiny variant | | catch_location | TEXT | Where caught (task category) | | is_active | BOOLEAN | In active team | | is_favorite | BOOLEAN | Marked as favorite | | can_evolve | BOOLEAN | Evolution available | | evolution_id | INTEGER | Evolution target ID | | evolution_level | INTEGER | Level required to evolve | | sprite_url | TEXT | Remote sprite URL | | sprite_path | TEXT | Local cached sprite path |

pokedex | Column | Type | Description | |——–|——|————-| | trainer_id | INTEGER PK | Owning trainer profile | | pokedex_id | INTEGER PK | National Pokedex number | | name | TEXT | Species name | | type1, type2 | TEXT | Pokemon types | | is_seen | BOOLEAN | Encountered | | is_caught | BOOLEAN | Successfully caught | | times_caught | INTEGER | Total catch count | | first_caught_at | TIMESTAMP | First catch time | | shiny_caught | BOOLEAN | Shiny variant caught | | sprite_url | TEXT | Sprite URL | | rarity | TEXT | Rarity tier | | evolves_from | INTEGER | Pre-evolution ID | | evolves_to | TEXT | JSON array of evolution IDs |

trainer | Column | Type | Description | |——–|——|————-| | id | INTEGER PK | Trainer profile ID | | name | TEXT | Trainer name | | created_at | TIMESTAMP | Profile creation time | | total_xp | INTEGER | Accumulated XP | | tasks_completed | INTEGER | Total tasks done | | pokemon_caught | INTEGER | Total Pokemon caught | | pokemon_released | INTEGER | Total released | | evolutions_triggered | INTEGER | Total evolutions | | pokedex_seen | INTEGER | Unique species seen | | pokedex_caught | INTEGER | Unique species caught | | daily_streak_count | INTEGER | Current task streak | | daily_streak_best | INTEGER | Best task streak | | daily_streak_last_date | DATE | Last task completion | | wellbeing_streak_count | INTEGER | Current wellbeing streak | | wellbeing_streak_best | INTEGER | Best wellbeing streak | | wellbeing_streak_last_date | DATE | Last wellbeing log | | badges | TEXT | JSON array of badges | | inventory | TEXT | JSON object of items | | favorite_pokemon_id | INTEGER | Favorite Pokemon | | last_active_date | DATE | Last activity |


External Integrations

PokeAPI Integration

Base URL: https://pokeapi.co/api/v2

Endpoints Used:

Caching Strategy:

Rate Limiting:


Game Mechanics

XP and Leveling

Task XP Rewards: | Difficulty | XP | |————|—–| | Easy | 10 | | Medium | 25 | | Hard | 50 | | Epic | 100 |

Trainer Level Formula:

level = 1
while total_xp >= level * 100:
    total_xp -= level * 100
    level += 1

Pokemon Level:

Rarity System

Rarity Weights by Difficulty: | Rarity | Easy | Medium | Hard | Epic | |——–|——|——–|——|——| | Common | 70% | 50% | 30% | 15% | | Uncommon | 25% | 35% | 35% | 25% | | Rare | 4% | 10% | 20% | 25% | | Epic | 1% | 4% | 12% | 25% | | Legendary | 0% | 1% | 3% | 10% |

Pokemon Rarity Classification:

Catch Rate Formula

base_rate = {
    'common': 0.90,
    'uncommon': 0.75,
    'rare': 0.50,
    'epic': 0.30,
    'legendary': 0.15,
    'mythical': 0.05
}

# Modifiers
trainer_bonus = min(trainer_level * 0.02, 0.20)  # Max +20%
ball_bonus = {'pokeball': 0, 'great': 0.10, 'ultra': 0.20, 'master': 1.0}
sleep_bonus = get_sleep_modifier()  # +10% or -20%

final_rate = min(base_rate + trainer_bonus + ball_bonus + sleep_bonus, 1.0)

Shiny Rate Formula

base_shiny_rate = 0.01  # 1%
streak_bonus = streak_days * 0.005  # +0.5% per day
max_shiny_rate = 0.10  # 10% cap

shiny_rate = min(base_shiny_rate + streak_bonus, max_shiny_rate)

Type Affinities

Task categories influence Pokemon type encounter probabilities:

Category Boosted Types
Work Steel, Electric, Normal
Exercise Fighting, Fire, Rock
Learning Psychic, Ghost, Dark
Personal Normal, Fairy, Flying
Health Grass, Water, Poison
Creative Fairy, Dragon, Ice

Wellbeing actions also affect type encounters:


EV/IV Persistence & Affinity Backfill

Configuration

Default Paths

DATA_DIR = Path.home() / ".pokedo"
DB_PATH = DATA_DIR / "pokedo.db"
CACHE_DIR = DATA_DIR / "cache"
SPRITES_DIR = CACHE_DIR / "sprites"

Game Constants

# XP Values
TASK_XP = {'easy': 10, 'medium': 25, 'hard': 50, 'epic': 100}

# Encounter Rates
BASE_ENCOUNTER_RATE = 0.70
DIFFICULTY_BONUS = {'easy': 0.05, 'medium': 0.10, 'hard': 0.12, 'epic': 0.15}
STREAK_BONUS_PER_DAY = 0.01
MAX_STREAK_BONUS = 0.10

# Catch Rates
BASE_CATCH_RATE = 0.60
SHINY_RATE = 0.01
STREAK_SHINY_BONUS = 0.005

# Generation Ranges
GENERATION_RANGES = {
    1: (1, 151),    # Kanto
    2: (152, 251),  # Johto
    3: (252, 386),  # Hoenn
    4: (387, 493),  # Sinnoh
    5: (494, 649),  # Unova
    6: (650, 721),  # Kalos
    7: (722, 809),  # Alola
    8: (810, 905),  # Galar
    9: (906, 1025)  # Paldea
}

Streak Milestones

STREAK_REWARDS = {
    3: {'item': 'great_ball', 'quantity': 5},
    7: {'item': 'evolution_stone', 'quantity': 1},
    14: {'item': 'ultra_ball', 'quantity': 5},
    21: {'item': 'rare_candy', 'quantity': 3},
    30: {'item': 'master_ball', 'quantity': 1},
    50: {'item': 'legendary_ticket', 'quantity': 1},
    100: {'item': 'mythical_ticket', 'quantity': 1}
}

Design Decisions

Why SQLite?

Why Typer + Rich?

Why PokeAPI?

Why Local Caching?

Why Pydantic?


Future Considerations

Potential Enhancements

Scalability Notes