Some checks failed
CI / lint-and-test (pull_request) Has been cancelled
- Rename dbt project from toronto_housing to portfolio - Restructure dbt models into domain subdirectories: - shared/ for cross-domain dimensions (dim_time) - staging/toronto/, intermediate/toronto/, marts/toronto/ - Update SQLAlchemy models for raw_toronto schema - Add explicit cross-schema FK relationships for FactRentals - Namespace figure factories under figures/toronto/ - Namespace notebooks under notebooks/toronto/ - Update Makefile with domain-specific targets and env loading - Update all documentation for multi-dashboard structure This enables adding new dashboard projects (e.g., /football, /energy) without structural conflicts or naming collisions. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
79 lines
2.1 KiB
SQL
79 lines
2.1 KiB
SQL
-- Mart: Neighbourhood Safety Analysis
|
|
-- Dashboard Tab: Safety
|
|
-- Grain: One row per neighbourhood per year
|
|
|
|
with crime as (
|
|
select * from {{ ref('int_neighbourhood__crime_summary') }}
|
|
),
|
|
|
|
-- City-wide averages for comparison
|
|
city_avg as (
|
|
select
|
|
year,
|
|
avg(crime_rate_per_100k) as city_avg_crime_rate,
|
|
avg(assault_count) as city_avg_assault,
|
|
avg(auto_theft_count) as city_avg_auto_theft,
|
|
avg(break_enter_count) as city_avg_break_enter
|
|
from crime
|
|
group by year
|
|
),
|
|
|
|
final as (
|
|
select
|
|
c.neighbourhood_id,
|
|
c.neighbourhood_name,
|
|
c.geometry,
|
|
c.population,
|
|
c.year,
|
|
|
|
-- Total crime
|
|
c.total_incidents,
|
|
c.crime_rate_per_100k,
|
|
c.yoy_change_pct as crime_yoy_change_pct,
|
|
|
|
-- Crime breakdown
|
|
c.assault_count,
|
|
c.auto_theft_count,
|
|
c.break_enter_count,
|
|
c.robbery_count,
|
|
c.theft_over_count,
|
|
c.homicide_count,
|
|
|
|
-- Per 100K rates by type
|
|
case when c.population > 0
|
|
then round(c.assault_count::numeric / c.population * 100000, 2)
|
|
else null
|
|
end as assault_rate_per_100k,
|
|
|
|
case when c.population > 0
|
|
then round(c.auto_theft_count::numeric / c.population * 100000, 2)
|
|
else null
|
|
end as auto_theft_rate_per_100k,
|
|
|
|
case when c.population > 0
|
|
then round(c.break_enter_count::numeric / c.population * 100000, 2)
|
|
else null
|
|
end as break_enter_rate_per_100k,
|
|
|
|
-- Comparison to city average
|
|
round(ca.city_avg_crime_rate::numeric, 2) as city_avg_crime_rate,
|
|
|
|
-- Crime index (100 = city average)
|
|
case
|
|
when ca.city_avg_crime_rate > 0
|
|
then round(c.crime_rate_per_100k / ca.city_avg_crime_rate * 100, 1)
|
|
else null
|
|
end as crime_index,
|
|
|
|
-- Safety tier based on crime rate percentile
|
|
ntile(5) over (
|
|
partition by c.year
|
|
order by c.crime_rate_per_100k desc
|
|
) as safety_tier
|
|
|
|
from crime c
|
|
left join city_avg ca on c.year = ca.year
|
|
)
|
|
|
|
select * from final
|