Project Volume Curve¶
This recipe shows how to compute a volume curve for a project by grouping derivative volumes per date.
from datetime import date
from django.db.models import DateField, ForeignKey
from core.managers import Derivative, Project
from general_manager.interface import DatabaseInterface
from general_manager.manager import GeneralManager
from general_manager.measurement import Measurement, MeasurementField
class DerivativeVolume(GeneralManager):
project: Project
derivative: Derivative
date: date
volume: Measurement
class Interface(DatabaseInterface):
project = ForeignKey(Project, on_delete=models.CASCADE)
derivative = ForeignKey(Derivative, on_delete=models.CASCADE)
date = DateField()
volume = MeasurementField(base_unit="kWh")
Give optional volumes a logical default¶
When zero is meaningful for a new row, make it the field default:
def default_volume() -> Measurement:
return Measurement(0, "kWh")
# Inside DerivativeVolume.Interface:
volume = MeasurementField(base_unit="kWh", default=default_volume)
The Django model generated by DerivativeVolume.Interface receives the default during model initialization and writes it through the normal paired-column conversion. An explicit volume=None or measurement value wins over the default. This applies to inherited concrete models as well and does not create a database-side default.
Concrete defaults are normalized to Measurement objects and checked for compatibility with base_unit in ORM interface metadata, retaining the supplied unit. The backing-column magnitude is converted separately to base_unit. Generated GraphQL SDL and introspection can expose a string default such as "0 kilowatt_hour", using Pint's canonical unit spelling for "0 kWh". Callable defaults are intentionally omitted from schema defaults because they are evaluated only during model initialization. Partial-update arguments omit defaults: omission preserves the stored volume, while explicit null clears it when the field is nullable.
def project_volume_curve(project: Project) -> tuple[tuple[date, Measurement], ...]:
grouped = (
project.derivative_volume_list
.filter(project=project)
.group_by("date")
.sort("date")
)
return grouped.values_list("date", "volume")
Result:
curve = project_volume_curve(Project(id=1))
for entry_date, volume in curve:
print(entry_date, volume.to("MWh"))
Use this pattern to power charts in dashboards or export data to CSV.
Project selected fields¶
values_list() materializes the grouped bucket in its existing order without constructing another list of manager objects. Use values("date", "volume") instead when a dictionary shape is more convenient for a serializer or CSV writer. Both forms use the bucket's source and historical context during evaluation, then return detached shallow snapshots; the resulting tuples or dictionaries do not retain that bucket metadata.