refactor: multi-dashboard structural migration
Some checks failed
CI / lint-and-test (pull_request) Has been cancelled
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>
This commit is contained in:
87
dbt/models/intermediate/toronto/_intermediate.yml
Normal file
87
dbt/models/intermediate/toronto/_intermediate.yml
Normal file
@@ -0,0 +1,87 @@
|
||||
version: 2
|
||||
|
||||
models:
|
||||
- name: int_rentals__annual
|
||||
description: "Rental data enriched with time and zone dimensions"
|
||||
columns:
|
||||
- name: rental_id
|
||||
tests:
|
||||
- unique
|
||||
- not_null
|
||||
- name: zone_code
|
||||
tests:
|
||||
- not_null
|
||||
|
||||
- name: int_neighbourhood__demographics
|
||||
description: "Combined census demographics with neighbourhood attributes"
|
||||
columns:
|
||||
- name: neighbourhood_id
|
||||
description: "Neighbourhood identifier"
|
||||
tests:
|
||||
- not_null
|
||||
- name: census_year
|
||||
description: "Census year"
|
||||
tests:
|
||||
- not_null
|
||||
- name: income_quintile
|
||||
description: "Income quintile (1-5, city-wide)"
|
||||
|
||||
- name: int_neighbourhood__housing
|
||||
description: "Housing indicators combining census and rental data"
|
||||
columns:
|
||||
- name: neighbourhood_id
|
||||
description: "Neighbourhood identifier"
|
||||
tests:
|
||||
- not_null
|
||||
- name: year
|
||||
description: "Reference year"
|
||||
- name: rent_to_income_pct
|
||||
description: "Rent as percentage of median income"
|
||||
- name: is_affordable
|
||||
description: "Boolean: rent <= 30% of income"
|
||||
|
||||
- name: int_neighbourhood__crime_summary
|
||||
description: "Aggregated crime with year-over-year trends"
|
||||
columns:
|
||||
- name: neighbourhood_id
|
||||
description: "Neighbourhood identifier"
|
||||
tests:
|
||||
- not_null
|
||||
- name: year
|
||||
description: "Statistics year"
|
||||
tests:
|
||||
- not_null
|
||||
- name: crime_rate_per_100k
|
||||
description: "Total crime rate per 100K population"
|
||||
- name: yoy_change_pct
|
||||
description: "Year-over-year change percentage"
|
||||
|
||||
- name: int_neighbourhood__amenity_scores
|
||||
description: "Normalized amenities per capita and per area"
|
||||
columns:
|
||||
- name: neighbourhood_id
|
||||
description: "Neighbourhood identifier"
|
||||
tests:
|
||||
- not_null
|
||||
- name: year
|
||||
description: "Reference year"
|
||||
- name: total_amenities_per_1000
|
||||
description: "Total amenities per 1000 population"
|
||||
- name: amenities_per_sqkm
|
||||
description: "Total amenities per square km"
|
||||
|
||||
- name: int_rentals__neighbourhood_allocated
|
||||
description: "CMHC rental data allocated to neighbourhoods via area weights"
|
||||
columns:
|
||||
- name: neighbourhood_id
|
||||
description: "Neighbourhood identifier"
|
||||
tests:
|
||||
- not_null
|
||||
- name: year
|
||||
description: "Survey year"
|
||||
tests:
|
||||
- not_null
|
||||
- name: avg_rent_2bed
|
||||
description: "Weighted average 2-bedroom rent"
|
||||
- name: vacancy_rate
|
||||
description: "Weighted average vacancy rate"
|
||||
60
dbt/models/intermediate/toronto/int_census__toronto_cma.sql
Normal file
60
dbt/models/intermediate/toronto/int_census__toronto_cma.sql
Normal file
@@ -0,0 +1,60 @@
|
||||
-- Intermediate: Toronto CMA census statistics by year
|
||||
-- Provides city-wide averages for metrics not available at neighbourhood level
|
||||
-- Used when neighbourhood-level data is unavailable (e.g., median household income)
|
||||
-- Grain: One row per year
|
||||
|
||||
with years as (
|
||||
select * from {{ ref('int_year_spine') }}
|
||||
),
|
||||
|
||||
census as (
|
||||
select * from {{ ref('stg_toronto__census') }}
|
||||
),
|
||||
|
||||
-- Census data is only available for 2016 and 2021
|
||||
-- Map each analysis year to the appropriate census year
|
||||
year_to_census as (
|
||||
select
|
||||
y.year,
|
||||
case
|
||||
when y.year <= 2018 then 2016
|
||||
else 2021
|
||||
end as census_year
|
||||
from years y
|
||||
),
|
||||
|
||||
-- Toronto CMA median household income from Statistics Canada
|
||||
-- Source: Census Profile Table 98-316-X2021001
|
||||
-- 2016: $65,829 (from Census Profile)
|
||||
-- 2021: $84,000 (from Census Profile)
|
||||
cma_income as (
|
||||
select 2016 as census_year, 65829 as median_household_income union all
|
||||
select 2021 as census_year, 84000 as median_household_income
|
||||
),
|
||||
|
||||
-- City-wide aggregates from loaded neighbourhood data
|
||||
city_aggregates as (
|
||||
select
|
||||
census_year,
|
||||
sum(population) as total_population,
|
||||
avg(population_density) as avg_population_density,
|
||||
avg(unemployment_rate) as avg_unemployment_rate
|
||||
from census
|
||||
where population is not null
|
||||
group by census_year
|
||||
),
|
||||
|
||||
final as (
|
||||
select
|
||||
y.year,
|
||||
y.census_year,
|
||||
ci.median_household_income,
|
||||
ca.total_population,
|
||||
ca.avg_population_density,
|
||||
ca.avg_unemployment_rate
|
||||
from year_to_census y
|
||||
left join cma_income ci on y.census_year = ci.census_year
|
||||
left join city_aggregates ca on y.census_year = ca.census_year
|
||||
)
|
||||
|
||||
select * from final
|
||||
@@ -0,0 +1,79 @@
|
||||
-- Intermediate: Normalized amenities per 1000 population
|
||||
-- Pivots amenity types and calculates per-capita metrics
|
||||
-- Grain: One row per neighbourhood per year
|
||||
|
||||
with neighbourhoods as (
|
||||
select * from {{ ref('stg_toronto__neighbourhoods') }}
|
||||
),
|
||||
|
||||
amenities as (
|
||||
select * from {{ ref('stg_toronto__amenities') }}
|
||||
),
|
||||
|
||||
-- Aggregate amenity types
|
||||
amenities_by_year as (
|
||||
select
|
||||
neighbourhood_id,
|
||||
amenity_year as year,
|
||||
sum(case when amenity_type = 'Parks' then amenity_count else 0 end) as parks_count,
|
||||
sum(case when amenity_type = 'Schools' then amenity_count else 0 end) as schools_count,
|
||||
sum(case when amenity_type = 'Transit Stops' then amenity_count else 0 end) as transit_count,
|
||||
sum(case when amenity_type = 'Libraries' then amenity_count else 0 end) as libraries_count,
|
||||
sum(case when amenity_type = 'Community Centres' then amenity_count else 0 end) as community_centres_count,
|
||||
sum(case when amenity_type = 'Recreation' then amenity_count else 0 end) as recreation_count,
|
||||
sum(amenity_count) as total_amenities
|
||||
from amenities
|
||||
group by neighbourhood_id, amenity_year
|
||||
),
|
||||
|
||||
amenity_scores as (
|
||||
select
|
||||
n.neighbourhood_id,
|
||||
n.neighbourhood_name,
|
||||
n.geometry,
|
||||
n.population,
|
||||
n.land_area_sqkm,
|
||||
|
||||
coalesce(a.year, 2021) as year,
|
||||
|
||||
-- Raw counts
|
||||
a.parks_count,
|
||||
a.schools_count,
|
||||
a.transit_count,
|
||||
a.libraries_count,
|
||||
a.community_centres_count,
|
||||
a.recreation_count,
|
||||
a.total_amenities,
|
||||
|
||||
-- Per 1000 population
|
||||
case when n.population > 0
|
||||
then round(a.parks_count::numeric / n.population * 1000, 3)
|
||||
else null
|
||||
end as parks_per_1000,
|
||||
|
||||
case when n.population > 0
|
||||
then round(a.schools_count::numeric / n.population * 1000, 3)
|
||||
else null
|
||||
end as schools_per_1000,
|
||||
|
||||
case when n.population > 0
|
||||
then round(a.transit_count::numeric / n.population * 1000, 3)
|
||||
else null
|
||||
end as transit_per_1000,
|
||||
|
||||
case when n.population > 0
|
||||
then round(a.total_amenities::numeric / n.population * 1000, 3)
|
||||
else null
|
||||
end as total_amenities_per_1000,
|
||||
|
||||
-- Per square km
|
||||
case when n.land_area_sqkm > 0
|
||||
then round(a.total_amenities::numeric / n.land_area_sqkm, 2)
|
||||
else null
|
||||
end as amenities_per_sqkm
|
||||
|
||||
from neighbourhoods n
|
||||
left join amenities_by_year a on n.neighbourhood_id = a.neighbourhood_id
|
||||
)
|
||||
|
||||
select * from amenity_scores
|
||||
@@ -0,0 +1,83 @@
|
||||
-- Intermediate: Aggregated crime by neighbourhood with YoY change
|
||||
-- Pivots crime types and calculates year-over-year trends
|
||||
-- Grain: One row per neighbourhood per year
|
||||
|
||||
with neighbourhoods as (
|
||||
select * from {{ ref('stg_toronto__neighbourhoods') }}
|
||||
),
|
||||
|
||||
crime as (
|
||||
select * from {{ ref('stg_toronto__crime') }}
|
||||
),
|
||||
|
||||
-- Aggregate crime types
|
||||
crime_by_year as (
|
||||
select
|
||||
neighbourhood_id,
|
||||
crime_year as year,
|
||||
sum(incident_count) as total_incidents,
|
||||
sum(case when crime_type = 'Assault' then incident_count else 0 end) as assault_count,
|
||||
sum(case when crime_type = 'Auto Theft' then incident_count else 0 end) as auto_theft_count,
|
||||
sum(case when crime_type = 'Break and Enter' then incident_count else 0 end) as break_enter_count,
|
||||
sum(case when crime_type = 'Robbery' then incident_count else 0 end) as robbery_count,
|
||||
sum(case when crime_type = 'Theft Over' then incident_count else 0 end) as theft_over_count,
|
||||
sum(case when crime_type = 'Homicide' then incident_count else 0 end) as homicide_count,
|
||||
avg(rate_per_100k) as avg_rate_per_100k
|
||||
from crime
|
||||
group by neighbourhood_id, crime_year
|
||||
),
|
||||
|
||||
-- Add year-over-year changes
|
||||
with_yoy as (
|
||||
select
|
||||
c.*,
|
||||
lag(c.total_incidents, 1) over (
|
||||
partition by c.neighbourhood_id
|
||||
order by c.year
|
||||
) as prev_year_incidents,
|
||||
round(
|
||||
(c.total_incidents - lag(c.total_incidents, 1) over (
|
||||
partition by c.neighbourhood_id
|
||||
order by c.year
|
||||
))::numeric /
|
||||
nullif(lag(c.total_incidents, 1) over (
|
||||
partition by c.neighbourhood_id
|
||||
order by c.year
|
||||
), 0) * 100,
|
||||
2
|
||||
) as yoy_change_pct
|
||||
from crime_by_year c
|
||||
),
|
||||
|
||||
crime_summary as (
|
||||
select
|
||||
n.neighbourhood_id,
|
||||
n.neighbourhood_name,
|
||||
n.geometry,
|
||||
n.population,
|
||||
|
||||
w.year,
|
||||
w.total_incidents,
|
||||
w.assault_count,
|
||||
w.auto_theft_count,
|
||||
w.break_enter_count,
|
||||
w.robbery_count,
|
||||
w.theft_over_count,
|
||||
w.homicide_count,
|
||||
w.yoy_change_pct,
|
||||
|
||||
-- Crime rate per 100K population (use source data avg, or calculate if population available)
|
||||
coalesce(
|
||||
w.avg_rate_per_100k,
|
||||
case
|
||||
when n.population > 0
|
||||
then round(w.total_incidents::numeric / n.population * 100000, 2)
|
||||
else null
|
||||
end
|
||||
) as crime_rate_per_100k
|
||||
|
||||
from neighbourhoods n
|
||||
inner join with_yoy w on n.neighbourhood_id = w.neighbourhood_id
|
||||
)
|
||||
|
||||
select * from crime_summary
|
||||
@@ -0,0 +1,45 @@
|
||||
-- Intermediate: Combined census demographics by neighbourhood
|
||||
-- Joins neighbourhoods with census data for demographic analysis
|
||||
-- Grain: One row per neighbourhood per census year
|
||||
|
||||
with neighbourhoods as (
|
||||
select * from {{ ref('stg_toronto__neighbourhoods') }}
|
||||
),
|
||||
|
||||
census as (
|
||||
select * from {{ ref('stg_toronto__census') }}
|
||||
),
|
||||
|
||||
demographics as (
|
||||
select
|
||||
n.neighbourhood_id,
|
||||
n.neighbourhood_name,
|
||||
n.geometry,
|
||||
n.land_area_sqkm,
|
||||
|
||||
-- Use census_year from census data, or fall back to dim_neighbourhood's year
|
||||
coalesce(c.census_year, n.census_year, 2021) as census_year,
|
||||
c.population,
|
||||
c.population_density,
|
||||
c.median_household_income,
|
||||
c.average_household_income,
|
||||
c.median_age,
|
||||
c.unemployment_rate,
|
||||
c.pct_bachelors_or_higher as education_bachelors_pct,
|
||||
c.average_dwelling_value,
|
||||
|
||||
-- Tenure mix
|
||||
c.pct_owner_occupied,
|
||||
c.pct_renter_occupied,
|
||||
|
||||
-- Income quintile (city-wide comparison)
|
||||
ntile(5) over (
|
||||
partition by c.census_year
|
||||
order by c.median_household_income
|
||||
) as income_quintile
|
||||
|
||||
from neighbourhoods n
|
||||
left join census c on n.neighbourhood_id = c.neighbourhood_id
|
||||
)
|
||||
|
||||
select * from demographics
|
||||
@@ -0,0 +1,56 @@
|
||||
-- Intermediate: Housing indicators by neighbourhood
|
||||
-- Combines census housing data with allocated CMHC rental data
|
||||
-- Grain: One row per neighbourhood per year
|
||||
|
||||
with neighbourhoods as (
|
||||
select * from {{ ref('stg_toronto__neighbourhoods') }}
|
||||
),
|
||||
|
||||
census as (
|
||||
select * from {{ ref('stg_toronto__census') }}
|
||||
),
|
||||
|
||||
allocated_rentals as (
|
||||
select * from {{ ref('int_rentals__neighbourhood_allocated') }}
|
||||
),
|
||||
|
||||
housing as (
|
||||
select
|
||||
n.neighbourhood_id,
|
||||
n.neighbourhood_name,
|
||||
n.geometry,
|
||||
|
||||
coalesce(r.year, c.census_year, 2021) as year,
|
||||
|
||||
-- Census housing metrics
|
||||
c.pct_owner_occupied,
|
||||
c.pct_renter_occupied,
|
||||
c.average_dwelling_value,
|
||||
c.median_household_income,
|
||||
|
||||
-- Allocated rental metrics (weighted average from CMHC zones)
|
||||
r.avg_rent_2bed,
|
||||
r.vacancy_rate,
|
||||
|
||||
-- Affordability calculations
|
||||
case
|
||||
when c.median_household_income > 0 and r.avg_rent_2bed > 0
|
||||
then round((r.avg_rent_2bed * 12 / c.median_household_income) * 100, 2)
|
||||
else null
|
||||
end as rent_to_income_pct,
|
||||
|
||||
-- Affordability threshold (30% of income)
|
||||
case
|
||||
when c.median_household_income > 0 and r.avg_rent_2bed > 0
|
||||
then r.avg_rent_2bed * 12 <= c.median_household_income * 0.30
|
||||
else null
|
||||
end as is_affordable
|
||||
|
||||
from neighbourhoods n
|
||||
left join census c on n.neighbourhood_id = c.neighbourhood_id
|
||||
left join allocated_rentals r
|
||||
on n.neighbourhood_id = r.neighbourhood_id
|
||||
and r.year = c.census_year
|
||||
)
|
||||
|
||||
select * from housing
|
||||
57
dbt/models/intermediate/toronto/int_rentals__annual.sql
Normal file
57
dbt/models/intermediate/toronto/int_rentals__annual.sql
Normal file
@@ -0,0 +1,57 @@
|
||||
-- Intermediate: Annual rental data enriched with dimensions
|
||||
-- Joins rentals with time and zone dimensions for analysis
|
||||
|
||||
with rentals as (
|
||||
select * from {{ ref('stg_cmhc__rentals') }}
|
||||
),
|
||||
|
||||
time_dim as (
|
||||
select * from {{ ref('stg_dimensions__time') }}
|
||||
),
|
||||
|
||||
zone_dim as (
|
||||
select * from {{ ref('stg_dimensions__cmhc_zones') }}
|
||||
),
|
||||
|
||||
enriched as (
|
||||
select
|
||||
r.rental_id,
|
||||
|
||||
-- Time attributes
|
||||
t.date_key,
|
||||
t.full_date,
|
||||
t.year,
|
||||
t.month,
|
||||
t.quarter,
|
||||
|
||||
-- Zone attributes
|
||||
z.zone_key,
|
||||
z.zone_code,
|
||||
z.zone_name,
|
||||
|
||||
-- Bedroom type
|
||||
r.bedroom_type,
|
||||
|
||||
-- Metrics
|
||||
r.rental_universe,
|
||||
r.avg_rent,
|
||||
r.median_rent,
|
||||
r.vacancy_rate,
|
||||
r.availability_rate,
|
||||
r.turnover_rate,
|
||||
r.year_over_year_rent_change,
|
||||
r.reliability_code,
|
||||
|
||||
-- Calculated metrics
|
||||
case
|
||||
when r.rental_universe > 0 and r.vacancy_rate is not null
|
||||
then round(r.rental_universe * (r.vacancy_rate / 100), 0)
|
||||
else null
|
||||
end as vacant_units_estimate
|
||||
|
||||
from rentals r
|
||||
inner join time_dim t on r.date_key = t.date_key
|
||||
inner join zone_dim z on r.zone_key = z.zone_key
|
||||
)
|
||||
|
||||
select * from enriched
|
||||
@@ -0,0 +1,73 @@
|
||||
-- Intermediate: CMHC rentals allocated to neighbourhoods via area weights
|
||||
-- Disaggregates zone-level rental data to neighbourhood level
|
||||
-- Grain: One row per neighbourhood per year
|
||||
|
||||
with crosswalk as (
|
||||
select * from {{ ref('stg_cmhc__zone_crosswalk') }}
|
||||
),
|
||||
|
||||
rentals as (
|
||||
select * from {{ ref('int_rentals__annual') }}
|
||||
),
|
||||
|
||||
neighbourhoods as (
|
||||
select * from {{ ref('stg_toronto__neighbourhoods') }}
|
||||
),
|
||||
|
||||
-- Allocate rental metrics to neighbourhoods using area weights
|
||||
allocated as (
|
||||
select
|
||||
c.neighbourhood_id,
|
||||
r.year,
|
||||
r.bedroom_type,
|
||||
|
||||
-- Weighted average rent (using area weight)
|
||||
sum(r.avg_rent * c.area_weight) as weighted_avg_rent,
|
||||
sum(r.median_rent * c.area_weight) as weighted_median_rent,
|
||||
sum(c.area_weight) as total_weight,
|
||||
|
||||
-- Weighted vacancy rate
|
||||
sum(r.vacancy_rate * c.area_weight) / nullif(sum(c.area_weight), 0) as vacancy_rate,
|
||||
|
||||
-- Weighted rental universe
|
||||
sum(r.rental_universe * c.area_weight) as rental_units_estimate
|
||||
|
||||
from crosswalk c
|
||||
inner join rentals r on c.cmhc_zone_code = r.zone_code
|
||||
group by c.neighbourhood_id, r.year, r.bedroom_type
|
||||
),
|
||||
|
||||
-- Pivot to get 2-bedroom as primary metric
|
||||
pivoted as (
|
||||
select
|
||||
neighbourhood_id,
|
||||
year,
|
||||
max(case when bedroom_type = 'Two Bedroom' then weighted_avg_rent / nullif(total_weight, 0) end) as avg_rent_2bed,
|
||||
max(case when bedroom_type = 'One Bedroom' then weighted_avg_rent / nullif(total_weight, 0) end) as avg_rent_1bed,
|
||||
max(case when bedroom_type = 'Bachelor' then weighted_avg_rent / nullif(total_weight, 0) end) as avg_rent_bachelor,
|
||||
max(case when bedroom_type = 'Three Bedroom +' then weighted_avg_rent / nullif(total_weight, 0) end) as avg_rent_3bed,
|
||||
avg(vacancy_rate) as vacancy_rate,
|
||||
sum(rental_units_estimate) as total_rental_units
|
||||
from allocated
|
||||
group by neighbourhood_id, year
|
||||
),
|
||||
|
||||
final as (
|
||||
select
|
||||
n.neighbourhood_id,
|
||||
n.neighbourhood_name,
|
||||
n.geometry,
|
||||
|
||||
p.year,
|
||||
round(p.avg_rent_bachelor::numeric, 2) as avg_rent_bachelor,
|
||||
round(p.avg_rent_1bed::numeric, 2) as avg_rent_1bed,
|
||||
round(p.avg_rent_2bed::numeric, 2) as avg_rent_2bed,
|
||||
round(p.avg_rent_3bed::numeric, 2) as avg_rent_3bed,
|
||||
round(p.vacancy_rate::numeric, 2) as vacancy_rate,
|
||||
round(p.total_rental_units::numeric, 0) as total_rental_units
|
||||
|
||||
from neighbourhoods n
|
||||
inner join pivoted p on n.neighbourhood_id = p.neighbourhood_id
|
||||
)
|
||||
|
||||
select * from final
|
||||
25
dbt/models/intermediate/toronto/int_rentals__toronto_cma.sql
Normal file
25
dbt/models/intermediate/toronto/int_rentals__toronto_cma.sql
Normal file
@@ -0,0 +1,25 @@
|
||||
-- Intermediate: Toronto CMA rental metrics by year
|
||||
-- Aggregates rental data to city-wide averages by year
|
||||
-- Source: StatCan CMHC data at CMA level
|
||||
-- Grain: One row per year
|
||||
|
||||
with rentals as (
|
||||
select * from {{ ref('stg_cmhc__rentals') }}
|
||||
),
|
||||
|
||||
-- Pivot bedroom types to columns
|
||||
yearly_rentals as (
|
||||
select
|
||||
year,
|
||||
max(case when bedroom_type = 'bachelor' then avg_rent end) as avg_rent_bachelor,
|
||||
max(case when bedroom_type = '1bed' then avg_rent end) as avg_rent_1bed,
|
||||
max(case when bedroom_type = '2bed' then avg_rent end) as avg_rent_2bed,
|
||||
max(case when bedroom_type = '3bed' then avg_rent end) as avg_rent_3bed,
|
||||
-- Use 2-bedroom as standard reference
|
||||
max(case when bedroom_type = '2bed' then avg_rent end) as avg_rent_standard,
|
||||
max(vacancy_rate) as vacancy_rate
|
||||
from rentals
|
||||
group by year
|
||||
)
|
||||
|
||||
select * from yearly_rentals
|
||||
11
dbt/models/intermediate/toronto/int_year_spine.sql
Normal file
11
dbt/models/intermediate/toronto/int_year_spine.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
-- Intermediate: Year spine for analysis
|
||||
-- Creates a row for each year from 2014-2025
|
||||
-- Used to drive time-series analysis across all data sources
|
||||
|
||||
with years as (
|
||||
-- Generate years from available data sources
|
||||
-- Crime data: 2014-2024, Rentals: 2019-2025
|
||||
select generate_series(2014, 2025) as year
|
||||
)
|
||||
|
||||
select year from years
|
||||
Reference in New Issue
Block a user