Skip to content

feat: 리포트 가드레일·응급 안내와 건강 앱 하루 요약 API - #11

Merged
Frekion2002 merged 3 commits into
mainfrom
feat/health-daily-summary
Nov 8, 2026
Merged

Frekion2002 merged 3 commits into
mainfrom
feat/health-daily-summary

Conversation

@Frekion2002

Copy link
Copy Markdown
Collaborator

변경 내용

  • 리포트 가드레일: Gemini 추출 뒤 규칙 2차 검사(부모가 말하지 않은 병명·진단·약 지시 제거, 부모가 직접 말한 복약 변화는 유지)와 LLM 검토(설정 GUARDRAIL_LLM_REVIEW, 실패 시 규칙 결과로 진행).
  • 응급 안내: 부모 발화의 흉통·호흡 곤란·마비 등 표현을 찾아 고정 문구 저장(부정·운동 뒤 숨참 제외). 짧아서 분석 제외된 통화도 검사. 리포트 safetyNotices, 통화 extraction safetyNotice.
  • 건강 앱 하루 요약: GET/POST/DELETE /v1/health/link(별도 선택 동의, 이력 보관), PUT /v1/health/daily, GET /v1/parents/{id}/health/daily(리포트 공유 범위를 따름). 연결 해제·탈퇴 시 요약 삭제. 필수 동의(v5) 미완료면 403.
  • 마이그레이션 0016_health_daily_summary(health_link_consents, health_daily_summaries, health_extractions.safety_notice). 배포 스크립트가 백업 뒤 자동 적용.

검증

  • 테스트 341 → 422 통과, ruff 통과.
  • 기존 API 응답은 필드 추가만 있어 기존 iOS·안드로이드 앱과 호환.

참고

  • 실제 Gemini 모드에서는 분석한 통화마다 검토 호출 1회가 늘어남. 끄려면 운영 env GUARDRAIL_LLM_REVIEW=false.

Parents can link a phone health store (Health Connect, later HealthKit) with
a separate optional consent and upload one summary per Asia/Seoul day:
sleep minutes, steps, resting heart rate, last blood pressure and glucose.
Minute-level records are never accepted. The required v5 consent is
untouched.

- GET/POST/DELETE /v1/health/link: consent document, link with history
  (version, platform, IP, user agent), revoke. Revoking or deleting the
  account removes the stored summaries immediately.
- PUT /v1/health/daily: 1-35 days, today back to 400 days, range-checked
  values, upsert per (user, day). 403 HEALTH_LINK_REQUIRED when not linked,
  409 HEALTH_CONSENT_VERSION_MISMATCH for a stale document.
- GET /v1/parents/{parentId}/health/daily: up to 62 days, same access rule
  as reports (ensure_subject_access with the report share flag).

Guardrails after Gemini extraction:
- A rule check drops facts whose summary adds a diagnosis, disease name or
  medication/treatment instruction the parent did not say.
- An optional Gemini review (GUARDRAIL_LLM_REVIEW, off in mock mode) drops
  facts it flags; a failed review keeps the rule-checked facts and logs.
- Emergency phrases in parent speech store a fixed safety notice on the
  extraction, exposed as safetyNotice on the extraction and safetyNotices
  on reports.

Migration 0016 adds health_link_consents, health_daily_summaries and
health_extractions.safety_notice.
…tion changes

Emergency detection
- Match common phrasings with an optional particle and up to six characters
  in between (가슴 아파, 가슴이 너무 아파, 숨을 잘 못 쉬겠어, 숨 막혀,
  숨쉬기 힘들어, 말이 좀 어눌해, 한쪽 팔이 저려, 정신을 잃었어, 피 토했어).
- Skip negations right before or after the phrase (안 아파, 아프지 않아,
  답답하진 않아), objects falling over (쓰러뜨리) and idioms (기절할 뻔,
  기절하는 줄), and 피곤/주말/숨기다 look-alikes.
- Plain breathlessness (숨이 차) no longer raises a notice; it is common
  after stairs or walking. Breathlessness at rest (가만히/누워/쉬어도/앉아
  있어도 ... 숨이 차) still does. The eval case 「계단을 오르니 숨이 찼어」
  is now pinned as no notice.

Short calls
- The emergency check now runs before the short-speech exclusion. An
  excluded call with a notice stores an extraction row (parseStatus
  EXCLUDED) so GET /v1/calls/{callId}/extraction and the report's
  safetyNotices show it. A cached report is reissued when its notices
  differ.

Rule check
- Directive medication wording (복용하세요, 줄이세요, 바꿔야 ...) is
  dropped regardless of the evidence. Descriptive changes pass when the
  evidence has 약 and the same stem (줄이/늘리/끊/바꾸/받, 처방).
- Add 위험(이|도)? (있|높|하) and 가능성(이|도)? (있|높), and the disease
  names 관절염, 고지혈증, 골다공증, 디스크. 암 counts only as a word (or
  with an organ prefix such as 폐암), so 암만 no longer matches.
…by seq

- POST /v1/health/link and PUT /v1/health/daily now require the current
  required consent; otherwise 403 CONSENT_REQUIRED.
- PUT /v1/health/daily accepts dates up to tomorrow (Asia/Seoul) so a
  device clock slightly ahead near midnight does not reject the whole
  upload. Later dates are still 422.
- health_link_consents gets a per-user seq (migration 0016) used as the
  second sort key, so GRANTED/REVOKED records with the same agreed_at
  resolve deterministically.
- Note the single-Caddy-hop assumption behind the recorded IP.
@Frekion2002
Frekion2002 merged commit b9e6912 into main Nov 8, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant