Health Tracker - Система мониторинга медицинских анализов

Практическое руководство для тестирования полнофункционального веб-приложения

Описание системы

Health Tracker - полнофункциональная система для мониторинга медицинских анализов с возможностями OCR распознавания медицинских справок, ведения дневника здоровья и планирования медицинских мероприятий.

Технологический стек:

  • Frontend: React 18 + TypeScript + Tailwind CSS
  • Backend: Node.js + Express + TypeScript
  • База данных: PostgreSQL + Prisma ORM
  • OCR: Google Gemini API
  • Аутентификация: JWT токены
  • Деплой: Render.com

🌐 API Документация

Базовый URL и аутентификация

Базовый URL: https://health-tracker.ru/api

Аутентификация:
- Заголовок: Authorization: Bearer {JWT_TOKEN}
- Content-Type: application/json

Получение токена:

POST /auth/login
{
  "email": "user@example.com",
  "password": "password123"
}

Ответ:
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "id": "uuid",
    "email": "user@example.com",
    "firstName": "Иван"
  }
}

📊 Структура данных

Пользователь (User)

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "email": "ivan@example.com",
  "firstName": "Иван",
  "lastName": "Петров",
  "isEmailVerified": true,
  "createdAt": "2024-01-15T10:30:00Z"
}

Профиль (Profile)

{
  "id": "profile-uuid",
  "userId": "user-uuid",
  "name": "Основной профиль",
  "birthDate": "1990-05-15T00:00:00Z",
  "gender": "MALE",
  "isPrimary": true,
  "createdAt": "2024-01-15T10:30:00Z"
}

Анализ (Analysis)

{
  "id": "analysis-uuid",
  "profileId": "profile-uuid", 
  "date": "2024-06-15T00:00:00Z",
  "laboratory": "Инвитро",
  "documentUrl": "/uploads/scan123.jpg",
  "values": [
    {
      "metricId": "metric-uuid",
      "value": 4.5,
      "isAbnormal": false
    }
  ]
}

Метрика (Metric)

{
  "id": "metric-uuid",
  "name": "Гемоглобин",
  "unit": "г/л",
  "description": "Белок крови, переносящий кислород",
  "minValue": 120,
  "maxValue": 160,
  "isGlobal": true,
  "category": {
    "keyName": "hematology",
    "displayName": "Гематология"
  }
}

🔐 Аутентификация и пользователи

Регистрация нового пользователя

POST /auth/register
{
  "firstName": "Анна",
  "lastName": "Смирнова",
  "email": "anna@example.com",
  "password": "SecurePass123!",
  "confirmPassword": "SecurePass123!"
}

Ответ 201:
{
  "message": "Пользователь зарегистрирован. Проверьте email для подтверждения."
}

Подтверждение email

POST /auth/verify-email
{
  "token": "verification-token-from-email"
}

Ответ 200:
{
  "message": "Email успешно подтвержден"
}

Сброс пароля

POST /auth/forgot-password
{
  "email": "user@example.com"
}

POST /auth/reset-password
{
  "token": "reset-token-from-email",
  "password": "NewPassword123!",
  "confirmPassword": "NewPassword123!"
}

📋 Анализы и метрики

Получение таблицы анализов

GET /analyses/table-data
Authorization: Bearer {token}

Ответ 200:
{
  "rows": [
    {
      "metric": {
        "id": "uuid",
        "name": "Гемоглобин",
        "unit": "г/л",
        "refRange": "120-160"
      },
      "values": {
        "2024-06-15": {
          "value": 145,
          "isAbnormal": false
        },
        "2024-03-10": {
          "value": 110,
          "isAbnormal": true
        }
      }
    }
  ],
  "dates": ["2024-06-15", "2024-03-10"]
}

Добавление нового анализа

POST /analyses/data
Authorization: Bearer {token}
{
  "date": "2024-07-20",
  "laboratory": "Хеликс",
  "metrics": [
    {
      "metricId": "metric-uuid-1",
      "value": 4.8
    },
    {
      "metricId": "metric-uuid-2", 
      "value": 8.2
    }
  ]
}

Ответ 201:
{
  "id": "new-analysis-uuid",
  "date": "2024-07-20T00:00:00Z",
  "laboratory": "Хеликс",
  "values": [...]
}

Получение списка метрик

GET /metrics
Authorization: Bearer {token}

Ответ 200:
[
  {
    "id": "uuid",
    "name": "Глюкоза",
    "unit": "ммоль/л",
    "minValue": 3.3,
    "maxValue": 5.5,
    "category": {
      "displayName": "Биохимия"
    }
  }
]

Создание пользовательской метрики

POST /metrics
Authorization: Bearer {token}
{
  "name": "Витамин B12",
  "unit": "пмоль/л",
  "description": "Водорастворимый витамин",
  "categoryId": "biochemistry-uuid",
  "minValue": 118,
  "maxValue": 701
}

🖼️ OCR функциональность

Загрузка изображения для распознавания

POST /ocr/upload
Authorization: Bearer {token}
Content-Type: multipart/form-data

Form data:
- image: [файл изображения] (max 10MB)

Ответ 201:
{
  "importId": "ocr-import-uuid",
  "status": "PROCESSING",
  "message": "Изображение загружено и обрабатывается"
}

Проверка статуса обработки

GET /ocr/imports/{importId}
Authorization: Bearer {token}

Ответ 200:
{
  "id": "ocr-import-uuid",
  "status": "READY_FOR_REVIEW",
  "imageUrl": "/uploads/image123.jpg",
  "entries": [
    {
      "id": "entry-uuid",
      "metricName": "Гемоглобин",
      "value": 145,
      "unit": "г/л",
      "matchedId": "metric-uuid"
    }
  ]
}

Редактирование распознанных данных

PATCH /ocr/entries/{entryId}
Authorization: Bearer {token}
{
  "metricName": "Лейкоциты",
  "value": 6.2,
  "unit": "×10⁹/л"
}

Подтверждение и сохранение OCR данных

POST /ocr/imports/{importId}/confirm
Authorization: Bearer {token}
{
  "date": "2024-07-20",
  "laboratory": "СМ-Клиника",
  "entries": [
    {
      "entryId": "entry-uuid",
      "metricId": "metric-uuid",
      "value": 145
    }
  ]
}

Планирование событий

Получение запланированных событий

GET /planning/events?month=2024-07
Authorization: Bearer {token}

Ответ 200:
[
  {
    "id": "event-uuid",
    "title": "Общий анализ крови",
    "description": "Плановое обследование",
    "date": "2024-07-25T09:00:00Z",
    "status": "PLANNED",
    "healthGroup": {
      "name": "Базовые анализы",
      "metrics": ["Гемоглобин", "Лейкоциты"]
    }
  }
]

Создание нового события

POST /planning/events
Authorization: Bearer {token}
{
  "title": "УЗИ брюшной полости",
  "description": "Профилактическое обследование",
  "date": "2024-08-15T14:30:00Z",
  "reminderDate": "2024-08-14T09:00:00Z"
}

Отметка события как выполненного

PATCH /planning/events/{eventId}/complete
Authorization: Bearer {token}
{
  "completedDate": "2024-07-25T09:30:00Z",
  "analysisId": "analysis-uuid"
}

Дневник артериального давления

Добавление записи о давлении

POST /blood-pressure
Authorization: Bearer {token}
{
  "date": "2024-07-20",
  "morningSystolic": 120,
  "morningDiastolic": 80,
  "morningPulse": 72,
  "eveningSystolic": 125,
  "eveningDiastolic": 82,
  "eveningPulse": 68,
  "comment": "Чувствую себя хорошо"
}

Получение записей за период

GET /blood-pressure?startDate=2024-07-01&endDate=2024-07-31
Authorization: Bearer {token}

Ответ 200:
[
  {
    "id": "bp-record-uuid",
    "date": "2024-07-20T00:00:00Z",
    "morningSystolic": 120,
    "morningDiastolic": 80,
    "morningPulse": 72,
    "eveningSystolic": 125,
    "eveningDiastolic": 82,
    "eveningPulse": 68,
    "comment": "Чувствую себя хорошо"
  }
]

Публичные ссылки

Создание публичной ссылки

POST /public-links
Authorization: Bearer {token}
{
  "title": "Анализы за 2024 год",
  "selectedMetricIds": ["metric-uuid-1", "metric-uuid-2"],
  "selectedDates": ["2024-06-15", "2024-03-10"],
  "expiresAt": "2024-12-31T23:59:59Z"
}

Ответ 201:
{
  "id": "link-uuid",
  "token": "pub_1234567890abcdef",
  "title": "Анализы за 2024 год",
  "shareUrl": "https://health-tracker.ru/share/pub_1234567890abcdef"
}

Просмотр публичных данных (без аутентификации)

GET /share/{token}

Ответ 200:
{
  "title": "Анализы за 2024 год",
  "profileName": "Основной профиль",
  "data": {
    "rows": [...],
    "dates": [...]
  }
}

🧪 Frontend функциональность

Структура приложения

┌─────────────────────────────────────┐
│             Header                  │
│ - Логотип Health Tracker            │
│ - Навигационное меню                │
│ - Профиль пользователя              │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│           Main Content              │
│                                     │
│ ┌─────────┐ ┌─────────────────────┐ │
│ │Sidebar  │ │    Page Content     │ │
│ │- Анализы│ │                     │ │
│ │- Графики│ │   Динамический      │ │
│ │- План   │ │   контент страницы  │ │
│ │- OCR    │ │                     │ │
│ │- АД     │ │                     │ │
│ │- Профиль│ │                     │ │
│ └─────────┘ └─────────────────────┘ │
└─────────────────────────────────────┘

Основные маршруты:

  • / - Dashboard (главная страница)
  • /analyses - Таблица анализов
  • /planning - Календарь планирования
  • /ocr-upload - Загрузка OCR
  • /ocr-review/:importId - Редактирование OCR
  • /blood-pressure - Дневник АД
  • /profile - Настройки профиля
  • /share/:token - Публичный просмотр

SQL запросы для тестирования БД

Проверка целостности данных:

-- Анализы без профиля (ошибки foreign key)
SELECT a.id, a.profile_id 
FROM analyses a 
LEFT JOIN profiles p ON a.profile_id = p.id 
WHERE p.id IS NULL;

-- Результаты метрик без анализа
SELECT mr.id, mr.analysis_id
FROM metric_results mr
LEFT JOIN analyses a ON mr.analysis_id = a.id
WHERE a.id IS NULL;

-- Пользователи без первичного профиля
SELECT u.id, u.email
FROM users u
WHERE u.id NOT IN (
  SELECT DISTINCT user_id 
  FROM profiles 
  WHERE is_primary = true
);

Проверка бизнес-логики:

-- Аномальные значения метрик
SELECT mr.value, m.name, m.min_value, m.max_value
FROM metric_results mr
JOIN metrics m ON mr.metric_id = m.id
WHERE (mr.value < m.min_value OR mr.value > m.max_value)
  AND mr.is_abnormal = false;

-- OCR импорты в статусе PROCESSING дольше часа
SELECT id, created_at, status
FROM ocr_imports
WHERE status = 'PROCESSING' 
  AND created_at < NOW() - INTERVAL '1 hour';

-- Публичные ссылки с истекшим сроком
SELECT id, token, expires_at
FROM public_links
WHERE expires_at < NOW() AND is_active = true;