Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP Refatora estatísticas covid19 para usar queryset #399

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions covid19/newstats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from django.db.models import Sum

from brazil_data.cities import ibge_data_per_state
from core.models import get_table_model
from covid19.serializers import CityCaseSerializer


class BaseStats:

table_name = None
turicas marked this conversation as resolved.
Show resolved Hide resolved

def __init__(self, state=None):
self.state = state

def get_queryset(self):
Model = get_table_model("covid19", self.table_name)
qs = Model.objects
if self.state:
qs = qs.for_state(self.state)
return qs


class CityStats(BaseStats):
def get_queryset(self):
qs = ibge_data_per_state()
if self.state:
qs = qs[self.state]
else:
result = []
for cities in qs.values():
result.extend(cities)
qs = result

return qs

@property
def number_of_cities(self):
return len(self.get_queryset())

@property
def population(self):
return sum(city.estimated_population for city in self.get_queryset())


class BoletimStats(BaseStats):

table_name = "boletim"

@property
def total_reports(self):
return self.get_queryset().count()


class CasoStats(BaseStats):

table_name = "caso"

@property
def number_of_cities_with_cases(self):
return self.get_queryset().latest_city_cases().count()

@property
def number_of_cities_with_deaths(self):
return self.get_queryset().latest_city_cases().with_deaths().count()

@property
def affected_population(self):
return (
self.get_queryset().latest_city_cases().aggregate(population=Sum("estimated_population_2019"))["population"]
)
Comment on lines +58 to +70
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acho que vale a pena adicionarmos testes para essas métricas, não? Sei que esse não é o foco do trabalho agora, mas assim como fizemos com os dynamic models com issue #396, me parece que aqui também vale uma issue lembrete dos testes uma hora que tenhamos esse PR mergeado


@property
def confirmed(self):
return self.get_queryset().latest_state_cases().aggregate(confirmed=Sum("confirmed"))["confirmed"]

@property
def deaths(self):
return self.get_queryset().latest_state_cases().aggregate(deaths=Sum("deaths"))["deaths"]

@property
def per_city(self):
return CityCaseSerializer(instance=self.get_queryset().latest_city_cases(), many=True).data
40 changes: 40 additions & 0 deletions covid19/qs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from django.db.models import Q, QuerySet


class Covid19QuerySetMixin:
def for_state(self, state):
if state is None:
return self
return self.filter(state=state)


class Covid19BoletimQuerySet(Covid19QuerySetMixin, QuerySet):
pass


class Covid19CasoQuerySet(Covid19QuerySetMixin, QuerySet):
def city(self, imported=True):
qs = self.filter(place_type="city")
if not imported:
qs = qs.filter(city_ibge_code__isnull=False)
return qs

def state(self):
return self.filter(place_type="state")

def lastest(self):
return self.filter(is_last=True)

def with_cases(self):
qs = self.filter(confirmed__gt=0)
qs = qs.exclude((Q(confirmed=0) | Q(confirmed__isnull=True)) & (Q(deaths=0) | Q(deaths__isnull=True)))
return qs

def with_deaths(self):
return self.with_cases().filter(deaths__gt=0)

def latest_city_cases(self):
return self.with_cases().lastest().city(imported=False)

def latest_state_cases(self):
return self.with_cases().lastest().state()
111 changes: 3 additions & 108 deletions covid19/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@

from django.db.models import Max, Sum

from brazil_data.cities import brazilian_cities_per_state
from core.models import get_table_model
from covid19.serializers import CityCaseSerializer


def max_values(data):
Expand Down Expand Up @@ -126,10 +124,6 @@ class Covid19Stats:
"new_deaths_total": (Sum, "new_deaths_total_2019"),
}

@property
def Boletim(self):
return get_table_model("covid19", "boletim")

@property
def Caso(self):
return get_table_model("covid19", "caso")
Expand All @@ -142,58 +136,10 @@ def CasoFull(self):
def ObitoCartorio(self):
return get_table_model("covid19", "obito_cartorio")

@property
def city_cases(self):
return self.Caso.objects.filter(is_last=True, place_type="city", confirmed__gt=0)

@property
def state_cases(self):
return self.Caso.objects.filter(is_last=True, place_type="state")

@property
def total_reports(self):
return self.Boletim.objects.count()

def total_reports_for_state(self, state):
return self.Boletim.objects.filter(state=state).count()

@property
def affected_cities(self):
return self.city_cases.filter(city_ibge_code__isnull=False)

@property
def number_of_affected_cities(self):
return self.affected_cities.count()

def affected_cities_for_state(self, state):
return self.affected_cities.filter(state=state)

def number_of_affected_cities_for_state(self, state):
return self.affected_cities_for_state(state).count()

@property
def cities_with_deaths(self):
return self.city_cases.filter(deaths__gt=0).count()

def cities_with_deaths_for_state(self, state):
return self.city_cases.filter(state=state, deaths__gt=0).count()

@property
def number_of_cities(self):
return sum(len(cities) for cities in brazilian_cities_per_state().values())

def number_of_cities_for_state(self, state):
return len(brazilian_cities_per_state()[state])

@property
def state_totals(self):
return self.state_cases.aggregate(
total_confirmed=Sum("confirmed"),
total_deaths=Sum("deaths"),
total_population=Sum("estimated_population_2019"),
last_date=Max("date"),
)

@property
def country_row(self):
state_totals = self.state_totals
Expand Down Expand Up @@ -244,66 +190,15 @@ def state_row(self, state):
"state": state,
}

def state_aggregate(self, state):
return self.state_cases.filter(state=state).aggregate(
@property
def state_totals(self):
return self.state_cases.aggregate(
total_confirmed=Sum("confirmed"),
total_deaths=Sum("deaths"),
total_population=Sum("estimated_population_2019"),
last_date=Max("date"),
)

def total_confirmed_for_state(self, state):
return self.state_aggregate(state)["total_confirmed"]

def total_deaths_for_state(self, state):
return self.state_aggregate(state)["total_deaths"]

def total_population_for_state(self, state):
return self.state_aggregate(state)["total_population"]

@property
def total_confirmed(self):
return self.state_totals["total_confirmed"]

@property
def total_deaths(self):
return self.state_totals["total_deaths"]

@property
def total_population(self):
return self.state_totals["total_population"]

@property
def city_totals(self):
return self.city_cases.aggregate(
max_confirmed=Max("confirmed"),
max_confirmed_per_100k_inhabitants=Max("confirmed_per_100k_inhabitants"),
max_death_rate=Max("death_rate"),
max_deaths=Max("deaths"),
total_population=Sum("estimated_population_2019"),
)

def city_totals_for_state(self, state):
return self.city_cases.filter(state=state).aggregate(total_population=Sum("estimated_population_2019"))

@property
def affected_population(self):
return self.city_totals["total_population"]

def affected_population_for_state(self, state):
return self.city_totals_for_state(state)["total_population"]

@property
def city_data(self):
# XXX: what should we do about "Importados/Indefinidos"?
city_cases = self.city_cases.filter(city_ibge_code__isnull=False)
serializer = CityCaseSerializer(instance=city_cases, many=True)
cities = [row for row in serializer.data if (row["confirmed"], row["deaths"]) != (0, 0)]
return cities

def city_data_for_state(self, state):
return [row for row in self.city_data if row["state"] == state]

def aggregate_state_data(self, select_columns, groupby_columns, state=None):
qs = self.CasoFull.objects.filter(place_type="state")
if state is not None:
Expand Down
58 changes: 36 additions & 22 deletions covid19/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from covid19.exceptions import SpreadsheetValidationErrors
from covid19.geo import city_geojson, state_geojson
from covid19.models import DailyBulletin, StateSpreadsheet
from covid19.newstats import BoletimStats, CasoStats, CityStats
from covid19.spreadsheet import merge_state_data
from covid19.stats import Covid19Stats, max_values

Expand All @@ -32,9 +33,11 @@ def cities(request):
if state is not None and not get_state_info(state):
raise Http404

brazil_city_data = stats.city_data
country_caso_stats = CasoStats()
brazil_city_data = country_caso_stats.per_city
if state:
city_data = stats.city_data_for_state(state)
state_caso_stats = CasoStats(state)
city_data = state_caso_stats.per_city
total_row = stats.state_row(state)
else:
city_data = brazil_city_data
Expand Down Expand Up @@ -133,10 +136,12 @@ def cities_geojson(request):
elif state:
state = state.upper()
high_fidelity = True
city_data = stats.city_data_for_state(state)
state_caso_stats = CasoStats(state)
city_data = state_caso_stats.per_city
else:
high_fidelity = False
city_data = stats.city_data
country_caso_stats = CasoStats()
city_data = country_caso_stats.per_city

city_ids = set(row["city_ibge_code"] for row in city_data)
data = city_geojson(high_fidelity=high_fidelity)
Expand Down Expand Up @@ -203,34 +208,43 @@ def dashboard(request, state=None):
if state:
state = state.upper()

country_city_stats = CityStats()
country_boletim_stats = BoletimStats()
country_caso_stats = CasoStats()

country_aggregate = make_aggregate(
reports=stats.total_reports,
confirmed=stats.total_confirmed,
deaths=stats.total_deaths,
affected_cities=stats.number_of_affected_cities,
cities=stats.number_of_cities,
affected_population=stats.affected_population,
population=stats.total_population,
cities_with_deaths=stats.cities_with_deaths,
reports=country_boletim_stats.total_reports,
affected_cities=country_caso_stats.number_of_cities_with_cases,
cities_with_deaths=country_caso_stats.number_of_cities_with_deaths,
cities=country_city_stats.number_of_cities,
affected_population=country_caso_stats.affected_population,
population=country_city_stats.population,
confirmed=country_caso_stats.confirmed,
deaths=country_caso_stats.deaths,
)
if state:
city_data = stats.city_data_for_state(state)
state_city_stats = CityStats(state)
state_boletim_stats = BoletimStats(state)
state_caso_stats = CasoStats(state)

city_data = state_caso_stats.per_city
state_data = STATE_BY_ACRONYM[state]
state_id = state_data.ibge_code
state_name = state_data.name

state_aggregate = make_aggregate(
reports=stats.total_reports_for_state(state),
confirmed=stats.total_confirmed_for_state(state),
deaths=stats.total_deaths_for_state(state),
affected_cities=stats.number_of_affected_cities_for_state(state),
cities=stats.number_of_cities_for_state(state),
affected_population=stats.affected_population_for_state(state),
population=stats.total_population_for_state(state),
cities_with_deaths=stats.cities_with_deaths_for_state(state),
reports=state_boletim_stats.total_reports,
affected_cities=state_caso_stats.number_of_cities_with_cases,
cities_with_deaths=state_caso_stats.number_of_cities_with_deaths,
cities=state_city_stats.number_of_cities,
affected_population=state_caso_stats.affected_population,
population=state_city_stats.population,
confirmed=state_caso_stats.confirmed,
deaths=state_caso_stats.deaths,
for_state=True,
)
else:
city_data = stats.city_data
city_data = country_caso_stats.per_city
state_id = state_name = None
state_aggregate = None

Expand Down