- 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>
10 KiB
10 KiB
PRD — Deployment + Sensors Integration (v2)
Цель
Обеспечить полную интеграцию: при создании beneficiary автоматически создаётся deployment на Legacy API, к которому затем привязываются BLE сенсоры. Использовать credentials robster/rob2 (больше прав). Добавить Dropdown для выбора комнаты сенсора.
Текущее состояние (уже работает!)
✅ Что УЖЕ реализовано:
- Deployment создаётся автоматически при создании beneficiary (
beneficiaries.js:445-501) - Legacy API integration полностью работает (
legacyAPI.js) - BLE сенсоры подключаются и настраиваются (
PRD-SENSORS.md— все задачи выполнены) - Device Settings есть поля location/description (TextInput)
❌ Что НЕ работает:
- Credentials
anandkимеют ограниченные права → нуженrobster/rob2 - Location вводится текстом → нужен Dropdown с комнатами
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
- Picker на Android vs iOS — выглядит по-разному, возможно нужен ActionSheet
- Старые данные — если location уже сохранён как текст "kitchen", не найдётся в LOCATION_NAMES
- Нет интернета — Legacy API недоступен, нужен graceful error
Порядок выполнения
- ✅ Backend: обновить credentials (5 мин)
- 🔨 Frontend: добавить константы ROOM_LOCATIONS (5 мин)
- 🔨 Frontend: исправить updateDeviceMetadata (10 мин)
- 🔨 Frontend: установить Picker пакет (2 мин)
- 🔨 Frontend: заменить TextInput на Picker (20 мин)
- 🔨 Frontend: конвертация code↔name (15 мин)
- ✅ Тестирование E2E (15 мин)
Общее время: ~1.5 часа
Минимальный проходной балл: 8/10