{ "built_at": "2026-02-26T07:47:14.502315+08:00", "prompt_id": "P20260226-074515", "prompt_at": "2026-02-26T07:45:15.438463+08:00", "audit_required": true, "db_docs_required": true, "reasons": [ "root-file", "dir:backend", "dir:etl", "dir:db", "db-schema-change", "dir:shared" ], "changed_files": [ ".gitignore", "README.md", "ai-icon-demo-deep-color.png", "ai-icon-demo-final.png", "ai-icon-demo-full.png", "ai-icon-demo-light-border.png", "ai-icon-demo-light-v1.png", "ai-icon-demo-light-v2.png", "ai-icon-demo-v2.png", "ai-icon-demo-v3.png", "ai-icon-demo-white-glow.png", "apps/admin-web/README.md", "apps/backend/app/auth/dependencies.py", "apps/backend/app/auth/jwt.py", "apps/backend/app/main.py", "apps/backend/app/routers/xcx_auth.py", "apps/backend/app/schemas/xcx_auth.py", "apps/backend/app/services/application.py", "apps/backend/app/services/matching.py", "apps/backend/app/services/role.py", "apps/backend/app/services/task_registry.py", "apps/backend/app/services/wechat.py", "apps/backend/auth_only.txt", "apps/backend/auth_only_results.txt", "apps/backend/auth_test_results.txt", "apps/backend/docs/", "apps/backend/test_results.txt", "apps/etl/connectors/feiqiu/docs/CHANGELOG.md", "apps/etl/connectors/feiqiu/docs/README.md", "apps/etl/connectors/feiqiu/docs/business-rules/dws_metrics.md", "apps/etl/connectors/feiqiu/docs/business-rules/scd2_rules.md", "apps/etl/connectors/feiqiu/docs/etl_tasks/README.md", "apps/etl/connectors/feiqiu/docs/etl_tasks/base_task_mechanism.md", "apps/etl/connectors/feiqiu/docs/etl_tasks/dws_tasks.md", "apps/etl/connectors/feiqiu/docs/etl_tasks/index_tasks.md", "apps/etl/connectors/feiqiu/docs/etl_tasks/ods_tasks.md", "apps/etl/connectors/feiqiu/docs/operations/environment_setup.md", "apps/etl/connectors/feiqiu/docs/operations/troubleshooting.md", "apps/etl/connectors/feiqiu/orchestration/flow_runner.py", "apps/etl/connectors/feiqiu/orchestration/task_executor.py", "apps/etl/connectors/feiqiu/orchestration/task_registry.py", "apps/etl/connectors/feiqiu/orchestration/topological_sort.py", "apps/etl/connectors/feiqiu/quality/consistency_checker.py", "apps/etl/connectors/feiqiu/scripts/verify_dws_extensions.py", "apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py", "apps/etl/connectors/feiqiu/tasks/dws/__init__.py", "apps/etl/connectors/feiqiu/tasks/dws/assistant_daily_task.py", "apps/etl/connectors/feiqiu/tasks/dws/assistant_order_contribution_task.py", "apps/etl/connectors/feiqiu/tasks/dws/member_consumption_task.py", "apps/etl/connectors/feiqiu/tasks/dws/member_visit_task.py" ], "high_risk_files": [ "apps/backend/app/auth/dependencies.py", "apps/backend/app/auth/jwt.py", "apps/backend/app/main.py", "apps/backend/app/routers/xcx_auth.py", "apps/backend/app/schemas/xcx_auth.py", "apps/backend/app/services/application.py", "apps/backend/app/services/matching.py", "apps/backend/app/services/role.py", "apps/backend/app/services/task_registry.py", "apps/backend/app/services/wechat.py", "apps/etl/connectors/feiqiu/orchestration/flow_runner.py", "apps/etl/connectors/feiqiu/orchestration/task_executor.py", "apps/etl/connectors/feiqiu/orchestration/task_registry.py", "apps/etl/connectors/feiqiu/orchestration/topological_sort.py", "apps/etl/connectors/feiqiu/quality/consistency_checker.py", "apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py", "apps/etl/connectors/feiqiu/tasks/dws/__init__.py", "apps/etl/connectors/feiqiu/tasks/dws/assistant_daily_task.py", "apps/etl/connectors/feiqiu/tasks/dws/assistant_order_contribution_task.py", "apps/etl/connectors/feiqiu/tasks/dws/member_consumption_task.py", "apps/etl/connectors/feiqiu/tasks/dws/member_visit_task.py" ], "external_files": [ "db/zqyy_app/migrations/2026-02-25__p3_create_auth_tables.sql", "db/zqyy_app/migrations/2026-02-25__p3_seed_roles_permissions.sql", "docs/DOCUMENTATION-MAP.md", "docs/README.md", "docs/database/BD_Manual_app_schema_rls_views.md", "docs/database/BD_Manual_auth_biz_schemas.md", "docs/database/BD_Manual_auth_tables.md", "docs/database/BD_Manual_dws_assistant_order_contribution.md", "docs/database/BD_Manual_dws_member_spending_power_index.md", "docs/database/BD_Manual_fdw_etl_setup.md", "docs/database/BD_Manual_goods_stock_warning_info.md", "docs/database/README.md", "docs/database/ddl/etl_feiqiu__app.sql", "docs/database/ddl/etl_feiqiu__core.sql", "docs/database/ddl/etl_feiqiu__dwd.sql", "docs/database/ddl/etl_feiqiu__dws.sql", "docs/database/ddl/etl_feiqiu__meta.sql", "docs/database/ddl/etl_feiqiu__ods.sql", "docs/database/ddl/fdw.sql", "docs/database/ddl/zqyy_app__auth.sql", "docs/database/ddl/zqyy_app__public.sql", "docs/deployment/LAUNCH-CHECKLIST.md", "docs/h5_ui/css/ai-icons.css", "docs/h5_ui/index.html", "docs/h5_ui/js/ai-icons.js", "docs/h5_ui/pages/ai-icon-demo.html", "docs/h5_ui/pages/board-finance.html", "docs/h5_ui/pages/customer-detail.html", "docs/h5_ui/pages/feiqiu-ETL.code-workspace", "docs/h5_ui/pages/my-profile.html", "docs/h5_ui/pages/task-detail-callback.html", "docs/h5_ui/pages/task-detail-priority.html", "docs/h5_ui/pages/task-detail-relationship.html", "docs/h5_ui/pages/task-detail.html", "docs/h5_ui/pages/task-list.html", "docs/prd/specs/00-/346/225/260/346/215/256/344/276/235/350/265/226/347/237/251/351/230/265.md", "docs/prd/specs/01-SPEC/344/273/273/345/212/241/346/213/206/345/210/206/346/200/273/350/247/210.md", "docs/prd/specs/P10-tenant-admin-web.md", "docs/prd/specs/P2-etl-dws-miniapp-extensions.md", "docs/prd/specs/P3-miniapp-auth-system.md", "docs/roadmap/2026-02-24__fdw-dwd-to-core-migration-plan.md", "docs/roadmap/BACKLOG.md", "export/ETL-Connectors/feiqiu/JSON/ODS_ASSISTANT_ACCOUNT/ODS_ASSISTANT_ACCOUNT-8931-20260224-002444/", "export/ETL-Connectors/feiqiu/JSON/ODS_ASSISTANT_ACCOUNT/ODS_ASSISTANT_ACCOUNT-8952-20260224-021528/", "export/ETL-Connectors/feiqiu/JSON/ODS_ASSISTANT_ACCOUNT/ODS_ASSISTANT_ACCOUNT-8983-20260224-062414/", "export/ETL-Connectors/feiqiu/JSON/ODS_ASSISTANT_ACCOUNT/ODS_ASSISTANT_ACCOUNT-9012-20260224-122723/", "export/ETL-Connectors/feiqiu/JSON/ODS_ASSISTANT_ACCOUNT/ODS_ASSISTANT_ACCOUNT-9041-20260225-001547/", "export/ETL-Connectors/feiqiu/JSON/ODS_ASSISTANT_ACCOUNT/ODS_ASSISTANT_ACCOUNT-9069-20260225-050638/", "export/ETL-Connectors/feiqiu/JSON/ODS_ASSISTANT_LEDGER/ODS_ASSISTANT_LEDGER-8932-20260224-002450/", "export/ETL-Connectors/feiqiu/JSON/ODS_ASSISTANT_LEDGER/ODS_ASSISTANT_LEDGER-8953-20260224-021535/", "export/ETL-Connectors/feiqiu/JSON/ODS_ASSISTANT_LEDGER/ODS_ASSISTANT_LEDGER-8984-20260224-062420/", "export/ETL-Connectors/feiqiu/JSON/ODS_ASSISTANT_LEDGER/ODS_ASSISTANT_LEDGER-9013-20260224-122730/", "export/ETL-Connectors/feiqiu/JSON/ODS_ASSISTANT_LEDGER/ODS_ASSISTANT_LEDGER-9042-20260225-001554/", "export/ETL-Connectors/feiqiu/JSON/ODS_ASSISTANT_LEDGER/ODS_ASSISTANT_LEDGER-9070-20260225-050644/", "export/ETL-Connectors/feiqiu/JSON/ODS_GOODS_CATEGORY/ODS_GOODS_CATEGORY-8965-20260224-022946/", "export/ETL-Connectors/feiqiu/JSON/ODS_GOODS_CATEGORY/ODS_GOODS_CATEGORY-8995-20260224-063823/", "export/ETL-Connectors/feiqiu/JSON/ODS_GOODS_CATEGORY/ODS_GOODS_CATEGORY-9024-20260224-124218/", "export/ETL-Connectors/feiqiu/JSON/ODS_GOODS_CATEGORY/ODS_GOODS_CATEGORY-9053-20260225-003115/", "export/ETL-Connectors/feiqiu/JSON/ODS_GOODS_CATEGORY/ODS_GOODS_CATEGORY-9081-20260225-052134/", "export/ETL-Connectors/feiqiu/JSON/ODS_GROUP_BUY_REDEMPTION/ODS_GROUP_BUY_REDEMPTION-8946-20260224-004949/", "export/ETL-Connectors/feiqiu/JSON/ODS_GROUP_BUY_REDEMPTION/ODS_GROUP_BUY_REDEMPTION-8971-20260224-023859/", "export/ETL-Connectors/feiqiu/JSON/ODS_GROUP_BUY_REDEMPTION/ODS_GROUP_BUY_REDEMPTION-9001-20260224-064657/", "export/ETL-Connectors/feiqiu/JSON/ODS_GROUP_BUY_REDEMPTION/ODS_GROUP_BUY_REDEMPTION-9030-20260224-125319/", "export/ETL-Connectors/feiqiu/JSON/ODS_GROUP_BUY_REDEMPTION/ODS_GROUP_BUY_REDEMPTION-9059-20260225-004056/", "export/ETL-Connectors/feiqiu/JSON/ODS_GROUP_BUY_REDEMPTION/ODS_GROUP_BUY_REDEMPTION-9087-20260225-053101/", "export/ETL-Connectors/feiqiu/JSON/ODS_GROUP_PACKAGE/ODS_GROUP_PACKAGE-8945-20260224-004946/", "export/ETL-Connectors/feiqiu/JSON/ODS_GROUP_PACKAGE/ODS_GROUP_PACKAGE-8970-20260224-023856/", "export/ETL-Connectors/feiqiu/JSON/ODS_GROUP_PACKAGE/ODS_GROUP_PACKAGE-9000-20260224-064654/", "export/ETL-Connectors/feiqiu/JSON/ODS_GROUP_PACKAGE/ODS_GROUP_PACKAGE-9029-20260224-125316/", "export/ETL-Connectors/feiqiu/JSON/ODS_GROUP_PACKAGE/ODS_GROUP_PACKAGE-9058-20260225-004053/", "export/ETL-Connectors/feiqiu/JSON/ODS_GROUP_PACKAGE/ODS_GROUP_PACKAGE-9086-20260225-053058/", "export/ETL-Connectors/feiqiu/JSON/ODS_INVENTORY_CHANGE/ODS_INVENTORY_CHANGE-8973-20260224-024333/", "export/ETL-Connectors/feiqiu/JSON/ODS_INVENTORY_CHANGE/ODS_INVENTORY_CHANGE-9003-20260224-065054/", "export/ETL-Connectors/feiqiu/JSON/ODS_INVENTORY_CHANGE/ODS_INVENTORY_CHANGE-9032-20260224-125752/", "export/ETL-Connectors/feiqiu/JSON/ODS_INVENTORY_CHANGE/ODS_INVENTORY_CHANGE-9061-20260225-004540/", "export/ETL-Connectors/feiqiu/JSON/ODS_INVENTORY_CHANGE/ODS_INVENTORY_CHANGE-9089-20260225-053508/", "export/ETL-Connectors/feiqiu/JSON/ODS_INVENTORY_STOCK/ODS_INVENTORY_STOCK-8972-20260224-024323/", "export/ETL-Connectors/feiqiu/JSON/ODS_INVENTORY_STOCK/ODS_INVENTORY_STOCK-9002-20260224-065046/", "export/ETL-Connectors/feiqiu/JSON/ODS_INVENTORY_STOCK/ODS_INVENTORY_STOCK-9031-20260224-125742/", "export/ETL-Connectors/feiqiu/JSON/ODS_INVENTORY_STOCK/ODS_INVENTORY_STOCK-9060-20260225-004531/", "export/ETL-Connectors/feiqiu/JSON/ODS_INVENTORY_STOCK/ODS_INVENTORY_STOCK-9088-20260225-053459/", "export/ETL-Connectors/feiqiu/JSON/ODS_MEMBER/ODS_MEMBER-8947-20260224-005404/", "export/ETL-Connectors/feiqiu/JSON/ODS_MEMBER/ODS_MEMBER-8961-20260224-022706/", "export/ETL-Connectors/feiqiu/JSON/ODS_MEMBER/ODS_MEMBER-8991-20260224-063552/", "export/ETL-Connectors/feiqiu/JSON/ODS_MEMBER/ODS_MEMBER-9020-20260224-123913/", "export/ETL-Connectors/feiqiu/JSON/ODS_MEMBER/ODS_MEMBER-9049-20260225-002816/", "export/ETL-Connectors/feiqiu/JSON/ODS_MEMBER/ODS_MEMBER-9077-20260225-051851/", "export/ETL-Connectors/feiqiu/JSON/ODS_MEMBER_BALANCE/ODS_MEMBER_BALANCE-8949-20260224-005446/", "export/ETL-Connectors/feiqiu/JSON/ODS_MEMBER_BALANCE/ODS_MEMBER_BALANCE-8963-20260224-022749/", "export/ETL-Connectors/feiqiu/JSON/ODS_MEMBER_BALANCE/ODS_MEMBER_BALANCE-8993-20260224-063631/", "export/ETL-Connectors/feiqiu/JSON/ODS_MEMBER_BALANCE/ODS_MEMBER_BALANCE-9022-20260224-124004/", "export/ETL-Connectors/feiqiu/JSON/ODS_MEMBER_BALANCE/ODS_MEMBER_BALANCE-9051-20260225-002911/", "export/ETL-Connectors/feiqiu/JSON/ODS_MEMBER_BALANCE/ODS_MEMBER_BALANCE-9079-20260225-051934/", "export/ETL-Connectors/feiqiu/JSON/ODS_MEMBER_CARD/ODS_MEMBER_CARD-8948-20260224-005416/", "export/ETL-Connectors/feiqiu/JSON/ODS_MEMBER_CARD/ODS_MEMBER_CARD-8962-20260224-022719/", "export/ETL-Connectors/feiqiu/JSON/ODS_MEMBER_CARD/ODS_MEMBER_CARD-8992-20260224-063604/", "export/ETL-Connectors/feiqiu/JSON/ODS_MEMBER_CARD/ODS_MEMBER_CARD-9021-20260224-123928/", "export/ETL-Connectors/feiqiu/JSON/ODS_MEMBER_CARD/ODS_MEMBER_CARD-9050-20260225-002831/", "export/ETL-Connectors/feiqiu/JSON/ODS_MEMBER_CARD/ODS_MEMBER_CARD-9078-20260225-051904/", "export/ETL-Connectors/feiqiu/JSON/ODS_PAYMENT/ODS_PAYMENT-8939-20260224-003129/", "export/ETL-Connectors/feiqiu/JSON/ODS_PAYMENT/ODS_PAYMENT-8956-20260224-021747/", "export/ETL-Connectors/feiqiu/JSON/ODS_PAYMENT/ODS_PAYMENT-8986-20260224-062624/", "export/ETL-Connectors/feiqiu/JSON/ODS_PAYMENT/ODS_PAYMENT-9015-20260224-122940/", "export/ETL-Connectors/feiqiu/JSON/ODS_PAYMENT/ODS_PAYMENT-9044-20260225-001806/", "export/ETL-Connectors/feiqiu/JSON/ODS_PAYMENT/ODS_PAYMENT-9072-20260225-050857/", "export/ETL-Connectors/feiqiu/JSON/ODS_PLATFORM_COUPON/ODS_PLATFORM_COUPON-8944-20260224-004106/", "export/ETL-Connectors/feiqiu/JSON/ODS_PLATFORM_COUPON/ODS_PLATFORM_COUPON-8969-20260224-022959/", "export/ETL-Connectors/feiqiu/JSON/ODS_PLATFORM_COUPON/ODS_PLATFORM_COUPON-8999-20260224-063835/", "export/ETL-Connectors/feiqiu/JSON/ODS_PLATFORM_COUPON/ODS_PLATFORM_COUPON-9028-20260224-124234/", "export/ETL-Connectors/feiqiu/JSON/ODS_PLATFORM_COUPON/ODS_PLATFORM_COUPON-9057-20260225-003128/", "export/ETL-Connectors/feiqiu/JSON/ODS_PLATFORM_COUPON/ODS_PLATFORM_COUPON-9085-20260225-052147/", "export/ETL-Connectors/feiqiu/JSON/ODS_RECHARGE_SETTLE/ODS_RECHARGE_SETTLE-8950-20260224-005639/", "export/ETL-Connectors/feiqiu/JSON/ODS_RECHARGE_SETTLE/ODS_RECHARGE_SETTLE-8964-20260224-022942/", "export/ETL-Connectors/feiqiu/JSON/ODS_RECHARGE_SETTLE/ODS_RECHARGE_SETTLE-8994-20260224-063819/", "export/ETL-Connectors/feiqiu/JSON/ODS_RECHARGE_SETTLE/ODS_RECHARGE_SETTLE-9023-20260224-124214/", "export/ETL-Connectors/feiqiu/JSON/ODS_RECHARGE_SETTLE/ODS_RECHARGE_SETTLE-9052-20260225-003111/", "export/ETL-Connectors/feiqiu/JSON/ODS_RECHARGE_SETTLE/ODS_RECHARGE_SETTLE-9080-20260225-052130/", "export/ETL-Connectors/feiqiu/JSON/ODS_REFUND/ODS_REFUND-8940-20260224-003541/", "export/ETL-Connectors/feiqiu/JSON/ODS_REFUND/ODS_REFUND-8957-20260224-022140/", "export/ETL-Connectors/feiqiu/JSON/ODS_REFUND/ODS_REFUND-8987-20260224-063015/", "export/ETL-Connectors/feiqiu/JSON/ODS_REFUND/ODS_REFUND-9016-20260224-123347/", "export/ETL-Connectors/feiqiu/JSON/ODS_REFUND/ODS_REFUND-9045-20260225-002213/", "export/ETL-Connectors/feiqiu/JSON/ODS_REFUND/ODS_REFUND-9073-20260225-051302/", "export/ETL-Connectors/feiqiu/JSON/ODS_SETTLEMENT_RECORDS/ODS_SETTLEMENT_RECORDS-8938-20260224-002943/", "export/ETL-Connectors/feiqiu/JSON/ODS_SETTLEMENT_RECORDS/ODS_SETTLEMENT_RECORDS-8955-20260224-021559/", "export/ETL-Connectors/feiqiu/JSON/ODS_SETTLEMENT_RECORDS/ODS_SETTLEMENT_RECORDS-8985-20260224-062445/", "export/ETL-Connectors/feiqiu/JSON/ODS_SETTLEMENT_RECORDS/ODS_SETTLEMENT_RECORDS-9014-20260224-122754/", "export/ETL-Connectors/feiqiu/JSON/ODS_SETTLEMENT_RECORDS/ODS_SETTLEMENT_RECORDS-9043-20260225-001622/", "export/ETL-Connectors/feiqiu/JSON/ODS_SETTLEMENT_RECORDS/ODS_SETTLEMENT_RECORDS-9071-20260225-050710/", "export/ETL-Connectors/feiqiu/JSON/ODS_STORE_GOODS/ODS_STORE_GOODS-8966-20260224-022948/", "export/ETL-Connectors/feiqiu/JSON/ODS_STORE_GOODS/ODS_STORE_GOODS-8996-20260224-063825/", "export/ETL-Connectors/feiqiu/JSON/ODS_STORE_GOODS/ODS_STORE_GOODS-9025-20260224-124220/", "export/ETL-Connectors/feiqiu/JSON/ODS_STORE_GOODS/ODS_STORE_GOODS-9054-20260225-003118/", "export/ETL-Connectors/feiqiu/JSON/ODS_STORE_GOODS/ODS_STORE_GOODS-9082-20260225-052136/", "export/ETL-Connectors/feiqiu/JSON/ODS_STORE_GOODS_SALES/ODS_STORE_GOODS_SALES-8967-20260224-022953/", "export/ETL-Connectors/feiqiu/JSON/ODS_STORE_GOODS_SALES/ODS_STORE_GOODS_SALES-8997-20260224-063829/", "export/ETL-Connectors/feiqiu/JSON/ODS_STORE_GOODS_SALES/ODS_STORE_GOODS_SALES-9026-20260224-124227/", "export/ETL-Connectors/feiqiu/JSON/ODS_STORE_GOODS_SALES/ODS_STORE_GOODS_SALES-9055-20260225-003122/", "export/ETL-Connectors/feiqiu/JSON/ODS_STORE_GOODS_SALES/ODS_STORE_GOODS_SALES-9083-20260225-052141/", "export/ETL-Connectors/feiqiu/JSON/ODS_TABLES/ODS_TABLES-8943-20260224-004103/", "export/ETL-Connectors/feiqiu/JSON/ODS_TABLES/ODS_TABLES-8960-20260224-022703/", "export/ETL-Connectors/feiqiu/JSON/ODS_TABLES/ODS_TABLES-8990-20260224-063548/", "export/ETL-Connectors/feiqiu/JSON/ODS_TABLES/ODS_TABLES-9019-20260224-123910/", "export/ETL-Connectors/feiqiu/JSON/ODS_TABLES/ODS_TABLES-9048-20260225-002812/", "export/ETL-Connectors/feiqiu/JSON/ODS_TABLES/ODS_TABLES-9076-20260225-051848/", "export/ETL-Connectors/feiqiu/JSON/ODS_TABLE_FEE_DISCOUNT/ODS_TABLE_FEE_DISCOUNT-8942-20260224-004011/", "export/ETL-Connectors/feiqiu/JSON/ODS_TABLE_FEE_DISCOUNT/ODS_TABLE_FEE_DISCOUNT-8959-20260224-022559/", "export/ETL-Connectors/feiqiu/JSON/ODS_TABLE_FEE_DISCOUNT/ODS_TABLE_FEE_DISCOUNT-8989-20260224-063453/", "export/ETL-Connectors/feiqiu/JSON/ODS_TABLE_FEE_DISCOUNT/ODS_TABLE_FEE_DISCOUNT-9018-20260224-123816/", "export/ETL-Connectors/feiqiu/JSON/ODS_TABLE_FEE_DISCOUNT/ODS_TABLE_FEE_DISCOUNT-9047-20260225-002706/", "export/ETL-Connectors/feiqiu/JSON/ODS_TABLE_FEE_DISCOUNT/ODS_TABLE_FEE_DISCOUNT-9075-20260225-051752/", "export/ETL-Connectors/feiqiu/JSON/ODS_TABLE_USE/ODS_TABLE_USE-8941-20260224-003546/", "export/ETL-Connectors/feiqiu/JSON/ODS_TABLE_USE/ODS_TABLE_USE-8958-20260224-022143/", "export/ETL-Connectors/feiqiu/JSON/ODS_TABLE_USE/ODS_TABLE_USE-8988-20260224-063018/", "export/ETL-Connectors/feiqiu/JSON/ODS_TABLE_USE/ODS_TABLE_USE-9017-20260224-123351/", "export/ETL-Connectors/feiqiu/JSON/ODS_TABLE_USE/ODS_TABLE_USE-9046-20260225-002216/", "export/ETL-Connectors/feiqiu/JSON/ODS_TABLE_USE/ODS_TABLE_USE-9074-20260225-051306/", "export/ETL-Connectors/feiqiu/JSON/ODS_TENANT_GOODS/ODS_TENANT_GOODS-8968-20260224-022954/", "export/ETL-Connectors/feiqiu/JSON/ODS_TENANT_GOODS/ODS_TENANT_GOODS-8998-20260224-063830/", "export/ETL-Connectors/feiqiu/JSON/ODS_TENANT_GOODS/ODS_TENANT_GOODS-9027-20260224-124229/", "export/ETL-Connectors/feiqiu/JSON/ODS_TENANT_GOODS/ODS_TENANT_GOODS-9056-20260225-003124/", "export/ETL-Connectors/feiqiu/JSON/ODS_TENANT_GOODS/ODS_TENANT_GOODS-9084-20260225-052142/", "export/ETL-Connectors/feiqiu/REPORTS/blackbox_report_20260220_181225.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_check_20260221_115751.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_check_20260221_120249.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_check_20260221_122116.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_check_20260221_125127.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_check_20260221_130447.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_check_20260221_130620.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_check_20260224_030422.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_check_20260224_030606.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_check_20260224_172522.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_check_20260224_172711.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_check_20260224_173114.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_check_20260224_173244.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_check_20260225_005410.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_check_20260225_005603.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_check_20260225_005756.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_check_20260225_005954.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260220_072152.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260220_072211.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260220_073610.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260220_091414.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260221_153910.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260221_193018.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260221_195222.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260221_200857.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260221_203129.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260221_211445.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260221_212639.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260221_213501.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260221_224027.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260221_225013.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260224_005646.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260224_025039.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260224_065727.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260224_130543.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_005032.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_023232.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_023253.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_023315.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_023339.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_023402.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_023444.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_023506.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_023528.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_023550.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_023612.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_023658.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_023721.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_023744.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_023806.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_023831.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_023913.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_023935.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_023957.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_024019.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_024041.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_024124.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_024146.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_024210.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_024238.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_024307.md", "export/ETL-Connectors/feiqiu/REPORTS/consistency_report_20260225_053925.md", "export/ETL-Connectors/feiqiu/REPORTS/context_handoff_task2.md", "export/ETL-Connectors/feiqiu/REPORTS/ddl_consistency_20260221_212255.md", "export/ETL-Connectors/feiqiu/REPORTS/ddl_consistency_20260221_212621.md", "export/ETL-Connectors/feiqiu/REPORTS/ddl_consistency_20260221_212726.md", "export/ETL-Connectors/feiqiu/REPORTS/dwd_quality_report.json", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260220_072133.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260220_072152.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260220_073610.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260220_091414.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260221_153910.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260221_193018.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260221_195222.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260221_200857.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260221_203129.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260221_211445.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260221_212639.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260221_213501.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260221_224027.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260221_225013.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260224_005646.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260224_025039.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260224_065727.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260224_130543.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_005032.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_023232.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_023253.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_023315.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_023339.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_023402.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_023444.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_023506.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_023528.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_023550.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_023612.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_023658.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_023721.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_023744.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_023806.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_023831.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_023913.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_023935.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_023957.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_024019.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_024041.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_024124.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_024146.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_024210.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_024238.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_024307.md", "export/ETL-Connectors/feiqiu/REPORTS/etl_timing_20260225_053925.md", "export/ETL-Connectors/feiqiu/REPORTS/field_level_report_20260220_233100.md", "export/ETL-Connectors/feiqiu/REPORTS/field_level_report_20260220_233247.md", "export/ETL-Connectors/feiqiu/REPORTS/field_level_report_20260220_233335.md", "export/ETL-Connectors/feiqiu/REPORTS/field_level_report_20260220_233432.md", "export/ETL-Connectors/feiqiu/REPORTS/field_level_report_20260220_233443.md", "export/SYSTEM/LOGS/2026-02-21__dws_assistant_daily_bug_fix.md", "export/SYSTEM/LOGS/2026-02-21__etl_full_bug_report.md", "export/SYSTEM/LOGS/2026-02-21__etl_run_raw.json", "export/SYSTEM/LOGS/2026-02-21__etl_run_raw_v2.json", "export/SYSTEM/LOGS/2026-02-21__etl_run_raw_v3.json", "export/SYSTEM/LOGS/2026-02-21__etl_run_raw_v4.json", "export/SYSTEM/LOGS/2026-02-21__etl_run_raw_v5.json", "export/SYSTEM/LOGS/2026-02-21__etl_run_raw_v6.json", "export/SYSTEM/LOGS/2026-02-21__etl_run_raw_v7.json", "export/SYSTEM/LOGS/2026-02-21__etl_run_raw_v8.json", "export/SYSTEM/LOGS/2026-02-21__etl_run_result.md", "export/SYSTEM/LOGS/2026-02-21__etl_run_result_v2.md", "export/SYSTEM/LOGS/2026-02-21__etl_run_result_v3.md", "export/SYSTEM/LOGS/2026-02-21__etl_run_result_v4.md", "export/SYSTEM/LOGS/2026-02-21__etl_run_result_v5.md", "export/SYSTEM/LOGS/2026-02-21__etl_run_result_v6.md", "export/SYSTEM/LOGS/2026-02-21__etl_run_result_v8.md", "export/SYSTEM/LOGS/2026-02-24__etl_integration_report.md", "export/debug_errors_extract.txt", "export/debug_logs_raw.json", "export/debug_logs_raw.txt", "export/temp_execution_logs.json", "export/temp_raw_execution_log.txt", "export/temp_timing_report.md", "packages/shared/README.md", "scripts/audit/gen_audit_dashboard.py", "scripts/ops/_consistency_output.txt", "scripts/ops/_run_migrations_20260224.py", "scripts/ops/_tmp_execution_logs.json", "scripts/ops/_verify_backend.py", "scripts/ops/_verify_migration_20260224.py", "scripts/ops/api_health_check.py", "scripts/ops/etl_integration_report.py", "scripts/ops/etl_monitor.py", "scripts/ops/extract_timing_data.py", "scripts/ops/fix_assistant_ledger_misdelete.py", "scripts/ops/gen_consolidated_ddl.py", "scripts/ops/gen_integration_report.py", "scripts/ops/run_seed_spi_params.py", "scripts/ops/validate_p1_db_foundation.py", "scripts/server/init-server-env.py", "scripts/server/server-exclude.txt", "test_p4_output.txt", "tests/test_auth_system_properties.py", "tests/test_dws_contribution_properties.py", "tests/test_etl_refactor_properties.py", "tests/test_p1_default_privileges.py", "tests/test_p1_env_missing.py", "tests/test_p1_fdw_properties.py", "tests/test_p1_migration_idempotency.py", "tests/test_p1_migration_structure.py", "tests/test_p1_rls_filtering.py", "tests/test_p1_rls_view_properties.py" ], "compliance": { "code_without_docs": [ { "file": "apps/backend/app/auth/dependencies.py", "expected_docs": [ "apps/backend/docs/API-REFERENCE.md", "apps/backend/README.md" ] }, { "file": "apps/backend/app/auth/jwt.py", "expected_docs": [ "apps/backend/docs/API-REFERENCE.md", "apps/backend/README.md" ] }, { "file": "apps/backend/app/routers/xcx_auth.py", "expected_docs": [ "apps/backend/docs/API-REFERENCE.md" ] }, { "file": "apps/backend/app/services/application.py", "expected_docs": [ "apps/backend/docs/API-REFERENCE.md", "apps/backend/README.md" ] }, { "file": "apps/backend/app/services/matching.py", "expected_docs": [ "apps/backend/docs/API-REFERENCE.md", "apps/backend/README.md" ] }, { "file": "apps/backend/app/services/role.py", "expected_docs": [ "apps/backend/docs/API-REFERENCE.md", "apps/backend/README.md" ] }, { "file": "apps/backend/app/services/task_registry.py", "expected_docs": [ "apps/backend/docs/API-REFERENCE.md", "apps/backend/README.md" ] }, { "file": "apps/backend/app/services/wechat.py", "expected_docs": [ "apps/backend/docs/API-REFERENCE.md", "apps/backend/README.md" ] }, { "file": "apps/etl/connectors/feiqiu/orchestration/flow_runner.py", "expected_docs": [ "apps/etl/connectors/feiqiu/docs/architecture/" ] }, { "file": "apps/etl/connectors/feiqiu/orchestration/task_executor.py", "expected_docs": [ "apps/etl/connectors/feiqiu/docs/architecture/" ] }, { "file": "apps/etl/connectors/feiqiu/orchestration/task_registry.py", "expected_docs": [ "apps/etl/connectors/feiqiu/docs/architecture/" ] }, { "file": "apps/etl/connectors/feiqiu/orchestration/topological_sort.py", "expected_docs": [ "apps/etl/connectors/feiqiu/docs/architecture/" ] } ], "new_migration_sql": [ "db/etl_feiqiu/migrations/2025-02-24__alter_assistant_daily_add_penalty_fields.sql", "db/etl_feiqiu/migrations/2025-02-24__alter_member_consumption_add_recharge_fields.sql", "db/etl_feiqiu/migrations/2025-02-24__create_dws_assistant_order_contribution.sql", "db/etl_feiqiu/migrations/2025-02-24__create_rls_view_assistant_order_contribution.sql", "db/etl_feiqiu/migrations/2026-02-24__add_goods_stock_warning_info.sql", "db/etl_feiqiu/migrations/2026-02-24__cleanup_assistant_abolish_residual.sql", "db/etl_feiqiu/migrations/2026-02-24__p1_create_app_schema_rls_views.sql", "db/zqyy_app/migrations/2026-02-24__p1_create_auth_biz_schemas.sql", "db/zqyy_app/migrations/2026-02-24__p1_setup_fdw_etl.sql", "db/zqyy_app/migrations/2026-02-25__p3_create_auth_tables.sql", "db/zqyy_app/migrations/2026-02-25__p3_seed_roles_permissions.sql" ], "has_bd_manual": true, "has_audit_record": false, "has_ddl_baseline": true }, "diff_stat": ".gitignore | 14 +-\n .kiro/.last_prompt_id.json | 4 +-\n .kiro/agents/audit-writer.md | 108 +-\n .kiro/hooks/audit-flagger.kiro.hook | 2 +-\n .kiro/hooks/audit-reminder.kiro.hook | 2 +-\n .kiro/hooks/prompt-audit-log.kiro.hook | 2 +-\n .kiro/hooks/run-audit-writer.kiro.hook | 6 +-\n .kiro/specs/etl-fullstack-integration/design.md | 126 -\n .../etl-fullstack-integration/requirements.md | 70 -\n .kiro/specs/etl-fullstack-integration/tasks.md | 86 -\n .kiro/specs/spi-spending-power-index/tasks.md | 14 +-\n README.md | 38 +-\n apps/backend/app/auth/dependencies.py | 99 +-\n apps/backend/app/auth/jwt.py | 44 +-\n apps/backend/app/main.py | 4 +-\n apps/backend/app/services/task_registry.py | 13 +-\n apps/etl/connectors/feiqiu/docs/CHANGELOG.md | 20 +\n apps/etl/connectors/feiqiu/docs/README.md | 4 -\n .../feiqiu/docs/business-rules/dws_metrics.md | 215 +-\n .../feiqiu/docs/business-rules/scd2_rules.md | 130 +-\n .../etl/connectors/feiqiu/docs/etl_tasks/README.md | 19 +-\n .../feiqiu/docs/etl_tasks/base_task_mechanism.md | 4 +-\n .../connectors/feiqiu/docs/etl_tasks/dws_tasks.md | 58 +-\n .../feiqiu/docs/etl_tasks/index_tasks.md | 205 +-\n .../connectors/feiqiu/docs/etl_tasks/ods_tasks.md | 2 +-\n .../feiqiu/docs/operations/environment_setup.md | 6 -\n .../feiqiu/docs/operations/troubleshooting.md | 2 +-\n .../connectors/feiqiu/orchestration/flow_runner.py | 6 +-\n .../feiqiu/orchestration/task_executor.py | 5 +\n .../feiqiu/orchestration/task_registry.py | 5 +-\n .../feiqiu/orchestration/topological_sort.py | 42 +-\n .../feiqiu/quality/consistency_checker.py | 5 +\n .../connectors/feiqiu/tasks/dwd/dwd_load_task.py | 3 +\n apps/etl/connectors/feiqiu/tasks/dws/__init__.py | 2 +\n .../feiqiu/tasks/dws/assistant_daily_task.py | 205 +-\n .../feiqiu/tasks/dws/member_consumption_task.py | 91 +-\n .../feiqiu/tasks/dws/member_visit_task.py | 2 +\n apps/etl/connectors/feiqiu/tasks/ods/ods_tasks.py | 71 +-\n .../feiqiu/tests/unit/test_topological_sort.py | 86 +-\n apps/miniprogram/README.md | 135 +-\n db/README.md | 21 +-\n .../db/etl_feiqiu/schemas/dwd.sql | 3 +\n .../db/etl_feiqiu/schemas/ods.sql | 3 +\n db/etl_feiqiu/seeds/seed_ods_tasks.sql | 2 +-\n db/etl_feiqiu/seeds/seed_scheduler_tasks.sql | 2 +-\n docs/README.md | 166 +-\n docs/audit/audit_dashboard.md | 5 +-\n docs/database/README.md | 15 +-\n docs/database/ddl/etl_feiqiu__app.sql | 1070 ++++++-\n docs/database/ddl/etl_feiqiu__core.sql | 2 +-\n docs/database/ddl/etl_feiqiu__dwd.sql | 7 +-\n docs/database/ddl/etl_feiqiu__dws.sql | 38 +-\n docs/database/ddl/etl_feiqiu__meta.sql | 2 +-\n docs/database/ddl/etl_feiqiu__ods.sql | 7 +-\n docs/database/ddl/fdw.sql | 2 +-\n docs/database/ddl/zqyy_app__public.sql | 2 +-\n docs/deployment/LAUNCH-CHECKLIST.md | 22 +-\n docs/h5_ui/index.html | 16 +\n docs/h5_ui/pages/board-finance.html | 36 +-\n docs/h5_ui/pages/customer-detail.html | 6 +-\n docs/h5_ui/pages/feiqiu-ETL.code-workspace | 13 -\n docs/h5_ui/pages/my-profile.html | 15 -\n docs/h5_ui/pages/task-detail-callback.html | 47 +-\n docs/h5_ui/pages/task-detail-priority.html | 47 +-\n docs/h5_ui/pages/task-detail-relationship.html | 41 +-\n docs/h5_ui/pages/task-detail.html | 110 +-\n docs/h5_ui/pages/task-list.html | 17 +-\n ...276\\235\\350\\265\\226\\347\\237\\251\\351\\230\\265.md\" | 11 +-\n ...213\\206\\345\\210\\206\\346\\200\\273\\350\\247\\210.md\" | 5 +-\n docs/prd/specs/P10-tenant-admin-web.md | 4 +-\n docs/prd/specs/P2-etl-dws-miniapp-extensions.md | 8 +-\n docs/prd/specs/P3-miniapp-auth-system.md | 15 +-\n .../REPORTS/blackbox_report_20260220_181225.md | 183 --\n .../REPORTS/consistency_check_20260221_115751.md | 785 -----\n .../REPORTS/consistency_check_20260221_120249.md | 1851 ------------\n .../REPORTS/consistency_check_20260221_122116.md | 1851 ------------\n .../REPORTS/consistency_check_20260221_125127.md | 2001 -------------\n .../REPORTS/consistency_check_20260221_130447.md | 2005 -------------\n .../REPORTS/consistency_check_20260221_130620.md | 2003 -------------\n .../REPORTS/consistency_report_20260220_072152.md | 335 ---\n .../REPORTS/consistency_report_20260220_072211.md | 335 ---\n .../REPORTS/consistency_report_20260220_073610.md | 335 ---\n .../REPORTS/consistency_report_20260220_091414.md | 335 ---\n .../REPORTS/consistency_report_20260221_153910.md | 125 -\n .../REPORTS/consistency_report_20260221_193018.md | 125 -\n .../REPORTS/consistency_report_20260221_195222.md | 125 -\n .../REPORTS/consistency_report_20260221_200857.md | 125 -\n .../REPORTS/consistency_report_20260221_203129.md | 125 -\n .../REPORTS/consistency_report_20260221_211445.md | 125 -\n .../REPORTS/consistency_report_20260221_212639.md | 125 -\n .../REPORTS/consistency_report_20260221_213501.md | 125 -\n .../REPORTS/consistency_report_20260221_224027.md | 88 -\n .../REPORTS/consistency_report_20260221_225013.md | 88 -\n .../feiqiu/REPORTS/context_handoff_task2.md | 57 -\n .../REPORTS/ddl_consistency_20260221_212255.md | 152 -\n .../REPORTS/ddl_consistency_20260221_212621.md | 133 -\n .../REPORTS/ddl_consistency_20260221_212726.md | 131 -\n .../feiqiu/REPORTS/dwd_quality_report.json | 783 -----\n .../feiqiu/REPORTS/etl_timing_20260220_072133.md | 16 -\n .../feiqiu/REPORTS/etl_timing_20260220_072152.md | 16 -\n .../feiqiu/REPORTS/etl_timing_20260220_073610.md | 13 -\n .../feiqiu/REPORTS/etl_timing_20260220_091414.md | 13 -\n .../feiqiu/REPORTS/etl_timing_20260221_153910.md | 13 -\n .../feiqiu/REPORTS/etl_timing_20260221_193018.md | 13 -\n .../feiqiu/REPORTS/etl_timing_20260221_195222.md | 13 -\n .../feiqiu/REPORTS/etl_timing_20260221_200857.md | 13 -\n .../feiqiu/REPORTS/etl_timing_20260221_203129.md | 13 -\n .../feiqiu/REPORTS/etl_timing_20260221_211445.md | 13 -\n .../feiqiu/REPORTS/etl_timing_20260221_212639.md | 13 -\n .../feiqiu/REPORTS/etl_timing_20260221_213501.md | 13 -\n .../feiqiu/REPORTS/etl_timing_20260221_224027.md | 13 -\n .../feiqiu/REPORTS/etl_timing_20260221_225013.md | 13 -\n .../REPORTS/field_level_report_20260220_233100.md | 2749 -----------------\n .../REPORTS/field_level_report_20260220_233247.md | 2973 -------------------\n .../REPORTS/field_level_report_20260220_233335.md | 3111 --------------------\n .../REPORTS/field_level_report_20260220_233432.md | 3111 --------------------\n .../REPORTS/field_level_report_20260220_233443.md | 3111 --------------------\n .../2026-02-21__dws_assistant_daily_bug_fix.md | 178 --\n .../SYSTEM/LOGS/2026-02-21__etl_full_bug_report.md | 443 ---\n export/SYSTEM/LOGS/2026-02-21__etl_run_raw.json | 428 ---\n export/SYSTEM/LOGS/2026-02-21__etl_run_raw_v2.json | 50 -\n export/SYSTEM/LOGS/2026-02-21__etl_run_raw_v3.json | 5 -\n export/SYSTEM/LOGS/2026-02-21__etl_run_raw_v4.json | 5 -\n export/SYSTEM/LOGS/2026-02-21__etl_run_raw_v5.json | 5 -\n export/SYSTEM/LOGS/2026-02-21__etl_run_raw_v6.json | 5 -\n export/SYSTEM/LOGS/2026-02-21__etl_run_raw_v7.json | 5 -\n export/SYSTEM/LOGS/2026-02-21__etl_run_raw_v8.json | 5 -\n export/SYSTEM/LOGS/2026-02-21__etl_run_result.md | 124 -\n .../SYSTEM/LOGS/2026-02-21__etl_run_result_v2.md | 136 -\n .../SYSTEM/LOGS/2026-02-21__etl_run_result_v3.md | 85 -\n .../SYSTEM/LOGS/2026-02-21__etl_run_result_v4.md | 70 -\n .../SYSTEM/LOGS/2026-02-21__etl_run_result_v5.md | 69 -\n .../SYSTEM/LOGS/2026-02-21__etl_run_result_v6.md | 59 -\n .../SYSTEM/LOGS/2026-02-21__etl_run_result_v8.md | 109 -\n scripts/audit/gen_audit_dashboard.py | 2 +-\n scripts/ops/gen_consolidated_ddl.py | 4 +-\n scripts/server/server-exclude.txt | 2 +-\n tests/test_etl_refactor_properties.py | 1 +\n 138 files changed, 2949 insertions(+), 32250 deletions(-)", "high_risk_diff": "diff --git a/apps/backend/app/auth/dependencies.py b/apps/backend/app/auth/dependencies.py\nindex 5e4757f..a015fe6 100644\n--- a/apps/backend/app/auth/dependencies.py\n+++ b/apps/backend/app/auth/dependencies.py\n@@ -5,9 +5,15 @@ FastAPI 依赖注入:从 JWT 提取当前用户信息。\n @router.get(\"/protected\")\n async def protected_endpoint(user: CurrentUser = Depends(get_current_user)):\n print(user.user_id, user.site_id)\n+\n+ # 允许 pending 用户(受限令牌)访问\n+ @router.get(\"/apply\")\n+ async def apply_endpoint(user: CurrentUser = Depends(get_current_user_or_limited)):\n+ if user.limited:\n+ ... # 受限逻辑\n \"\"\"\n \n-from dataclasses import dataclass\n+from dataclasses import dataclass, field\n \n from fastapi import Depends, HTTPException, status\n from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer\n@@ -24,7 +30,10 @@ class CurrentUser:\n \"\"\"从 JWT 解析出的当前用户上下文。\"\"\"\n \n user_id: int\n- site_id: int\n+ site_id: int = 0\n+ roles: list[str] = field(default_factory=list)\n+ status: str = \"pending\"\n+ limited: bool = False\n \n \n async def get_current_user(\n@@ -33,7 +42,7 @@ async def get_current_user(\n \"\"\"\n FastAPI 依赖:从 Authorization header 提取 JWT,验证后返回用户信息。\n \n- 失败时抛出 401。\n+ 要求完整令牌(非 limited),失败时抛出 401。\n \"\"\"\n token = credentials.credentials\n try:\n@@ -45,6 +54,14 @@ async def get_current_user(\n headers={\"WWW-Authenticate\": \"Bearer\"},\n )\n \n+ # 受限令牌不允许通过此依赖\n+ if payload.get(\"limited\"):\n+ raise HTTPException(\n+ status_code=status.HTTP_401_UNAUTHORIZED,\n+ detail=\"受限令牌无法访问此端点\",\n+ headers={\"WWW-Authenticate\": \"Bearer\"},\n+ )\n+\n user_id_raw = payload.get(\"sub\")\n site_id = payload.get(\"site_id\")\n \n@@ -64,4 +81,78 @@ async def get_current_user(\n headers={\"WWW-Authenticate\": \"Bearer\"},\n )\n \n- return CurrentUser(user_id=user_id, site_id=site_id)\n+ roles = payload.get(\"roles\", [])\n+\n+ return CurrentUser(\n+ user_id=user_id,\n+ site_id=site_id,\n+ roles=roles,\n+ status=\"approved\",\n+ limited=False,\n+ )\n+\n+\n+async def get_current_user_or_limited(\n+ credentials: HTTPAuthorizationCredentials = Depends(_bearer_scheme),\n+) -> CurrentUser:\n+ \"\"\"\n+ FastAPI 依赖:允许 pending 用户(受限令牌)访问。\n+\n+ - 受限令牌(limited=True):返回 CurrentUser(limited=True, roles=[], status=\"pending\")\n+ - 完整令牌:正常返回 CurrentUser\n+ \"\"\"\n+ token = credentials.credentials\n+ try:\n+ payload = decode_access_token(token)\n+ except JWTError:\n+ raise HTTPException(\n+ status_code=status.HTTP_401_UNAUTHORIZED,\n+ detail=\"无效的令牌\",\n+ headers={\"WWW-Authenticate\": \"Bearer\"},\n+ )\n+\n+ user_id_raw = payload.get(\"sub\")\n+ if user_id_raw is None:\n+ raise HTTPException(\n+ status_code=status.HTTP_401_UNAUTHORIZED,\n+ detail=\"令牌缺少必要字段\",\n+ headers={\"WWW-Authenticate\": \"Bearer\"},\n+ )\n+\n+ try:\n+ user_id = int(user_id_raw)\n+ except (TypeError, ValueError):\n+ raise HTTPException(\n+ status_code=status.HTTP_401_UNAUTHORIZED,\n+ detail=\"令牌中 user_id 格式无效\",\n+ headers={\"WWW-Authenticate\": \"Bearer\"},\n+ )\n+\n+ # 受限令牌:pending 用户\n+ if payload.get(\"limited\"):\n+ return CurrentUser(\n+ user_id=user_id,\n+ site_id=0,\n+ roles=[],\n+ status=\"pending\",\n+ limited=True,\n+ )\n+\n+ # 完整令牌:要求 site_id\n+ site_id = payload.get(\"site_id\")\n+ if site_id is None:\n+ raise HTTPException(\n+ status_code=status.HTTP_401_UNAUTHORIZED,\n+ detail=\"令牌缺少必要字段\",\n+ headers={\"WWW-Authenticate\": \"Bearer\"},\n+ )\n+\n+ roles = payload.get(\"roles\", [])\n+\n+ return CurrentUser(\n+ user_id=user_id,\n+ site_id=site_id,\n+ roles=roles,\n+ status=\"approved\",\n+ limited=False,\n+ )\ndiff --git a/apps/backend/app/auth/jwt.py b/apps/backend/app/auth/jwt.py\nindex 5be3acc..227b4be 100644\n--- a/apps/backend/app/auth/jwt.py\n+++ b/apps/backend/app/auth/jwt.py\n@@ -27,11 +27,14 @@ def hash_password(password: str) -> str:\n return bcrypt.hashpw(password.encode(\"utf-8\"), bcrypt.gensalt()).decode(\"utf-8\")\n \n \n-def create_access_token(user_id: int, site_id: int) -> str:\n+def create_access_token(\n+ user_id: int, site_id: int, roles: list[str] | None = None\n+) -> str:\n \"\"\"\n 生成 access_token。\n \n- payload: sub=user_id, site_id, type=access, exp\n+ payload: sub=user_id, site_id, roles, type=access, exp\n+ roles 参数默认 None,保持向后兼容。\n \"\"\"\n expire = datetime.now(timezone.utc) + timedelta(\n minutes=config.JWT_ACCESS_TOKEN_EXPIRE_MINUTES\n@@ -42,6 +45,8 @@ def create_access_token(user_id: int, site_id: int) -> str:\n \"type\": \"access\",\n \"exp\": expire,\n }\n+ if roles is not None:\n+ payload[\"roles\"] = roles\n return jwt.encode(payload, config.JWT_SECRET_KEY, algorithm=config.JWT_ALGORITHM)\n \n \n@@ -63,15 +68,46 @@ def create_refresh_token(user_id: int, site_id: int) -> str:\n return jwt.encode(payload, config.JWT_SECRET_KEY, algorithm=config.JWT_ALGORITHM)\n \n \n-def create_token_pair(user_id: int, site_id: int) -> dict[str, str]:\n+def create_token_pair(user_id: int, site_id: int, roles: list[str] | None = None) -> dict[str, str]:\n \"\"\"生成 access_token + refresh_token 令牌对。\"\"\"\n return {\n- \"access_token\": create_access_token(user_id, site_id),\n+ \"access_token\": create_access_token(user_id, site_id, roles=roles),\n \"refresh_token\": create_refresh_token(user_id, site_id),\n \"token_type\": \"bearer\",\n }\n \n \n+def create_limited_token_pair(user_id: int) -> dict[str, str]:\n+ \"\"\"\n+ 为 pending 用户签发受限令牌。\n+\n+ payload 不含 site_id 和 roles,仅包含 user_id + type + limited=True。\n+ 受限令牌仅允许访问申请提交和状态查询端点。\n+ \"\"\"\n+ now = datetime.now(timezone.utc)\n+ access_payload = {\n+ \"sub\": str(user_id),\n+ \"type\": \"access\",\n+ \"limited\": True,\n+ \"exp\": now + timedelta(minutes=config.JWT_ACCESS_TOKEN_EXPIRE_MINUTES),\n+ }\n+ refresh_payload = {\n+ \"sub\": str(user_id),\n+ \"type\": \"refresh\",\n+ \"limited\": True,\n+ \"exp\": now + timedelta(days=config.JWT_REFRESH_TOKEN_EXPIRE_DAYS),\n+ }\n+ return {\n+ \"access_token\": jwt.encode(\n+ access_payload, config.JWT_SECRET_KEY, algorithm=config.JWT_ALGORITHM\n+ ),\n+ \"refresh_token\": jwt.encode(\n+ refresh_payload, config.JWT_SECRET_KEY, algorithm=config.JWT_ALGORITHM\n+ ),\n+ \"token_type\": \"bearer\",\n+ }\n+\n+\n def decode_token(token: str) -> dict:\n \"\"\"\n 解码并验证 JWT 令牌。\ndiff --git a/apps/backend/app/main.py b/apps/backend/app/main.py\nindex 5aa5df2..bab0063 100644\n--- a/apps/backend/app/main.py\n+++ b/apps/backend/app/main.py\n@@ -14,7 +14,8 @@ from app import config\n # CHANGE 2026-02-19 | 新增 xcx_test 路由(MVP 验证)+ wx_callback 路由(微信消息推送)\n # CHANGE 2026-02-22 | 新增 member_birthday 路由(助教手动补录会员生日)\n # CHANGE 2026-02-23 | 新增 ops_panel 路由(运维控制面板)\n-from app.routers import auth, execution, schedules, tasks, env_config, db_viewer, etl_status, xcx_test, wx_callback, member_birthday, ops_panel\n+# CHANGE 2026-02-25 | 新增 xcx_auth 路由(小程序微信登录 + 申请 + 状态查询 + 店铺切换)\n+from app.routers import auth, execution, schedules, tasks, env_config, db_viewer, etl_status, xcx_test, wx_callback, member_birthday, ops_panel, xcx_auth\n from app.services.scheduler import scheduler\n from app.services.task_queue import task_queue\n from app.ws.logs import ws_router\n@@ -64,6 +65,7 @@ app.include_router(xcx_test.router)\n app.include_router(wx_callback.router)\n app.include_router(member_birthday.router)\n app.include_router(ops_panel.router)\n+app.include_router(xcx_auth.router)\n \n \n @app.get(\"/health\", tags=[\"系统\"])\ndiff --git a/apps/backend/app/services/task_registry.py b/apps/backend/app/services/task_registry.py\nindex 118ca43..c271e30 100644\n--- a/apps/backend/app/services/task_registry.py\n+++ b/apps/backend/app/services/task_registry.py\n@@ -44,7 +44,7 @@ class DwdTableDefinition:\n ODS_TASKS: list[TaskDefinition] = [\n TaskDefinition(\"ODS_ASSISTANT_ACCOUNT\", \"助教账号\", \"抽取助教账号主数据\", \"助教\", \"ODS\", is_ods=True),\n TaskDefinition(\"ODS_ASSISTANT_LEDGER\", \"助教服务记录\", \"抽取助教服务流水\", \"助教\", \"ODS\", is_ods=True),\n- TaskDefinition(\"ODS_ASSISTANT_ABOLISH\", \"助教取消记录\", \"抽取助教取消/作废记录\", \"助教\", \"ODS\", is_ods=True),\n+\n TaskDefinition(\"ODS_SETTLEMENT_RECORDS\", \"结算记录\", \"抽取订单结算记录\", \"结算\", \"ODS\", is_ods=True),\n # CHANGE [2026-07-20] intent: 同步 ETL 侧移除——ODS_SETTLEMENT_TICKET 已在 Task 7.3 中彻底移除\n TaskDefinition(\"ODS_TABLE_USE\", \"台费流水\", \"抽取台费使用流水\", \"台桌\", \"ODS\", is_ods=True),\n@@ -65,6 +65,7 @@ ODS_TASKS: list[TaskDefinition] = [\n TaskDefinition(\"ODS_STORE_GOODS\", \"门店商品\", \"抽取门店商品主数据\", \"商品\", \"ODS\", is_ods=True, requires_window=False),\n TaskDefinition(\"ODS_STORE_GOODS_SALES\", \"商品销售\", \"抽取门店商品销售记录\", \"商品\", \"ODS\", is_ods=True),\n TaskDefinition(\"ODS_TENANT_GOODS\", \"租户商品\", \"抽取租户级商品主数据\", \"商品\", \"ODS\", is_ods=True, requires_window=False),\n+ TaskDefinition(\"ODS_STAFF_INFO\", \"员工档案\", \"抽取员工档案(含在职/离职)\", \"助教\", \"ODS\", is_ods=True, requires_window=False),\n ]\n \n # ── DWD 任务定义 ──────────────────────────────────────────────\n@@ -105,18 +106,17 @@ INDEX_TASKS: list[TaskDefinition] = [\n TaskDefinition(\"DWS_ML_MANUAL_IMPORT\", \"手动导入 (ML)\", \"手动导入机器学习数据\", \"指数\", \"INDEX\", requires_window=False, is_common=False),\n # CHANGE [2026-02-19] intent: 补充说明 RelationIndexTask 产出 RS/OS/MS/ML 四个子指数\n TaskDefinition(\"DWS_RELATION_INDEX\", \"关系指数 (RS)\", \"产出 RS/OS/MS/ML 四个子指数\", \"指数\", \"INDEX\"),\n+ TaskDefinition(\"DWS_SPENDING_POWER_INDEX\", \"消费力指数 (SPI)\", \"计算会员消费力指数\", \"指数\", \"INDEX\"),\n ]\n \n # ── 工具类任务定义 ────────────────────────────────────────────\n \n UTILITY_TASKS: list[TaskDefinition] = [\n TaskDefinition(\"MANUAL_INGEST\", \"手动导入\", \"从本地 JSON 文件手动导入数据\", \"工具\", \"UTILITY\", requires_window=False, is_common=False),\n- TaskDefinition(\"INIT_ODS_SCHEMA\", \"初始化 ODS Schema\", \"创建 ODS 层表结构\", \"工具\", \"UTILITY\", requires_window=False, is_common=False),\n- TaskDefinition(\"INIT_DWD_SCHEMA\", \"初始化 DWD Schema\", \"创建 DWD 层表结构\", \"工具\", \"UTILITY\", requires_window=False, is_common=False),\n- TaskDefinition(\"INIT_DWS_SCHEMA\", \"初始化 DWS Schema\", \"创建 DWS 层表结构\", \"工具\", \"UTILITY\", requires_window=False, is_common=False),\n+ # CHANGE [2026-02-24] intent: 移除 4 个一次性初始化任务(INIT_ODS/DWD/DWS_SCHEMA、SEED_DWS_CONFIG),\n+ # 环境已搭建完成,仅保留 ETL 侧实现供运维脚本直接 import 使用,UI 不再展示\n TaskDefinition(\"ODS_JSON_ARCHIVE\", \"ODS JSON 归档\", \"归档 ODS 原始 JSON 文件\", \"工具\", \"UTILITY\", requires_window=False, is_common=False),\n TaskDefinition(\"CHECK_CUTOFF\", \"游标检查\", \"检查各任务数据游标截止点\", \"工具\", \"UTILITY\", requires_window=False, is_common=False),\n- TaskDefinition(\"SEED_DWS_CONFIG\", \"DWS 配置种子\", \"初始化 DWS 配置数据\", \"工具\", \"UTILITY\", requires_window=False, is_common=False),\n TaskDefinition(\"DATA_INTEGRITY_CHECK\", \"数据完整性校验\", \"校验跨层数据完整性\", \"工具\", \"UTILITY\", requires_window=False, is_common=False),\n ]\n \n@@ -202,8 +202,7 @@ DWD_TABLES: list[DwdTableDefinition] = [\n DwdTableDefinition(\"dwd.dwd_store_goods_sale_ex\", \"商品销售(扩展)\", \"商品\", \"ods.store_goods_sales_records\"),\n DwdTableDefinition(\"dwd.dwd_assistant_service_log\", \"助教服务流水\", \"助教\", \"ods.assistant_service_records\"),\n DwdTableDefinition(\"dwd.dwd_assistant_service_log_ex\", \"助教服务流水(扩展)\", \"助教\", \"ods.assistant_service_records\"),\n- DwdTableDefinition(\"dwd.dwd_assistant_trash_event\", \"助教取消事件\", \"助教\", \"ods.assistant_cancellation_records\"),\n- DwdTableDefinition(\"dwd.dwd_assistant_trash_event_ex\", \"助教取消事件(扩展)\", \"助教\", \"ods.assistant_cancellation_records\"),\n+ # CHANGE [2026-02-24] intent: 移除已废弃的 assistant_trash_event 表定义(ODS_ASSISTANT_ABOLISH 全链路已清理)\n DwdTableDefinition(\"dwd.dwd_member_balance_change\", \"会员余额变动\", \"会员\", \"ods.member_balance_changes\"),\n DwdTableDefinition(\"dwd.dwd_member_balance_change_ex\", \"会员余额变动(扩展)\", \"会员\", \"ods.member_balance_changes\"),\n DwdTableDefinition(\"dwd.dwd_groupbuy_redemption\", \"团购核销\", \"团购\", \"ods.group_buy_redemption_records\"),\ndiff --git a/apps/etl/connectors/feiqiu/orchestration/flow_runner.py b/apps/etl/connectors/feiqiu/orchestration/flow_runner.py\nindex 8351b32..3fd8032 100644\n--- a/apps/etl/connectors/feiqiu/orchestration/flow_runner.py\n+++ b/apps/etl/connectors/feiqiu/orchestration/flow_runner.py\n@@ -166,7 +166,11 @@ class FlowRunner:\n \n timer.start_step(\"INCREMENT_ETL\")\n if task_codes:\n- results = self.task_executor.run_tasks(task_codes, data_source=data_source)\n+ # CHANGE [2026-02-24] intent: 对前端传入的 task_codes 也执行拓扑排序,\n+ # 避免 DWS 在 DWD 未完成时就开始计算(跨层依赖顺序缺失 bug)\n+ # prompt: \"修复管理后台全选任务时不按层级顺序执行的问题\"\n+ sorted_codes = topological_sort(task_codes, self.task_registry)\n+ results = self.task_executor.run_tasks(sorted_codes, data_source=data_source)\n else:\n auto_tasks = self._resolve_tasks(layers)\n results = self.task_executor.run_tasks(auto_tasks, data_source=data_source)\ndiff --git a/apps/etl/connectors/feiqiu/orchestration/task_executor.py b/apps/etl/connectors/feiqiu/orchestration/task_executor.py\nindex 0d68f44..142b24c 100644\n--- a/apps/etl/connectors/feiqiu/orchestration/task_executor.py\n+++ b/apps/etl/connectors/feiqiu/orchestration/task_executor.py\n@@ -107,6 +107,11 @@ class TaskExecutor:\n results.append(result_entry)\n except Exception as exc: # noqa: BLE001\n self.logger.error(\"任务 %s 失败: %s\", task_code, exc, exc_info=True)\n+ # CHANGE 2026-02-24 | 任务失败后 rollback,防止 InFailedSqlTransaction 级联\n+ try:\n+ self.db.rollback()\n+ except Exception:\n+ pass\n results.append({\n \"task_code\": task_code,\n \"status\": \"失败\",\ndiff --git a/apps/etl/connectors/feiqiu/orchestration/task_registry.py b/apps/etl/connectors/feiqiu/orchestration/task_registry.py\nindex b6499b9..38fa90b 100644\n--- a/apps/etl/connectors/feiqiu/orchestration/task_registry.py\n+++ b/apps/etl/connectors/feiqiu/orchestration/task_registry.py\n@@ -30,6 +30,7 @@ from tasks.utility.seed_dws_config_task import SeedDwsConfigTask\n # DWS 层任务导入\n from tasks.dws import (\n AssistantDailyTask,\n+ AssistantOrderContributionTask,\n AssistantMonthlyTask,\n AssistantCustomerTask,\n AssistantSalaryTask,\n@@ -147,6 +148,7 @@ default_registry.register(\"DATA_INTEGRITY_CHECK\", DataIntegrityTask, requires_db\n # ── DWS 层业务任务 ────────────────────────────────────────────\n default_registry.register(\"DWS_BUILD_ORDER_SUMMARY\", DwsBuildOrderSummaryTask, requires_db_config=False, layer=\"DWS\")\n default_registry.register(\"DWS_ASSISTANT_DAILY\", AssistantDailyTask, layer=\"DWS\")\n+default_registry.register(\"DWS_ASSISTANT_ORDER_CONTRIBUTION\", AssistantOrderContributionTask, layer=\"DWS\", depends_on=[\"DWD_LOAD_FROM_ODS\"])\n # CHANGE [2026-07-17] intent: 为已知依赖关系添加 depends_on 声明(需求 8.1, 8.2)\n default_registry.register(\"DWS_ASSISTANT_MONTHLY\", AssistantMonthlyTask, layer=\"DWS\", depends_on=[\"DWS_ASSISTANT_DAILY\"])\n default_registry.register(\"DWS_ASSISTANT_CUSTOMER\", AssistantCustomerTask, layer=\"DWS\")\n@@ -166,7 +168,8 @@ default_registry.register(\"DWS_GOODS_STOCK_MONTHLY\", GoodsStockMonthlyTask, laye\n # 替换为统一维护任务 DWS_MAINTENANCE(需求 4.5)\n # depends_on: 所有其他 DWS 任务——MV 刷新和清理应在数据写入后执行\n default_registry.register(\"DWS_MAINTENANCE\", DwsMaintenanceTask, layer=\"DWS\", depends_on=[\n- \"DWS_ASSISTANT_DAILY\", \"DWS_ASSISTANT_MONTHLY\", \"DWS_ASSISTANT_CUSTOMER\",\n+ \"DWS_ASSISTANT_DAILY\", \"DWS_ASSISTANT_ORDER_CONTRIBUTION\",\n+ \"DWS_ASSISTANT_MONTHLY\", \"DWS_ASSISTANT_CUSTOMER\",\n \"DWS_ASSISTANT_SALARY\", \"DWS_ASSISTANT_FINANCE\",\n \"DWS_MEMBER_CONSUMPTION\", \"DWS_MEMBER_VISIT\",\n \"DWS_FINANCE_DAILY\", \"DWS_FINANCE_RECHARGE\",\ndiff --git a/apps/etl/connectors/feiqiu/orchestration/topological_sort.py b/apps/etl/connectors/feiqiu/orchestration/topological_sort.py\nindex e6dc081..bc18098 100644\n--- a/apps/etl/connectors/feiqiu/orchestration/topological_sort.py\n+++ b/apps/etl/connectors/feiqiu/orchestration/topological_sort.py\n@@ -2,6 +2,8 @@\n \"\"\"拓扑排序模块 — Kahn's algorithm\n \n 对任务列表按依赖关系执行拓扑排序:\n+- 显式依赖:TaskMeta.depends_on 声明的任务间依赖\n+- 隐含层级依赖:ODS → DWD → DWS → INDEX,同批任务中低层任务必须先于高层任务\n - 仅对当前执行列表内的任务排序\n - depends_on 中引用的任务不在列表内时记录警告\n - 检测循环依赖并抛出 ValueError\n@@ -11,10 +13,22 @@ import logging\n \n logger = logging.getLogger(__name__)\n \n+# 层级优先级:数值越小越先执行\n+_LAYER_ORDER: dict[str, int] = {\n+ \"ODS\": 0,\n+ \"DWD\": 1,\n+ \"DWS\": 2,\n+ \"INDEX\": 3,\n+}\n+\n \n def topological_sort(task_codes: list[str], registry) -> list[str]:\n \"\"\"对任务列表执行拓扑排序(Kahn's algorithm)。\n \n+ 除了显式 depends_on 依赖外,还注入隐含的层级依赖:\n+ 同批任务中,所有 ODS 任务排在 DWD 之前,DWD 排在 DWS 之前,\n+ DWS 排在 INDEX 之前。这确保跨层执行顺序正确。\n+\n Args:\n task_codes: 待排序的任务代码列表\n registry: TaskRegistry 实例,提供 get_metadata() 查询依赖\n@@ -29,9 +43,10 @@ def topological_sort(task_codes: list[str], registry) -> list[str]:\n return []\n \n in_degree = {code: 0 for code in task_codes}\n- graph = {code: [] for code in task_codes}\n+ graph: dict[str, list[str]] = {code: [] for code in task_codes}\n task_set = set(task_codes)\n \n+ # 1. 显式依赖(depends_on)\n for code in task_codes:\n meta = registry.get_metadata(code)\n if meta and meta.depends_on:\n@@ -44,6 +59,31 @@ def topological_sort(task_codes: list[str], registry) -> list[str]:\n \"任务 %s 依赖 %s,但后者不在当前执行列表中\", code, dep\n )\n \n+ # CHANGE [2026-02-24] intent: 注入隐含层级依赖,确保跨层执行顺序正确\n+ # assumptions: 层级顺序固定为 ODS→DWD→DWS→INDEX;同层任务无隐含互相依赖\n+ # prompt: \"修复管理后台全选任务时不按层级顺序执行的问题\"\n+ # 2. 隐含层级依赖:按层分组,相邻层之间建立边\n+ # 选择每层一个\"代表节点\"作为屏障,避免 O(n*m) 的全连接边\n+ layer_groups: dict[int, list[str]] = {}\n+ for code in task_codes:\n+ meta = registry.get_metadata(code)\n+ if meta and meta.layer:\n+ order = _LAYER_ORDER.get(meta.layer.upper())\n+ if order is not None:\n+ layer_groups.setdefault(order, []).append(code)\n+\n+ sorted_layers = sorted(layer_groups.keys())\n+ for i in range(len(sorted_layers) - 1):\n+ lower_layer = sorted_layers[i]\n+ higher_layer = sorted_layers[i + 1]\n+ # 高层的每个任务都依赖低层的所有任务\n+ for higher_code in layer_groups[higher_layer]:\n+ for lower_code in layer_groups[lower_layer]:\n+ # 避免重复添加已有的显式依赖边\n+ if higher_code not in graph[lower_code]:\n+ graph[lower_code].append(higher_code)\n+ in_degree[higher_code] += 1\n+\n queue = deque(code for code in task_codes if in_degree[code] == 0)\n result = []\n while queue:\ndiff --git a/apps/etl/connectors/feiqiu/quality/consistency_checker.py b/apps/etl/connectors/feiqiu/quality/consistency_checker.py\nindex dc2b075..1bfdf2a 100644\n--- a/apps/etl/connectors/feiqiu/quality/consistency_checker.py\n+++ b/apps/etl/connectors/feiqiu/quality/consistency_checker.py\n@@ -606,6 +606,11 @@ def run_consistency_check(\n report.ods_vs_dwd_results.append(result)\n \n except Exception as exc:\n+ # CHANGE 2026-02-24 | rollback 防止 InFailedSqlTransaction 级联到后续表检查\n+ try:\n+ db_conn.conn.rollback()\n+ except Exception:\n+ pass\n result = TableCheckResult(\n table_name=dwd_full,\n check_type=\"ods_vs_dwd\",\ndiff --git a/apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py b/apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py\nindex 66285ae..d2b6c27 100644\n--- a/apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py\n+++ b/apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py\n@@ -269,6 +269,9 @@ class DwdLoadTask(BaseTask):\n (\"days_on_shelf\", \"days_available\", None),\n (\"sort_order\", \"sort\", None),\n (\"time_slot_sale\", \"time_slot_sale\", None), # CHANGE 2026-02-21: 新增分时段销售标记\n+ (\"warning_sales_day\", \"warning_sales_day\", None), # CHANGE 2026-02-24: 库存预警日均销量\n+ (\"warning_day_max\", \"warning_day_max\", None), # CHANGE 2026-02-24: 预警天数上限\n+ (\"warning_day_min\", \"warning_day_min\", None), # CHANGE 2026-02-24: 预警天数下限\n ],\n \"dwd.dim_goods_category\": [\n (\"category_id\", \"id\", None),\ndiff --git a/apps/etl/connectors/feiqiu/tasks/dws/__init__.py b/apps/etl/connectors/feiqiu/tasks/dws/__init__.py\nindex a585feb..f53b0e8 100644\n--- a/apps/etl/connectors/feiqiu/tasks/dws/__init__.py\n+++ b/apps/etl/connectors/feiqiu/tasks/dws/__init__.py\n@@ -13,6 +13,7 @@ DWS层ETL任务模块\n \n from .base_dws_task import BaseDwsTask, TimeLayer, TimeWindow, CourseType, DiscountType\n from .assistant_daily_task import AssistantDailyTask\n+from .assistant_order_contribution_task import AssistantOrderContributionTask\n from .assistant_monthly_task import AssistantMonthlyTask\n from .assistant_customer_task import AssistantCustomerTask\n from .assistant_salary_task import AssistantSalaryTask\n@@ -47,6 +48,7 @@ __all__ = [\n \"DiscountType\",\n # 助教维度\n \"AssistantDailyTask\",\n+ \"AssistantOrderContributionTask\",\n \"AssistantMonthlyTask\",\n \"AssistantCustomerTask\",\n \"AssistantSalaryTask\",\ndiff --git a/apps/etl/connectors/feiqiu/tasks/dws/assistant_daily_task.py b/apps/etl/connectors/feiqiu/tasks/dws/assistant_daily_task.py\nindex 4ddbeed..b6c0e0a 100644\n--- a/apps/etl/connectors/feiqiu/tasks/dws/assistant_daily_task.py\n+++ b/apps/etl/connectors/feiqiu/tasks/dws/assistant_daily_task.py\n@@ -29,12 +29,19 @@\n \n from __future__ import annotations\n \n-from datetime import date, datetime, timedelta\n-from decimal import Decimal\n+from collections import defaultdict\n+from datetime import date, datetime, time, timedelta\n+from decimal import Decimal, ROUND_HALF_UP\n from typing import Any, Dict, List, Optional, Set, Tuple\n \n from .base_dws_task import BaseDwsTask, CourseType, TaskContext\n \n+# 惩罚区域集合:大厅 A/B/C/S/TV + 麻将房 M1–M7\n+PENALTY_AREAS: Set[str] = {\n+ \"A\", \"B\", \"C\", \"S\", \"TV\",\n+ \"M1\", \"M2\", \"M3\", \"M4\", \"M5\", \"M6\", \"M7\",\n+}\n+\n \n class AssistantDailyTask(BaseDwsTask):\n \"\"\"\n@@ -93,7 +100,7 @@ class AssistantDailyTask(BaseDwsTask):\n \n def transform(self, extracted: Dict[str, Any], context: TaskContext) -> List[Dict[str, Any]]:\n \"\"\"\n- 转换数据:按助教+日期聚合\n+ 转换数据:按助教+日期聚合,并执行定档折算惩罚检测\n \"\"\"\n service_records = extracted['service_records']\n site_id = extracted['site_id']\n@@ -108,6 +115,68 @@ class AssistantDailyTask(BaseDwsTask):\n service_records, \n site_id\n )\n+\n+ # ── 定档折算惩罚检测 ──\n+ # 构造重叠检测所需的记录格式\n+ overlap_records = []\n+ for r in service_records:\n+ start_t = r.get(\"start_use_time\")\n+ end_t = r.get(\"last_use_time\")\n+ if start_t is None or end_t is None:\n+ continue\n+ overlap_records.append({\n+ \"assistant_id\": r.get(\"assistant_id\"),\n+ \"table_id\": r.get(\"table_id\"),\n+ \"table_area\": r.get(\"table_area_name\", \"\"),\n+ \"start_time\": start_t,\n+ \"end_time\": end_t,\n+ \"service_date\": r.get(\"service_date\"),\n+ })\n+\n+ violations = self.detect_overlap_violations(overlap_records, PENALTY_AREAS)\n+\n+ # 将惩罚信息填充到聚合结果\n+ for agg in aggregated:\n+ aid = agg[\"assistant_id\"]\n+ stat_date = agg[\"stat_date\"]\n+ key = (aid, stat_date)\n+\n+ if agg.get(\"is_exempt\"):\n+ # 豁免:不计算惩罚\n+ agg[\"penalty_minutes\"] = Decimal(\"0\")\n+ agg[\"penalty_reason\"] = None\n+ agg[\"is_exempt\"] = True\n+ agg[\"per_hour_contribution\"] = None\n+ elif key in violations:\n+ # 有违规:计算惩罚\n+ # 取第一条违规信息(同一天可能有多条,取最严重的)\n+ v_list = violations[key]\n+ overlap_count = max(v[\"overlap_count\"] for v in v_list)\n+ # per_hour_contribution 需要从台费数据计算\n+ # 此处使用聚合后的 base_ledger_amount 和 base_hours 近似\n+ base_hours = agg.get(\"base_hours\", Decimal(\"0\"))\n+ base_amount = agg.get(\"base_ledger_amount\", Decimal(\"0\"))\n+ if base_hours > 0:\n+ per_hour = base_amount / base_hours / Decimal(str(overlap_count))\n+ else:\n+ per_hour = Decimal(\"0\")\n+\n+ actual_minutes = agg.get(\"base_hours\", Decimal(\"0\")) * Decimal(\"60\")\n+ penalty = self.compute_penalty_minutes(actual_minutes, per_hour)\n+\n+ agg[\"penalty_minutes\"] = penalty\n+ agg[\"penalty_reason\"] = (\n+ f\"规则2违规:同台桌{overlap_count}名助教重叠挂台,\"\n+ f\"单人每小时贡献={per_hour:.2f}元\"\n+ )\n+ agg[\"is_exempt\"] = False\n+ agg[\"per_hour_contribution\"] = per_hour\n+ else:\n+ # 无违规\n+ agg[\"penalty_minutes\"] = Decimal(\"0\")\n+ agg[\"penalty_reason\"] = None\n+ agg[\"is_exempt\"] = False\n+ agg[\"per_hour_contribution\"] = None\n \n return aggregated\n \n@@ -143,6 +212,9 @@ class AssistantDailyTask(BaseDwsTask):\n asl.real_use_seconds,\n asl.ledger_amount,\n asl.ledger_unit_price,\n+ asl.start_use_time,\n+ asl.last_use_time,\n+ asl.table_area_name,\n DATE(asl.start_use_time) AS service_date,\n COALESCE(ex.is_trash, 0) AS is_trash\n FROM dwd.dwd_assistant_service_log asl\n@@ -281,6 +353,131 @@ class AssistantDailyTask(BaseDwsTask):\n \n return result\n \n+ # ==========================================================================\n+ # 定档折算惩罚 — 纯函数(静态方法,不依赖数据库)\n+ # ==========================================================================\n+\n+ @staticmethod\n+ def detect_overlap_violations(\n+ service_records: List[Dict[str, Any]],\n+ penalty_areas: Set[str],\n+ ) -> Dict[Tuple[int, date], List[Dict[str, Any]]]:\n+ \"\"\"\n+ 检测同一台桌同一时间段超过 2 名助教挂台的违规。\n+\n+ 输入:\n+ service_records: 服务记录\n[TRUNCATED: apps/etl/connectors/feiqiu/tasks/dws/assistant_daily_task.py diff too long]\ndiff --git a/apps/etl/connectors/feiqiu/tasks/dws/member_consumption_task.py b/apps/etl/connectors/feiqiu/tasks/dws/member_consumption_task.py\nindex a7bd10e..e618dbb 100644\n--- a/apps/etl/connectors/feiqiu/tasks/dws/member_consumption_task.py\n+++ b/apps/etl/connectors/feiqiu/tasks/dws/member_consumption_task.py\n@@ -85,10 +85,14 @@ class MemberConsumptionTask(BaseDwsTask):\n # 3. 获取会员卡余额\n card_balances = self._extract_card_balances(site_id)\n \n+ # CHANGE 2025-07-15 | task 4.1: 获取充值统计(30/60/90 天窗口)\n+ recharge_stats = self._extract_recharge_stats(site_id, stat_date)\n+ \n return {\n 'consumption_stats': consumption_stats,\n 'member_info': member_info,\n 'card_balances': card_balances,\n+ 'recharge_stats': recharge_stats,\n 'stat_date': stat_date,\n 'site_id': site_id\n }\n@@ -100,6 +104,7 @@ class MemberConsumptionTask(BaseDwsTask):\n consumption_stats = extracted['consumption_stats']\n member_info = extracted['member_info']\n card_balances = extracted['card_balances']\n+ recharge_stats = extracted.get('recharge_stats', {})\n stat_date = extracted['stat_date']\n site_id = extracted['site_id']\n \n@@ -119,11 +124,20 @@ class MemberConsumptionTask(BaseDwsTask):\n \n memb_info = member_info.get(member_id, {})\n balance = card_balances.get(member_id, {})\n+ # CHANGE 2025-07-15 | task 4.2: 合并充值统计,无记录时默认 0\n+ recharge = recharge_stats.get(member_id, {})\n \n # 计算活跃度和客户分层\n days_since_last = self._calc_days_since(stat_date, stats.get('last_consume_date'))\n customer_tier = self._calculate_customer_tier(stats, days_since_last)\n \n+ # CHANGE 2025-07-15 | task 4.2: 次均消费 = total_consume_amount / MAX(total_visit_count, 1)\n+ total_consume_amount = self.safe_decimal(stats.get('total_consume_amount', 0))\n+ total_visit_count = self.safe_int(stats.get('total_visit_count', 0))\n+ avg_ticket_amount = (\n+ total_consume_amount / max(total_visit_count, 1)\n+ ).quantize(Decimal('0.01'))\n+ \n record = {\n 'site_id': site_id,\n 'tenant_id': self.config.get(\"app.tenant_id\", site_id),\n@@ -137,8 +151,8 @@ class MemberConsumptionTask(BaseDwsTask):\n # 全量累计统计\n 'first_consume_date': stats.get('first_consume_date'),\n 'last_consume_date': stats.get('last_consume_date'),\n- 'total_visit_count': self.safe_int(stats.get('total_visit_count', 0)),\n- 'total_consume_amount': self.safe_decimal(stats.get('total_consume_amount', 0)),\n+ 'total_visit_count': total_visit_count,\n+ 'total_consume_amount': total_consume_amount,\n 'total_recharge_amount': self.safe_decimal(memb_info.get('recharge_money_sum', 0)),\n 'total_table_fee': self.safe_decimal(stats.get('total_table_fee', 0)),\n 'total_goods_amount': self.safe_decimal(stats.get('total_goods_amount', 0)),\n@@ -156,6 +170,15 @@ class MemberConsumptionTask(BaseDwsTask):\n 'consume_amount_30d': self.safe_decimal(stats.get('consume_amount_30d', 0)),\n 'consume_amount_60d': self.safe_decimal(stats.get('consume_amount_60d', 0)),\n 'consume_amount_90d': self.safe_decimal(stats.get('consume_amount_90d', 0)),\n+ # 充值窗口统计(30/60/90 天)\n+ 'recharge_count_30d': self.safe_int(recharge.get('count_30d', 0)),\n+ 'recharge_count_60d': self.safe_int(recharge.get('count_60d', 0)),\n+ 'recharge_count_90d': self.safe_int(recharge.get('count_90d', 0)),\n+ 'recharge_amount_30d': self.safe_decimal(recharge.get('amount_30d', 0)),\n+ 'recharge_amount_60d': self.safe_decimal(recharge.get('amount_60d', 0)),\n+ 'recharge_amount_90d': self.safe_decimal(recharge.get('amount_90d', 0)),\n+ # 次均消费\n+ 'avg_ticket_amount': avg_ticket_amount,\n # 卡余额\n 'cash_card_balance': self.safe_decimal(balance.get('cash_balance', 0)),\n 'gift_card_balance': self.safe_decimal(balance.get('gift_balance', 0)),\n@@ -259,13 +282,14 @@ class MemberConsumptionTask(BaseDwsTask):\n ) AS birthday\n FROM dwd.dim_member m\n WHERE m.member_id IN (\n- SELECT DISTINCT tenant_member_id\n+ SELECT DISTINCT member_id\n FROM dwd.dwd_settlement_head\n WHERE site_id = %s\n- AND tenant_member_id IS NOT NULL\n- AND tenant_member_id != 0\n+ AND member_id IS NOT NULL\n+ AND member_id != 0\n ) AND m.scd2_is_current = 1\n \"\"\"\n+ # CHANGE 2026-02-24 | 修复列\n[TRUNCATED: apps/etl/connectors/feiqiu/tasks/dws/member_consumption_task.py diff too long]\n\n[TRUNCATED: diff exceeds 30KB]", "latest_prompt_log": "- [P20260226-074515] 2026-02-26 07:45:15 +0800\n - summary: - 调查说明现在HOOKS是如何判断文件的修改?- 对这3个HOOKS的详细工作说明,处理流程,输出一个.md文档。\n - prompt:\n```text\n- 调查说明现在HOOKS是如何判断文件的修改?- 对这3个HOOKS的详细工作说明,处理流程,输出一个.md文档。\n```\n" }