Files
HM_project_Viewer_Board/scripts/retry_failed_wehago_accounts_direct.py
T

227 lines
8.3 KiB
Python

from __future__ import annotations
import base64
import hashlib
import hmac
import json
import os
import sys
import time
import urllib.parse
import urllib.request
import uuid
from datetime import datetime
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
import scripts.refresh_hanmac_wehago_ledgers as refresh
import scripts.wehago_data_download_2022_work as wehago
import scripts.wehago_ledger_api_download as api_download
SOURCE_RUNS = {
2022: "20260615_194953",
2023: "20260615_195024",
2024: "20260615_195215",
2025: "20260615_195405",
}
DIRECT_GISU_BY_YEAR = dict(refresh.GISU_BY_YEAR)
LEDGER_API_URL = "https://api.wehago.com/smarta/sabk0107/jungi_slip/"
LEDGER_API_PATH = "/smarta/sabk0107/jungi_slip/"
def wsl_unc(path: str) -> Path:
if os.name == "nt" and path.startswith("/home/"):
return Path(r"\\wsl.localhost\Ubuntu" + path.replace("/", "\\"))
return Path(path)
def report_entries(report: dict[str, Any]) -> list[dict[str, Any]]:
reports = report.get("reports")
if isinstance(reports, list):
return reports
if isinstance(reports, dict):
return list(reports.values())
return [report]
def failed_codes_from_report(report_path: Path) -> list[str]:
report = json.loads(report_path.read_text(encoding="utf-8"))
entries = report_entries(report)
if not entries:
return []
validation = entries[0].get("validation", {})
provenance = validation.get("api_provenance") or validation.get("api_progress") or {}
return [
str(item["account_code"])
for item in provenance.get("download_failures", [])
if item.get("account_code")
]
def cookie_map(driver: Any) -> dict[str, str]:
cookies = {item["name"]: item.get("value", "") for item in driver.get_cookies()}
if "AUTH_A_TOKEN" not in cookies or "wehago_s" not in cookies:
script_cookies = driver.execute_script("return document.cookie || '';") or ""
for part in str(script_cookies).split(";"):
if "=" not in part:
continue
key, value = part.strip().split("=", 1)
cookies.setdefault(key, value)
missing = [key for key in ("AUTH_A_TOKEN", "wehago_s") if not cookies.get(key)]
if missing:
raise RuntimeError(f"WEHAGO 인증 쿠키를 찾지 못했습니다: {missing}")
return cookies
def wehago_sign(wehago_s: str, path_search: str, timestamp: str, transaction_id: str) -> str:
secret = base64.b64encode(hashlib.sha256((wehago_s + timestamp).encode("utf-8")).digest()).decode("ascii")
digest = hmac.new(
secret.encode("utf-8"),
(path_search + timestamp + transaction_id).encode("utf-8"),
hashlib.sha256,
).digest()
return base64.b64encode(digest).decode("ascii")
def ledger_payload(year: int, gisu: int, account: wehago.Account, wehago_s: str, timestamp: str) -> dict[str, str]:
code = f"{account.code}00"
return {
"start_slip_date": f"{year}0101",
"end_slip_date": f"{year}1231",
"from_search_date": f"{year}0101",
"to_search_date": f"{year}1231",
"from_cd_acctit": code,
"to_cd_acctit": code,
"cd_details": "",
"gb_code": "0",
"gb_semok": "0",
"gisu": str(gisu),
"gubn_mon": "",
"gubn_total": "",
"gubn_balance": "0",
"balance_color": "0",
"sort": "1",
"mn_gubun": "0",
"mn_start": "0",
"mn_end": "9999999999999999",
"count_check": "1",
"timestamp": timestamp,
"cno": "1173867",
"ccode": "biz202103030006368",
"user_id": "b21344",
"ym_insa": "2026",
"wehago_s": wehago_s,
"oldview": "0" if year == 2025 else "1",
"locale": "ko",
}
def fetch_ledger_rows(year: int, account: wehago.Account, cookies: dict[str, str]) -> list[dict[str, Any]]:
timestamp = str(int(time.time()))
transaction_id = uuid.uuid4().hex[:10]
payload = ledger_payload(year, DIRECT_GISU_BY_YEAR[year], account, cookies["wehago_s"], timestamp)
body = urllib.parse.urlencode(payload).encode("utf-8")
headers = {
"Authorization": f"Bearer {cookies['AUTH_A_TOKEN']}",
"Content-Type": "application/x-www-form-urlencoded",
"timestamp": timestamp,
"transaction-id": transaction_id,
"wehago-sign": wehago_sign(cookies["wehago_s"], LEDGER_API_PATH, timestamp, transaction_id),
"client-id": "smarta",
"service": "smarta",
"cno": "1173867",
"Origin": "https://smarta.wehago.com",
"Referer": "https://smarta.wehago.com/",
}
request = urllib.request.Request(LEDGER_API_URL, data=body, headers=headers, method="POST")
with urllib.request.urlopen(request, timeout=30) as response:
rows = json.loads(response.read().decode("utf-8"))
if not isinstance(rows, list):
raise RuntimeError(f"{account.code}: API 응답이 목록이 아닙니다: {type(rows).__name__}")
return rows
def main() -> int:
base = wsl_unc("/home/b17301/WEHAGO_DB/data_download/hanmac_refresh")
canonical_base = wsl_unc("/home/b17301/WEHAGO_DB/data_download/hanmac")
run_root = base / f"retry_failed_direct_{datetime.now():%Y%m%d_%H%M%S}"
run_root.mkdir(parents=True, exist_ok=True)
wehago.CHROME_DEBUGGER_ADDRESS = "127.0.0.1:9225"
wehago.DOWNLOAD_DIR = run_root
driver = wehago.build_driver(run_root)
try:
cookies = cookie_map(driver)
finally:
driver.quit()
summary: dict[str, Any] = {}
for year, source_run in SOURCE_RUNS.items():
failed_codes = failed_codes_from_report(base / source_run / "refresh_report.json")
canonical_dir = canonical_base / str(year)
by_code = {account.code: account for account in wehago.discover_downloaded_accounts(canonical_dir)}
accounts = [by_code[code] for code in failed_codes if code in by_code]
staging_dir = run_root / "staging" / str(year)
staging_dir.mkdir(parents=True, exist_ok=True)
manifest: list[dict[str, Any]] = []
failures: list[dict[str, str]] = []
print(f"YEAR {year}: direct API retry accounts={len(accounts)} staging={staging_dir}", flush=True)
for index, account in enumerate(accounts, start=1):
try:
rows = fetch_ledger_rows(year, account, cookies)
api_download.validate_api_rows(account, rows)
target = staging_dir / account.safe_filename
api_download.write_api_rows(target, account, rows)
manifest.append(
{
"account_code": account.code,
"account_name": account.name,
"api_request_code": f"{account.code}00",
"api_response_rows": len(rows),
}
)
print(f"[{year} {index}/{len(accounts)}] OK {account.code} rows={len(rows)}", flush=True)
except Exception as exc:
reason = f"{type(exc).__name__}: {exc}"
failures.append({"account_code": account.code, "account_name": account.name, "reason": reason})
print(f"[{year} {index}/{len(accounts)}] FAIL {account.code}: {reason}", flush=True)
api_download.write_progress(
staging_dir,
{
"status": "completed_with_failures" if failures else "completed",
"completed": len(manifest),
"total": len(accounts),
"accounts": manifest,
"failures": failures,
},
)
validation = refresh.validate_staging(staging_dir)
summary[str(year)] = {
"requested": len(accounts),
"downloaded": len(manifest),
"failures": len(failures),
"failed_codes": [item["account_code"] for item in failures],
"staging": str(staging_dir),
"validation": validation,
}
(run_root / "retry_summary.json").write_text(
json.dumps(summary, ensure_ascii=False, indent=2),
encoding="utf-8",
)
print(f"YEAR {year}: downloaded={len(manifest)} failures={len(failures)}", flush=True)
print(f"SUMMARY_PATH {run_root / 'retry_summary.json'}", flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())