Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d15d4dc
chore: CLUADE.mdを更新
273Do Apr 22, 2026
8399443
feat: llmに渡す睡眠推定データのフォーマッタを実装
273Do Apr 22, 2026
5118a20
chore: error lensの設定を変更
273Do Apr 22, 2026
d740b74
feat: 短縮プロンプトを作成
273Do Apr 23, 2026
519b456
feat: 専門的なプロンプトを返すかどうかのフラグを追加
273Do Apr 23, 2026
86e8e5f
fix: docstringを修正
273Do Apr 23, 2026
f8d7d8f
fix: is_specializedのスキーマ説明を修正
273Do Apr 23, 2026
891cb82
feat: 専門的なプロンプトを作成
273Do Apr 24, 2026
25243c2
feat: 夜更かしかどうかによってフィードバック内容が変化するよう通常プロンプトを改善
273Do Apr 25, 2026
314eaae
feat: 夜更かしかどうかによってフィードバック内容が変化するよう専門的なプロンプトを改善
273Do Apr 25, 2026
c2c7590
refactor: アプリ用モデルのプレフィックスを環境変数に追加
273Do Apr 28, 2026
662fb15
feat: フィードバックに使用するモデルとパラメータを刷新
273Do Apr 28, 2026
d129032
fix: アプリ専用モデルのみ記録するよう修正
273Do Apr 28, 2026
fe0becb
refactor: via-emailのスキーマなどをリファクタ
273Do Apr 28, 2026
5166e46
chore: envファイルの例にて一部間違っていた箇所を修正
273Do Apr 28, 2026
02efac1
feat: メールのテンプレートに英語版を追加
273Do Apr 28, 2026
5ba8222
feat: メールのテンプレートがi18nに対応するよう実装
273Do Apr 28, 2026
a8009ca
refactor: テンプレートファイル名を修正
273Do Apr 28, 2026
a558837
feat: プロンプトに文章を優先する記述を追加
273Do Apr 29, 2026
c8664c7
feat: 使用するllmを選定して決定
273Do Apr 29, 2026
7c99147
feat: readmeのモデル記述を更新
273Do Apr 29, 2026
930d925
chore: readmeを更新
273Do Apr 29, 2026
2282c54
fix: 専門的なプロンプトの内容と軸を睡眠異常を主とするよう修正
273Do Apr 29, 2026
2a7d7fa
fix: 簡易的なプロンプトの内容と軸を睡眠異常を主とするよう修正
273Do Apr 29, 2026
0cf95fb
fix: readmeを修正
273Do Apr 29, 2026
ed400e5
feat: プロンプトに前提条件を追加
273Do Apr 29, 2026
89653f4
feat: llmフィードバックにてfbタイプを設定
273Do May 5, 2026
510f236
refactor: 軽微なリファクタ
273Do May 5, 2026
0319070
feat: プライバシー項目を追記
273Do May 5, 2026
afbf81a
fix: 表示文を修正
273Do May 5, 2026
4b47d43
feat: フロントの型を修正
273Do May 5, 2026
993fd47
fix: email経由の解析のリクエスト型を修正
273Do May 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ FRONTEND_ENDPOINT=http://localhost:${FRONTEND_PORT}
BACKEND_ENDPOINT=http://localhost:${BACKEND_PORT}/api/v1
OLLAMA_ENDPOINT=http://host.docker.internal:${OLLAMA_PORT}/v1

MODEL_NAME_PREFIX=thor-

CLOUDFLARE_TUNNEL_TOKEN=changeme
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"rgba(79,236,236, 0.2)"
],
// Error Lens
"errorLens.gutterIconsEnabled": true,
"errorLens.gutterIconsEnabled": false,
"errorLens.gutterIconSet": "emoji",
"errorLens.gutterEmoji": {
"error": "🔥",
Expand Down
202 changes: 92 additions & 110 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

Thor is an AI-driven full-stack monorepo project that extracts and analyzes step count data from Apple Healthcare exports.
Thor is an AI-driven full-stack monorepo that extracts step count data from Apple Healthcare XML exports to estimate and visualize sleep patterns, with LLM-generated feedback via Ollama.

## Architecture

Expand All @@ -22,136 +22,138 @@ Thor-Monorepo/

```
backend/src/
├── core/ # Common utilities (environment variables, etc.)
├── routers/ # FastAPI endpoint definitions
├── schemas/ # Pydantic schemas (request/response models)
└── usecases/ # Business logic layer
├── core/ # Shared utilities (Envs class, constants)
├── routers/ # FastAPI endpoint definitions (thin layer only)
├── schemas/ # Pydantic request/response models
└── usecases/ # All business logic
├── extract_steps/ # Apple XML parsing
├── estimate_sleep/# K-means clustering → feature extraction → sleep time estimation
├── llm_feedback/ # Ollama integration (via OpenAI-compatible SDK)
└── via_email/ # SMTP email delivery with Jinja2 templates
```

**Important design principles:**
**Design principles:**
- `routers/` must only contain endpoint definitions — business logic goes in `usecases/`
- `schemas/` holds all Pydantic models
- `core/` contains shared utilities: `load_env.py` (Envs class) and `constants.py`

- `routers/` should only contain endpoint definitions
- Business logic must be placed in `usecases/`
- Request/response type definitions go in `schemas/`
- `core/` contains shared utilities like environment variables and middleware
### Backend API Endpoints (`/api/v1`)

### Frontend Architecture
1. **POST `/extract-steps`** — Parses Apple Healthcare XML, returns step data in 15-min intervals. Body is raw `text/xml`. Query params: `months_of_extract` (int) OR `start_date_of_extract`+`end_date_of_extract` (ISO 8601), `include_recorded_sleep` (bool).
2. **POST `/estimate-sleep`** — Runs ML pipeline (K-means → feature extraction → late-night detection → bed/wake time estimation). Returns daily sleep estimates + available LLM models.
3. **POST `/feedback`** — Fetches LLM feedback from Ollama for a previously saved estimation (identified by `id`). Params: `id`, `llm` (model name), `lang` (`ja`/`en`).
4. **POST `/via-email`** — One-shot workflow: extract → estimate → feedback → send email. FormData with `xml_file` (multipart) + `req` (JSON string).

Built with React Router v7, a full-stack framework with server-side rendering support.
**Data persistence pattern**: `extract-steps` generates a hash-based `id` and saves data as JSON to `VAULT_DIR`. Subsequent endpoints use this `id` to retrieve the saved data, enabling stateless requests.

## Development Environment
### Frontend Architecture

### Prerequisites
Single-page application (all UI in `app/routes/home.tsx`) with three UI states:
1. **Input** — Survey form (3 questions about phone habits + bedtime), XML file upload, optional email
2. **Loading** — Progress through extract → estimate → feedback stages
3. **Results** — Recharts timeline/bar charts for bed/wake times; LLM feedback rendered as Markdown via react-markdown

- VSCode with Dev Container extension
- Docker running
- GitHub SSH connection configured
Key directories:
- `app/components/` — Reusable components (survey-form, file-upload, result-view, ai-feedback)
- `app/utils/` — SWR hooks (`use-extract-steps`, `use-estimate-sleep`, `use-ai-feedback`) + fetch wrappers in `api.ts`
- `app/core/` — `constants.ts` (API endpoint), `survey-schema.ts` (Zod validation)
- `app/locales/` — `translation-ja.json` and `translation-en.json` (i18next)

## Development Environment

### Container Services

- `thor-workspace`: Development workspace
- `thor-backend`: FastAPI server (port 8000)
- `thor-frontend`: React Router dev server (port 5173)
- `thor-ollama`: AI/LLM service (port 11434)
- `thor-workspace`: Dev Container (VSCode)
- `thor-backend`: FastAPI on port 8000
- `thor-frontend`: React Router dev server on port 5173
- `thor-ollama`: Ollama LLM service on port 11434

### Environment Variables

- Backend environment variables are managed in `backend/.env`
- Defined and accessed via the `Envs` class in `src/core/load_env.py`
**Backend** (`backend/.env`) — all defined in `src/core/load_env.py` as the `Envs` class:

## Development Commands

### Task Runner

This project uses Go-Task. View available commands with `task -l`.

### Start Development Servers
Required:
```
DATA_ID_SALT= # Salt for ID generation
MAIL_ADDRESS= # Gmail address
MAIL_USERNAME= # Same as MAIL_ADDRESS
MAIL_PASSWORD= # Gmail App Password (16 chars, not account password)
MAIL_FROM= # Sender address
```

```bash
# Start backend
task backend:dev
Optional (defaults shown):
```
IS_DEBUG=false
DATASTORE_DIR=./datastore
VAULT_DIR=./datastore/vault
OLLAMA_ENDPOINT=http://host.docker.internal:11434/v1/
FRONTEND_ENDPOINT=http://localhost:5173
API_V1_PREFIX=/api/v1
MAIL_PORT=587
MAIL_SERVER=smtp.gmail.com
```

# Start frontend
task frontend:dev
**Frontend** (`frontend/.env`):
```
VITE_BACKEND_ENDPOINT=http://localhost:8000
```

Backend runs at `http://localhost:8000`, Frontend at `http://localhost:5173`.
## Development Commands

### Format & Lint
This project uses Go-Task. View all commands with `task -l`.

```bash
# Format everything
task format
### Servers

# Lint everything
task lint
```bash
task backend:dev # FastAPI with auto-reload on port 8000
task frontend:dev # Vite HMR on port 5173
```

# Backend only
task backend:format
task backend:fix
### Format, Lint & Type Check

# Frontend only
task frontend:format
task frontend:lint
```bash
task format # Format backend (Ruff) + frontend (Prettier)
task lint # Lint and auto-fix backend (Ruff) + frontend (ESLint)
task type-check # TypeScript type check (frontend only)
task check # CI-equivalent: format-check + lint-check + type-check (no auto-fix)
```

### Tests

```bash
# Run all tests
task test

# Backend only
task backend:test

# Frontend only
task frontend:test
task test # Run all tests
task backend:test # pytest -v
task frontend:test # pnpm run test
```

### Type Checking

Run a single backend test file or function:
```bash
# Type check everything
task type-check

# Frontend only
task frontend:type-check
task backend -- uv run pytest path/to/test_file.py -v
task backend -- uv run pytest path/to/test_file.py::test_function_name -v
```

### CI-equivalent Checks
### Arbitrary Container Commands

```bash
# Run pre-push checks (format-check + lint-check + type-check)
task check
task backend -- <command> # Run in backend container (e.g., uv add <pkg>)
task frontend -- <command> # Run in frontend container (e.g., pnpm add <pkg>)
```

## Commit Conventions

Git hooks are managed by Lefthook.

### Commit Message Format

```
<type>: <description>
```

Allowed `<type>` values:
Git hooks via Lefthook:
- **pre-commit**: Auto-formats and lints, stages fixes
- **pre-push**: Runs `task check` (no auto-fix)

- `feat`: New feature
- `fix`: Bug fix
- `refactor`: Code refactoring
- `chore`: Other changes
Commit message format: `<type>: <description>`

### Automatic Hooks

- **pre-commit**: Auto-formats and lints code, staging fixes automatically
- **pre-push**: Runs all checks (format-check + lint-check + type-check)
Allowed types: `feat`, `fix`, `refactor`, `chore`

## API Development Guide

### Receiving XML Files

For large files like Apple Healthcare's export.xml, use this pattern:
For raw XML bodies (not JSON), use `Body()` directly — `BaseModel` schemas expect JSON and won't work:

```python
from fastapi import APIRouter, Body
Expand All @@ -164,40 +166,20 @@ async def extract_steps(
example="<?xml version=\"1.0\"?>..."
)
):
# xml_data contains raw XML string
pass
```

Client-side usage:

```bash
# Using curl
curl -X POST -H 'Content-Type: text/xml' \
--data-binary @export.xml \
http://localhost:8000/api/v1/extract-steps

# Using JavaScript fetch
const file = document.getElementById('input').files[0];
await fetch('http://localhost:8000/api/v1/extract-steps', {
method: 'POST',
headers: {'Content-Type': 'text/xml'},
body: file
});
'http://localhost:8000/api/v1/extract-steps?months_of_extract=1'
```

**Note**: Using a `BaseModel` schema expects JSON format and won't work with raw XML. Use `Body()` directly instead.

## Package Management
### LLM Integration

- **Backend**: Uses `uv` (dependencies managed in `pyproject.toml`)
- **Frontend**: Uses `pnpm` (dependencies managed in `package.json`)
The backend uses the OpenAI Python SDK pointed at the Ollama endpoint — not actual OpenAI. The `OLLAMA_ENDPOINT` env var configures this. Model names come from Ollama's model list (e.g., `thor-gemma3:latest`).

Execute commands inside containers:

```bash
# Run arbitrary command in backend container
task backend -- <command>
## Package Management

# Run arbitrary command in frontend container
task frontend -- <command>
```
- **Backend**: `uv` (`pyproject.toml`)
- **Frontend**: `pnpm` (`package.json`)
35 changes: 24 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
Thor の AI 駆動のフルスタック

https://github.com/273Do/Thor
https://github.com/273Do/Thor-Web-App-Frontend
https://github.com/273Do/Thor-Web-App-Backend

## アプリ概要

本 web アプリは、iPhone のヘルスケアデータから睡眠パターンを推定・分析するWebサービスです
本 web アプリは、iPhone のヘルスケアデータから睡眠パターンを推定・分析し、LLM からフィードバックを取得するWebサービスです

### フロー

Expand All @@ -29,6 +27,20 @@ https://github.com/273Do/Thor-Web-App-Backend

本サービスは Apple ヘルスケアの XML エクスポートを利用するため、iPhone ユーザーを対象としています。Android など他のデバイスをお使いの方は、ヘルスデータを[指定の形式](https://github.com/273Do/Thor-Monorepo/blob/1923da19e313a79f8afa3c7c4b4036ce1542586a/backend/src/schemas/estimate_sleep.py#L29-L46)に変換したうえで API を直接呼び出すことでご利用いただけます。

### LLM を用いたフィードバック

独自のアルゴリズムによって推定された睡眠データを参考に、ローカル LLM を使用してフィードバックを行います。対応経路によって使用するモデルとプロンプトが異なります。

- ブラウザ表示では解析結果がグラフで視覚的に確認できるため、LLM フィードバックは補助的な位置づけです。応答速度がユーザー体験に直結するため、軽量モデルを使用し、プロンプトも睡眠傾向の要約と手軽なアドバイスに絞っています。

- メール送信では応答速度より質を優先できます。グラフを伴わないテキストのみの出力になります。睡眠パター
ンの詳細な分析・専門的見解・生活習慣への影響・具体的な改善アドバイスを含む包括的なフィードバックを返すプロンプトを使用しています。

| 種別 | モデル | レスポンス内容 | レスポンス速度 | 対応経路 |
| ---------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------ | -------------- | -------------------- |
| 専門的なフィードバック | [gemma3:27b](https://ollama.com/library/gemma3:27b) | 解析結果をもとにした睡眠異常・専門的見解・生活への影響・具体的な改善アドバイス | 1分程度 | メール経由のみ |
| 簡易的なフィードバック | [mistral-small3.1:24b](https://ollama.com/library/mistral-small3.1:24b) | 睡眠傾向のサマリーと、すぐに実践できる簡単なアドバイス | 10秒程度 | ブラウザ・メール経由 |

## プロジェクト構成

- **Frontend**: React + TypeScript + Tailwind CSS
Expand Down Expand Up @@ -58,24 +70,25 @@ git clone git@github.com:273Do/Thor-Monorepo.git

### 3. LLM の用意

以下のコマンドを実行して`ollama/` 内に用意された LLM を読み込みます。
以下のコマンドを実行して`ollama/` 内に用意された LLM を読み込みます。かなり時間がかかります。

```bash
chmod +x ollama/setup.sh
./ollama/setup.sh
```

うまく読み込めると以下のように表示されますが、自作モデル(thor-\*)はベースモデルの重みを共有して参照しているだけなので、ディスク容量が2倍になるわけではありません。Modelfile
で設定したパラメータの差分だけが追加で保持されています。
うまく読み込めると以下のように表示されますが、自作モデル(thor-\*)はベースモデルの重みを共有して参照しているだけなので、ディスク容量が2倍になるわけではありません。Modelfile で設定したパラメータの差分だけが追加で保持されています。

```
NAME ID SIZE MODIFIED
gemma3:12b hogehogehoge o GB x seconds ago
thor-gemma3:latest fugafugafuga o GB x seconds ago
thor-llama3:latest piyopiyopiyo o GB x seconds ago
llama3.1:8b fofoofoofoof o GB x seconds ago
gemma3:27b abcdefghijkl 17 GB X hours ago
thor-gemma3-27b:latest mnopqrstuvwx 17 GB X hours ago
thor-mistral-small3-1-24b:latest yz0123456789 15 GB X hours ago
mistral-small3.1:24b ABCDEFGHIJKL 15 GB X hours ago
```

Modelfile の各パラメータ詳細は[公式ドキュメント](https://docs.ollama.com/modelfile#parameter)を参照。

### 4. 起動方法

VSCode で Dev Container でプロジェクトを開きます。
Expand Down Expand Up @@ -122,7 +135,7 @@ claude

## 公開設定

- cloudflare tunnel を使用してアプリを公開します。
- cloudflare tunnel を使用してアプリを公開します。(未対応)

1. [Cloudflareダッシュボード](https://dash.cloudflare.com)から [Zero Trust] > [Networks] > [Overview] > [Manage Tunnels] > [Create new cloudflared Tunnel] を選択します。

Expand Down
2 changes: 1 addition & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ MAIL_USERNAME=${MAIL_ADDRESS}
MAIL_PASSWORD=xxxxxxxxxxxxxxxx
MAIL_FROM=${MAIL_ADDRESS}
MAIL_PORT=587
MAIL_SERVER=smtp.mail.com
MAIL_SERVER=smtp.gmail.com
Loading
Loading