"""Normalize peer data cards without assuming an upstream/downstream dependency.""" import math import re from decimal import Decimal, InvalidOperation from datetime import date MONEY_FIELDS = {"facility.amount", "financial.total_assets", "financial.total_liabilities", "financial.operating_cashflow", "lease.total_value", "insurance.amount", "deposit.amount", "guarantee.amount"} SCALE = {"元": Decimal("0.0001"), "万元": Decimal(1), "亿元": Decimal(10000)} def period_key(value): text = str(value or "") if re.fullmatch(r"\d{4}", text): return date(int(text), 12, 31) if re.fullmatch(r"\d{4}-\d{2}", text): return date.fromisoformat(text + "-01") return date.fromisoformat(text) def align(card, rows, project_id, requested_period=None): config = card.get("alignment", {}) project_field = config.get("project_field", "project_id") period_field = config.get("period_field", "") scope_field = config.get("scope_field", "") selection = config.get("selection", "single") if not rows: raise ValueError("未取得项目记录,不得解释为无风险") if not project_field or any(str(row.get(project_field, "")) != project_id for row in rows): raise ValueError("返回记录的项目标识缺失或不一致,请检查查询和项目关联字段") records = [] for row in rows: period = row.get(period_field) if period_field else card.get("period") scope = row.get(scope_field) if scope_field else card.get("scope") mapped, units = {}, {} for field, source in card.get("mapping", {}).items(): value = row.get(source) unit = card.get("field_units", {}).get(field, card.get("unit") if field in MONEY_FIELDS else None) if value is not None and unit: if unit not in SCALE: raise ValueError(f"{field} 的金额单位不支持:{unit}") try: value = float(Decimal(str(value)) * SCALE[unit]) if not math.isfinite(value): raise ValueError("非有限值") except (ValueError, InvalidOperation): raise ValueError(f"{field} 金额不是有效数值") units[field] = "万元" mapped[field] = value records.append({"period": period, "scope": scope, "values": mapped, "units": units, "raw": row}) target_scope = config.get("target_scope", "") candidates = [r for r in records if not target_scope or r["scope"] == target_scope] if not candidates: raise ValueError("未取得指定财务口径的记录") if len({r["scope"] for r in candidates}) > 1: raise ValueError("返回多个财务口径,请明确目标口径,不能混合计算") if selection in ("latest", "period", "all"): try: for r in candidates: period_key(r["period"]) except (ValueError, TypeError): raise ValueError("多期记录缺少有效期间,应使用 YYYY、YYYY-MM 或 YYYY-MM-DD") keys = [(r["period"], r["scope"]) for r in candidates] if len(set(keys)) != len(keys): raise ValueError("同一期间与口径存在重复记录,可能由多表关联放大行数") if (requested_period and period_field) or selection == "period": target = requested_period or config.get("target_period") candidates = [r for r in candidates if str(r["period"]) == str(target)] elif selection == "latest": newest = max(period_key(r["period"]) for r in candidates) candidates = [r for r in candidates if period_key(r["period"]) == newest] if selection == "all" and not (requested_period and period_field): return records, None if len(candidates) != 1: raise ValueError(f"取得 {len(candidates)} 条候选记录,请指定期间或明确唯一粒度") return records, candidates[0]