0a97324f1b
R56–R59: agent.ts 2731→1529줄 god-file 분해 (25 modules) · attrParsers + LLM 메서드 8개 (callNonStreaming, streamChatOnce 등) · executeActions 415줄 → 8 handler 그룹 (file/run/list/brain/calendar/sheets/tasks) · handlePrompt 1100줄 → 7 phase 모듈 (system prompt + budget + autoContinue 등) R50–R55: extension.ts 1145→349줄 (telegram/settings/provider commands 분리) Stocks feature 신규: /stocks slash command (v2.2.152~158) · .astra/stocks.json 저장소 + Yahoo Finance 현재가 갱신 · 8 키워드 필터 (ROE/성장성/유동성/수익성/영업효율/기술력/안정성/PBR) · Naver 시가총액 페이지 JSON API (m.stock.naver.com) 발굴 · LLM Top 5 매력도 분석 + Telegram 자동 보고서 · KST 09:00/15:00 watcher 자동 모니터링 대화 연속성 (v2.2.150~157): · [PRIOR TURN CONCLUSION] block 으로 직전 결론 anchor · thin follow-up 분류 → boilerplate 헤더 suppression · slash 명령 결과 chatHistory mirror (capture wrapper) · echo/parrot 금지 system prompt rule 기타: /stocks 슬래시 자동완성 dropdown UI, Naver JSON API 전환 (cheerio 제거) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
100 lines
4.1 KiB
TypeScript
100 lines
4.1 KiB
TypeScript
import * as vscode from 'vscode';
|
|
import { writeSheetRange, type SheetValues } from '../sheets';
|
|
import { logError, logInfo } from '../../utils';
|
|
import { classifyAll } from './signalClassifier';
|
|
import { readStocksStore } from './stocksStore';
|
|
import type { ClassifiedStock } from './types';
|
|
|
|
/**
|
|
* 분류된 종목 리스트 → Google Sheets 동기화.
|
|
*
|
|
* invest_results/gs_update_api.js 의 시트 레이아웃을 유지:
|
|
* - Sheet1!A1:O = 스윙/중기
|
|
* - Sheet2!A1:O = 장기투자
|
|
* - Sheet3!A1:O = 저평가우량주
|
|
* (시트 이름은 spreadsheet 의 1/2/3번째 탭 — 사용자가 본인 spreadsheet 에 미리 만들어 둠.)
|
|
*
|
|
* spreadsheet ID 는 설정 `g1nation.stocks.spreadsheetId` 에서 읽음. 미설정이면 skip 안내.
|
|
* OAuth token 은 calendar 와 공유 (oauth.ts SCOPE 에 spreadsheets 이미 포함).
|
|
*/
|
|
|
|
const HEADER = ['종목명', '심볼', '상장일', '현재가', '적정주가', '매수권장가', '3/4 필터', '매수 신호', 'ROE', 'PBR', '영업이익률', '유보율', 'PER', 'EPS', '특이사항'];
|
|
|
|
function buildSheetRows(classified: ClassifiedStock[]): SheetValues {
|
|
const rows: SheetValues = [HEADER];
|
|
for (const s of classified) {
|
|
rows.push([
|
|
s.이름,
|
|
s.심볼,
|
|
s.상장일 ?? '',
|
|
s.현재가 ?? 0,
|
|
s.적정주가 ?? '',
|
|
s.매수권장가 ?? '',
|
|
s.filterPass ? 'Pass' : 'Fail',
|
|
s.signalText,
|
|
s['ROE(25E)'] ?? '',
|
|
s.PBR ?? '',
|
|
s['영업이익률(25E)'] ?? '',
|
|
s.유보율 ?? '',
|
|
s['PER(25E)'] ?? '',
|
|
s['EPS(25E)'] ?? '',
|
|
s.특이사항 ?? '',
|
|
]);
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
/**
|
|
* 3 시트 일괄 동기화. 결과는 시트별 성공/실패 카운트로 반환 — caller 가 webview 에 표시.
|
|
*
|
|
* Sheet 이름은 1/2/3번째 탭 인덱스가 아니라 *이름* 으로 range 를 짜야 안전.
|
|
* 사용자가 본인 spreadsheet 의 탭 이름을 코드 기본값과 맞추도록 안내:
|
|
* 'Sheet1' / 'Sheet2' / 'Sheet3' — 또는 설정으로 override 가능.
|
|
*/
|
|
export async function syncToSheets(
|
|
context: vscode.ExtensionContext,
|
|
): Promise<{ ok: boolean; errors: string[]; updatedRanges: string[] }> {
|
|
const cfg = vscode.workspace.getConfiguration('g1nation');
|
|
const spreadsheetId = (cfg.get<string>('stocks.spreadsheetId') || '').trim();
|
|
if (!spreadsheetId) {
|
|
return {
|
|
ok: false,
|
|
errors: ['Settings 에 g1nation.stocks.spreadsheetId 가 설정되지 않았습니다.'],
|
|
updatedRanges: [],
|
|
};
|
|
}
|
|
const sheetSwing = (cfg.get<string>('stocks.sheetSwing') || 'Sheet1').trim();
|
|
const sheetLong = (cfg.get<string>('stocks.sheetLong') || 'Sheet2').trim();
|
|
const sheetUltra = (cfg.get<string>('stocks.sheetUltraLow') || 'Sheet3').trim();
|
|
|
|
const store = readStocksStore();
|
|
const groups = classifyAll(store);
|
|
|
|
const errors: string[] = [];
|
|
const updatedRanges: string[] = [];
|
|
|
|
const tasks: Array<{ tab: string; rows: SheetValues; label: string }> = [
|
|
{ tab: sheetSwing, rows: buildSheetRows(groups.swing), label: '스윙/중기' },
|
|
{ tab: sheetLong, rows: buildSheetRows(groups.long), label: '장기투자' },
|
|
{ tab: sheetUltra, rows: buildSheetRows(groups.ultraLow), label: '저평가우량주' },
|
|
];
|
|
|
|
for (const t of tasks) {
|
|
if (t.rows.length <= 1) continue;
|
|
const range = `${t.tab}!A1:O${t.rows.length}`;
|
|
try {
|
|
const r = await writeSheetRange(context, spreadsheetId, range, t.rows);
|
|
if (r.ok) {
|
|
updatedRanges.push(r.updatedRange);
|
|
logInfo(`Stocks sheets sync: ${t.label} OK.`, { range: r.updatedRange, cells: r.updatedCells });
|
|
} else {
|
|
errors.push(`${t.label}: ${r.error}`);
|
|
logError(`Stocks sheets sync: ${t.label} 실패.`, { error: r.error });
|
|
}
|
|
} catch (e: any) {
|
|
errors.push(`${t.label}: ${e?.message ?? String(e)}`);
|
|
}
|
|
}
|
|
return { ok: errors.length === 0, errors, updatedRanges };
|
|
}
|