import re from typing import List, Optional from pydantic import BaseModel from datetime import datetime class ParsedItem(BaseModel): raw_name: str amount: float price: float sum: float # Регулярка для поиска чисел с плавающей точкой: 123.00, 123,00, 10.5 FLOAT_RE = r'\d+[.,]\d{2}' def clean_text(text: str) -> str: """Удаляет лишние символы из названия товара.""" return re.sub(r'[^\w\s.,%/-]', '', text).strip() def parse_float(val: str) -> float: """Преобразует строку '123,45' или '123.45' в float.""" if not val: return 0.0 return float(val.replace(',', '.').replace(' ', '')) def extract_fiscal_data(text: str) -> Optional[str]: """ Ищет в тексте: - Дата и время (t): 19.12.25 12:16 -> 20251219T1216 - Сумма (s): ИТОГ: 770.00 - ФН (fn): 16 цифр, начинается на 73 - ФД (i): до 8 цифр (ищем после Д: или ФД:) - ФП (fp): 10 цифр (ищем после П: или ФП:) Возвращает строку формата: t=20251219T1216&s=770.00&fn=7384440800514469&i=11194&fp=3334166168&n=1 """ # 1. Поиск даты и времени # Ищем форматы DD.MM.YY HH:MM или DD.MM.YYYY HH:MM date_match = re.search(r'(\d{2})\.(\d{2})\.(\d{2,4})\s+(\d{2}):(\d{2})', text) t_param = "" if date_match: d, m, y, hh, mm = date_match.groups() if len(y) == 2: y = "20" + y t_param = f"{y}{m}{d}T{hh}{mm}" # 2. Поиск суммы (Итог) # Ищем слово ИТОГ и число после него sum_match = re.search(r'(?:ИТОГ|СУММА|СУММА:)\s*[:]*\s*(\d+[.,]\d{2})', text, re.IGNORECASE) s_param = "" if sum_match: s_param = sum_match.group(1).replace(',', '.') # 3. Поиск ФН (16 цифр, начинается с 73) fn_match = re.search(r'\b(73\d{14})\b', text) fn_param = fn_match.group(1) if fn_match else "" # 4. Поиск ФД (i) - ищем после маркеров Д: или ФД: # Берем набор цифр до 8 знаков fd_match = re.search(r'(?:ФД|Д)[:\s]+(\d{1,8})\b', text) i_param = fd_match.group(1) if fd_match else "" # 5. Поиск ФП (fp) - ищем после маркеров П: или ФП: # Строго 10 цифр fp_match = re.search(r'(?:ФП|П)[:\s]+(\d{10})\b', text) fp_param = fp_match.group(1) if fp_match else "" # Валидация: для формирования запроса к API нам критически важны все параметры if all([t_param, s_param, fn_param, i_param, fp_param]): return f"t={t_param}&s={s_param}&fn={fn_param}&i={i_param}&fp={fp_param}&n=1" return None def parse_receipt_text(text: str) -> List[ParsedItem]: """ Парсит текст чека построчно (Regex-метод). """ lines = text.split('\n') items = [] name_buffer = [] for line in lines: line = line.strip() if not line: continue floats = re.findall(FLOAT_RE, line) is_price_line = False if len(floats) >= 2: is_price_line = True vals = [parse_float(f) for f in floats] price = 0.0 amount = 1.0 total = vals[-1] if len(vals) == 2: price = vals[0] amount = 1.0 if total > price and price > 0: calc_amount = total / price if abs(round(calc_amount) - calc_amount) < 0.05: amount = float(round(calc_amount)) elif len(vals) >= 3: v1, v2 = vals[-3], vals[-2] if abs(v1 * v2 - total) < 0.5: price, amount = v1, v2 elif abs(v2 * v1 - total) < 0.5: price, amount = v2, v1 else: price, amount = vals[-2], 1.0 full_name = " ".join(name_buffer).strip() if not full_name: text_without_floats = re.sub(FLOAT_RE, '', line) full_name = clean_text(text_without_floats) if len(full_name) > 2 and total > 0: items.append(ParsedItem( raw_name=full_name, amount=amount, price=price, sum=total )) name_buffer = [] else: upper_line = line.upper() if any(stop in upper_line for stop in ["ИТОГ", "СУММА", "ПРИХОД"]): name_buffer = [] continue name_buffer.append(line) return items