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

전체 진행률: 50% (`test_list.md` 완료 55 / 전체 110)
문서 체계: 100%
마우스 제어 폐기: 100%
공통 MEDIA_SAVED 로컬 처리: 100%
SQLite 기본·다운로드 구조: 100%
1단계-A 로컬 분석 기반·자동실행·명시저장: 100%
IG 현재 페이지 observation 연결: 0% 완료 (`코드반영·실행로그 검증대기`)
중앙 1시간 통계 이력 송수신: 0% 완료 (`코드반영·운영서버 검증대기`)
Ext 대량 게시물 메모리 보호: 0% 완료 (`코드반영·전체 카드 DOM 고정·화면 밖 이미지 src/srcset 해제·실제 Chrome 128~800개 검증대기`)
로컬 10분 통계 변경분 저장: 0% 완료 (`코드반영·실제 SQLite 장시간 검증대기`)
동일 1시간 신규 통계 증분 전송: 0% 완료 (`코드반영·실제 WS 로그 검증대기`)
PHP/MySQL 시간 이력 집계: 0% 완료 (`코드반영·운영서버 검증대기`)
수집자·그룹·연구 기능: 0%
P2P·포인트: 0%

## 0. 최종 목적 — Instagram 크리에이터 도우미

- 이 프로젝트의 핵심은 단순 다운로드가 아니라, Instagram 기본 화면이 제공하지 않는 공개 게시물 순위·성과·변화·비교 정보를 인젝션으로 가공해 보여주는 크리에이터 도우미다.
- 대박이 난 영상과 성장 가능성이 높은 콘텐츠를 기간·조회수·좋아요·댓글·리포스트·참여점수 기준으로 빠르게 찾게 한다.
- Instagram에는 더 좋은 콘텐츠와 활동이 늘고, 크리에이터는 성과를 이해해 성장하며, 사용자는 재미있고 유익한 콘텐츠를 쉽게 찾는 상생 구조를 목표로 한다.
- 사용자가 많아지고 공개 관측 데이터가 쌓일수록 더 빠르고 세밀한 정보를 제공하되, 공개 정보·요청 절제·출처와 수집시각 보존·실패의 명확한 표시 원칙을 지킨다.
- 다운로드는 필요한 원본을 보관하는 보조 기능이다. 통계 수집과 분석은 다운로드 여부와 독립적으로 동작해야 한다.
- IG 핵심 기능이 실제 로그로 안정화된 뒤 TikTok 도우미를 `TT_*` 독립 모듈로 추가한다. Instagram과 TikTok 전용 DOM·API 코드는 섞지 않고 공통 저장·통계·로그·분석만 `Shared`에서 공유한다.

최종 흐름:

```text
수집 계정 선택
→ 현재 Instagram 페이지 공개 게시물만 인식
→ 기간·정렬·최소 조건 적용
→ 성과 게시물과 수집 당시 순위 표시
→ 로컬 10분 통계 저장
→ 선택 시 서버 1시간 통계 연동
→ 필요한 미디어만 WS8771 단일 경로로 저장
→ 계정 HTML·Analyze8780에서 다시 조회·비교
```

IG 핵심 완료는 다계정 전환 혼입 없음, 조건·순위·통계 정확성, WS 단일 다운로드, JSON·HTML·SQLite·MySQL 일치, 중단·재개·대상 0개 처리, 실제 로그와 `test_*` OK가 모두 확인됐을 때 선언한다.

## 1. 절대 원칙

- 자동·수동 다운로드는 OS 마우스, 원점, hover 검증을 사용하지 않는다. 모든 미디어 다운로드는 WS8771 단일 경로이며 WS 미연결·timeout·오류 상태에서는 시작하지 않는다.
- 다운로드 시작 방식은 유지하되 파일 저장 완료 이후에는 공통 `MEDIA_SAVED` 흐름을 사용한다.
- 통계 수집은 다운로드 여부와 분리한다. 파일을 받지 않은 게시물도 계정·게시물·통계 저장 대상이다.
- Injection의 수동 Image/Video 다운로드는 현재 shortcode의 전체 수집 객체를 공통 메타 규격으로 전달한다. shortcode·asset_kind·media_index가 같은 파일·JSON·SQLite·서버 항목에 조회수·좋아요·댓글·리포스트·게시시각·썸네일·caption을 병합한다.
- 다운로드 성공 표시는 한글 문구 대신 해당 shortcode·미디어 종류 버튼 하나의 재생 아이콘을 `✅`로 교체한다. 성공 응답에 shortcode가 없으면 완료 표시하지 않는다.
- 버전은 실제 수정된 모듈만 변경한다. 관련 없는 파일과 테스트의 버전 문자열만 바꾸지 않는다.
- 완료된 일회성 테스트는 누적하지 않고 현재 기능을 보호하는 고정 이름 최소 회귀 테스트만 유지한다.
- `test_*`는 코드 문법·정적·단위 회귀 검증이다. 실제 기능 완료는 사용자가 전달한 Windows·Chrome·운영 MySQL 실행 로그로 확인하며, 그 로그가 없으면 `[코드반영]` 또는 `[검증대기]`로 둔다.
- 삭제는 우선 soft-delete로 처리한다. 계정·게시물을 분석과 수집 목록에서 제외하지만 MP4/JPG 실제 파일은 자동 삭제하지 않는다.
- 서버에는 MP4/JPG 원본과 사용자 로컬 절대경로·상대경로를 저장하지 않는다.
- 실패·불확실 상태를 성공으로 기록하지 않는다.
- Top sorted 자동 완료는 후보 배열의 마지막 No가 아니라 **WS8771 저장·JSON·보고서 완료가 확인된 고유 shortcode 수가 Top목표에 도달했을 때**만 성립한다. 목표 미달이면 swipe를 계속한다. 기간 필터는 새로 로딩된 알려진 날짜가 모두 범위 밖일 때, All time은 게시물 처음과 추가 로딩 없음이 확인될 때만 `더이상없음`으로 판정하고 있는 대상만 처리한 뒤 `자동종료`를 표시한다.
- Top sorted 자동은 Top목표 전체 후보를 먼저 모으지 않는다. 아직 완료하지 않은 후보 snapshot의 shortcode·owner·원본 URL이 준비되면 그 후보를 즉시 다운로드하고, 저장 완료 뒤 목표가 남으면 다음 후보를 찾기 위해 swipe를 계속한다. 후보 0개 상태는 날짜 범위나 게시물 끝 근거가 생길 때까지 탐색하며, 끝 근거가 생기면 현재 완료 수를 유지하고 다음 계정 URL로 이동한다.
- 조회수 비공개·미제공은 내부 후보·메타에서 `views=-1`, `views_available=0`으로 구분한다. 조회수 0은 공개된 실제 0이며 비공개와 합치지 않는다.
- 조회수 기준이 활성인데 조회수가 비공개이면 좋아요·댓글·리포스트 중 설정값이 1개 이상일 때만 후보가 된다. 활성 조건은 모두 AND로 통과해야 하며 순위 점수는 활성 지표 원값의 **단순합산**이다. 입력값 0인 지표와 공유 수는 합산하지 않는다.
- 자동실행은 Main 시각 No DOM을 요구하지 않는다. 시작 시 만든 불변 snapshot의 `No/shortcode/owner/원본 URL`을 내부 Current 대상으로 복원할 수 있다.
- 자동 다운로드는 `AD-03`의 rank·shortcode·owner·원본 URL 확인이 `[체크완료]`가 된 뒤에만 `AD-04` 중복확인과 `AD-05` 다운로드 큐로 진행한다. `AD-03 [체크실패]` 뒤 캐시 다운로드를 실행하는 경로는 금지한다.
- 계정 목록 자동 완료는 실제 목록 index가 n/n에 도달했을 때만 성립한다. `automatic_end` 상태만으로 n/n을 강제 표시하지 않는다. 완료 결과에는 n/n을 남기고, 다음 실행을 위한 시작 index만 완료 처리 뒤 0/n으로 초기화한다. 중간 이동 실패나 대상 0개는 n/n 도달 전 정상 완료로 처리하지 않는다.
- 인젝션 썸네일은 Base64로 변환·누적하지 않고 원본 URL만 유지한다. 전체 미디어 목록은 내용이 바뀌었을 때 또는 30초 keepalive 때만 전달하며 매초 전체 직렬화를 반복하지 않는다.
- Side는 전체 메타데이터·전체 순위와 현재 목록의 모든 `.post-item` DOM을 문서 순서대로 유지한다. 스크롤 중 카드 추가·삭제·행 높이 측정·spacer·`scrollBy` 보정을 실행하지 않는다. 썸네일 영역은 16:9로 고정하고 `IntersectionObserver` 범위 밖에서는 `img`의 `src`·`srcset`만 제거해 이미지 디코딩 메모리 회수 기회를 제공한다. 다시 화면 근처에 들어오면 보관 URL을 복원하며 `img_small` 실패 시 `img_origin`을 한 번 fallback한다. `content-visibility:auto`로 화면 밖 카드의 레이아웃·페인트 부담을 줄인다.
- Side 설정의 인젝션 표시 모드는 `detail / download / none` 3개를 유지한다. isolated content bridge가 storage·runtime 메시지를 MAIN world의 `ndy_ig_overlay_mode`로 전달해야 하며, `detail`은 통계+다운로드 버튼, `download`는 다운로드 버튼만, `none`은 해당 인젝션 카드 전체를 숨긴다. 설정 UI만 남고 전달 함수가 누락된 상태를 정상으로 보지 않는다.
- `Visual settings`의 `Use / Side / Main`은 서로 독립된 저장값이다. Metric underline bars와 Date border 모두 `main`을 `side` 값으로 덮어쓰거나 Side 체크값으로 Main을 저장하지 않는다. Side 렌더링은 `side`, Instagram Main 렌더링은 `main`을 검사한다.
- Visual settings 기본값은 막대 3px, 항목간 4px, 글자-바 2px, 항목높이 18px, 썸네일 200px, Views 1,000,000, Likes 100,000, Comments 10,000, Repost 10,000이다.
- 기존 `다운버튼 크게 표시` 설정은 폐기하고 같은 저장영역을 `다운로드 상태 강조`로 사용한다. 정확한 shortcode·asset_kind가 있는 실제 다운로드 시작·진행·성공·중복·부분·실패만 Main DOM 카드와 MP4/JPG 버튼에 표시하며 Glow·색상·테두리·radius·투명도·확대 설정을 적용한다. 실패를 성공 표시로 바꾸지 않는다.
- 다운로드 상태는 background의 `tabs.sendMessage` → isolated content → MAIN world 순서로 전달한다. extension page용 `runtime.sendMessage`만으로 Main DOM 전달이 된 것으로 간주하지 않는다.
- 수동 MP4/JPG 다운로드는 사용자가 해당 버튼을 클릭한 순간 정확한 shortcode·asset_kind·media_index의 `start` 상태를 Main에 전달한다. 이후 기존 성공·부분·실패 상태로 갱신한다. 이 즉시 클릭 처리는 자동다운로드 실행 경로에 추가하지 않는다.
- v276 응답 없음 진단은 MAIN world의 `window.__IGBLUE_RUNTIME_DIAG_V276__`와 Instagram localStorage `IGBLUE/runtime_diag_v276`에 10초 간격의 DOM·이미지·video·인젝션 카드·미디어 맵·JS heap·SPA retry timer·Long Task·DOM scan 시간·Observer callback·debug/media 처리 누적을 최대 ring buffer로 기록한다. Side 자동 상태는 계정 index/total·계정명·phase·run_seq·Top 진행 수를 `IGBLUE/auto_runtime_status_v276`으로 함께 기록해 1/3→2/3→3/3을 비교할 수 있게 한다. 이 진단은 원인 확인용이며 자동 새로고침·자동복구·`unload→pagehide` 후킹을 실행하지 않는다.
- Side의 `탭 새로읽기`는 탭 새로고침과 분리한다. 현재 Instagram 탭의 Main Injection에 재읽기 요청을 보내 동일 payload 30초 전송 억제를 초기화하고 현재 DOM·캐시를 다시 스캔한다. 이 기능은 swipe·페이지 reload·계정 이동을 실행하지 않는다.
- 사용자 직접 중단은 진행상태 삭제와 분리한다. 중단 시 계정 queue index·현재 No·현재 계정 집계·전체 실행 집계·완료 shortcode를 보존한다. 다음 `시작`은 보존 상태가 있으면 이어서 실행하고, 상태가 없으면 0부터 시작한다. 처음부터 다시 실행할 때만 `진행상태 리셋`을 사용한다.
- 통계 수집 스냅샷은 처리 중 최신 1개만 유지한다. 로컬은 10분 구간이 바뀐 시점에 마지막 수치와 비교해 실제 조회수 제공 여부·조회수·좋아요·댓글·리포스트가 변경된 shortcode만 최대 100개씩 저장한다. Analyze8780·Shared 저장소도 마지막 정상 수치와 게시물·계정 메타가 모두 동일하면 계정·게시물·latest·history·10분 테이블에 아무 쓰기도 하지 않아 Side 재시작 후 중복과 불필요한 디스크 쓰기를 막는다. 메타만 변경된 경우에는 메타만 갱신하고 통계 이력은 추가하지 않는다. 중앙은 같은 1시간 신규·변경 shortcode를 최대 100개 단위로 처리하며 성공한 chunk만 완료 signature로 기록한다.
- 자동·수동 다운로드 전후에 Instagram 탭, Side Panel, Chrome 창, Chrome 프로세스를 닫지 않는다. Auto8700 종료 시 WS8771·RTC8790_8791은 종료하지만 Chrome/CDP와 Analyze8780은 유지한다.
- 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를 반복하지 않고 프로세스 종료만 감시한다.
- Auto8700이 실행 중인 동안 WS8771·Analyze8780·RTC8790_8791 등 관리 서비스가 X 종료·정상 종료·비정상 종료·포트 down 상태가 되면 다시 실행한다. Auto8700 종료 시 Analyze8780은 상시 조회용으로 유지하고, Analyze8780 소스 변경 시에는 manager가 해당 서비스만 재시작한다.
- 수집계정 저장 표시는 `@account`로 통일하고 중복을 제거하되, 일반 `💾 저장`은 화면 순서를 그대로 보존한다. 새 계정은 목록 끝에 추가하며 `정렬` 버튼을 눌렀을 때만 username 오름차순으로 정렬한다. `🗑️ @account`는 삭제 tombstone이며 수집·카운팅·Instagram URL 생성·재시도에서 절대 제외한다.
- Side의 별도 `표시계정추가` 일괄 버튼은 제거한다. Side 카드의 `IG 계정명` 클릭은 기존 계정추가 버튼의 대상만 바꾸며, 실제 목록 추가와 SQLite 확정은 기존 계정추가·`💾 저장` 절차를 따른다.
- 수집계정 자동 시작은 `0/N`과 `1/N 이상`을 분리한다. `0/N`은 현재 웹페이지가 Instagram일 때만 시작하며, `1/N 이상`은 현재 페이지 종류와 관계없이 지정 index의 계정 URL을 background `tabs.update`로 먼저 연 뒤 content/Main 준비를 확인한다.
- `0/N`에서 현재 페이지가 Instagram이 아니면 자동 실행 상태·Main 대기 상태로 진입하지 않는다. "Instagram 페이지에서 시작을 눌러주세요"를 표시하고 시작은 사용 가능, 중지는 사용 불가인 대기 상태를 유지한다. `Goto Instagram` 새 탭을 기존 자동 세션 tab_id로 간주하지 않는다.
- 사용자 중지 완료와 자동종료 완료는 `자동실행 사용` 설정을 끄지 않는다. 두 경우 모두 `시작`은 다시 누를 수 있고 `중지`는 누를 수 없는 대기 상태로 복귀한다. `stopping`은 WS 중지 응답이 없어도 제한 시간 뒤 해제하며 이전 실행의 늦은 응답이 새 실행 상태를 덮지 못하게 run sequence로 차단한다.
- 자동실행 대상 `tab_id`는 `다운로드 방식 3 · Top sorted 자동`의 개수 앞에 한 번만 표시한다. 유효한 자동실행 Instagram 탭이 있으면 숫자, 미확정·종료·무효 대상이면 `tab_id: -`로 표시한다. `자동실행 사용 / 시작 / 중지` 사이에는 넣지 않는다.
- 전체 주소 종료예상은 현재 계정 남은 Top목표와 이후 계정 수×Top목표를 합친 남은 게시물 수를 기준으로 한다. 성공한 게시물의 MP4+JPG 실제 소요시간을 `download/auto_eta_stats_v274` JSON에 누적하고 평균 게시물 다운로드 시간에 `다운 지연`을 더해 계산한다. 실패·중복·미실행 시간을 성공 평균으로 기록하지 않으며 전체 완료 시 00시간 00분으로 표시한다.
- 계정 URL이 Instagram의 명시적인 페이지 없음 문구와 동일 username URL로 확인될 때만 `🗑️` tombstone으로 저장한다. 로그인·challenge·비공개·네트워크·content/Main 지연은 삭제로 판정하지 않는다. tombstone 저장 뒤 같은 계정을 다시 열지 않고 축소된 활성목록의 같은 index에 있는 다음 URL로 진행한다.
- 수집계정 원본 SQLite 기본 파일은 `_St/Shared/data/sort_local.sqlite3`이다. Side에서 안전한 파일명으로 `파일열기 / 새 파일 / 다른이름 저장`을 수행하며 선택 파일명은 `_St/Shared/data/sort_database_selection.json`에 저장한다. 새 파일과 다른이름 저장은 기존 파일을 덮어쓰지 않는다.
- 선택된 SQLite 파일은 수집계정 목록뿐 아니라 Analyze8780 조회·통계와 WS8771의 공통 저장 경로가 함께 사용한다. DB 전환 시 Side 진행상태와 계정 index는 0으로 초기화하며, Analyze8780을 `--db` 명시 경로로 실행한 경우 런타임 파일 전환을 허용하지 않는다.
- Explore 게시물 팝업은 새 문서가 아니라 같은 SPA 문서 위의 전환이다. `/explore/ → /p|reel|reels/{code} → /explore/` 동안 전체 Explore 미디어 맵을 비우지 않는다. 상세 주소 하나만 carry하고 나머지 Explore 데이터를 삭제하는 방식은 금지한다.
- Explore 계정 hover는 0.2초 유지 후 확정한다. 포인터가 Side 버튼으로 이동하며 다른 썸네일을 잠깐 통과해도 기존 확정 계정을 유지하고, 다른 카드가 0.2초 유지됐을 때만 교체한다.
- Instagram 홈의 같은 `article + shortcode`에는 시각 인젝션 카드 하나만 유지한다. 현재 보이는 video가 같은 shortcode이면 `D_VIDEO_LINK`를 기준 카드로 사용하고 `H_IMAGE_LINK` poster 경로는 차단한다. 서로 다른 article의 같은 shortcode를 전역 하나로 합치지 않으며 숨겨진 video 때문에 이미지 카드를 제거하지 않는다.
- 홈→Explore SPA 이동은 초기 HTML JSON에 의존하지 않는다. fetch clone과 `Response.json/text`에서 Instagram GraphQL·`/api/v1/` 응답을 수집하고 0~8초 재스캔한다. `INJ-01~INJ-05`는 localStorage 체크 로그와 `DOM 진단복사`의 `injection_check_log`에 남기며 `media_map_count`, `capture_counters`, 카드 수로 실패 단계를 구분한다.
- 현재 실제 확인 순서는 루트 `검증목록_v276.md` 01~56을 기준으로 한다. 로그·DB로 확인 가능한 항목과 사용자 화면 확인 항목을 구분하며 확인되지 않은 항목을 완료로 올리지 않는다.
- Side의 `No000` 순위 행·Rank badge·`순위` 설정 탭은 정상 기능으로 유지한다. Side 순위 설정은 Side에만 적용하며 Main 체크나 Main 순위 payload를 만들지 않는다.
- Main의 `No000` 순위 행·Rank badge·미디어 종류 순위 행·우측 `1/Prev/Next/마지막` 버튼은 제거한다. 자동실행은 화면 DOM 버튼을 클릭하지 않고 `IGBLUE/internal_no_nav_state`, 내부 Current/Next handler, shortcode 기반 `.ndy_ins_info[data-code]` 탐색만 사용한다.


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

### 목적

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

### 허용 상태

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

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

### 로그 형식

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

예시:

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

### 중복 방지

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

### 단계 진행 기준

- 의존 관계가 있는 다음 체크는 이전 체크가 `[체크완료]`일 때만 실행한다. 독립 체크는 `blocking=false`로 계속할 수 있다.
- `[체크실패]` 이후 의존 단계를 성공처럼 기록하지 않으며, 독립 진행 결과와 실패 항목을 분리해 기록한다.
- 코드 존재 확인은 실제 정상동작 확인과 구분한다.
- 문서의 `[완료]`는 사용자가 전달한 실제 실행 로그에서 정상 동작이 확인된 뒤에만 사용한다. `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.250`; UI를 main thread에서 생성하고 시작 위치를 주 모니터 안으로 보정한다. 종료 시 WS8771·RTC8790_8791은 종료하고 Chrome/CDP·Analyze8780은 유지한다.
- `IG_StYellow`: 실행 버전 `2.3.8.275`; `자동실행 사용 / 시작 / 중지` 한국어 UI, 저장값 없음일 때 자동실행 기본 사용, 방식 3 개수 앞 `tab_id`, 설정 톱니바퀴 우측 정렬, Side 카드 IG 계정 클릭 대상 선택, 수동 MP4/JPG 클릭 즉시 시작 강조를 반영한다. v274 자동실행 제어·전체 ETA, v273 계정목록·SQLite 전환, v272 Visual settings·다운로드 상태 강조를 유지한다.
- `WS8771`: 실행 버전 `2.3.8.250`; Side의 `보고서 재생성` 요청을 처리하고 계정 HTML에 Analyze8780 링크를 보장한다. 계정별 UTC 1시간 bucket을 한 batch로 `media_stat_hour_upsert`에 원자 저장한 뒤 같은 bucket을 `media_stat_hour_list`로 수신한다. 요청 개수와 saved_count가 다르거나 수신이 실패하면 전체 실패로 반환하며 시간 완료키를 기록하지 않는다. 공유값은 항상 0이다. 기존 원본 URL을 `_StDown/instagram/{계정}`에 직접 저장하고 해시·JSON·ECharts·Shared `MEDIA_SAVED`까지 완료한다. 정적 계정 HTML/index.htm 보고서 v4는 게시 시각을 표시하고 320px 정사각 썸네일과 Analyze8780 `전체보기` 링크를 제공한다. 별도 프로젝트 진행로그에 AD 체크 결과를 기록하고, 실패 시 기존 실행로그 검색 표식과 실패 발췌로그를 연결한다. Save As 감지·Enter는 보조 진단 기능
- `Analyze8780`: 실행 버전 `2.3.8.273`; 기존 observation·계정·게시물 API와 UI를 유지하면서 활성 SQLite 파일 목록·열기·새 파일·다른이름 저장 API를 제공한다. 기본 실행은 Shared 선택 DB를 사용하며 `--db` 고정 실행은 런타임 전환을 금지한다.
- `Shared/storage`: SQLite 스키마 v7. 기본 `sort_local.sqlite3`와 선택 파일명 JSON을 유지하고, 안전한 파일명 기준의 open/new/save-as를 제공한다. Analyze8780과 WS8771은 같은 선택 DB를 사용한다. Side 수집계정 순서·삭제 우선, views_available와 동일 통계 생략, MySQL v47 기준은 유지한다.
- `RTC8790_8791`: 기존 v141 유지
- `PHP Yellow API`: `/var/www/html/i`, API v47.0.0; `media_stat_hour_upsert`는 최대 500개를 transaction으로 master+1시간 이력에 저장하고 `media_stat_hour_list`는 동일 shortcode/hour의 서버 전체 최신 관측 1건만 반환한다; `schema_check`의 테이블 확인은 빈 이름을 차단한 뒤 단순 `SHOW TABLES LIKE`로 처리하고, 호환 SQL은 동적 SQL 없이 필요한 `ALTER TABLE` 문장만 제공

실행 주소:

```text
http://127.0.0.1:8780/
http://127.0.0.1:8780/?account=계정명
```

실행 파일:

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


## 2-0. 플랫폼 탭 연결·자동 실행 범위 기준

- 새 자동 세션 시작 시 **현재 활성 Instagram 탭 한 개만** 시작 `tab_id`로 보존한다. 열린 Instagram 탭 전체 검색, 마지막 연결 탭 복구, 후보 탭 순회는 사용하지 않는다.
- 계정 목록 내부의 다음 계정 이동은 새 세션이 아니므로 같은 시작 `tab_id`와 전체 실행 집계를 유지한다.
- 시작 탭이 다른 탭·Chrome 창 때문에 비활성화되면 `사용자가 다른 탭 또는 Chrome 창을 사용 중`으로 무한 대기한다. 원래 탭이 다시 활성화되면 같은 단계에서 자동 재개한다.
- 같은 시작 `tab_id`가 Instagram 외 주소·`about:blank`·로딩 화면으로 바뀌면 중단하지 않고 Instagram 주소 복귀를 무한 대기한다. 시작 탭 자체가 닫힌 경우만 실제 자동중단한다.
- WS8771, Main Injection, rank label, `NoNav_Current`가 일시 미준비이면 `_St.zip 다운로드·실행` 안내 영역을 경고 상태로 전환해 사유와 계정 위치를 표시하고 무한 대기한다. 정상화 시 `자동 재개됨`을 표시한 뒤 이어서 진행한다.
- 기간 필터 끝, All time 하단, 여러 swipe 후 증가 없음, 조건 대상 0개가 확정된 경우는 대기·중단이 아니라 현재 계정을 정상 종료하고 다음 URL로 이동한다.
- 계정 URL 이동은 v249 방식으로 명령 전송 후 4.5초 대기하고 현재 상태를 다시 읽는다. v250의 전체 탭 검색·마지막 탭 복구·18초 owner 강제 gate는 되살리지 않는다.
- No 이동 불일치가 발생하면 v249 수집 캐시 보완 경로를 사용할 수 있다. 실제 rank·shortcode 불일치로 잘못된 게시물을 받을 위험이 확인된 경우만 중단한다.

## 2-1. 수집계정 저장·삭제 기준

- 입력은 `account`, `@account`, Instagram 프로필 URL을 허용하되 저장 표시를 `@account`로 통일한다.
- 일반 `💾 저장`은 현재 화면 순서를 그대로 저장하고 대소문자와 입력 형식이 다른 중복을 하나로 합친다. username 오름차순 정렬은 별도 `정렬` 버튼에서만 실행한다.
- 삭제 계정은 `🗑️ @account`로 보존하며 동일 계정의 활성 입력보다 삭제 상태가 우선한다.
- 삭제 계정에서는 Instagram 프로필 URL을 만들지 않고 자동 계정 이동 대상으로 사용하지 않는다.
- PC 공통 원본은 Analyze8780·Shared SQLite이며 localStorage는 임시 캐시일 뿐 원본이 아니다.


## 2-2. Injection 수동 다운로드 메타 기준

- `Download Image`·`Download Video`는 Injection이 이미 수집한 shortcode 전체 객체를 그대로 기준으로 한다. Side rank label은 순위·정렬 스냅샷 보강값일 뿐 통계 원본을 대체하지 않는다.
- 공통 필드는 `shortcode`, `asset_kind`, `media_index`, `owner_username`, `thumbnail_url`, `video_url`, `upload_at`, `caption`, `views`, `likes`, `comments`, `reposts`, `sort`, `stable_identity`다.
- 파일과 목록의 식별키는 `shortcode + asset_kind + media_index`다. carousel은 media_index를 각각 전달한다.
- WS가 기존 로컬 JSON 항목을 갱신할 때 새 payload에서 실제로 제공되지 않은 통계·URL은 기존 정상값을 유지한다. `metric_presence=true`인 명시적 0은 실제 0으로 반영한다.
- 이미 다운로드된 파일을 다시 누르면 파일은 받지 않아도 현재 shortcode 메타를 기존 JSON·보고서에 병합한다.
- 성공 응답은 shortcode·asset_kind·media_index·원본 meta를 MAIN으로 되돌리고, 정확히 일치하는 Injection 버튼만 `✅`로 바꾼다.

## 3. IG 표시 기준

### 메인 주입 화면

```text
IG double_king0.1
```

- 계정명이 없으면 `IG`만 표시한다.
- Side에는 `No000`, Rank badge와 Side 전용 순위 설정을 유지한다.
- Main에는 `No000`, Rank badge, 순위용 미디어 종류 행, `1/Prev/Next/마지막` 버튼을 표시하지 않는다.
- Main 순위 표시·Main Rank badge 설정·Main 순위 체크값을 저장하거나 복원하지 않는다.
- 자동실행의 현재 순번은 화면 텍스트가 아니라 내부 상태와 shortcode 카드 연결로만 관리한다.
- Main 날짜 색상과 통계별 하단 막대 등 순위와 무관한 기존 시각 설정은 유지한다.
- 자동실행 현재 대상의 노란 강조와 Explore hover의 청록 강조는 유지한다.
- Main 우측 원점·HOVER·DEV·RTC 상시 영역은 숨기고 진단 패널과 `DOM 진단복사`만 유지한다.

### Side 게시물 카드

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

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

### Analyze8780 게시물 카드

- 기존 게시물 새 창 링크는 `rel="noopener noreferrer"`를 사용한다. 기존 `noopener`를 제거하지 않는다.
- 게시물 이미지 위에 로컬 저장 완료 종류를 `JPG`, `MP4`, `JPG · MP4` 배지로 직접 표시한다.
- 미다운로드 게시물은 배지 없이 표시하며 목록·통계 수집은 그대로 유지한다.
- 이미지 요소만 16:9와 `object-fit:cover`를 적용한다. 카드 전체·텍스트·통계·버튼 영역 비율은 변경하지 않는다.


## 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·계정 화면의 순위를 서로 합치지 않는다.
- 조회수 정렬에서 `play_count`가 실제로 있는 게시물은 조회수 내림차순을 유지한다. 조회수가 비공개·미제공인 게시물은 활성화된 좋아요·댓글·리포스트 최소 조건을 모두 AND 통과한 경우에만 후보로 유지하며, 순위 점수는 활성 지표 원값의 단순합산이다. 입력값 0인 지표와 공유 수는 참여점수에서 제외한다. 조회수 기준 입력 자체가 0이면 조회수를 순위에서도 제외하고 활성 참여점수만 사용한다.
- 조회수 기준값이 양수인데 게시물 조회수가 미제공인 경우, 하트·댓글·리포스트 최소 입력 중 하나 이상이 활성화되고 활성 조건을 모두 AND 통과할 때만 후보로 유지한다. 자동 Top 대상에는 최종 순서대로 `No001~Top목표`를 정상 부여한다.
- 공유 수는 검증 전까지 입력값·표시값·통계 payload·서버 payload를 모두 0으로 고정하고 Side UI와 Sort by에서 `d-none/disabled` 처리한다. 공유는 필터와 참여점수에 사용하지 않는다.

## 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
실제 ndy_side_shop 인젝션 수신
→ 현재 source URL 기준 계정/shortcode scope 필터·중복 제거
→ Analyze8780 `/api/observations/batch`
→ SQLite v7 latest/history/10분 bucket 저장
→ 통계 단독: 로컬 처리 종료
→ 통계 연동: 계정별 UTC 1시간 bucket 최초 1회 WS8771 batch 요청
→ PHP `media_stat_hour_upsert` transaction으로 ig_media 최신값+ig_media_stat_hour 저장
→ PHP `media_stat_hour_list`로 같은 계정·bucket의 shortcode별 최신 관측 수신
→ `source_kind=SERVER`로 로컬 10분 저장소에 병합
→ 송신·수신 전체 성공 때만 시간 완료키 저장
```

- URL 변경 시 인젝션 미디어 캐시를 초기화하고 Side에서도 현재 프로필 계정 또는 단일 shortcode를 다시 확인한다.
- 같은 collector+shortcode+UTC 1시간 bucket은 UPSERT하고 같은 observed_at 재시도는 sample_count를 올리지 않는다. 더 최신 observed_at만 수치와 정렬값을 갱신한다.
- `views_available`로 실제 0회와 조회수 미제공을 구분한다.
- Side 재실행·마지막 화면 복원만으로 observation을 다시 저장하지 않는다.
- 통계 연동은 로컬 파일 경로·직접 thumbnail/video CDN URL을 전송하지 않는다.
- 과거 전체 backfill·페이지네이션·통계 차트·영구 재시도 큐는 후속 작업이다.

v237 자동 다운로드는 실제 Windows 로그에서 No001·No002의 MP4/JPG·JSON·HTML 저장까지 확인됐다. v248 현재 페이지 10분 수집·중앙 1시간 이력 송수신은 코드·단위 테스트만 통과했으며 실제 Windows/Chrome/운영 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-1-1. 서버 schema_check v241 기준

- 2026-07-24 실제 Windows 로그에서 HTTP 500 응답 body 보존이 확인됐다.
- 실패 단계는 `schema_check`, 원인은 `SHOW TABLES LIKE ?`의 `?` 문법 오류였다.
- 테이블명은 빈 문자열을 먼저 차단하고 `yellow_safe_table()` 검증 후 `SHOW TABLES LIKE '테이블명'`으로 직접 조회한다.
- `yellow_schema_v46_compat.sql`에는 `PREPARE`, `EXECUTE`, `information_schema` 기반 동적 SQL을 사용하지 않는다.
- 운영 MySQL의 세 테이블 구조는 사용자가 제공한 `SHOW CREATE TABLE` 기준으로 v46 필수 컬럼과 일치한다.
- 실제 `media_insert` HTTP 200은 v241 서버 파일 배포 후 재검증한다.

## 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][체크실패] ... / 다음단계=중단 / 상세로그=... / 검색어=*** 상세로그 *** ... / 실패로그=...
```

## 5-3. v240 서버 등록 진단 기준

2026-07-22 실제 Windows 로그 판정:

- `syanjajsmartcleaning`, `lotor_food_studio`의 게시물 4개에서 MP4 4개와 JPG 4개, 총 8개 로컬 저장 성공
- `DIRECT_DOWNLOAD_OK`, `MEDIA_SAVED_OK`, JSON·MD5·HTML 보고서 갱신 확인
- 서버 `media_exists`는 `ok=1`, `exists=0`으로 정상
- 서버 `media_insert`만 모든 자산에서 HTTP 500 실패

v240 진단 흐름:

```text
WS HTTPError body 보존
→ PHP schema_check
→ master_upsert
→ user_download_upsert
→ collect_event_upsert
→ 실패 단계·테이블·누락 컬럼·SQL 오류 코드 반환
```

- 운영 MySQL에는 `yellow_schema_reset.sql`을 적용하지 않는다.
- `yellow_schema_v46_compat.sql`은 DROP/TRUNCATE/DELETE 없이 `ig_media_collect_event`의 누락 컬럼과 `event_key` unique key만 보완한다.
- 실제 운영 DB `media_insert OK` 로그가 오기 전 서버 등록은 `[검증대기]`다.
- AD-03 복구로그와 AD-06·AD-07 최종 판정은 이번 v240 목표에서 제외하고 다음 패치로 유지한다.

## 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을 계정으로 해석하지 않는다.
- `표시계정추가` 버튼은 사용하지 않는다. Side 카드의 `IG 계정명` 영역을 클릭하면 그 카드의 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 저장 버튼 동작은 `[검증대기]`다.

### v240 바로 다음 검증

- 서버에 `/var/www/html/i` 파일 배포
- 토큰 인증 후 `mode=schema_check` 실행
- `schema_ok=0`이면 `yellow_schema_v46_compat.sql` 적용
- 동일 게시물 `media_insert` 재실행 후 HTTP 200·`ok=1` 확인
- 그 다음 AD-03 복구·AD-06/07 최종 판정 패치 진행

### 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`만 읽으면 현재 기준과 다음 작업을 알 수 있어야 한다.

## 5-1. 다음 대형 작업 — 사용자 간 통계 공유·10분 집계

- 이 기능은 Side 단독 수정이 아니라 IG→WS8771→Shared SQLite→PHP/MySQL→Analyze8780/Side의 다단계 작업으로 분리한다.
- 1단계: 한 계정 처리가 끝난 뒤 게시물별 `account, shortcode, source_scope, sort_by, No, views, likes, comments, reposts, ER, published_at, collected_at`을 한 배치로 WS8771에 전달한다.
- 2단계: WS8771은 `batch_id + 수신 개수 + 실패 개수` ACK를 반환한다. ACK가 OK일 때만 로컬 SQLite에 전달 완료를 기록한다.
- 전달 완료 시각은 현재 시각을 10분 단위로 내림 처리한 bucket으로 저장한다. 예: `14:27 → 14:20`. 브라우저 localStorage만을 원본으로 사용하지 않는다.
- 3단계: WS8771이 PHP API로 전송하고 서버는 `계정 + shortcode + sort_by + 10분 bucket + collector/device` 고유키로 UPSERT한다.
- 4단계: 서버는 일자별·계정별·sort_by별 집계를 제공하고 Analyze8780 또는 Side가 이를 조회해 시각화한다.
- 공유 데이터에는 Instagram 쿠키·세션·토큰, 로컬 절대경로, JPG/MP4 원본 URL을 포함하지 않는다.
- 대량 기능이므로 `배치 수집 → WS ACK → 로컬 전달 flag → PHP 저장 → 서버 집계 → UI 시각화` 순서로 각각 별도 버전과 실제 운영 로그를 받아 완료 처리한다.
