Skip to content

lridalc/weather-analytics-dashboard

Repository files navigation

🌦 Weather Analytics Dashboard

Python 3.12+ FastAPI uv SQLite Architecture License

A production-style weather data service with intelligent caching, query history, and dual interfaces (REST API + CLI). Built to demonstrate clean architecture, async Python, and real-world backend patterns.

Important

🚧 PROJECT STATUS: Architecture and documentation completed. Development in progress β€” vertical slice (/weather/current endpoint) currently being implemented.

CI Pipeline

Note

OpenWeatherMap was initially considered as first weather provider, but Open-Meteo has been selected as the preferred provider to simplify the MVP, as it does not require an API key. See ADR-024.


🎯 Why This Project Exists

This is a portfolio project designed to demonstrate:

  • Clean Architecture (Ports & Adapters)
  • Async Python across the entire stack
  • Separation of concerns (API / domain / infrastructure)
  • External API integration with caching, retry logic, and fallback strategies
  • Professional engineering practices (ADRs, testing strategy, documentation)

πŸš€ Quick Start

git clone https://github.com/lridalc/weather-analytics-dashboard
cd weather-analytics-dashboard

# Install dependencies
make install-dev

# Configure environment
cp .env.example .env

# Run API
make dev

# Test endpoints
curl http://localhost:8000/health
curl "http://localhost:8000/weather/current?city=London"

# CLI usage
make cli now London

πŸ—ΊοΈ Development Roadmap

This project follows a vertical-slice, test-driven development approach, where each milestone delivers a fully working feature across all layers.

Note

For a more detailed insight, see development plan.


πŸ“¦ Version Milestones

Version Focus Status
0.1.0 Foundation (/health) βœ…
0.2.0 Current Weather API (/weather/current) 🚧
0.3.0 Forecast System (/weather/forecast) -
0.4.0 History Persistence (/weather/history) -
0.5.0 Retry & Resilience -
0.6.0 Caching Layer -
1.0.0 Production Release -

🧱 Development Phases

πŸš€ Foundation & Setup

  • Project scaffolding
  • Documentation structure (README, ADRs, scope)
  • Smoke tests (API + CLI)
  • CI/CD pipeline setup and automation
  • Settings & configuration system
  • Base FastAPI application bootstrap

Health Endpoint (Vertical Slice #0)

  • /health endpoint implementation
  • Health check tests
  • Minimal API wiring validation

Current Weather (Vertical Slice #1)

  • Domain models for weather
  • Use case: get current weather
  • Geocoding service (shared infrastructure)
  • Open-Meteo adapter integration
  • /weather/current endpoint with error mapping
  • CLI: weather now <city>
  • Full integration tests

Forecast (Vertical Slice #2)

  • Forecast domain model
  • Forecast use case implementation
  • Open-Meteo adapter extension for forecasts
  • /weather/forecast endpoint with error mapping
  • CLI: weather forecast <city> --days N

History System (Vertical Slice #3)

  • History domain contracts (port + models)
  • SQLite repository with aiosqlite
  • FIFO eviction (max 10 entries per city)
  • History retrieval service
  • Automatic query persistence on current
  • /weather/history endpoint with error mapping
  • CLI: weather history <city>

Resilience & Retry Logic

  • HTTP client with exponential backoff (tenacity)
  • Global rate limiting (quota protection)
  • Migration of OpenMeteoAtapter to retry-enabled client
  • Retry only on transient errors (5xx, timeouts)

Caching Layer

  • Weather cache decorator (5 min TTL)
  • FIFO eviction for weather cache
  • Geocoding cache (7 days TTL, internal to service)
  • Cache hit/miss integration tests
  • Dependency wiring for cached provider

🎯 Final Release (v1.0.0)

  • All endpoints complete
  • CLI feature parity with API
  • Full async execution (no blocking I/O)
  • Clean Architecture enforced across layers
  • High test coverage achieved
  • Production-ready documentation

✨ Features

🌍 REST API

Endpoint Description
/weather/current Current temperature
/weather/forecast Multi-day forecast
/weather/history Last 10 queries
/health Service health

βœ” Automatic OpenAPI docs at /docs


πŸ–₯ CLI Interface

weather now Madrid
weather forecast Paris --days 5
weather history Tokyo

Same business logic as API β†’ different interface.


⚑ Smart Caching

Two-layer cache system:

Layer TTL Purpose
Geocoding 7 days Location resolution
Weather 5 minutes Weather data

βœ” Reduces API calls significantly βœ” Improves response time (<200ms cached) βœ” FIFO eviction prevents memory leaks

Architectural note: These two caches operate at different layers:

  • Weather cache wraps the domain port (system-wide)
  • Geocoding cache is internal to the shared geocoding service (infrastructure detail)

From a user perspective, both contribute to performance optimization.


πŸ” Resilience

  • Automatic retry with exponential backoff on transient external API failures
  • Global rate limiting (55 req/min) to protect the external API quota
  • Graceful degradation when the provider is unavailable

πŸ“œ Query History

  • SQLite persistence
  • Last 10 queries per location
  • FIFO eviction
  • Survives restarts

πŸ”„ Async Throughout

  • FastAPI (ASGI)
  • HTTPX (non-blocking HTTP)
  • SQLAlchemy async + aiosqlite

βœ” No blocking I/O βœ” Efficient concurrent handling


πŸ— Architecture

This project follows Clean Architecture + Ports & Adapters:

Presentation (API / CLI)
        ↓
Application (Use Cases)
        ↓
Domain (Business Logic)
        ↑
Infrastructure (DB, Cache, External APIs)

Key Patterns

  • Provider Pattern (Port + Adapter) β†’ external weather services abstraction
  • Repository Pattern β†’ database abstraction
  • Decorator Pattern β†’ caching layer
  • Service Layer β†’ use case orchestration
  • Dependency Injection β†’ testability

πŸ“š Documentation:


🌐 External API Integration

Weather data retrieval is provider-driven:

  • The domain interacts with a single abstraction: WeatherProvider
  • Each provider implementation decides how to resolve a location

Example: Open-Meteo (MVP)

  1. Resolve location β†’ coordinates (via shared geocoding service)
  2. Fetch weather data using coordinates

This logic is fully encapsulated inside the provider adapter, keeping the domain and application layers independent of provider-specific requirements.

βœ” No geocoding concepts leak into the domain βœ” Shared geocoding service prevents logic duplication across providers βœ” Easy to swap providers without changing business logic


πŸ“ Project Structure

The current structure is the foundational layer. The system will evolve incrementally following the design defined in ADR-023.

.
β”œβ”€β”€ docs/                                  # Architecture, ADRs, and development planning
β”‚   β”œβ”€β”€ architecture.md
β”‚   β”œβ”€β”€ decisions.md
β”‚   β”œβ”€β”€ development-plan.md
β”‚   └── project-scope.md
β”‚
β”œβ”€β”€ src/weather_analytics_dashboard/
β”‚   β”œβ”€β”€ application/                       # Use cases (business workflows)
β”‚   β”‚   └── services/
β”‚   β”‚       └── get_current_weather.py
β”‚   β”œβ”€β”€ config/                            # Configuration, settings, constants, and exceptions
β”‚   β”‚   β”œβ”€β”€ constants.py
β”‚   β”‚   β”œβ”€β”€ exceptions.py
β”‚   β”‚   β”œβ”€β”€ logging.py
β”‚   β”‚   └── settings.py
β”‚   β”œβ”€β”€ domain/                            # Core business logic and models
β”‚   β”‚   β”œβ”€β”€ exceptions.py
β”‚   β”‚   β”œβ”€β”€ models.py
β”‚   β”‚   └── ports.py
β”‚   β”œβ”€β”€ infrastructure/                    # External systems (DB, APIs, cache)
β”‚   β”œβ”€β”€ presentation/                      # Interfaces (API + CLI)
β”‚   β”‚   β”œβ”€β”€ api/
β”‚   β”‚   β”‚   β”œβ”€β”€ app.py
β”‚   β”‚   β”‚   β”œβ”€β”€ dependencies.py
β”‚   β”‚   β”‚   β”œβ”€β”€ routes/
β”‚   β”‚   β”‚   β”‚   └── health.py
β”‚   β”‚   β”‚   └── schemas/
β”‚   β”‚   β”‚       └── health.py
β”‚   β”‚   └── cli/
β”‚   β”‚       └── main.py
β”‚   β”œβ”€β”€ bootstrap.py                       # Dependency injection and app initialization
β”‚   └── main.py                            # Application entry point
β”‚
β”œβ”€β”€ tests/
β”‚   β”œβ”€β”€ integration/                       # Integration tests (API, DB)
β”‚   β”‚   β”œβ”€β”€ bootstrap/
β”‚   β”‚   β”œβ”€β”€ config/
β”‚   β”‚   β”œβ”€β”€ presentation/
β”‚   β”‚   β”‚   β”œβ”€β”€ api/
β”‚   β”‚   β”‚   └── cli/
β”‚   β”‚   └── test_00_smoke.py
β”‚   β”œβ”€β”€ unit/                              # Unit tests (domain & services)
β”‚   β”‚   └── config/
β”‚   β”‚       └── test_settings.py
β”‚   └── conftest.py                        # Pytest fixtures and configuration
β”‚
β”œβ”€β”€ .github/                               # CI/CD workflows, actions, and PR templates
β”‚   β”œβ”€β”€ actions/
β”‚   β”œβ”€β”€ workflows/
β”‚   └── pull_request_template.md
β”‚
β”œβ”€β”€ Makefile                               # Development commands
β”œβ”€β”€ pyproject.toml                         # Project configuration and dependencies
└── README.md

πŸ“‘ Example API Responses

βœ… Current Weather

GET /weather/current?city=London
{
  "city": "London",
  "temperature": 18.5,
  "timestamp": "2026-04-08T12:00:00Z"
}

πŸ“… Forecast

GET /weather/forecast?city=London&days=3
{
  "city": "London",
  "forecast": [
    {
      "date": "2026-04-09",
      "min_temp": 12.1,
      "max_temp": 19.3,
      "avg_temp": 15.7
    }
  ]
}

πŸ•˜ Query History

GET /weather/history?city=London
{
  "city": "London",
  "history": [
    {
      "timestamp": "2026-04-08T12:00:00Z",
      "temperature": 18.5
    }
  ]
}

❗ Error Response

{
  "error": {
    "code": "CITY_NOT_FOUND",
    "message": "Location could not be resolved"
  }
}

πŸ§ͺ Testing Strategy

Type Scope
Unit Domain + services
Integration API + DB
Mocks External providers
make test

βš™οΈ Configuration

Variable Required Default
RATE_LIMIT_RPM ❌ 250
CACHE_TTL_WEATHER ❌ 300
CACHE_TTL_GEOCODING ❌ 604800
CACHE_MAX_SIZE ❌ 100
REQUEST_TIMEOUT_SECONDS ❌ 10
RETRY_MAX_ATTEMPTS ❌ 3
RETRY_INITIAL_WAIT_SECONDS ❌ 1
ENVIRONMENT ❌ DEV
LOG_LEVEL ❌ INFO

πŸ“Š Performance

Metric Expected
Cached response <200ms
Cache hit rate ~60–90%
Memory usage <50MB

πŸ› οΈ Development Commands

This project uses a Makefile to standardize development workflows and ensure consistency across environments.

Tip

Running make without arguments shows all availble commands (equivalent to make help).
πŸ’‘ All commands use uv run internally, so you don’t need to manually activate a virtual environment.


πŸ“¦ Installation

make all          # Install everything necessary for development (install-dev + pre-commit)
make install      # Install production dependencies
make install-dev  # Install all dependencies including development tools

πŸš€ Running the application

make dev  # Run API with hot reload (development)
make run  # Run API without reload (production-like)
make cli  # Run CLI tool

πŸ§ͺ Testing

make test              # Run all tests
make test-smoke        # Run smoke tests
make test-bootstrap    # Run bootstrap tests
make test-unit         # Run unit tests
make test-integration  # Run integration tests
make test-cov          # Run tests with coverage report

All tests are run with verbose output.


🎨 Code Quality

make lint         # Run ruff linter
make format       # Auto-format code
make format-diff  # Show formatting differences without applying changes
make format-check # Check formatting without modifying files
make type-check   # Run mypy static type checking

πŸ”§ Git Hooks

make pre-commit                    # Install pre-commit hooks
make pre-commit-force              # Force reinstall pre-commit hooks
make pre-commit-all                # Run all hooks on all files
make pre-commit-run HOOK=ruff      # Run specific hook
make pre-commit-update             # Update hooks to latest versions
make pre-commit-uninstall          # Uninstall pre-commit hooks

βœ… Full project checks

make check

Runs all quality checks:

  • Linting (ruff)
  • Formatting validation (ruff)
  • Tests (pytest)
  • Type checking (mypy)

🧹 Cleanup

make clean

Removes cache files and temporary artifacts:

  • __pycache__
  • .pyc
  • .mypy_cache
  • .pytest_cache
  • .ruff_cache

🚒 Release

make version                      # Show current version
make release                      # Show release process steps
make release VERSION=0.2.0        # Execute release (creates tag, pushes, syncs branches)

🚦 CI Validation

make ci                           # Run complete CI pipeline locally (same as GitHub Actions)

πŸ“‹ Help

make help

Displays all available commands (default).


🚒 Deployment

Local

gunicorn weather_analytics_dashboard.main:app \
  --workers 4 \
  --worker-class uvicorn.workers.UvicornWorker

Planned

  • Redis cache
  • Docker support
  • Cloud deployment (Railway / Render)

πŸ“ˆ Future Roadmap

  • Metrics endpoint (Prometheus)
  • Redis cache
  • PostgreSQL support
  • Docker

πŸ“ License

MIT License


πŸ‘€ Author

lridalc - https://github.com/lridalc

Project Link - https://github.com/lridalc/weather-analytics-dashboard


πŸ’‘ What This Project Demonstrates

This project is intentionally designed to reflect real-world backend engineering, including:

  • Clear architectural boundaries
  • Explicit trade-offs (documented via ADRs)
  • Provider abstraction without leaking infrastructure details
  • Resilience patterns (retry with exponential backoff)
  • Async-first design
  • Testable, modular components

This project was built to practice designing a production-ready backend system from scratch, focusing on maintainability, scalability, and real-world trade-offs.

It is not just a weather API wrapperβ€”it is a system design exercise implemented in code.

About

Weather Analytics Dashboard with REST API and CLI

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors