# sort 현재 기준서 — 패키지 v238

전체 진행률: 74% (`test_list.md` 완료 32 / 전체 43)
문서 체계: 100%
마우스 제어 폐기: 100%
공통 MEDIA_SAVED 로컬 처리: 100%
SQLite 기본·다운로드 구조: 100%
1단계-A 로컬 분석 기반·자동실행·명시저장: 100%
IG 실시간 observation 연결: 0%
PHP/MySQL 10분 중앙 집계: 0%
수집자·그룹·연구 기능: 0%
P2P·포인트: 0%

## 1. 절대 원칙

- 자동·수동 다운로드는 OS 마우스, 원점, hover 검증, Heart8772를 사용하지 않는다.
- 다운로드 시작 방식은 유지하되 파일 저장 완료 이후에는 공통 `MEDIA_SAVED` 흐름을 사용한다.
- 통계 수집은 다운로드 여부와 분리한다. 파일을 받지 않은 게시물도 계정·게시물·통계 저장 대상이다.
- 버전은 실제 수정된 모듈만 변경한다. 관련 없는 파일과 테스트의 버전 문자열만 바꾸지 않는다.
- 완료된 일회성 테스트는 누적하지 않고 현재 기능을 보호하는 고정 이름 최소 회귀 테스트만 유지한다.
- 실제 Windows·Chrome·운영 MySQL 로그가 없는 항목은 `[검증대기]` 또는 `[예정]`으로 둔다.
- 삭제는 우선 soft-delete로 처리한다. 계정·게시물을 분석과 수집 목록에서 제외하지만 MP4/JPG 실제 파일은 자동 삭제하지 않는다.
- 서버에는 MP4/JPG 원본과 사용자 로컬 절대경로·상대경로를 저장하지 않는다.
- 실패·불확실 상태를 성공으로 기록하지 않는다.
- 자동·수동 다운로드 전후에 Instagram 탭, Side Panel, Chrome 창, Chrome 프로세스를 닫지 않는다. Auto8700의 `전체종료`는 WS8771·Analyze8780·RTC8790_8791을 모두 종료하지만 Chrome/CDP는 유지한다.
- Instagram 다운로드는 IG가 원본 미디어 URL·owner·원본 파일명을 WS8771에 전달하고, WS가 `_StDown/instagram/{계정}/{원본파일명}`에 직접 기록한다. Chrome 다운로드 API·CDP download behavior·Save As·Enter는 주 흐름에서 사용하지 않는다.
- Analyze8780 `/api/health` HTTP 확인은 서비스가 열린 뒤 1회만 수행하고 기동 확인 뒤에는 8780 HTTP·TCP health를 반복하지 않고 프로세스 종료만 감시한다.


## 1-1. 기능 체크형 로그 운영 절대 기준

### 목적

- 로그는 상세 실행문을 계속 쌓는 용도가 아니라, 기능별 체크리스트가 어디까지 통과했는지 확인하는 용도로 사용한다.
- 사용자가 해당 기능의 로그만 전달하면 다음 단계 진행 가능 여부와 실패 지점을 판단할 수 있어야 한다.
- 모든 기능명, 체크 항목, 결과와 오류 원인은 한국어를 우선한다.

### 허용 상태

```text
[체크시작]
[체크완료]
[체크실패]
[예외추적]
[복구완료]
```

- `[체크시작]`: 해당 기능 단계에 실제 진입했을 때 1회 기록한다.
- `[체크완료]`: 단계의 실제 결과가 확인됐을 때 1회 기록한다.
- `[체크실패]`: 다음 단계 진행 조건을 충족하지 못했을 때 기록하고 후속 단계를 중단한다.
- `[예외추적]`: 기존 체크 항목만으로 원인이 확인되지 않을 때 해당 실패 항목 아래에 임시 세부 항목을 추가해 기록한다.
- `[복구완료]`: 실패 상태가 실제로 정상 복구됐을 때 기록한다.

### 로그 형식

```text
[기능명][체크ID][상태] 한국어 체크 내용 / 핵심 결과
```

예시:

```text
[자동다운로드][AD-01][체크시작] 자동 시작 상태 확인
[자동다운로드][AD-01][체크완료] 자동 시작 활성화
[자동다운로드][AD-02][체크완료] 대상=No003 계정=4eu_pick
[자동다운로드][AD-03][체크실패] 원본 미디어 URL 없음 / 다음점검=게시물 데이터 추출
```

### 중복 방지

- 같은 실행·같은 기능·같은 체크 ID·같은 결과는 한 번만 기록한다.
- 상태 또는 핵심 값이 바뀔 때만 다시 기록한다.
- 정상 health, 대기, 감시 결과를 초·주기 단위로 반복 기록하지 않는다.
- 동일 오류 반복은 최초 1회만 기록하고, 복구 또는 실행 종료 시 누적 횟수를 한 줄로 요약한다.
- 게시물·다운로드처럼 반복 작업은 `cycle_id`를 사용하되, 같은 cycle 안에서는 중복 기록하지 않는다.

### 단계 진행 기준

- 이전 체크가 `[체크완료]`일 때만 다음 체크를 실행한다.
- `[체크실패]` 이후 단계를 성공처럼 기록하지 않는다.
- 코드 존재 확인은 실제 정상동작 확인과 구분한다.
- 문서의 `[완료]`는 `test_* OK`와 실제 필요한 실행 로그의 `[체크완료]`가 모두 있을 때만 사용한다.

### 문서 연결

- `ai_read.md`: 기능별 전체 체크리스트와 절대 진행 기준
- `ai_read_패치.md`: 현재 실패 체크 ID, 예외추적 항목, 다음 작업
- `ai_read_완료.md`: 실제 테스트·실행 로그로 완료된 체크 ID
- `ai_read_삭제.md`: 반복 로그, 폐기 체크, 다시 사용하면 안 되는 진단 방식
- `test_list.md`: 체크 ID와 테스트 ID의 대응, 현재 결과, 다음 작업

### 기능별 체크리스트 작성 기준

- 새 기능 또는 회귀 수정 전에 먼저 한국어 체크 ID 목록을 정의한다.
- 처음부터 과도한 세부 로그를 넣지 않는다.
- 실패가 발생한 항목에만 `-E01`, `-E02` 형식의 예외추적 체크를 추가한다.
- 예외 원인이 해결되면 임시 추적 로그의 유지 필요성을 검토하고, 불필요하면 제거한다.


## 1-2. 프로젝트 진행로그·상세로그 연결 기준

- 프로젝트 진행로그 파일명은 `ws_project_progress_log_YYYYMMDD_HHMMSS_pidNNNN.log`로 한다.
- 프로젝트 진행로그만 보고 기능별 성공·실패·다음 단계 진행 가능 여부를 판단할 수 있어야 한다.
- 기존 `ws_run_log_*`는 상세 실행 근거로 유지하되 프로젝트 진행로그와 같은 내용을 반복 복사하지 않는다.
- `[체크실패]` 발생 시 기존 상세 실행로그에 `*** 상세로그 *** {검색키워드}` 표식을 남긴다.
- 프로젝트 진행로그에는 상세 실행로그 전체 경로, 검색키워드, 실패 발췌로그 경로를 기록한다.
- 실패 발췌로그 파일명은 `ws_failure_log_YYYYMMDD_HHMMSS_{체크ID}_{검색키워드}.log`로 한다.
- 실패 발췌로그에는 해당 실패 판정과 관련 상세 항목만 기록하고 전체 실행로그를 복사하지 않는다.
- 같은 cycle에서 후속 파일 저장이 성공하면 이전 중간 실패를 `[복구완료]`로 기록한다.
- 서버 등록 실패처럼 로컬 저장을 막지 않는 항목은 `다음단계=로컬처리계속·실패항목별도추적`으로 기록한다.
- 실행로그·에러로그·프로젝트 진행로그·실패로그는 각 접두사별 최근 10개만 유지한다.
- 로그 줄 번호로 연결하지 않고 실행ID·체크ID·시간·검색키워드로 연결한다.

## 2. 현재 실행 구조

- `Auto8700`: 실행 버전 `2.3.8.235`; UI를 main thread에서 생성하고 시작 위치를 주 모니터 안으로 보정한다. 단계별 HWND 로그를 남기며 `전체종료` 시 WS8771·Analyze8780·RTC8790_8791을 종료하고 Chrome/CDP는 유지한다.
- `IG_StYellow`: 실행 버전 `2.3.8.237`; owner username 기반 수집계정 목록, IG 계정·No 분리 표시, 자동 다운로드 background WS 강제 확인과 기능 체크 로그
- `WS8771`: 실행 버전 `2.3.8.238`; 원본 URL을 `_StDown/instagram/{계정}`에 직접 저장하고 해시·JSON·ECharts·Shared `MEDIA_SAVED`까지 완료한다. 별도 프로젝트 진행로그에 AD 체크 결과를 기록하고, 실패 시 기존 실행로그 검색 표식과 실패 발췌로그를 연결한다. Save As 감지·Enter는 보조 진단 기능
- `Analyze8780`: 실행 버전 `2.3.8.232`; 계정·게시물·10분 통계 조회·삭제, 수집계정 API, 저장 폴더명·원본 파일명 표시
- `Shared/storage`: SQLite 스키마 v5, 공통 저장·repository·soft-delete, `collection_account_queue`, 미디어 `folder_name`
- `RTC8790_8791`: 기존 v141 유지
- `Heart8772`: 폐기 및 ZIP 제외

실행 주소:

```text
http://127.0.0.1:8780/
```

실행 파일:

```text
_St/Analyze8780/run_Analyze8780.bat
```

## 3. IG 표시 기준

### 메인 주입 화면

```text
IG double_king0.1
```

- 계정명이 없으면 `IG`만 표시한다.
- 메인 Top 순위 라벨은 기존 스냅샷 표시를 유지한다.
- 실시간으로 계속 변하는 순위를 메인 화면에서 재계산하지 않는다.

### Side 게시물 카드

```text
IG double_king0.1
No003
```

- `IG account`는 항상 별도 줄이다.
- `No000`은 Top 조건이 활성화되고 해당 게시물이 조건에 포함될 때만 별도 줄로 표시한다.
- 계정명과 `No000`을 하나의 박스로 묶지 않는다.
- 순위는 Side 정렬 시점의 스냅샷이며 실시간 재계산 대상이 아니다.


## 3-1. Instagram URL·수집 범위 절대 기준

- `/explore/`, `/reels/`, 계정 프로필, Instagram 홈은 게시물 URL이 아니다.
- 특별관리 URL은 수집 화면 `source_scope`이며 계정 username으로 저장하지 않는다.
- 게시물 계정은 현재 화면 URL이 아니라 게시물 데이터의 `username`·owner 정보로 판별한다.
- 게시물 링크는 shortcode가 포함된 `/p/{shortcode}/`, `/reel/{shortcode}/`, `/tv/{shortcode}/`만 허용한다.
- 잘못 저장된 기존 scope URL은 Analyze8780 조회 시 shortcode 기반 링크로 안전하게 표시하며 영상은 `/reel/`, 그 외는 `/p/`를 사용한다.
- 순위 `No000`은 단독 값이 아니라 EXT의 현재 `source_scope + 기간 옵션 + sort_by + 기준값 + Top 개수` 조합별 스냅샷이다. Explore·Reels·계정 화면의 순위를 서로 합치지 않는다.

## 4. 저장 폴더 기준

```text
상위폴더/
├─ _St/
│  ├─ Analyze8780/
│  ├─ Auto8700/
│  ├─ IG_StYellow/
│  ├─ WS8771/
│  ├─ RTC8790_8791/
│  └─ Shared/
└─ _StDown/
   └─ instagram/
      ├─ account_a/
      │  ├─ 원래파일명.mp4
      │  └─ 원래파일명.jpg
      ├─ account_a.html
      └─ 사용자집계명.html
```

- 기존 `index.htm`·계정별 HTML·JSON 표시 기능은 유지한다.
- Analyze8780은 기존 정적 HTML을 삭제하지 않고 SQLite 조회용 새 로컬 화면을 제공한다.
- 실제 파일 정체성은 `platform + shortcode + asset_kind + media_index`와 해시로 판단한다.

## 5. 다운로드와 observation 분리

다운로드 source mode:

```text
MANUAL_SINGLE
BULK_ALL
BULK_FILTERED
TOP_SORTED
TRANSCRIPT_REQUEST
```

다운로드 완료 흐름:

```text
IG 원본 미디어 URL·owner·원본 파일명
→ WS8771 `ig_media_download_direct` 작업 큐
→ `..\_StDown\instagram\{계정}\{원본파일명}.part`
→ 다운로드 완료 후 원본 파일명으로 atomic rename
→ WS 파일·해시 확인
→ JSON·ECharts 갱신
→ Shared MEDIA_SAVED(folder_name, file_name, relative_path)
→ Analyze8780 저장 폴더·원본 파일 표시
→ sync_queue
```

통계 observation 목표 흐름:

```text
Instagram 주입 통계
→ Analyze8780/Python 수신
→ SQLite 계정·게시물 upsert
→ 10분 bucket upsert
→ sync_queue
→ PHP 호출
→ MySQL 동일 bucket upsert
```

v237 자동 다운로드는 실제 Windows 로그에서 No001·No002의 MP4/JPG·JSON·HTML 저장까지 확인됐다. v238은 기능 성공 여부와 진단 근거를 분리하기 위해 프로젝트 진행로그·상세로그 검색 표식·실패 발췌로그를 도입했다. v238 로그 파일 생성과 연결은 실제 Windows 확인 전까지 검증대기다. IG 실시간 observation 전송과 PHP/MySQL 중앙 저장은 다음 단계다.

## 5-1. 브라우저·저장창 기준

- 주 다운로드는 `chrome.downloads.download`를 호출하지 않는다.
- 실제 확장 Chrome과 8700 CDP Chrome이 다를 수 있으므로 `Browser.setDownloadBehavior`를 다운로드 보장 수단으로 사용하지 않는다.
- WS8771이 원본 URL을 직접 내려받아 `_StDown/instagram/{계정}/{원본파일명}`에 저장한다.
- `.part` 임시 파일을 사용하고 완료 후 원본 파일명으로 교체한다.
- 다운로드·완료 처리 실패는 성공으로 기록하지 않는다.
- Save As Enter는 다른 환경의 보조 진단 기능이며 자동 다운로드 완료 조건이 아니다.
- 다운로드 완료 또는 popup 전환을 이유로 Instagram 탭, Side Panel, Chrome 창을 닫지 않는다.
- Auto8700 종료·재시작은 Chrome/CDP 프로세스를 유지한다.


## 5-2. 자동 다운로드 기능 체크리스트

```text
AD-01 자동 시작 활성화 확인
AD-02 조건에 맞는 No000 대상 게시물 선정
AD-03 게시물 owner·shortcode·원본 URL 확인
AD-04 background → WS8771 중복 확인 연결
AD-05 WS8771 직접 다운로드 큐 등록
AD-06 파일 저장·JSON·ECharts·SQLite 완료
AD-07 현재 게시물 완료 후 다음 게시물 진행 가능
```

- 같은 실행·cycle·체크 ID·상태·자산 종류는 한 번만 기록한다.
- 기존 `S00~S99` 상세 cycle 상태는 내부 상태 보존용이며 v237부터 정상 상태를 사용자 로그에 반복 출력하지 않는다.
- `[체크실패]`가 발생하면 다음 단계는 성공으로 기록하지 않는다.
- v237 실제 Windows 로그로 자동 다운로드 파일 저장은 확인됐다. v238에서는 동일 실행의 프로젝트 진행로그에서 `AD-01 → AD-07` 판정과 실패 연결 경로를 추가 확인한다.
- v237 수정 원인: 자동 다운로드의 background `ig_media_exists` 호출이 WS 미연결 상태에서 `force_probe` 없이 즉시 오프라인 반환되어 파일 저장 시작 전에 차단됐다. 자동 경로에만 `priority + force + force_probe`를 적용한다.

v238 프로젝트 진행로그 기준:

```text
[자동다운로드][AD-01][체크완료] ... / 다음단계=진행가능
[자동다운로드][AD-06][체크실패] ... / 다음단계=중단 / 상세로그=... / 검색어=*** 상세로그 *** ... / 실패로그=...
```

## 6. SQLite v5 기준

### 기존 v1·v2

- 계정·수집 조건·게시물·최신 통계·통계 이력
- 미디어·대본·후킹·카테고리·평가·댓글
- 다운로드 batch/job·`MEDIA_SAVED`
- P2P 보유 캐시·sync_queue

### v3 추가

- `post_stat_10m`
  - 게시물별 10분 버킷 통계
  - `UNIQUE(post_id, bucket_at)`
  - 같은 10분 구간 재수집은 새 행이 아니라 UPSERT
  - `sample_count`로 같은 구간 수집 횟수 기록
- `deletion_events`
  - 계정·게시물 soft-delete tombstone
  - 서버 DELETE 동기화 대기 근거
- `social_accounts.deleted_at`
- `posts.deleted_at`


### v4 추가

- `collection_account_queue`
  - Side 수집계정 username, 순서, enabled, is_deleted 저장
  - 특별관리 URL은 저장 거부
  - SQLite가 PC 기준 원본이며 localStorage는 캐시

### v5 추가

- `media_assets.folder_name`
- `media_saved_events.folder_name`
- `file_name`은 Instagram 원본 파일명을 유지한다.
- `relative_path`는 `계정/원본파일명`으로 로컬 SQLite에만 저장한다.
- 서버 sync payload에는 로컬 폴더명·경로를 포함하지 않는다.

10분 버킷 예:

```text
14:02:37 → 14:00:00
14:09:59 → 14:00:00
14:10:01 → 14:10:00
14:27:20 → 14:20:00
14:59:59 → 14:50:00
```

한 게시물은 시간당 최대 6개 버킷을 가진다.

## 7. Analyze8780 API

```text
GET    /api/health
GET    /api/accounts?page=1&page_size=50
GET    /api/accounts/{id}/posts?page=1&page_size=50&sort_by=views
GET    /api/posts/{id}
GET    /api/posts/{id}/stats?limit=144
GET    /api/deleted/accounts
GET    /api/collection-accounts
PUT    /api/collection-accounts
POST   /api/observations
DELETE /api/accounts/{id}
DELETE /api/posts/{id}
```

정렬 기준:

```text
views
likes
comments
reposts
views_delta
views_per_hour
published_at
```

기본 분석값:

- 현재 조회수·좋아요·댓글·리포스트
- 최근 10분 조회수 증가량
- 시간당 환산 증가량
- 좋아요율·댓글률
- 계정별 게시물 수·최고 조회수·합계 조회수

떡상 점수 공식은 아직 확정하지 않는다. 실제 10분 데이터가 누적된 뒤 가중치를 결정한다.

## 8. 삭제 기준

### 게시물 삭제

- Analyze8780 목록에서 숨김
- `posts.deleted_at` 기록
- `deletion_events`와 `sync_queue DELETE` 기록
- 실제 MP4/JPG 파일은 유지

### 계정 삭제

- `social_accounts.active=0`, `deleted_at` 기록
- 해당 계정 수집 규칙 `enabled=0`
- 해당 계정 게시물을 분석 목록에서 숨김
- Side 수집계정 목록의 해당 username 앞에 `🗑️` 표시
- `🗑️` 항목은 수집 순회와 계정 카운팅에서 제외
- 실제 파일은 유지

Side 목록 예:

```text
account_a
🗑️ account_b
account_c
```

실행할 때만 `https://www.instagram.com/{username}/` 프로필 URL을 생성한다.

표시 카운트는 2개이며 삭제 1개는 별도 안내한다. 단순 전체 줄 수에서 이모지 개수를 빼는 방식이 아니라 각 항목의 삭제 상태를 판별해 제외한다.

## 9. Side 수집계정 저장 기준

- 수집계정 원본은 URL이 아니라 게시물 주입 데이터의 `username`이다.
- `/explore/`, `/reels/`, `/p/`, `/reel/` 같은 현재 화면 URL을 계정으로 해석하지 않는다.
- `표시계정추가`는 섬네일의 `IG account`를 만들 때 사용한 owner username을 그대로 추가한다.
- `💾 저장`은 Analyze8780 `PUT /api/collection-accounts`를 호출해 SQLite `collection_account_queue`에 저장한다.
- Side 시작 시 `GET /api/collection-accounts`로 복원하므로 Chrome 프로필이 바뀌어도 같은 PC 목록을 공유한다.
- localStorage는 8780 연결 실패 때 보여줄 캐시일 뿐 기준 저장소가 아니다.
- 계정목록은 포커스 이탈로 자동 저장하지 않으며 미저장 변경은 `💾 저장*`으로 표시한다.
- 삭제 계정은 `🗑️ username`, `enabled=0`, `is_deleted=1`로 유지하고 수집·카운팅에서 제외한다.

## 10. 기존 index.htm 유지와 향후 역할

현재 만족 확인된 기능:

- 개별 수동 다운로드
- JSON 연동 index.htm 표시
- 계정별 HTML·ECharts 보고서

유지 원칙:

- 기존 index.htm 생성·JSON 연동을 깨지 않는다.
- Analyze8780은 데이터가 많아질 때 페이지 조회·삭제·분석을 담당한다.
- 향후 index.htm 또는 8780 화면에서 대본 추출 버튼을 누르면 Python이 처리한다.
- 대본 완료 후 본문 표시와 `📋 복사` 버튼을 제공한다.

## 10-1. 게시물 지표·그룹 예정 기준

- 게시물 지표는 조회수, 좋아요, 댓글, 공유, 저장, 리포스트를 각각 독립 저장한다.
- 원본 응답에 없는 공유·저장·리포스트는 `0`이 아니라 `NULL/UNKNOWN`으로 둔다.
- 그룹 수집 조건은 임의의 "관찰 기간"을 만들지 않고 EXT의 실제 `1day`, `2day` 등 기간 옵션, `sort_by`, 기준값, Top 개수를 그대로 저장한다.
- 한 사용자는 여러 그룹에 가입할 수 있으며 자료 공개 범위는 PRIVATE, 복수 GROUP, PUBLIC으로 구분한다.
- PRIVATE 사용자는 타인의 자료를 받아 포인트 차감은 가능하지만 비공개 자료 제공으로 포인트 적립은 하지 않는다.
- P2P·포인트는 파일 해시·크기 검증 후 원장 거래로만 확정한다.

## 11. 단계별 우선순위

### 1단계-A — v231 코드·자동검증 완료

- Side `IG account`·`No000` 분리
- Analyze8780 서비스와 Auto8700 시작 시 자동 실행
- 8780 포트와 `/api/health` 상태 확인
- SQLite 계정·게시물 repository
- 10분 통계 UPSERT·조회
- 계정·게시물 soft-delete
- Side `🗑️` 삭제 계정 제외
- 계정목록 `💾 저장` 명시 버튼
- username-only 수집목록을 Analyze8780 SQLite에 저장·복원
- Chrome 프로필별 localStorage는 캐시로만 사용
- 포커스 이탈 자동 저장 폐기
- 미저장 변경은 `💾 저장*`으로 표시
- 다운로드 최종 저장 위치를 `_StDown/instagram/계정`으로 고정

실제 Windows Auto8700→Analyze8780 기동과 실제 Chrome 저장 버튼 동작은 `[검증대기]`다.

### 1단계-B — 다음 우선순위

- IG 주입 통계를 다운로드와 무관하게 8780으로 실시간 전송
- observation 전송량 제어·중복 방지·재시도
- SQLite 10분 bucket UPSERT와 observation sync_queue 등록
- 8780 미실행·일시 장애 시 실패를 숨기지 않고 재시도 대기

### 2단계

- PHP/MySQL 10분 통계 중앙 저장
- 같은 게시물·같은 10분 버킷 UPSERT
- 중앙 계정별·게시물별 집계
- 서버 최신값·이력 조회

### 3단계

- 수집자 회원·컴퓨터·토큰

### 4단계

- 그룹·공개 범위·공동 수집

### 5단계

- 대본 추출·카테고리·평가·후킹·검색

### 6단계

- P2P·포인트 정산

## 12. 완료 선언 기준

- 코드만 반영된 상태는 완료가 아니다.
- 관련 고정 테스트가 존재하고 실행 결과가 `OK`여야 한다.
- 실제 Windows·Chrome·서버 동작은 실제 로그 확인 전 `[검증대기]`다.
- 다음 채팅에서도 `ai_read*`와 `test_list.md`만 읽으면 현재 기준과 다음 작업을 알 수 있어야 한다.
