diff --git a/CLAUDE.md b/CLAUDE.md index 489de4ac..6785f7ab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -128,7 +128,7 @@ Stock agent (`agents/stock/workspace/scripts/`), run with `python3`: **메모 모달 (`note-modal`)**(2026-06-26) — 보유·관심·감시종목 행 모두에 `📝 메모` 버튼. 클릭 → 팝업 textarea(최대 1000자, 여러 줄)에 기존 메모 prefill → [저장]은 `/notes/set` POST(submit 핸들러가 `note-form`을 fetch로 가로채 **현재 탭 유지** — 네이티브 폼이면 303→`/` 리다이렉트로 자산정보 탭으로 튐) 후 패널 새로고침, [삭제]는 버튼 왼쪽 위 작은 **확인 팝업(예/아니오)** 후 빈 텍스트 submit으로 항목 제거(오삭제 방지, `note-confirm`). 메모 있으면 버튼이 `📝 메모 ●` 노란 강조(`has-note`)+hover 툴팁. 저장소 `state/behive_stock_notes.json`(`{by_code, by_name}`, code 우선→name fallback, `stock_tags`와 동일한 fcntl lock·atomic write). **종목 단위 공용**(계좌 분리 X). 백엔드 `_load/_get/_set_stock_note`·`_note_button_html`·`_render_note_modal`, 태그 모달 패턴 복제. - **공모주 sub-tab**(2026-10-02) — 자산정보 탭 하단 `[공모주]`(4번째). **타이틀 배지**: 헤더 `자산현황` 옆 `#ipo-flag` 텍스트 배지 `[청약]` **하나로 통일**(관리자님 지정 — 사유별로 나누지 않는다, 사유는 툴팁) — ①청약기간 중인데 청약완료 안 한 종목 ②청약완료한 종목의 오늘 상장, 둘 중 하나라도 있으면 표시(관리자님 지정, 클릭 동작 없음, 사유는 title 툴팁). `_ipo_flag()` 가 `/api/panels` 응답 `ipo_flag` 로 실어 `apply()` 가 토글 — 셸은 데이터 fetch 없는 원칙이라 첫 패널 로드 뒤에 뜬다. 종목 카드는 `
` **기본 접힘**(요약줄 = 종목명·시장·청약기간·증권사 수·마감일/완료 배지), 펼침 상태는 `apply()` 의 패널 swap 때 `data-ipo-key` 로 복원(`details.row` 복원과 별도 — 공모주 카드는 종목 행이 아니라 `row` 클래스를 쓰면 종목코드 기반 셀렉터들과 엮인다). ①**오늘청약** = 청약기간(`poStartDate ≤ 오늘 ≤ poEndDate`) 중인 종목 전부를 **증권사별 아이템**(청약일·증거금(최소 수량)·수수료·상장일, 마지막 날 `마감일` 배지)으로 ②**청약완료** = 기록된 종목을 **상장일까지**(미정이면 계속) 표시. `[청약완료]` → 작은 확인 팝업(`.note-confirm` 재사용) → 예 → `POST /ipo/subscribe`(fetch, JSON) → `state/behive_ipo_subscribed.json`(`{ipoCode:{name,po_start,po_end,lcal_date,by:{self|gahee:{broker,at}},interest_added?}}`, fcntl lock+atomic). **사람별 버튼 2개**(2026-10-02 관리자님 지시로 종목 단위 1건 → 사람별로 변경): 아이템마다 `[관리자(청약)] [가희(청약)]`, 중복청약 금지가 1인 기준이라 **사람별 종목당 1건** — 누르면 그 사람의 버튼만 그 종목 모든 증권사에서 `✅ 관리자(완료)` 로 바뀐다(청약한 증권사 카드만 초록 강조, 증권사명은 청약완료 섹션에 — 관리자님 지정 문구). 이미 기록된 사람은 덮어쓰지 않는다. 옛 형식(종목 단위 `broker/at`)은 `_load_ipo_subscribed` 가 관리자 기록으로 읽는다(멜콘 1건 그렇게 이관). 타이틀 [청약] 배지의 미완료 판정은 **한 명이라도 했으면 완료**(관리자님 지정). **되돌리기 버튼 없음**(잘못 눌렀으면 state 파일에서 제거). 데이터원은 `ipo_alert.py` 와 같은 네이버 IPO API — 목록(`ipo_calendar_sync._naver_api_rows`)·상세(`ipo_alert._detail`) 각 30분 캐시, **증거금·수수료는 `ipo_alert._manager_blocks` 를 그대로 import**해 텔레그램 알림과 숫자가 항상 같다(수수료 표 `BROKER_ONLINE_FEES` 도 거기 한 곳). 상세 조회는 청약기간 중·완료 종목만, **키움 콜 0**. ⚠️ 예전 공모주 조회 실패는 `finance.naver.com/sise/ipo.naver` HTML 스크래핑 시절 얘기고 2026-09-30 경 JSON API 로 교체된 뒤엔 정상(실측). 렌더 예외는 `_render_summary_panel` 이 잡아 공모주 패널만 에러 문구. **상장일 관심종목 자동등록**: 청약완료한 종목은 상장일 아침 `ipo_alert.py listing`(launchd `stock.ipo-alert` 08:30, 기존 트리거 재사용 — 새 트리거 없음)이 `behive_web.register_listed_ipo_interests()` 를 불러 관심종목에 넣는다(코드 = ipoCode 앞 `A` 제거, 메모 `공모주 청약 (관리자 대신증권, 가희 하나증권)`). 종목당 1회 — 기록에 `interest_added` 를 남기고, 이미 관심종목이면 성공으로 친다. 상장일 판정은 API 상장 목록 ∪ 기록의 `lcal_date` 라 **네이버 조회가 실패해도 기록으로 시도**한다(그래서 `ipo_alert` 가 소스 실패 시 바로 return 하지 않는다). `--dry-run` 이면 건너뜀. 하단 탭 버튼이 4개가 되면서 `.sub-tabs-bottom .sub-tab` 을 `flex:1 1 0; max-width:110px; nowrap` 으로 바꿨다(360px 에서 '자산보 기'로 꺾이던 것 — 320px 넘침 0 실측). + **공모주 sub-tab**(2026-10-02) — 자산정보 탭 하단 `[공모주]`(4번째). **타이틀 배지**: 헤더 `자산현황` 옆 `#ipo-flag` 텍스트 배지 `[청약]` **하나로 통일**(관리자님 지정 — 사유별로 나누지 않는다, 사유는 툴팁) — ①청약기간 중인데 청약완료 안 한 종목 ②청약완료한 종목의 오늘 상장, 둘 중 하나라도 있으면 표시(관리자님 지정, 클릭 동작 없음, 사유는 title 툴팁). `_ipo_flag()` 가 `/api/panels` 응답 `ipo_flag` 로 실어 `apply()` 가 토글 — 셸은 데이터 fetch 없는 원칙이라 첫 패널 로드 뒤에 뜬다. 종목 카드는 `
` **기본 접힘**(요약줄 = 종목명·시장·청약기간·증권사 수·마감일/완료 배지), 펼침 상태는 `apply()` 의 패널 swap 때 `data-ipo-key` 로 복원(`details.row` 복원과 별도 — 공모주 카드는 종목 행이 아니라 `row` 클래스를 쓰면 종목코드 기반 셀렉터들과 엮인다). ①**오늘청약** = 청약기간(`poStartDate ≤ 오늘 ≤ poEndDate`) 중인 종목 전부를 **증권사별 아이템**(청약일·증거금(최소 수량)·수수료·상장일, 마지막 날 `마감일` 배지)으로 ②**청약완료** = 기록된 종목을 **상장일까지**(미정이면 계속) 표시. `[청약완료]` → 작은 확인 팝업(`.note-confirm` 재사용) → 예 → `POST /ipo/subscribe`(fetch, JSON) → `state/behive_ipo_subscribed.json`(`{ipoCode:{name,po_start,po_end,lcal_date,by:{self|gahee:{broker,at}},interest_added?}}`, fcntl lock+atomic). **사람별 버튼 2개**(2026-10-02 관리자님 지시로 종목 단위 1건 → 사람별로 변경): 아이템마다 `[관리자(청약)] [가희(청약)]`, 중복청약 금지가 1인 기준이라 **사람별 종목당 1건** — 누르면 그 사람의 버튼만 그 종목 모든 증권사에서 `✅ 관리자(완료)` 로 바뀐다(청약한 증권사 카드만 초록 강조, 증권사명은 청약완료 섹션에 — 관리자님 지정 문구). 이미 기록된 사람은 덮어쓰지 않는다. 옛 형식(종목 단위 `broker/at`)은 `_load_ipo_subscribed` 가 관리자 기록으로 읽는다(멜콘 1건 그렇게 이관). 타이틀 [청약] 배지의 미완료 판정은 **한 명이라도 했으면 완료**(관리자님 지정). **되돌리기 버튼 없음**(잘못 눌렀으면 state 파일에서 제거). 데이터원은 `ipo_alert.py` 와 같은 네이버 IPO API — 목록(`ipo_calendar_sync._naver_api_rows`)·상세(`ipo_alert._detail`) 각 30분 캐시, **증거금·수수료는 `ipo_alert._manager_blocks` 를 그대로 import**해 텔레그램 알림과 숫자가 항상 같다(수수료 표 `BROKER_ONLINE_FEES` 도 거기 한 곳). 상세 조회는 청약기간 중·완료 종목만, **키움 콜 0**. ⚠️ 예전 공모주 조회 실패는 `finance.naver.com/sise/ipo.naver` HTML 스크래핑 시절 얘기고 2026-09-30 경 JSON API 로 교체된 뒤엔 정상(실측). 렌더 예외는 `_render_summary_panel` 이 잡아 공모주 패널만 에러 문구. **상장일 관심종목 자동등록**: 청약완료한 종목은 상장일 아침 `ipo_alert.py listing`(launchd `stock.ipo-alert` 08:30, 기존 트리거 재사용 — 새 트리거 없음)이 `behive_web.register_listed_ipo_interests()` 를 불러 관심종목에 넣는다(코드 = ipoCode 앞 `A` 제거, 메모 `공모주 청약 (관리자 대신증권, 가희 하나증권)`). 종목당 1회 — 기록에 `interest_added` 를 남기고, 이미 관심종목이면 성공으로 친다. 상장일 판정은 API 상장 목록 ∪ 기록의 `lcal_date` 라 **네이버 조회가 실패해도 기록으로 시도**한다(그래서 `ipo_alert` 가 소스 실패 시 바로 return 하지 않는다). `--dry-run` 이면 건너뜀. 하단 탭 버튼이 4개가 되면서 `.sub-tabs-bottom .sub-tab` 을 `flex:1 1 0; max-width:110px; nowrap` 으로 바꿨다(360px 에서 '자산보 기'로 꺾이던 것 — 320px 넘침 0 실측). **비하이브 공모청약 영상 요약**(2026-10-07): 카드 펼침 상단(청약완료 카드는 kv 아래)에 `🐝 비하이브 의견` 블록 — 결론 한 구절(긍정 초록·중립 회색·부정 빨강 왼쪽 띠) + 핵심 3~4줄 + 영상 링크. 생산은 `ipo_youtube_digest.py` — 채널 영상 목록에서 제목 `공모청약` 필터 → 처리 안 한 영상만 자막 → LLM(`openai/gpt-5.6-sol`) → `state/ipo_youtube_summaries.json`(영상 단위, `done`·`attempts`). ⚠️ **영상↔종목 매칭은 제목 해시태그로 하지 않는다** — 월간 일정 영상처럼 한 영상이 여러 종목을 다루므로, 네이버 IPO 후보 목록(상장 2주 이내까지)을 LLM 에 주고 **ipoCode 로 돌려받아** 목록 밖 코드는 버린다. 한 종목에 영상이 여럿이면 **제목 `#종목명` 전용 영상 우선 → 최신순** 1건만 본문, 나머지는 링크. 트리거는 **기존 `stock.ipo-alert`(08:30·10:00)에 얹었다** — `ipo_alert.main` 끝에서 `run()`(dry-run 제외, 실패는 stderr 만). 새 영상 없으면 채널 페이지 1콜로 끝나고, 렌더 경로는 파일 읽기만(LLM·네트워크 0). 자막 미생성·LLM 실패는 `attempts` 증가 후 다음 실행에서 재시도, 6회(≈3일)면 포기. ⚠️ **LLM 은 `~/.local/bin/openclaw` 절대경로로 부른다** — launchd PATH 에선 `openclaw` 가 `/opt/homebrew/bin`(node22)로 잡혀 안 뜬다. 한계: 하루 2회라 10:00 이후 올라온 영상은 다음 날 08:30 에 붙는다. CLI: `python3 ipo_youtube_digest.py [run|show] [--dry-run]`. **급등락 알림 — 표시자 🔔 + `[🔔 알림]` 버튼 + 팝업**(2026-08-04 도입 → **2026-08-06 개편**) — 처음엔 `detail-name` 줄의 **아이콘 전용 토글**이었으나, 관리자님 지시로 **아이콘은 on/off 표시만 하고 조작은 팝업 안으로** 옮겼다(아이콘 하나뿐이라 `title` 툴팁이 유일한 설명이었고 — 모바일엔 hover가 없다 — 오조작 위험도 있었다). 구성 3개: - **표시자**(`_surge_indicator_html`) — `detail-name` 줄 종목명 옆의 **비대화형 ``**. 꺼짐 🔕(opacity 0.45 + grayscale)·켜짐 🔔(초록 배경). ⚠️ **`data-surge-toggle` 을 달지 말 것** — 그 속성이 있어야 위임 핸들러가 잡으므로, 없어야 클릭이 무반응이다. CSS `.detail-name .surge-ind` 에 `cursor/hover/active` 를 주지 않는 것도 같은 이유(누를 수 있어 보이면 안 된다).