From 5395d694ef3a791d1aee6a5505f85fc2b9758c3a Mon Sep 17 00:00:00 2001 From: Claude-51124 Date: Sat, 29 Nov 2025 14:15:29 +0900 Subject: [PATCH 1/4] =?UTF-8?q?docs:=20IR=20Deck=20API=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=20=ED=94=8C=EB=A1=9C=EC=9A=B0=20=EB=B0=8F=20=EC=A4=91?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=A0=90=20=EC=84=B9=EC=85=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...1128_ir_deck_valuation_backend_architecture.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/journey/troubleshooting/251128_ir_deck_valuation_backend_architecture.md b/journey/troubleshooting/251128_ir_deck_valuation_backend_architecture.md index 3e04ed2..31e31cb 100644 --- a/journey/troubleshooting/251128_ir_deck_valuation_backend_architecture.md +++ b/journey/troubleshooting/251128_ir_deck_valuation_backend_architecture.md @@ -155,6 +155,21 @@ CREATE TABLE ir_deck_feedback ( - 등급 분류를 위한 베이지안 분류기 구현 (향후) - 피드백 데이터가 충분히 쌓이면 학습 시작 +## 전체 플로우 + +1. **업로드**: `POST /rb8001/api/ir-deck/upload` → `document_id` 반환 +2. **평가**: `POST /rb8001/api/ir-deck/evaluate` → 동기식으로 완료까지 대기 → `evaluation_id`, `total_score`, `grade`, `page_evaluations` 반환 +3. **조회**: `GET /rb8001/api/ir-deck/evaluation/{id}` → DB 조회 (폴링용) + +**참고**: `/rb8001`는 Nginx 프록시 경로 (51123 → 51124:8001) + +## 중요한 점 + +- **동기식 평가**: `/evaluate` API는 평가 완료까지 대기 (페이지별 순차 실행, 타임아웃 주의) +- **중복 방지**: `force_reevaluate=False` 시 기존 평가 반환 +- **의존 서비스**: skill-rag-file (포트 8508), 내장 LLM +- **DB 테이블**: `ir_deck_evaluations`, `ir_deck_page_evaluations`, `ir_deck_feedback` + ## 교훈 - **기존 인프라 활용**: skill-rag-file의 업로드/검색 기능 재사용으로 개발 시간 단축 From 6f257b0308585c01cb719ba94455e303fa8d54bf Mon Sep 17 00:00:00 2001 From: Claude-51124 Date: Sat, 29 Nov 2025 17:08:10 +0900 Subject: [PATCH 2/4] =?UTF-8?q?docs:=20FastAPI=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EC=9B=90=EC=B9=99=EC=97=90=20=EB=A1=9C=EA=B9=85=20=EC=9B=90?= =?UTF-8?q?=EC=B9=99=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../300_architecture/311_FastAPI_구조_원칙.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/book/300_architecture/311_FastAPI_구조_원칙.md b/book/300_architecture/311_FastAPI_구조_원칙.md index fd5726c..d5efb2c 100644 --- a/book/300_architecture/311_FastAPI_구조_원칙.md +++ b/book/300_architecture/311_FastAPI_구조_원칙.md @@ -208,7 +208,20 @@ from app.services import coldmail_filter # ✅ 모듈 import # 긴급 수정: 2025-10-02, 사유: DB 장애 복구 ``` -## 11. 모범 사례 참고 +## 11. 로깅 원칙 + +**로그 레벨 사용 기준** (Python logging 공식 문서): +- **DEBUG**: 개발/디버깅용 상세 정보 (중간 과정, 내부 상태) +- **INFO**: 정상 동작 및 주요 이벤트 (시작/종료, 주요 단계) +- **WARNING**: 잠재적 문제 (예상치 못한 상황, 성능 저하 가능성) +- **ERROR**: 오류 발생 (기능 실패, 예외 처리) + +**규칙**: +- 시작/종료는 반드시 INFO 레벨 +- 중간 과정은 DEBUG 레벨 +- 프로덕션에서는 INFO 기본, DEBUG는 필요 시에만 활성화 + +## 12. 모범 사례 참고 본 문서는 FastAPI 커뮤니티의 다음 모범 사례를 반영하였습니다: From 3757467e46a56ad638ad3ef96452e231c1131406 Mon Sep 17 00:00:00 2001 From: Claude-51124 Date: Sat, 29 Nov 2025 17:12:21 +0900 Subject: [PATCH 3/4] =?UTF-8?q?docs:=20FastAPI=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EC=9B=90=EC=B9=99=20=EB=AC=B8=EC=84=9C=20=EC=A0=95=EB=A6=AC=20?= =?UTF-8?q?(=EC=A4=91=EB=B3=B5/=EB=B6=88=ED=95=84=EC=9A=94=20=ED=91=9C?= =?UTF-8?q?=ED=98=84=20=EC=A0=9C=EA=B1=B0,=20=EB=AA=85=ED=99=95=EC=84=B1?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../311_FastAPI_구조_원칙.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/book/300_architecture/311_FastAPI_구조_원칙.md b/book/300_architecture/311_FastAPI_구조_원칙.md index d5efb2c..086a094 100644 --- a/book/300_architecture/311_FastAPI_구조_원칙.md +++ b/book/300_architecture/311_FastAPI_구조_원칙.md @@ -19,7 +19,7 @@ | 계층 | 역할 | 금지 사항 | |------|------|----------| | **router/** | HTTP 요청/응답 처리만 | DB 직접 접근, 비즈니스 로직 | -| **services/llm/** | 비즈니스 로직 구현 | DB 직접 연결 (state를 통해서만) | +| **services/** | 비즈니스 로직 구현 | DB 직접 연결 (state를 통해서만) | | **state/** | DB CRUD만 | 비즈니스 로직 포함 | ## 2. 폴더 구조 규칙 @@ -43,7 +43,7 @@ ### 폴더 명명 규칙 - `router/` 또는 `api/`: HTTP 처리 -- `services/`: 도메인 로직 +- `services/`: 비즈니스 로직 - `state/` 또는 `repositories/`: Repository 패턴으로 CRUD 캡슐화 - `models/`: SQLAlchemy 등 ORM 모델 - `schemas/`: Pydantic 요청/응답 스키마 (models와 분리) @@ -62,7 +62,7 @@ ### state/ (Repository 패턴) - `database.py`: 통합 DB 접근 -- `{도메인}_repository.py`: 도메인별 CRUD 캡슐화 (예: user_repository.py는 User 모델 CRUD만) +- `{도메인}_repository.py`: 도메인별 CRUD 캡슐화 ### models/ - `{도메인}_model.py`: ORM 모델 (예: user_model.py, emotion_model.py) @@ -95,8 +95,8 @@ utils ### LangGraph 워크플로우 - **복잡한 다단계 처리**: LangGraph 적극 활용 -- **프로덕션 핵심 워크플로우**: PostgresSaver로 체크포인트를 두어 부분 재시도 가능하게 구현 (권장) -- **실험/경량 플로우**: stateless LangGraph도 허용하되, 추후 stateful 전환 여부를 문서나 주석으로 명시 +- **프로덕션 핵심 워크플로우**: PostgresSaver로 체크포인트 구현 (권장) +- **실험/경량 플로우**: stateless LangGraph 허용 ### router 계층 ```python @@ -152,7 +152,6 @@ async def save_emotion(data: dict): from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession async def get_session() -> AsyncSession: - # 의존성 주입으로 세션 제공 pass # state/database.py: 직접 연결 (간단한 경우) @@ -164,11 +163,11 @@ async def get_connection(): - ❌ 프로덕션 router/services에서 직접 asyncpg.connect() - ❌ 하드코딩된 DB URL - ❌ JSONB 저장 시 dict 직접 전달 (json.dumps() 필수) - - 마이그레이션/원샷 스크립트 등 서비스 외부 유틸에서는 직접 연결 허용하되, 프로덕션 요청 경로에서 재사용 금지 +- ❌ 프로덕션 요청 경로에서 직접 DB 연결 재사용 ## 7. 파일 크기 제한 -- **한 파일 최대 300줄 권장** (핵심 모듈은 가급적 유지) +- **한 파일 최대 300줄 권장** - 초과 시 기능별 분리 - 예: `services/email_integration.py` (500줄) → `email_send.py` + `email_fetch.py` @@ -204,8 +203,8 @@ from app.services import coldmail_filter # ✅ 모듈 import ### 예외 처리 시 ```python -# TODO: 계층 위반 - 리팩토링 필요 (issue #123) -# 긴급 수정: 2025-10-02, 사유: DB 장애 복구 +# TODO: 계층 위반 - 리팩토링 필요 +# 긴급 수정: 사유 명시 ``` ## 11. 로깅 원칙 From dd578a42f710f3d33e0e1afd568ad0fabd86b629 Mon Sep 17 00:00:00 2001 From: Claude-51124 Date: Sat, 29 Nov 2025 17:15:36 +0900 Subject: [PATCH 4/4] =?UTF-8?q?docs:=20FastAPI=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EC=9B=90=EC=B9=99=20=EB=AC=B8=EC=84=9C=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EA=B8=B4=20=EC=BD=94=EB=93=9C=20=EC=98=88=EC=8B=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20(=EC=9B=90=EC=B9=99=EB=A7=8C=20=EA=B0=84=EA=B2=B0?= =?UTF-8?q?=ED=95=98=EA=B2=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../311_FastAPI_구조_원칙.md | 76 +++---------------- 1 file changed, 12 insertions(+), 64 deletions(-) diff --git a/book/300_architecture/311_FastAPI_구조_원칙.md b/book/300_architecture/311_FastAPI_구조_원칙.md index 086a094..002fec8 100644 --- a/book/300_architecture/311_FastAPI_구조_원칙.md +++ b/book/300_architecture/311_FastAPI_구조_원칙.md @@ -98,46 +98,10 @@ utils - **프로덕션 핵심 워크플로우**: PostgresSaver로 체크포인트 구현 (권장) - **실험/경량 플로우**: stateless LangGraph 허용 -### router 계층 -```python -# ✅ 올바름 -async def handle_request(data: dict): - result = await some_service.process(data) # 서비스 호출 - return {"result": result} - -# ❌ 금지 -async def handle_request(data: dict): - conn = await asyncpg.connect(...) # DB 직접 접근 - result = complex_logic(data) # 비즈니스 로직 -``` - -### services 계층 -```python -# ✅ 올바름 -async def process_data(data: dict): - validated = validate(data) # 비즈니스 로직 - await save_to_db(validated) # state 호출 - return validated - -# ❌ 금지 -async def process_data(data: dict): - conn = await asyncpg.connect(...) # 직접 DB 연결 -``` - -### state 계층 -```python -# ✅ 올바름 -async def save_emotion(data: dict): - conn = await asyncpg.connect(METRICS_DB_URL) - await conn.execute("INSERT INTO ...") # DB만 - await conn.close() - -# ❌ 금지 -async def save_emotion(data: dict): - if data['emotion'] == 'anger': # 비즈니스 로직 - data = transform(data) - await conn.execute(...) -``` +### 계층별 원칙 +- **router**: 서비스 호출만, DB/비즈니스 로직 금지 +- **services**: 비즈니스 로직 구현, state를 통한 DB 접근 +- **state**: DB CRUD만, 비즈니스 로직 금지 ## 6. DB 접근 규칙 @@ -147,17 +111,8 @@ async def save_emotion(data: dict): - `TEST_DATABASE_URL`: 테스트 DB ### 연결 방식 -```python -# db/database.py: DB 세션 중앙 관리 (권장) -from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession - -async def get_session() -> AsyncSession: - pass - -# state/database.py: 직접 연결 (간단한 경우) -async def get_connection(): - return await asyncpg.connect(os.getenv("DATABASE_URL")) -``` +- **권장**: `db/database.py`에서 DB 세션 중앙 관리 (의존성 주입) +- **간단한 경우**: `state/database.py`에서 직접 연결 ### 금지 사항 - ❌ 프로덕션 router/services에서 직접 asyncpg.connect() @@ -169,21 +124,16 @@ async def get_connection(): - **한 파일 최대 300줄 권장** - 초과 시 기능별 분리 -- 예: `services/email_integration.py` (500줄) → `email_send.py` + `email_fetch.py` ## 8. Import 규칙 ### 금지 -```python -from app.state.database import * # ❌ wildcard -from ..router.slack_handler import x # ❌ 순환 가능성 -``` +- ❌ wildcard import (`from module import *`) +- ❌ 상대 import로 순환 참조 가능성 (`from ..router import x`) ### 권장 -```python -from app.state.database import save_emotion_reading # ✅ 명시적 -from app.services import coldmail_filter # ✅ 모듈 import -``` +- ✅ 명시적 import (`from app.state.database import save_emotion_reading`) +- ✅ 모듈 import (`from app.services import coldmail_filter`) ## 9. 체크리스트 @@ -202,10 +152,8 @@ from app.services import coldmail_filter # ✅ 모듈 import 3. **성능 최적화**: 충분한 근거 필요 ### 예외 처리 시 -```python -# TODO: 계층 위반 - 리팩토링 필요 -# 긴급 수정: 사유 명시 -``` +- TODO 주석으로 계층 위반 표시 +- 긴급 수정 사유 명시 ## 11. 로깅 원칙