release: consolidate web simulation and real CV prototype

Add cleaned RealSense/OpenCV CV under cv/ with secret-free example
config, MQTT disabled by default, and canonical README coverage for
web + CV relationship without claiming production integration.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
root
2026-08-02 22:23:05 +02:00
parent 3777fecacf
commit 21e83be26e
26 changed files with 5050 additions and 178 deletions

86
cv/test_classify.py Executable file
View File

@@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""Юнит-тесты правил классификации (без камеры)."""
from __future__ import annotations
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from classify import Category, classify_from_dims
def test_suitable_box():
r = classify_from_dims(120, 80, 40, circle_ratio=0.5)
assert r.category == Category.SUITABLE
assert r.category.zone == "B"
def test_oversize_priority_over_circle():
# Большой цилиндр: габариты важнее круга
r = classify_from_dims(500, 100, 100, circle_ratio=0.95)
assert r.category == Category.OVERSIZE
assert r.category.zone == "C"
def test_too_small():
r = classify_from_dims(5, 5, 5, circle_ratio=0.2)
assert r.category == Category.OVERSIZE
def test_need_pack_cylinder():
r = classify_from_dims(100, 50, 50, circle_ratio=0.85)
assert r.category == Category.NEED_PACK
assert r.category.zone == "D"
def test_lying_bottle_must_be_D_not_B():
"""ТЗ: круг в ЛЮБОМ сечении. Лежачая бутылка сверху не круг, но сечение круглое."""
# имитация: top-view низкий, но итоговый circle_ratio после 3D-срезов высокий
r = classify_from_dims(220, 70, 70, circle_ratio=0.86)
assert r.category == Category.NEED_PACK
assert r.category.zone == "D"
def test_border_circle():
r = classify_from_dims(100, 50, 50, circle_ratio=0.8)
assert r.category == Category.NEED_PACK
r2 = classify_from_dims(100, 50, 50, circle_ratio=0.799)
assert r2.category == Category.SUITABLE
def test_border_dims_strict():
"""ТЗ: строго больше 10×10×10 и строго меньше 450×320×320."""
# ровно на максимуме → C
assert classify_from_dims(450, 320, 320, 0.3).category == Category.OVERSIZE
# ровно на минимуме → C
assert classify_from_dims(10, 10, 10, 0.3).category == Category.OVERSIZE
# чуть внутри границ → B
assert classify_from_dims(449, 319, 319, 0.3).category == Category.SUITABLE
assert classify_from_dims(11, 11, 11, 0.3).category == Category.SUITABLE
# 321 мм влезает вдоль оси 450 → B (сопоставление после сортировки)
assert classify_from_dims(100, 321, 100, 0.3).category == Category.SUITABLE
# а вот две стороны > 320 уже не влезают → C
assert classify_from_dims(400, 330, 100, 0.3).category == Category.OVERSIZE
def test_dims_order_independent():
"""Стороны сопоставляются после сортировки — порядок L/W/H не важен."""
assert classify_from_dims(319, 449, 318, 0.3).category == Category.SUITABLE
assert classify_from_dims(318, 319, 449, 0.3).category == Category.SUITABLE
# ровно 320 по строгому правилу «меньше» → C, в любом порядке
assert classify_from_dims(320, 449, 319, 0.3).category == Category.OVERSIZE
assert classify_from_dims(319, 320, 449, 0.3).category == Category.OVERSIZE
if __name__ == "__main__":
test_suitable_box()
test_oversize_priority_over_circle()
test_too_small()
test_need_pack_cylinder()
test_lying_bottle_must_be_D_not_B()
test_border_circle()
test_border_dims_strict()
test_dims_order_independent()
print("OK: all classification tests passed")