108 lines
4.0 KiB
Python
108 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""外部市場來源與報價的正規化 ORM models。"""
|
|
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
from sqlalchemy import (
|
|
Boolean,
|
|
Column,
|
|
DateTime,
|
|
Float,
|
|
ForeignKey,
|
|
Index,
|
|
Integer,
|
|
String,
|
|
Text,
|
|
UniqueConstraint,
|
|
)
|
|
from sqlalchemy.orm import relationship
|
|
|
|
from database.models import Base
|
|
|
|
|
|
TAIPEI_TZ = timezone(timedelta(hours=8))
|
|
|
|
|
|
def taipei_now():
|
|
return datetime.now(TAIPEI_TZ).replace(tzinfo=None)
|
|
|
|
|
|
class ExternalMarketSource(Base):
|
|
"""外部市場資料來源,例如 MOMO 參考價、蝦皮 API、酷澎 CSV。"""
|
|
|
|
__tablename__ = "external_market_sources"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
code = Column(String(80), unique=True, nullable=False, index=True)
|
|
display_name = Column(String(160), nullable=False)
|
|
platform_code = Column(String(80), nullable=False, index=True)
|
|
source_kind = Column(String(60), nullable=False, index=True)
|
|
status = Column(String(40), default="paused", nullable=False, index=True)
|
|
enabled = Column(Boolean, default=False, nullable=False)
|
|
write_enabled = Column(Boolean, default=False, nullable=False)
|
|
allowed_input_methods_json = Column(Text)
|
|
quality_policy_json = Column(Text)
|
|
plain_note = Column(Text)
|
|
created_at = Column(DateTime, default=taipei_now, nullable=False)
|
|
updated_at = Column(DateTime, default=taipei_now, onupdate=taipei_now, nullable=False)
|
|
|
|
offers = relationship("ExternalOffer", back_populates="source")
|
|
|
|
__table_args__ = (
|
|
Index("idx_external_market_sources_status", "status", "enabled"),
|
|
)
|
|
|
|
|
|
class ExternalOffer(Base):
|
|
"""正規化後的外部商品報價。"""
|
|
|
|
__tablename__ = "external_offers"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
source_code = Column(String(80), ForeignKey("external_market_sources.code"), nullable=False, index=True)
|
|
platform_code = Column(String(80), nullable=False, index=True)
|
|
source_product_id = Column(String(220), nullable=False, index=True)
|
|
source_offer_key = Column(String(260), nullable=False)
|
|
title = Column(Text, nullable=False)
|
|
brand = Column(String(180), index=True)
|
|
category_text = Column(String(320), index=True)
|
|
product_url = Column(Text)
|
|
image_url = Column(Text)
|
|
price = Column(Float)
|
|
original_price = Column(Float)
|
|
currency = Column(String(12), default="TWD", nullable=False)
|
|
stock_status = Column(String(80), index=True)
|
|
sold_count = Column(Integer)
|
|
rating = Column(Float)
|
|
review_count = Column(Integer)
|
|
observed_at = Column(DateTime, default=taipei_now, nullable=False, index=True)
|
|
expires_at = Column(DateTime, index=True)
|
|
ingestion_method = Column(String(60), nullable=False, index=True)
|
|
connector_key = Column(String(120), index=True)
|
|
pchome_product_id = Column(String(120), index=True)
|
|
momo_sku = Column(String(80), index=True)
|
|
match_status = Column(String(40), default="unmatched", nullable=False, index=True)
|
|
quality_score = Column(Float, default=0.0, nullable=False)
|
|
data_quality_status = Column(String(40), default="needs_review", nullable=False, index=True)
|
|
quality_notes_json = Column(Text)
|
|
raw_payload_json = Column(Text)
|
|
created_at = Column(DateTime, default=taipei_now, nullable=False)
|
|
updated_at = Column(DateTime, default=taipei_now, onupdate=taipei_now, nullable=False)
|
|
|
|
source = relationship("ExternalMarketSource", back_populates="offers")
|
|
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"source_code",
|
|
"source_product_id",
|
|
"observed_at",
|
|
"ingestion_method",
|
|
name="uq_external_offer_source_product_observed",
|
|
),
|
|
Index("idx_external_offers_source_seen", "source_code", "observed_at"),
|
|
Index("idx_external_offers_platform_product", "platform_code", "source_product_id"),
|
|
Index("idx_external_offers_pchome_product", "pchome_product_id", "source_code"),
|
|
Index("idx_external_offers_match_quality", "match_status", "data_quality_status", "quality_score"),
|
|
)
|