arm-ka-docs/scripts/osv_inspect.py
2026-10-02 23:02:44 +04:00

111 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Быстрый осмотр ОСВ-файлов из 1С: шапка, схема группировок, колонки сумм, уровни строк.
Назначение — исследовательский инструмент для Среза 2 (парсер ОСВ), а не парсер.
Запуск: python scripts/osv_inspect.py tests/fixtures/osv --rows 15
Зависимости: openpyxl, xlrd (pip install openpyxl xlrd).
"""
from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
import openpyxl
import xlrd
TITLE_RE = re.compile(r"по счету\s+([\d.]+)\s*(?:за\s+(.+?))?\s*$")
def load_rows(path: Path):
"""Возвращает список (level, [cells...]) и имя листа. xlsx — openpyxl (не read_only!), xls — xlrd."""
if path.suffix.lower() == ".xlsx":
wb = openpyxl.load_workbook(path, data_only=True)
ws = wb.worksheets[0]
out = []
for r in range(1, ws.max_row + 1):
lvl = ws.row_dimensions[r].outline_level or 0
out.append((lvl, [ws.cell(r, c).value for c in range(1, ws.max_column + 1)]))
return ws.title, out
wb = xlrd.open_workbook(path, formatting_info=True)
ws = wb.sheets()[0]
out = []
for r in range(ws.nrows):
lvl = ws.rowinfo_map[r].outline_level if r in ws.rowinfo_map else 0
out.append((lvl, [c.value if c.value != "" else None for c in ws.row(r)]))
return ws.name, out
def col_letter(i: int) -> str:
return openpyxl.utils.get_column_letter(i + 1)
def inspect(path: Path, max_rows: int) -> None:
sheet, rows = load_rows(path)
print("=" * 110)
print(path.name, f"[{sheet}]", f"rows={len(rows)}")
first = [str(r[1][0]).strip() for r in rows[:3] if r[1] and r[1][0]]
org = first[0] if first else "?"
title = next((str(c) for _, cells in rows[:5] for c in cells if c and "по счету" in str(c)), "")
m = TITLE_RE.search(title)
acc, per = (m.group(1), m.group(2)) if m else ("?", "?")
data_line = next((str(c) for _, cells in rows[:8] for c in cells if c and "Выводимые данные" in str(c)), "")
print(f" org={org!r} account={acc} period={per!r} {data_line}")
# заголовок таблицы: строка с «Счет» в первой колонке
hdr_idx = next((i for i, (_, cells) in enumerate(rows) if cells and str(cells[0]).strip() in ("Счет", "Субконто")), None)
if hdr_idx is None:
print(" !! заголовок таблицы не найден")
return
groups = []
inn_col = None
sums = {}
i = hdr_idx
while i < len(rows):
_, cells = rows[i]
c0 = str(cells[0]).strip() if cells and cells[0] else ""
if i > hdr_idx and (not c0 or re.match(r"^\d", c0)):
break
if c0:
groups.append(c0)
for j, v in enumerate(cells):
if v is None:
continue
s = str(v).strip()
if s == "ИНН":
inn_col = col_letter(j)
if s in ("Дебет", "Кредит"):
sums.setdefault(s, []).append(col_letter(j))
i += 1
data_start = i
print(f" group rows (шапка): {groups} inn_col={inn_col} Дебет={sums.get('Дебет')} Кредит={sums.get('Кредит')}")
print(f" table starts at row {data_start + 1}")
for lvl, cells in rows[data_start : data_start + max_rows]:
vals = [f"{col_letter(j)}={str(v)[:38]!r}" for j, v in enumerate(cells) if v is not None]
print(f" L{lvl} | " + " ".join(vals)[:170])
last = next(((lvl, cells) for lvl, cells in reversed(rows) if cells and cells[0]), None)
if last:
print(f" last: L{last[0]} {str(last[1][0])!r} {[v for v in last[1][1:] if v is not None][:6]}")
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("paths", nargs="+")
ap.add_argument("--rows", type=int, default=12)
a = ap.parse_args()
files: list[Path] = []
for p in a.paths:
p = Path(p)
files += sorted(p.rglob("*.xls*")) if p.is_dir() else [p]
for f in files:
try:
inspect(f, a.rows)
except Exception as e: # noqa: BLE001 — исследовательский скрипт
print(f"!! {f}: {e}")
return 0
if __name__ == "__main__":
sys.exit(main())