184 lines
7.4 KiB
Python
184 lines
7.4 KiB
Python
from datetime import datetime
|
|
from typing import Any
|
|
from uuid import UUID
|
|
|
|
from sqlalchemy import func, select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.domain.models import AttributeDefinition, AttributeStatement, Offer, PriceObservation, Product, ProductVariant
|
|
from app.domain.validation import parse_profile, parse_scoring_model
|
|
from app.persistence import models as orm
|
|
from app.persistence.mapping import to_attribute, to_definition, to_observation, to_offer, to_product, to_variant
|
|
|
|
|
|
class CatalogRepository:
|
|
def __init__(self, session: Session) -> None:
|
|
self.session = session
|
|
|
|
def get_definition(self, key: str) -> AttributeDefinition | None:
|
|
row = self.session.get(orm.AttributeDefinitionRecord, key)
|
|
return None if row is None else to_definition(row)
|
|
|
|
def list_definitions(self, limit: int, offset: int) -> tuple[list[AttributeDefinition], int]:
|
|
total = self.session.scalar(select(func.count()).select_from(orm.AttributeDefinitionRecord)) or 0
|
|
rows = self.session.scalars(
|
|
select(orm.AttributeDefinitionRecord).order_by(orm.AttributeDefinitionRecord.key).limit(limit).offset(offset)
|
|
).all()
|
|
return [to_definition(row) for row in rows], total
|
|
|
|
def add_definition(self, row: orm.AttributeDefinitionRecord) -> AttributeDefinition:
|
|
self.session.add(row)
|
|
self.session.flush()
|
|
return to_definition(row)
|
|
|
|
def list_sources(self) -> list[orm.SourceRecord]:
|
|
return list(self.session.scalars(select(orm.SourceRecord).order_by(orm.SourceRecord.id)).all())
|
|
|
|
def get_source(self, source_id: str) -> orm.SourceRecord | None:
|
|
return self.session.get(orm.SourceRecord, source_id)
|
|
|
|
def get_scoring_model(self, version: str):
|
|
row = self.session.get(orm.ScoringModelRecord, version)
|
|
return None if row is None else parse_scoring_model(row.definition)
|
|
|
|
def scoring_versions(self) -> set[str]:
|
|
return set(self.session.scalars(select(orm.ScoringModelRecord.version)).all())
|
|
|
|
def all_definitions(self) -> dict[str, AttributeDefinition]:
|
|
rows = self.session.scalars(select(orm.AttributeDefinitionRecord)).all()
|
|
return {row.key: to_definition(row) for row in rows}
|
|
|
|
def get_profile(self, profile_id: str, version: int):
|
|
row = self.session.get(orm.ProductProfileRecord, (profile_id, version))
|
|
if row is None:
|
|
return None
|
|
model = self.get_scoring_model(row.scoring_version)
|
|
if model is None:
|
|
return None
|
|
return parse_profile(
|
|
row.definition,
|
|
attribute_definitions=self.all_definitions(),
|
|
scoring_versions=self.scoring_versions(),
|
|
supported_strategies=set(model.supported_preference_strategies),
|
|
)
|
|
|
|
def add_profile(self, row: orm.ProductProfileRecord):
|
|
self.session.add(row)
|
|
self.session.flush()
|
|
return row
|
|
|
|
def list_profiles(self, limit: int, offset: int) -> tuple[list[orm.ProductProfileRecord], int]:
|
|
total = self.session.scalar(select(func.count()).select_from(orm.ProductProfileRecord)) or 0
|
|
rows = self.session.scalars(
|
|
select(orm.ProductProfileRecord)
|
|
.order_by(orm.ProductProfileRecord.id, orm.ProductProfileRecord.version)
|
|
.limit(limit)
|
|
.offset(offset)
|
|
).all()
|
|
return list(rows), total
|
|
|
|
def get_product(self, product_id: UUID) -> Product | None:
|
|
row = self.session.get(orm.ProductRecord, product_id)
|
|
return None if row is None else to_product(row)
|
|
|
|
def add_product(self, row: orm.ProductRecord) -> Product:
|
|
self.session.add(row)
|
|
self.session.flush()
|
|
return to_product(row)
|
|
|
|
def get_variant(self, variant_id: UUID) -> ProductVariant | None:
|
|
row = self.session.get(orm.ProductVariantRecord, variant_id)
|
|
return None if row is None else to_variant(row)
|
|
|
|
def add_variant(self, row: orm.ProductVariantRecord) -> ProductVariant:
|
|
self.session.add(row)
|
|
self.session.flush()
|
|
return to_variant(row)
|
|
|
|
def list_attributes(self, variant_id: UUID) -> list[AttributeStatement]:
|
|
rows = self.session.scalars(
|
|
select(orm.VariantAttributeRecord)
|
|
.where(orm.VariantAttributeRecord.variant_id == variant_id)
|
|
.order_by(
|
|
orm.VariantAttributeRecord.observed_at,
|
|
orm.VariantAttributeRecord.ingested_at,
|
|
orm.VariantAttributeRecord.id,
|
|
)
|
|
).all()
|
|
return [to_attribute(row) for row in rows]
|
|
|
|
def get_attribute(self, attribute_id: UUID) -> AttributeStatement | None:
|
|
row = self.session.get(orm.VariantAttributeRecord, attribute_id)
|
|
return None if row is None else to_attribute(row)
|
|
|
|
def add_attribute(self, row: orm.VariantAttributeRecord) -> AttributeStatement:
|
|
self.session.add(row)
|
|
self.session.flush()
|
|
return to_attribute(row)
|
|
|
|
def get_offer(self, offer_id: UUID) -> Offer | None:
|
|
row = self.session.get(orm.OfferRecord, offer_id)
|
|
return None if row is None else to_offer(row)
|
|
|
|
def get_offer_by_external(self, source_id: str, external_id: str) -> Offer | None:
|
|
row = self.session.scalar(
|
|
select(orm.OfferRecord).where(
|
|
orm.OfferRecord.source_id == source_id,
|
|
orm.OfferRecord.external_id == external_id,
|
|
)
|
|
)
|
|
return None if row is None else to_offer(row)
|
|
|
|
def add_offer(self, row: orm.OfferRecord) -> Offer:
|
|
self.session.add(row)
|
|
self.session.flush()
|
|
return to_offer(row)
|
|
|
|
def list_observations(self, offer_id: UUID, limit: int | None = None, offset: int = 0) -> tuple[list[PriceObservation], int]:
|
|
total = (
|
|
self.session.scalar(
|
|
select(func.count())
|
|
.select_from(orm.PriceObservationRecord)
|
|
.where(orm.PriceObservationRecord.offer_id == offer_id)
|
|
)
|
|
or 0
|
|
)
|
|
query = (
|
|
select(orm.PriceObservationRecord)
|
|
.where(orm.PriceObservationRecord.offer_id == offer_id)
|
|
.order_by(
|
|
orm.PriceObservationRecord.observed_at,
|
|
orm.PriceObservationRecord.ingested_at,
|
|
orm.PriceObservationRecord.id,
|
|
)
|
|
.offset(offset)
|
|
)
|
|
if limit is not None:
|
|
query = query.limit(limit)
|
|
rows = self.session.scalars(query).all()
|
|
return [to_observation(row) for row in rows], total
|
|
|
|
def get_observation_by_external(self, offer_id: UUID, external_id: str) -> orm.PriceObservationRecord | None:
|
|
return self.session.scalar(
|
|
select(orm.PriceObservationRecord).where(
|
|
orm.PriceObservationRecord.offer_id == offer_id,
|
|
orm.PriceObservationRecord.external_observation_id == external_id,
|
|
)
|
|
)
|
|
|
|
def add_observation(self, row: orm.PriceObservationRecord) -> PriceObservation:
|
|
self.session.add(row)
|
|
self.session.flush()
|
|
return to_observation(row)
|
|
|
|
def get_evaluation(self, evaluation_id: UUID) -> orm.EvaluationRecord | None:
|
|
return self.session.get(orm.EvaluationRecord, evaluation_id)
|
|
|
|
def add_evaluation(self, row: orm.EvaluationRecord) -> orm.EvaluationRecord:
|
|
self.session.add(row)
|
|
self.session.flush()
|
|
return row
|
|
|
|
def add_component(self, row: orm.EvaluationComponentRecord) -> None:
|
|
self.session.add(row)
|