Skip to content

Commit 05940a7

Browse files
authored
Merge pull request #224 from BayAreaMetro/fb_preserve
Preserve units as deed-restricted
2 parents 1ba37b4 + 27e898d commit 05940a7

File tree

7 files changed

+200
-5
lines changed

7 files changed

+200
-5
lines changed

baus.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ def get_simulation_models(SCENARIO):
163163
"price_vars",
164164
"scheduled_development_events",
165165

166+
# preserve some units
167+
"preserve_affordable",
166168
# run the subsidized acct system
167169
"lump_sum_accounts",
168170
"subsidized_residential_developer_lump_sum_accts",

baus/datasources.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ def parcels_geography(parcels, scenario, settings, policy):
537537
df['juris_ppa'] = df.juris + '-' + df.ppa_id
538538
df["sesit_id"] = df.sesit_id.str.lower()
539539
df['juris_sesit'] = df.juris + '-' + df.sesit_id
540+
df['gg_id'] = df.gg_id.str.lower()
540541
# Use Final Blueprint geographies: PDA, TRA, PPA, sesit
541542
elif scenario in policy['geographies_fb_enable']:
542543
df["pda_id_pba50"] = df.pda_id_pba50_fb.str.lower()
@@ -546,6 +547,7 @@ def parcels_geography(parcels, scenario, settings, policy):
546547
df['juris_ppa'] = df.juris + '-' + df.ppa_id
547548
df["sesit_id"] = df.fbp_sesit_id.str.lower()
548549
df['juris_sesit'] = df.juris + '-' + df.sesit_id
550+
df['gg_id'] = df.fbp_gg_id.str.lower()
549551

550552
return df
551553

@@ -729,6 +731,7 @@ def development_projects(parcels, mapping, scenario):
729731
df["building_sqft"] = df.building_sqft.fillna(0)
730732
df["non_residential_sqft"] = df.non_residential_sqft.fillna(0)
731733
df["residential_units"] = df.residential_units.fillna(0).astype("int")
734+
df["preserved_units"] = 0.0
732735

733736
df["building_type"] = df.building_type.replace("HP", "OF")
734737
df["building_type"] = df.building_type.replace("GV", "OF")

baus/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,7 @@ def add_extra_columns_func(df):
507507
else:
508508
print("Number of deed restricted units built = %d" %
509509
df.deed_restricted_units.sum())
510+
df["preserved_units"] = 0.0
510511

511512
df["redfin_sale_year"] = 2012
512513
df["redfin_sale_price"] = np.nan

baus/preprocessing.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,8 @@ def preproc_buildings(store, parcels, manual_edits):
293293
store.households_preproc.building_id.value_counts()],
294294
axis=1).max(axis=1)
295295

296+
df["preserved_units"] = 0.0
297+
296298
# XXX need to make sure jobs don't exceed capacity
297299

298300
# drop columns we don't needed

baus/subsidies.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,90 @@ def coffer(policy, scenario):
5656
return d
5757

5858

59+
@orca.step()
60+
def preserve_affordable(year, base_year, scenario, policy, residential_units,
61+
taz_geography, buildings, parcels_geography):
62+
63+
if scenario not in policy["unit_preservation"]["enable_in_scenarios"]:
64+
return
65+
66+
# join several geography columns to units table so that we can apply units
67+
res_units = residential_units.to_frame()
68+
bldgs = buildings.to_frame()
69+
parcels_geog = parcels_geography.to_frame()
70+
taz_geog = taz_geography.to_frame()
71+
72+
res_units = res_units.merge(bldgs[['parcel_id']], left_on='building_id',
73+
right_index=True, how='left').\
74+
merge(parcels_geog[['gg_id', 'sesit_id', 'tra_id',
75+
'juris']], left_on='parcel_id', right_index=True, how='left').\
76+
merge(taz_geog, left_on='zone_id', right_index=True, how='left')
77+
78+
s = policy["unit_preservation"]["settings"]
79+
80+
# only preserve units that are not already deed-restricted
81+
res_units = res_units.loc[res_units.deed_restricted != 1]
82+
83+
# initialize list of units to mark deed restricted
84+
dr_units = []
85+
86+
# apply deed-restriced units by geography (county here)
87+
for geog, value in s.items():
88+
89+
# apply deed-restriced units by filters within each geography
90+
l = ['first', 'second', 'third', 'fourth']
91+
for item in l:
92+
93+
if value[item+"_unit_filter"] is None or \
94+
value[item+"_unit_target"] is None:
95+
continue
96+
97+
filter_nm = value[item+"_unit_filter"]
98+
unit_target = value[item+"_unit_target"]
99+
100+
# exclude units that have been preserved through this loop
101+
res_units = res_units[~res_units.index.isin(dr_units)]
102+
103+
# subset units to the geography
104+
geography = policy["unit_preservation"]["geography"]
105+
geog_units = res_units.loc[res_units[geography] == geog]
106+
# subset units to the filters within the geography
107+
filter_units = geog_units.query(filter_nm)
108+
109+
# pull a random set of units based on the target except in cases
110+
# where there aren't enough units in the filtered geography or
111+
# they're already marked as deed restricted
112+
if len(filter_units) == 0:
113+
dr_units_set = []
114+
print("%s %s: target is %d but no units are available" %
115+
(geog, filter_nm, unit_target))
116+
elif unit_target > len(filter_units):
117+
dr_units_set = filter_units.index
118+
print("%s %s: target is %d but only %d units are available" %
119+
(geog, filter_nm, unit_target, len(filter_units)))
120+
else:
121+
dr_units_set = np.random.choice(filter_units.index,
122+
unit_target, replace=False)
123+
124+
dr_units.extend(dr_units_set)
125+
126+
# mark units as deed restriced in residential units table
127+
residential_units = residential_units.to_frame()
128+
residential_units.loc[residential_units.index.isin(dr_units),
129+
'deed_restricted'] = 1
130+
orca.add_table("residential_units", residential_units)
131+
132+
# mark units as deed restricted in buildings table
133+
buildings = buildings.to_frame(buildings.local_columns)
134+
new_dr_res_units = residential_units.building_id.loc[residential_units.\
135+
index.isin(dr_units)].value_counts()
136+
buildings["preserved_units"] = (buildings["preserved_units"] +
137+
buildings.index.map(new_dr_res_units).fillna(0.0))
138+
buildings["deed_restricted_units"] = (buildings["deed_restricted_units"] +
139+
buildings.index.map(new_dr_res_units).fillna(0.0))
140+
orca.add_table("buildings", buildings)
141+
142+
59143
@orca.injectable(cache=True)
60144
def acct_settings(policy):
61145
return policy["acct_settings"]

baus/summaries.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,16 @@ def policy_activated(policy_loc, policy_nm, scenario):
360360
regional_funding += amount*5*7
361361
write("Total funding is $%d" % regional_funding)
362362

363+
# preservation units
364+
regional_units = 0
365+
for geog, value in policy["unit_preservation"]["settings"].items():
366+
l = ['first', 'second', 'third', 'fourth']
367+
for item in l:
368+
units = value[item+"_unit_target"]
369+
if units is not None:
370+
regional_units += units*8
371+
write("Total unit target for preserving units is %d" % regional_units)
372+
363373
f.close()
364374

365375

@@ -951,8 +961,8 @@ def geographic_summary(parcels, households, jobs, buildings, taz_geography,
951961
'buildings',
952962
[parcels, buildings],
953963
columns=['pda_pba40', 'pda_pba50', 'superdistrict', 'juris',
954-
'building_type', 'zone_id', 'residential_units',
955-
'building_sqft', 'non_residential_sqft',
964+
'building_type', 'zone_id', 'residential_units',
965+
'preserved_units', 'building_sqft', 'non_residential_sqft',
956966
'juris_trich', 'juris_tra', 'juris_sesit', 'juris_ppa'])
957967

958968
parcel_output = summary.parcel_output
@@ -1079,6 +1089,9 @@ def geographic_summary(parcels, households, jobs, buildings, taz_geography,
10791089
summary_table.total_subsidy / \
10801090
summary_table.subsidized_units
10811091

1092+
summary_table['preserved_units'] = buildings_df.\
1093+
groupby(geography).preserved_units.sum()
1094+
10821095
summary_table = summary_table.sort_index()
10831096

10841097
if base is False:
@@ -1190,7 +1203,7 @@ def building_summary(parcels, run_number, year,
11901203
columns=['performance_zone', 'year_built', 'building_type',
11911204
'residential_units', 'unit_price', 'zone_id',
11921205
'non_residential_sqft', 'vacant_res_units',
1193-
'deed_restricted_units', 'job_spaces',
1206+
'deed_restricted_units', 'preserved_units', 'job_spaces',
11941207
'x', 'y', 'geom_id', 'source'])
11951208

11961209
df.to_csv(
@@ -1247,11 +1260,14 @@ def parcel_summary(parcels, buildings, households, jobs,
12471260
building_df = orca.merge_tables(
12481261
'buildings',
12491262
[parcels, buildings],
1250-
columns=['parcel_id', 'residential_units', 'deed_restricted_units'])
1263+
columns=['parcel_id', 'residential_units', 'deed_restricted_units',
1264+
'preserved_units'])
12511265
df['residential_units'] = \
12521266
building_df.groupby('parcel_id')['residential_units'].sum()
12531267
df['deed_restricted_units'] = \
12541268
building_df.groupby('parcel_id')['deed_restricted_units'].sum()
1269+
df['preserved_units'] = \
1270+
building_df.groupby('parcel_id')['preserved_units'].sum()
12551271

12561272
jobs_df = orca.merge_tables(
12571273
'jobs',

configs/policy.yaml

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1984,4 +1984,91 @@ acct_settings:
19841984
'Sebastopol': 10
19851985
'Sonoma': 10
19861986
'Unincorporated Sonoma': 5
1987-
'Windsor': 3
1987+
'Windsor': 3
1988+
1989+
1990+
unit_preservation:
1991+
enable_in_scenarios: ["24"]
1992+
geography: county_name
1993+
settings: # change geography as needed
1994+
'Alameda':
1995+
first_unit_filter: (sesit_id == 'dis' or sesit_id == 'hradis') and tra_id == tra_id
1996+
first_unit_target: 3438
1997+
second_unit_filter: (sesit_id == 'dis' or sesit_id == 'hradis') and tra_id != tra_id
1998+
second_unit_target:
1999+
third_unit_filter: tra_id == tra_id and (sesit_id != 'dis' and sesit_id != 'hradis')
2000+
third_unit_target: 12375
2001+
fourth_unit_filter: gg_id == gg_id
2002+
fourth_unit_target: 313
2003+
'Contra Costa':
2004+
first_unit_filter: (sesit_id == 'dis' or sesit_id == 'hradis') and tra_id == tra_id
2005+
first_unit_target: 1063
2006+
second_unit_filter: (sesit_id == 'dis' or sesit_id == 'hradis') and tra_id != tra_id
2007+
second_unit_target:
2008+
third_unit_filter: tra_id == tra_id and (sesit_id != 'dis' and sesit_id != 'hradis')
2009+
third_unit_target: 1563
2010+
fourth_unit_filter: gg_id == gg_id
2011+
fourth_unit_target: 125
2012+
'Marin':
2013+
first_unit_filter: (sesit_id == 'dis' or sesit_id == 'hradis') and tra_id == tra_id
2014+
first_unit_target: 625
2015+
second_unit_filter: (sesit_id == 'dis' or sesit_id == 'hradis') and tra_id != tra_id
2016+
second_unit_target: 750
2017+
third_unit_filter: tra_id == tra_id and (sesit_id != 'dis' and sesit_id != 'hradis')
2018+
third_unit_target: 625
2019+
fourth_unit_filter: gg_id == gg_id
2020+
fourth_unit_target: 1500
2021+
'Napa':
2022+
first_unit_filter: (sesit_id == 'dis' or sesit_id == 'hradis') and tra_id == tra_id
2023+
first_unit_target:
2024+
second_unit_filter: (sesit_id == 'dis' or sesit_id == 'hradis') and tra_id != tra_id
2025+
second_unit_target:
2026+
third_unit_filter: tra_id == tra_id and (sesit_id != 'dis' and sesit_id != 'hradis')
2027+
third_unit_target: 63
2028+
fourth_unit_filter: gg_id == gg_id
2029+
fourth_unit_target:
2030+
'San Francisco':
2031+
first_unit_filter: (sesit_id == 'dis' or sesit_id == 'hradis') and tra_id == tra_id
2032+
first_unit_target: 3438
2033+
second_unit_filter: (sesit_id == 'dis' or sesit_id == 'hradis') and tra_id != tra_id
2034+
second_unit_target: 63
2035+
third_unit_filter: tra_id == tra_id and (sesit_id != 'dis' and sesit_id != 'hradis')
2036+
third_unit_target: 11625
2037+
fourth_unit_filter: gg_id == gg_id
2038+
fourth_unit_target: 6750
2039+
'San Mateo':
2040+
first_unit_filter: (sesit_id == 'dis' or sesit_id == 'hradis') and tra_id == tra_id
2041+
first_unit_target: 938
2042+
second_unit_filter: (sesit_id == 'dis' or sesit_id == 'hradis') and tra_id != tra_id
2043+
second_unit_target:
2044+
third_unit_filter: tra_id == tra_id and (sesit_id != 'dis' and sesit_id != 'hradis')
2045+
third_unit_target: 2125
2046+
fourth_unit_filter: gg_id == gg_id
2047+
fourth_unit_target: 5188
2048+
'Santa Clara':
2049+
first_unit_filter: (sesit_id == 'dis' or sesit_id == 'hradis') and tra_id == tra_id
2050+
first_unit_target: 4813
2051+
second_unit_filter: (sesit_id == 'dis' or sesit_id == 'hradis') and tra_id != tra_id
2052+
second_unit_target:
2053+
third_unit_filter: tra_id == tra_id and (sesit_id != 'dis' and sesit_id != 'hradis')
2054+
third_unit_target: 8000
2055+
fourth_unit_filter: gg_id == gg_id
2056+
fourth_unit_target: 250
2057+
'Solano':
2058+
first_unit_filter: (sesit_id == 'dis' or sesit_id == 'hradis') and tra_id == tra_id
2059+
first_unit_target: 63
2060+
second_unit_filter: (sesit_id == 'dis' or sesit_id == 'hradis') and tra_id != tra_id
2061+
second_unit_target:
2062+
third_unit_filter: tra_id == tra_id and (sesit_id != 'dis' and sesit_id != 'hradis')
2063+
third_unit_target: 375
2064+
fourth_unit_filter: gg_id == gg_id
2065+
fourth_unit_target: 63
2066+
'Sonoma':
2067+
first_unit_filter: (sesit_id == 'dis' or sesit_id == 'hradis') and tra_id == tra_id
2068+
first_unit_target: 625
2069+
second_unit_filter: (sesit_id == 'dis' or sesit_id == 'hradis') and tra_id != tra_id
2070+
second_unit_target:
2071+
third_unit_filter: tra_id == tra_id and (sesit_id != 'dis' and sesit_id != 'hradis')
2072+
third_unit_target: 625
2073+
fourth_unit_filter: gg_id == gg_id
2074+
fourth_unit_target: 63

0 commit comments

Comments
 (0)