mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
добавил гигачада и заворачивание в проверку чека, если данные для QR распознались
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import re
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
|
||||
class ParsedItem(BaseModel):
|
||||
raw_name: str
|
||||
@@ -13,20 +14,64 @@ 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 = []
|
||||
@@ -37,74 +82,38 @@ def parse_receipt_text(text: str) -> List[ParsedItem]:
|
||||
if not line:
|
||||
continue
|
||||
|
||||
# Ищем все числа, похожие на цену (с двумя знаками после запятой/точки)
|
||||
# Пример: 129.99
|
||||
floats = re.findall(FLOAT_RE, line)
|
||||
|
||||
# Эвристика: строка считается "товарной позицией с ценой", если в ней есть минимум 2 числа
|
||||
# (обычно Цена и Сумма, или Кол-во и Сумма)
|
||||
# ИЛИ одно число, если это итоговая сумма, но мы ищем товары.
|
||||
|
||||
is_price_line = False
|
||||
|
||||
if len(floats) >= 2:
|
||||
is_price_line = True
|
||||
|
||||
vals = [parse_float(f) for f in floats]
|
||||
|
||||
# Попытка определить структуру: Цена x Кол-во = Сумма
|
||||
price = 0.0
|
||||
amount = 1.0
|
||||
total = 0.0
|
||||
|
||||
# Обычно последнее число - это сумма (итог по строке)
|
||||
total = vals[-1]
|
||||
|
||||
if len(vals) == 2:
|
||||
# Скорее всего: Цена ... Сумма (кол-во = 1)
|
||||
# Или: Кол-во ... Сумма (если цена не распозналась)
|
||||
# Предположим amount=1, тогда первое число - цена
|
||||
price = vals[0]
|
||||
amount = 1.0
|
||||
|
||||
# Проверка на адекватность: сумма обычно >= цены
|
||||
# Если total < price, возможно порядок перепутан или это скидка
|
||||
if total < price and total != 0:
|
||||
# Если total сильно меньше, возможно это не сумма
|
||||
pass
|
||||
elif total > price and price > 0:
|
||||
# Пытаемся вычислить кол-во
|
||||
if total > price and price > 0:
|
||||
calc_amount = total / price
|
||||
# Если результат близок к целому (например 1.999 -> 2), то ок
|
||||
if abs(round(calc_amount) - calc_amount) < 0.05:
|
||||
amount = float(round(calc_amount))
|
||||
|
||||
elif len(vals) >= 3:
|
||||
# Варианты: [Цена, Кол-во, Сумма] или [Кол-во, Цена, Сумма]
|
||||
# Проверяем математику: A * B = C
|
||||
v1, v2 = vals[-3], vals[-2]
|
||||
|
||||
if abs(v1 * v2 - total) < 0.5: # Допуск 0.5 руб
|
||||
price = v1
|
||||
amount = v2
|
||||
if abs(v1 * v2 - total) < 0.5:
|
||||
price, amount = v1, v2
|
||||
elif abs(v2 * v1 - total) < 0.5:
|
||||
price = v2
|
||||
amount = v1
|
||||
price, amount = v2, v1
|
||||
else:
|
||||
# Если математика не сходится, берем предпоследнее как цену
|
||||
price = vals[-2]
|
||||
amount = 1.0
|
||||
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,
|
||||
@@ -112,21 +121,12 @@ def parse_receipt_text(text: str) -> List[ParsedItem]:
|
||||
price=price,
|
||||
sum=total
|
||||
))
|
||||
|
||||
# Очищаем буфер, так как позиция закрыта
|
||||
name_buffer = []
|
||||
|
||||
else:
|
||||
# Строка не похожа на цену.
|
||||
# Проверяем на стоп-слова (конец чека)
|
||||
upper_line = line.upper()
|
||||
if "ИТОГ" in upper_line or "СУММА" in upper_line or "ПРИХОД" in upper_line:
|
||||
# Считаем, что товары закончились
|
||||
if any(stop in upper_line for stop in ["ИТОГ", "СУММА", "ПРИХОД"]):
|
||||
name_buffer = []
|
||||
# Можно здесь сделать break, если уверены, что ниже товаров нет
|
||||
continue
|
||||
|
||||
# Добавляем в буфер названия
|
||||
name_buffer.append(line)
|
||||
|
||||
return items
|
||||
Reference in New Issue
Block a user