WellNuo/PRD-DEPLOYMENT.md
Sergei d453126c89 feat: Room location picker + robster credentials
- Backend: Update Legacy API credentials to robster/rob2
- Frontend: ROOM_LOCATIONS with icons and legacyCode mapping
- Device Settings: Modal picker for room selection
- api.ts: Bidirectional conversion (code ↔ name)
- Various UI/UX improvements across screens

PRD-DEPLOYMENT.md completed (Score: 9/10)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-24 15:22:40 -08:00

10 KiB
Raw Blame History

PRD — Deployment + Sensors Integration (v2)

Цель

Обеспечить полную интеграцию: при создании beneficiary автоматически создаётся deployment на Legacy API, к которому затем привязываются BLE сенсоры. Использовать credentials robster/rob2 (больше прав). Добавить Dropdown для выбора комнаты сенсора.

Текущее состояние (уже работает!)

Что УЖЕ реализовано:

  1. Deployment создаётся автоматически при создании beneficiary (beneficiaries.js:445-501)
  2. Legacy API integration полностью работает (legacyAPI.js)
  3. BLE сенсоры подключаются и настраиваются (PRD-SENSORS.md — все задачи выполнены)
  4. Device Settings есть поля location/description (TextInput)

Что НЕ работает:

  1. Credentials anandk имеют ограниченные права → нужен robster/rob2
  2. Location вводится текстом → нужен Dropdown с комнатами
  3. updateDeviceMetadata отправляет строку вместо числового кода

User Flow

Flow 1: Создание Beneficiary + Deployment (УЖЕ РАБОТАЕТ)

# Актор Действие Система Результат
1 User Заполняет форму "Add Loved One" Вводит имя, адрес
2 User Нажимает "Continue" POST /me/beneficiaries
3 Backend Создаёт beneficiary в PostgreSQL INSERT beneficiaries beneficiary_id
4 Backend Создаёт deployment в PostgreSQL INSERT beneficiary_deployments deployment.id
5 Backend Авторизуется на Legacy API legacyAPI.getLegacyToken() legacy_token
6 Backend Создаёт deployment на Legacy legacyAPI.createLegacyDeployment() legacy_deployment_id
7 Backend Сохраняет legacy_deployment_id UPDATE beneficiary_deployments Связь установлена
8 User Переходит к purchase/demo Deployment готов

Статус: Полностью реализовано в beneficiaries.js:419-501

Flow 2: Настройка устройства с Dropdown

# Актор Действие Система Результат
1 User Открывает Device Settings GET devices Текущие данные
2 User Видит Dropdown "Location" Показывает текущую комнату
3 User Выбирает комнату из списка "Bedroom", "Kitchen", etc.
4 User Вводит description (опционально) Свободный текст
5 User Нажимает "Save" POST device_form location=102
6 System Сохраняет на Legacy API Обновлено

Задачи

Backend (Простые)

  • 1. Обновить Legacy API credentials
    • Путь: backend/.env
    • Изменить:
      LEGACY_API_USERNAME=robster
      LEGACY_API_PASSWORD=rob2
      
    • Задеплоить: scp .env root@91.98.205.156:/var/www/wellnuo-api/.env
    • Перезапустить: ssh root@91.98.205.156 "pm2 restart wellnuo-api"

Frontend (Основная работа)

  • 2. Добавить константы ROOM_LOCATIONS в api.ts

    • Путь: services/api.ts
    • Добавить в начало файла:
      // Room location codes for Legacy API
      export const ROOM_LOCATIONS: Record<string, number> = {
        'Bedroom': 102,
        'Living Room': 103,
        'Kitchen': 104,
        'Bathroom': 105,
        'Hallway': 106,
        'Office': 107,
        'Garage': 108,
        'Dining Room': 109,
        'Basement': 110,
        'Other': 200
      };
      
      export const LOCATION_NAMES: Record<number, string> = Object.fromEntries(
        Object.entries(ROOM_LOCATIONS).map(([k, v]) => [v, k])
      );
      
  • 3. Исправить updateDeviceMetadata для location codes

    • Путь: services/api.ts (строка ~1783)
    • Изменить:
      // БЫЛО:
      formData.append('location', updates.location);
      
      // СТАЛО:
      if (updates.location !== undefined) {
        // Convert room name to location code
        const locationCode = ROOM_LOCATIONS[updates.location] || ROOM_LOCATIONS['Other'];
        formData.append('location', locationCode.toString());
      }
      
  • 4. Device Settings: заменить TextInput на Picker

    • Путь: app/(tabs)/beneficiaries/[id]/device-settings/[deviceId].tsx
    • Импорт: import { Picker } from '@react-native-picker/picker'
    • Или использовать: @react-native-community/picker / кастомный ActionSheet

    Заменить (строки 349-358):

    // БЫЛО:
    <TextInput
      style={styles.editableInput}
      value={location}
      onChangeText={setLocation}
      placeholder="e.g., Living Room, Kitchen..."
      placeholderTextColor={AppColors.textMuted}
    />
    
    // СТАЛО:
    <View style={styles.pickerContainer}>
      <Picker
        selectedValue={location}
        onValueChange={(value) => setLocation(value)}
        style={styles.picker}
      >
        <Picker.Item label="Select room..." value="" />
        <Picker.Item label="Bedroom" value="Bedroom" />
        <Picker.Item label="Living Room" value="Living Room" />
        <Picker.Item label="Kitchen" value="Kitchen" />
        <Picker.Item label="Bathroom" value="Bathroom" />
        <Picker.Item label="Hallway" value="Hallway" />
        <Picker.Item label="Office" value="Office" />
        <Picker.Item label="Garage" value="Garage" />
        <Picker.Item label="Dining Room" value="Dining Room" />
        <Picker.Item label="Basement" value="Basement" />
        <Picker.Item label="Other" value="Other" />
      </Picker>
    </View>
    
  • 5. Конвертировать location code → name при загрузке

    • Путь: app/(tabs)/beneficiaries/[id]/device-settings/[deviceId].tsx
    • В loadSensorInfo() добавить конвертацию:
      import { LOCATION_NAMES } from '@/services/api';
      
      // При получении sensor:
      const locationName = sensor.location
        ? (LOCATION_NAMES[parseInt(sensor.location)] || sensor.location)
        : '';
      setLocation(locationName);
      
  • 6. Добавить стили для Picker

    • Путь: тот же файл
    • Добавить в StyleSheet:
      pickerContainer: {
        backgroundColor: AppColors.background,
        borderRadius: BorderRadius.md,
        borderWidth: 1,
        borderColor: AppColors.border,
        overflow: 'hidden',
      },
      picker: {
        height: 50,
        color: AppColors.textPrimary,
      },
      
  • 7. Установить @react-native-picker/picker

    • Команда: npx expo install @react-native-picker/picker

Справочник: Location Codes (из legacyAPI.js)

Код Название Описание
102 Bedroom Спальня
103 Living Room Гостиная
104 Kitchen Кухня
105 Bathroom Ванная
106 Hallway Коридор
107 Office Кабинет
108 Garage Гараж
109 Dining Room Столовая
110 Basement Подвал
200 Other Другое

Вне scope

  • Синхронизация location с голосовым AI (Julia) — отдельная задача
  • WellNuo Lite интеграция — пока не трогаем
  • Редактирование deployment после создания — пока не нужно
  • Front Door (101) — нет в текущем mapping, не добавляем

Чеклист верификации

Backend

  • Credentials обновлены на robster/rob2 в .env
  • PM2 перезапущен
  • Тест: создать beneficiary → в логах видно "Created Legacy deployment: XXX"

Frontend

  • Device Settings показывает Picker/Dropdown вместо TextInput для location
  • Picker содержит все 10 комнат
  • При выборе комнаты — сохраняется location_code (число) на Legacy API
  • При загрузке — location_code конвертируется в название
  • Description остаётся TextInput
  • Сохранение работает без ошибок

End-to-End Flow

  • Создать beneficiary → deployment создан на Legacy API
  • Подключить BLE сенсор → привязан к deployment
  • Открыть Device Settings → видно Dropdown
  • Выбрать "Kitchen" → Save → проверить в Legacy API что location=104
  • Перезагрузить экран → показывает "Kitchen"

Риски и Edge Cases

  1. Picker на Android vs iOS — выглядит по-разному, возможно нужен ActionSheet
  2. Старые данные — если location уже сохранён как текст "kitchen", не найдётся в LOCATION_NAMES
  3. Нет интернета — Legacy API недоступен, нужен graceful error

Порядок выполнения

  1. Backend: обновить credentials (5 мин)
  2. 🔨 Frontend: добавить константы ROOM_LOCATIONS (5 мин)
  3. 🔨 Frontend: исправить updateDeviceMetadata (10 мин)
  4. 🔨 Frontend: установить Picker пакет (2 мин)
  5. 🔨 Frontend: заменить TextInput на Picker (20 мин)
  6. 🔨 Frontend: конвертация code↔name (15 мин)
  7. Тестирование E2E (15 мин)

Общее время: ~1.5 часа


Минимальный проходной балл: 8/10