feat: restore source parity and harden agent runtime
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""解析 JNPF 风格左/上父格(none / default / custom)。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
CellKey = Tuple[str, int, int]
|
||||
|
||||
|
||||
def col_letter_to_index(letters: str) -> int:
|
||||
s = (letters or "A").upper()
|
||||
n = 0
|
||||
for ch in s:
|
||||
if not ("A" <= ch <= "Z"):
|
||||
continue
|
||||
n = n * 26 + (ord(ch) - 64)
|
||||
return max(0, n - 1)
|
||||
|
||||
|
||||
def col_index_to_letter(col: int) -> str:
|
||||
n = col
|
||||
s = ""
|
||||
while n >= 0:
|
||||
s = chr(65 + (n % 26)) + s
|
||||
n = n // 26 - 1
|
||||
return s or "A"
|
||||
|
||||
|
||||
def _int_coord(v: Any, default: int = 0) -> int:
|
||||
try:
|
||||
return int(v)
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
|
||||
def cell_key(cell: Dict[str, Any]) -> CellKey:
|
||||
return (
|
||||
str(cell.get("sheet") or "sheet1"),
|
||||
_int_coord(cell.get("row"), 0),
|
||||
_int_coord(cell.get("col"), 0),
|
||||
)
|
||||
|
||||
|
||||
def _is_data_source(cell: Dict[str, Any]) -> bool:
|
||||
return cell.get("type") == "dataSource"
|
||||
|
||||
|
||||
def _index_data_sources(cells: List[Dict[str, Any]]) -> Tuple[Dict[CellKey, Dict[str, Any]], Dict[str, List[Dict[str, Any]]]]:
|
||||
by_pos: Dict[CellKey, Dict[str, Any]] = {}
|
||||
by_sheet: Dict[str, List[Dict[str, Any]]] = {}
|
||||
for c in cells:
|
||||
if not _is_data_source(c):
|
||||
continue
|
||||
k = cell_key(c)
|
||||
by_pos[k] = c
|
||||
by_sheet.setdefault(k[0], []).append(c)
|
||||
return by_pos, by_sheet
|
||||
|
||||
|
||||
def resolve_parent(
|
||||
cell: Dict[str, Any],
|
||||
*,
|
||||
is_left: bool,
|
||||
by_pos: Dict[CellKey, Dict[str, Any]],
|
||||
by_sheet: Dict[str, List[Dict[str, Any]]],
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""返回父格 dataSource 元数据;none 为 None,default 为最近左/上数据源格。"""
|
||||
custom = cell.get("custom") or {}
|
||||
ptype = (
|
||||
custom.get("leftParentCellType") if is_left else custom.get("topParentCellType")
|
||||
) or "default"
|
||||
sheet, row, col = cell_key(cell)
|
||||
|
||||
if ptype == "none":
|
||||
return None
|
||||
|
||||
if ptype == "custom":
|
||||
if is_left:
|
||||
letters = custom.get("leftParentCellCustomRowName") or "A"
|
||||
row_num = custom.get("leftParentCellCustomColName")
|
||||
else:
|
||||
letters = custom.get("topParentCellCustomRowName") or "A"
|
||||
row_num = custom.get("topParentCellCustomColName")
|
||||
if row_num is None:
|
||||
return None
|
||||
try:
|
||||
parent_row = int(row_num) - 1
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
parent_col = col_letter_to_index(str(letters))
|
||||
return by_pos.get((sheet, parent_row, parent_col))
|
||||
|
||||
# default:同行向左 / 同列向上找最近 dataSource
|
||||
candidates = by_sheet.get(sheet) or []
|
||||
best: Optional[Dict[str, Any]] = None
|
||||
if is_left:
|
||||
for c in candidates:
|
||||
cr, cc = _int_coord(c.get("row")), _int_coord(c.get("col"))
|
||||
if cr == row and cc < col:
|
||||
if best is None or _int_coord(best.get("col")) < cc:
|
||||
best = c
|
||||
else:
|
||||
for c in candidates:
|
||||
cr, cc = _int_coord(c.get("row")), _int_coord(c.get("col"))
|
||||
if cc == col and cr < row:
|
||||
if best is None or _int_coord(best.get("row")) < cr:
|
||||
best = c
|
||||
return best
|
||||
|
||||
|
||||
def resolve_parents(
|
||||
cell: Dict[str, Any],
|
||||
by_pos: Dict[CellKey, Dict[str, Any]],
|
||||
by_sheet: Dict[str, List[Dict[str, Any]]],
|
||||
) -> Tuple[Optional[Dict[str, Any]], Optional[Dict[str, Any]]]:
|
||||
left = resolve_parent(cell, is_left=True, by_pos=by_pos, by_sheet=by_sheet)
|
||||
top = resolve_parent(cell, is_left=False, by_pos=by_pos, by_sheet=by_sheet)
|
||||
custom = cell.get("custom") or {}
|
||||
ds_name = _dataset_name(cell)
|
||||
# 汇总格且无扩展时,JNPF 在 none+none 时清除父格
|
||||
poly = str(custom.get("polymerizationType") or "1")
|
||||
if poly == "3" and custom.get("leftParentCellType") == "none" and custom.get("topParentCellType") == "none":
|
||||
return None, None
|
||||
if left and _dataset_name(left) != ds_name:
|
||||
left = None
|
||||
if top and _dataset_name(top) != ds_name:
|
||||
top = None
|
||||
return left, top
|
||||
|
||||
|
||||
def _dataset_name(cell: Dict[str, Any]) -> str:
|
||||
custom = cell.get("custom") or {}
|
||||
name = str(
|
||||
custom.get("dataSetName") or custom.get("dataSet") or custom.get("alias") or ""
|
||||
)
|
||||
if name:
|
||||
return name
|
||||
field = str(custom.get("field") or custom.get("bindField") or "")
|
||||
if "." in field:
|
||||
return field.split(".", 1)[0]
|
||||
return ""
|
||||
|
||||
|
||||
def filter_rows_by_parents(
|
||||
rows: List[Any],
|
||||
cell: Dict[str, Any],
|
||||
*,
|
||||
left_parent: Optional[Dict[str, Any]],
|
||||
top_parent: Optional[Dict[str, Any]],
|
||||
left_bind: Optional[List[Dict[str, Any]]],
|
||||
top_bind: Optional[List[Dict[str, Any]]],
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""对齐 JNPF DataUtils.fetchData:按父格 bindData 切片过滤。"""
|
||||
data = [r if isinstance(r, dict) else {} for r in rows]
|
||||
if not left_bind and not top_bind:
|
||||
return data
|
||||
|
||||
left_rows = left_bind
|
||||
top_rows = top_bind
|
||||
if left_rows is None and top_rows is not None:
|
||||
return top_rows
|
||||
if top_rows is None and left_rows is not None:
|
||||
return left_rows
|
||||
if left_rows is None or top_rows is None:
|
||||
return data
|
||||
|
||||
left_field = _bind_field(left_parent) if left_parent else ""
|
||||
top_field = _bind_field(top_parent) if top_parent else ""
|
||||
left_val = _first_field_value(left_rows, left_field) if left_rows else None
|
||||
top_val = _first_field_value(top_rows, top_field) if top_rows else None
|
||||
|
||||
from_top: List[Dict[str, Any]] = []
|
||||
for row in top_rows:
|
||||
if left_field and _get_nested_value(row, left_field) == left_val:
|
||||
from_top.append(row)
|
||||
from_left: List[Dict[str, Any]] = []
|
||||
for row in left_rows:
|
||||
if top_field and _get_nested_value(row, top_field) == top_val:
|
||||
from_left.append(row)
|
||||
return from_top if len(from_top) <= len(from_left) else from_left
|
||||
|
||||
|
||||
def _bind_field(parent: Optional[Dict[str, Any]]) -> str:
|
||||
if not parent:
|
||||
return ""
|
||||
custom = parent.get("custom") or {}
|
||||
field = custom.get("field") or custom.get("bindField") or ""
|
||||
if "." in field:
|
||||
return field.split(".", 1)[1]
|
||||
return field
|
||||
|
||||
|
||||
def resolve_field_path(field: str, dataset_alias: str = "") -> str:
|
||||
"""JNPF 字段常为 alias.prop,行数据一般为扁平 prop 或嵌套 prop。"""
|
||||
field = str(field or "")
|
||||
alias = str(dataset_alias or "")
|
||||
if alias and field.startswith(f"{alias}."):
|
||||
return field[len(alias) + 1 :]
|
||||
return field
|
||||
|
||||
|
||||
def _get_nested_value(row: Dict[str, Any], field: str) -> Any:
|
||||
if not field:
|
||||
return None
|
||||
if field in row:
|
||||
return row[field]
|
||||
parts = field.split(".")
|
||||
cur: Any = row
|
||||
for p in parts:
|
||||
if isinstance(cur, dict) and p in cur:
|
||||
cur = cur[p]
|
||||
else:
|
||||
return None
|
||||
return cur
|
||||
|
||||
|
||||
def _first_field_value(rows: List[Dict[str, Any]], field: str) -> Any:
|
||||
if not rows or not field:
|
||||
return None
|
||||
return _get_nested_value(rows[0], field)
|
||||
Reference in New Issue
Block a user