mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
132 lines
6.0 KiB
Python
132 lines
6.0 KiB
Python
import re
|
||
from typing import List
|
||
from pydantic import BaseModel
|
||
|
||
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 parse_receipt_text(text: str) -> List[ParsedItem]:
|
||
"""
|
||
Парсит текст чека построчно.
|
||
Логика: накапливаем строки названия, пока не встретим строку с математикой (цена/сумма).
|
||
"""
|
||
lines = text.split('\n')
|
||
items = []
|
||
name_buffer = []
|
||
|
||
for line in lines:
|
||
line = line.strip()
|
||
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:
|
||
# Пытаемся вычислить кол-во
|
||
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
|
||
elif abs(v2 * v1 - total) < 0.5:
|
||
price = v2
|
||
amount = v1
|
||
else:
|
||
# Если математика не сходится, берем предпоследнее как цену
|
||
price = vals[-2]
|
||
amount = 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 "ИТОГ" in upper_line or "СУММА" in upper_line or "ПРИХОД" in upper_line:
|
||
# Считаем, что товары закончились
|
||
name_buffer = []
|
||
# Можно здесь сделать break, если уверены, что ниже товаров нет
|
||
continue
|
||
|
||
# Добавляем в буфер названия
|
||
name_buffer.append(line)
|
||
|
||
return items |