mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
.venv deleted
ocr ready to test
This commit is contained in:
132
ocr-service/parser.py
Normal file
132
ocr-service/parser.py
Normal file
@@ -0,0 +1,132 @@
|
||||
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
|
||||
Reference in New Issue
Block a user