dbt uses env_var() in profiles.yml to read POSTGRES_PASSWORD, but subprocess.run() doesn't automatically load .env files. Added python-dotenv to load credentials before dbt runs. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Analytics Portfolio
Live Demo: leodata.science
A personal portfolio website showcasing data engineering and visualization capabilities, featuring an interactive Toronto Neighbourhood Dashboard.
Live Pages
| Route | Page | Description |
|---|---|---|
/ |
Home | Bio landing page |
/about |
About | Background and experience |
/projects |
Projects | Portfolio project showcase |
/resume |
Resume | Professional CV |
/contact |
Contact | Contact form |
/blog |
Blog | Technical articles |
/blog/{slug} |
Article | Individual blog posts |
/toronto |
Toronto Dashboard | Neighbourhood analysis (5 tabs) |
/toronto/methodology |
Methodology | Dashboard data sources and methods |
/health |
Health | API health check endpoint |
Toronto Neighbourhood Dashboard
An interactive choropleth dashboard analyzing Toronto's 158 official neighbourhoods across five dimensions:
- Overview: Composite livability scores, income vs safety scatter
- Housing: Affordability index, rent trends, dwelling types
- Safety: Crime rates, breakdowns by type, trend analysis
- Demographics: Income distribution, age pyramids, population density
- Amenities: Parks, schools, transit accessibility
Data Sources:
- City of Toronto Open Data Portal (neighbourhoods, census profiles, amenities)
- Toronto Police Service (crime statistics)
- CMHC Rental Market Survey (rental data by zone)
Architecture
flowchart LR
subgraph Sources
A1[City of Toronto API]
A2[Toronto Police API]
A3[CMHC Data]
end
subgraph ETL
B1[Parsers]
B2[Loaders]
end
subgraph Database
C1[(PostgreSQL/PostGIS)]
C2[dbt Models]
end
subgraph Application
D1[Dash App]
D2[Plotly Figures]
end
A1 & A2 & A3 --> B1 --> B2 --> C1 --> C2 --> D1 --> D2
Pipeline Stages:
- Sources: External APIs and data files (City of Toronto, Toronto Police, CMHC)
- ETL: Python parsers extract and validate data; loaders persist to database
- Database: PostgreSQL with PostGIS for geospatial; dbt transforms raw → staging → marts
- Application: Dash serves interactive dashboards with Plotly visualizations
For detailed database schema, see docs/DATABASE_SCHEMA.md.
Quick Start
# Clone and setup
git clone https://gitea.hotserv.cloud/lmiranda/personal-portfolio.git
cd personal-portfolio
# Install dependencies and configure environment
make setup
# Start database
make docker-up
# Initialize database schema
make db-init
# Run development server
make run
Visit http://localhost:8050 to view the portfolio.
Project Structure
portfolio_app/
├── app.py # Dash app factory
├── config.py # Pydantic settings
├── pages/
│ ├── home.py # Bio landing (/)
│ ├── about.py # About page
│ ├── contact.py # Contact form
│ ├── projects.py # Project showcase
│ ├── resume.py # Resume/CV
│ ├── blog/ # Blog system
│ │ ├── index.py # Article listing
│ │ └── article.py # Article renderer
│ └── toronto/ # Toronto dashboard
│ ├── dashboard.py # Main layout with tabs
│ ├── methodology.py # Data documentation
│ ├── tabs/ # Tab layouts (5)
│ └── callbacks/ # Interaction logic
├── components/ # Shared UI components
├── figures/ # Plotly figure factories
├── content/
│ └── blog/ # Markdown blog articles
├── toronto/ # Toronto data logic
│ ├── parsers/ # API data extraction
│ ├── loaders/ # Database operations
│ ├── schemas/ # Pydantic models
│ └── models/ # SQLAlchemy ORM
└── errors/ # Exception handling
dbt/
├── models/
│ ├── staging/ # 1:1 source tables
│ ├── intermediate/ # Business logic
│ └── marts/ # Analytical tables
notebooks/ # Data documentation (15 notebooks)
├── overview/ # Overview tab visualizations
├── housing/ # Housing tab visualizations
├── safety/ # Safety tab visualizations
├── demographics/ # Demographics tab visualizations
└── amenities/ # Amenities tab visualizations
docs/
├── PROJECT_REFERENCE.md # Architecture reference
├── CONTRIBUTING.md # Developer guide
└── project-lessons-learned/
Tech Stack
| Layer | Technology |
|---|---|
| Database | PostgreSQL 16 + PostGIS |
| Validation | Pydantic 2.x |
| ORM | SQLAlchemy 2.x |
| Transformation | dbt-postgres |
| Data Processing | Pandas, GeoPandas |
| Visualization | Dash + Plotly |
| UI Components | dash-mantine-components |
| Testing | pytest |
| Python | 3.11+ |
Development
make test # Run pytest
make lint # Run ruff linter
make format # Format code
make ci # Run all checks
make dbt-run # Run dbt models
make dbt-test # Run dbt tests
Environment Variables
Copy .env.example to .env and configure:
DATABASE_URL=postgresql://user:pass@localhost:5432/portfolio
POSTGRES_USER=portfolio
POSTGRES_PASSWORD=<secure>
POSTGRES_DB=portfolio
DASH_DEBUG=true
SECRET_KEY=<random>
Documentation
- For developers: See
docs/CONTRIBUTING.mdfor setup and contribution guidelines - For Claude Code: See
CLAUDE.mdfor AI assistant context - Architecture: See
docs/PROJECT_REFERENCE.mdfor technical details
License
MIT
Author
Leo Miranda
Languages
Python
82.6%
Jupyter Notebook
14.7%
Makefile
1.4%
Shell
0.7%
CSS
0.6%