feat: Sprint 6 polish - methodology, demo data, deployment prep
- Add policy event markers to time series charts - Create methodology page (/toronto/methodology) with data sources - Add demo data module for testing without full pipeline - Update README with project documentation - Add health check endpoint (/health) - Add database initialization script - Export new figure factory functions Closes #21 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
257
portfolio_app/toronto/demo_data.py
Normal file
257
portfolio_app/toronto/demo_data.py
Normal file
@@ -0,0 +1,257 @@
|
||||
"""Demo/sample data for testing the Toronto Housing Dashboard without full pipeline.
|
||||
|
||||
This module provides synthetic data for development and demonstration purposes.
|
||||
Replace with real data from the database in production.
|
||||
"""
|
||||
|
||||
from datetime import date
|
||||
from typing import Any
|
||||
|
||||
|
||||
def get_demo_districts() -> list[dict[str, Any]]:
|
||||
"""Return sample TRREB district data."""
|
||||
return [
|
||||
{"district_code": "W01", "district_name": "Long Branch", "area_type": "West"},
|
||||
{"district_code": "W02", "district_name": "Mimico", "area_type": "West"},
|
||||
{
|
||||
"district_code": "W03",
|
||||
"district_name": "Kingsway South",
|
||||
"area_type": "West",
|
||||
},
|
||||
{"district_code": "W04", "district_name": "Edenbridge", "area_type": "West"},
|
||||
{"district_code": "W05", "district_name": "Islington", "area_type": "West"},
|
||||
{"district_code": "W06", "district_name": "Rexdale", "area_type": "West"},
|
||||
{"district_code": "W07", "district_name": "Willowdale", "area_type": "West"},
|
||||
{"district_code": "W08", "district_name": "York", "area_type": "West"},
|
||||
{
|
||||
"district_code": "C01",
|
||||
"district_name": "Downtown Core",
|
||||
"area_type": "Central",
|
||||
},
|
||||
{"district_code": "C02", "district_name": "Annex", "area_type": "Central"},
|
||||
{
|
||||
"district_code": "C03",
|
||||
"district_name": "Forest Hill",
|
||||
"area_type": "Central",
|
||||
},
|
||||
{
|
||||
"district_code": "C04",
|
||||
"district_name": "Lawrence Park",
|
||||
"area_type": "Central",
|
||||
},
|
||||
{
|
||||
"district_code": "C06",
|
||||
"district_name": "Willowdale East",
|
||||
"area_type": "Central",
|
||||
},
|
||||
{"district_code": "C07", "district_name": "Thornhill", "area_type": "Central"},
|
||||
{"district_code": "C08", "district_name": "Waterfront", "area_type": "Central"},
|
||||
{"district_code": "E01", "district_name": "Leslieville", "area_type": "East"},
|
||||
{"district_code": "E02", "district_name": "The Beaches", "area_type": "East"},
|
||||
{"district_code": "E03", "district_name": "Danforth", "area_type": "East"},
|
||||
{"district_code": "E04", "district_name": "Birch Cliff", "area_type": "East"},
|
||||
{"district_code": "E05", "district_name": "Scarborough", "area_type": "East"},
|
||||
]
|
||||
|
||||
|
||||
def get_demo_purchase_data() -> list[dict[str, Any]]:
|
||||
"""Return sample purchase data for time series visualization."""
|
||||
import random
|
||||
|
||||
random.seed(42)
|
||||
data = []
|
||||
|
||||
base_prices = {
|
||||
"W01": 850000,
|
||||
"C01": 1200000,
|
||||
"E01": 950000,
|
||||
}
|
||||
|
||||
for year in [2024, 2025]:
|
||||
for month in range(1, 13):
|
||||
if year == 2025 and month > 12:
|
||||
break
|
||||
|
||||
for district, base_price in base_prices.items():
|
||||
# Add some randomness and trend
|
||||
trend = (year - 2024) * 12 + month
|
||||
price_variation = random.uniform(-0.05, 0.05)
|
||||
trend_factor = 1 + (trend * 0.002) # Slight upward trend
|
||||
|
||||
avg_price = int(base_price * trend_factor * (1 + price_variation))
|
||||
sales = random.randint(50, 200)
|
||||
|
||||
data.append(
|
||||
{
|
||||
"district_code": district,
|
||||
"full_date": date(year, month, 1),
|
||||
"year": year,
|
||||
"month": month,
|
||||
"avg_price": avg_price,
|
||||
"median_price": int(avg_price * 0.95),
|
||||
"sales_count": sales,
|
||||
"new_listings": int(sales * random.uniform(1.2, 1.8)),
|
||||
"active_listings": int(sales * random.uniform(2.0, 3.5)),
|
||||
"days_on_market": random.randint(15, 45),
|
||||
"sale_to_list_ratio": round(random.uniform(0.95, 1.05), 2),
|
||||
}
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_demo_rental_data() -> list[dict[str, Any]]:
|
||||
"""Return sample rental data for visualization."""
|
||||
data = []
|
||||
|
||||
zones = [
|
||||
("Zone01", "Downtown"),
|
||||
("Zone02", "Midtown"),
|
||||
("Zone03", "North York"),
|
||||
("Zone04", "Scarborough"),
|
||||
("Zone05", "Etobicoke"),
|
||||
]
|
||||
|
||||
bedroom_types = ["bachelor", "1_bedroom", "2_bedroom", "3_bedroom"]
|
||||
|
||||
base_rents = {
|
||||
"bachelor": 1800,
|
||||
"1_bedroom": 2200,
|
||||
"2_bedroom": 2800,
|
||||
"3_bedroom": 3400,
|
||||
}
|
||||
|
||||
for year in [2021, 2022, 2023, 2024, 2025]:
|
||||
for zone_code, zone_name in zones:
|
||||
for bedroom in bedroom_types:
|
||||
# Rental trend: ~5% increase per year
|
||||
year_factor = 1 + ((year - 2021) * 0.05)
|
||||
base_rent = base_rents[bedroom]
|
||||
|
||||
data.append(
|
||||
{
|
||||
"zone_code": zone_code,
|
||||
"zone_name": zone_name,
|
||||
"survey_year": year,
|
||||
"full_date": date(year, 10, 1),
|
||||
"bedroom_type": bedroom,
|
||||
"average_rent": int(base_rent * year_factor),
|
||||
"median_rent": int(base_rent * year_factor * 0.98),
|
||||
"vacancy_rate": round(
|
||||
2.5 - (year - 2021) * 0.3, 1
|
||||
), # Decreasing vacancy
|
||||
"universe": 5000 + (year - 2021) * 200,
|
||||
}
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_demo_policy_events() -> list[dict[str, Any]]:
|
||||
"""Return sample policy events for annotation."""
|
||||
return [
|
||||
{
|
||||
"event_date": date(2024, 6, 5),
|
||||
"effective_date": date(2024, 6, 5),
|
||||
"level": "federal",
|
||||
"category": "monetary",
|
||||
"title": "BoC Rate Cut (25bp)",
|
||||
"description": "Bank of Canada cuts overnight rate by 25 basis points to 4.75%",
|
||||
"expected_direction": "bullish",
|
||||
},
|
||||
{
|
||||
"event_date": date(2024, 7, 24),
|
||||
"effective_date": date(2024, 7, 24),
|
||||
"level": "federal",
|
||||
"category": "monetary",
|
||||
"title": "BoC Rate Cut (25bp)",
|
||||
"description": "Bank of Canada cuts overnight rate by 25 basis points to 4.50%",
|
||||
"expected_direction": "bullish",
|
||||
},
|
||||
{
|
||||
"event_date": date(2024, 9, 4),
|
||||
"effective_date": date(2024, 9, 4),
|
||||
"level": "federal",
|
||||
"category": "monetary",
|
||||
"title": "BoC Rate Cut (25bp)",
|
||||
"description": "Bank of Canada cuts overnight rate by 25 basis points to 4.25%",
|
||||
"expected_direction": "bullish",
|
||||
},
|
||||
{
|
||||
"event_date": date(2024, 10, 23),
|
||||
"effective_date": date(2024, 10, 23),
|
||||
"level": "federal",
|
||||
"category": "monetary",
|
||||
"title": "BoC Rate Cut (50bp)",
|
||||
"description": "Bank of Canada cuts overnight rate by 50 basis points to 3.75%",
|
||||
"expected_direction": "bullish",
|
||||
},
|
||||
{
|
||||
"event_date": date(2024, 12, 11),
|
||||
"effective_date": date(2024, 12, 11),
|
||||
"level": "federal",
|
||||
"category": "monetary",
|
||||
"title": "BoC Rate Cut (50bp)",
|
||||
"description": "Bank of Canada cuts overnight rate by 50 basis points to 3.25%",
|
||||
"expected_direction": "bullish",
|
||||
},
|
||||
{
|
||||
"event_date": date(2024, 9, 16),
|
||||
"effective_date": date(2024, 12, 15),
|
||||
"level": "federal",
|
||||
"category": "regulatory",
|
||||
"title": "CMHC 30-Year Amortization",
|
||||
"description": "30-year amortization extended to all first-time buyers and new builds",
|
||||
"expected_direction": "bullish",
|
||||
},
|
||||
{
|
||||
"event_date": date(2024, 9, 16),
|
||||
"effective_date": date(2024, 12, 15),
|
||||
"level": "federal",
|
||||
"category": "regulatory",
|
||||
"title": "Insured Mortgage Cap $1.5M",
|
||||
"description": "Insured mortgage cap raised from $1M to $1.5M",
|
||||
"expected_direction": "bullish",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def get_demo_summary_metrics() -> dict[str, dict[str, Any]]:
|
||||
"""Return summary metrics for KPI cards."""
|
||||
return {
|
||||
"avg_price": {
|
||||
"value": 1067968,
|
||||
"title": "Avg. Price (2025)",
|
||||
"delta": -4.7,
|
||||
"delta_suffix": "%",
|
||||
"prefix": "$",
|
||||
"format_spec": ",.0f",
|
||||
"positive_is_good": True,
|
||||
},
|
||||
"total_sales": {
|
||||
"value": 67610,
|
||||
"title": "Total Sales (2024)",
|
||||
"delta": 2.6,
|
||||
"delta_suffix": "%",
|
||||
"format_spec": ",.0f",
|
||||
"positive_is_good": True,
|
||||
},
|
||||
"avg_rent": {
|
||||
"value": 2450,
|
||||
"title": "Avg. Rent (2025)",
|
||||
"delta": 3.2,
|
||||
"delta_suffix": "%",
|
||||
"prefix": "$",
|
||||
"format_spec": ",.0f",
|
||||
"positive_is_good": False,
|
||||
},
|
||||
"vacancy_rate": {
|
||||
"value": 1.8,
|
||||
"title": "Vacancy Rate",
|
||||
"delta": -0.4,
|
||||
"delta_suffix": "pp",
|
||||
"suffix": "%",
|
||||
"format_spec": ".1f",
|
||||
"positive_is_good": False,
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user