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
94 changes: 94 additions & 0 deletions GLANCES_USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Glances μ‚¬μš© κ°€μ΄λ“œ

## μ„€μΉ˜ μ™„λ£Œ
glances 3.4.0.3 버전이 μ„€μΉ˜λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

## κΈ°λ³Έ μ‚¬μš©λ²•

### ν„°λ―Έλ„μ—μ„œ μ‹€μ‹œκ°„ λͺ¨λ‹ˆν„°λ§
```bash
glances
```

### μ›Ή μ„œλ²„ λͺ¨λ“œλ‘œ μ‹€ν–‰ (원격 접속 κ°€λŠ₯)
```bash
# κΈ°λ³Έ 포트(61208)둜 μ‹€ν–‰
glances -w

# νŠΉμ • 포트 μ§€μ •
glances -w -p 61208

# νŠΉμ • IPμ—μ„œλ§Œ 접속 ν—ˆμš© (λ³΄μ•ˆ κ°•ν™”)
glances -w -B 0.0.0.0 -p 61208
```

μ›Ή λΈŒλΌμš°μ €μ—μ„œ 접속: `http://μ„œλ²„IP:61208`

### RESTful API λͺ¨λ“œ
```bash
glances -s
```

### CPU, λ©”λͺ¨λ¦¬, λ””μŠ€ν¬, λ„€νŠΈμ›Œν¬λ§Œ κ°„λ‹¨νžˆ 보기
```bash
glances --percpu
```

## μ£Όμš” 단좕킀

- `q` λ˜λŠ” `ESC`: μ’…λ£Œ
- `h`: 도움말
- `c`: CPU 정보 ν‘œμ‹œ/μˆ¨κΉ€
- `m`: λ©”λͺ¨λ¦¬ 정보 ν‘œμ‹œ/μˆ¨κΉ€
- `d`: λ””μŠ€ν¬ 정보 ν‘œμ‹œ/μˆ¨κΉ€
- `n`: λ„€νŠΈμ›Œν¬ 정보 ν‘œμ‹œ/μˆ¨κΉ€
- `p`: ν”„λ‘œμ„ΈμŠ€ μ •λ ¬ λ³€κ²½
- `w`: κ²½κ³  μ‚­μ œ
- `x`: κ²½κ³ /μ€‘μš” μž„κ³„κ°’ μ‚­μ œ

## μ„œλΉ„μŠ€λ‘œ μ‹€ν–‰

glancesκ°€ systemd μ„œλΉ„μŠ€λ‘œ μžλ™ λ“±λ‘λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

```bash
# μ„œλΉ„μŠ€ μ‹œμž‘
sudo systemctl start glances

# μ„œλΉ„μŠ€ 쀑지
sudo systemctl stop glances

# μ„œλΉ„μŠ€ μƒνƒœ 확인
sudo systemctl status glances

# λΆ€νŒ… μ‹œ μžλ™ μ‹œμž‘ ν™œμ„±ν™”
sudo systemctl enable glances
```

## λ°±κ·ΈλΌμš΄λ“œ μ‹€ν–‰

```bash
# nohup으둜 λ°±κ·ΈλΌμš΄λ“œ μ‹€ν–‰
nohup glances -w > /dev/null 2>&1 &

# tmux/screen μ‚¬μš©
tmux new-session -d -s monitoring 'glances'
```

## μœ μš©ν•œ μ˜΅μ…˜

- `--refresh 2`: 2μ΄ˆλ§ˆλ‹€ κ°±μ‹  (κΈ°λ³Έκ°’: 3초)
- `--disable-plugin docker`: Docker ν”ŒλŸ¬κ·ΈμΈ λΉ„ν™œμ„±ν™”
- `--enable-plugin docker`: Docker ν”ŒλŸ¬κ·ΈμΈ ν™œμ„±ν™”
- `--percpu`: CPU 코어별 μ‚¬μš©λŸ‰ ν‘œμ‹œ
- `--process-short-name`: 짧은 ν”„λ‘œμ„ΈμŠ€ 이름 ν‘œμ‹œ
- `--time`: μ‹œκ°„ ν‘œμ‹œ

## μ˜ˆμ‹œ: μ›Ή λͺ¨λ“œλ‘œ λ°±κ·ΈλΌμš΄λ“œ μ‹€ν–‰

```bash
# μ›Ή μ„œλ²„ λͺ¨λ“œλ‘œ λ°±κ·ΈλΌμš΄λ“œ μ‹€ν–‰
glances -w -B 0.0.0.0 -p 61208 &
```

λΈŒλΌμš°μ €μ—μ„œ `http://μ„œλ²„IP:61208` μ ‘μ†ν•˜μ—¬ λͺ¨λ‹ˆν„°λ§ κ°€λŠ₯ν•©λ‹ˆλ‹€.

25 changes: 25 additions & 0 deletions app/dto.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,28 @@ class ErrorResponse(BaseModel):
class SuccessResponse(BaseModel):
message: str
status: str = "success"


# OpenAI 뢄석 κ²°κ³Ό DTO
class AnalysisResultResponse(BaseModel):
"""OpenAI 쒅합뢄석 κ²°κ³Ό 응닡"""
source: str # weekly | frequency
message: str


class WeeklyDayItem(BaseModel):
date: str
weekday: str
top_emotion: Optional[str] = None


class WeeklyAnalysisCombinedResponse(BaseModel):
"""μ£Όκ°„ 쒅합뢄석: OpenAI λ©”μ‹œμ§€ + κΈ°μ‘΄ μ£Όκ°„ μš”μ•½"""
message: str
weekly: List[WeeklyDayItem]


class FrequencyAnalysisCombinedResponse(BaseModel):
"""μ›”κ°„ λΉˆλ„ 쒅합뢄석: OpenAI λ©”μ‹œμ§€ + κΈ°μ‘΄ λΉˆλ„ κ²°κ³Ό"""
message: str
frequency: dict
101 changes: 76 additions & 25 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
UserInfoResponse, CareInfoResponse,
FcmTokenRegisterRequest, FcmTokenRegisterResponse, FcmTokenDeactivateResponse,
NotificationListResponse,
TopEmotionResponse, CareTopEmotionResponse
TopEmotionResponse, CareTopEmotionResponse,
AnalysisResultResponse, WeeklyAnalysisCombinedResponse, FrequencyAnalysisCombinedResponse
)
from .care_service import CareService
import random
Expand Down Expand Up @@ -361,24 +362,35 @@ async def upload_voice_with_question(
else:
raise HTTPException(status_code=400, detail=result["message"])

@users_router.get("/voices/analyzing/frequency")
@users_router.get("/voices/analyzing/frequency", response_model=FrequencyAnalysisCombinedResponse)
async def get_user_emotion_frequency(username: str, month: str, db: Session = Depends(get_db)):
"""μ‚¬μš©μž 본인의 ν•œλ‹¬κ°„ 감정 λΉˆλ„μˆ˜ 집계"""
voice_service = get_voice_service(db)
result = voice_service.get_user_emotion_monthly_frequency(username, month)
if not result.get("success"):
raise HTTPException(status_code=400, detail=result.get("message", "쑰회 μ‹€νŒ¨"))
return result
"""μ‚¬μš©μž 본인의 μ›”κ°„ λΉˆλ„ 쒅합뢄석(OpenAI μΊμ‹œ + κΈ°μ‘΄ λΉˆλ„ κ²°κ³Ό)"""
from .services.analysis_service import get_frequency_result
try:
message = get_frequency_result(db, username=username, is_care=False)
voice_service = get_voice_service(db)
base = voice_service.get_user_emotion_monthly_frequency(username, month)
frequency = base.get("frequency", {}) if base.get("success") else {}
return FrequencyAnalysisCombinedResponse(message=message, frequency=frequency)
except Exception as e:
raise HTTPException(status_code=400, detail=f"뢄석 μ‹€νŒ¨: {str(e)}")
Comment on lines +365 to +376
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

λͺ¨λ“  μ˜ˆμ™Έλ₯Ό 400으둜 μ²˜λ¦¬ν•˜λŠ” 것은 λΆ€μ μ ˆν•©λ‹ˆλ‹€

OpenAI API νƒ€μž„μ•„μ›ƒ, λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²° 였λ₯˜ λ“± μ„œλ²„ μΈ‘ λ¬Έμ œλŠ” 500 μ—λŸ¬λ‘œ μ²˜λ¦¬λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€. ν˜„μž¬λŠ” λͺ¨λ“  μ˜ˆμ™Έλ₯Ό 400(ν΄λΌμ΄μ–ΈνŠΈ 였λ₯˜)으둜 λ°˜ν™˜ν•˜μ—¬ ν΄λΌμ΄μ–ΈνŠΈκ°€ μž¬μ‹œλ„ λ‘œμ§μ„ μ˜¬λ°”λ₯΄κ²Œ κ΅¬ν˜„ν•˜κΈ° μ–΄λ ΅μŠ΅λ‹ˆλ‹€.

μ˜ˆμ™Έ μœ ν˜•μ— 따라 μ μ ˆν•œ μƒνƒœ μ½”λ“œλ₯Ό λ°˜ν™˜ν•˜λ„λ‘ μˆ˜μ •ν•˜μ„Έμš”:

-    except Exception as e:
-        raise HTTPException(status_code=400, detail=f"뢄석 μ‹€νŒ¨: {str(e)}")
+    except ValueError as e:
+        # μ‚¬μš©μž μž…λ ₯ 였λ₯˜ (user not found λ“±)
+        raise HTTPException(status_code=400, detail=f"잘λͺ»λœ μš”μ²­: {str(e)}")
+    except Exception as e:
+        # μ„œλ²„ λ‚΄λΆ€ 였λ₯˜ (OpenAI API 였λ₯˜, DB 였λ₯˜ λ“±)
+        raise HTTPException(status_code=500, detail=f"μ„œλ²„ 였λ₯˜: {str(e)}")
🧰 Tools
πŸͺ› Ruff (0.14.3)

366-366: 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)


375-375: Do not catch blind exception: Exception

(BLE001)


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

(B904)


376-376: Use explicit conversion flag

Replace with conversion flag

(RUF010)


@users_router.get("/voices/analyzing/weekly")
@users_router.get("/voices/analyzing/weekly", response_model=WeeklyAnalysisCombinedResponse)
async def get_user_emotion_weekly(username: str, month: str, week: int, db: Session = Depends(get_db)):
"""μ‚¬μš©μž 본인의 μ›”/주차별 μš”μΌλ³„ top 감정 μš”μ•½"""
voice_service = get_voice_service(db)
result = voice_service.get_user_emotion_weekly_summary(username, month, week)
"""μ‚¬μš©μž 본인의 μ£Όκ°„ 쒅합뢄석(OpenAI μΊμ‹œ μ‚¬μš©)"""
from .services.analysis_service import get_weekly_result
try:
message = get_weekly_result(db, username=username, is_care=False)
# κΈ°μ‘΄ μ£Όκ°„ μš”μ•½λ„ ν•¨κ»˜ 제곡
voice_service = get_voice_service(db)
weekly_result = voice_service.get_user_emotion_weekly_summary(username, month, week)
weekly = weekly_result.get("weekly", []) if weekly_result.get("success") else []
return WeeklyAnalysisCombinedResponse(message=message, weekly=weekly)
except Exception as e:
raise HTTPException(status_code=400, detail=f"뢄석 μ‹€νŒ¨: {str(e)}")
Comment on lines +378 to +390
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

get_user_emotion_weekly μ—”λ“œν¬μΈνŠΈμ— λ™μΌν•œ μ˜ˆμ™Έ 처리 이슈

Line 365-376μ—μ„œ μ§€μ ν•œ μ˜ˆμ™Έ 처리 λ¬Έμ œκ°€ 이 μ—”λ“œν¬μΈνŠΈμ—λ„ λ™μΌν•˜κ²Œ μ‘΄μž¬ν•©λ‹ˆλ‹€.

λ™μΌν•œ μˆ˜μ •μ‚¬ν•­μ„ μ μš©ν•˜μ„Έμš”.

🧰 Tools
πŸͺ› Ruff (0.14.3)

379-379: 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)


389-389: Do not catch blind exception: Exception

(BLE001)


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

(B904)


390-390: Use explicit conversion flag

Replace with conversion flag

(RUF010)

πŸ€– Prompt for AI Agents
In app/main.py around lines 378-390, the endpoint uses a broad "except Exception
as e" like the earlier block; replace that with targeted error handling by
catching only expected exceptions (or rethrowing unexpected ones), and when
handling errors log the full stacktrace via the application's logger (e.g.,
process_logger.exception) before raising an HTTPException; ensure the
HTTPException returns an appropriate status code (500 for unexpected server
errors or a more specific code for known errors) and a concise detail message
instead of exposing internal stack traces.


if not result.get("success"):
raise HTTPException(status_code=400, detail=result.get("message", "쑰회 μ‹€νŒ¨"))
return result




@users_router.get("/top_emotion", response_model=TopEmotionResponse)
Expand Down Expand Up @@ -529,26 +541,49 @@ async def get_care_user_voice_list(
result = voice_service.get_care_voice_list(care_username, date=date)
return CareUserVoiceListResponse(success=result["success"], voices=result.get("voices", []))

@care_router.get("/users/voices/analyzing/frequency")
@care_router.get("/users/voices/analyzing/frequency", response_model=FrequencyAnalysisCombinedResponse)
async def get_emotion_monthly_frequency(
care_username: str, month: str, db: Session = Depends(get_db)
):
"""
보호자 νŽ˜μ΄μ§€: μ—°κ²°λœ μœ μ €μ˜ ν•œλ‹¬κ°„ 감정 λΉˆλ„μˆ˜ 집계 (CareService λ‚΄λΆ€ 둜직 μ‚¬μš©)
"""
care_service = CareService(db)
return care_service.get_emotion_monthly_frequency(care_username, month)
"""보호자: μ—°κ²° μœ μ €μ˜ μ›”κ°„ λΉˆλ„ 쒅합뢄석(OpenAI μΊμ‹œ + κΈ°μ‘΄ λΉˆλ„ κ²°κ³Ό)"""
from .services.analysis_service import get_frequency_result
try:
message = get_frequency_result(db, username=care_username, is_care=True)
from .care_service import CareService
care_service = CareService(db)
base = care_service.get_emotion_monthly_frequency(care_username, month)
frequency = base.get("frequency", {}) if base.get("success") else {}
return FrequencyAnalysisCombinedResponse(message=message, frequency=frequency)
except Exception as e:
raise HTTPException(status_code=400, detail=f"뢄석 μ‹€νŒ¨: {str(e)}")
Comment on lines +544 to +558
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Care μ—”λ“œν¬μΈνŠΈμ—λ„ λ™μΌν•œ μ˜ˆμ™Έ 처리 이슈

μ˜ˆμ™Έ μœ ν˜• ꡬ뢄 및 μ˜ˆμ™Έ 체이닝 λ¬Έμ œκ°€ λ™μΌν•˜κ²Œ μ‘΄μž¬ν•©λ‹ˆλ‹€.

Lines 365-376μ—μ„œ μ œμ•ˆν•œ μˆ˜μ •μ‚¬ν•­μ„ 이 μ—”λ“œν¬μΈνŠΈμ—λ„ μ μš©ν•˜μ„Έμš”.

🧰 Tools
πŸͺ› Ruff (0.14.3)

546-546: 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)


557-557: Do not catch blind exception: Exception

(BLE001)


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

(B904)


558-558: Use explicit conversion flag

Replace with conversion flag

(RUF010)

πŸ€– Prompt for AI Agents
In app/main.py around lines 544-558, replace the broad "except Exception as e"
with targeted exception handling: let existing HTTPException pass through
(except HTTPException: raise), catch expected domain/validation/db errors (e.g.,
ValueError, sqlalchemy.exc.SQLAlchemyError, or your service-specific exceptions)
and convert them to an HTTPException using "raise HTTPException(status_code=400,
detail=f'뢄석 μ‹€νŒ¨: {e}') from e" to preserve exception chaining; for any other
unexpected exceptions you can log them and raise a generic HTTPException with
chaining as well. Ensure you import any exception classes you reference (e.g.,
SQLAlchemyError) and use "from e" when re-raising to keep the original
traceback.


@care_router.get("/users/voices/analyzing/weekly")






@care_router.get("/users/voices/analyzing/weekly", response_model=WeeklyAnalysisCombinedResponse)
async def get_emotion_weekly_summary(
care_username: str,
month: str,
week: int,
db: Session = Depends(get_db)
):
"""λ³΄ν˜ΈμžνŽ˜μ΄μ§€ - μ—°κ²°μœ μ € μ›”/주차별 μš”μΌ top 감정 톡계"""
care_service = CareService(db)
return care_service.get_emotion_weekly_summary(care_username, month, week)
"""보호자: μ—°κ²° μœ μ €μ˜ μ£Όκ°„ 쒅합뢄석(OpenAI μΊμ‹œ μ‚¬μš©)"""
from .services.analysis_service import get_weekly_result
try:
message = get_weekly_result(db, username=care_username, is_care=True)
# κΈ°μ‘΄ μ£Όκ°„ μš”μ•½λ„ ν•¨κ»˜ 제곡
care_service = CareService(db)
weekly_result = care_service.get_emotion_weekly_summary(care_username, month, week)
weekly = weekly_result.get("weekly", []) if weekly_result.get("success") else []
return WeeklyAnalysisCombinedResponse(message=message, weekly=weekly)
except Exception as e:
raise HTTPException(status_code=400, detail=f"뢄석 μ‹€νŒ¨: {str(e)}")
Comment on lines +566 to +583
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Care weekly μ—”λ“œν¬μΈνŠΈμ—λ„ λ™μΌν•œ μ˜ˆμ™Έ 처리 이슈

λͺ¨λ“  뢄석 μ—”λ“œν¬μΈνŠΈμ—μ„œ μΌκ΄€λ˜κ²Œ μ˜ˆμ™Έ 처리λ₯Ό κ°œμ„ ν•΄μ•Ό ν•©λ‹ˆλ‹€.

Lines 365-376μ—μ„œ μ œμ•ˆν•œ μ˜ˆμ™Έ 처리 νŒ¨ν„΄μ„ 이 μ—”λ“œν¬μΈνŠΈμ—λ„ μ μš©ν•˜μ„Έμš”.

🧰 Tools
πŸͺ› Ruff (0.14.3)

571-571: 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)


582-582: Do not catch blind exception: Exception

(BLE001)


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

(B904)


583-583: Use explicit conversion flag

Replace with conversion flag

(RUF010)

πŸ€– Prompt for AI Agents
In app/main.py around lines 566 to 583, the endpoint uses a broad except that
returns a 400 with only the exception string; apply the same improved
exception-handling pattern used at lines 365-376: replace the broad except with
targeted handling (catch expected service/validation errors and raise
HTTPException with appropriate status and user-facing detail), and for any
unexpected Exception call logger.exception(...) to record the full stack trace
and then raise HTTPException(status_code=500, detail="μ„œλ²„ 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€") so
internal errors are logged but not leaked to clients.





@care_router.get("/notifications", response_model=NotificationListResponse)
async def get_care_notifications(care_username: str, db: Session = Depends(get_db)):
Expand Down Expand Up @@ -800,6 +835,22 @@ async def test_error(statusCode: int):
detail=f"Invalid statusCode: {statusCode}. Only 400 or 500 are allowed."
)


@test_router.post("/fcm/send")
async def test_fcm_send(
token: Optional[str] = None,
title: str = "Test Title",
body: str = "Test Body",
db: Session = Depends(get_db)
):
"""단일 ν† ν°μœΌλ‘œ FCM ν…ŒμŠ€νŠΈ 전솑 (SDKμ—μ„œ λ°œκΈ‰λ°›μ€ 토큰 μ‚¬μš©)"""
if not token:
raise HTTPException(status_code=400, detail="token is required")
from .services.fcm_service import FcmService
svc = FcmService(db)
result = svc.send_notification_to_tokens([token], title, body)
return {"success": True, "result": result}

# ---------------- router 등둝 ----------------
app.include_router(users_router)
app.include_router(care_router)
Expand Down
36 changes: 36 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,39 @@ class Notification(Base):
Index('idx_notification_voice', 'voice_id'),
Index('idx_notification_created', 'created_at'),
)


class WeeklyResult(Base):
"""μ£Όκ°„ OpenAI 쒅합뢄석 μΊμ‹œ"""
__tablename__ = "weekly_result"

weekly_result_id = Column(BigInteger, primary_key=True, autoincrement=True)
user_id = Column(BigInteger, ForeignKey("user.user_id", ondelete="CASCADE"), nullable=False)
latest_voice_composite_id = Column(BigInteger, ForeignKey("voice_composite.voice_composite_id", ondelete="CASCADE"), nullable=True)
message = Column(Text, nullable=False)
created_at = Column(DateTime, nullable=False, server_default=func.current_timestamp())
updated_at = Column(DateTime, nullable=False, server_default=func.current_timestamp(), onupdate=func.current_timestamp())

__table_args__ = (
Index('idx_weekly_user', 'user_id'),
Index('idx_weekly_latest_vc', 'latest_voice_composite_id'),
UniqueConstraint('user_id', name='uq_weekly_user'),
)


class FrequencyResult(Base):
"""μ›”κ°„ λΉˆλ„ OpenAI 쒅합뢄석 μΊμ‹œ"""
__tablename__ = "frequency_result"

frequency_result_id = Column(BigInteger, primary_key=True, autoincrement=True)
user_id = Column(BigInteger, ForeignKey("user.user_id", ondelete="CASCADE"), nullable=False)
latest_voice_composite_id = Column(BigInteger, ForeignKey("voice_composite.voice_composite_id", ondelete="CASCADE"), nullable=True)
message = Column(Text, nullable=False)
created_at = Column(DateTime, nullable=False, server_default=func.current_timestamp())
updated_at = Column(DateTime, nullable=False, server_default=func.current_timestamp(), onupdate=func.current_timestamp())

__table_args__ = (
Index('idx_freq_user', 'user_id'),
Index('idx_freq_latest_vc', 'latest_voice_composite_id'),
UniqueConstraint('user_id', name='uq_freq_user'),
)
Loading