|
@@ -59,6 +59,7 @@ from post_clear_check.chain import (
|
|
|
DIMENSIONS,
|
|
DIMENSIONS,
|
|
|
DIMENSION_NAME,
|
|
DIMENSION_NAME,
|
|
|
GATE_MISSING_TABLE,
|
|
GATE_MISSING_TABLE,
|
|
|
|
|
+ CLEAR_MARK,
|
|
|
INDEX_MARK,
|
|
INDEX_MARK,
|
|
|
OWNER_BATCH,
|
|
OWNER_BATCH,
|
|
|
OWNER_FUND,
|
|
OWNER_FUND,
|
|
@@ -71,7 +72,7 @@ from post_clear_check.chain import (
|
|
|
owner_of,
|
|
owner_of,
|
|
|
unassign,
|
|
unassign,
|
|
|
)
|
|
)
|
|
|
-from post_clear_check.dimension_body import GATE_FORMULAS, dimension_of_gate
|
|
|
|
|
|
|
+from post_clear_check.dimension_body import GATE_FORMULAS, dimension_of_gate, format_topic
|
|
|
from post_clear_check.issue_common import is_population_bucket
|
|
from post_clear_check.issue_common import is_population_bucket
|
|
|
from post_clear_check.issue_reports import check_issue_reports
|
|
from post_clear_check.issue_reports import check_issue_reports
|
|
|
from post_clear_check.order_cell import CheckLine
|
|
from post_clear_check.order_cell import CheckLine
|
|
@@ -346,10 +347,11 @@ def assemble_cards(
|
|
|
buckets[dimension_of_gate(line.formula_text, line.check_text)].append(line)
|
|
buckets[dimension_of_gate(line.formula_text, line.check_text)].append(line)
|
|
|
if buckets[DIM_ORDER]:
|
|
if buckets[DIM_ORDER]:
|
|
|
buckets[DIM_ORDER] = [
|
|
buckets[DIM_ORDER] = [
|
|
|
- *_status_lines(buckets),
|
|
|
|
|
- *buckets[DIM_ORDER],
|
|
|
|
|
- *_index_lines(population, buckets),
|
|
|
|
|
- ]
|
|
|
|
|
|
|
+ *_status_lines(buckets),
|
|
|
|
|
+ *buckets[DIM_ORDER],
|
|
|
|
|
+ *_clear_topic_lines(buckets, population),
|
|
|
|
|
+ *_index_lines(population, buckets),
|
|
|
|
|
+ ]
|
|
|
drafts: list[CardDraft] = []
|
|
drafts: list[CardDraft] = []
|
|
|
for code in DIMENSIONS:
|
|
for code in DIMENSIONS:
|
|
|
rows = tuple(buckets[code])
|
|
rows = tuple(buckets[code])
|
|
@@ -385,7 +387,11 @@ def _table_dimension(table: str) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
def _annotation(line: CheckLine) -> bool:
|
|
def _annotation(line: CheckLine) -> bool:
|
|
|
- return line.formula_text.startswith(INDEX_MARK) or line.formula_text.startswith(STATUS_MARK)
|
|
|
|
|
|
|
+ return (
|
|
|
|
|
+ line.formula_text.startswith(INDEX_MARK)
|
|
|
|
|
+ or line.formula_text.startswith(STATUS_MARK)
|
|
|
|
|
+ or line.formula_text.startswith(CLEAR_MARK)
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
|
|
|
|
|
def _dimension_verdict(lines: Sequence[CheckLine]) -> str:
|
|
def _dimension_verdict(lines: Sequence[CheckLine]) -> str:
|
|
@@ -423,50 +429,134 @@ def _status_lines(buckets: Mapping[str, Sequence[CheckLine]]) -> list[CheckLine]
|
|
|
|
|
|
|
|
|
|
|
|
|
def _index_lines(population: set[str], buckets: Mapping[str, Sequence[CheckLine]]) -> list[CheckLine]:
|
|
def _index_lines(population: set[str], buckets: Mapping[str, Sequence[CheckLine]]) -> list[CheckLine]:
|
|
|
- """本轮主单在三张报表卡上的对错笔数。没有主单号的账户级报表不收进来。"""
|
|
|
|
|
|
|
+ """关联报表按巡检稿展开。只收本轮主单挂得上的报表行。"""
|
|
|
|
|
|
|
|
found: list[CheckLine] = []
|
|
found: list[CheckLine] = []
|
|
|
- for code, label in ((DIM_ISSUE, "发行"), (DIM_SALES, "销售"), (DIM_TRS, "TRS")):
|
|
|
|
|
- grouped: dict[str, dict[str, str]] = {}
|
|
|
|
|
- for line in buckets[code]:
|
|
|
|
|
- if not line.report_code or line.master_order_no not in population:
|
|
|
|
|
- continue
|
|
|
|
|
- orders = grouped.setdefault(line.report_code, {})
|
|
|
|
|
- orders[line.master_order_no] = _worse_verdict(orders.get(line.master_order_no), line.verdict)
|
|
|
|
|
- for report_code in sorted(grouped):
|
|
|
|
|
- orders = grouped[report_code]
|
|
|
|
|
- counts = {VERDICT_CORRECT: 0, VERDICT_ERROR: 0, VERDICT_SUSPECT: 0}
|
|
|
|
|
- for verdict in orders.values():
|
|
|
|
|
- counts[verdict] += 1
|
|
|
|
|
- bits = [f"{label} {report_code}:本轮主单 {len(orders)} 笔"]
|
|
|
|
|
- for verdict, name in (
|
|
|
|
|
- (VERDICT_CORRECT, "正确"),
|
|
|
|
|
- (VERDICT_ERROR, "错误"),
|
|
|
|
|
- (VERDICT_SUSPECT, "可疑"),
|
|
|
|
|
- ):
|
|
|
|
|
- if counts[verdict]:
|
|
|
|
|
- bits.append(f"{name} {counts[verdict]}")
|
|
|
|
|
- text = ",".join(bits) + f"。明细在{DIMENSION_NAME[code]}。"
|
|
|
|
|
- found.append(
|
|
|
|
|
- CheckLine(
|
|
|
|
|
- formula_text=f"{INDEX_MARK}{report_code}",
|
|
|
|
|
- calc_text=text,
|
|
|
|
|
- calc_value=None,
|
|
|
|
|
- check_text=text,
|
|
|
|
|
- check_value=None,
|
|
|
|
|
- basis="",
|
|
|
|
|
- verdict=VERDICT_CORRECT,
|
|
|
|
|
- report_code=report_code,
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ for title, selected in _report_groups(buckets, population):
|
|
|
|
|
+ text = _clip(format_topic(title, selected), 1000)
|
|
|
|
|
+ found.append(
|
|
|
|
|
+ CheckLine(
|
|
|
|
|
+ formula_text=f"{INDEX_MARK}{title}",
|
|
|
|
|
+ calc_text=text,
|
|
|
|
|
+ calc_value=None,
|
|
|
|
|
+ check_text=title,
|
|
|
|
|
+ check_value=None,
|
|
|
|
|
+ basis="",
|
|
|
|
|
+ verdict=VERDICT_CORRECT,
|
|
|
|
|
+ # 标题如「发行 OTC007 / OTC008」超过 report_code 的 16 字,编号留空,标题在公式和正文里。
|
|
|
|
|
+ report_code="",
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ return found
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _clear_topic_lines(buckets: Mapping[str, Sequence[CheckLine]], population: set[str]) -> list[CheckLine]:
|
|
|
|
|
+ """清算对照补上交收、流水、汇率和留痕。已发布执行列仍由订单自己的公式写出。"""
|
|
|
|
|
+
|
|
|
|
|
+ found: list[CheckLine] = []
|
|
|
|
|
+ for title, selected in _clear_groups(buckets, population):
|
|
|
|
|
+ text = _clip(format_topic(title, selected), 1000)
|
|
|
|
|
+ found.append(
|
|
|
|
|
+ CheckLine(
|
|
|
|
|
+ formula_text=f"{CLEAR_MARK}{title}",
|
|
|
|
|
+ calc_text=text,
|
|
|
|
|
+ calc_value=None,
|
|
|
|
|
+ check_text=title,
|
|
|
|
|
+ check_value=None,
|
|
|
|
|
+ basis="",
|
|
|
|
|
+ verdict=VERDICT_CORRECT,
|
|
|
)
|
|
)
|
|
|
|
|
+ )
|
|
|
|
|
+ return found
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _report_groups(
|
|
|
|
|
+ buckets: Mapping[str, Sequence[CheckLine]], population: set[str]
|
|
|
|
|
+) -> list[tuple[str, list[CheckLine]]]:
|
|
|
|
|
+ """巡检稿里的关联报表。发行和销售的 OTC004、OTC007、OTC008 各自成段。"""
|
|
|
|
|
+
|
|
|
|
|
+ specs = (
|
|
|
|
|
+ ("发行 OTC003", DIM_ISSUE, ("OTC003",)),
|
|
|
|
|
+ ("销售 OTC003", DIM_SALES, ("OTC003",)),
|
|
|
|
|
+ ("发行 OTC004", DIM_ISSUE, ("OTC004",)),
|
|
|
|
|
+ ("销售 OTC004", DIM_SALES, ("OTC004",)),
|
|
|
|
|
+ ("发行 OTC007 / OTC008", DIM_ISSUE, ("OTC007", "OTC008")),
|
|
|
|
|
+ ("销售 OTC007 / OTC008", DIM_SALES, ("OTC007", "OTC008")),
|
|
|
|
|
+ )
|
|
|
|
|
+ return [(title, _pick(buckets[code], population, reports)) for title, code, reports in specs]
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _clear_groups(
|
|
|
|
|
+ buckets: Mapping[str, Sequence[CheckLine]], population: set[str]
|
|
|
|
|
+) -> list[tuple[str, list[CheckLine]]]:
|
|
|
|
|
+ """巡检稿清算对照里、不在订单公式上的四项。"""
|
|
|
|
|
+
|
|
|
|
|
+ issue = _touching(buckets[DIM_ISSUE], population)
|
|
|
|
|
+ sales = _touching(buckets[DIM_SALES], population)
|
|
|
|
|
+ trs = _touching(buckets[DIM_TRS], population)
|
|
|
|
|
+ return [
|
|
|
|
|
+ ("交收台账", [line for line in issue + sales if _ledger_line(line)]),
|
|
|
|
|
+ ("销售资金流水", [line for line in sales if _cash_line(line)]),
|
|
|
|
|
+ ("TRS 发生额与汇率口径", [line for line in trs if _fx_line(line)]),
|
|
|
|
|
+ ("清算核对留痕", [line for line in issue + trs if _trace_line(line)]),
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _pick(lines: Sequence[CheckLine], population: set[str], reports: tuple[str, ...]) -> list[CheckLine]:
|
|
|
|
|
+ return [line for line in _touching(lines, population) if line.report_code in reports]
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _touching(lines: Sequence[CheckLine], population: set[str]) -> list[CheckLine]:
|
|
|
|
|
+ """主单号对上,或正文里写了本轮主单号。账户级空主单不整表拉进来。"""
|
|
|
|
|
+
|
|
|
|
|
+ found: list[CheckLine] = []
|
|
|
|
|
+ for line in lines:
|
|
|
|
|
+ if line.master_order_no in population:
|
|
|
|
|
+ found.append(line)
|
|
|
|
|
+ continue
|
|
|
|
|
+ blob = f"{line.check_text}\n{line.calc_text}"
|
|
|
|
|
+ if any(order_no and _mentions(blob, order_no) for order_no in population):
|
|
|
|
|
+ found.append(line)
|
|
|
return found
|
|
return found
|
|
|
|
|
|
|
|
|
|
|
|
|
-def _worse_verdict(current: str | None, verdict: str) -> str:
|
|
|
|
|
- rank = {VERDICT_ERROR: 0, VERDICT_SUSPECT: 1, VERDICT_CORRECT: 2}
|
|
|
|
|
- if current is None or rank[verdict] < rank[current]:
|
|
|
|
|
- return verdict
|
|
|
|
|
- return current
|
|
|
|
|
|
|
+def _mentions(blob: str, order_no: str) -> bool:
|
|
|
|
|
+ """主单号要整段命中。ORD-A 不能算进 ORD-A1。"""
|
|
|
|
|
+
|
|
|
|
|
+ start = 0
|
|
|
|
|
+ while True:
|
|
|
|
|
+ index = blob.find(order_no, start)
|
|
|
|
|
+ if index < 0:
|
|
|
|
|
+ return False
|
|
|
|
|
+ end = index + len(order_no)
|
|
|
|
|
+ if end >= len(blob) or not blob[end].isalnum():
|
|
|
|
|
+ return True
|
|
|
|
|
+ start = end
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _ledger_line(line: CheckLine) -> bool:
|
|
|
|
|
+ text = line.formula_text
|
|
|
|
|
+ return text in {"交收推演", "T日交收结果", "销售交收"} or "交收金额" in text or text.startswith("交收日")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _cash_line(line: CheckLine) -> bool:
|
|
|
|
|
+ text = line.formula_text
|
|
|
|
|
+ return line.report_code == "OTC002" or text in {
|
|
|
|
|
+ "原始流水",
|
|
|
|
|
+ "T日流水",
|
|
|
|
|
+ "金额 = 同一流水编号的 fund_ledger 金额",
|
|
|
|
|
+ "金额 = 同一流水编号的流水金额,且流水业务类型、营业日期一致",
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _fx_line(line: CheckLine) -> bool:
|
|
|
|
|
+ text = line.formula_text
|
|
|
|
|
+ return any(key in text for key in ("汇率", "发生金额", "发生额"))
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _trace_line(line: CheckLine) -> bool:
|
|
|
|
|
+ blob = f"{line.formula_text}\n{line.check_text}\n{line.calc_text}"
|
|
|
|
|
+ return any(key in blob for key in ("留痕", "recon", "mismatch", "差异表"))
|
|
|
|
|
|
|
|
|
|
|
|
|
def assemble_batch_cards(lines: Sequence[ConclusionLine]) -> tuple[CardDraft, ...]:
|
|
def assemble_batch_cards(lines: Sequence[ConclusionLine]) -> tuple[CardDraft, ...]:
|
|
@@ -1276,6 +1366,7 @@ def _normalize_line(line: CheckLine) -> CheckLine:
|
|
|
calc_text=_clip(line.calc_text, 1000),
|
|
calc_text=_clip(line.calc_text, 1000),
|
|
|
check_text=_clip(line.check_text, 1000),
|
|
check_text=_clip(line.check_text, 1000),
|
|
|
basis=_clip(line.basis, 500),
|
|
basis=_clip(line.basis, 500),
|
|
|
|
|
+ report_code=_clip(line.report_code, 16),
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
|