Files
rmser/ocr-service/parser.py

132 lines
4.8 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.

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