feat(snapshot-admin): align store validation and db snapshots

This commit is contained in:
2026-06-23 18:01:01 +09:00
parent f73a66818f
commit 13185b79d2
5 changed files with 47 additions and 61 deletions
Binary file not shown.
Binary file not shown.
+41 -2
View File
@@ -810,6 +810,25 @@ def _as_number(value: Any) -> float | None:
return None
def _as_int(value: Any) -> int | None:
if value is None or value == "":
return None
if isinstance(value, bool):
return None
if isinstance(value, int):
return value
if isinstance(value, float):
return int(value) if value.is_integer() else None
try:
text = str(value).strip().replace(",", "")
if not text:
return None
parsed = float(text)
return int(parsed) if parsed.is_integer() else None
except Exception:
return None
@lru_cache(maxsize=1)
def _load_settings_spec() -> dict[str, Any]:
return yaml.safe_load(SETTINGS_SPEC_PATH.read_text(encoding="utf-8")) or {}
@@ -867,8 +886,14 @@ def validate_account_snapshot_rows(rows: list[dict[str, Any]]) -> list[str]:
name = str(row.get("name") or "").strip()
account_type = str(row.get("account_type") or "").strip()
parse_status = str(row.get("parse_status") or "").strip()
holding_quantity = _as_number(row.get("holding_quantity"))
holding_quantity = _as_int(row.get("holding_quantity"))
available_quantity = _as_int(row.get("available_quantity"))
average_cost = _as_number(row.get("average_cost"))
total_cost = _as_number(row.get("total_cost"))
current_price = _as_number(row.get("current_price"))
market_value = _as_number(row.get("market_value"))
profit_loss = _as_number(row.get("profit_loss"))
return_pct = _as_number(row.get("return_pct"))
stop_price = _as_number(row.get("stop_price"))
entry_stage = str(row.get("entry_stage") or "").strip()
position_type = str(row.get("position_type") or "").strip()
@@ -881,7 +906,7 @@ def validate_account_snapshot_rows(rows: list[dict[str, Any]]) -> list[str]:
errors.append(f"account_snapshot row {idx}: account_type required")
if account_type and canonical.get("account_type", {}).get("allowed") and account_type not in canonical["account_type"]["allowed"]:
errors.append(f"account_snapshot row {idx}: invalid account_type {account_type!r}")
if not ticker:
if not ticker and name != "예수금/D+2현금":
errors.append(f"account_snapshot row {idx}: ticker required")
if not name:
errors.append(f"account_snapshot row {idx}: name required")
@@ -889,8 +914,22 @@ def validate_account_snapshot_rows(rows: list[dict[str, Any]]) -> list[str]:
errors.append(f"account_snapshot row {idx}: invalid parse_status {parse_status!r}")
if holding_quantity is not None and holding_quantity < 0:
errors.append(f"account_snapshot row {idx}: holding_quantity must be >= 0")
if available_quantity is not None and available_quantity < 0:
errors.append(f"account_snapshot row {idx}: available_quantity must be >= 0")
if average_cost is not None and average_cost < 0:
errors.append(f"account_snapshot row {idx}: average_cost must be >= 0")
if total_cost is not None and total_cost < 0:
errors.append(f"account_snapshot row {idx}: total_cost must be >= 0")
if current_price is not None and current_price < 0:
errors.append(f"account_snapshot row {idx}: current_price must be >= 0")
if market_value is not None and market_value < 0:
errors.append(f"account_snapshot row {idx}: market_value must be >= 0")
if profit_loss is not None and profit_loss != profit_loss:
errors.append(f"account_snapshot row {idx}: profit_loss invalid")
if return_pct is not None and abs(return_pct) > 1000:
errors.append(f"account_snapshot row {idx}: return_pct out of range")
if stop_price is not None and stop_price < 0:
errors.append(f"account_snapshot row {idx}: stop_price must be >= 0")
if user_confirmed and user_confirmed not in {"Y", "N"}:
errors.append(f"account_snapshot row {idx}: user_confirmed must be Y or N")
if parse_status == "CAPTURE_READ_OK" and user_confirmed != "Y":