改 相对路径 完成客户端
This commit is contained in:
@@ -11,6 +11,7 @@ from psycopg2.extras import Json, execute_values
|
||||
|
||||
from models.parsers import TypeParser
|
||||
from .base_task import BaseTask
|
||||
from utils.windowing import build_window_segments, calc_window_minutes
|
||||
|
||||
|
||||
ColumnTransform = Callable[[Any], Any]
|
||||
@@ -64,64 +65,112 @@ class BaseOdsTask(BaseTask):
|
||||
|
||||
def execute(self, cursor_data: dict | None = None) -> dict:
|
||||
spec = self.SPEC
|
||||
self.logger.info("寮€濮嬫墽琛?%s (ODS)", spec.code)
|
||||
self.logger.info("开始执行%s (ODS)", spec.code)
|
||||
|
||||
window_start, window_end, window_minutes = self._resolve_window(cursor_data)
|
||||
segments = build_window_segments(
|
||||
self.config,
|
||||
window_start,
|
||||
window_end,
|
||||
tz=self.tz,
|
||||
override_only=True,
|
||||
)
|
||||
if not segments:
|
||||
segments = [(window_start, window_end)]
|
||||
|
||||
total_segments = len(segments)
|
||||
if total_segments > 1:
|
||||
self.logger.info("%s: 窗口拆分为 %s 段", spec.code, total_segments)
|
||||
|
||||
store_id = TypeParser.parse_int(self.config.get("app.store_id"))
|
||||
if not store_id:
|
||||
raise ValueError("app.store_id 鏈厤缃紝鏃犳硶鎵ц ODS 浠诲姟")
|
||||
raise ValueError("app.store_id 未配置,无法执行 ODS 任务")
|
||||
|
||||
page_size = self.config.get("api.page_size", 200)
|
||||
params = self._build_params(
|
||||
spec,
|
||||
store_id,
|
||||
window_start=window_start,
|
||||
window_end=window_end,
|
||||
)
|
||||
|
||||
counts = {"fetched": 0, "inserted": 0, "updated": 0, "skipped": 0, "errors": 0}
|
||||
total_counts = {"fetched": 0, "inserted": 0, "updated": 0, "skipped": 0, "errors": 0}
|
||||
segment_results: list[dict] = []
|
||||
params_list: list[dict] = []
|
||||
source_file = self._resolve_source_file_hint(spec)
|
||||
|
||||
try:
|
||||
for _, page_records, _, response_payload in self.api.iter_paginated(
|
||||
endpoint=spec.endpoint,
|
||||
params=params,
|
||||
page_size=page_size,
|
||||
data_path=spec.data_path,
|
||||
list_key=spec.list_key,
|
||||
):
|
||||
inserted, skipped = self._insert_records_schema_aware(
|
||||
table=spec.table_name,
|
||||
records=page_records,
|
||||
response_payload=response_payload,
|
||||
source_file=source_file,
|
||||
source_endpoint=spec.endpoint if spec.include_source_endpoint else None,
|
||||
for idx, (seg_start, seg_end) in enumerate(segments, start=1):
|
||||
params = self._build_params(
|
||||
spec,
|
||||
store_id,
|
||||
window_start=seg_start,
|
||||
window_end=seg_end,
|
||||
)
|
||||
counts["fetched"] += len(page_records)
|
||||
counts["inserted"] += inserted
|
||||
counts["skipped"] += skipped
|
||||
params_list.append(params)
|
||||
segment_counts = {"fetched": 0, "inserted": 0, "updated": 0, "skipped": 0, "errors": 0}
|
||||
|
||||
self.db.commit()
|
||||
self.logger.info("%s ODS 浠诲姟瀹屾垚: %s", spec.code, counts)
|
||||
self.logger.info(
|
||||
"%s: 开始执行(%s/%s),窗口[%s ~ %s]",
|
||||
spec.code,
|
||||
idx,
|
||||
total_segments,
|
||||
seg_start,
|
||||
seg_end,
|
||||
)
|
||||
|
||||
for _, page_records, _, response_payload in self.api.iter_paginated(
|
||||
endpoint=spec.endpoint,
|
||||
params=params,
|
||||
page_size=page_size,
|
||||
data_path=spec.data_path,
|
||||
list_key=spec.list_key,
|
||||
):
|
||||
inserted, skipped = self._insert_records_schema_aware(
|
||||
table=spec.table_name,
|
||||
records=page_records,
|
||||
response_payload=response_payload,
|
||||
source_file=source_file,
|
||||
source_endpoint=spec.endpoint if spec.include_source_endpoint else None,
|
||||
)
|
||||
segment_counts["fetched"] += len(page_records)
|
||||
segment_counts["inserted"] += inserted
|
||||
segment_counts["skipped"] += skipped
|
||||
|
||||
self.db.commit()
|
||||
self._accumulate_counts(total_counts, segment_counts)
|
||||
if total_segments > 1:
|
||||
segment_results.append(
|
||||
{
|
||||
"window": {
|
||||
"start": seg_start,
|
||||
"end": seg_end,
|
||||
"minutes": calc_window_minutes(seg_start, seg_end),
|
||||
},
|
||||
"counts": segment_counts,
|
||||
}
|
||||
)
|
||||
|
||||
self.logger.info("%s ODS 任务完成: %s", spec.code, total_counts)
|
||||
allow_empty_advance = bool(self.config.get("run.allow_empty_result_advance", False))
|
||||
status = "SUCCESS"
|
||||
if counts["fetched"] == 0 and not allow_empty_advance:
|
||||
if total_counts["fetched"] == 0 and not allow_empty_advance:
|
||||
status = "PARTIAL"
|
||||
|
||||
result = self._build_result(status, counts)
|
||||
result = self._build_result(status, total_counts)
|
||||
overall_start = segments[0][0]
|
||||
overall_end = segments[-1][1]
|
||||
result["window"] = {
|
||||
"start": window_start,
|
||||
"end": window_end,
|
||||
"minutes": window_minutes,
|
||||
"start": overall_start,
|
||||
"end": overall_end,
|
||||
"minutes": calc_window_minutes(overall_start, overall_end),
|
||||
}
|
||||
result["request_params"] = params
|
||||
if total_segments > 1:
|
||||
result["segments"] = segment_results
|
||||
if len(params_list) == 1:
|
||||
result["request_params"] = params_list[0]
|
||||
else:
|
||||
result["request_params"] = params_list
|
||||
return result
|
||||
|
||||
except Exception:
|
||||
self.db.rollback()
|
||||
counts["errors"] += 1
|
||||
self.logger.error("%s ODS 浠诲姟澶辫触", spec.code, exc_info=True)
|
||||
total_counts["errors"] += 1
|
||||
self.logger.error("%s ODS 任务失败", spec.code, exc_info=True)
|
||||
raise
|
||||
|
||||
def _resolve_window(self, cursor_data: dict | None) -> tuple[datetime, datetime, int]:
|
||||
@@ -966,72 +1015,121 @@ class OdsSettlementTicketTask(BaseOdsTask):
|
||||
|
||||
def execute(self, cursor_data: dict | None = None) -> dict:
|
||||
spec = self.SPEC
|
||||
context = self._build_context(cursor_data)
|
||||
store_id = TypeParser.parse_int(self.config.get("app.store_id")) or 0
|
||||
base_context = self._build_context(cursor_data)
|
||||
segments = build_window_segments(
|
||||
self.config,
|
||||
base_context.window_start,
|
||||
base_context.window_end,
|
||||
tz=self.tz,
|
||||
override_only=True,
|
||||
)
|
||||
if not segments:
|
||||
segments = [(base_context.window_start, base_context.window_end)]
|
||||
|
||||
counts = {"fetched": 0, "inserted": 0, "updated": 0, "skipped": 0, "errors": 0}
|
||||
total_segments = len(segments)
|
||||
if total_segments > 1:
|
||||
self.logger.info("%s: 窗口拆分为 %s 段", spec.code, total_segments)
|
||||
|
||||
store_id = TypeParser.parse_int(self.config.get("app.store_id")) or 0
|
||||
counts_total = {"fetched": 0, "inserted": 0, "updated": 0, "skipped": 0, "errors": 0}
|
||||
segment_results: list[dict] = []
|
||||
source_file = self._resolve_source_file_hint(spec)
|
||||
|
||||
try:
|
||||
existing_ids = self._fetch_existing_ticket_ids()
|
||||
candidates = self._collect_settlement_ids(
|
||||
store_id, existing_ids, context.window_start, context.window_end
|
||||
)
|
||||
candidates = [cid for cid in candidates if cid and cid not in existing_ids]
|
||||
counts["fetched"] = len(candidates)
|
||||
|
||||
if not candidates:
|
||||
for idx, (seg_start, seg_end) in enumerate(segments, start=1):
|
||||
context = self._build_context_for_window(seg_start, seg_end, cursor_data)
|
||||
self.logger.info(
|
||||
"%s: 绐楀彛[%s ~ %s] 鏈彂鐜伴渶瑕佹姄鍙栫殑灏忕エ",
|
||||
"%s: 开始执行(%s/%s),窗口[%s ~ %s]",
|
||||
spec.code,
|
||||
idx,
|
||||
total_segments,
|
||||
context.window_start,
|
||||
context.window_end,
|
||||
)
|
||||
result = self._build_result("SUCCESS", counts)
|
||||
result["window"] = {
|
||||
"start": context.window_start,
|
||||
"end": context.window_end,
|
||||
"minutes": context.window_minutes,
|
||||
}
|
||||
result["request_params"] = {"candidates": 0}
|
||||
return result
|
||||
|
||||
payloads, skipped = self._fetch_ticket_payloads(candidates)
|
||||
counts["skipped"] += skipped
|
||||
inserted, skipped2 = self._insert_records_schema_aware(
|
||||
table=spec.table_name,
|
||||
records=payloads,
|
||||
response_payload=None,
|
||||
source_file=source_file,
|
||||
source_endpoint=spec.endpoint,
|
||||
)
|
||||
counts["inserted"] += inserted
|
||||
counts["skipped"] += skipped2
|
||||
self.db.commit()
|
||||
candidates = self._collect_settlement_ids(
|
||||
store_id, existing_ids, context.window_start, context.window_end
|
||||
)
|
||||
candidates = [cid for cid in candidates if cid and cid not in existing_ids]
|
||||
segment_counts = {"fetched": 0, "inserted": 0, "updated": 0, "skipped": 0, "errors": 0}
|
||||
segment_counts["fetched"] = len(candidates)
|
||||
|
||||
if not candidates:
|
||||
self.logger.info(
|
||||
"%s: 窗口[%s ~ %s] 未发现需要抓取的小票",
|
||||
spec.code,
|
||||
context.window_start,
|
||||
context.window_end,
|
||||
)
|
||||
self._accumulate_counts(counts_total, segment_counts)
|
||||
if total_segments > 1:
|
||||
segment_results.append(
|
||||
{
|
||||
"window": {
|
||||
"start": context.window_start,
|
||||
"end": context.window_end,
|
||||
"minutes": context.window_minutes,
|
||||
},
|
||||
"counts": segment_counts,
|
||||
}
|
||||
)
|
||||
continue
|
||||
|
||||
payloads, skipped = self._fetch_ticket_payloads(candidates)
|
||||
segment_counts["skipped"] += skipped
|
||||
inserted, skipped2 = self._insert_records_schema_aware(
|
||||
table=spec.table_name,
|
||||
records=payloads,
|
||||
response_payload=None,
|
||||
source_file=source_file,
|
||||
source_endpoint=spec.endpoint,
|
||||
)
|
||||
segment_counts["inserted"] += inserted
|
||||
segment_counts["skipped"] += skipped2
|
||||
|
||||
self.db.commit()
|
||||
existing_ids.update(candidates)
|
||||
self._accumulate_counts(counts_total, segment_counts)
|
||||
if total_segments > 1:
|
||||
segment_results.append(
|
||||
{
|
||||
"window": {
|
||||
"start": context.window_start,
|
||||
"end": context.window_end,
|
||||
"minutes": context.window_minutes,
|
||||
},
|
||||
"counts": segment_counts,
|
||||
}
|
||||
)
|
||||
|
||||
self.logger.info(
|
||||
"%s: 灏忕エ鎶撳彇瀹屾垚锛屽€欓€?%s 鎻掑叆=%s 鏇存柊=%s 璺宠繃=%s",
|
||||
"%s: 小票抓取完成,抓取=%s 插入=%s 更新=%s 跳过=%s",
|
||||
spec.code,
|
||||
len(candidates),
|
||||
inserted,
|
||||
counts_total["fetched"],
|
||||
counts_total["inserted"],
|
||||
0,
|
||||
counts["skipped"],
|
||||
counts_total["skipped"],
|
||||
)
|
||||
result = self._build_result("SUCCESS", counts)
|
||||
result = self._build_result("SUCCESS", counts_total)
|
||||
overall_start = segments[0][0]
|
||||
overall_end = segments[-1][1]
|
||||
result["window"] = {
|
||||
"start": context.window_start,
|
||||
"end": context.window_end,
|
||||
"minutes": context.window_minutes,
|
||||
"start": overall_start,
|
||||
"end": overall_end,
|
||||
"minutes": calc_window_minutes(overall_start, overall_end),
|
||||
}
|
||||
result["request_params"] = {"candidates": len(candidates)}
|
||||
if segment_results:
|
||||
result["segments"] = segment_results
|
||||
result["request_params"] = {"candidates": counts_total["fetched"]}
|
||||
return result
|
||||
|
||||
except Exception:
|
||||
counts["errors"] += 1
|
||||
counts_total["errors"] += 1
|
||||
self.db.rollback()
|
||||
self.logger.error("%s: 灏忕エ鎶撳彇澶辫触", spec.code, exc_info=True)
|
||||
self.logger.error("%s: 小票抓取失败", spec.code, exc_info=True)
|
||||
raise
|
||||
|
||||
# ------------------------------------------------------------------ helpers
|
||||
def _fetch_existing_ticket_ids(self) -> set[int]:
|
||||
sql = """
|
||||
SELECT DISTINCT
|
||||
|
||||
Reference in New Issue
Block a user