import re from datetime import datetime from pathlib import Path from typing import List import pdfplumber from backend.parsers.base import BaseBankParser, ParsedTransaction # Я-банк использует EN DASH (U+2013) для минуса MINUS_CHARS = "\u2013-" def _normalize_amount(s: str) -> float: s = s.replace("\u00a0", " ").replace(" ", "").replace(",", ".") for c in MINUS_CHARS: s = s.replace(c, "-") if s.startswith("−"): s = "-" + s[1:] return float(s) def _parse_datetime_y(date_str: str, time_str: str = "") -> str: try: part = date_str.strip() + " " + (time_str or "00:00").strip() dt = datetime.strptime(part, "%d.%m.%Y %H:%M") return dt.strftime("%Y-%m-%dT%H:%M:%S") except ValueError: return date_str.strip().replace(".", "-")[:10] + "T00:00:00" class BankYParser(BaseBankParser): """Парсер выписок Яндекс Банка. Файлы Я-MM-YY.pdf.""" # Строка: описание ... DD.MM.YYYY DD.MM.YYYY [*XXXX] сумма ₽ сумма ₽ (минус может быть – U+2013, карта опциональна) ROW_RE = re.compile( r"^(.+?)\s+(\d{2}\.\d{2}\.\d{4})\s+(\d{2}\.\d{2}\.\d{4})\s+(?:\*(\d{4})\s+)?([+\u2013\-]?[\d\s,]+)\s*₽\s+([+\u2013\-]?[\d\s,]+)\s*₽\s*$", re.UNICODE, ) # Вторая строка может содержать время: "в 18:13" или "клиента в 21:35" TIME_RE = re.compile(r"^(?:.*\s+)?в\s+(\d{1,2}:\d{2})\s*$", re.UNICODE) PIGGY_MARKER = "Перевод между счетами одного клиента" def can_parse(self, filename: str) -> bool: name = Path(filename).name return name.startswith("Я-") and name.lower().endswith(".pdf") def parse(self, file_path: str) -> List[ParsedTransaction]: result: List[ParsedTransaction] = [] with pdfplumber.open(file_path) as pdf: lines: List[str] = [] for page in pdf.pages: text = page.extract_text() if text: lines.extend(text.split("\n")) i = 0 while i < len(lines): line = lines[i].strip() m = self.ROW_RE.match(line) if m: desc, date_op, date_proc, card_tail, amt1, amt2 = m.groups() card_tail = card_tail or "0000" amount = _normalize_amount(amt1) time_str = "" if i + 1 < len(lines): tm = self.TIME_RE.match(lines[i + 1].strip()) if tm: time_str = tm.group(1) desc = (desc + " " + lines[i + 1].strip()).strip() i += 1 result.append( ParsedTransaction( operation_date=_parse_datetime_y(date_op, time_str), debit_date=_parse_datetime_y(date_proc), amount=amount, amount_card_currency=_normalize_amount(amt2) if amt2 else None, description=desc.strip(), card_tail=card_tail, ) ) i += 1 return result