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

Imprecise columns in MD hour + week #310

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
53ef6e4
ensure all items are there
why-not-try-calmer Nov 27, 2023
5cf52c5
add report
why-not-try-calmer Nov 27, 2023
076fc43
non-naive datetimes
why-not-try-calmer Nov 27, 2023
714e52c
more assertions
why-not-try-calmer Nov 27, 2023
8bbb241
removed unnecessary
why-not-try-calmer Nov 27, 2023
f9a9faf
remove files
why-not-try-calmer Nov 27, 2023
9d1c4b8
gitignore
why-not-try-calmer Nov 28, 2023
ff4fd3c
Removing dead code and more type hints (#309)
why-not-try-calmer Dec 1, 2023
c3ac57d
test for precision
why-not-try-calmer Dec 1, 2023
3e03ede
checking for 3 decimals
why-not-try-calmer Dec 1, 2023
df44afa
---
why-not-try-calmer Dec 1, 2023
e2e91ec
__init__ helper function
why-not-try-calmer Dec 1, 2023
6bb7f47
test
why-not-try-calmer Dec 1, 2023
78ca3ba
Merge branch 'master' into 269-imprecise-columns-in-MD-hour-week
why-not-try-calmer Dec 1, 2023
f73827f
---
why-not-try-calmer Dec 1, 2023
e2afecc
---
why-not-try-calmer Dec 1, 2023
8254b65
--
why-not-try-calmer Dec 11, 2023
e5a951e
---
why-not-try-calmer Dec 11, 2023
67d61d5
---
why-not-try-calmer Dec 11, 2023
c58a4f2
Revert "--"
why-not-try-calmer Dec 11, 2023
9110bef
fixed test
why-not-try-calmer Dec 11, 2023
ded6307
type hints, isort
why-not-try-calmer Dec 12, 2023
e2d75c3
test
why-not-try-calmer Dec 12, 2023
f02aab5
report status
why-not-try-calmer Dec 12, 2023
c695fbb
more accurate aggregates and annotations
why-not-try-calmer Dec 18, 2023
6ef8c14
cleanup
why-not-try-calmer Dec 18, 2023
ba843a8
finalized aggregates
why-not-try-calmer Dec 18, 2023
4005265
small fix in vallues by day and month
why-not-try-calmer Dec 19, 2023
f730107
requirements
why-not-try-calmer Dec 19, 2023
12c2571
cleanup
why-not-try-calmer Dec 19, 2023
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
9 changes: 5 additions & 4 deletions comptages/core/report.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import os
from datetime import date, datetime, timedelta
from typing import Generator

from datetime import timedelta, datetime
from openpyxl import load_workbook, Workbook
from openpyxl import Workbook, load_workbook

from comptages.datamodel import models
from comptages.core import statistics
from comptages.datamodel import models


def simple_print_callback(progress):
Expand Down Expand Up @@ -89,7 +90,7 @@ def _prepare_yearly_report(
workbook.save(filename=output)


def _mondays_of_count(count: models.Count):
def _mondays_of_count(count: models.Count) -> Generator[date, None, None]:
"""Generator that return the Mondays of the count"""

start = count.start_process_date
Expand Down
10 changes: 4 additions & 6 deletions comptages/core/statistics.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import pandas as pd

from datetime import timedelta, datetime
from datetime import datetime, timedelta

from django.db.models import F, CharField, Value, Q
from django.db.models import Sum
from django.db.models.functions import ExtractHour, Trunc, Concat
import pandas as pd
from django.db.models import CharField, F, Q, Sum, Value
from django.db.models.functions import Concat, ExtractHour, Trunc

from comptages.core import definitions
from comptages.datamodel import models
Expand Down
79 changes: 46 additions & 33 deletions comptages/report/yearly_report_bike.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import os


from django.db.models import Sum, Count
from django.db.models.functions import Cast
from django.db.models.fields import DateField
from django.db.models.functions import (
ExtractIsoWeekDay,
Cast,
ExtractHour,
ExtractIsoWeekDay,
ExtractMonth,
TruncDate,
)

from django.db.models import Count, Sum, Avg
from openpyxl import load_workbook

from comptages.core import definitions
from comptages.datamodel.models import CountDetail, Section, Lane
from comptages.datamodel.models import CountDetail, Lane, Section


class YearlyReportBike:
Expand All @@ -24,7 +22,7 @@ def __init__(self, file_path, year, section_id):
self.year = year
self.section_id = section_id

def values_by_direction(self):
def values_by_direction(self) -> "ValuesQuerySet[CountDetail, Any]":
# Get all the count details for section and the year
qs = CountDetail.objects.filter(
id_lane__id_section__id=self.section_id,
Expand All @@ -34,14 +32,14 @@ def values_by_direction(self):
)

# Total by day of the week (0->monday, 7->sunday) and by direction
result = (
return (
qs.annotate(weekday=ExtractIsoWeekDay("timestamp"))
.values("weekday")
.annotate(total=Sum("times"))
.values("weekday", "id_lane__direction", "total")
)

def values_by_day_and_hour(self):
def values_by_day_and_hour(self) -> "ValuesQuerySet[CountDetail, dict[str, Any]]":
# Get all the count details for section and the year
qs = CountDetail.objects.filter(
id_lane__id_section__id=self.section_id,
Expand All @@ -54,16 +52,20 @@ def values_by_day_and_hour(self):

# Total by day of the week (0->monday, 6->sunday) and by hour (0->23)
result = (
qs.annotate(weekday=ExtractIsoWeekDay("timestamp"))
qs.annotate(date=TruncDate("timestamp"))
.values("date")
.annotate(Sum("times"))
.annotate(weekday=ExtractIsoWeekDay("date"))
.values("weekday")
.annotate(tjm=Avg("times"))
.annotate(hour=ExtractHour("timestamp"))
.values("weekday", "hour")
.annotate(tjm=Sum("times") / 51)
.values("weekday", "hour", "tjm")
)

return result

def values_by_hour_and_direction(self, direction, weekdays=[0, 1, 2, 3, 4, 5, 6]):
def values_by_hour_and_direction(
self, direction, weekdays=[0, 1, 2, 3, 4, 5, 6]
) -> "ValuesQuerySet[CountDetail, Any]":
# Get all the count details for section and the year
qs = CountDetail.objects.filter(
id_lane__id_section__id=self.section_id,
Expand All @@ -77,15 +79,18 @@ def values_by_hour_and_direction(self, direction, weekdays=[0, 1, 2, 3, 4, 5, 6]

# Total by hour (0->23)
result = (
qs.annotate(hour=ExtractHour("timestamp"))
.values("hour")
.annotate(tjm=Sum("times") / 365)
qs.annotate(date=TruncDate("timestamp"))
.values("date")
.annotate(
tjm=Sum("times"),
hour=ExtractHour("timestamp"),
)
.values("hour", "tjm")
)

return result

def values_by_day_and_month(self):
def values_by_day_and_month(self) -> "ValuesQuerySet[CountDetail, Any]":
# Get all the count details for section and the year
qs = CountDetail.objects.filter(
id_lane__id_section__id=self.section_id,
Expand All @@ -98,16 +103,18 @@ def values_by_day_and_month(self):

# Total by day of the week (0->monday, 6->sunday) and by month (1->12)
result = (
qs.annotate(weekday=ExtractIsoWeekDay("timestamp"))
.annotate(month=ExtractMonth("timestamp"))
.values("weekday", "month")
.annotate(tjm=Sum("times") / 12)
qs.annotate(date=TruncDate("timestamp"))
.values("date")
.annotate(Sum("times"))
.annotate(weekday=ExtractIsoWeekDay("timestamp"))
.values("weekday")
.annotate(tjm=Avg("times"), month=ExtractMonth("timestamp"))
.values("weekday", "month", "tjm")
)

return result

def values_by_day(self):
def values_by_day(self) -> "ValuesQuerySet[CountDetail, dict[str, Any]]":
# Get all the count details for section and the year
qs = CountDetail.objects.filter(
id_lane__id_section__id=self.section_id,
Expand All @@ -125,27 +132,31 @@ def values_by_day(self):

return result

def values_by_day_of_week(self):
def values_by_day_of_week(self) -> "ValuesQuerySet[CountDetail, dict[str, Any]]":
# Get all the count details for section and the year
qs = CountDetail.objects.filter(
id_lane__id_section__id=self.section_id,
timestamp__year=self.year,
import_status=definitions.IMPORT_STATUS_DEFINITIVE,
)

# TODO: don't divide by 51 but actually aggregate first by the
# real days (with sum) and then aggregate by weekday (with average)

# Group by day of the week (0->monday, 7->sunday)
result = (
qs.annotate(weekday=ExtractIsoWeekDay("timestamp"))
qs.annotate(date=TruncDate("timestamp"))
.values("date")
.annotate(Sum("times"))
.annotate(weekday=ExtractIsoWeekDay("timestamp"))
.values("weekday")
.annotate(tjm=Sum("times") / 51)
.annotate(tjm=Avg("times"))
.values("weekday", "tjm")
)

return result

def values_by_class(self):
def values_by_class(self) -> "ValuesQuerySet[CountDetail, dict[str, Any]]":
# Get all the count details for section and the year
qs = CountDetail.objects.filter(
id_lane__id_section__id=self.section_id,
Expand All @@ -161,7 +172,9 @@ def values_by_class(self):
)
return result

def tjm_direction_bike(self, categories, direction, weekdays=[0, 1, 2, 3, 4, 5, 6]):
def tjm_direction_bike(
self, categories, direction, weekdays=[0, 1, 2, 3, 4, 5, 6]
) -> dict:
qs = CountDetail.objects.filter(
id_lane__id_section__id=self.section_id,
timestamp__year=self.year,
Expand All @@ -174,7 +187,7 @@ def tjm_direction_bike(self, categories, direction, weekdays=[0, 1, 2, 3, 4, 5,
# TODO: avoid the division?
return qs.aggregate(res=Sum("times"))["res"] / 365

def total(self, categories=[1]):
def total(self, categories=[1]) -> dict:
qs = CountDetail.objects.filter(
timestamp__year=self.year,
id_category__code__in=categories,
Expand All @@ -183,7 +196,7 @@ def total(self, categories=[1]):

return qs.aggregate(res=Sum("times"))["res"]

def max_day(self, categories=[1]):
def max_day(self, categories=[1]) -> tuple:
qs = (
CountDetail.objects.filter(
timestamp__year=self.year,
Expand All @@ -198,7 +211,7 @@ def max_day(self, categories=[1]):

return qs[0]["total"], qs[0]["date"]

def max_month(self, categories=[1]):
def max_month(self, categories=[1]) -> tuple:
qs = (
CountDetail.objects.filter(
timestamp__year=self.year,
Expand All @@ -213,7 +226,7 @@ def max_month(self, categories=[1]):

return qs[0]["total"], qs[0]["month"]

def min_month(self, categories=[1]):
def min_month(self, categories=[1]) -> tuple:
qs = (
CountDetail.objects.filter(
timestamp__year=self.year,
Expand Down
32 changes: 32 additions & 0 deletions comptages/test/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from datetime import datetime
from typing import Optional
from comptages.datamodel import models
import pytz


def yearly_count_for(
year: int,
installation: models.Installation,
class_: Optional[models.Class] = None,
model: Optional[models.Model] = None,
device: Optional[models.Device] = None,
sensor_type: Optional[models.SensorType] = None,
) -> models.Count:
tz = pytz.timezone("Europe/Zurich")
model = model or models.Model.objects.all()[0]
device = device or models.Device.objects.all()[0]
sensor_type = sensor_type or models.SensorType.objects.all()[0]
class_ = class_ or models.Class.objects.get(name="SWISS10")
return models.Count.objects.create(
start_put_date=tz.localize(datetime(year, 1, 1)),
start_service_date=tz.localize(datetime(year, 1, 8)),
start_process_date=tz.localize(datetime(year, 1, 15)),
end_process_date=tz.localize(datetime(year, 12, 17)),
end_service_date=tz.localize(datetime(year, 12, 24)),
end_put_date=tz.localize(datetime(year, 12, 31)),
id_model=model,
id_device=device,
id_sensor_type=sensor_type,
id_class=class_,
id_installation=installation,
)
Loading