profit.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. from marshmallow import (
  2. fields,
  3. ValidationError,
  4. validates_schema,
  5. validate,
  6. post_load,
  7. validates,
  8. )
  9. from flexmeasures.data.schemas.reporting import (
  10. ReporterConfigSchema,
  11. ReporterParametersSchema,
  12. )
  13. from flexmeasures.data.schemas.io import Input
  14. from flexmeasures.data.schemas.sensors import SensorIdField
  15. from flexmeasures.utils.unit_utils import is_currency_unit
  16. class ProfitOrLossReporterConfigSchema(ReporterConfigSchema):
  17. """Schema for the ProfitOrLossReporter configuration
  18. Example:
  19. .. code-block:: json
  20. {
  21. "production-price-sensor" : 1,
  22. "consumption-price-sensor" : 2,
  23. "loss_is_positive" : True
  24. }
  25. """
  26. consumption_price_sensor = SensorIdField(required=False)
  27. production_price_sensor = SensorIdField(required=False)
  28. # set this to True to get the losses as positive values, otherwise, profit is positive.
  29. loss_is_positive = fields.Bool(
  30. load_default=False, dump_default=True, required=False
  31. )
  32. @validates_schema
  33. def validate_price_sensors(self, data, **kwargs):
  34. """check that at least one of the price sensors is given"""
  35. if (
  36. "consumption_price_sensor" not in data
  37. and "production_price_sensor" not in data
  38. ):
  39. raise ValidationError(
  40. "At least one of the two price sensors, consumption or production, is required."
  41. )
  42. @post_load
  43. def complete_price_sensors(self, data, **kwargs):
  44. if "consumption_price_sensor" not in data:
  45. data["consumption_price_sensor"] = data["production_price_sensor"]
  46. if "production_price_sensor" not in data:
  47. data["production_price_sensor"] = data["consumption_price_sensor"]
  48. return data
  49. @validates("consumption_price_sensor")
  50. def validate_consumption_price_units(self, value):
  51. if not value.measures_energy_price:
  52. raise ValidationError(
  53. f"`consumption_price_sensor` has wrong units. Expected `Energy / Currency` but got `{value.unit}`"
  54. )
  55. @validates("production_price_sensor")
  56. def validate_production_price_units(self, value):
  57. if not value.measures_energy_price:
  58. raise ValidationError(
  59. f"`production_price_sensor` has wrong units. Expected `Energy / Currency` but got `{value.unit}`"
  60. )
  61. class ProfitOrLossReporterParametersSchema(ReporterParametersSchema):
  62. """Schema for the ProfitOrLossReporter parameters
  63. Example:
  64. .. code-block:: json
  65. {
  66. "input": [
  67. {
  68. "sensor": 1,
  69. },
  70. ],
  71. "output": [
  72. {
  73. "sensor": 2,
  74. }
  75. ],
  76. "start" : "2023-01-01T00:00:00+00:00",
  77. "end" : "2023-01-03T00:00:00+00:00",
  78. }
  79. """
  80. # redefining output to restrict the input length to 1
  81. input = fields.List(fields.Nested(Input()), validate=validate.Length(min=1, max=1))
  82. @validates("input")
  83. def validate_input_measures_power_energy(self, value):
  84. if not (
  85. value[0]["sensor"].measures_power or value[0]["sensor"].measures_energy
  86. ):
  87. raise ValidationError(
  88. "Input sensor can only contain power or energy values."
  89. )
  90. @validates("output")
  91. def validate_output_unit_currency(self, value):
  92. for output_description in value:
  93. if not is_currency_unit(output_description["sensor"].unit):
  94. raise ValidationError(
  95. "Output sensor unit can only be a currency, e.g. EUR."
  96. )