Тест-режим: отправлять на /check/ авто-кроп лица (как в превью/публикации), а не оригинал

- make_face_crop_bytes(): тот же квадратный кроп по центру лица, что
  превью и публикуемый кадр (полное разрешение, JPEG q90)
- handlers: кнопка 1 шлёт на проверку кроп, не оригинал
- правило «голова >= 30%» теперь меряется по публикуемому результату
This commit is contained in:
2026-08-23 15:06:04 +07:00
parent b5cd3df5dd
commit 612564389b
2 changed files with 46 additions and 5 deletions
+9 -2
View File
@@ -14,7 +14,7 @@ from trueconf.types import FSInputFile
import config.config as config
from utils import states
from utils.menu import MENU_TEXT
from photo_bot.photo_processor import prepare_ad_photo, get_crop_coordinates, check_photo_api
from photo_bot.photo_processor import prepare_ad_photo, get_crop_coordinates, check_photo_api, make_face_crop_bytes
from utils.texts import (
UNKNOWN_MAIN_CMD_TEXT,
EMOJI_DIGITS,
@@ -339,9 +339,16 @@ async def photo_module_handler(msg: Message):
try:
with open(original_path, 'rb') as f:
raw_photo = f.read()
# На проверку уходит АВТО-КРОП ЛИЦА (тот же, что в превью
# и что публикуется на сайте) — правила «голова ≥ 30%» и
# прочее имеют смысл для публикуемого результата, а не
# для произвольно снятого оригинала.
crop_bytes = await asyncio.to_thread(
make_face_crop_bytes, raw_photo, photo_data.get('coords')
)
# Dry-run: полный пайплайн проверки на САЙТЕ (CV + гримасы + ИИ),
# фото в БД/AD/Orion не попадает
check_res = await asyncio.to_thread(check_photo_api, raw_photo)
check_res = await asyncio.to_thread(check_photo_api, crop_bytes)
except Exception as e:
logger.error(f"🔥 [TEST MODE] Ошибка проверки фото: {e}")
check_res = {"status": "error", "reason": f"Ошибка бота: {e}"}
+37 -3
View File
@@ -189,13 +189,47 @@ def prepare_ad_photo(input_path: str, user_id: str, output_path: str = None, tar
return {"success": False, "error": str(e)}
def make_face_crop_bytes(image_bytes: bytes, coords: tuple) -> bytes:
"""
Вырезает квадратный кадр по центру лица (тот же регион, что превью
бота и публикуемый на сайте кроп) и возвращает JPEG-байты.
На проверку уходит именно этот кроп: правило «голова ≥ 30%» и все
дальнейшие проверки имеют смысл для публикуемого результата, а не
для произвольно снятого оригинала.
Returns:
JPEG-байты квадрата (полное разрешение, без ресайза).
"""
from PIL import Image
import io
img = Image.open(io.BytesIO(image_bytes))
if img.mode != 'RGB':
img = img.convert('RGB')
x1, y1, x2, y2 = coords
cropped = img.crop((x1, y1, x2, y2))
# Квадрат из центра (как превью)
side = min(cropped.size[0], cropped.size[1])
left = (cropped.size[0] - side) // 2
top = (cropped.size[1] - side) // 2
cropped = cropped.crop((left, top, left + side, top + side))
buf = io.BytesIO()
cropped.save(buf, format="JPEG", quality=90)
return buf.getvalue()
def check_photo_api(image_bytes: bytes, timeout: float = 300.0) -> dict:
"""
Dry-run проверка фото через API сайта (POST /api/photo-bot/check/).
Фото проходит полный пайплайн проверки на сайте (MediaPipe CV +
ARKit-формы + VLM ИИ), НО ни в БД, ни в AD, ни в Orion не попадает —
ответ возвращается боту для тест-под-режима.
image_bytes — АВТО-КРОП ЛИЦА от бота (make_face_crop_bytes), тот же
кадр, что и превью/публикация. Фото проходит полный пайплайн проверки
на сайте (MediaPipe CV + ARKit-формы + VLM ИИ), НО ни в БД, ни в AD,
ни в Orion не попадает — ответ возвращается боту для тест-под-режима.
Returns:
{"status": "ok", "verdict", "ai_reason", "cv_approved", "cv_reason",