mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
132 lines
4.8 KiB
Python
132 lines
4.8 KiB
Python
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 |