- Загрузка/сканирование видео, извлечение кадров, анализ через SDK ФПГ - Понятный диагноз с сохранением в SQLite и share-ссылками - Экспорт PDF/DOCX, скачивание записанного видео - Камерное сканирование на мобильных (canvas + MediaRecorder) - Секреты вынесены в переменные окружения
367 lines
18 KiB
Python
367 lines
18 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Модуль интерпретации результатов SDK ФПГ (GigaDoc).
|
||
Переводит JSON-ответ в понятный человеку диагноз.
|
||
"""
|
||
|
||
|
||
def interpret_result(data: dict) -> dict:
|
||
"""
|
||
Преобразует JSON-ответ SDK в структурированный, понятный диагноз.
|
||
|
||
Возвращает dict с секциями:
|
||
- summary: краткое резюме
|
||
- metrics: список показателей с оценкой (норма/отклонение)
|
||
- warnings: список предупреждений
|
||
- advice: рекомендации
|
||
"""
|
||
if not data or not data.get("face_ok"):
|
||
return {
|
||
"summary": "Лицо не было распознано на видео. Убедитесь, что лицо хорошо освещено и находится в кадре.",
|
||
"metrics": [],
|
||
"warnings": ["Лицо не распознано — анализ невозможен"],
|
||
"advice": ["Переснимите видео: лицо анфас, хорошее освещение, без очков и головных уборов"],
|
||
}
|
||
|
||
metrics = []
|
||
warnings = []
|
||
advice = []
|
||
|
||
# ── Демография ─────────────────────────────────────────────
|
||
gender = "мужчина" if data.get("is_male") else "женщина"
|
||
age = data.get("age", 0)
|
||
ethnicity_map = {
|
||
"white": "европеоид",
|
||
"black": "негроид",
|
||
"asian": "монголоид",
|
||
"unknown": "не определён",
|
||
}
|
||
ethnicity = ethnicity_map.get(data.get("ethnicity", ""), data.get("ethnicity", "не определён"))
|
||
|
||
emotion_map = {
|
||
"happiness": "радость",
|
||
"sadness": "грусть",
|
||
"neutral": "спокойствие",
|
||
"anger": "злость",
|
||
"surprise": "удивление",
|
||
"fear": "страх",
|
||
"disgust": "отвращение",
|
||
"contempt": "презрение",
|
||
}
|
||
emotion = emotion_map.get(data.get("emotion", ""), data.get("emotion", "не определена"))
|
||
|
||
metrics.append({
|
||
"name": "Пол",
|
||
"value": "Мужской" if data.get("is_male") else "Женский",
|
||
"status": "info",
|
||
"comment": "",
|
||
})
|
||
metrics.append({
|
||
"name": "Возраст",
|
||
"value": f"{age} лет",
|
||
"status": "info",
|
||
"comment": "Оценка по лицу",
|
||
})
|
||
metrics.append({
|
||
"name": "Этнос",
|
||
"value": ethnicity.capitalize(),
|
||
"status": "info",
|
||
"comment": "",
|
||
})
|
||
metrics.append({
|
||
"name": "Эмоция",
|
||
"value": emotion.capitalize(),
|
||
"status": "info",
|
||
"comment": "Эмоция в момент съёмки",
|
||
})
|
||
|
||
# ── ИМТ ────────────────────────────────────────────────────
|
||
bmi = data.get("bmi", 0)
|
||
if bmi > 0:
|
||
if bmi < 18.5:
|
||
bmi_status, bmi_comment = "warning", "Недостаточный вес"
|
||
elif bmi < 25:
|
||
bmi_status, bmi_comment = "ok", "Нормальный вес"
|
||
elif bmi < 30:
|
||
bmi_status, bmi_comment = "warning", "Избыточный вес"
|
||
else:
|
||
bmi_status, bmi_comment = "danger", "Ожирение"
|
||
metrics.append({
|
||
"name": "Индекс массы тела (ИМТ)",
|
||
"value": f"{bmi:.1f}",
|
||
"status": bmi_status,
|
||
"comment": bmi_comment,
|
||
})
|
||
if bmi_status == "warning":
|
||
warnings.append(f"Избыточный вес (ИМТ {bmi:.1f}) — рекомендуется контроль питания и физическая активность")
|
||
advice.append("Снизить вес до нормы (ИМТ 18.5–24.9): сбалансированное питание, регулярные нагрузки")
|
||
elif bmi_status == "danger":
|
||
warnings.append(f"Ожирение (ИМТ {bmi:.1f}) — рекомендуется консультация врача")
|
||
advice.append("Обратиться к врачу для разработки программы снижения веса")
|
||
|
||
# ── Диабет ─────────────────────────────────────────────────
|
||
diabet = data.get("diabet", "unknown")
|
||
diabet_map = {
|
||
"small": ("ok", "Низкий риск диабета"),
|
||
"middle": ("warning", "Средний риск диабета"),
|
||
"big": ("danger", "Высокий риск диабета"),
|
||
"unknown": ("info", "Не определён"),
|
||
}
|
||
d_status, d_comment = diabet_map.get(diabet, ("info", "Не определён"))
|
||
metrics.append({
|
||
"name": "Риск диабета",
|
||
"value": {"small": "Низкий", "middle": "Средний", "big": "Высокий", "unknown": "Не определён"}.get(diabet, "Не определён"),
|
||
"status": d_status,
|
||
"comment": d_comment,
|
||
})
|
||
if d_status == "warning":
|
||
warnings.append("Средний риск диабета — рекомендуется проверить уровень сахара в крови")
|
||
advice.append("Сдать анализ на гликированный гемоглобин и сахар крови натощак")
|
||
elif d_status == "danger":
|
||
warnings.append("Высокий риск диабета — необходима консультация врача")
|
||
advice.append("Срочно обратиться к врачу и сдать анализы на сахар")
|
||
|
||
# ── Усталость ──────────────────────────────────────────────
|
||
fatigue = data.get("fatigue", "small")
|
||
fatigue_map = {
|
||
"small": ("ok", "Низкая усталость"),
|
||
"middle": ("warning", "Средняя усталость"),
|
||
"big": ("warning", "Высокая усталость"),
|
||
"over": ("danger", "Переутомление"),
|
||
}
|
||
f_status, f_comment = fatigue_map.get(fatigue, ("info", "Не определена"))
|
||
metrics.append({
|
||
"name": "Усталость",
|
||
"value": {"small": "Низкая", "middle": "Средняя", "big": "Высокая", "over": "Переутомление"}.get(fatigue, "Не определена"),
|
||
"status": f_status,
|
||
"comment": f_comment,
|
||
})
|
||
if f_status == "warning":
|
||
warnings.append("Повышенная усталость — рекомендуется полноценный отдых")
|
||
advice.append("Нормализовать сон (7–8 часов), снизить нагрузку")
|
||
elif f_status == "danger":
|
||
warnings.append("Переутомление — необходима консультация врача")
|
||
advice.append("Обратиться к врачу, исключить перегрузки")
|
||
|
||
# ── Пульс ──────────────────────────────────────────────────
|
||
hr = data.get("hr", 0)
|
||
if hr > 0:
|
||
if hr < 60:
|
||
hr_status, hr_comment = "warning", "Замедленный пульс (брадикардия)"
|
||
elif hr <= 100:
|
||
hr_status, hr_comment = "ok", "Нормальный пульс"
|
||
else:
|
||
hr_status, hr_comment = "warning", "Учащённый пульс (тахикардия)"
|
||
metrics.append({
|
||
"name": "Пульс (ЧСС)",
|
||
"value": f"{hr} уд/мин",
|
||
"status": hr_status,
|
||
"comment": hr_comment,
|
||
})
|
||
if hr_status == "warning":
|
||
warnings.append(f"Пульс {hr} уд/мин — вне нормы (60–100)")
|
||
advice.append("Проконсультироваться с врачом по поводу пульса")
|
||
|
||
# ── Давление ───────────────────────────────────────────────
|
||
upper_ap = data.get("upper_ap", 0)
|
||
lower_ap = data.get("lower_ap", 0)
|
||
if upper_ap > 0 and lower_ap > 0:
|
||
if upper_ap < 120 and lower_ap < 80:
|
||
ap_status, ap_comment = "ok", "Оптимальное давление"
|
||
elif upper_ap < 130 and lower_ap < 85:
|
||
ap_status, ap_comment = "ok", "Нормальное давление"
|
||
elif upper_ap < 140 or lower_ap < 90:
|
||
ap_status, ap_comment = "warning", "Повышенное давление"
|
||
else:
|
||
ap_status, ap_comment = "danger", "Высокое давление"
|
||
metrics.append({
|
||
"name": "Артериальное давление",
|
||
"value": f"{upper_ap}/{lower_ap} мм рт.ст.",
|
||
"status": ap_status,
|
||
"comment": ap_comment,
|
||
})
|
||
if ap_status == "warning":
|
||
warnings.append(f"Повышенное давление {upper_ap}/{lower_ap} — рекомендуется контроль")
|
||
advice.append("Контролировать давление, ограничить соль, кофеин, алкоголь")
|
||
elif ap_status == "danger":
|
||
warnings.append(f"Высокое давление {upper_ap}/{lower_ap} — необходима консультация врача")
|
||
advice.append("Обратиться к врачу для назначения лечения")
|
||
|
||
# ── Гликированный гемоглобин ───────────────────────────────
|
||
gh = data.get("glycated_hemoglobin", 0)
|
||
if gh > 0:
|
||
if gh < 5.7:
|
||
gh_status, gh_comment = "ok", "Норма (без диабета)"
|
||
elif gh < 6.5:
|
||
gh_status, gh_comment = "warning", "Преддиабет"
|
||
else:
|
||
gh_status, gh_comment = "danger", "Диабет"
|
||
metrics.append({
|
||
"name": "Гликированный гемоглобин",
|
||
"value": f"{gh:.1f}%",
|
||
"status": gh_status,
|
||
"comment": gh_comment,
|
||
})
|
||
if gh_status == "warning":
|
||
warnings.append(f"Гликированный гемоглобин {gh:.1f}% — преддиабет")
|
||
advice.append("Снизить потребление сахара, проверить сахар крови")
|
||
elif gh_status == "danger":
|
||
warnings.append(f"Гликированный гемоглобин {gh:.1f}% — возможен диабет")
|
||
advice.append("Срочно обратиться к врачу")
|
||
|
||
# ── Холестерин ─────────────────────────────────────────────
|
||
chol = data.get("cholesterol", 0)
|
||
if chol > 0:
|
||
if chol < 5.2:
|
||
chol_status, chol_comment = "ok", "Норма"
|
||
elif chol < 6.2:
|
||
chol_status, chol_comment = "warning", "Повышенный"
|
||
else:
|
||
chol_status, chol_comment = "danger", "Высокий"
|
||
metrics.append({
|
||
"name": "Общий холестерин",
|
||
"value": f"{chol:.1f} ммоль/л",
|
||
"status": chol_status,
|
||
"comment": chol_comment,
|
||
})
|
||
if chol_status == "warning":
|
||
warnings.append(f"Холестерин {chol:.1f} ммоль/л — выше нормы (до 5.2)")
|
||
advice.append("Ограничить жирную пищу, добавить овощи и физическую активность")
|
||
|
||
chol_ld = data.get("cholesterol_ld", 0)
|
||
if chol_ld > 0:
|
||
if chol_ld < 3.0:
|
||
ld_status, ld_comment = "ok", "Норма"
|
||
elif chol_ld < 4.1:
|
||
ld_status, ld_comment = "warning", "Повышенный"
|
||
else:
|
||
ld_status, ld_comment = "danger", "Высокий"
|
||
metrics.append({
|
||
"name": "Холестерин ЛПНП («плохой»)",
|
||
"value": f"{chol_ld:.1f} ммоль/л",
|
||
"status": ld_status,
|
||
"comment": ld_comment,
|
||
})
|
||
|
||
chol_hd = data.get("cholesterol_hd", 0)
|
||
if chol_hd > 0:
|
||
if chol_hd >= 1.0:
|
||
hd_status, hd_comment = "ok", "Норма (защищает сердце)"
|
||
else:
|
||
hd_status, hd_comment = "warning", "Пониженный"
|
||
metrics.append({
|
||
"name": "Холестерин ЛПВП («хороший»)",
|
||
"value": f"{chol_hd:.1f} ммоль/л",
|
||
"status": hd_status,
|
||
"comment": hd_comment,
|
||
})
|
||
|
||
trig = data.get("triglycerides", 0)
|
||
if trig > 0:
|
||
if trig < 1.7:
|
||
trig_status, trig_comment = "ok", "Норма"
|
||
elif trig < 2.3:
|
||
trig_status, trig_comment = "warning", "Повышенные"
|
||
else:
|
||
trig_status, trig_comment = "danger", "Высокие"
|
||
metrics.append({
|
||
"name": "Триглицериды",
|
||
"value": f"{trig:.1f} ммоль/л",
|
||
"status": trig_status,
|
||
"comment": trig_comment,
|
||
})
|
||
|
||
# ── Lp(a) ──────────────────────────────────────────────────
|
||
lpa50 = data.get("lpa50", False)
|
||
if lpa50:
|
||
warnings.append("Повышенный уровень липопротеина (а) — фактор риска сердечно-сосудистых заболеваний")
|
||
advice.append("Проконсультироваться с кардиологом")
|
||
|
||
# ── Дыхание ────────────────────────────────────────────────
|
||
resp = data.get("respiratory", 0)
|
||
if resp > 0:
|
||
if 12 <= resp <= 20:
|
||
resp_status, resp_comment = "ok", "Норма"
|
||
else:
|
||
resp_status, resp_comment = "warning", "Вне нормы"
|
||
metrics.append({
|
||
"name": "Частота дыхания",
|
||
"value": f"{resp:.1f} вдохов/мин",
|
||
"status": resp_status,
|
||
"comment": resp_comment,
|
||
})
|
||
|
||
# ── Стресс ─────────────────────────────────────────────────
|
||
stress = data.get("stress", 0)
|
||
if stress > 0:
|
||
if stress < 50:
|
||
stress_status, stress_comment = "ok", "Низкий уровень стресса"
|
||
elif stress < 100:
|
||
stress_status, stress_comment = "ok", "Умеренный уровень стресса"
|
||
elif stress < 150:
|
||
stress_status, stress_comment = "warning", "Повышенный уровень стресса"
|
||
else:
|
||
stress_status, stress_comment = "danger", "Высокий уровень стресса"
|
||
metrics.append({
|
||
"name": "Уровень стресса",
|
||
"value": f"{stress}",
|
||
"status": stress_status,
|
||
"comment": stress_comment,
|
||
})
|
||
if stress_status == "warning":
|
||
warnings.append("Повышенный уровень стресса")
|
||
advice.append("Практиковать релаксацию, дыхательные упражнения, прогулки")
|
||
elif stress_status == "danger":
|
||
warnings.append("Высокий уровень стресса — рекомендуется консультация")
|
||
advice.append("Обратиться к психологу или психотерапевту")
|
||
|
||
# ── Эластичность сосудов ───────────────────────────────────
|
||
rigidity = data.get("rigidity", 0)
|
||
if rigidity > 0:
|
||
if rigidity < 9:
|
||
rig_status, rig_comment = "ok", "Сосуды эластичные"
|
||
elif rigidity < 12:
|
||
rig_status, rig_comment = "warning", "Сниженная эластичность"
|
||
else:
|
||
rig_status, rig_comment = "danger", "Жёсткие сосуды"
|
||
metrics.append({
|
||
"name": "Эластичность сосудов",
|
||
"value": f"{rigidity:.1f} м/с",
|
||
"status": rig_status,
|
||
"comment": rig_comment,
|
||
})
|
||
|
||
# ── Моргания ───────────────────────────────────────────────
|
||
blinks = data.get("blinks", 0)
|
||
metrics.append({
|
||
"name": "Моргания",
|
||
"value": f"{blinks}",
|
||
"status": "info",
|
||
"comment": "За время сканирования",
|
||
})
|
||
|
||
# ── Сводка ─────────────────────────────────────────────────
|
||
summary = f"{gender.capitalize()}, {age} лет, {ethnicity}. Эмоция: {emotion}."
|
||
|
||
# Считаем проблемы
|
||
problems = [m for m in metrics if m["status"] in ("warning", "danger")]
|
||
if not problems:
|
||
summary += " Все основные показатели в норме."
|
||
else:
|
||
danger = [m for m in metrics if m["status"] == "danger"]
|
||
warning = [m for m in metrics if m["status"] == "warning"]
|
||
if danger:
|
||
summary += f" Обнаружены серьёзные отклонения: {', '.join(m['name'] for m in danger)}."
|
||
if warning:
|
||
summary += f" Требуют внимания: {', '.join(m['name'] for m in warning)}."
|
||
|
||
if not advice:
|
||
advice.append("Продолжайте вести здоровый образ жизни и проходите профилактические осмотры")
|
||
|
||
return {
|
||
"summary": summary,
|
||
"metrics": metrics,
|
||
"warnings": warnings,
|
||
"advice": advice,
|
||
}
|