Реализация AI-ассистента для медицинских консультаций (Symptom Checker) в мобильном приложении
Symptom Checker — одна из самых ответственных AI-задач в мобильной разработке. Ошибочный вывод «вероятно, это просто усталость» при симптомах, требующих немедленной помощи, может стоить жизни. Это не преувеличение — это требование к архитектуре.
Юридические и медицинские ограничения
Прежде чем писать код — базовые ограничения, которые диктуют архитектуру:
- Приложение не ставит диагнозы. Выводит «вероятные причины», «рекомендуется консультация врача»
- Симптомы высокого риска (боль в груди, затруднённое дыхание, признаки инсульта) — автоматический редирект на звонок 112, без AI-анализа
- Все данные о симптомах — медицинские данные, требуют шифрования уровня HealthKit / HIPAA
- Disclaimer должен быть виден до первого запроса, не мелким шрифтом
На iOS — HealthKit для хранения анамнеза с HKHealthStore authorization. На Android — Health Connect с HealthPermission.READ_STEPS и аналогичными разрешениями.
Детерминированная проверка критических симптомов
До любого LLM-вызова — жёсткий фильтр по критическим симптомам:
struct CriticalSymptomChecker {
// Симптомы, требующие немедленной скорой помощи
static let emergencySymptoms = [
"chest pain", "боль в груди",
"difficulty breathing", "затруднение дыхания", "не могу дышать",
"sudden severe headache", "внезапная сильная головная боль",
"face drooping", "numbness arm", "speech difficulty", // FAST-тест инсульта
"loss of consciousness", "потеря сознания",
"severe bleeding", "сильное кровотечение",
"choking", "подавился"
]
static func requiresEmergency(_ symptomText: String) -> Bool {
let lowercased = symptomText.lowercased()
return emergencySymptoms.contains { lowercased.contains($0) }
}
}
// Проверяем ДО отправки в LLM
func processSymptoms(_ userInput: String) async {
if CriticalSymptomChecker.requiresEmergency(userInput) {
showEmergencyAlert() // Кнопка 112, заблокировать продолжение
return
}
await analyzeWithAI(userInput)
}
Это не AI — это детерминированная логика. Нельзя доверять LLM решение о вызове скорой помощи.
Структурированный сбор симптомов
Свободный ввод текста — не лучший UX для медицинского контекста. Пользователь в стрессе пишет неточно. Лучше — чат с уточняющими вопросами по протоколу.
// Протокол сбора симптомов (адаптация OPQRST для мобильного)
enum SymptomQuestion: CaseIterable {
case onset // когда началось
case provocation // что усиливает/ослабляет
case quality // характер (острая, тупая, давящая)
case radiation // куда отдаёт
case severity // 1-10 шкала
case time // как давно, постоянно/периодически
var prompt: String {
switch self {
case .onset: return "Когда появился симптом? (недавно, несколько часов, несколько дней)"
case .severity: return "Оцените интенсивность по шкале от 1 до 10"
// ...
}
}
}
AI генерирует следующий уточняющий вопрос на основе предыдущих ответов — адаптивный опрос, а не фиксированный список.
Промпт для анализа симптомов
func buildSymptomAnalysisPrompt(
symptoms: SymptomCollection,
patientContext: PatientContext
) -> String {
return """
You are a medical triage assistant. Analyze symptoms and suggest possible conditions.
IMPORTANT: Always recommend consulting a qualified doctor. Never provide a definitive diagnosis.
If symptoms suggest any serious condition, clearly state urgency level.
Patient context:
- Age: \(patientContext.age)
- Known conditions: \(patientContext.knownConditions.joined(separator: ", "))
- Current medications: \(patientContext.medications.isEmpty ? "none" : patientContext.medications.joined(separator: ", "))
Symptoms:
- Main complaint: \(symptoms.mainComplaint)
- Duration: \(symptoms.duration)
- Severity (1-10): \(symptoms.severity)
- Character: \(symptoms.quality)
- Associated symptoms: \(symptoms.associated.joined(separator: ", "))
Return JSON:
{
"urgency": "emergency|urgent|routine",
"possible_conditions": [{"name": "", "likelihood": "high|medium|low", "brief_explanation": ""}],
"recommended_action": "call_112|er_today|see_doctor_soon|see_doctor_routine|home_care",
"home_care_advice": "",
"red_flags": ["symptoms to watch for that require immediate care"],
"disclaimer": "This is not a medical diagnosis..."
}
"""
}
urgency: emergency в ответе LLM — дополнительный триггер для показа кнопки 112, даже если детерминированный фильтр его не поймал.
Отображение результатов
Медицинская информация требует особой подачи в UI:
@Composable
fun SymptomCheckResult(result: SymptomAnalysis) {
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
// Уровень срочности — первым, крупно
UrgencyBanner(urgency = result.urgency)
Spacer(Modifier.height(16.dp))
// Рекомендованное действие — вторым
RecommendedActionCard(action = result.recommendedAction)
Spacer(Modifier.height(16.dp))
// Возможные причины — с оговорками
Text("Возможные причины", style = MaterialTheme.typography.titleMedium)
Text(
"Информация носит ознакомительный характер и не является диагнозом",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
result.possibleConditions.forEach { condition ->
ConditionCard(condition)
}
// Красные флаги — отдельным блоком
if (result.redFlags.isNotEmpty()) {
RedFlagsSection(flags = result.redFlags)
}
// Disclaimer — обязателен, но не мелким шрифтом
DisclaimerCard(text = result.disclaimer)
}
}
Интеграция с телемедициной
Если приложение связано с телемедицинским сервисом, кнопка «Проконсультироваться с врачом» появляется в результатах. Контекст из Symptom Checker (структурированные симптомы, анамнез) передаётся врачу автоматически — это экономит 10–15 минут опроса.
Ориентиры по срокам
Базовый Symptom Checker с чатом, детерминированным фильтром экстренных случаев и AI-анализом — 2–3 недели. Полная реализация с адаптивным опросом, HealthKit/Health Connect, профилем пациента, интеграцией телемедицины и соответствием требованиям GDPR/HIPAA — 2–3 месяца.







