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

- Добавлены логи в frontend (ClaimForm.tsx) для отслеживания unified_id и запросов к API
- Добавлены логи в backend (claims.py) для отладки SQL запросов
- Создан лог сессии с описанием проблемы и текущего состояния
- Проблема: API возвращает 0 черновиков, хотя в БД есть данные
This commit is contained in:
AI Assistant
2025-11-19 18:46:48 +03:00
parent cbab1c0fe6
commit 4c8fda5f55
57 changed files with 6574 additions and 304 deletions

View File

@@ -0,0 +1,431 @@
# Архитектура личного кабинета и возобновления заполнения формы
## Сценарии использования
### 1. Пользователь начинает заполнять форму
```
1. Вводит телефон → SMS верификация
2. Заполняет шаг 1 (полис)
3. Заполняет шаг 2 (визард)
4. Закрывает браузер (не завершил)
```
### 2. Пользователь возвращается через час/день/неделю
```
1. Заходит в личный кабинет
2. Видит список незавершенных заявок
3. Нажимает "Продолжить заполнение"
4. Форма должна быстро загрузиться с сохраненным состоянием
```
---
## Варианты архитектуры
### Вариант 1: Только PostgreSQL (простой)
**Как работает:**
```
Личный кабинет → Запрос в PostgreSQL → Получение данных → Отображение формы
```
**Плюсы:**
- ✅ Просто (один источник данных)
- ✅ Всегда актуальные данные
- ✅ Нет рассинхронизации
**Минусы:**
- ❌ Каждый раз запрос к PostgreSQL (1-10 мс)
- ❌ Нагрузка на БД при частых обращениях
**Когда использовать:**
- Небольшая нагрузка
- Простота важнее скорости
---
### Вариант 2: PostgreSQL + Redis кеш (рекомендую)
**Как работает:**
#### При сохранении данных:
```
1. Сохраняем в PostgreSQL (основное хранилище)
2. Сохраняем в Redis с TTL 24 часа (быстрый доступ)
```
#### При чтении данных:
```
1. Пробуем Redis (быстро, 0.1-1 мс)
2. Если нет в кеше → PostgreSQL (1-10 мс)
3. Загружаем в Redis на 24 часа (для следующих обращений)
```
**Плюсы:**
- ✅ Быстрый доступ (если есть в кеше)
- ✅ Fallback на PostgreSQL (если кеш пуст)
- ✅ Автоматическая очистка (TTL 24 часа)
- ✅ Lazy loading (загружаем в Redis при первом обращении)
**Минусы:**
- ⚠️ Нужно обновлять оба хранилища
- ⚠️ Риск устаревших данных (если забыли обновить кеш)
**Когда использовать:**
- Средняя/высокая нагрузка
- Важна скорость загрузки
- Пользователи часто возвращаются к формам
---
### Вариант 3: Только Redis с периодической синхронизацией
**Как работает:**
```
1. Основное хранилище - Redis (TTL 7 дней)
2. Периодически синхронизируем с PostgreSQL (раз в час/день)
3. При завершении формы - сохраняем в PostgreSQL
```
**Плюсы:**
- ✅ Очень быстрый доступ
- ✅ Автоматическая очистка старых сессий
**Минусы:**
- ❌ Риск потери данных (если Redis упал)
- ❌ Сложнее синхронизация
- ❌ Нет истории изменений
**Когда использовать:**
- Не рекомендуется (рискованно)
---
## Рекомендуемая архитектура (Вариант 2)
### Структура данных в Redis:
**Ключ:** `claim:CLM-2025-11-18-GEQ3KL`
**Значение:**
```json
{
"claim_id": "CLM-2025-11-18-GEQ3KL",
"contact_id": "398523",
"phone": "72352352352",
"status": "draft",
"current_step": 3,
"payload": {
"answers": {...},
"wizard_plan": {...},
"documents_meta": [...]
},
"created_at": "2025-11-18T20:43:47.033Z",
"updated_at": "2025-11-18T20:44:59.217Z"
}
```
**TTL:** 24 часа (86400 секунд)
---
### Алгоритм работы:
#### 1. При сохранении данных (claimsave):
```python
# В n8n workflow после SQL запроса
# 1. Сохраняем в PostgreSQL (уже сделано)
# 2. Сохраняем в Redis для быстрого доступа
redis_key = f"claim:{claim_id}"
redis_value = {
"claim_id": claim_id,
"contact_id": contact_id,
"phone": phone,
"status": "draft",
"current_step": current_step,
"payload": {
"answers": answers,
"wizard_plan": wizard_plan,
"documents_meta": documents_meta
},
"updated_at": datetime.now().isoformat()
}
await redis.set_json(
redis_key,
redis_value,
expire=86400 # 24 часа
)
```
#### 2. При чтении данных (личный кабинет):
```python
async def get_claim_for_resume(claim_id: str):
# 1. Пробуем Redis (быстро)
cached = await redis.get_json(f"claim:{claim_id}")
if cached:
logger.info(f"✅ Cache hit: {claim_id}")
return cached
# 2. Если нет в кеше - из PostgreSQL
logger.info(f"🔄 Cache miss: {claim_id}, loading from PostgreSQL")
claim = await db.get_claim_by_claim_id(claim_id)
if not claim:
return None
# 3. Формируем данные для Redis
redis_data = {
"claim_id": claim_id,
"contact_id": claim.payload.get("contact_id"),
"phone": claim.payload.get("phone"),
"status": claim.status_code,
"current_step": calculate_current_step(claim.payload),
"payload": {
"answers": claim.payload.get("answers", {}),
"wizard_plan": claim.payload.get("wizard_plan"),
"documents_meta": claim.payload.get("documents_meta", [])
},
"updated_at": claim.updated_at.isoformat()
}
# 4. Сохраняем в Redis на 24 часа (lazy loading)
await redis.set_json(f"claim:{claim_id}", redis_data, expire=86400)
return redis_data
```
#### 3. При обновлении данных:
```python
async def update_claim(claim_id: str, data: dict):
# 1. Обновляем PostgreSQL (основное хранилище)
await db.update_claim(claim_id, data)
# 2. Обновляем Redis кеш (если есть)
redis_key = f"claim:{claim_id}"
if await redis.exists(redis_key):
cached = await redis.get_json(redis_key)
if cached:
# Мерджим данные
cached.update(data)
cached["updated_at"] = datetime.now().isoformat()
await redis.set_json(redis_key, cached, expire=86400)
# Или просто удаляем кеш (при следующем чтении загрузится из PostgreSQL)
# await redis.delete(redis_key)
```
---
## Стратегии TTL
### Вариант A: Фиксированный TTL (24 часа)
**Плюсы:**
- ✅ Просто
- ✅ Автоматическая очистка старых данных
**Минусы:**
- ❌ Может истечь, даже если пользователь активен
### Вариант B: Продлеваем TTL при обращении
**Плюсы:**
- ✅ Активные заявки не истекают
- ✅ Старые заявки автоматически очищаются
**Минусы:**
- ⚠️ Нужно продлевать TTL при каждом чтении
**Реализация:**
```python
async def get_claim_with_refresh(claim_id: str):
cached = await redis.get_json(f"claim:{claim_id}")
if cached:
# Продлеваем TTL на 24 часа
await redis.expire(f"claim:{claim_id}", 86400)
return cached
# ... загрузка из PostgreSQL
```
### Вариант C: Длинный TTL для незавершенных заявок
**Плюсы:**
- ✅ Незавершенные заявки хранятся долго (7 дней)
- ✅ Завершенные заявки удаляются быстро (1 час)
**Реализация:**
```python
ttl = 604800 if status == "draft" else 3600 # 7 дней или 1 час
await redis.set_json(redis_key, data, expire=ttl)
```
---
## Личный кабинет: Список незавершенных заявок
### Как получить список:
**Вариант 1: Из PostgreSQL (рекомендую)**
```sql
SELECT
id,
payload->>'claim_id' as claim_id,
status_code,
payload->'answers' as answers,
updated_at
FROM clpr_claims
WHERE
payload->>'claim_id' LIKE 'CLM-%'
AND status_code IN ('draft', 'in_work')
AND channel = 'web_form'
AND updated_at > NOW() - INTERVAL '30 days'
ORDER BY updated_at DESC
LIMIT 20;
```
**Вариант 2: Из Redis (если нужно очень быстро)**
```python
# Ищем все ключи claim:CLM-*
keys = await redis.keys("claim:CLM-*")
claims = []
for key in keys:
claim = await redis.get_json(key)
if claim and claim.get("status") in ["draft", "in_work"]:
claims.append(claim)
```
**Проблема:** Redis не предназначен для поиска по паттернам (медленно)
**Решение:** Использовать индекс в PostgreSQL:
```sql
CREATE INDEX idx_clpr_claims_status_channel
ON clpr_claims(status_code, channel)
WHERE status_code IN ('draft', 'in_work');
```
---
## Рекомендуемая архитектура
### Для веб-формы:
1. **Основное хранилище:** PostgreSQL (`clpr_claims`)
- Полные данные
- История изменений
- Надежность
2. **Кеш:** Redis (`claim:CLM-...`)
- Быстрый доступ
- TTL 24 часа
- Lazy loading (загружаем при первом обращении)
3. **Алгоритм:**
```
Чтение:
1. Redis (если есть) → возврат
2. PostgreSQL → загрузка → сохранение в Redis → возврат
Запись:
1. PostgreSQL (основное)
2. Redis (обновление кеша или удаление)
```
4. **TTL стратегия:**
- Незавершенные заявки (`draft`, `in_work`): 7 дней
- Завершенные заявки (`submitted`): 1 час
- Продлеваем TTL при обращении
---
## Реализация в n8n
### После `claimsave`:
```javascript
// Code Node: Save to Redis
const claim = $json.claim;
const channel = $json.channel || 'web_form';
if (channel === 'web_form') {
// Определяем TTL в зависимости от статуса
const status = claim.status_code || 'draft';
const ttl = (status === 'draft' || status === 'in_work')
? 604800 // 7 дней для незавершенных
: 3600; // 1 час для завершенных
return {
redis_key: `claim:${claim.claim_id_str}`,
redis_value: JSON.stringify({
claim_id: claim.claim_id_str,
contact_id: claim.payload?.contact_id,
phone: claim.payload?.phone,
status: status,
current_step: calculateStep(claim.payload),
payload: {
answers: claim.payload?.answers,
wizard_plan: claim.payload?.wizard_plan,
documents_meta: claim.payload?.documents_meta
},
updated_at: new Date().toISOString()
}),
ttl: ttl
};
}
// Redis Node: SET with TTL
// Key: {{ $json.redis_key }}
// Value: {{ $json.redis_value }}
// TTL: {{ $json.ttl }}
```
### При чтении (личный кабинет):
```javascript
// Code Node: Get claim with cache
const claim_id = $json.claim_id;
// 1. Пробуем Redis
const cached = await redis.get(`claim:${claim_id}`);
if (cached) {
return JSON.parse(cached);
}
// 2. Если нет - из PostgreSQL
// (выполняется SQL запрос)
const claim = await postgres.get_claim(claim_id);
// 3. Сохраняем в Redis
if (claim) {
await redis.set(`claim:${claim_id}`, JSON.stringify(claim), 'EX', 86400);
}
return claim;
```
---
## Итог
### Рекомендуемая архитектура:
1. **PostgreSQL** - основное хранилище (источник истины)
2. **Redis** - кеш для быстрого доступа (TTL 24 часа, продлеваем при обращении)
3. **Lazy loading** - загружаем в Redis при первом обращении
4. **Инвалидация** - обновляем или удаляем кеш при изменении данных
### Преимущества:
- ✅ Быстрый доступ (если есть в кеше)
- ✅ Надежность (данные в PostgreSQL)
- ✅ Автоматическая очистка (TTL)
- ✅ Гибкость (можно отключить кеш, если не нужен)
### Когда использовать:
- ✅ Личный кабинет (список незавершенных заявок)
- ✅ Возобновление заполнения формы
- ✅ Быстрая загрузка состояния формы