324 lines
17 KiB
Python
324 lines
17 KiB
Python
from datetime import datetime
|
|
from decimal import Decimal
|
|
from typing import Any
|
|
from uuid import UUID
|
|
|
|
from sqlalchemy import Boolean, DateTime, ForeignKey, ForeignKeyConstraint, Integer, Numeric, String, Text, UniqueConstraint
|
|
from sqlalchemy.dialects.postgresql import JSONB, UUID as PGUUID
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from app.persistence.models import Base
|
|
|
|
|
|
class ProductCategoryRecord(Base):
|
|
__tablename__ = "product_categories"
|
|
|
|
id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
name: Mapped[str] = mapped_column(String(256), nullable=False)
|
|
status: Mapped[str] = mapped_column(String(32), nullable=False, default="ACTIVE")
|
|
|
|
|
|
class BuyingProjectRecord(Base):
|
|
__tablename__ = "buying_projects"
|
|
__table_args__ = (
|
|
ForeignKeyConstraint(
|
|
["template_id", "template_version"],
|
|
["product_profiles.id", "product_profiles.version"],
|
|
name="fk_buying_projects_template",
|
|
),
|
|
)
|
|
|
|
id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True)
|
|
name: Mapped[str] = mapped_column(String(256), nullable=False)
|
|
status: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
workspace_id: Mapped[str | None] = mapped_column(String(64))
|
|
product_category_id: Mapped[str] = mapped_column(ForeignKey("product_categories.id"), nullable=False)
|
|
market: Mapped[str] = mapped_column(String(8), nullable=False)
|
|
currency: Mapped[str] = mapped_column(String(3), nullable=False)
|
|
target_budget: Mapped[Decimal | None] = mapped_column(Numeric(12, 2))
|
|
template_id: Mapped[str | None] = mapped_column(String(128))
|
|
template_version: Mapped[int | None] = mapped_column(Integer)
|
|
shortlist_size: Mapped[int] = mapped_column(Integer, nullable=False, default=5)
|
|
criteria_version: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
archived_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
|
|
@property
|
|
def profile_id(self) -> str | None:
|
|
return self.template_id
|
|
|
|
@property
|
|
def profile_version(self) -> int | None:
|
|
return self.template_version
|
|
|
|
|
|
class BuyingProjectCriteriaRecord(Base):
|
|
__tablename__ = "buying_project_criteria"
|
|
|
|
project_id: Mapped[UUID] = mapped_column(ForeignKey("buying_projects.id"), primary_key=True)
|
|
version: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
definition_hash: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
|
|
|
|
class ProjectCriterionRecord(Base):
|
|
__tablename__ = "project_criteria"
|
|
__table_args__ = (
|
|
ForeignKeyConstraint(
|
|
["project_id", "version"],
|
|
["buying_project_criteria.project_id", "buying_project_criteria.version"],
|
|
name="fk_project_criteria_version",
|
|
),
|
|
UniqueConstraint("project_id", "version", "item_id", name="uq_project_criterion_item"),
|
|
)
|
|
|
|
id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True)
|
|
project_id: Mapped[UUID] = mapped_column(nullable=False)
|
|
version: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
item_id: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
criterion_definition_id: Mapped[UUID] = mapped_column(ForeignKey("criterion_definitions.id"), nullable=False)
|
|
criterion_key: Mapped[str | None] = mapped_column(String(128))
|
|
operator: Mapped[str] = mapped_column(String(16), nullable=False)
|
|
value: Mapped[Any] = mapped_column(JSONB)
|
|
enabled_for_discovery: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
|
|
@property
|
|
def mandatory(self) -> bool:
|
|
return self.enabled_for_discovery
|
|
|
|
@property
|
|
def criteria_version(self) -> int:
|
|
return self.version
|
|
|
|
|
|
class SourceCategoryRecord(Base):
|
|
__tablename__ = "source_categories"
|
|
__table_args__ = (UniqueConstraint("source_id", "external_category_key", name="uq_source_category"),)
|
|
|
|
id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True)
|
|
source_id: Mapped[str] = mapped_column(ForeignKey("sources.id"), nullable=False)
|
|
external_category_key: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
name: Mapped[str] = mapped_column(String(256), nullable=False)
|
|
source_url: Mapped[str] = mapped_column(Text, nullable=False)
|
|
product_category_id: Mapped[str | None] = mapped_column(ForeignKey("product_categories.id"))
|
|
canonical_category: Mapped[str | None] = mapped_column(String(128))
|
|
status: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
taxonomy_status: Mapped[str] = mapped_column(String(32), nullable=False, default="NOT_LEARNED")
|
|
last_taxonomy_scan_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
last_verified_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
|
|
|
|
class SourceFilterDefinitionRecord(Base):
|
|
__tablename__ = "source_filter_definitions"
|
|
__table_args__ = (UniqueConstraint("source_category_id", "external_filter_key", name="uq_source_filter"),)
|
|
|
|
id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True)
|
|
source_category_id: Mapped[UUID] = mapped_column(ForeignKey("source_categories.id"), nullable=False)
|
|
external_filter_key: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
label: Mapped[str] = mapped_column(String(256), nullable=False)
|
|
filter_type: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
canonical_unit: Mapped[str | None] = mapped_column(String(32))
|
|
status: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
|
|
|
|
class SourceFilterOptionRecord(Base):
|
|
__tablename__ = "source_filter_options"
|
|
__table_args__ = (UniqueConstraint("filter_id", "source_value", name="uq_filter_option"),)
|
|
|
|
id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True)
|
|
filter_id: Mapped[UUID] = mapped_column(ForeignKey("source_filter_definitions.id"), nullable=False)
|
|
source_value: Mapped[str] = mapped_column(String(256), nullable=False)
|
|
normalized_value: Mapped[Any] = mapped_column(JSONB)
|
|
operator: Mapped[str | None] = mapped_column(String(16))
|
|
unit: Mapped[str | None] = mapped_column(String(32))
|
|
|
|
|
|
class SourceFilterRangeRecord(Base):
|
|
__tablename__ = "source_filter_ranges"
|
|
|
|
filter_id: Mapped[UUID] = mapped_column(ForeignKey("source_filter_definitions.id"), primary_key=True)
|
|
minimum: Mapped[Decimal | None] = mapped_column(Numeric(12, 2))
|
|
maximum: Mapped[Decimal | None] = mapped_column(Numeric(12, 2))
|
|
step: Mapped[Decimal | None] = mapped_column(Numeric(12, 2))
|
|
unit: Mapped[str | None] = mapped_column(String(32))
|
|
|
|
|
|
class CriterionDefinitionRecord(Base):
|
|
__tablename__ = "criterion_definitions"
|
|
__table_args__ = (UniqueConstraint("key", name="uq_criterion_definition_key"),)
|
|
|
|
id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True)
|
|
key: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
origin: Mapped[str] = mapped_column(String(16), nullable=False, default="SOURCE")
|
|
source: Mapped[str | None] = mapped_column(String(64))
|
|
product_category_id: Mapped[str | None] = mapped_column(ForeignKey("product_categories.id"))
|
|
label: Mapped[str] = mapped_column(String(256), nullable=False)
|
|
value_type: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
unit: Mapped[str | None] = mapped_column(String(32))
|
|
semantic: Mapped[str | None] = mapped_column(String(128))
|
|
category: Mapped[str | None] = mapped_column(String(128))
|
|
description: Mapped[str | None] = mapped_column(Text)
|
|
scoring_attribute_key: Mapped[str | None] = mapped_column(String(128))
|
|
|
|
|
|
class SourceAttributeMappingRecord(Base):
|
|
__tablename__ = "source_attribute_mappings"
|
|
|
|
id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True)
|
|
source_id: Mapped[str] = mapped_column(ForeignKey("sources.id"), nullable=False)
|
|
source_category_id: Mapped[UUID | None] = mapped_column(ForeignKey("source_categories.id"))
|
|
raw_name: Mapped[str] = mapped_column(String(256), nullable=False)
|
|
criterion_definition_id: Mapped[UUID] = mapped_column(ForeignKey("criterion_definitions.id"), nullable=False)
|
|
status: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
|
|
|
|
class CriterionMappingRecord(Base):
|
|
__tablename__ = "criterion_mappings"
|
|
|
|
id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True)
|
|
criterion_definition_id: Mapped[UUID] = mapped_column(ForeignKey("criterion_definitions.id"), nullable=False)
|
|
criterion_key: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
source_id: Mapped[str] = mapped_column(ForeignKey("sources.id"), nullable=False)
|
|
source_category_id: Mapped[UUID] = mapped_column(ForeignKey("source_categories.id"), nullable=False)
|
|
external_filter_key: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
source_value: Mapped[str | None] = mapped_column(String(256))
|
|
encoding: Mapped[dict[str, Any]] = mapped_column(JSONB, nullable=False)
|
|
status: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
definition_hash: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
|
|
|
|
class DiscoverySearchRecord(Base):
|
|
__tablename__ = "discovery_searches"
|
|
|
|
id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True)
|
|
buying_project_id: Mapped[UUID] = mapped_column(ForeignKey("buying_projects.id"), nullable=False)
|
|
source_id: Mapped[str] = mapped_column(ForeignKey("sources.id"), nullable=False)
|
|
source_category_id: Mapped[UUID] = mapped_column(ForeignKey("source_categories.id"), nullable=False)
|
|
criteria_version: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
mapping_version: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
generated_url: Mapped[str] = mapped_column(Text, nullable=False)
|
|
status: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
schedule_interval_minutes: Mapped[int] = mapped_column(Integer, nullable=False, default=180)
|
|
broad_result_threshold: Mapped[int] = mapped_column(Integer, nullable=False, default=300)
|
|
next_run_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
last_successful_run_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
|
|
|
|
class SearchRunRecord(Base):
|
|
__tablename__ = "search_runs"
|
|
|
|
id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True)
|
|
discovery_search_id: Mapped[UUID] = mapped_column(ForeignKey("discovery_searches.id"), nullable=False)
|
|
criteria_version: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
mapping_version: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
generated_url: Mapped[str] = mapped_column(Text, nullable=False)
|
|
started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
status: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
result_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
page_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
new_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
changed_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
unchanged_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
missing_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
http_status_summary: Mapped[dict[str, Any]] = mapped_column(JSONB, nullable=False, default=dict)
|
|
error_code: Mapped[str | None] = mapped_column(String(64))
|
|
|
|
|
|
class SourceProductReferenceRecord(Base):
|
|
__tablename__ = "source_product_references"
|
|
|
|
id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True)
|
|
source_id: Mapped[str] = mapped_column(ForeignKey("sources.id"), nullable=False)
|
|
source_category_id: Mapped[UUID] = mapped_column(ForeignKey("source_categories.id"), nullable=False)
|
|
source_external_id: Mapped[str | None] = mapped_column(String(256))
|
|
source_url: Mapped[str | None] = mapped_column(Text)
|
|
source_title: Mapped[str] = mapped_column(String(512), nullable=False)
|
|
manufacturer: Mapped[str | None] = mapped_column(String(256))
|
|
model: Mapped[str | None] = mapped_column(String(256))
|
|
manufacturer_sku: Mapped[str | None] = mapped_column(String(128))
|
|
gtin_ean: Mapped[str | None] = mapped_column(String(32))
|
|
normalized_spec_fingerprint: Mapped[str | None] = mapped_column(Text)
|
|
first_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
last_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
lifecycle_status: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
match_status: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
consecutive_missing_success_runs: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
product_variant_id: Mapped[UUID | None] = mapped_column(ForeignKey("product_variants.id"))
|
|
|
|
|
|
class ProductObservationRecord(Base):
|
|
__tablename__ = "product_observations"
|
|
|
|
id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True)
|
|
source_product_reference_id: Mapped[UUID] = mapped_column(ForeignKey("source_product_references.id"), nullable=False)
|
|
search_run_id: Mapped[UUID] = mapped_column(ForeignKey("search_runs.id"), nullable=False)
|
|
observed_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
raw_title: Mapped[str] = mapped_column(String(512), nullable=False)
|
|
best_price: Mapped[Decimal | None] = mapped_column(Numeric(12, 2))
|
|
currency: Mapped[str | None] = mapped_column(String(3))
|
|
availability: Mapped[str | None] = mapped_column(String(32))
|
|
seller_count: Mapped[int | None] = mapped_column(Integer)
|
|
|
|
|
|
class ProductObservationAttributeRecord(Base):
|
|
__tablename__ = "product_observation_attributes"
|
|
__table_args__ = (UniqueConstraint("observation_id", "attribute_key", name="uq_observation_attribute"),)
|
|
|
|
id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True)
|
|
observation_id: Mapped[UUID] = mapped_column(ForeignKey("product_observations.id"), nullable=False)
|
|
criterion_definition_id: Mapped[UUID | None] = mapped_column(ForeignKey("criterion_definitions.id"))
|
|
attribute_key: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
raw_name: Mapped[str | None] = mapped_column(String(256))
|
|
raw_value: Mapped[Any] = mapped_column(JSONB)
|
|
normalized_value: Mapped[Any] = mapped_column(JSONB)
|
|
unit: Mapped[str | None] = mapped_column(String(32))
|
|
|
|
|
|
class SearchRunItemRecord(Base):
|
|
__tablename__ = "search_run_items"
|
|
__table_args__ = (UniqueConstraint("search_run_id", "source_product_reference_id", name="uq_run_item"),)
|
|
|
|
id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True)
|
|
search_run_id: Mapped[UUID] = mapped_column(ForeignKey("search_runs.id"), nullable=False)
|
|
source_product_reference_id: Mapped[UUID] = mapped_column(ForeignKey("source_product_references.id"), nullable=False)
|
|
observation_id: Mapped[UUID | None] = mapped_column(ForeignKey("product_observations.id"))
|
|
diff_status: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
|
|
|
|
class ProjectCandidateRecord(Base):
|
|
__tablename__ = "project_candidates"
|
|
__table_args__ = (
|
|
UniqueConstraint("buying_project_id", "source_product_reference_id", name="uq_project_candidate"),
|
|
)
|
|
|
|
id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True)
|
|
buying_project_id: Mapped[UUID] = mapped_column(ForeignKey("buying_projects.id"), nullable=False)
|
|
source_product_reference_id: Mapped[UUID] = mapped_column(ForeignKey("source_product_references.id"), nullable=False)
|
|
product_variant_id: Mapped[UUID | None] = mapped_column(ForeignKey("product_variants.id"))
|
|
status: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
match_status: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
product_eligibility: Mapped[str | None] = mapped_column(String(16))
|
|
product_score: Mapped[Decimal | None] = mapped_column(Numeric(6, 2))
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
|
|
|
|
class CandidateStatusHistoryRecord(Base):
|
|
__tablename__ = "candidate_status_history"
|
|
|
|
id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True)
|
|
candidate_id: Mapped[UUID] = mapped_column(ForeignKey("project_candidates.id"), nullable=False)
|
|
from_status: Mapped[str | None] = mapped_column(String(32))
|
|
to_status: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
reason: Mapped[str | None] = mapped_column(String(128))
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|