Skip to content

Commit 98436df

Browse files
committed
Merge branch 'main' into feat/census-data-maps
2 parents bd11fc4 + da23232 commit 98436df

20 files changed

+509
-271
lines changed

hub/graphql/types/model_types.py

Lines changed: 61 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,11 @@ class BaseDataSource(Analytics):
959959
allow_updates: bool = attr_field()
960960
default_data_type: Optional[str] = attr_field()
961961
defaults: JSON = attr_field()
962+
auto_update_enabled: auto
963+
auto_import_enabled: auto
964+
remote_name: Optional[str] = fn_field()
965+
remote_url: Optional[str] = fn_field()
966+
healthcheck: bool = fn_field()
962967

963968
field_definitions: Optional[List[FieldDefinition]] = strawberry_django.field(
964969
resolver=lambda self: self.field_definitions()
@@ -1063,11 +1068,6 @@ class ExternalDataSource(BaseDataSource):
10631068
strawberry_django_dataloaders.fields.auto_dataloader_field()
10641069
)
10651070
update_mapping: Optional[List["AutoUpdateConfig"]]
1066-
auto_update_enabled: auto
1067-
auto_import_enabled: auto
1068-
remote_name: Optional[str] = fn_field()
1069-
remote_url: Optional[str] = fn_field()
1070-
healthcheck: bool = fn_field()
10711071
orgs_with_access: List[Organisation]
10721072

10731073
@strawberry_django.field
@@ -1222,6 +1222,7 @@ def get_queryset(cls, queryset, info, **kwargs):
12221222
@strawberry.enum
12231223
class InspectorDisplayType(Enum):
12241224
BigNumber = "BigNumber"
1225+
BigRecord = "BigRecord"
12251226
ElectionResult = "ElectionResult"
12261227
List = "List"
12271228
Properties = "Properties"
@@ -1239,9 +1240,7 @@ class MapLayer:
12391240
icon_image: Optional[str] = dict_key_field()
12401241
mapbox_paint: Optional[JSON] = dict_key_field()
12411242
mapbox_layout: Optional[JSON] = dict_key_field()
1242-
inspector_type: Optional[InspectorDisplayType] = dict_key_field(
1243-
default=InspectorDisplayType.Table
1244-
)
1243+
inspector_type: Optional[str] = dict_key_field(default=InspectorDisplayType.Table)
12451244
inspector_config: Optional[JSON] = dict_key_field()
12461245

12471246
@strawberry_django.field
@@ -1679,6 +1678,12 @@ class MapBounds:
16791678
south: float
16801679
west: float
16811680

1681+
@strawberry.enum
1682+
class ChoroplethMode(Enum):
1683+
Count = "Count"
1684+
Field = "Field"
1685+
Formula = "Formula"
1686+
16821687

16831688
@strawberry_django.field()
16841689
def choropleth_data_for_source(
@@ -1687,8 +1692,10 @@ def choropleth_data_for_source(
16871692
analytical_area_key: AnalyticalAreaType,
16881693
# Field could be a column name or a Pandas formulaic expression
16891694
# or, if not provided, a count of records
1695+
mode: Optional[ChoroplethMode] = ChoroplethMode.Count,
16901696
field: Optional[str] = None,
16911697
map_bounds: Optional[MapBounds] = None,
1698+
formula: Optional[str] = None,
16921699
) -> List[GroupedDataCount]:
16931700
# Check user can access the external data source
16941701
user = get_current_user(info)
@@ -1728,7 +1735,7 @@ def choropleth_data_for_source(
17281735
)
17291736
combined_area = areas.aggregate(union=GisUnion("polygon"))["union"]
17301737
# all geocoded GenericData should have `point` set
1731-
qs = qs.filter(point__within=combined_area)
1738+
qs = qs.filter(point__within=combined_area or bbox)
17321739

17331740
qs = qs.values("json", "label", "gss")
17341741

@@ -1753,12 +1760,47 @@ def choropleth_data_for_source(
17531760
# TODO: maybe make this explicit via an argument?
17541761
# is_data_source_statistical = external_data_source.data_type == models.ExternalDataSource.DataSourceType.AREA_STATS
17551762
# check that field is in DF
1756-
field_is_set = field and field is not None and len(field)
1757-
is_explicit_row_count = field_is_set and field == "__COUNT__"
1758-
is_valid_statistical_field = field_is_set and not is_explicit_row_count
1759-
is_valid_row_counter = is_explicit_row_count or not field_is_set
1763+
is_valid_field = field and field is not None and len(field) and field in df.columns
1764+
is_row_count = mode is ChoroplethMode.Count
1765+
is_valid_formula = formula and formula is not None and len(formula)
1766+
1767+
if mode is ChoroplethMode.Field and not is_valid_field:
1768+
raise ValueError("Field not found in data source")
1769+
1770+
if mode is ChoroplethMode.Formula and not is_valid_formula:
1771+
raise ValueError("Formula is invalid")
17601772

1761-
if is_valid_statistical_field:
1773+
if is_row_count:
1774+
# Simple count of data points per area
1775+
1776+
# Count the number of rows per GSS
1777+
df_count = (
1778+
df.drop(columns=["label"]).groupby("gss").size().reset_index(name="count")
1779+
)
1780+
1781+
# Calculate the mode for the 'label' column
1782+
def get_mode(series):
1783+
try:
1784+
return series.mode()[0]
1785+
except KeyError:
1786+
return None
1787+
1788+
df_mode = df.groupby("gss")["label"].agg(get_mode).reset_index()
1789+
1790+
# Merge the summed DataFrame with the mode DataFrame
1791+
df = pd.merge(df_count, df_mode, on="gss")
1792+
1793+
# Convert DF to GroupedDataCount(label=label, gss=gss, count=count) list
1794+
return [
1795+
GroupedDataCount(
1796+
label=row.label,
1797+
gss=row.gss,
1798+
count=row.count,
1799+
formatted_count=f"{row.count:,.0f}",
1800+
)
1801+
for row in df.itertuples()
1802+
]
1803+
elif is_valid_field or is_valid_formula:
17621804
# Convert any stringified JSON numbers to floats
17631805
for column in df:
17641806
if all(df[column].apply(check_numeric)):
@@ -1818,16 +1860,16 @@ def get_mode(series):
18181860

18191861
# Now fetch the requested series from the dataframe
18201862
# If the field is a column name, we can just return that column
1821-
if field in df.columns:
1863+
if is_valid_field:
18221864
df["count"] = df[field]
18231865
# If the field is a formula, we need to evaluate it
1824-
else:
1866+
elif is_valid_formula:
18251867
try:
1826-
df["count"] = df.eval(field)
1868+
df["count"] = df.eval(formula)
18271869
except ValueError:
18281870
# In case "where" is used, which pandas doesn't support
18291871
# https://github.com/pandas-dev/pandas/issues/34834
1830-
df["count"] = ne.evaluate(field, local_dict=df)
1872+
df["count"] = ne.evaluate(formula, local_dict=df)
18311873

18321874
# Check if count is between 0 and 1: if so, it's a percentage
18331875
is_percentage = df["count"].between(0, 2).all() or False
@@ -1852,35 +1894,6 @@ def get_mode(series):
18521894
)
18531895
for row in df.itertuples()
18541896
]
1855-
elif is_valid_row_counter:
1856-
# Simple count of data points per area
1857-
1858-
# Count the number of rows per GSS
1859-
df_count = (
1860-
df.drop(columns=["label"]).groupby("gss").size().reset_index(name="count")
1861-
)
1862-
1863-
# Calculate the mode for the 'label' column
1864-
def get_mode(series):
1865-
try:
1866-
return series.mode()[0]
1867-
except KeyError:
1868-
return None
1869-
1870-
df_mode = df.groupby("gss")["label"].agg(get_mode).reset_index()
1871-
1872-
# Merge the summed DataFrame with the mode DataFrame
1873-
df = pd.merge(df_count, df_mode, on="gss")
1874-
1875-
# Convert DF to GroupedDataCount(label=label, gss=gss, count=count) list
1876-
return [
1877-
GroupedDataCount(
1878-
label=row.label,
1879-
gss=row.gss,
1880-
count=row.count,
1881-
formatted_count=f"{row.count:,.0f}",
1882-
)
1883-
for row in df.itertuples()
1884-
]
1897+
18851898
else:
18861899
raise ValueError("Incorrect configuration for choropleth")

hub/models.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -800,16 +800,26 @@ def __str__(self):
800800

801801
@property
802802
def name(self) -> Optional[str]:
803-
if self.full_name:
804-
return self.full_name
805-
elif self.first_name and self.last_name:
806-
return f"{self.first_name} {self.last_name}"
807-
elif self.first_name:
808-
return self.first_name
809-
elif self.last_name:
810-
return self.last_name
811-
elif self.title:
812-
return self.title
803+
full_name = (
804+
self.full_name if self.full_name and len(self.full_name) > 0 else None
805+
)
806+
807+
merged_name = (
808+
f"{self.first_name} {self.last_name}".strip()
809+
if self.first_name
810+
and self.last_name
811+
and len(self.first_name) > 0
812+
and len(self.last_name) > 0
813+
else None
814+
)
815+
816+
# pick whichever is longer
817+
if full_name and merged_name:
818+
full_name = sorted([full_name, merged_name], key=len, reverse=True)[0]
819+
820+
for option in [full_name, self.first_name, self.last_name, self.title]:
821+
if option and len(option) > 0:
822+
return option
813823

814824
return None
815825

hub/tests/test_external_data_source_parsers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ def setUpTestData(cls):
349349
"id": "3",
350350
"venue_name": "Sainsbury's Local",
351351
"address": "Gordon Street",
352-
"expected_postcode": "G1 3RS",
352+
"expected_postcode": "G1 3PE",
353353
},
354354
{
355355
# Special case: "online"

hub/tests/test_permissions.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def test_aggregate_data_count(self):
107107
choroplethDataForSource(
108108
sourceId: $sourceId
109109
analyticalAreaKey: european_electoral_region
110-
field: ""
110+
mode: Count
111111
)
112112
{
113113
gss
@@ -251,7 +251,7 @@ def test_aggregate_data_count(self):
251251
choroplethDataForSource(
252252
sourceId: $sourceId
253253
analyticalAreaKey: european_electoral_region
254-
field: ""
254+
mode: Count
255255
)
256256
{
257257
gss
@@ -393,7 +393,7 @@ def test_aggregate_data_count(self):
393393
choroplethDataForSource(
394394
sourceId: $sourceId
395395
analyticalAreaKey: european_electoral_region
396-
field: ""
396+
mode: Count
397397
)
398398
{
399399
gss
@@ -532,7 +532,7 @@ def test_aggregate_data_count(self):
532532
choroplethDataForSource(
533533
sourceId: $sourceId
534534
analyticalAreaKey: european_electoral_region
535-
field: ""
535+
mode: Count
536536
)
537537
{
538538
gss
@@ -659,7 +659,7 @@ def test_aggregate_data_count(self):
659659
choroplethDataForSource(
660660
sourceId: $sourceId
661661
analyticalAreaKey: european_electoral_region
662-
field: ""
662+
mode: Count
663663
)
664664
{
665665
gss
@@ -751,7 +751,7 @@ def test_aggregate_data_count(self):
751751
choroplethDataForSource(
752752
sourceId: $sourceId
753753
analyticalAreaKey: european_electoral_region
754-
field: ""
754+
mode: Count
755755
)
756756
{
757757
gss
@@ -852,7 +852,7 @@ def test_aggregate_data_count(self):
852852
choroplethDataForSource(
853853
sourceId: $sourceId
854854
analyticalAreaKey: european_electoral_region
855-
field: ""
855+
mode: Count
856856
)
857857
{
858858
gss
@@ -944,7 +944,7 @@ def test_aggregate_data_count(self):
944944
choroplethDataForSource(
945945
sourceId: $sourceId
946946
analyticalAreaKey: european_electoral_region
947-
field: ""
947+
mode: Count
948948
)
949949
{
950950
gss
@@ -1080,7 +1080,7 @@ def test_aggregate_data_count(self):
10801080
choroplethDataForSource(
10811081
sourceId: $sourceId
10821082
analyticalAreaKey: european_electoral_region
1083-
field: ""
1083+
mode: Count
10841084
)
10851085
{
10861086
gss

0 commit comments

Comments
 (0)