From badc306e5b9ac8e5239e6c1b3f7ebd13958975e6 Mon Sep 17 00:00:00 2001 From: manNomi Date: Wed, 26 Aug 2026 16:17:39 +0900 Subject: [PATCH] =?UTF-8?q?chore:=20=EC=A1=B0=EC=A7=81=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=20=EC=9A=B4=EC=98=81=20=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 21 +++++++++++++ .github/CODEOWNERS | 1 + .github/pull_request_template.md | 15 +++++++++ .github/workflows/ci.yml | 39 +++++++++++++++++++++++ README.md | 34 ++++++++++++++++++-- SECURITY.md | 7 +++++ docs/REPOSITORY_OPERATIONS.md | 54 ++++++++++++++++++++++++++++++++ requirements-ci.txt | 6 ++++ tests/test_swap.py | 5 +-- 9 files changed, 177 insertions(+), 5 deletions(-) create mode 100644 .env.example create mode 100644 .github/CODEOWNERS create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/ci.yml create mode 100644 SECURITY.md create mode 100644 docs/REPOSITORY_OPERATIONS.md create mode 100644 requirements-ci.txt diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..afe8e62 --- /dev/null +++ b/.env.example @@ -0,0 +1,21 @@ +# 운영 DB는 저장소에 커밋하지 않는다. 배포 환경의 실제 경로를 지정한다. +ML_CATALOG_DB=./soundlog.db + +ML_ALPHA=0.3 +ML_AXIS_MIN_TRACKS=40 +ML_AXIS_MIN_SOURCES=10 +ML_PER_VIDEO_CAP=3 +ML_PER_ARTIST_CAP=2 +ML_POSITION_DECAY=0.01 +ML_MAX_SEQ_LEN=256 +ML_EMBED_MODEL=nlpai-lab/KURE-v1 +ML_EXCLUDE_CHANNELS= + +# 관광공사에서 발급받은 키를 배포 환경에서만 넣는다. +TOUR_API_KEY= +ML_TOUR_API=1 +ML_TOUR_FALLBACK=1 +ML_TOUR_DETAIL=1 +ML_TOUR_BUDGET=2.0 +ML_TOUR_DETAIL_BLOCKING=0 +ML_TOUR_OVERVIEW_QUERY=0 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..953ff2e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @KimJaegeol1 @manNomi diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..296b4fc --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ +## 변경 내용 + +- 변경 내용을 작성해주세요. + +## 확인 방법 + +- [ ] `python -m compileall -q .` +- [ ] `python tools/make_fixture_db.py` +- [ ] `python tests/test_swap.py` + +## 운영 영향 + +- [ ] `/recommend` 응답 계약 변경 여부를 `API.md`에 반영했습니다. +- [ ] 운영 DB 또는 임베딩 산출물 변경 여부를 설명했습니다. +- [ ] 환경 변수 변경 여부를 `.env.example`에 반영했습니다. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..578667a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +jobs: + logic-regression: + name: Logic regression + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + cache-dependency-path: requirements-ci.txt + + - name: Install light dependencies + run: python -m pip install -r requirements-ci.txt + + - name: Compile Python sources + run: python -m compileall -q -x '/\.venv/' . + + - name: Build fixture database + run: python tools/make_fixture_db.py + + - name: Run logic regression + run: python tests/test_swap.py diff --git a/README.md b/README.md index 756eb89..7b6cbf3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ # Soundlog 모델 서비스 +[![CI](https://github.com/SoundLogTeam/soundlog-ml/actions/workflows/ci.yml/badge.svg)](https://github.com/SoundLogTeam/soundlog-ml/actions/workflows/ci.yml) + +이 저장소는 `SoundLogTeam`이 관리하는 공식 ML 저장소다. 운영 API는 +[`SoundLogServer`](https://github.com/SoundLogTeam/SoundLogServer)가 담당하며 +이 서비스의 `/recommend` 응답을 사용한다. 저장소 운영 원칙과 배포 자산의 +경계는 [`docs/REPOSITORY_OPERATIONS.md`](docs/REPOSITORY_OPERATIONS.md)에 정리했다. + 좌표 + 온보딩(여행 상태·무드) → **추천 곡 10 + 배경 사진 1**을 돌려주는 FastAPI 서비스. 2026 관광데이터 활용 공모전 출품작의 ML 컴포넌트다. @@ -26,9 +33,9 @@ POST /recommend {x, y, state, mood} ``` 곡과 사진이 **같은 POI**를 본다 — 배경은 해수욕장인데 곡은 옆 음식점 -기준이 되는 어긋남을 없애기 위해서다. 관광공사 TourAPI는 더 이상 부르지 -않는다(대표 장소를 구조적으로 누락해서 자체 카탈로그로 피벗 — -`recommend.py` 상단 주석에 근거). +기준이 되는 어긋남을 없애기 위해서다. 대표 POI는 자체 카탈로그에서 먼저 +고른다. 카탈로그 범위를 벗어난 좌표에는 관광공사 TourAPI를 폴백으로 사용한다. +`recommend.py` 상단 주석에 설계 근거가 있다. ## 폴더 지도 @@ -52,6 +59,10 @@ POST /recommend {x, y, state, mood} ## 실행 +운영 실행에는 Git에 포함되지 않는 `soundlog.db`와 `data/photo_embs.npy`가 +필요하다. `.env.example`을 `.env`로 복사한 뒤 실제 자산 경로와 관광공사 키를 +설정한다. 운영 자산은 저장소에 커밋하지 않는다. + ```bat :: 로컬 (Windows) .venv\Scripts\activate @@ -69,6 +80,22 @@ nohup .venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000 >> server.log 2>&1 & 장소 축 지원표(4/8), POI 카탈로그·별칭·부모 수가 전부 찍힌다. 침묵하는 단계가 없으니, 로그에 없는 건 안 돈 것이다. +## 모델 없이 회귀 검사 + +CI는 대용량 KURE 모델과 운영 DB를 내려받지 않는다. 대신 저장소에 포함된 +fixture 생성기로 같은 스키마의 소형 DB를 만든 뒤 계약과 추천 로직을 검사한다. + +```bash +python -m venv .venv +. .venv/bin/activate +pip install -r requirements-ci.txt +python tools/make_fixture_db.py +python tests/test_swap.py +``` + +이 검사는 코드 계약을 검증한다. 실제 추천 품질과 운영 데이터 정합성은 +`TESTING.md`의 실전 경로로 별도 확인해야 한다. + ## 배포가 제대로 됐는지 30초 확인 ``` @@ -115,6 +142,7 @@ DIST_HALF`. POI 선택이 카탈로그로 옮겨가 요청당 호출 예산이 - **TESTING.md** — 기능별 테스트 명령·기대 출력·트러블슈팅 - **API.md** — `/recommend` 응답 계약. 백엔드에 이 파일만 전달하면 된다 +- **docs/REPOSITORY_OPERATIONS.md** — 조직 소유권, 운영 자산, 변경 절차 - 설계 근거는 각 파일 **상단 주석**에 그 자리에서 남겼다 (왜 TourAPI를 버렸나 → recommend.py, 왜 부족 축은 안 섞나 → soundlog_place.py, 왜 POI 선택에 임베딩을 안 쓰나 → poi_select.py, diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..a4ba01b --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,7 @@ +# 보안 문제 제보 + +비밀 키 노출과 인증 우회와 원격 코드 실행 가능성처럼 공개하면 위험한 문제는 +일반 이슈로 등록하지 않는다. GitHub 저장소의 Security 탭에서 비공개 보안 +제보를 사용하거나 SoundLogTeam 조직 관리자에게 직접 알린다. + +일반적인 기능 오류와 문서 오류는 GitHub 이슈로 등록할 수 있다. diff --git a/docs/REPOSITORY_OPERATIONS.md b/docs/REPOSITORY_OPERATIONS.md new file mode 100644 index 0000000..bdce92b --- /dev/null +++ b/docs/REPOSITORY_OPERATIONS.md @@ -0,0 +1,54 @@ +# SoundLog ML 저장소 운영 원칙 + +## 기준 저장소 + +공식 ML 저장소는 `SoundLogTeam/soundlog-ml`이다. 개인 계정의 기존 저장소는 +조직 이관 전 이력을 확인하는 용도로만 남긴다. 새 기능과 이슈와 배포 기준은 +조직 저장소에서 관리한다. + +운영 API의 기준 저장소는 `SoundLogTeam/SoundLogServer`다. 과거 API 개발 +스냅샷은 `SoundLogTeam/soundlog-api`에 이관 기록으로 보존한다. 두 API +저장소를 동시에 운영하지 않는다. + +## 권한과 변경 절차 + +`KimJaegeol1`은 이 저장소의 관리자다. 저장소 설정과 시크릿과 배포 환경을 +관리할 수 있다. 코드 변경은 작업 브랜치에서 시작하고 pull request의 CI를 +통과한 뒤 병합한다. `main`에 직접 푸시하지 않는다. + +## Git에 포함하지 않는 운영 자산 + +다음 파일은 크기가 크거나 배포 환경에서 생성되는 산출물이므로 Git에 넣지 +않는다. + +- `soundlog.db` 또는 `soundlog_serve.db` +- `data/photo_embs.npy` +- `.env` +- 모델 캐시와 서버 로그 + +운영 배포는 위 자산이 준비됐는지 별도로 확인해야 한다. `soundlog.db`는 현재 +추적된 CSV만으로 운영 데이터 전체를 재생성할 수 없다. 자산이 없는데도 배포가 +가능하다고 판단하면 안 된다. + +사진 임베딩은 `data/photos_ready.csv`와 행 순서가 일치해야 한다. 두 파일의 +행 수가 다르면 시작 과정에서 재계산될 수 있고 시작 시간이 크게 늘어난다. + +## 검증 단계 + +pull request의 CI는 fixture DB와 가짜 인코더를 사용한다. 이 검사는 다음 +항목을 확인한다. + +- Python 소스가 컴파일되는지 확인한다. +- 추천 데이터 스키마와 교정 규칙이 유지되는지 확인한다. +- `/recommend` 응답에 `backgroundImageUrl`이 항상 존재하는지 확인한다. +- POI가 없는 경우에도 음악 추천이 실패하지 않는지 확인한다. + +CI는 실제 KURE 모델의 추천 품질과 운영 DB의 내용과 외부 TourAPI 연결을 +증명하지 않는다. 배포 전에는 `TESTING.md`의 실제 모델 테스트와 HTTP 스모크를 +추가로 수행한다. + +## 비밀 정보 + +`.env.example`에는 변수 이름과 안전한 기본값만 둔다. 관광공사 키와 서버 +접속 정보와 토큰은 GitHub 환경 또는 서버 비밀 저장소에서 관리한다. 키가 +커밋되면 즉시 폐기하고 새 키를 발급한다. diff --git a/requirements-ci.txt b/requirements-ci.txt new file mode 100644 index 0000000..a49b709 --- /dev/null +++ b/requirements-ci.txt @@ -0,0 +1,6 @@ +# 대용량 모델을 받지 않는 CI 및 로직 회귀 검사 의존성 +fastapi>=0.115,<1 +python-dotenv>=1,<2 +numpy>=1.26,<3 +pandas>=2.2,<3 +pydantic>=2.9,<3 diff --git a/tests/test_swap.py b/tests/test_swap.py index 580afcf..c162fa3 100644 --- a/tests/test_swap.py +++ b/tests/test_swap.py @@ -19,9 +19,10 @@ import numpy as np +_ROOT = os.path.normpath( + os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir)) os.environ.setdefault("ML_CATALOG_DB", - os.path.join(os.path.dirname(os.path.abspath(__file__)), - "soundlog_serve.db")) + os.path.join(_ROOT, "soundlog_serve.db")) DIM = 1024