Skip to content

Merge to main#15

Merged
H4nnhoi merged 8 commits intomainfrom
develop
Nov 3, 2025
Merged

Merge to main#15
H4nnhoi merged 8 commits intomainfrom
develop

Conversation

@H4nnhoi
Copy link
Contributor

@H4nnhoi H4nnhoi commented Nov 3, 2025

🔎 Description

edit todo

🔗 Related Issue

x

🏷️ What type of PR is this?

  • ✨ Feature
  • ♻️ Code Refactor
  • 🐛 Bug Fix
  • 🚑 Hot Fix
  • 📝 Documentation Update
  • 🎨 Style
  • ⚡️ Performance Improvements
  • ✅ Test
  • 👷 CI
  • 💚 CI Fix

📋 Changes Made

  • Change 1
  • Change 2
  • Change 3

🧪 Testing

  • Unit tests pass
  • Integration tests pass
  • Manual testing completed

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 감정 알림 목록 제공: 케어용 알림 조회 API 추가 및 알림 저장 기능 도입
    • 날짜별 주요 감정 조회: 사용자 및 케어 대상의 특정 날짜 주요 감정 조회 API 추가
    • 음성 목록 날짜 필터링: 음성 기록 조회에 날짜 파라미터 지원
    • 업로드 확장: 음성 업로드 요청에 기본 언어 코드(language_code) 추가
  • 개선

    • 음성 변환 신뢰성 강화: FFmpeg 기반 변환 및 검증 추가로 업로드 안정성 향상
    • 음성 분석 응답에 사용자 메타데이터(이름, 생성 시간) 포함

@H4nnhoi H4nnhoi self-assigned this Nov 3, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 3, 2025

Caution

Review failed

The pull request is closed.

개요

이 풀 리퀘스트는 음성 분석 모델을 VoiceAnalyze에서 VoiceComposite로 변경하고, 새로운 알림 시스템을 도입하며, 날짜 기반 필터링을 지원하고, 사용자 감정 추적을 위한 새로운 API 엔드포인트를 추가합니다.

Walkthrough

VoiceComposite로 모델 참조를 교체하고 Notification 모델/마이그레이션 및 저장소를 추가했으며, 날짜 필터링을 도입하고 사용자·보호자용 상위 감정 API와 알림 생성(저장 후 FCM 전송) 로직을 추가했습니다.

Changes

응집된 변경사항 / 파일 변경 요약
데이터 모델 및 마이그레이션
app/models.py, database_schema.sql, migrations/versions/202511010002_add_notification.py
새로운 Notification 모델/테이블 추가(notification_id PK, voice_id FK, name, top_emotion, created_at, 인덱스 2개)
DTO 정의
app/dto.py
NotificationItem, NotificationListResponse, TopEmotionResponse, CareTopEmotionResponse 추가; VoiceUploadRequestlanguage_code 필드 추가
감정 집계 서비스 변경
app/care_service.py, app/voice_service.py
모든 VoiceAnalyze 참조를 VoiceComposite로 교체; 월별/주별 집계 쿼리에서 VoiceComposite.top_emotion 사용 및 null 필터링 추가; 관련 변수명·루프 조정
DB 서비스 및 음성 조회
app/db_service.py, app/voice_service.py
get_care_voicesget_care_voice_list 서명에 날짜 필터(date: Optional[str]) 추가; 페이징 제거(또는 변경) 및 날짜 기반 필터링 로직 추가
알림 저장소 및 작업 흐름
app/repositories/notification_repo.py, app/repositories/job_repo.py
create_notification 리포지토리 추가; job에서 FCM 전송 전에 DB에 Notification 레코드 생성하도록 변경(FCM 실패는 로깅)
상위 감정 서비스
app/services/top_emotion_service.py
get_top_emotion_for_date(session, user_id, date_str) 추가 — 날짜별 감정 집계 및 tie-break 로직 구현
API 엔드포인트 및 라우트
app/main.py
GET /users/top_emotion, GET /care/notifications, GET /care/top_emotion 엔드포인트 추가; GET /care/users/voicesdate 파라미터 추가; 일부 응답에 사용자 메타데이터 필드(username, name, created_at) 포함
오디오 변환 및 업로드 로직
app/voice_service.py
ffmpeg 기반 WAV 변환 경로 추가(타임아웃/검증 포함) 및 비동기(to_thread) 처리, 기존 librosa 폴백 유지; DB 세션 분리 및 예외/롤백 처리 개선

Sequence Diagram(s)

sequenceDiagram
    participant Client as 클라이언트
    participant API as API 엔드포인트
    participant VoiceSvc as VoiceService
    participant JobRepo as Job(Notifier)
    participant Repo as NotificationRepo
    participant DB as DB
    participant FCM as FCM 서버

    Client->>API: 음성 업로드 요청
    API->>VoiceSvc: upload_user_voice (비동기 WAV 변환)
    VoiceSvc->>VoiceSvc: ffmpeg/librosa 변환 및 검증
    VoiceSvc->>DB: Voice, VoiceComposite 저장
    VoiceSvc->>JobRepo: 알림 전송 작업 트리거
    JobRepo->>Repo: create_notification(voice_id, name, top_emotion)
    Repo->>DB: Notification 레코드 생성 (commit)
    JobRepo->>FCM: FCM 푸시 전송 (비동기)
    FCM-->>JobRepo: 전송 결과(성공/실패)
    JobRepo-->>API: 작업 결과 반환
    API-->>Client: 응답
Loading
sequenceDiagram
    participant Client as 클라이언트
    participant API as API 엔드포인트
    participant TopSvc as top_emotion_service
    participant DB as DB

    Client->>API: GET /users/top_emotion?date=YYYY-MM-DD
    API->>TopSvc: get_top_emotion_for_date(user_id, date)
    TopSvc->>DB: Voice JOIN VoiceComposite 쿼리 (date window)
    TopSvc->>TopSvc: top_emotion 집계 (unknown 제외, tie-break by earliest)
    TopSvc-->>API: {date, top_emotion}
    API-->>Client: 응답
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • 집중 검토 필요 항목:
    • app/repositories/job_repo.py: Notification 생성 실패/성공과 FCM 전송 실패 시의 처리 흐름과 롤백/재시도 정책
    • app/services/top_emotion_service.py: 감정 집계·tie-break 및 unknown 제외 로직의 경계 조건
    • app/voice_service.py: ffmpeg subprocess 경로, 타임아웃/오류 처리 및 librosa 폴백 호환성
    • 마이그레이션 파일 및 스키마(database_schema.sql, migrations/...)의 제약·인덱스 정확성

Possibly related PRs

  • Merge to main #11 — care_service의 감정 집계 메서드에서 VoiceComposite로 교체한 변경과 직접적으로 관련됨.
  • Merge to main #14 — VoiceComposite 도입, 알림 저장/전송 로직, job_repo와 DB 마이그레이션 변경들과 코드 레벨로 밀접하게 연관됨.

Poem

🐰 새 Composite로 감정을 모아요,
날짜 따라 흐름을 그려요.
알림을 적어 DB에 남기고,
푸시로 작은 손길 전해요.
당근보다 따뜻한 변화! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning PR 제목 "Merge to main"은 실제 변경 사항과 무관하며 구체적이지 않습니다. 실제 변경 내용은 VoiceAnalyze에서 VoiceComposite로의 마이그레이션, 새로운 Notification 모델 추가, 다양한 엔드포인트 추가 등 상당한 규모의 변경을 포함하고 있습니다. 이 제목은 일반적이고 모호하여 팀원이 변경 사항의 주요 내용을 파악할 수 없습니다. PR 제목을 주요 변경 사항을 명확히 반영하는 구체적인 내용으로 변경하십시오. 예를 들어 "Migrate VoiceAnalyze to VoiceComposite and add Notification system" 또는 "Refactor voice emotion models and introduce notification features"과 같이 작성하면 변경의 핵심을 효과적으로 전달할 수 있습니다.
Description check ⚠️ Warning PR 설명이 템플릿의 필수 항목들을 충분히 작성하지 않았습니다. "Description" 섹션에는 "edit todo"만 있어 실제 변경 사항을 설명하지 못하고 있으며, "Related Issue" 섹션에는 "x"로 입력되어 있고, "Changes Made" 섹션의 체크박스 항목들도 모두 미완료 상태입니다. 실제로는 모델 마이그레이션, 새 API 엔드포인트, 데이터베이스 스키마 변경, 마이그레이션 파일 추가 등 여러 중요한 변경이 있으나 설명되지 않았습니다. PR 설명을 상세히 작성하십시오. Description 섹션에는 VoiceAnalyze에서 VoiceComposite로의 마이그레이션, Notification 모델 추가, 새 API 엔드포인트(top_emotion, notifications) 등을 명확히 기술하고, Related Issue에 실제 이슈 링크를 입력하며, Changes Made 섹션에 구체적인 변경 사항들을 작성하고 적절히 체크하십시오.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 579ca08 and 6673502.

📒 Files selected for processing (1)
  • migrations/versions/202511010002_add_notification.py (1 hunks)

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/db_service.py (1)

93-135: datetime 중복 import를 제거하세요.

Line 3에서 이미 datetime를 import했는데 Line 101에서 다시 import하고 있습니다.

     def get_care_voices(self, care_username: str, date: Optional[str] = None) -> List[Voice]:
         """보호자(care)의 연결 사용자 음성 중 voice_analyze가 존재하는 항목만 최신순 조회
         
         Args:
             care_username: 보호자 username
             date: 날짜 필터 (YYYY-MM-DD, Optional). None이면 전체 조회
         """
         from sqlalchemy.orm import joinedload
-        from datetime import datetime
         # 1) 보호자 조회
🧹 Nitpick comments (7)
app/repositories/notification_repo.py (1)

1-7: top_emotion 타입 힌트를 Optional로 맞춰주세요
Line [6]에서 기본값을 None으로 주고 있지만 타입 힌트가 str이라 정적 검사에서 오류가 발생할 수 있습니다. Optional[str]로 선언하고 typing.Optional을 import하면 일관성이 맞습니다.

-"""Notification Repository"""
-from sqlalchemy.orm import Session
-from ..models import Notification, Voice, VoiceComposite
+"""Notification Repository"""
+from typing import Optional
+from sqlalchemy.orm import Session
+from ..models import Notification, Voice, VoiceComposite
@@
-def create_notification(session: Session, voice_id: int, name: str, top_emotion: str = None) -> Notification:
+def create_notification(session: Session, voice_id: int, name: str, top_emotion: Optional[str] = None) -> Notification:
app/services/top_emotion_service.py (2)

42-46: 사용하지 않는 루프 변수명을 수정하세요.

루프 변수 v가 사용되지 않습니다. 언더스코어 접두사를 사용하여 의도를 명확히 하세요.

-        for v, vc in q:
-            em = vc.top_emotion if vc else None
+        for _v, vc in q:
+            em = vc.top_emotion if vc else None

82-86: 예외 처리를 구체적으로 개선하세요.

일반적인 Exception을 잡아서 None을 반환하면 예기치 않은 버그를 숨길 수 있습니다. 구체적인 예외만 처리하거나 최소한 로깅을 추가하는 것을 고려하세요.

     except ValueError:
         # 날짜 형식 오류
         return None
-    except Exception:
+    except Exception as e:
+        import logging
+        logging.error(f"Unexpected error in get_top_emotion_for_date: {e}")
         return None
app/main.py (1)

510-530: 날짜 검증 예외 처리를 개선하세요.

날짜 형식 오류 시 HTTPException을 발생시킬 때 원본 예외를 체이닝하면 디버깅이 더 쉬워집니다.

     if date:
         try:
             datetime.strptime(date, "%Y-%m-%d")
         except ValueError:
-            raise HTTPException(status_code=400, detail="Invalid date format. Use YYYY-MM-DD")
+            raise HTTPException(status_code=400, detail="Invalid date format. Use YYYY-MM-DD") from None
app/voice_service.py (3)

37-95: ffmpeg 기반 변환 구현이 잘 되었으나 개선사항이 있습니다.

ffmpeg를 stdin/stdout 파이프로 사용하여 임시 파일을 제거한 최적화가 훌륭합니다. 하지만 몇 가지 개선할 점이 있습니다:

  1. 하드코딩된 ffmpeg 명령어를 사용하므로 S603 보안 경고는 거짓 양성입니다.
  2. WAV 헤더 검증 로직이 좋습니다.
  3. 적절한 타임아웃(30초) 설정됨.

다음과 같이 상수로 분리하면 유지보수가 더 쉬워집니다:

# 파일 상단에 상수 정의
FFMPEG_TIMEOUT_SECONDS = 30
MIN_AUDIO_FILE_SIZE = 100
MIN_WAV_OUTPUT_SIZE = 3200  # 0.1초 분량 (16kHz * 2 bytes * 0.1s)

245-312: 독립적인 DB 세션 사용이 올바릅니다.

비동기 백그라운드 작업에서 독립적인 세션을 생성하고 finally 블록에서 정리하는 패턴이 적절합니다. 하지만 예외 처리를 개선하면 디버깅이 더 쉬워집니다.

         except Exception as e:
-            print(f"STT → NLP 처리 중 오류 발생: {e}")
+            import logging
+            logging.error(f"STT → NLP 처리 중 오류 발생 (voice_id={voice_id}): {e}", exc_info=True)
             db.rollback()

314-444: 음성 감정 분석 백그라운드 처리의 세션 관리가 적절합니다.

독립적인 DB 세션을 사용하고 있으며, 에러 처리 및 롤백 로직이 올바릅니다. 마찬가지로 로깅 개선을 고려하세요.

         except Exception as e:
-            print(f"Audio emotion background error: {e}", flush=True)
+            import logging
+            logging.error(f"Audio emotion background error (voice_id={voice_id}): {e}", exc_info=True)
             db.rollback()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 154e41e and 579ca08.

📒 Files selected for processing (11)
  • app/care_service.py (4 hunks)
  • app/db_service.py (2 hunks)
  • app/dto.py (2 hunks)
  • app/main.py (6 hunks)
  • app/models.py (1 hunks)
  • app/repositories/job_repo.py (3 hunks)
  • app/repositories/notification_repo.py (1 hunks)
  • app/services/top_emotion_service.py (1 hunks)
  • app/voice_service.py (14 hunks)
  • database_schema.sql (1 hunks)
  • migrations/versions/202511010002_add_notification.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
app/repositories/job_repo.py (2)
app/models.py (1)
  • VoiceComposite (146-180)
app/repositories/notification_repo.py (1)
  • create_notification (6-27)
app/repositories/notification_repo.py (1)
app/models.py (3)
  • Notification (222-239)
  • Voice (32-58)
  • VoiceComposite (146-180)
app/db_service.py (1)
app/models.py (2)
  • Voice (32-58)
  • VoiceAnalyze (85-110)
app/services/top_emotion_service.py (1)
app/models.py (3)
  • Voice (32-58)
  • VoiceComposite (146-180)
  • User (8-29)
app/voice_service.py (4)
app/models.py (3)
  • VoiceAnalyze (85-110)
  • Voice (32-58)
  • VoiceComposite (146-180)
app/db_service.py (4)
  • get_db_service (328-330)
  • create_voice_content (142-160)
  • create_voice_analyze (192-212)
  • get_care_voices (93-135)
app/repositories/job_repo.py (3)
  • mark_text_done (98-101)
  • try_aggregate (110-160)
  • mark_audio_done (104-107)
app/performance_logger.py (1)
  • get_performance_logger (109-115)
app/main.py (6)
app/dto.py (3)
  • NotificationListResponse (83-85)
  • TopEmotionResponse (89-92)
  • CareTopEmotionResponse (95-99)
app/database.py (1)
  • get_db (38-44)
app/services/top_emotion_service.py (1)
  • get_top_emotion_for_date (10-86)
app/auth_service.py (2)
  • get_auth_service (297-299)
  • get_user_by_username (144-146)
app/voice_service.py (2)
  • get_voice_service (804-806)
  • get_care_voice_list (517-537)
app/models.py (3)
  • Notification (222-239)
  • Voice (32-58)
  • User (8-29)
app/care_service.py (1)
app/models.py (3)
  • User (8-29)
  • Voice (32-58)
  • VoiceComposite (146-180)
🪛 Ruff (0.14.2)
app/repositories/job_repo.py

48-48: Do not catch blind exception: Exception

(BLE001)


50-50: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


50-50: Use explicit conversion flag

Replace with conversion flag

(RUF010)


85-85: Use explicit conversion flag

Replace with conversion flag

(RUF010)

app/repositories/notification_repo.py

6-6: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)

app/db_service.py

101-101: Redefinition of unused datetime from line 3

Remove definition: datetime

(F811)

app/services/top_emotion_service.py

42-42: Loop control variable v not used within loop body

Rename unused v to _v

(B007)


80-80: Consider moving this statement to an else block

(TRY300)


85-85: Do not catch blind exception: Exception

(BLE001)

app/voice_service.py

66-66: subprocess call: check for execution of untrusted input

(S603)


86-86: f-string without any placeholders

Remove extraneous f prefix

(F541)


92-92: Do not catch blind exception: Exception

(BLE001)


108-108: Avoid specifying long messages outside the exception class

(TRY003)


120-120: Avoid specifying long messages outside the exception class

(TRY003)


308-308: Do not catch blind exception: Exception

(BLE001)


440-440: Do not catch blind exception: Exception

(BLE001)

app/main.py

385-385: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


514-514: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


526-526: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


554-554: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


592-592: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

🔇 Additional comments (5)
app/models.py (1)

222-239: 새 Notification 모델 구조가 적절합니다.

알림 기록을 위한 새 모델이 잘 설계되었습니다:

  • 적절한 인덱스 구성 (voice_id, created_at)
  • CASCADE 삭제로 참조 무결성 유지
  • Voice와의 관계 설정 올바름
app/care_service.py (1)

2-2: VoiceComposite로의 마이그레이션이 올바르게 수행되었습니다.

VoiceAnalyze에서 VoiceComposite로 전환이 일관되게 이루어졌으며, null 감정 필터링을 추가하여 데이터 품질을 개선했습니다.

Also applies to: 31-39, 74-86

app/main.py (1)

384-405: 새로운 엔드포인트들이 잘 구현되었습니다.

사용자 및 보호자 대표 감정 조회, 알림 목록 조회 엔드포인트가 기존 패턴을 따르며 적절히 구현되었습니다:

  • 사용자 검증 로직 포함
  • 적절한 에러 처리
  • 일관된 응답 형식

Also applies to: 553-588, 591-617

app/voice_service.py (2)

172-174: 비동기 처리 개선이 적절합니다.

CPU 집약적인 WAV 변환 작업을 asyncio.to_thread로 처리하여 이벤트 루프 블로킹을 방지한 것이 좋습니다.

Also applies to: 636-638


517-537: VoiceComposite로의 마이그레이션이 일관되게 수행되었습니다.

VoiceAnalyze에서 VoiceComposite로의 전환이 모든 쿼리에서 올바르게 이루어졌습니다:

  • 조인 조건 업데이트
  • top_emotion 필드 참조 변경
  • null 필터링 추가

Also applies to: 725-734, 756-768

@H4nnhoi H4nnhoi merged commit 28155a1 into main Nov 3, 2025
1 check was pending
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