BuyingAgent/backend/app/persistence/repositories.py

184 lines
7.4 KiB
Python
Raw Normal View History

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)