import logging import requests import re from typing import Optional, List from pyzbar.pyzbar import decode from PIL import Image import numpy as np from app.schemas.models import ParsedItem API_TOKEN = "36590.yqtiephCvvkYUKM2W" API_URL = "https://proverkacheka.com/api/v1/check/get" logger = logging.getLogger(__name__) 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 is_valid_fiscal_qr(qr_string: str) -> bool: """ Проверяет, соответствует ли строка формату фискального чека ФНС. Ожидаемый формат: t=...&s=...&fn=...&i=...&fp=...&n=... Мы проверяем наличие хотя бы 3-х ключевых параметров. """ if not qr_string: return False # Ключевые параметры, которые обязаны быть в строке чека required_keys = ["t=", "s=", "fn="] # Проверяем, что все ключевые параметры присутствуют # (порядок может отличаться, поэтому проверяем вхождение каждого) matches = [key in qr_string for key in required_keys] return all(matches) def detect_and_decode_qr(image: np.ndarray) -> Optional[str]: """ Ищет ВСЕ QR-коды на изображении и возвращает только тот, который похож на фискальный чек. """ try: pil_img = Image.fromarray(image) # Декодируем все коды на картинке decoded_objects = decode(pil_img) if not decoded_objects: logger.info("No QR codes detected on the image.") return None logger.info(f"Detected {len(decoded_objects)} code(s). Scanning for fiscal data...") for obj in decoded_objects: if obj.type == 'QRCODE': qr_data = obj.data.decode("utf-8") # Логируем найденное (для отладки, если вдруг формат хитрый) # Обрезаем длинные строки, чтобы не засорять лог log_preview = (qr_data[:75] + '..') if len(qr_data) > 75 else qr_data logger.info(f"Checking QR content: {log_preview}") if is_valid_fiscal_qr(qr_data): logger.info("Valid fiscal QR found!") return qr_data else: logger.info("QR skipped (not a fiscal receipt pattern).") logger.warning("QR codes were found, but none matched the fiscal receipt format.") return None except Exception as e: logger.error(f"Error during QR detection: {e}") return None def fetch_data_from_api(qr_raw: str) -> List[ParsedItem]: """ Отправляет данные QR-кода в API proverkacheka.com и парсит JSON-ответ. """ try: payload = { 'qrraw': qr_raw, 'token': API_TOKEN } logger.info("Sending request to Check API...") response = requests.post(API_URL, data=payload, timeout=10) if response.status_code != 200: logger.error(f"API Error: Status {response.status_code}") return [] data = response.json() if data.get('code') != 1: logger.warning(f"API returned non-success code: {data.get('code')}") return [] json_data = data.get('data', {}).get('json', {}) items_data = json_data.get('items', []) parsed_items = [] for item in items_data: price = float(item.get('price', 0)) / 100.0 total_sum = float(item.get('sum', 0)) / 100.0 quantity = float(item.get('quantity', 0)) name = item.get('name', 'Unknown') parsed_items.append(ParsedItem( raw_name=name, amount=quantity, price=price, sum=total_sum )) return parsed_items except Exception as e: logger.error(f"Error fetching/parsing API data: {e}") return []