Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 1 addition & 7 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"editor.formatOnSave": true,
"editor.tabSize": 2,

// Formatter
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
Expand All @@ -16,11 +15,11 @@
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},

// Python Settings
"python.analysis.extraPaths": [
"/workspace/backend/.venv/lib/python3.14/site-packages"
],
"python.defaultInterpreterPath": "${workspaceFolder}/backend/.venv/bin/python3.14",
"python.analysis.autoImportCompletions": true,
"python.analysis.typeCheckingMode": "strict",
"python.analysis.fixAll": [
Expand All @@ -36,7 +35,6 @@
},
"ruff.interpreter": ["${workspaceFolder}/backend/.venv/bin/python"],
"ruff.path": ["${workspaceFolder}/backend/.venv/bin/ruff"],

// Indent Rainbow
"indentRainbow.indicatorStyle": "light",
"indentRainbow.lightIndicatorStyleLineWidth": 1,
Expand All @@ -47,7 +45,6 @@
"rgba(255,127,255, 0.2)",
"rgba(79,236,236, 0.2)"
],

// Error Lens
"errorLens.gutterIconsEnabled": true,
"errorLens.gutterIconSet": "emoji",
Expand All @@ -57,7 +54,6 @@
"info": "📘",
"hint": "🍏"
},

// Spell
"cSpell.enabledSchemes": {
"comment": true,
Expand All @@ -73,10 +69,8 @@
"vsls": true,
"overleaf-workshop": true
},

// Commnet Translate
"commentTranslate.hover.nearShow": false,

// TODO Tree
"todo-tree.highlights.defaultHighlight": {
"gutterIcon": true
Expand Down
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,22 +83,28 @@ VSCode で Dev Container でプロジェクトを開きます。
### 5. 初期設定

1. `backend`ディレクトリに事前のデータを格納するディレクトリを作成します。
2. 各環境変数を作成します。
3. 以下のコマンドでパッケージをインストールします。
2. 以下の手順に従って送信用のメールアドレスを設定します。
1. [myaccount.google.com/security](https://myaccount.google.com/security) を開きます。
2. 「2段階認証プロセス」を有効にします。
3. [myaccount.google.com/apppasswords](https://myaccount.google.com/apppasswords) を開きます。
4. 「アプリ名」に任意の名前を入力して「作成」を押し、表示された16文字のパスワード(xxxx xxxx xxxx xxxx形式<環境変数に設定する際は空白は含まない>)を控える。

3. 各環境変数を作成します。
4. 以下のコマンドでパッケージをインストールします。

```bash
task frontend -- pnpm i
task backend -- uv sync
```

4. 以下のコマンドでアプリを起動します。
5. 以下のコマンドでアプリを起動します。

```bash
task frontend:dev
task backend:dev
```

5. アプリを公開する場合はドメインを取得しておきます。
6. アプリを公開する場合はドメインを取得しておきます。

### 6. Claude Code

Expand Down
4 changes: 3 additions & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ tasks:
desc: Run backend in development mode
dir: backend
cmds:
- task backend -- uv run uvicorn main:app --reload --host 0.0.0.0
- task backend -- uv run uvicorn main:app --reload --host 0.0.0.0 --workers 1
# 本番では以下のコマンドでワーカーを増やしたいが、キューシステムを導入するならシングルワーカーで動作させたい
# - task backend -- uv run uvicorn main:app --host 0.0.0.0 --workers 4

# Frontend tasks
frontend:format:
Expand Down
11 changes: 10 additions & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,13 @@ VAULT_DIR=${DATASTORE_DIR}/vault
API_V1_PREFIX=/api/v1

# 識別id生成用のソルト
DATA_ID_SALT=change_me
DATA_ID_SALT=change_me


# メール送信設定(<gmailの例>SMTPサーバー情報)
MAIL_ADDRESS=your-address@gmail.com
MAIL_USERNAME=${MAIL_ADDRESS}
MAIL_PASSWORD=xxxxxxxxxxxxxxxx
MAIL_FROM=${MAIL_ADDRESS}
MAIL_PORT=587
MAIL_SERVER=smtp.mail.com
3 changes: 2 additions & 1 deletion backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from fastapi.middleware.cors import CORSMiddleware

from src.core.load_env import envs
from src.routers import estimate_sleep, extract_steps, llm_feedback
from src.routers import estimate_sleep, extract_steps, llm_feedback, via_email

app = FastAPI()

Expand All @@ -28,3 +28,4 @@
app.include_router(extract_steps.router, prefix=envs.API_V1_PREFIX)
app.include_router(estimate_sleep.router, prefix=envs.API_V1_PREFIX)
app.include_router(llm_feedback.router, prefix=envs.API_V1_PREFIX)
app.include_router(via_email.router, prefix=envs.API_V1_PREFIX)
6 changes: 5 additions & 1 deletion backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ requires-python = ">=3.12"
dependencies = [
"fastapi>=0.115.0",
"uvicorn[standard]>=0.32.0",
"pydantic>=2.9.0",
"pydantic[email]>=2.9.0",
"pydantic-settings>=2.6.0",
"pandas>=2.2.0",
"python-dateutil>=2.9.0.post0",
"pandera>=0.28.1",
"scikit-learn>=1.8.0",
"openai>=2.20.0",
"python-multipart>=0.0.24",
"fastapi-mail>=1.6.2",
"jinja2>=3.1.6",
"markdown>=3.10.2",
]

[build-system]
Expand Down
3 changes: 3 additions & 0 deletions backend/src/core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@

HOLIDAY_TIME_RANGE = ["3:00", "4:45", "12:45", "20:45"]
"""休日の精査範囲"""

FAILED_GENERATE_FEEDBACK = "フィードバックの生成に失敗しました。"
"""フィードバックの生成に失敗した際のメッセージ"""
20 changes: 19 additions & 1 deletion backend/src/core/load_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from functools import lru_cache

from pydantic import Field
from pydantic import EmailStr, Field
from pydantic_settings import BaseSettings, SettingsConfigDict


Expand Down Expand Up @@ -33,6 +33,24 @@ class Envs(BaseSettings):
DATA_ID_SALT: str = Field()
"""識別id生成用のソルト"""

MAIL_ADDRESS: EmailStr = Field()
"""メールアドレス"""

MAIL_USERNAME: EmailStr = Field()
"""SMTPサーバーのユーザー名"""

MAIL_PASSWORD: str = Field()
"""SMTPサーバーのパスワード"""

MAIL_FROM: EmailStr = Field()
"""送信元メールアドレス"""

MAIL_PORT: int = Field(default=587)
"""ポート"""

MAIL_SERVER: str = Field(default="smtp.gmail.com")
"""SMTPサーバーのホスト名"""

OLLAMA_ENDPOINT: str = Field(default="http://host.docker.internal:11434/v1/")
"""ollamaのエンドポイント"""

Expand Down
57 changes: 3 additions & 54 deletions backend/src/routers/estimate_sleep.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,8 @@
import pandas as pd
from fastapi import APIRouter
from pandera.typing import DataFrame

from src.schemas.estimate_sleep import EstimateSleepRequest, EstimateSleepResponse
from src.schemas.extract_steps import StepCountDFSchema
from src.usecases.estimate_sleep.detection_late_night_usecase import detect_late_night
from src.usecases.estimate_sleep.estimate_sleep_duration_usecase import (
estimate_sleep_duration_from_step,
)
from src.usecases.estimate_sleep.feature_of_late_night_usecase import (
create_feature_value,
)
from src.usecases.estimate_sleep.save_data_to_storage_usecase import (
get_llms,
save_data_to_storage,
)
from src.usecases.estimate_sleep.step_clustering_usecase import step_clustering
from src.usecases.estimate_sleep.run_estimate_sleep_usecase import run_estimate_sleep
from src.usecases.estimate_sleep.save_data_to_storage_usecase import get_llms

router = APIRouter(prefix="/estimate-sleep", tags=["estimate-sleep"])

Expand All @@ -36,45 +23,7 @@
def estimate_sleep(req: EstimateSleepRequest) -> EstimateSleepResponse:
"""睡眠推定を受け付ける"""

id = req.id
answers = req.answers
step_data = req.step_data

# NAS に入力データを保存
save_data_to_storage(id, step_data)

step_count_df: DataFrame[StepCountDFSchema] = pd.DataFrame(
[record.model_dump() for record in step_data]
) # type: ignore

# 外出検知のため1分あたりの歩数を計算しクラスタリングを行う
estimate_going_out_df, cluster_stats = step_clustering(step_count_df)

# 歩数データから夜更かしを推定するための特徴量を抽出
feature = create_feature_value(step_count_df, answers.bedtime_answer)

# 特徴量から夜更かし検知を行う
late_night_list = detect_late_night(feature)

# print(late_night_list)

# print(estimate_going_out_df)

# print(cluster_stats)

# 歩数データから睡眠時刻を推定する
estimated_data = estimate_sleep_duration_from_step(
estimate_going_out_df,
late_night_list,
answers.charging_before_bed_answer,
answers.carrying_a_smartphone_answer,
)

# NAS に推定結果を保存
save_data_to_storage(id, cluster_stats.clusters)
save_data_to_storage(id, estimated_data)

# フィードバックに使用可能なLLMを取得
estimated_data, _ = run_estimate_sleep(req.id, req.step_data, req.answers)
models = get_llms()

return EstimateSleepResponse(data=estimated_data, models=models)
43 changes: 9 additions & 34 deletions backend/src/routers/extract_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
ExtractedSteps,
ExtractedStepsResponse,
ExtractStepsQueryParams,
validate_extract_params,
)
from src.usecases.extract_steps.extract_steps_usecase import (
extract_steps_from_applehealthcare,
Expand Down Expand Up @@ -34,43 +35,17 @@ def extract_steps(
) -> ExtractedStepsResponse:
"""歩数データの抽出を受け付ける"""

start_date_of_extract = query_params.start_date_of_extract
end_date_of_extract = query_params.end_date_of_extract
months_of_extract = query_params.months_of_extract
include_recorded_sleep = query_params.include_recorded_sleep

# バリデーション: start_date と end_date の両方が指定されているか、months_of_extract のみが指定されているか
has_date_range = (
start_date_of_extract is not None and end_date_of_extract is not None
)
has_months = months_of_extract is not None

if has_date_range and has_months:
raise HTTPException(
status_code=400,
detail="start_date_of_extract / end_date_of_extract と months_of_extract は同時に指定できません。どちらか一方を指定してください。",
)

if not has_date_range and not has_months:
raise HTTPException(
status_code=400,
detail="start_date_of_extract / end_date_of_extract の両方、または months_of_extract のいずれかを指定してください。",
)

if has_date_range:
# start_date と end_date の片方だけ指定されていないかチェック
if start_date_of_extract is None or end_date_of_extract is None:
raise HTTPException(
status_code=400,
detail="start_date_of_extract と end_date_of_extract は両方指定する必要があります。",
)
try:
validate_extract_params(query_params)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))

extracted_data: ExtractedSteps = extract_steps_from_applehealthcare(
xml_data,
start_date_of_extract,
end_date_of_extract,
months_of_extract,
include_recorded_sleep,
query_params.start_date_of_extract,
query_params.end_date_of_extract,
query_params.months_of_extract,
query_params.include_recorded_sleep,
)

return ExtractedStepsResponse(data=extracted_data)
Loading
Loading