1) 핵심 기술 내역 요약
영역별 대표 기술
| 영역 | 핵심 기술 |
| AI·생성 | Gemini 감성 분석 · 매칭 근거 KO/EN 동시 생성 · JSON-only 프롬프트 + 코드펜스 스트리핑 |
| 검색·매칭 | 텍스트 임베딩 → pgvector 코사인 거리(<=>) top-k 검색 · Redis 결과 캐시(TTL 1h) |
| 데이터 모델링 | 1NF 감성 태그 행 분리 · 3NF 장단 마스터 분리 · JSONB 의도적 비정규화 · sort_order 순서 보존 |
| 쿼리 최적화 | joinedload 2종 + result.unique() N+1 제거 · ARRAY 연산자 → B-tree JOIN 전환 |
| 아키텍처 | Hexagonal(Ports & Adapters) + Clean Architecture + DDD · 프랙탈 네이밍 · Composition Root 단일화 |
| 도메인 설계 | frozen Value Object · 파생 프로퍼티(loop_unit_beats·is_commercial·primary_emotion) |
| 백엔드 런타임 | Python 3.13 · FastAPI · SQLAlchemy 2.0 async · psycopg 3 · Pydantic v2 · Alembic |
| 프론트엔드 | Next.js 16 App Router · React 19 · Tailwind 4 · WaveSurfer.js · 전역 PlayerContext |
| 데이터 파이프라인 | XML 파싱 · 파생 규칙(장단·감성·BPM·라이선스) · 중복 키 스킵 · 임베딩 이어받기 |
| 배포·운영 | Docker Compose 4서비스 · healthcheck 기반 기동 순서 · GitHub Actions → Pages(문서) |
수치로 보는 구현
| 항목 | 값 |
| 수집 음원 | 500건 (gugak_500.csv, 정식 CSV 파서 기준) |
| 임베딩 차원 | 1,536 (gugak_tracks.embedding) |
| MVP 물리 테이블 | 4개 |
| 장단 마스터 | 6종 / 루프 단위 4·6·10·12박 |
| 감성 태그 | 6종 (도메인 enum 기준) |
| 라이선스 유형 | 2종 (KOGL 제1·제2유형) |
| 매칭 결과 수 | top-3 |
| 캐시 TTL | 3,600초 |
| BPM 프리셋 마진 | ±20 (하한 60 / 상한 200) |
| 검색당 LLM 호출 | 최대 4회 (캐시 MISS 시) |
아키텍처 규칙 준수 현황
| 규칙 | 상태 |
| Domain에 FastAPI·SQLAlchemy import 없음 | ✅ |
| Router가 PgRepository를 직접 import하지 않음 | ✅ |
Interactor가 ORM·HTTPException을 import하지 않음 | ✅ |
| 프랙탈 네이밍 일치 | ✅ |
모든 Provider가 dependencies/에만 존재 | ✅ |
| ORM 파일 = 테이블 1개당 1파일 | ✅ |
joinedload 전략이 포트 인터페이스 밖으로 노출되지 않음 | ✅ |
| Python type hint 100% · async 일관 · Pydantic v2 | ✅ |
emotion_tags ARRAY 컬럼 없음 / loop_unit_beats 컬럼 없음 | ✅ |
match_logs.similarity_score Float | ✅ |
모든 트랙 조회에 joinedload + result.unique() | ✅ |
DISCOVER 응답에 preset_url 포함 | ✅ |
잔여 과제 (우선순위 순)
문서를 정직하게 유지하기 위해, 현재 코드에서 확인된 미완 지점을 모아 둡니다.
| # | 항목 | 위치 | 영향 | 우선순위 |
| 1 | 임베딩 차원 불일치 (1536 vs 768) | embed_pipeline.py ↔ 001_init | 임베딩 적재 실패 → 검색 불가 | 🔴 높음 |
| 2 | embed_text() 미구현 (TODO) | track_discover_pg_repository.py | DISCOVER 파이프라인 미완결 | 🔴 높음 |
| 3 | track_embeddings 테이블 참조 | embed_pipeline.py | 현 스키마에 없는 테이블 — 스크립트 갱신 필요 | 🔴 높음 |
| 4 | 결과 조립 미구현 (TODO) | _build_result() · _to_track_result() | 응답 조립 로직 | 🟠 중간 |
| 5 | 캐시 직렬화 | json.dumps(result.__dict__) | 중첩 dataclass 직렬화 실패 → asdict() 필요 | 🟠 중간 |
| 6 | 감성 enum 정합 | infer_emotions() ↔ EmotionTag | enum 밖 태그가 필터에 안 잡힘 | 🟠 중간 |
| 7 | 장단 폴백 편중 | infer_jangdan() | 대부분 중모리 → 장단·루프 필터 변별력 저하 | 🟠 중간 |
| 8 | 디렉터리 이중화 | backend/ ↔ backend/soundbridge/ | 같은 트리가 두 벌 — 패키지 루트 정리 필요 | 🟠 중간 |
| 9 | 라이선스 결측 낙관 처리 | normalize_license() | 결측 시 KOGL_1 → v1.1에서 보수 처리로 전환 | 🟡 낮음 |
| 10 | explain_match 순차 호출 | gemini_adapter.py | 캐시 MISS 시 지연 3배 → asyncio.gather | 🟡 낮음 |
| 11 | Interactor 파라미터명 claude | track_discover_interactor.py | Gemini 교체 후 이름만 잔존 | 🟡 낮음 |
| 12 | 프리셋 계약 테스트 부재 | 백엔드 ↔ 프론트 | 상수 불일치 시 조용히 0건 반환 | 🟡 낮음 |
| 13 | maximum-scale=1 | layout.tsx | 확대 차단 — 접근성 재검토 | 🟡 낮음 |
v1.1 이후 계획 기능
| 기능 | 관련 포트/테이블 |
| 이메일·Google 인증 | users · email_verifications · EmailPort(Resend) |
| 트랙 저장 | saved_tracks · SavedTrackRepository |
| 다운로드 이력 | download_logs · DownloadLogRepository |
| CUE 자동 추출 | GeminiPort.extract_cue_points() |
| 공연·체험 정보 | KopisPort — KOPIS · 한국관광공사 |
| HNSW 인덱스 | gugak_tracks.embedding (v1.2) |