This commit is contained in:
107
database/external_market_models.py
Normal file
107
database/external_market_models.py
Normal file
@@ -0,0 +1,107 @@
|
||||
#!/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"),
|
||||
)
|
||||
@@ -48,6 +48,7 @@ from .market_intel_models import ( # noqa: F401 - ADR-035 market_* 表
|
||||
MarketCrawlerRun,
|
||||
MarketAlertReviewQueue,
|
||||
)
|
||||
from .external_market_models import ExternalMarketSource, ExternalOffer # noqa: F401 - 外部市場正規化表
|
||||
|
||||
# 🚩 導入優化後的日誌管理模組
|
||||
from utils.logger_manager import SystemLogger
|
||||
|
||||
Reference in New Issue
Block a user