采集端与 data-hub 统一用 style_id (= dim_style.id) 关联款式,style_name 降级为显示冗余。
款式主键:
- 14 张采集表加 style_id BIGINT + style_match_method + *_style_id_idx 索引
- 一律逻辑外键:不建 FOREIGN KEY,早期 10 个 *_style_id_fk 在 schema.sql 里幂等 DROP
- loader 下发 dim_style.id;db 层 attach_style_ids / resolve_style_ids_by_product 兜底
- 新增 backfill_style_id.py 三段回填(款名 / 日报按商品 ID / 广告表按 spu_id)
- 新增视图 v_style_id_coverage、v_style_unmatched_product、v_style_unmatched_source
- 实库补录 199 个商品 ID 到 67 款 spus;日报 style_id 覆盖 49.1% -> 79.0%,
tm 广告 99.4% / jd 92.4% / dy 83.2%,其余 11 张 100%
评价采集下线:
- product_reviews 归档为 product_reviews_archived_20261101
- 差评改由 shop.{tmall,jd,dy}_negative_reviews.daily 采集,消费方读 v_negative_reviews
- 删除 import_product_reviews.py / orchestrate_review_collection.py 及相关命令与绑定
- dy_product_scraping 默认流程摘除评论采集,保留 --reviews-only
天猫广告:
- 迁移到独立表 tm_product_ad_daily_metrics,不再写 product_daily_metrics.raw_data
同批附带:
- 光合上传支持天猫新店 new_bag(仅黑曜石系列)
- 京东商智双月日历排除 -diff-month 补位格
- 万相主体搜索词覆盖 STYLE_SEARCH_KEYWORDS
- ERP 部分编码失败时用已成功编码销量兜底,避免整款归零
84 lines
2.9 KiB
Python
84 lines
2.9 KiB
Python
from __future__ import annotations
|
|
|
|
import tomllib
|
|
from pathlib import Path
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
BUSINESS_MODULES = (
|
|
"content_marketing",
|
|
"product_commerce",
|
|
"shop_intelligence",
|
|
"supply_chain",
|
|
)
|
|
|
|
|
|
def test_repository_keeps_the_simplified_public_structure() -> None:
|
|
modules_root = PROJECT_ROOT / "src" / "gyxx_flow" / "modules"
|
|
for module in BUSINESS_MODULES:
|
|
module_root = modules_root / module
|
|
compatibility_items = {
|
|
path.name
|
|
for path in (module_root / "runtime").iterdir()
|
|
if path.name != "__pycache__"
|
|
}
|
|
assert compatibility_items == {"__init__.py"}
|
|
assert not (module_root / "tests").exists()
|
|
assert not (module_root / "runtime_tests").exists()
|
|
|
|
primary_docs = {
|
|
path.name for path in (PROJECT_ROOT / "docs").iterdir() if path.is_file()
|
|
}
|
|
assert primary_docs == {
|
|
"acceptance-report.md",
|
|
"architecture.md",
|
|
"deployment.md",
|
|
"dynamic-workflow-config.md",
|
|
"note-inventory.md",
|
|
"plan.md",
|
|
"runbook.md",
|
|
"style-identity-refactor-plan.md",
|
|
"workflow-acceptance-test-report.md",
|
|
"workflow-acceptance-test-requirements.md",
|
|
"workflow-console.md",
|
|
"workflows-overview.md",
|
|
"workflow-time-distribution.md",
|
|
}
|
|
assert not {
|
|
"design.md",
|
|
"final_report.md",
|
|
"plan.md",
|
|
"review.md",
|
|
} & {path.name for path in PROJECT_ROOT.iterdir() if path.is_file()}
|
|
|
|
|
|
def test_development_group_contains_reproducible_quality_tools() -> None:
|
|
configuration = tomllib.loads(
|
|
(PROJECT_ROOT / "pyproject.toml").read_text(encoding="utf-8")
|
|
)
|
|
|
|
development = configuration["dependency-groups"]["dev"]
|
|
package_names = {requirement.split(">=", 1)[0] for requirement in development}
|
|
|
|
assert {"build", "pytest", "ruff"} <= package_names
|
|
assert (
|
|
"src/gyxx_flow/modules/*/**"
|
|
in configuration["tool"]["ruff"]["extend-exclude"]
|
|
)
|
|
assert configuration["tool"]["pytest"]["ini_options"]["testpaths"] == ["tests"]
|
|
|
|
|
|
def test_gitlab_ci_runs_lock_lint_tests_and_build() -> None:
|
|
pipeline = (PROJECT_ROOT / ".gitlab-ci.yml").read_text(encoding="utf-8")
|
|
|
|
assert "uv sync --frozen --group dev" in pipeline
|
|
assert "uv run ruff check src tests" in pipeline
|
|
assert "uv run ruff check src/gyxx_flow/modules/content_marketing/daily_creator_exposure_scope.py" in pipeline
|
|
assert "src/gyxx_flow/modules/product_commerce/market_rank_product_import.py" in pipeline
|
|
assert "uv run pytest" in pipeline
|
|
assert "test_daily_creator_exposure_scope.py" in pipeline
|
|
assert "test_chanmama_refresh_before_export.py" in pipeline
|
|
assert "test_daily_exposure_fields.py" in pipeline
|
|
assert "test_market_rank_limits.py" in pipeline
|
|
assert "test_market_rank_product_import.py" in pipeline
|
|
assert "uv build" in pipeline
|