mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-22 08:48:38 +00:00
The ASHP bundle is priced from the rate sheet (ADR-0025); the catalogue row is read only for its material id, which is nullable end-to-end. The live `material` catalogue has no `air_source_heat_pump` row, so `products.get` raised `ValueError: no active product` and aborted every ASHP-eligible property. Add `ProductNotFound(ValueError)` + a concrete `ProductRepository .get_optional`, raise the typed error from both repos, and have `_ashp_option` look the row up optionally — a missing row now yields an ASHP Option with `material_id=None` rather than crashing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
37 lines
1.5 KiB
Python
37 lines
1.5 KiB
Python
from __future__ import annotations
|
|
|
|
from abc import ABC, abstractmethod
|
|
from typing import Optional
|
|
|
|
from domain.modelling.product import Product
|
|
|
|
|
|
class ProductNotFound(ValueError):
|
|
"""Raised when the catalogue has no active entry for a Measure Type. A
|
|
subclass of ``ValueError`` so existing callers that catch ``ValueError``
|
|
keep working, while callers that only want to know *whether* a row exists
|
|
(see ``get_optional``) can catch this case alone."""
|
|
|
|
|
|
class ProductRepository(ABC):
|
|
"""Loads Products from the catalogue, abstracting the data source (a
|
|
Postgres-backed materials table today; a JSON file for costs the ETL does
|
|
not yet supply, behind the same port later). Maps the raw source row into
|
|
the `Product` domain object, joining the per-Measure-Type contingency."""
|
|
|
|
@abstractmethod
|
|
def get(self, measure_type: str) -> Product:
|
|
"""Return the Product for a Measure Type, raising ``ProductNotFound``
|
|
if there is no active catalogue entry."""
|
|
...
|
|
|
|
def get_optional(self, measure_type: str) -> Optional[Product]:
|
|
"""Return the Product for a Measure Type, or None when the catalogue has
|
|
no active entry. For measures whose cost is composed off-catalogue (e.g.
|
|
ASHP, priced from the rate sheet per ADR-0025) the catalogue row is read
|
|
only for its id, so a missing row is not an error — the measure is still
|
|
offered, just without a ``material_id``."""
|
|
try:
|
|
return self.get(measure_type)
|
|
except ProductNotFound:
|
|
return None
|