# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## 0. 작업 규칙 (최우선)

1. **코드 주석**: 모든 코드에 주석을 작성하고, 주석은 반드시 한국어로 쓴다.
2. **DB 데이터 변경 (DELETE / UPDATE 등)**: 먼저 SELECT로 대상을 확인한 뒤 아래 두 쿼리를 함께 안내한다.
   1. 확인용 SELECT 쿼리
   2. 변경용 쿼리 (실제 DELETE / UPDATE)
   - 새 번역 키는 DB `Language` 테이블에 행을 추가해야 하므로 이 규칙을 똑같이 따른다.
3. **기존 소스 스타일에 맞춘다**: 언어(JavaScript, TypeScript 금지), 페이지 구조, 함수/클래스명, CSS 클래스명, 폴더 구성은 기존 방식을 그대로 쓴다. 새 패턴이나 라이브러리를 임의로 도입하지 않는다.
4. **배포 흐름**

```
[A서버] /var/www/html/bwave (dev)
        ↓ 파일 수정 → commit → push
   GitHub (origin/dev)
        ↓ fetch → merge
[A서버] /var/www/html/bwave-main-sync (main)
        ↓ push origin main
   GitHub (origin/main)
        ↓ Beyond Compare로 비교
[B서버] /var/www/html/bwave (라이브) ← 여기로 수동 반영
```

- "배포해달라"고 하면 **Beyond Compare 이전 단계까지만** 한다.
  1. `/var/www/html/bwave`(dev)에서 commit → `push origin dev`
  2. `/var/www/html/bwave-main-sync`(main)에서 `fetch origin` → `merge origin/dev`
  3. `push origin main`. 거절되면 `merge origin/main`으로 먼저 합친 뒤 다시 push한다. **충돌이 나면 멈추고 사용자에게 알린다.**
- 커밋 메시지는 사용자가 정한 문구를 쓴다(임의로 작성하지 않는다).
- B서버(라이브) 반영은 사용자가 직접 한다. Claude가 대신하지 않는다.
- `tests/`, `docs/`, 목업 HTML(`app.html`, `web.html`)과 루트 이미지(`1.png`, `2.png`)는 배포 대상이 아니다.

## 프로젝트 개요

비웨이브(BWAVE) **마음결**의 생체신호 리포트 생성 백엔드다. 뇌파(EEG)·맥파(PPG/HRV) 측정 파일을 받아 분석하고 PDF/PNG 리포트를 만들어 알림톡·메일로 보낸다. PHP 웹 애플리케이션과 Python 분석 파이프라인으로 이루어져 있다.

- 빌드 시스템이 없다. PHP는 문서 루트 `/var/www/html/bwave/`에서 바로 서빙되고, Python은 `python/venv/`로 실행된다.
- `index.php`는 JS로 **라이브 URL** `https://mwbwave.com/survey.php`로 이동시키기만 한다.
- 이 작업 디렉터리는 **A서버 개발(dev)** 기준이다.

## 데이터 모델: 1세대 / 2세대가 공존한다

| 구분 | 테이블 | 사용처 |
|---|---|---|
| 1세대 (기존) | `User`, `MHSD`/`MHSA`(+`_en`), `BWave`, `LightData` 등 | `survey.php`, `onestop/`, `manager/main.php`, `manager/list.php`, `ajax/main/mainUpload.php` 등 |
| 2세대 (basic, 개발 중) | `Member`, `MHSD_NEW`/`MHSA_NEW`(+`_en_NEW`), `BWave_NEW`(`member_sn`), `LightData_NEW`, `LogSendData_NEW`, `LogDataQuality_NEW`, `Admin`, `SurveyLink*` | `ajax/basic/`, `ajax/member/`, `ajax/report/`, `common/*New.php`, `*New.php` 엔드포인트 |

- `MHSD_NEW`/`MHSA_NEW`의 `usn` 컬럼에는 `Member.sn`이 들어간다.
- `common/function.php`의 `getNextBID()`는 `BWave`와 `BWave_NEW`를 **둘 다** 조회해 리포트 번호를 정한다. 따라서 `_NEW` 테이블이 없는 DB에 반영하면 1세대 흐름의 번호 발급도 실패한다(라이브 반영 시 테이블을 먼저 만들 것).
- 2세대 작업 현황과 설계는 `docs/changes/`, `docs/superpowers/{specs,plans}/`, `docs/api/`(앱 연동 계약)에 있다. 작업 전에 `docs/changes/` 최신 현황 문서를 먼저 읽는다.

## 아키텍처 (측정 → 리포트)

1. **업로드 진입점**
   - 1세대: `ajax/main/mainUpload.php`(관리자 웹), `onestop/upload_onestop.php`(원스톱), `api/uploadLight.php`(라이트 기기)
   - 2세대: `ajax/basic/check.php`(앱 품질 검사), `ajax/basic/upload.php`(앱 생성+자동 발송), `ajax/main/mainUploadNew.php`(웹 수동 생성). 공통 로직은 `common/reportNew.php`에 있다.
   - 측정 파일은 `storage/`(gitignore) 아래 기기별 경로(`bwavedata/`, `eeglight/`, `linkband/`, `laxtha/`)에 저장되고, 결과는 `storage/report/` 쪽에 쌓인다.
2. **품질 검사**: `run_python.sh dataQuality.py <파일> <device> <msn> <결과경로>`. 호출부는 `api/uploadLight.php`, `onestop/check_onestop.php`, `common/reportNew.php`.
3. **리포트 생성**: `run_python.sh main.py` 뒤에 인자 **19개**를 **순서대로** 넘긴다. `python/main.py`의 `sys.argv[1..19]`와 정확히 일치해야 한다.
   `파일, device, number(bid), date, name, sex, age, k_dep, k_anx, 언어, 결과경로, PDF비번여부, 생년월일, eeg_only, ppg_only, ebsi, msn, ai_use, mdd_use`
   - 호출부: `ajax/main/mainUpload.php`(2곳), `onestop/upload_onestop.php`(2곳), `common/reportNew.php`(`PY_RUNNER` 사용). `tests/unit/reportNew_test.php`가 인자 순서를 검증한다.
   - 인자를 추가하거나 순서를 바꿀 때는 위 호출부 전부와 `main.py`, 테스트를 함께 고친다.
4. **Python 파이프라인** (`python/`): `main.py`가 전체를 조율한다.
   - `EEG.py` / `PPG.py`: 신호 분석
   - `PNG_REPORT.py`, `PNG_EBSI.py`, `PNG_classifier.py`, `PNG_MDD_REPORT.py`, `PLOT.py`: 이미지 생성
   - `PDF_ALL.py`: PDF 합성
   - `classifier.py`: `model/stage1_*.pkl` + `stage2_*.pkl` 2단계 분류기
   - `MDD_classifier.py` + `mdd_*.py`: 우울증(MDD) 페이지. `model/mdd/`, torch 사용, `mdd_use=1`일 때만 동작
   - `directory.py`: 경로 정의. 정상규준 데이터는 `normative/`, 레이아웃은 `base_file_new/`, `base_file_en_new/`, 기관 로고는 `company_img/`
   - 그 외 `main_two_files.py`, `main_viewer.py`가 있다.
5. **발송**: 1세대는 `plugin/sendAlimTalk.php`, `plugin/sendMail.php`. 2세대는 `plugin/send_new.php`, `send_helper_new.php`, `alimTalk_new.php`, `mail_new.php`와 `common/sendNew.php`, `common/alimtalkTemplatesNew.php`를 쓴다. `Member.sentType`이 0이면 이메일, 1이면 카카오톡(실패하면 대체문자)이다.

## 디렉터리

- `common/`: `setting.php`(부트스트랩), `function.php`(헬퍼), `msgcode.php`(응답 코드), `db.php`(gitignore), `reportNew.php`/`sendNew.php`/`surveyNew.php`/`alimtalkTemplatesNew.php`(2세대 공용 로직, 앞부분은 순수 함수)
- `ajax/`
  - `login/`, `main/`, `list/`, `group/`, `statistics/`, `survey/`: 1세대 관리자 웹과 설문
  - `basic/`: 2세대 앱 API(`login`, `member_save`, `survey*`, `check`, `upload`, `checkRecent`, `link_survey`)와 웹 공개 설문(`web_signup`, `web_guardian_verify`, `web_survey*`). 응답은 `printApiResponse()`
  - `member/`: 회원·그룹·사전설문 링크 관리
  - `report/sendReport.php`: 리포트 재발송(같은 리포트는 기관 계정 2회까지)
- `api/`: `uploadLight.php`, `checkManager.php`
- `onestop/`: 설문→측정→업로드를 한 흐름으로 묶은 원스톱(국문 `survey_onestop_new.php`, 영문 `survey_onestop_en_new.php`)
- `manager/`: `index.php`(HOME), `main.php`, `main_multi.php`(리포트 생성), `list.php`(리포트 목록)
- `manager.php`: 로그인 화면. `member.php`: 회원 화면
- `group/`, `statistics/`: 그룹관리, 통계 화면
- `include/`: `head.php`, `header.php`, `footer.php`, `sidebar.php`
- `htmldata/`: 약관·개인정보·민감정보·마케팅 동의 HTML(`mini_*` = 마음결 미니)
- `survey.php`, `survey_en.php`: 1세대 설문 본체(단일 파일로 큼, 각각 약 1,300줄과 900줄)
- `login/`: 별도 로그인 페이지
- `semina/`: 별도 정적 사이트. 자체 `CLAUDE.md`가 있으므로 그 파일을 따른다.
- `tests/`: PHP CLI 테스트(배포하지 않음)
- `docs/`: 설계, 계획, 변경 내역, 앱 API 계약

## 환경 분기 (개발 vs 라이브)

`common/setting.php`에서 손으로 바꾸는 값이 있다. 라이브 반영 시 반드시 확인한다.
- 세션 쿠키: 개발은 `secure=false`, `samesite=Lax`이고 **라이브는 `secure=true`, `samesite=None`**이다.
- 에러 표시: `SERVER_NAME`이 `localhost`이거나 `dev.`로 시작하면 출력하고, 운영에서는 억제한다.
- `sslRedirect()`는 정의만 되어 있고 호출은 주석 처리돼 있다.
- `STORAGE_URL`과 `BASE_URL`은 IP(`http://211.188.52.229/`)로 하드코딩돼 있고, `STORAGE_PATH`와 `BASE_PATH`는 `/var/www/html/bwave/`다.
- `setting.php`는 **파일째 덮어쓰지 않는다.** A서버와 B서버 값이 다르므로 바뀐 블록만 손으로 병합한다.

## 설정·시크릿

- gitignore 대상(서버에만 있음): `common/db.php`, `ajax/survey/sms.php`, `plugin/alimTalk.php`, `plugin/mail.php`, `plugin/surveyAlimTalk.php`, `storage/`, `python/venv/`, `python/model/`, `python/normative/`, `python/base_file_*`, `python/font/`, `python/en/`, `python/company_img/`
- 주의: `common/setting.php`에 `ALIGO_APIKEY`, `ALIGO_TOKEN`, `ALIGO_SENDERKEY`, `DIRECTSEND_KEY` 등 발송 키가 평문으로 들어 있다. 출력이나 로그에 노출하지 말 것.
- `PY_RUNNER` 상수(기본 `python/run_python.sh`)는 테스트에서 가짜 실행기로 바꿀 수 있도록 `!defined` 가드로 정의돼 있다. 2세대 코드는 이 상수로 python을 호출한다.

## Python 실행·의존성

- 실행: `python/run_python.sh <script.py> <args...>`. 이 래퍼가 `python/`로 cd하고 numba·matplotlib 캐시 환경변수를 설정한 뒤 venv 파이썬으로 실행한다. 경로는 `/var/www/html/bwave/`로 하드코딩돼 있다.
- 의존성: `python/requirements.txt`에 버전이 고정돼 있다(mne 1.1.1, numpy 1.22.4, scipy 1.10.1, numba 0.57.1, scikit-learn 1.3.2, torch 2.1.2, timm 0.9.16, heartpy, hrv-analysis 등). 조합이 민감하므로 임의로 업그레이드하지 않는다.
- pytest가 설치돼 있지만 Python 테스트 파일은 없다.

## 테스트 (PHP CLI)

```
php tests/unit/reportNew_test.php
php tests/unit/sendNew_test.php
php tests/unit/surveyNew_test.php
php tests/integration/report_send_test.php
php tests/integration/basic_flow_test.php
php tests/integration/web_flow_test.php
php tests/integration/web_survey_test.php
```

- `tests/bootstrap.php`가 웹 요청 환경을 흉내 내고 `PY_RUNNER=tests/fake_python.sh`, `SEND_DRY_RUN`, `TEST_MODE`를 켠다. 실제 python 실행과 외부 발송은 일어나지 않는다.
- 엔드포인트 단건 실행: `php tests/harness.php <경로> '<POST JSON>' '<SESSION JSON>' '<FILES JSON>'`
- **통합 테스트는 개발 DB에 실제로 쓴다**(이름 `플로우테스트`인 데이터가 남는다). 실행 전 사용자 승인을 받는다. 정리는 `php tests/tools/cleanup_flow_test_data.php check`로 확인한 뒤 `run`으로 한다.

## 기술 스택 (반드시 준수)

빌드 도구, 트랜스파일러, 프레임워크가 **없다.** TypeScript, React/Vue, npm/webpack/vite, SCSS, ES 모듈 번들 등을 도입하지 않는다.

- 서버: **PHP 8.x**, 프레임워크 없음, 파일 기반 라우팅(URL = 파일 경로)
- DB: PDO(`common/function.php` 래퍼 경유)
- 프론트: **jQuery 3.6.0**(`assets/js/jquery-3.6.0.min.js`)과 바닐라 JS. 비동기는 `$.ajax`를 쓴다(`fetch`/`axios` 아님).
- 날짜 UI: `daterangepicker`와 `moment`(`assets/plugin/`)
- CSS: 일반 `.css`(`assets/css/`), 전처리기 없음
- 템플릿: PHP 안에 HTML을 직접 쓰고 `include/` 공용 레이아웃을 쓴다.

Claude 디자인 HTML(`web.html`, `app.html` 같은 목업)을 받으면 **구조와 기능만** 가져오고, 색상과 테마는 현재 사이트의 파란 테마와 공통 클래스를 유지한다. 정적 `.html`로 두지 말고 PHP 페이지로 편입하며, 데이터는 `ajax/<도메인>/` 엔드포인트와 `$.ajax`로 연결한다.

## PHP 작성 규칙

- 엔드포인트 첫 줄: `require_once($_SERVER['DOCUMENT_ROOT'] . '/common/setting.php');`. 이 한 줄로 상수, 세션, `function.php`, `db.php`, `msgcode.php`가 모두 로드된다. 2세대 공용 로직은 그 뒤에 `common/reportNew.php` 등을 추가로 require한다.
- **DB는 반드시 `function.php` 래퍼를 쓴다.** 조회는 `selectQuery()`, `getData()`, `getSimpleSelect()`, 변경은 `executeQuery()`다. `new PDO`나 raw 쿼리는 금지한다. 값은 `:name` 플레이스홀더와 `$params` 배열로만 바인딩하고, 변수를 SQL 문자열에 넣지 않는다. 동적 컬럼·정렬은 화이트리스트로 매핑하거나 `quoteIdent()`를 쓴다.
- 입력: `requireData('x')`(없으면 `-1` 응답 후 exit, 기본 trim)와 `requireArray()`를 쓴다. 선택값은 `$_POST['x'] ?? ''`에 `trim()`이나 `(int)`를 적용한다.
- 응답: 직접 `echo json_encode`하지 말고 헬퍼를 쓴다.
  - 웹 AJAX: `printResponse(code, msg='', isExit=true, data=[], lang=null)` → `{code, msg, data?}`. lang을 생략하면 `getLang()`으로 정한다.
  - 앱 API(`ajax/basic/`, 베이직2): `printApiResponse()` → data가 **최상위로 병합**된다.
- 코드값은 `msgcode::` 상수를 쓴다. 주의: `MSGCODE_OK`는 **1**이고 `MSGCODE_DB_OK`가 **1000**이다. 프론트는 `datas.code == 1000`으로 성공을 판단하므로 웹 성공 응답에는 `MSGCODE_DB_OK`를 쓴다.
- 권한: `$_SESSION['MSN']`이 기관 번호다. 비웨이브가 아니면(`$MSN != '1'`) `b.msn = :msn` 조건을 추가한다.
- `exec()` 인자: 1세대 호출부는 인자를 이스케이프 없이 이어 붙인다. 새 코드는 `common/reportNew.php`처럼 **`escapeshellarg()`**를 쓴다.
- 들여쓰기: 파일마다 탭과 스페이스가 섞여 있다. **수정하는 파일의 기존 방식**을 따른다. 타입 힌트는 `function.php`처럼 쓸 수 있는 곳에만 쓴다.

## 다국어 (ko / en)

- 화면 문구는 `textChange('키')`로 출력한다. `loadTexts()`가 DB `Language` 테이블(`textKey`, `ko`, `en`)을 읽고, 키가 없으면 키 문자열이 그대로 나온다.
- 언어는 `getLang()`/`setLang()`이 정한다(세션 → 쿠키 → 기본 `ko`). `include/header.php`가 `?lang=` 파라미터를 처리하고 `loadTexts()`를 호출한다.
- `msgcode`는 `getCodeMsg()`(국문)와 `getCodeMsgEn()`(영문)이 각각 있으므로 새 코드를 추가할 때 둘 다 채운다.
- 새 키는 `Language` 테이블에 INSERT해야 하므로 0번 규칙 2(DB 변경)를 따른다.

## JS 작성 규칙

- jQuery를 우선 쓰고 바닐라를 섞어도 된다(`common.js`는 `addEventListener`와 `data-*` 위임을 쓴다). 새 라이브러리는 추가하지 않는다.
- `$.ajax` 기본 형태:
  ```js
  $.ajax({
  	type: 'post',
  	url: '/ajax/list/getList.php',
  	data: param,
  	dataType: 'json',
  	error: function () { alert("<?= textChange('Request_processing_error') ?>"); },
  	success: function (datas) { if (datas.code == 1000) { /* 성공 */ } else { alert(datas.msg); } }
  });
  ```
  (`xhrFields: { withCredentials: true }`는 `manager.php`와 `header.php`에서만 쓴다. 같은 도메인 요청에는 필요 없다.)
- 디버그용 `console.log`는 새 코드에 남기지 않는다.
- 전역 함수를 HTML `onclick`에서 호출하는 방식(예: `closePopup(this)`)이 기존 관례다.

## 프론트엔드 작업 가이드

### 페이지 뼈대

```php
<?php
require_once($_SERVER['DOCUMENT_ROOT'] . '/common/setting.php');
require_once($_SERVER['DOCUMENT_ROOT'] . '/include/head.php');
require_once($_SERVER['DOCUMENT_ROOT'] . '/include/header.php');

$MSN = $_SESSION["MSN"];
?>
<style>/* 이 페이지 전용 스타일 */</style>

<div class="contents_inner mobile_fix_contents">
	<div class="contents_section bg_white m_first">
		<div class="title_box title_dp"><h2 class="form_title"><?= textChange('키') ?></h2></div>
	</div>
</div>

<script>
const STORAGE_URL = '<?php echo STORAGE_URL; ?>';  // PHP 값 → JS 주입은 이 방식
</script>
<?php require_once($_SERVER['DOCUMENT_ROOT'] . '/include/footer.php'); ?>
```

- `include/header.php`는 스스로 `setting.php`를 require하고, `?lang=` 처리, `loadTexts()`, 로그인 세션 검사(실패 시 `/manager.php`로 이동)를 **대신 처리한다**. 페이지마다 다시 구현하지 않는다.
- `head.php`는 `<body><div id="wrap">`까지 열고, `footer.php`는 `</section>`부터 닫은 뒤 `common.js`를 로드한다. 태그 균형이 맞아야 하므로 둘 다 include한다.
- 참고 페이지: `manager/list.php`(목록, 검색, 페이징, 팝업의 전형)와 짝이 되는 `ajax/list/getList.php`

### CSS

- 디자인 토큰은 `assets/css/base.css`의 `:root`에 있다. `base.css`는 `common.css` 상단의 `@import url(base.css)`로 로드되므로 `head.php`에 따로 추가하지 않는다.
- 색상 변수: `--color-black`, `--color-grey-00~05`, `--color-bg`, `--color-white`/`-01`, `--color-green`(`#0077FA`, 이름과 달리 **파랑**), `--color-green-02`(`#BFDB17`), `--color-red`, `--color-blue`. hex를 하드코딩하지 말고 변수를 쓴다.
- **`1rem = 10px`**(`--pc-default: 62.5%`)이다. 태블릿은 55.5%, 모바일은 50%다.
- 로드 순서: `daterangepicker.css` → `reset.css` → `font.css` → `common.css` → `main.css` → `sub.css`
- 새 스타일은 `sub.css`(페이지별) 또는 `common.css`(공통)에 넣는다. `main.css`는 사실상 빈 파일이므로 쓰지 않는다. 한 번만 쓰는 스타일은 페이지 안 `<style>`에 둔다.
- 클래스명은 snake_case로 쓴다: `basic_btn`, `basic_form`, `table_form`, `card_wrap`, `popup`/`pop_wrap`, `label_type`/`type_btn`, `contents_inner`, `contents_section`, `no_data`
- 외부 CDN은 `head.php`의 Chart.js 4.5.0(jsdelivr) 하나뿐이다. 더 추가하지 않는다.

### 공용 JS (다시 구현하지 않는다)

| 함수 | 파일 | 용도 |
|---|---|---|
| `buildPagination(current, totalPages, blockSize=10)` | `assets/js/common.js` | 페이징 HTML 반환(`<li data-page>`) |
| `closePopup(btn)` | `assets/js/common.js` | 팝업 닫기 |
| `changeName(evt)` / `changeID(evt)` | `assets/js/common.js` | 이름(한글만) / 아이디(영숫자만) 입력 필터 |
| `isEmpty(value)` | `assets/js/common.js` | 빈값 체크 |
| `applySingleDate(autoInput=true)` | `assets/js/init.js` | `.date_input`에 daterangepicker 적용 |

- **팝업**: 여는 버튼에 `data-popup="<팝업 id>"`만 달면 `common.js`의 document 위임이 처리한다(동적으로 만든 버튼도 동작). 닫기는 `onclick="closePopup(this)"`이고, 배경 클릭으로 닫기도 이미 구현돼 있다.
- **탭**: `.label_type` 안의 `.type_btn`을 클릭하면 `.on`이 자동으로 토글된다.
- **목록 렌더**: HTML 문자열을 조립해 `.html()`로 넣는다. 템플릿 엔진은 없다.
- **페이징**: `buildPagination()` 결과를 `#pagerBlock`에 넣고, `data-page` 위임 클릭으로 다시 조회한다.

### 데이터 흐름

```
페이지 .php ──$.ajax(post)──▶ ajax/<도메인>/xxx.php ──▶ DB
     ▲                                                │
     └──── {code, msg, data} ◀── printResponse() ─────┘
```

`data`는 보통 `{result, page, totalCount, totalPages}` 형태이고, 성공은 `code == 1000`이다.
자주 쓰는 코드: `-1` 파라미터 없음 · `-9910` DB 에러 · `-3000` 로그인 실패/필요 · `-2004`/`-2005`/`-2006` 전체/뇌파/맥파 품질 실패 · `-2009` 리포트 생성 실패 · `-2999` python 입력값 문제 · `-4004`/`-4005` 메일/SMS 발송 실패 · `-4006` 발송 횟수 초과 · `-8000`/`-8001` 사전설문 링크 없음/만료 · `-8010` 보호자 인증 필요

### 화면 ↔ 파일 ↔ 엔드포인트 (현재 작업 트리 기준)

| 메뉴 | 페이지 | 엔드포인트 |
|---|---|---|
| HOME | `manager/index.php` | — |
| 설문지 | `survey.php` / `survey_en.php` | `ajax/survey/*` |
| 리포트 생성 | `manager/main.php`, `main_multi.php` | `ajax/main/mainUpload.php`, `selectUser.php`, `selectData.php` (2세대용 `mainUploadNew.php`, `selectMember.php`, `selectDataNew.php` 준비됨) |
| 리포트 목록 | `manager/list.php` | `ajax/list/getList.php`, `modifySend.php`, `modifyUserInfo.php`, `exportList.php` (2세대용 `getListNew.php`, `modifyMemberInfo.php`, `ajax/report/sendReport.php` 준비됨) |
| 통계 | `statistics/statistics_score.php`, `_personal`, `_sub`, `_active`, `_external`, `personal_*` | `ajax/statistics/*` |
| 그룹관리 | `group/management.php`, `management_desc.php` | `ajax/group/*` |
| 회원 관리 | `member.php` | `ajax/member/*` |
| 로그인 | `manager.php` | `ajax/login/login.php`, `logout.php` |
| 원스톱 | `onestop/survey_onestop_new.php`, `_en_new.php` | `onestop/check_onestop.php`, `upload_onestop.php`, `login_onestop.php` |
| 앱(베이직) | (앱) | `ajax/basic/*` — 계약은 `docs/api/` |

### 국문/영문 쌍 파일: 한쪽만 고치지 않는다

`survey.php` ↔ `survey_en.php` / `onestop/survey_onestop_new.php` ↔ `survey_onestop_en_new.php` / `ajax/survey/survey.php` ↔ `survey_en.php`(+`surveyKid.php`) / `ajax/survey/select_survey.php` ↔ `select_survey_en.php` / `ajax/basic/survey.php` ↔ `survey_en.php`(+`surveyKid.php`) / `ajax/basic/web_survey.php` ↔ `web_survey_en.php`(+`web_survey_kid.php`) / `htmldata/` 동의 HTML / `msgcode` 국·영문 메시지 / `Language` 테이블의 `ko`·`en`

### 권한 분기

`$_SESSION['MSN']`이 기관 번호다. **`MSN == 1`이 비웨이브(전사 권한)**이고, 그 외 기관은 자기 데이터만 본다. 서버는 `if ($MSN != '1') { $where[] = "b.msn = :msn"; }` 패턴을 쓰고, 프론트는 `<?php echo $MSN; ?>`(예: `bwaveMsn !== '1'`)로 받아 버튼 노출을 분기한다.

## 주의사항

- 1세대 `exec()` 호출부는 이름 같은 사용자 입력을 그대로 셸 문자열에 이어 붙인다. 새 인자를 추가할 때는 셸 메타문자에 주의하고, 새 코드는 `escapeshellarg()`를 쓴다.
- 서버 절대경로(`/var/www/html/bwave/`)와 IP URL이 코드 곳곳에 하드코딩돼 있어 로컬 실행은 사실상 불가능하다. 화면은 개발 서버에서 확인하고, PHP 흐름은 `tests/`의 CLI 테스트로 확인한다.
- 루트의 `phpinfo.php`는 서버 정보를 노출하는 파일이다. 라이브 반영 대상에서 빼는 것을 권장한다.
- 라이브(B서버) 반영 시 `common/setting.php`, `common/msgcode.php`, `common/function.php`는 덮어쓰지 말고 바뀐 부분만 병합한다. `_NEW` 테이블은 라이브 DB에 먼저 만들어 둔다.


# 회사 배경지식 (BWAVE)

## 회사
- **비웨이브 (BWAVE Inc.)** — 디지털 정신건강 케어 기업
- 미션: "정신과 마음이 건강한 사회를 만드는 기업"
- ISO 9001 인증 (2025)

## 핵심 서비스: 마음결 (Maumgyeol)
뇌파(EEG) + 심박변이도(HRV) 생체신호를 AI로 분석하는 디지털 정신건강 케어 플랫폼.

### 마음결 미니 (출시)
- **플랫폼**: Android / iOS 앱 (스토어 배포)
- **측정**: 스마트폰 **카메라로 PPG**(광용적맥파) 측정 → 심박/심박변이도 기반 셀프케어 모니터링
- **타겟**: 일반 개인 (B2C, 셀프케어)

### 마음결 베이직 (출시)
- **플랫폼**: Android **태블릿** 앱
- **측정**: **2채널(2ch) 뇌파 측정 기기**와 연결해 생체신호 기반 정신건강 평가
- **타겟**: B2B (기관·전문가)
- **판매 모델**:
  - 회사가 **태블릿 + 측정 기기를 한 세트로 세팅**해 판매
  - **기기 첫 구매료**(초기 구매) 수령
  - 이후 **월 구독료**(소프트웨어 구독) 수령

### 마음결 기어 (예정)
- 고급 평가

### 마음결 프로 (예정)
- 우울증·조현병·PTSD 임상 진단

## 기술
- 생체신호 분석: 뇌파(EEG), 심박변이도(HRV)
- 사건관련전위(ERP) 분석, 안정상태 뇌파(resting-state EEG)
- 머신러닝/딥러닝 AI 알고리즘, 실시간 정신건강 바이오마커

## 타겟
개인(셀프 모니터링), 임상 전문가, 의료기관

## 도메인 용어
바이오마커, 뉴로피드백, AI 진단, 디지털 정신건강, 임상 빅데이터


## 전역 규칙 (Global Rules)
핵심 4가지 원칙 (Core 4 Principles)
이 원칙들은 LLM 기반 코딩 시 자주 발생하는 실수를 줄이기 위한 기본 토대입니다.

코딩 전 생각하기 (Think Before Coding) - 전제 조건을 명확히 밝히고, 불확실할 때는 진행하기 전에 질문하십시오.

단순성 우선 (Simplicity First) - 최소한의 코드로 문제를 해결하십시오. 추측에 기반한 기능 구현 및 과잉 엔지니어링(Over-engineering)을 피하십시오.

정밀한 변경 (Surgical Changes) - 필요한 부분만 수정하십시오. 본인이 변경한 작업만 정리하십시오.

목표 지향적 실행 (Goal-Driven Execution) - 성공 기준을 명확히 정의하고 검증 테스트 루프를 통해 확인하십시오.

커뮤니케이션 (Communication)
사용자에게 전달하는 모든 답변은 한국어로 작성합니다.

코드 식별자(Identifier) 및 기술 용어는 원어 그대로 유지합니다.

워크플로우 (Workflow)
한 번에 한 단계씩 진행하며, 다음 단계로 넘어가기 전에 완료 여부를 보고합니다.

명시적인 지시 없이 임의로 행동하지 마십시오. 먼저 이해한 내용을 확인하십시오.

절대로 지레짐작하지 마십시오. 불분명하거나 누락된 내용이 있다면 즉시 질문하십시오.

파일을 수정하기 전에는 항상 기존 파일을 먼저 읽으십시오.

코드 변경 전 필수 절차 (Required Procedure Before Code Changes)
코드 수정이나 생성을 요청받았을 때는 다음 순서를 따르십시오:

맥락 분석 (Analyze Context): 관련 파일을 읽고 현재 상태를 파악합니다.

계획 수립 (Create Plan): /make-plan을 사용하여 접근 방식, 맥락 노트, 체크리스트를 문서화하고 저장합니다.

사용자 확인 (Confirm with User): "계획을 저장했습니다. 진행할까요?"라고 질문합니다.

승인 대기 (Wait for Approval): 사용자가 명시적으로 승인할 때까지 코드를 수정하지 마십시오.

코드 품질 표준 (Code Quality Standards)
과잉 엔지니어링을 피하십시오. 최소한의 필수 코드로 요청된 내용만 구현하십시오.

추측성 기능을 절대 추가하지 마십시오. 미래의 요구사항을 예측하거나 성급한 추상화를 하지 마십시오.

실제로 발생할 수 있는 시나리오에 대해서만 예외/에러 처리를 추가하십시오.

모든 코드에는 주석을 작성하되, 한국어로 작성합니다 (상위 /var/www/html/bwave/CLAUDE.md 규칙을 따릅니다).

파일 및 구조 표준 (File and Structure Standards)
절대적으로 필요한 경우가 아니면 새 파일을 생성하지 마십시오.

새 파일을 만들기보다 기존 파일을 수정하는 것을 지향하십시오.

README나 문서 파일은 명시적으로 요청받았을 때만 생성하십시오.

본인이 변경한 사항만 정리하십시오. 관련 없는 코드를 리팩토링하지 마십시오.

보안 (Security)
OWASP Top 10 취약점(SQL Injection, XSS, Command Injection 등)을 절대 유발하지 마십시오.

자격 증명(Credentials), 시크릿(Secrets), API 키를 절대로 하드코딩하지 마십시오.