- Admin should be able to modify the geography's core attributes, thereby making it public
- Flow: Admin has to fill in
display_name
andslug
, then submit, then confirmlatitude
andlongitude
by submitting again.- Validation:
slug
anddisplay_name
must either both be filled, or neither. Admin cannot fill in only one or the other. - Admin should not see
latitude
,longitude
, norzoom
ifslug
ordisplay_name
are empty. - Admin should see
latitude
,longitude
, andzoom
ifslug
anddisplay_name
are set.- When
latitude
andlongitude
are shown, system should show a small thumbnail map to confirm the map configuration is accurate
- When
- Existing behavior of the API:
- If slug is NULL, the geograhy will not be exposed publicly
- If slug is provided, the geography is accessible at /geography/:slug
- New Behavior:
- If
latitude
andlongitude
are empty whendisplay_name
is updated, system should attempt to geolocate the city using the entered name and fill those fields
- If
- Validation:
- Flow: Admin has to fill in
- Admin should have to enable filters displayed for each Geography
- Flow: After filling in
display_name
andslug
, the filters config is also visible and editable- Admin should not see filters config if
slug
ordisplay_name
are empty - Admin should see filters config if
slug
anddisplay_name
are present - Filters config consists of ability to add, modify, or remove FilterGroups in a list.
- Each FilterGroup has the following attributes the Admin must configure. All Required:
group_name
- Text shown on the dropdown in the filters tab- Single-line text input
- Required
- Each FilterGroup has a sublist of Filters, and the Admin can Add Remove or Update items in the list.
- At least one filter is required
- Each Filter has the following attributes the Admin must configure:
field_name
- what data is queried to apply this Filter- Required
- Dropdown, showing these options (and corresponding values):
- Building Type (
buildings_geographies__building_subtype
) - Year Built (
year_built
) - Energy Usage Intensity (
eui
) - Heating Fuel Type (
fuel_type
) - Heat Pump Feasibility (
ashp_feasibility_score
) - Energy Efficiency Improvement Potential (
ee_potential_score
) - EJ Need (
ej_score
) - Media Family Income (
tract_income_level
)
- Building Type (
tooltip
- Text shown when the (i) icon is hovered- Single-line text input
- NULL if left blank
- Options -
- Each filter has a sublist of options, and the Admin can Add Remove or Edit items in the list
- At least one option is required
- Each Option has the following attributes the Admin must configure. All required:
display_name
- what the user sees when selecting this option- Single-line text input
operator
- how the Filter queries the field, given the value- Dropdown:
- "IN" ("IN")
- "RANGE" ("RANGE")
- "LESS_THAN" ("LESS_THAN")
- "GREATER_THAN" ("GREATER_THAN")
- Valid values for
operator
depend on thefield_name
on the Filter: - The following
field_name
s are considered numeric and cannot use the "IN" operator ej_score
ashp_feasibility_score
year_built
- The following
field_name
s are considered categorical and must only use the "IN" operator buildings_geographies__building_subtype
year_built
eui
ee_potential_score
tract_income_level
fuel_type
- Dropdown:
value
- Single-line text input
- Valid values for
value
depend on theoperator
on theoption
:- "IN" can be any string
- "RANGE" must be two, comma-separated integers (eg "50,100")
- "LESS_THAN" must be an integer
- "GREATER_THAN" must be an integer
- Admin should not see filters config if
- Flow: After filling in
Created
March 31, 2022 22:11
-
-
Save jfeldstein/d2ac31606610d63877b5e908108019b0 to your computer and use it in GitHub Desktop.
Spec for Geography CRUD tool
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Geographies(models.Model): | |
id = models.BigIntegerField(db_column="geo_id", primary_key=True, null=False) | |
name = models.CharField(db_column="geo_name", max_length=256) | |
slug = models.CharField(db_column="geo_slug", max_length=200) | |
latitude = models.FloatField(db_column="geo_centroid_latitude") | |
longitude = models.FloatField(db_column="geo_centroid_longitude") | |
update_date_time = models.DateTimeField() | |
@property | |
def defaultMap(self): | |
return { | |
"longitude": self.longitude, | |
"latitude": self.latitude, | |
"zoom": 13 | |
} | |
@property | |
def filterSet(self): | |
return [{ | |
"group_name": group_name, | |
"filters": [f for f in filters], | |
} for (group_name, filters) in self.grouped_filters] | |
@property | |
def grouped_filters(self): | |
return groupby(self.filters.active(), lambda f: f.group_name) | |
class Meta: | |
managed = True | |
db_table = 'geographies' | |
class params: | |
db = 'blocmaps_db' | |
class GeographyFilters(models.Model): | |
BUILDING_TYPE = 'buildings_geographies__building_subtype' | |
YEAR_BUILT = 'year_built' | |
EE_POTENTIAL_SCORE = 'ee_potential_score' | |
EJ_SCORE = 'ej_score' | |
ASHP_FEASIBILITY_SCORE = 'ashp_feasibility_score' | |
FIELD_NAME_CHOICES = [ | |
(BUILDING_TYPE, 'Building Type'), | |
(YEAR_BUILT, 'Year Built'), | |
(EE_POTENTIAL_SCORE, 'EE Potential Score'), | |
(EJ_SCORE, 'EJ Score'), | |
(ASHP_FEASIBILITY_SCORE, 'ASHP Feasibility Score'), | |
] | |
id = models.BigIntegerField(db_column="geo_filter_id", primary_key=True, null=False) | |
geography = models.ForeignKey('Geographies', related_name='filters', on_delete=models.CASCADE, null=False, | |
db_index=False, to_field='id', db_column='geo_id') | |
field_name = models.CharField(choices=FIELD_NAME_CHOICES, max_length=200, blank=False, db_column="filter_name") | |
group_name = models.CharField(max_length=200) | |
display_name = models.CharField(max_length=200, blank=False, db_column="filter_display_name") | |
action_description = models.CharField(max_length=500, db_column="filter_action_description") | |
tooltip = models.CharField(max_length=500, db_column="filter_tooltip") | |
is_active = models.BooleanField(db_column="filter_active") | |
class QuerySet(QuerySet): | |
def active(self, *args, **kwargs): | |
return self.filter(is_active=True, *args, **kwargs) | |
objects = PassManager.from_queryset(QuerySet)() | |
class Meta: | |
managed = True | |
db_table = 'geography_filters' | |
ordering = ('group_name', ) | |
class params: | |
db = 'blocmaps_db' | |
class FilterOptions(models.Model): | |
IN = 'IN' | |
RANGE = 'RANGE' | |
LESS_THAN = 'LESS_THAN' | |
GREATER_THAN = 'GREATER_THAN' | |
OPERATOR_CHOICES = [ | |
(IN, 'IN'), | |
(RANGE, 'RANGE'), | |
(LESS_THAN, 'LESS_THAN'), | |
(GREATER_THAN, 'GREATER_THAN') | |
] | |
id = models.BigIntegerField(db_column="filter_option_id", primary_key=True, null=False) | |
filter = models.ForeignKey('GeographyFilters', related_name='options', on_delete=models.CASCADE, null=False, | |
db_index=False, to_field='id', db_column='geo_filter_id') | |
display_name = models.CharField(max_length=200, blank=False, db_column="option_display_name") | |
operator = models.CharField(choices=OPERATOR_CHOICES, max_length=255, blank=False, db_column="option_operator") | |
values = models.CharField(max_length=255, blank=False, db_column="option_values") | |
sort_order = models.IntegerField(db_column="sort_order") | |
class Meta: | |
managed = True | |
db_table = 'filter_options' | |
ordering = ('sort_order', ) | |
class params: | |
db = 'blocmaps_db' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment