Files
obsidian-vault/personal/projects/psychologist-app/task-obsidian-import-compat.md
T

148 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
created: '2026-05-25'
updated: '2026-05-25'
tags:
- project
- psychologist-app
- task
status: ready
---
# Задача: совместимость импорта с форматом Obsidian vault
**Цель:** пользователь должен выбрать папку `personal/psychology/` из Obsidian
и приложение подхватит профиль и все сессии без конвертации.
**Репо:** `~/Developer/reflect-skip`
**Затрагиваемые файлы:**
- `Sources/ReflectApp/Services/DocumentImportService.swift`
- `Sources/ReflectApp/Services/ProfileManager.swift`
---
## Контекст: что сейчас есть в vault
```
personal/psychology/
├── profile.md ← есть **Имя:** Алексей (добавлено), YAML frontmatter, rich markdown
├── insights.md ← есть, формат: ## Отцовство / ## ADHD с нарративными блоками
├── prompts.md ← не нужен для импорта
└── observations/ ← папка с еженедельными файлами
├── 2026-W20.md
└── 2026-W21.md
```
**Что ожидает приложение сейчас:**
```
UserProfile/
├── profile.md ← строка "**Имя:** Алексей" (теперь есть ✅)
├── insights.md ← есть ✅
└── sessions/ ← ДРУГОЕ имя папки ❌
└── YYYY-MM-DD.md ← ДРУГОЙ формат имени файла (еженедельные vs ежедневные)
```
---
## Изменения в DocumentImportService.swift
### 1. Поддержка папки `observations/` как алиаса `sessions/`
В методах `validate()`, `countSessions()`, `apply()` — проверять обе папки.
**`countSessions(in:)`** — текущий код:
```swift
let sessionsURL = url.appendingPathComponent("sessions")
```
Заменить на:
```swift
let sessionsURL = sessionsFolder(in: url)
```
Добавить приватный метод:
```swift
private func sessionsFolder(in url: URL) -> URL {
let sessions = url.appendingPathComponent("sessions")
let observations = url.appendingPathComponent("observations")
if FileManager.default.fileExists(atPath: observations.path) { return observations }
return sessions
}
```
### 2. Обновить `validate()` — убрать упоминание "sessions" из ошибки
Текущий error description (`noProfileFound`) упоминает "папку sessions" — заменить на нейтральное:
```
В выбранной папке нет файла profile.md.
Убедитесь что выбрана папка с данными профиля.
```
### 3. Обновить `apply()` — копировать observations/ → sessions/
В методе `apply()` строка:
```swift
let sessionsSrc = result.sourceURL.appendingPathComponent("sessions")
```
Заменить на:
```swift
let sessionsSrc = sessionsFolder(in: result.sourceURL)
```
Папка всегда копируется в `sessions/` в app Documents (это правильно — внутреннее имя не меняем).
---
## Изменения в ProfileManager.swift
### 4. Fallback для `sessionCount` при импортированном vault
Текущий код ищет regex `- Сессий проведено: N` — vault profile этого не имеет.
Результат: `sessionCount` возвращает 0, приложение запускает "первую сессию" даже при 2+ импортированных.
Изменить `sessionCount` — добавить fallback:
```swift
var sessionCount: Int {
// Сначала пробуем разобрать из текста профиля
if let content = try? String(contentsOf: profileFileURL, encoding: .utf8),
let range = content.range(of: #"- Сессий проведено: (\d+)"#, options: .regularExpression),
let numRange = String(content[range]).range(of: #"\d+"#, options: .regularExpression),
let count = Int(String(content[range])[numRange]) {
return count
}
// Fallback: считаем фактические файлы в sessions/
let files = (try? FileManager.default.contentsOfDirectory(
at: sessionsDirectoryURL,
includingPropertiesForKeys: nil,
options: .skipsHiddenFiles
)) ?? []
return files.filter { $0.pathExtension == "md" && $0.lastPathComponent != "index.md" }.count
}
```
---
## Что НЕ нужно менять
- Формат файлов сессий (`YYYY-MM-DD.md` vs `2026-W20.md`): BM25 поиск и `loadRecentSessions()` сортируют лексикографически — оба формата работают одинаково хорошо.
- Структура profile.md: `loadProfile()` отдаёт raw markdown в Claude — богатый vault-формат работает лучше чем простой app-формат.
- insights.md: `loadInsights()` тоже отдаёт raw markdown — vault-формат с секциями лучше flat-списка.
- `appendSession()` / `updateProfile()` / `addPattern()`: создают/дополняют нужные секции — при отсутствии секции код добавляет в конец файла. OK.
---
## Acceptance criteria
1. Пользователь выбирает папку `~/obsidian/personal/psychology/` на iOS
2. Validation проходит: имя "Алексей", счётчик сессий = 2
3. После import в `Documents/UserProfile/`:
- `profile.md` скопирован
- `sessions/` содержит файлы из `observations/`
- `insights.md` скопирован
4. `sessionCount` возвращает 2 (файлы `2026-W20.md`, `2026-W21.md`)
5. `generateIntro()` использует `hasHistory = true` и показывает контекст из W20/W21
---
## Объём работы
~30 строк кода. Затрагивает 2 файла, нет API изменений, нет UI изменений.