From 6c5f3ff681f9d69d1d8647de48a8cd5b79c4bbb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SH=20KWON=20=F0=9F=87=B0=F0=9F=87=B7?= Date: Mon, 25 May 2026 23:58:43 +0900 Subject: [PATCH 01/70] Add /exit slash command (#1) Co-authored-by: Claude Opus 4.7 (1M context) --- src/cli.ts | 5 +++++ src/commands/index.ts | 1 + 2 files changed, 6 insertions(+) diff --git a/src/cli.ts b/src/cli.ts index c345d705c..374a4187b 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -333,6 +333,7 @@ export async function runCli() { /search Choose preferred web search provider /rules Show research rules /clear Clear conversation + /exit Exit Dexter โ†‘ / โ†“ Navigate input history`; const handleSlashCommand = async (command: string) => { @@ -387,6 +388,10 @@ export async function runCli() { chatLog.addChild(new Text(theme.muted(HELP_TEXT), 0, 0)); tui.requestRender(); break; + case 'exit': + case 'quit': + tui.stop(); + process.exit(0); } }; diff --git a/src/commands/index.ts b/src/commands/index.ts index d7741c0d1..5e116878e 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -12,6 +12,7 @@ export const SLASH_COMMANDS: SlashCommand[] = [ { name: 'heartbeat', description: 'Show your heartbeat monitoring checklist' }, { name: 'history', description: 'Show recent conversation summaries' }, { name: 'help', description: 'Show keyboard shortcuts and tips' }, + { name: 'exit', description: 'Exit Dexter' }, ]; /** From d2d861512d38ff4c6d7b76397fb424c20d7ce01e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SH=20KWON=20=F0=9F=87=B0=F0=9F=87=B7?= Date: Tue, 26 May 2026 04:22:53 +0900 Subject: [PATCH 02/70] Add Korean stock research section and CLAUDE.md (#2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - README: ๐Ÿ‡ฐ๐Ÿ‡ท ํ•œ๊ตญ ์ฃผ์‹ ๋ฆฌ์„œ์น˜ section with architecture + sequence diagrams (mermaid), tool table, DART setup, market-specific notes - CLAUDE.md: trap list, key contracts, KR adaptation roadmap (Phase 1โ€“4) Co-authored-by: Claude Opus 4.7 (1M context) --- CLAUDE.md | 85 +++++++++++++++++++++++++++++++++++++++++++++++ README.md | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..e711b2e5a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,85 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this is + +Dexter is a CLI financial research agent (Claude Code-style TUI). Iterative tool-calling loop against frontier LLMs (OpenAI/Anthropic/Google/xAI/OpenRouter/Ollama) + finance tools (Financial Datasets API, SEC, web/X search). + +Upstream: `github.com/virattt/dexter`. CalVer (`YYYY.M.D`). + +## Commands + +Runtime is **Bun** (not Node). + +```bash +bun install # postinstall runs playwright install chromium +bun start # run the TUI +bun run dev # watch mode +bun run typecheck # tsc --noEmit โ€” run before pushing +bun test # full suite +bun test path.test.ts # single file +``` + +CI runs `typecheck` + `test` on push/PR. + +## Traps to know + +These bite without warning. Fix once you learn them. + +- **TUI is `@mariozechner/pi-tui`, not Ink.** `src/index.tsx` has the `.tsx` extension for historical reasons; there is no JSX in the active CLI (`src/cli.ts`). `AGENTS.md` and `README.md` still say Ink โ€” trust the code. +- **Tools are conditionally registered by env var** (`src/tools/registry.ts`). Missing `EXASEARCH_API_KEY` โ†’ exa just isn't a `web_search` candidate. Missing `FINANCIAL_DATASETS_API_KEY` โ†’ all `get_*` finance tools absent. +- **`your-` is treated as a placeholder.** `checkApiKeyExists` (`src/utils/env.ts`) ignores values starting with `your-`. Copying `env.example` without replacing values = "key missing" behavior. +- **Tool output shape feeds the UI.** `summarizeToolResult` in `src/cli.ts` switches on `tool` name and inspects `parsed.data` shape. New finance tools must return `{ data: ... }` and need a matching case here, or the UI shows stale status lines. +- **`concurrencySafe` flag.** Each tool declares it; the executor parallelizes safe tools and serializes unsafe ones (browser, stateful APIs). Wrong value = subtle race conditions. +- **`bun.lock` churn.** `postinstall` sometimes touches one line. Don't bundle it into feature PRs. + +## Key contracts + +- **Agent loop**: `src/agent/agent.ts`, default `maxIterations: 10`. Scratchpad (`scratchpad.ts`) is single source of truth for tool results within a query. Final answer is a separate LLM call **with no tools bound**. +- **Slash commands**: append to `SLASH_COMMANDS` in `src/commands/index.ts` + add a case in `handleSlashCommand` in `src/cli.ts`. Help text lives next to that switch. +- **Skills**: drop a directory with `SKILL.md` under `src/skills/`. Auto-discovered; no code change needed. Each skill runs at most once per query. +- **Anthropic provider**: uses explicit `cache_control` on system prompt for prompt caching. Don't break that path. +- **`.dexter/` directory**: gitignored. Holds `settings.json` (model + search preference), `RULES.md`, `HEARTBEAT.md`, `memos/`, and the future `cache/` for the KR ticker registry. + +## Active Work / Roadmap + +**Current focus**: Korean stock support. See README "๐Ÿ‡ฐ๐Ÿ‡ท ํ•œ๊ตญ ์ฃผ์‹ ๋ฆฌ์„œ์น˜" for the user-facing spec. + +### Phase 1 โ€” Data pipe +- [ ] `src/data/fetchers/dart-corp-codes.ts` โ€” fetch + parse DART `corpCode.xml` +- [ ] `src/data/ticker-registry.ts` โ€” cache, 7-day refresh, ticker/name โ†’ corp_code lookup +- [ ] `src/tools/finance-kr/get-financials-kr.ts` โ€” first KR tool (DART ์‚ฌ์—…/๋ถ„๊ธฐ/๋ฐ˜๊ธฐ๋ณด๊ณ ์„œ) +- [ ] System prompt routing rules added to `src/agent/prompts.ts` + +### Phase 2 โ€” Disclosures +- [ ] `get_filings_kr` โ€” DART ๊ณต์‹œ ๊ฒ€์ƒ‰ +- [ ] `get_large_holders_kr` โ€” 5%๋ฃฐ (13F equivalent) +- [ ] `get_insider_trades_kr` โ€” ์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ + +### Phase 3 โ€” Korea-specific +- [ ] `get_nps_holdings` โ€” ๊ตญ๋ฏผ์—ฐ๊ธˆ ๋ถ„๊ธฐ ๊ณต์‹œ +- [ ] `get_short_balance_kr` โ€” KRX ๊ณต๋งค๋„ ์ž”๊ณ  +- [ ] `get_foreign_ownership_kr` โ€” ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ + +### Phase 4 โ€” Skill adaptation +- [ ] `src/skills/dcf` โ€” branch on market (K-IFRS, ๊ฑฐ๋ž˜์„ธ, ๋ฐฐ๋‹น์„ธ) +- [ ] New skill: ์žฌ๋ฒŒ ๊ทธ๋ฃน ๊ตฌ์กฐ / ๋ฌผ์ ๋ถ„ํ•  ๋ถ„์„ + +### Decided +- No `aliases.json`. LLM's training data covers common Korean tickers; resolver handles canonical ticker โ†’ corp_code only. +- KR tools live as separate names (`*_kr` suffix), not as a `market` parameter. LLM routes based on ticker pattern + language. +- Master files (DART `corpCode.xml`, SEC `company_tickers.json`) are runtime-fetched + cached, not bundled in repo. + +## Conventions + +- TypeScript strict, ESM. Avoid `any`. +- Don't add logging unless asked. Don't create `*.md` docs unless asked. +- Comments only when *why* is non-obvious. +- Tests colocated as `*.test.ts`, Bun runner (Jest config is legacy). + +## Stale docs + +- `AGENTS.md` is upstream's contributor doc with drift (says Ink/React, entry `cli.tsx`). +- `README.md` mentions Ink in places. The ๐Ÿ‡ฐ๐Ÿ‡ท section is current. +- When in doubt, read the code. diff --git a/README.md b/README.md index ed1b9350b..0d7ea0f98 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Dexter is an autonomous financial research agent that thinks, plans, and learns ## Table of Contents - [๐Ÿ‘‹ Overview](#-overview) +- [๐Ÿ‡ฐ๐Ÿ‡ท ํ•œ๊ตญ ์ฃผ์‹ ๋ฆฌ์„œ์น˜](#-ํ•œ๊ตญ-์ฃผ์‹-๋ฆฌ์„œ์น˜) - [โœ… Prerequisites](#-prerequisites) - [๐Ÿ’ป How to Install](#-how-to-install) - [๐Ÿš€ How to Run](#-how-to-run) @@ -45,6 +46,103 @@ Dexter takes complex financial questions and turns them into clear, step-by-step Screenshot 2026-02-18 at 12 21 25โ€ฏPM +## ๐Ÿ‡ฐ๐Ÿ‡ท ํ•œ๊ตญ ์ฃผ์‹ ๋ฆฌ์„œ์น˜ + +Dexter๋Š” ๋ฏธ๊ตญ ์ฃผ์‹๊ณผ ํ•œ๊ตญ ์ฃผ์‹์„ **ํ•˜๋‚˜์˜ ์—์ด์ „ํŠธ์—์„œ ๋™์‹œ์—** ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์‹œ์žฅ์„ ๋ช…์‹œํ•  ํ•„์š” ์—†์ด, LLM์ด ํ‹ฐ์ปค ํ˜•ํƒœ์™€ ํšŒ์‚ฌ๋ช… ์–ธ์–ด๋ฅผ ๋ณด๊ณ  ์•Œ๋งž์€ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ๊ณจ๋ผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. + +### ๋™์ž‘ ๋ฐฉ์‹ + +```mermaid +flowchart TB + Q[์‚ฌ์šฉ์ž ์ฟผ๋ฆฌ] --> LLM{LLM ๋ผ์šฐํŒ…} + + LLM -->|6์ž๋ฆฌ ์ˆซ์ž ํ‹ฐ์ปค
ํ•œ๊ตญ์–ด ํšŒ์‚ฌ๋ช…| KR[KR ํˆด] + LLM -->|์•ŒํŒŒ๋ฒณ ํ‹ฐ์ปค
์˜๋ฌธ ํšŒ์‚ฌ๋ช…| US[US ํˆด] + LLM -->|ํฌ๋กœ์Šค๋งˆ์ผ“ ๋น„๊ต| Both[์–‘์ชฝ ๋ชจ๋‘ ํ˜ธ์ถœ] + + KR --> DART[(DART API
๊ณต์‹œยท์žฌ๋ฌด)] + KR --> KRX[(KRX
์‹œ์„ธยท๊ณต๋งค๋„)] + + US --> FD[(Financial Datasets
์žฌ๋ฌดยท์‹œ์„ธ)] + US --> SEC[(SEC EDGAR
๊ณต์‹œ)] + + Both --> DART + Both --> FD + + DART --> Out[์ข…ํ•ฉ ๋‹ต๋ณ€] + KRX --> Out + FD --> Out + SEC --> Out +``` + +### ์‹ ๊ทœ ํˆด (DART ๊ธฐ๋ฐ˜) + +`OPEN_DART_KEY`๊ฐ€ ์„ค์ •๋˜๋ฉด ์ž๋™ ๋“ฑ๋ก๋ฉ๋‹ˆ๋‹ค. + +| ํˆด | ๋Œ€์‘๋˜๋Š” ๋ฏธ๊ตญ ๊ฐœ๋… | ๋ฐ์ดํ„ฐ ์†Œ์Šค | +|---|---|---| +| `get_financials_kr` | `get_financials` (10-K/10-Q) | DART ์‚ฌ์—…ยท๋ฐ˜๊ธฐยท๋ถ„๊ธฐ๋ณด๊ณ ์„œ | +| `get_filings_kr` | SEC EDGAR | DART ๊ณต์‹œ ๊ฒ€์ƒ‰ | +| `get_large_holders_kr` | 13F (5% ์ด์ƒ ๋ณด์œ ) | DART ๋Œ€๋Ÿ‰๋ณด์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ | +| `get_insider_trades_kr` | Form 4 (๋‚ด๋ถ€์ž ๊ฑฐ๋ž˜) | DART ์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ ๋ณด๊ณ  | +| `get_nps_holdings` | (๋ฏธ๊ตญ์—” ์—†์Œ) | ๊ตญ๋ฏผ์—ฐ๊ธˆ ๋ถ„๊ธฐ ๊ณต์‹œ | +| `get_short_balance_kr` | Short interest | KRX ๊ณต๋งค๋„ ์ž”๊ณ  | +| `get_foreign_ownership_kr` | (๋ฏธ๊ตญ์—” ์—†์Œ) | KRX ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ | + +### ์ข…๋ชฉ ์ฝ”๋“œ ํ•ด๊ฒฐ ํ๋ฆ„ + +LLM์ด "์‚ผ์„ฑ์ „์ž" ๊ฐ™์€ ์ž์—ฐ์–ด ์ž…๋ ฅ์„ ๋ฐ›์œผ๋ฉด, ํˆด ๋‚ด๋ถ€์—์„œ **ํ‹ฐ์ปค โ†’ corp_code** ๋ณ€ํ™˜์„ ๊ฑฐ์ณ DART API๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. + +```mermaid +sequenceDiagram + participant U as ์‚ฌ์šฉ์ž + participant L as LLM + participant T as get_financials_kr + participant R as TickerRegistry
(์บ์‹œ) + participant D as DART API + + U->>L: "์‚ผ์„ฑ์ „์ž ์žฌ๋ฌด ์–ด๋•Œ?" + L->>L: ํ•œ๊ตญ ์ข…๋ชฉ ์ธ์‹
โ†’ 005930์œผ๋กœ ์ •๊ทœํ™” + L->>T: get_financials_kr({ticker: "005930"}) + T->>R: resolve("005930") + R-->>T: corp_code: "00126380" + T->>D: ์‚ฌ์—…๋ณด๊ณ ์„œ ์กฐํšŒ (corp_code) + D-->>T: ์žฌ๋ฌด ๋ฐ์ดํ„ฐ + T-->>L: ์ •ํ˜•ํ™”๋œ ๊ฒฐ๊ณผ + L-->>U: ๋ถ„์„ ๋‹ต๋ณ€ ์ž‘์„ฑ +``` + +`TickerRegistry`๋Š” DART์˜ ๋งˆ์Šคํ„ฐ ํŒŒ์ผ(`corpCode.xml`)์„ **์ฒซ ์‹คํ–‰ ์‹œ ํ•œ ๋ฒˆ ๋‹ค์šด๋กœ๋“œ**ํ•ด `.dexter/cache/`์— ์ €์žฅํ•˜๊ณ , 7์ผ๋งˆ๋‹ค ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค. ์‹ ๊ทœ ์ƒ์žฅยท์‚ฌ๋ช… ๋ณ€๊ฒฝยท๋ฌผ์ ๋ถ„ํ• ์ด ์ž๋™ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. + +### ์„ค์ • + +`.env`์— ๋‹ค์Œ ํ•ญ๋ชฉ ์ถ”๊ฐ€: + +```bash +# ํ•œ๊ตญ ์ฃผ์‹ (ํ•„์ˆ˜) +OPEN_DART_KEY=your-dart-api-key +``` + +DART API ํ‚ค๋Š” [opendart.fss.or.kr](https://opendart.fss.or.kr/uss/umt/cmm/EgovMberInsertView.do)์—์„œ ๋ฌด๋ฃŒ ๋ฐœ๊ธ‰. ์ผ 10,000๊ฑด ํ˜ธ์ถœ ํ•œ๋„. + +### ํ•œ๊ตญ ์‹œ์žฅ ๊ณ ์œ  ์ฒ˜๋ฆฌ + +- **K-IFRS ์žฌ๋ฌด์ œํ‘œ** โ€” ์—ฐ๊ฒฐ/๋ณ„๋„ ๋‘˜ ๋‹ค ์กฐํšŒ. ์˜์—…์ด์ต ์ •์˜๊ฐ€ ๋ฏธ๊ตญ GAAP์™€ ๋ฏธ๋ฌ˜ํ•˜๊ฒŒ ๋‹ค๋ฆ„ +- **๋‹จ์œ„** โ€” ์›(KRW), ๋ฐฑ๋งŒ์›/์–ต/์กฐ ๋‹จ์œ„๋กœ ์ž๋™ ํฌ๋งทํŒ… +- **๊ฑฐ๋ž˜์‹œ๊ฐ„** โ€” 09:00โ€“15:30 KST ๊ธฐ์ค€ +- **์ƒํ•˜ํ•œ๊ฐ€** โ€” ยฑ30% ์ผ๊ฐ„ ๋ณ€๋™ ์ œํ•œ ๊ณ ๋ ค +- **์žฌ๋ฒŒ ๊ทธ๋ฃน ๊ตฌ์กฐ** โ€” ์‚ผ์„ฑยทํ˜„๋Œ€์ฐจยทSKยทLG ๋“ฑ ๊ทธ๋ฃน ๋‚ด ์ง€๋ถ„ ๊ด€๊ณ„ ๋ถ„์„ ๊ฐ€๋Šฅ +- **๋ฌผ์ ๋ถ„ํ•  ์ด๋ ฅ** โ€” LGํ™”ํ•™โ†’LG์—๋„ˆ์ง€์†”๋ฃจ์…˜ ๊ฐ™์€ ๋ถ„ํ•  ์ด๋ฒคํŠธ ์ถ”์  + +### ํฌ๋กœ์Šค๋งˆ์ผ“ ์ฟผ๋ฆฌ ์˜ˆ์‹œ + +``` +> TSMC์™€ ์‚ผ์„ฑ์ „์ž ํŒŒ์šด๋“œ๋ฆฌ ์‚ฌ์—… ๋น„๊ตํ•ด์ค˜ +``` + +LLM์ด `get_financials` (TSMC, US) + `get_financials_kr` (์‚ผ์„ฑ์ „์ž, KR)๋ฅผ ๋ชจ๋‘ ํ˜ธ์ถœํ•˜๊ณ , ๋‹จ์œ„ยทํšŒ๊ณ„๊ธฐ์ค€ ์ฐจ์ด๋ฅผ ๋ณด์ •ํ•ด์„œ ๋น„๊ต ํ‘œ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + + ## โœ… Prerequisites - [Bun](https://bun.com) runtime (v1.0 or higher) From 8935f3de73b1172609aa27983e315513d9adc4c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SH=20KWON=20=F0=9F=87=B0=F0=9F=87=B7?= Date: Sat, 30 May 2026 00:45:27 +0900 Subject: [PATCH 03/70] Add Korean stock data pipe (Phase 1) (#3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Korean stock data pipe (Phase 1) DART-backed financial data for KOSPI/KOSDAQ tickers: - src/data/fetchers/dart-corp-codes.ts โ€” fetch + unzip + parse DART corpCode.xml (fflate + fast-xml-parser, zero native deps) - src/data/ticker-registry.ts โ€” memory+disk cache (7-day TTL) mapping 6-digit ticker / corp name โ†’ corp_code; stale-on-error fallback + in-flight dedup so DART downtime doesn't break analysis - src/tools/finance-kr/api.ts โ€” DART HTTP wrapper; validates body `status`, strips crtfc_key from cached/logged URLs - src/tools/finance-kr/sub-tools/get-business-report.ts โ€” first KR tool, fnlttSinglAcntAll.json (์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ, K-IFRS) - src/tools/finance-kr/get-financials-kr.ts โ€” LLM router meta-tool; natural-language KR query โ†’ DART sub-tool(s) Wiring: - registry.ts gates registration on DART_API_KEY (ignores `your-`) - cli.ts summarizeToolResult handles get_financials_kr - prompts.ts routes 6-digit tickers to the KR tool typecheck + 60/60 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) * Document DART_API_KEY in env.example Phase 1 gated get_financials_kr on DART_API_KEY but the example env file never listed it. Add it next to the other market-data keys. Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- bun.lock | 16 ++ env.example | 4 + package.json | 2 + src/agent/prompts.ts | 1 + src/cli.ts | 2 +- src/data/fetchers/dart-corp-codes.test.ts | 83 +++++++++ src/data/fetchers/dart-corp-codes.ts | 95 ++++++++++ src/data/ticker-registry.test.ts | 73 ++++++++ src/data/ticker-registry.ts | 128 ++++++++++++++ src/tools/finance-kr/api.ts | 90 ++++++++++ src/tools/finance-kr/get-financials-kr.ts | 164 ++++++++++++++++++ .../sub-tools/get-business-report.ts | 111 ++++++++++++ src/tools/registry.ts | 11 ++ 13 files changed, 779 insertions(+), 1 deletion(-) create mode 100644 src/data/fetchers/dart-corp-codes.test.ts create mode 100644 src/data/fetchers/dart-corp-codes.ts create mode 100644 src/data/ticker-registry.test.ts create mode 100644 src/data/ticker-registry.ts create mode 100644 src/tools/finance-kr/api.ts create mode 100644 src/tools/finance-kr/get-financials-kr.ts create mode 100644 src/tools/finance-kr/sub-tools/get-business-report.ts diff --git a/bun.lock b/bun.lock index 936560395..2ef1a76b4 100644 --- a/bun.lock +++ b/bun.lock @@ -20,6 +20,8 @@ "diff": "^8.0.4", "dotenv": "^17.3.1", "exa-js": "^2.10.1", + "fast-xml-parser": "^5.8.0", + "fflate": "^0.8.3", "gray-matter": "^4.0.3", "langsmith": "^0.4.12", "linkedom": "^0.18.12", @@ -445,6 +447,8 @@ "@mozilla/readability": ["@mozilla/readability@0.6.0", "", {}, "sha512-juG5VWh4qAivzTAeMzvY9xs9HY5rAcr2E4I7tiSSCokRFi7XIZCAu92ZkSTsIj1OPceCifL3cpfteP3pDT9/QQ=="], + "@nodable/entities": ["@nodable/entities@2.1.0", "", {}, "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA=="], + "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], @@ -703,8 +707,14 @@ "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + "fast-xml-builder": ["fast-xml-builder@1.2.0", "", { "dependencies": { "path-expression-matcher": "^1.5.0", "xml-naming": "^0.1.0" } }, "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q=="], + + "fast-xml-parser": ["fast-xml-parser@5.8.0", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.2.0", "path-expression-matcher": "^1.5.0", "strnum": "^2.3.0", "xml-naming": "^0.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-6bIM7fsJxeo3uXv7OncQYsBAMPJ7V16Slahl/6M98C/i2q+vB1+4a0MtrvYwDFEUrwDSbAmeLDRXsOBwrL7yAg=="], + "fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="], + "fflate": ["fflate@0.8.3", "", {}, "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA=="], + "file-type": ["file-type@21.3.0", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA=="], "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], @@ -967,6 +977,8 @@ "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + "path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="], + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], @@ -1095,6 +1107,8 @@ "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + "strnum": ["strnum@2.3.0", "", {}, "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q=="], + "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -1179,6 +1193,8 @@ "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], + "xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="], + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], diff --git a/env.example b/env.example index 5ea4b4061..c98f2c4a2 100644 --- a/env.example +++ b/env.example @@ -17,6 +17,10 @@ OLLAMA_BASE_URL=http://127.0.0.1:11434 # Stock Market API Key FINANCIAL_DATASETS_API_KEY=your-financial-datasets-api-key +# Korean stocks (DART ์ „์ž๊ณต์‹œ) โ€” enables get_financials_kr +# Issue at https://opendart.fss.or.kr +DART_API_KEY=your-dart-api-key + # Web Search API Keys (Exa โ†’ Perplexity โ†’ Tavily โ†’ LangSearch) EXASEARCH_API_KEY=your-exa-api-key PERPLEXITY_API_KEY=your-perplexity-api-key diff --git a/package.json b/package.json index 3088c726e..a0325ad89 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,8 @@ "diff": "^8.0.4", "dotenv": "^17.3.1", "exa-js": "^2.10.1", + "fast-xml-parser": "^5.8.0", + "fflate": "^0.8.3", "gray-matter": "^4.0.3", "langsmith": "^0.4.12", "linkedom": "^0.18.12", diff --git a/src/agent/prompts.ts b/src/agent/prompts.ts index 442423f1c..e2144e324 100644 --- a/src/agent/prompts.ts +++ b/src/agent/prompts.ts @@ -244,6 +244,7 @@ ${toolDescriptions} ## Tool Usage Policy - Call get_financials or get_market_data ONCE with the full natural language query โ€” they handle multi-company/multi-metric requests internally. Do NOT break up queries into multiple calls. +- 6-digit numeric tickers (e.g. 005930, 035420) are Korean stocks โ€” use get_financials_kr. ASCII tickers (AAPL, MSFT) use get_financials. - Only use web_fetch when headlines are insufficient (need quotes, deal specifics, earnings details). - Tool results are automatically capped. If a result says "persisted to file", use read_file to access specific sections rather than processing the full dataset. - Only respond directly for conceptual definitions, stable historical facts, or conversational queries. diff --git a/src/cli.ts b/src/cli.ts index 374a4187b..3becbe33c 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -71,7 +71,7 @@ function summarizeToolResult(tool: string, args: Record, result } if (typeof parsed.data === 'object') { const keys = Object.keys(parsed.data).filter((key) => !key.startsWith('_')); - if (tool === 'get_financials' || tool === 'get_market_data' || tool === 'stock_screener') { + if (tool === 'get_financials' || tool === 'get_financials_kr' || tool === 'get_market_data' || tool === 'stock_screener') { if (keys.length === 0) return 'Done'; return keys.length === 1 ? 'Called 1 data source' : `Called ${keys.length} data sources`; } diff --git a/src/data/fetchers/dart-corp-codes.test.ts b/src/data/fetchers/dart-corp-codes.test.ts new file mode 100644 index 000000000..c802b878d --- /dev/null +++ b/src/data/fetchers/dart-corp-codes.test.ts @@ -0,0 +1,83 @@ +import { describe, expect, test } from 'bun:test'; +import { zipSync, strToU8 } from 'fflate'; +import { parseCorpCodeXml, extractCorpCodeXmlFromZip } from './dart-corp-codes.js'; + +const SAMPLE_XML = ` + + + 00126380 + ์‚ผ์„ฑ์ „์ž + 005930 + 20231201 + + + 00164779 + SKํ•˜์ด๋‹‰์Šค + 000660 + 20231115 + + + 00999999 + ๋น„์ƒ์žฅํšŒ์‚ฌ + + 20231101 + +`; + +describe('parseCorpCodeXml', () => { + test('parses Samsung and SK Hynix from sample XML', () => { + const entries = parseCorpCodeXml(SAMPLE_XML); + expect(entries).toHaveLength(3); + + const samsung = entries.find((e) => e.stock_code === '005930'); + expect(samsung).toEqual({ + corp_code: '00126380', + corp_name: '์‚ผ์„ฑ์ „์ž', + stock_code: '005930', + modify_date: '20231201', + }); + + const skHynix = entries.find((e) => e.stock_code === '000660'); + expect(skHynix?.corp_name).toBe('SKํ•˜์ด๋‹‰์Šค'); + }); + + test('normalizes empty stock_code to null', () => { + const entries = parseCorpCodeXml(SAMPLE_XML); + const unlisted = entries.find((e) => e.corp_name === '๋น„์ƒ์žฅํšŒ์‚ฌ'); + expect(unlisted?.stock_code).toBeNull(); + }); + + test('handles single-entry XML (XMLParser drops array wrapper)', () => { + const single = ` + + + 00126380 + ์‚ผ์„ฑ์ „์ž + 005930 + 20231201 + +`; + const entries = parseCorpCodeXml(single); + expect(entries).toHaveLength(1); + expect(entries[0].stock_code).toBe('005930'); + }); + + test('returns empty array when no list element', () => { + expect(parseCorpCodeXml('')).toEqual([]); + }); +}); + +describe('extractCorpCodeXmlFromZip', () => { + test('unzips and decodes CORPCODE.xml', () => { + const bytes = zipSync({ 'CORPCODE.xml': strToU8(SAMPLE_XML) }); + const xml = extractCorpCodeXmlFromZip(bytes); + expect(xml).toContain('์‚ผ์„ฑ์ „์ž'); + expect(xml).toContain('005930'); + }); + + test('falls back to first file if named entry absent', () => { + const bytes = zipSync({ 'something-else.xml': strToU8('') }); + const xml = extractCorpCodeXmlFromZip(bytes); + expect(xml).toBe(''); + }); +}); diff --git a/src/data/fetchers/dart-corp-codes.ts b/src/data/fetchers/dart-corp-codes.ts new file mode 100644 index 000000000..11e64c566 --- /dev/null +++ b/src/data/fetchers/dart-corp-codes.ts @@ -0,0 +1,95 @@ +import { unzipSync, strFromU8 } from 'fflate'; +import { XMLParser } from 'fast-xml-parser'; +import { logger } from '../../utils/logger.js'; + +const CORP_CODE_URL = 'https://opendart.fss.or.kr/api/corpCode.xml'; +const CORP_CODE_XML_FILENAME = 'CORPCODE.xml'; + +export interface CorpCodeEntry { + corp_code: string; + corp_name: string; + stock_code: string | null; + modify_date: string; +} + +interface RawCorpListEntry { + corp_code: string | number; + corp_name: string; + stock_code?: string | number; + modify_date: string | number; +} + +function getApiKey(): string { + return process.env.DART_API_KEY || ''; +} + +function normalize(value: string | number | undefined): string { + if (value === undefined || value === null) return ''; + const s = String(value).trim(); + return s; +} + +export function parseCorpCodeXml(xml: string): CorpCodeEntry[] { + const parser = new XMLParser({ + ignoreAttributes: true, + trimValues: true, + parseTagValue: false, + }); + const doc = parser.parse(xml) as { result?: { list?: RawCorpListEntry | RawCorpListEntry[] } }; + const list = doc.result?.list; + if (!list) return []; + + const rows = Array.isArray(list) ? list : [list]; + return rows.map((row) => { + const stockCode = normalize(row.stock_code); + return { + corp_code: normalize(row.corp_code), + corp_name: normalize(row.corp_name), + stock_code: stockCode === '' ? null : stockCode, + modify_date: normalize(row.modify_date), + }; + }); +} + +export function extractCorpCodeXmlFromZip(zipBytes: Uint8Array): string { + const files = unzipSync(zipBytes); + const entry = files[CORP_CODE_XML_FILENAME] ?? Object.values(files)[0]; + if (!entry) { + throw new Error('[DART corpCode] ZIP did not contain any files'); + } + return strFromU8(entry); +} + +export async function fetchDartCorpCodes(): Promise { + const apiKey = getApiKey(); + if (!apiKey) { + throw new Error('[DART corpCode] DART_API_KEY not set'); + } + + const url = `${CORP_CODE_URL}?crtfc_key=${encodeURIComponent(apiKey)}`; + let response: Response; + try { + response = await fetch(url); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`[DART corpCode] network error โ€” ${message}`); + throw new Error(`[DART corpCode] request failed: ${message}`); + } + + if (!response.ok) { + const detail = `${response.status} ${response.statusText}`; + logger.error(`[DART corpCode] HTTP error โ€” ${detail}`); + throw new Error(`[DART corpCode] request failed: ${detail}`); + } + + const contentType = response.headers.get('content-type') ?? ''; + // DART returns XML error payload (application/xml) for bad keys instead of a ZIP + if (contentType.includes('xml') || contentType.includes('text')) { + const body = await response.text(); + throw new Error(`[DART corpCode] expected ZIP, got ${contentType}: ${body.slice(0, 200)}`); + } + + const buffer = await response.arrayBuffer(); + const xml = extractCorpCodeXmlFromZip(new Uint8Array(buffer)); + return parseCorpCodeXml(xml); +} diff --git a/src/data/ticker-registry.test.ts b/src/data/ticker-registry.test.ts new file mode 100644 index 000000000..57c8e8501 --- /dev/null +++ b/src/data/ticker-registry.test.ts @@ -0,0 +1,73 @@ +import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; +import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { dirname } from 'node:path'; +import { dexterPath } from '../utils/paths.js'; +import { _resetTickerRegistryForTests, resolveTicker, resolveCorpName } from './ticker-registry.js'; + +const REGISTRY_FILE = dexterPath('cache', 'dart', 'corp-codes.json'); + +function seedRegistry(fetchedAt: string): void { + const dir = dirname(REGISTRY_FILE); + if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); + writeFileSync( + REGISTRY_FILE, + JSON.stringify({ + fetchedAt, + entries: [ + { corp_code: '00126380', corp_name: '์‚ผ์„ฑ์ „์ž', stock_code: '005930', modify_date: '20231201' }, + { corp_code: '00164779', corp_name: 'SKํ•˜์ด๋‹‰์Šค', stock_code: '000660', modify_date: '20231115' }, + { corp_code: '00999999', corp_name: '๋น„์ƒ์žฅํšŒ์‚ฌ', stock_code: null, modify_date: '20231101' }, + ], + }), + ); +} + +describe('ticker-registry', () => { + beforeEach(() => { + _resetTickerRegistryForTests(); + if (existsSync(REGISTRY_FILE)) rmSync(REGISTRY_FILE); + }); + + afterEach(() => { + _resetTickerRegistryForTests(); + if (existsSync(REGISTRY_FILE)) rmSync(REGISTRY_FILE); + }); + + test('resolves 6-digit ticker from fresh on-disk cache', async () => { + seedRegistry(new Date().toISOString()); + const result = await resolveTicker('005930'); + expect(result).toEqual({ corp_code: '00126380', corp_name: '์‚ผ์„ฑ์ „์ž' }); + }); + + test('returns null for non-6-digit input', async () => { + seedRegistry(new Date().toISOString()); + expect(await resolveTicker('AAPL')).toBeNull(); + expect(await resolveTicker('5930')).toBeNull(); + expect(await resolveTicker('0059301')).toBeNull(); + }); + + test('returns null when ticker not in registry', async () => { + seedRegistry(new Date().toISOString()); + expect(await resolveTicker('999999')).toBeNull(); + }); + + test('resolveCorpName matches by exact Korean name', async () => { + seedRegistry(new Date().toISOString()); + const result = await resolveCorpName('SKํ•˜์ด๋‹‰์Šค'); + expect(result?.corp_code).toBe('00164779'); + expect(result?.stock_code).toBe('000660'); + }); + + test('falls back to stale cache if refresh fails (no DART_API_KEY)', async () => { + // Stale by setting fetchedAt to 30 days ago + seedRegistry(new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString()); + const originalKey = process.env.DART_API_KEY; + delete process.env.DART_API_KEY; + try { + const result = await resolveTicker('005930'); + expect(result?.corp_code).toBe('00126380'); + } finally { + if (originalKey !== undefined) process.env.DART_API_KEY = originalKey; + } + }); +}); diff --git a/src/data/ticker-registry.ts b/src/data/ticker-registry.ts new file mode 100644 index 000000000..1124e834d --- /dev/null +++ b/src/data/ticker-registry.ts @@ -0,0 +1,128 @@ +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { dirname } from 'node:path'; +import { dexterPath } from '../utils/paths.js'; +import { logger } from '../utils/logger.js'; +import { fetchDartCorpCodes, type CorpCodeEntry } from './fetchers/dart-corp-codes.js'; + +const REGISTRY_FILE = dexterPath('cache', 'dart', 'corp-codes.json'); +const DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1000; + +interface RegistryFile { + fetchedAt: string; + entries: CorpCodeEntry[]; +} + +interface RegistryCache { + fetchedAt: number; + byTicker: Map; + byCorpName: Map; +} + +let memoryCache: RegistryCache | null = null; +let inflight: Promise | null = null; + +function buildIndex(entries: CorpCodeEntry[], fetchedAt: number): RegistryCache { + const byTicker = new Map(); + const byCorpName = new Map(); + for (const entry of entries) { + if (entry.stock_code) { + byTicker.set(entry.stock_code, entry); + } + if (entry.corp_name) { + byCorpName.set(entry.corp_name, entry); + } + } + return { fetchedAt, byTicker, byCorpName }; +} + +function readFromDisk(): RegistryCache | null { + if (!existsSync(REGISTRY_FILE)) return null; + try { + const raw = readFileSync(REGISTRY_FILE, 'utf-8'); + const parsed = JSON.parse(raw) as RegistryFile; + if (!parsed?.entries || !Array.isArray(parsed.entries)) return null; + return buildIndex(parsed.entries, Date.parse(parsed.fetchedAt)); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.warn(`[ticker-registry] failed to read cache โ€” ${message}`); + return null; + } +} + +function writeToDisk(entries: CorpCodeEntry[]): string { + const dir = dirname(REGISTRY_FILE); + if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); + const fetchedAt = new Date().toISOString(); + const payload: RegistryFile = { fetchedAt, entries }; + writeFileSync(REGISTRY_FILE, JSON.stringify(payload)); + return fetchedAt; +} + +async function loadRegistry(ttlMs: number): Promise { + const now = Date.now(); + if (memoryCache && now - memoryCache.fetchedAt < ttlMs) { + return memoryCache; + } + + const onDisk = readFromDisk(); + if (onDisk && now - onDisk.fetchedAt < ttlMs) { + memoryCache = onDisk; + return onDisk; + } + + if (inflight) return inflight; + + inflight = (async () => { + try { + const entries = await fetchDartCorpCodes(); + const fetchedAt = Date.parse(writeToDisk(entries)); + const cache = buildIndex(entries, fetchedAt); + memoryCache = cache; + return cache; + } catch (error) { + if (onDisk) { + logger.warn(`[ticker-registry] refresh failed, falling back to stale cache โ€” ${error instanceof Error ? error.message : String(error)}`); + memoryCache = onDisk; + return onDisk; + } + throw error; + } finally { + inflight = null; + } + })(); + + return inflight; +} + +export async function resolveTicker( + ticker: string, + options?: { ttlMs?: number }, +): Promise<{ corp_code: string; corp_name: string } | null> { + const normalized = ticker.trim(); + if (!/^\d{6}$/.test(normalized)) return null; + const cache = await loadRegistry(options?.ttlMs ?? DEFAULT_TTL_MS); + const entry = cache.byTicker.get(normalized); + if (!entry) return null; + return { corp_code: entry.corp_code, corp_name: entry.corp_name }; +} + +export async function resolveCorpName( + name: string, + options?: { ttlMs?: number }, +): Promise<{ corp_code: string; corp_name: string; stock_code: string | null } | null> { + const trimmed = name.trim(); + if (!trimmed) return null; + const cache = await loadRegistry(options?.ttlMs ?? DEFAULT_TTL_MS); + const entry = cache.byCorpName.get(trimmed); + if (!entry) return null; + return { + corp_code: entry.corp_code, + corp_name: entry.corp_name, + stock_code: entry.stock_code, + }; +} + +export function _resetTickerRegistryForTests(): void { + memoryCache = null; + inflight = null; +} diff --git a/src/tools/finance-kr/api.ts b/src/tools/finance-kr/api.ts new file mode 100644 index 000000000..3028a00cf --- /dev/null +++ b/src/tools/finance-kr/api.ts @@ -0,0 +1,90 @@ +import { readCache, writeCache, describeRequest } from '../../utils/cache.js'; +import { logger } from '../../utils/logger.js'; + +const BASE_URL = 'https://opendart.fss.or.kr/api'; + +export interface DartApiResponse { + data: Record; + url: string; +} + +function getApiKey(): string { + return process.env.DART_API_KEY || ''; +} + +/** + * DART returns HTTP 200 with a `status` field in the body for app-level + * outcomes. Codes other than '000' are not real data; surface them as errors. + * + * Reference: https://opendart.fss.or.kr/guide/main.do?apiGrpCd= + * 000=์ •์ƒ, 013=์กฐํšŒ๋œ ๋ฐ์ดํƒ€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค, 020=์‚ฌ์šฉํ•œ๋„์ดˆ๊ณผ, etc. + */ +function assertDartOk(label: string, data: Record): void { + const status = typeof data.status === 'string' ? data.status : undefined; + if (status && status !== '000') { + const message = typeof data.message === 'string' ? data.message : 'unknown'; + throw new Error(`[DART API] ${label} โ€” status=${status} (${message})`); + } +} + +export const dartApi = { + async get( + endpoint: string, + params: Record, + options?: { cacheable?: boolean; ttlMs?: number }, + ): Promise { + const label = describeRequest(endpoint, params); + + if (options?.cacheable) { + const cached = readCache(endpoint, params, options.ttlMs); + if (cached) return cached; + } + + const apiKey = getApiKey(); + if (!apiKey) { + throw new Error('[DART API] DART_API_KEY not set'); + } + + const url = new URL(`${BASE_URL}${endpoint}`); + url.searchParams.set('crtfc_key', apiKey); + for (const [key, value] of Object.entries(params)) { + if (value !== undefined && value !== null) { + url.searchParams.set(key, String(value)); + } + } + + let response: Response; + try { + response = await fetch(url.toString()); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`[DART API] network error: ${label} โ€” ${message}`); + throw new Error(`[DART API] request failed for ${label}: ${message}`); + } + + if (!response.ok) { + const detail = `${response.status} ${response.statusText}`; + logger.error(`[DART API] HTTP error: ${label} โ€” ${detail}`); + throw new Error(`[DART API] request failed: ${detail}`); + } + + const data = (await response.json().catch(() => { + const detail = `invalid JSON (${response.status} ${response.statusText})`; + logger.error(`[DART API] parse error: ${label} โ€” ${detail}`); + throw new Error(`[DART API] request failed: ${detail}`); + })) as Record; + + assertDartOk(label, data); + + // Strip the crtfc_key from the cached/logged URL to avoid leaking the secret + const safeUrl = new URL(url.toString()); + safeUrl.searchParams.delete('crtfc_key'); + const safeUrlString = safeUrl.toString(); + + if (options?.cacheable) { + writeCache(endpoint, params, data, safeUrlString); + } + + return { data, url: safeUrlString }; + }, +}; diff --git a/src/tools/finance-kr/get-financials-kr.ts b/src/tools/finance-kr/get-financials-kr.ts new file mode 100644 index 000000000..0626e30ed --- /dev/null +++ b/src/tools/finance-kr/get-financials-kr.ts @@ -0,0 +1,164 @@ +import { DynamicStructuredTool, StructuredToolInterface } from '@langchain/core/tools'; +import type { RunnableConfig } from '@langchain/core/runnables'; +import { AIMessage, ToolCall } from '@langchain/core/messages'; +import { z } from 'zod'; +import { callLlm } from '../../model/llm.js'; +import { formatToolResult } from '../types.js'; +import { getCurrentDate } from '../../agent/prompts.js'; +import { withTimeout, SUB_TOOL_TIMEOUT_MS } from '../finance/utils.js'; +import { getBusinessReport } from './sub-tools/get-business-report.js'; + +export const GET_FINANCIALS_KR_DESCRIPTION = ` +Intelligent meta-tool for retrieving Korean (KOSPI/KOSDAQ) listed company financial data from DART (Korea's official disclosure system). Takes a natural language query and routes to DART data sources. + +## When to Use + +- Any Korean stock query identified by a 6-digit ticker (e.g. 005930 = Samsung Electronics, 005380 = Hyundai Motor) +- ์‚ฌ์—…๋ณด๊ณ ์„œ / ๋ฐ˜๊ธฐ๋ณด๊ณ ์„œ / ๋ถ„๊ธฐ๋ณด๊ณ ์„œ (annual / semiannual / quarterly statements) +- K-IFRS income statement, balance sheet, cash flow data +- Multi-year Korean financial trend analysis + +## When NOT to Use + +- US-listed stocks (use get_financials for AAPL, MSFT, etc.) +- Korean stock prices or news (Phase 2 โ€” not yet implemented) +- 5%๋ฃฐ, ์ž„์› ๊ฑฐ๋ž˜, ๊ตญ๋ฏผ์—ฐ๊ธˆ ๋ณด์œ  (Phase 2/3 โ€” not yet implemented) +- General web searches (use web_search) + +## Usage Notes + +- Call ONCE with the complete natural language query โ€” the router handles internal complexity +- Ticker must be the 6-digit Korean code (e.g. 005930, not "005930.KS" or "์‚ผ์„ฑ์ „์ž") +- Handles year inference: "์ž‘๋…„", "์˜ฌํ•ด", "์ง€๋‚œ ๋ถ„๊ธฐ" โ€” converts to bsns_year + reprt_code +- Returns raw DART line items under K-IFRS account names (Korean labels) +`.trim(); + +const KR_SUB_TOOLS: StructuredToolInterface[] = [getBusinessReport]; +const KR_SUB_TOOL_MAP = new Map(KR_SUB_TOOLS.map((t) => [t.name, t])); + +function formatSubToolName(name: string): string { + return name.split('_').map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); +} + +function buildRouterPrompt(): string { + return `You are a Korean financial data routing assistant. +Current date: ${getCurrentDate()} + +Given a user's natural language query about a Korean (KOSPI/KOSDAQ) listed company, call the appropriate DART tool(s). + +## Guidelines + +1. **Ticker Resolution** โ€” Convert Korean company names to 6-digit ticker codes: + - Samsung Electronics / ์‚ผ์„ฑ์ „์ž โ†’ 005930 + - SK Hynix / SKํ•˜์ด๋‹‰์Šค โ†’ 000660 + - Hyundai Motor / ํ˜„๋Œ€์ฐจ โ†’ 005380 + - LG Energy Solution / LG์—๋„ˆ์ง€์†”๋ฃจ์…˜ โ†’ 373220 + - Naver / ๋„ค์ด๋ฒ„ โ†’ 035420 + - Kakao / ์นด์นด์˜ค โ†’ 035720 + - Samsung Biologics / ์‚ผ์„ฑ๋ฐ”์ด์˜ค๋กœ์ง์Šค โ†’ 207940 + - Celltrion / ์…€ํŠธ๋ฆฌ์˜จ โ†’ 068270 + - POSCO Holdings / ํฌ์Šค์ฝ”ํ™€๋”ฉ์Šค โ†’ 005490 + - Hyundai Mobis / ํ˜„๋Œ€๋ชจ๋น„์Šค โ†’ 012330 + Strip any suffix the user provides (".KS", ".KQ") โ€” pass only the 6-digit code. + +2. **Report Type Selection** (report_type): + - "์‚ฌ์—…๋ณด๊ณ ์„œ" / "annual" / "์—ฐ๊ฐ„" โ†’ annual (reprt_code 11011, typically filed in March) + - "๋ฐ˜๊ธฐ๋ณด๊ณ ์„œ" / "๋ฐ˜๊ธฐ" / "semiannual" โ†’ semiannual (11012, ~August) + - "1๋ถ„๊ธฐ" / "Q1" โ†’ quarterly_1 (11013, ~May) + - "3๋ถ„๊ธฐ" / "Q3" โ†’ quarterly_3 (11014, ~November) + - DART does NOT publish a standalone Q2/Q4 report โ€” Q2 data is inside the ๋ฐ˜๊ธฐ๋ณด๊ณ ์„œ, Q4 inside the ์‚ฌ์—…๋ณด๊ณ ์„œ. + +3. **Year Inference**: + - "์ž‘๋…„" / "last year" โ†’ year = currentYear - 1 + - "์ตœ๊ทผ ์‚ฌ์—…๋ณด๊ณ ์„œ" โ†’ year = currentYear - 1 (annual reports for year N publish in March of year N+1) + - "์ง€๋‚œ 5๋…„" / "past 5 years" โ†’ period_count: 5, year: currentYear - 1 + +4. **Statement Scope (fs_div)**: + - Default 'CFS' (์—ฐ๊ฒฐ์žฌ๋ฌด์ œํ‘œ, consolidated) + - Use 'OFS' (๊ฐœ๋ณ„์žฌ๋ฌด์ œํ‘œ) only if user explicitly asks for separate/non-consolidated + +5. **Efficiency**: For multi-year trend questions, use period_count rather than calling multiple times. + +Call the tool(s) now.`; +} + +const InputSchema = z.object({ + query: z.string().describe('Natural language query about Korean company financial data'), +}); + +export function createGetFinancialsKr(model: string): DynamicStructuredTool { + return new DynamicStructuredTool({ + name: 'get_financials_kr', + description: + 'Korean (KOSPI/KOSDAQ) listed company financial data from DART. Takes a natural language query and routes to DART tools (์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ). Use for 6-digit Korean tickers (e.g. 005930).', + schema: InputSchema, + func: async (input, _runManager, config?: RunnableConfig) => { + const onProgress = config?.metadata?.onProgress as ((msg: string) => void) | undefined; + + onProgress?.('Routing KR query...'); + const { response } = await callLlm(input.query, { + model, + systemPrompt: buildRouterPrompt(), + tools: KR_SUB_TOOLS, + }); + const aiMessage = response as AIMessage; + + const toolCalls = aiMessage.tool_calls as ToolCall[]; + if (!toolCalls || toolCalls.length === 0) { + return formatToolResult({ error: 'No DART tools selected for query' }, []); + } + + const toolNames = [...new Set(toolCalls.map((tc) => formatSubToolName(tc.name)))]; + onProgress?.(`Fetching from ${toolNames.join(', ')}...`); + + const results = await Promise.all( + toolCalls.map(async (tc) => { + try { + const tool = KR_SUB_TOOL_MAP.get(tc.name); + if (!tool) { + throw new Error(`Tool '${tc.name}' not found`); + } + const rawResult = await withTimeout(tool.invoke(tc.args), SUB_TOOL_TIMEOUT_MS, tc.name); + const result = typeof rawResult === 'string' ? rawResult : JSON.stringify(rawResult); + const parsed = JSON.parse(result); + return { + tool: tc.name, + args: tc.args, + data: parsed.data, + sourceUrls: parsed.sourceUrls || [], + error: null as string | null, + }; + } catch (error) { + return { + tool: tc.name, + args: tc.args, + data: null, + sourceUrls: [], + error: error instanceof Error ? error.message : String(error), + }; + } + }), + ); + + const combinedData: Record = {}; + const allUrls = results.flatMap((r) => r.sourceUrls); + + for (const result of results.filter((r) => r.error === null)) { + const ticker = (result.args as Record).ticker as string | undefined; + const key = ticker ? `${result.tool}_${ticker}` : result.tool; + combinedData[key] = result.data; + } + + const failed = results.filter((r) => r.error !== null); + if (failed.length > 0) { + combinedData._errors = failed.map((r) => ({ + tool: r.tool, + args: r.args, + error: r.error, + })); + } + + return formatToolResult(combinedData, allUrls); + }, + }); +} diff --git a/src/tools/finance-kr/sub-tools/get-business-report.ts b/src/tools/finance-kr/sub-tools/get-business-report.ts new file mode 100644 index 000000000..a5dffb7b1 --- /dev/null +++ b/src/tools/finance-kr/sub-tools/get-business-report.ts @@ -0,0 +1,111 @@ +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { z } from 'zod'; +import { dartApi } from '../api.js'; +import { resolveTicker } from '../../../data/ticker-registry.js'; +import { formatToolResult } from '../../types.js'; +import { TTL_24H } from '../../finance/utils.js'; + +const REPORT_TYPE_CODES = { + annual: '11011', // ์‚ฌ์—…๋ณด๊ณ ์„œ + semiannual: '11012', // ๋ฐ˜๊ธฐ๋ณด๊ณ ์„œ + quarterly_1: '11013', // 1๋ถ„๊ธฐ๋ณด๊ณ ์„œ + quarterly_3: '11014', // 3๋ถ„๊ธฐ๋ณด๊ณ ์„œ +} as const; + +type ReportType = keyof typeof REPORT_TYPE_CODES; + +const InputSchema = z.object({ + ticker: z + .string() + .regex(/^\d{6}$/, 'Korean ticker must be a 6-digit string (e.g. 005930 for Samsung).') + .describe('6-digit Korean stock ticker (e.g. 005930 for Samsung Electronics).'), + report_type: z + .enum(['annual', 'semiannual', 'quarterly_1', 'quarterly_3']) + .default('annual') + .describe( + "Report type: 'annual' (์‚ฌ์—…๋ณด๊ณ ์„œ, ~Mar following year), 'semiannual' (๋ฐ˜๊ธฐ๋ณด๊ณ ์„œ, ~Aug), " + + "'quarterly_1' (1๋ถ„๊ธฐ, ~May), 'quarterly_3' (3๋ถ„๊ธฐ, ~Nov)." + ), + year: z + .number() + .int() + .min(2015) + .optional() + .describe('Business year (bsns_year) for the report. Defaults to the most recent completed year.'), + period_count: z + .number() + .int() + .min(1) + .max(5) + .default(1) + .describe('Number of consecutive years to fetch ending at `year` (default 1).'), + fs_div: z + .enum(['CFS', 'OFS']) + .default('CFS') + .describe("Financial statement scope: 'CFS' (์—ฐ๊ฒฐ, consolidated) or 'OFS' (๊ฐœ๋ณ„, separate)."), +}); + +function defaultYear(): number { + // DART typically publishes annual reports in March of the following year. + // Default to last year so requests don't 404 in Januaryโ€“March. + return new Date().getUTCFullYear() - 1; +} + +export const getBusinessReport = new DynamicStructuredTool({ + name: 'get_business_report_kr', + description: + 'Fetches a Korean listed company\'s ์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ (annual/semiannual/quarterly statements) from DART. ' + + 'Returns full income statement, balance sheet, and cash flow line items as reported under K-IFRS.', + schema: InputSchema, + func: async (input) => { + const ticker = input.ticker.trim(); + const resolved = await resolveTicker(ticker); + if (!resolved) { + return formatToolResult({ + error: `Ticker ${ticker} not found in DART corp registry`, + }, []); + } + + const endYear = input.year ?? defaultYear(); + const years = Array.from({ length: input.period_count }, (_, i) => endYear - i); + const reprt_code = REPORT_TYPE_CODES[input.report_type as ReportType]; + + const results = await Promise.all( + years.map(async (bsns_year) => { + try { + const { data, url } = await dartApi.get( + '/fnlttSinglAcntAll.json', + { + corp_code: resolved.corp_code, + bsns_year, + reprt_code, + fs_div: input.fs_div, + }, + { cacheable: true, ttlMs: TTL_24H }, + ); + return { year: bsns_year, list: data.list ?? [], url, error: null as string | null }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { year: bsns_year, list: [], url: '', error: message }; + } + }), + ); + + const urls = results.map((r) => r.url).filter(Boolean); + const successful = results.filter((r) => r.error === null); + const failed = results.filter((r) => r.error !== null); + + return formatToolResult( + { + ticker, + corp_code: resolved.corp_code, + corp_name: resolved.corp_name, + report_type: input.report_type, + fs_div: input.fs_div, + periods: successful.map((r) => ({ bsns_year: r.year, list: r.list })), + ...(failed.length > 0 ? { _errors: failed.map((r) => ({ year: r.year, error: r.error })) } : {}), + }, + urls, + ); + }, +}); diff --git a/src/tools/registry.ts b/src/tools/registry.ts index 76e51acc9..51c15ce16 100644 --- a/src/tools/registry.ts +++ b/src/tools/registry.ts @@ -11,6 +11,7 @@ import { readFileTool, READ_FILE_DESCRIPTION } from './filesystem/read-file.js'; import { writeFileTool, WRITE_FILE_DESCRIPTION } from './filesystem/write-file.js'; import { editFileTool, EDIT_FILE_DESCRIPTION } from './filesystem/edit-file.js'; import { GET_FINANCIALS_DESCRIPTION } from './finance/get-financials.js'; +import { createGetFinancialsKr, GET_FINANCIALS_KR_DESCRIPTION } from './finance-kr/get-financials-kr.js'; import { GET_MARKET_DATA_DESCRIPTION } from './finance/get-market-data.js'; import { READ_FILINGS_DESCRIPTION } from './finance/read-filings.js'; import { SCREEN_STOCKS_DESCRIPTION } from './finance/screen-stocks.js'; @@ -178,6 +179,16 @@ export function getToolRegistry(model: string): RegisteredTool[] { }); } + if (process.env.DART_API_KEY && !process.env.DART_API_KEY.startsWith('your-')) { + tools.push({ + name: 'get_financials_kr', + tool: createGetFinancialsKr(model), + description: GET_FINANCIALS_KR_DESCRIPTION, + compactDescription: 'Korean stocks (6-digit tickers like 005930). DART ์‚ฌ์—…ยท๋ฐ˜๊ธฐยท๋ถ„๊ธฐ๋ณด๊ณ ์„œ (K-IFRS).', + concurrencySafe: true, + }); + } + if (process.env.X_BEARER_TOKEN) { tools.push({ name: 'x_search', From 650f8ddbce004b6c120d17db29ad3f50d30656ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SH=20KWON=20=F0=9F=87=B0=F0=9F=87=B7?= Date: Sat, 30 May 2026 02:27:00 +0900 Subject: [PATCH 04/70] Add Korean disclosure tools (Phase 2) (#4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Korean disclosure tools (Phase 2) Add three DART-backed disclosure tools, the Korean equivalents of the US filings/13F/insider tools: - get_filings_kr โ€” DART ๊ณต์‹œ๊ฒ€์ƒ‰ (/list.json), date/type filters - get_large_holders_kr โ€” ๋Œ€๋Ÿ‰๋ณด์œ  ์ƒํ™ฉ๋ณด๊ณ  5%๋ฃฐ (/majorstock.json) - get_insider_trades_kr โ€” ์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ ์†Œ์œ ๋ณด๊ณ  (/elestock.json) Each is a simple standalone tool taking a 6-digit ticker (no inner LLM router); the main agent resolves company name โ†’ ticker and routes by ticker pattern, per the CLAUDE.md "Decided" note. Ticker โ†’ corp_code via the existing resolveTicker registry; results returned as { data } through formatToolResult. majorstock/elestock have no server-side date filter, so the full history is sorted by rcept_dt desc and capped client-side. DART status 013 (no data) is treated as an empty result, not an error. Wires registration (DART_API_KEY gate), the 6-digit routing rule in the system prompt, and UI status lines (Found N filings/holders/reports). Shared helpers in finance-kr/utils.ts with colocated tests. Co-Authored-By: Claude Opus 4.8 (1M context) * Make get_filings_kr ordering explicit and guard list parsing Address review nits on the filings tool: - Send sort=date&sort_mth=desc instead of relying on DART's implicit default ordering, so the "most recent first" promise holds regardless of pblntf_ty. - Guard data.list with Array.isArray, matching get_large_holders_kr and get_insider_trades_kr. Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- src/agent/prompts.ts | 2 +- src/cli.ts | 12 ++ src/tools/finance-kr/get-filings-kr.ts | 112 ++++++++++++++++++ src/tools/finance-kr/get-insider-trades-kr.ts | 66 +++++++++++ src/tools/finance-kr/get-large-holders-kr.ts | 66 +++++++++++ src/tools/finance-kr/utils.test.ts | 41 +++++++ src/tools/finance-kr/utils.ts | 31 +++++ src/tools/registry.ts | 24 ++++ 8 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 src/tools/finance-kr/get-filings-kr.ts create mode 100644 src/tools/finance-kr/get-insider-trades-kr.ts create mode 100644 src/tools/finance-kr/get-large-holders-kr.ts create mode 100644 src/tools/finance-kr/utils.test.ts create mode 100644 src/tools/finance-kr/utils.ts diff --git a/src/agent/prompts.ts b/src/agent/prompts.ts index e2144e324..c1ee2f8d2 100644 --- a/src/agent/prompts.ts +++ b/src/agent/prompts.ts @@ -244,7 +244,7 @@ ${toolDescriptions} ## Tool Usage Policy - Call get_financials or get_market_data ONCE with the full natural language query โ€” they handle multi-company/multi-metric requests internally. Do NOT break up queries into multiple calls. -- 6-digit numeric tickers (e.g. 005930, 035420) are Korean stocks โ€” use get_financials_kr. ASCII tickers (AAPL, MSFT) use get_financials. +- 6-digit numeric tickers (e.g. 005930, 035420) are Korean stocks โ€” use get_financials_kr for fundamentals, get_filings_kr for DART disclosures, get_large_holders_kr for 5%-rule major shareholders, get_insider_trades_kr for executive/insider ownership. ASCII tickers (AAPL, MSFT) use the US tools. - Only use web_fetch when headlines are insufficient (need quotes, deal specifics, earnings details). - Tool results are automatically capped. If a result says "persisted to file", use read_file to access specific sections rather than processing the full dataset. - Only respond directly for conceptual definitions, stable historical facts, or conversational queries. diff --git a/src/cli.ts b/src/cli.ts index 3becbe33c..70941812e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -71,6 +71,18 @@ function summarizeToolResult(tool: string, args: Record, result } if (typeof parsed.data === 'object') { const keys = Object.keys(parsed.data).filter((key) => !key.startsWith('_')); + if (tool === 'get_filings_kr') { + const n = Array.isArray(parsed.data.filings) ? parsed.data.filings.length : 0; + return `Found ${n} filings`; + } + if (tool === 'get_large_holders_kr') { + const n = Array.isArray(parsed.data.holders) ? parsed.data.holders.length : 0; + return `Found ${n} holders`; + } + if (tool === 'get_insider_trades_kr') { + const n = Array.isArray(parsed.data.trades) ? parsed.data.trades.length : 0; + return `Found ${n} reports`; + } if (tool === 'get_financials' || tool === 'get_financials_kr' || tool === 'get_market_data' || tool === 'stock_screener') { if (keys.length === 0) return 'Done'; return keys.length === 1 ? 'Called 1 data source' : `Called ${keys.length} data sources`; diff --git a/src/tools/finance-kr/get-filings-kr.ts b/src/tools/finance-kr/get-filings-kr.ts new file mode 100644 index 000000000..d856a844d --- /dev/null +++ b/src/tools/finance-kr/get-filings-kr.ts @@ -0,0 +1,112 @@ +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { z } from 'zod'; +import { dartApi } from './api.js'; +import { toDartDate, isNoDataError } from './utils.js'; +import { resolveTicker } from '../../data/ticker-registry.js'; +import { formatToolResult } from '../types.js'; +import { TTL_1H } from '../finance/utils.js'; + +export const GET_FILINGS_KR_DESCRIPTION = `Retrieves disclosure (๊ณต์‹œ) metadata for a Korean (KOSPI/KOSDAQ) listed company from DART's ๊ณต์‹œ๊ฒ€์ƒ‰ (filing search). Returns a list of filings with report name (report_nm), receipt number (rcept_no), receipt date (rcept_dt), filer (flr_nm), and remarks (rm). This is metadata only โ€” the Korean equivalent of get_filings for US stocks. + +Use this for "what has company X disclosed recently", regulatory filing history, or to find a specific ๋ณด๊ณ ์„œ. Accepts a 6-digit ticker (e.g. 005930 for Samsung Electronics). Optionally filter by filing_type and a date range. For financial statement line items use get_financials_kr instead.`; + +/** DART ๊ณต์‹œ์œ ํ˜• (pblntf_ty) single-char codes. */ +const PBLNTF_TY: Record = { + periodic: 'A', // ์ •๊ธฐ๊ณต์‹œ (์‚ฌ์—…ยท๋ฐ˜๊ธฐยท๋ถ„๊ธฐ๋ณด๊ณ ์„œ) + material: 'B', // ์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ  + issuance: 'C', // ๋ฐœํ–‰๊ณต์‹œ + ownership: 'D', // ์ง€๋ถ„๊ณต์‹œ (5%๋ฃฐ, ์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ) + audit: 'F', // ์™ธ๋ถ€๊ฐ์‚ฌ๊ด€๋ จ + exchange: 'I', // ๊ฑฐ๋ž˜์†Œ๊ณต์‹œ +}; + +const InputSchema = z.object({ + ticker: z + .string() + .regex(/^\d{6}$/, 'Korean ticker must be a 6-digit string (e.g. 005930 for Samsung).') + .describe('6-digit Korean stock ticker (e.g. 005930 for Samsung Electronics).'), + filing_type: z + .enum(['periodic', 'material', 'issuance', 'ownership', 'audit', 'exchange']) + .optional() + .describe( + "Optional DART disclosure category: 'periodic' (์ •๊ธฐ๊ณต์‹œ), 'material' (์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ), " + + "'issuance' (๋ฐœํ–‰๊ณต์‹œ), 'ownership' (์ง€๋ถ„๊ณต์‹œ), 'audit' (์™ธ๋ถ€๊ฐ์‚ฌ๊ด€๋ จ), 'exchange' (๊ฑฐ๋ž˜์†Œ๊ณต์‹œ). " + + 'Omit to return filings of any type.', + ), + start_date: z + .string() + .regex(/^\d{4}-\d{2}-\d{2}$/, 'Date must be YYYY-MM-DD.') + .optional() + .describe('Search start date (YYYY-MM-DD). Defaults to ~1 year ago.'), + end_date: z + .string() + .regex(/^\d{4}-\d{2}-\d{2}$/, 'Date must be YYYY-MM-DD.') + .optional() + .describe('Search end date (YYYY-MM-DD). Defaults to today.'), + limit: z + .number() + .int() + .min(1) + .max(100) + .default(20) + .describe('Maximum number of filings to return (default 20, max 100). Returns the most recent first.'), +}); + +function defaultRange(): { bgn_de: string; end_de: string } { + const today = new Date(); + const oneYearAgo = new Date(today); + oneYearAgo.setUTCFullYear(today.getUTCFullYear() - 1); + const fmt = (d: Date) => d.toISOString().slice(0, 10).replace(/-/g, ''); + return { bgn_de: fmt(oneYearAgo), end_de: fmt(today) }; +} + +export const getFilingsKr = new DynamicStructuredTool({ + name: 'get_filings_kr', + description: GET_FILINGS_KR_DESCRIPTION, + schema: InputSchema, + func: async (input) => { + const ticker = input.ticker.trim(); + const resolved = await resolveTicker(ticker); + if (!resolved) { + return formatToolResult({ error: `Ticker ${ticker} not found in DART corp registry` }, []); + } + + const range = defaultRange(); + const params = { + corp_code: resolved.corp_code, + bgn_de: input.start_date ? toDartDate(input.start_date) : range.bgn_de, + end_de: input.end_date ? toDartDate(input.end_date) : range.end_de, + pblntf_ty: input.filing_type ? PBLNTF_TY[input.filing_type] : undefined, + page_count: input.limit, + page_no: 1, + // Don't rely on DART's implicit default ordering โ€” promise most-recent-first explicitly. + sort: 'date', + sort_mth: 'desc', + }; + + try { + const { data, url } = await dartApi.get('/list.json', params, { + cacheable: true, + ttlMs: TTL_1H, + }); + return formatToolResult( + { + ticker, + corp_code: resolved.corp_code, + corp_name: resolved.corp_name, + filings: Array.isArray(data.list) ? data.list : [], + }, + [url], + ); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + const base = { + ticker, + corp_code: resolved.corp_code, + corp_name: resolved.corp_name, + filings: [], + }; + return formatToolResult(isNoDataError(message) ? base : { ...base, _error: message }, []); + } + }, +}); diff --git a/src/tools/finance-kr/get-insider-trades-kr.ts b/src/tools/finance-kr/get-insider-trades-kr.ts new file mode 100644 index 000000000..0c1ed8472 --- /dev/null +++ b/src/tools/finance-kr/get-insider-trades-kr.ts @@ -0,0 +1,66 @@ +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { z } from 'zod'; +import { dartApi } from './api.js'; +import { sortByRceptDtDesc, isNoDataError } from './utils.js'; +import { resolveTicker } from '../../data/ticker-registry.js'; +import { formatToolResult } from '../types.js'; +import { TTL_24H } from '../finance/utils.js'; + +export const GET_INSIDER_TRADES_KR_DESCRIPTION = `Retrieves ์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ ์†Œ์œ ๋ณด๊ณ  (executives' and major shareholders' ownership reports) for a Korean (KOSPI/KOSDAQ) listed company from DART. Korean executives, directors, and 10%+ shareholders must report their holdings and any changes โ€” the closest equivalent of US insider trades (SEC Form 4). + +Each report includes the reporter (repror), relationship to the company (isu_exctv_rgist_at / isu_exctv_ofcps), shares held (sp_stock_lmp_cnt), change in shares (sp_stock_lmp_irds_cnt), and receipt date (rcept_dt). Use this for "what are insiders at X doing" or to track executive/major-shareholder buying and selling. Accepts a 6-digit ticker (e.g. 005930 for Samsung Electronics). Returns the most recent reports first.`; + +const InputSchema = z.object({ + ticker: z + .string() + .regex(/^\d{6}$/, 'Korean ticker must be a 6-digit string (e.g. 005930 for Samsung).') + .describe('6-digit Korean stock ticker (e.g. 005930 for Samsung Electronics).'), + limit: z + .number() + .int() + .min(1) + .max(100) + .default(20) + .describe('Maximum number of reports to return (default 20). Returns the most recent first.'), +}); + +export const getInsiderTradesKr = new DynamicStructuredTool({ + name: 'get_insider_trades_kr', + description: GET_INSIDER_TRADES_KR_DESCRIPTION, + schema: InputSchema, + func: async (input) => { + const ticker = input.ticker.trim(); + const resolved = await resolveTicker(ticker); + if (!resolved) { + return formatToolResult({ error: `Ticker ${ticker} not found in DART corp registry` }, []); + } + + try { + // elestock.json has no date filter โ€” returns full history; sort + cap client-side. + const { data, url } = await dartApi.get( + '/elestock.json', + { corp_code: resolved.corp_code }, + { cacheable: true, ttlMs: TTL_24H }, + ); + const list = Array.isArray(data.list) ? data.list : []; + return formatToolResult( + { + ticker, + corp_code: resolved.corp_code, + corp_name: resolved.corp_name, + trades: sortByRceptDtDesc(list, input.limit), + }, + [url], + ); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + const base = { + ticker, + corp_code: resolved.corp_code, + corp_name: resolved.corp_name, + trades: [], + }; + return formatToolResult(isNoDataError(message) ? base : { ...base, _error: message }, []); + } + }, +}); diff --git a/src/tools/finance-kr/get-large-holders-kr.ts b/src/tools/finance-kr/get-large-holders-kr.ts new file mode 100644 index 000000000..734fa34ac --- /dev/null +++ b/src/tools/finance-kr/get-large-holders-kr.ts @@ -0,0 +1,66 @@ +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { z } from 'zod'; +import { dartApi } from './api.js'; +import { sortByRceptDtDesc, isNoDataError } from './utils.js'; +import { resolveTicker } from '../../data/ticker-registry.js'; +import { formatToolResult } from '../types.js'; +import { TTL_24H } from '../finance/utils.js'; + +export const GET_LARGE_HOLDERS_KR_DESCRIPTION = `Retrieves ๋Œ€๋Ÿ‰๋ณด์œ  ์ƒํ™ฉ๋ณด๊ณ  (5%-rule large shareholding reports) for a Korean (KOSPI/KOSDAQ) listed company from DART. Under Korean law, any party crossing 5% beneficial ownership (or changing a held stake by โ‰ฅ1%) must disclose. This is the closest Korean equivalent of US 13F / large-holder data. + +Each report includes the filer (repror), shares held (stkqy), ownership ratio (stkrt), change in ratio (stkrt_irds), reason for filing (report_resn), and receipt date (rcept_dt). Use this for "who are the major shareholders of X" or to track stake-building. Accepts a 6-digit ticker (e.g. 005930 for Samsung Electronics). Returns the most recent reports first.`; + +const InputSchema = z.object({ + ticker: z + .string() + .regex(/^\d{6}$/, 'Korean ticker must be a 6-digit string (e.g. 005930 for Samsung).') + .describe('6-digit Korean stock ticker (e.g. 005930 for Samsung Electronics).'), + limit: z + .number() + .int() + .min(1) + .max(100) + .default(20) + .describe('Maximum number of reports to return (default 20). Returns the most recent first.'), +}); + +export const getLargeHoldersKr = new DynamicStructuredTool({ + name: 'get_large_holders_kr', + description: GET_LARGE_HOLDERS_KR_DESCRIPTION, + schema: InputSchema, + func: async (input) => { + const ticker = input.ticker.trim(); + const resolved = await resolveTicker(ticker); + if (!resolved) { + return formatToolResult({ error: `Ticker ${ticker} not found in DART corp registry` }, []); + } + + try { + // majorstock.json has no date filter โ€” returns full history; sort + cap client-side. + const { data, url } = await dartApi.get( + '/majorstock.json', + { corp_code: resolved.corp_code }, + { cacheable: true, ttlMs: TTL_24H }, + ); + const list = Array.isArray(data.list) ? data.list : []; + return formatToolResult( + { + ticker, + corp_code: resolved.corp_code, + corp_name: resolved.corp_name, + holders: sortByRceptDtDesc(list, input.limit), + }, + [url], + ); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + const base = { + ticker, + corp_code: resolved.corp_code, + corp_name: resolved.corp_name, + holders: [], + }; + return formatToolResult(isNoDataError(message) ? base : { ...base, _error: message }, []); + } + }, +}); diff --git a/src/tools/finance-kr/utils.test.ts b/src/tools/finance-kr/utils.test.ts new file mode 100644 index 000000000..ce8fae7f1 --- /dev/null +++ b/src/tools/finance-kr/utils.test.ts @@ -0,0 +1,41 @@ +import { describe, it, expect } from 'bun:test'; +import { toDartDate, sortByRceptDtDesc, isNoDataError } from './utils.js'; + +describe('toDartDate', () => { + it('strips dashes from an ISO date', () => { + expect(toDartDate('2024-03-15')).toBe('20240315'); + }); +}); + +describe('sortByRceptDtDesc', () => { + it('orders by rcept_dt descending and caps to limit', () => { + const list = [ + { rcept_dt: '20230101', id: 'a' }, + { rcept_dt: '20240601', id: 'b' }, + { rcept_dt: '20240105', id: 'c' }, + ]; + const result = sortByRceptDtDesc(list, 2); + expect(result.map((r) => r.id)).toEqual(['b', 'c']); + }); + + it('does not mutate the input array', () => { + const list = [{ rcept_dt: '20230101' }, { rcept_dt: '20240601' }]; + sortByRceptDtDesc(list, 10); + expect(list[0].rcept_dt).toBe('20230101'); + }); + + it('tolerates missing rcept_dt', () => { + const list = [{ rcept_dt: '20240101' }, {}]; + expect(sortByRceptDtDesc(list, 10)).toHaveLength(2); + }); +}); + +describe('isNoDataError', () => { + it('detects DART status 013 (no data)', () => { + expect(isNoDataError('[DART API] foo โ€” status=013 (์กฐํšŒ๋œ ๋ฐ์ดํƒ€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค)')).toBe(true); + }); + + it('returns false for other errors', () => { + expect(isNoDataError('[DART API] foo โ€” status=020 (์‚ฌ์šฉํ•œ๋„์ดˆ๊ณผ)')).toBe(false); + }); +}); diff --git a/src/tools/finance-kr/utils.ts b/src/tools/finance-kr/utils.ts new file mode 100644 index 000000000..36467a1a4 --- /dev/null +++ b/src/tools/finance-kr/utils.ts @@ -0,0 +1,31 @@ +/** + * Shared helpers for Korean (DART) disclosure tools. + */ + +/** Convert an ISO `YYYY-MM-DD` date to DART's `YYYYMMDD` format. */ +export function toDartDate(isoDate: string): string { + return isoDate.replace(/-/g, ''); +} + +/** + * DART's majorstock/elestock endpoints return the full reporting history with + * no server-side date filter. Sort by `rcept_dt` (YYYYMMDD string) descending + * so the most recent disclosures come first, then cap to `limit`. + */ +export function sortByRceptDtDesc( + list: T[], + limit: number, +): T[] { + return [...list] + .sort((a, b) => String(b.rcept_dt ?? '').localeCompare(String(a.rcept_dt ?? ''))) + .slice(0, limit); +} + +/** + * DART status '013' (์กฐํšŒ๋œ ๋ฐ์ดํƒ€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค) is a valid "no data" outcome, not + * a failure โ€” surfaced as an error by `dartApi`. Detect it so callers can return + * an empty list instead of an error. + */ +export function isNoDataError(message: string): boolean { + return message.includes('status=013'); +} diff --git a/src/tools/registry.ts b/src/tools/registry.ts index 51c15ce16..a825c43f5 100644 --- a/src/tools/registry.ts +++ b/src/tools/registry.ts @@ -12,6 +12,9 @@ import { writeFileTool, WRITE_FILE_DESCRIPTION } from './filesystem/write-file.j import { editFileTool, EDIT_FILE_DESCRIPTION } from './filesystem/edit-file.js'; import { GET_FINANCIALS_DESCRIPTION } from './finance/get-financials.js'; import { createGetFinancialsKr, GET_FINANCIALS_KR_DESCRIPTION } from './finance-kr/get-financials-kr.js'; +import { getFilingsKr, GET_FILINGS_KR_DESCRIPTION } from './finance-kr/get-filings-kr.js'; +import { getLargeHoldersKr, GET_LARGE_HOLDERS_KR_DESCRIPTION } from './finance-kr/get-large-holders-kr.js'; +import { getInsiderTradesKr, GET_INSIDER_TRADES_KR_DESCRIPTION } from './finance-kr/get-insider-trades-kr.js'; import { GET_MARKET_DATA_DESCRIPTION } from './finance/get-market-data.js'; import { READ_FILINGS_DESCRIPTION } from './finance/read-filings.js'; import { SCREEN_STOCKS_DESCRIPTION } from './finance/screen-stocks.js'; @@ -187,6 +190,27 @@ export function getToolRegistry(model: string): RegisteredTool[] { compactDescription: 'Korean stocks (6-digit tickers like 005930). DART ์‚ฌ์—…ยท๋ฐ˜๊ธฐยท๋ถ„๊ธฐ๋ณด๊ณ ์„œ (K-IFRS).', concurrencySafe: true, }); + tools.push({ + name: 'get_filings_kr', + tool: getFilingsKr, + description: GET_FILINGS_KR_DESCRIPTION, + compactDescription: 'Korean stock DART ๊ณต์‹œ (filings) metadata for 6-digit tickers. Disclosure history and report search.', + concurrencySafe: true, + }); + tools.push({ + name: 'get_large_holders_kr', + tool: getLargeHoldersKr, + description: GET_LARGE_HOLDERS_KR_DESCRIPTION, + compactDescription: 'Korean 5%๋ฃฐ ๋Œ€๋Ÿ‰๋ณด์œ  major shareholders for 6-digit tickers (13F-equivalent).', + concurrencySafe: true, + }); + tools.push({ + name: 'get_insider_trades_kr', + tool: getInsiderTradesKr, + description: GET_INSIDER_TRADES_KR_DESCRIPTION, + compactDescription: 'Korean ์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ ์†Œ์œ ๋ณด๊ณ  (insider/executive ownership) for 6-digit tickers.', + concurrencySafe: true, + }); } if (process.env.X_BEARER_TOKEN) { From 37c5b7501197fae500af8e7b952ced8eef2b7e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SH=20KWON=20=F0=9F=87=B0=F0=9F=87=B7?= Date: Sat, 30 May 2026 03:57:31 +0900 Subject: [PATCH 05/70] =?UTF-8?q?Add=20Korea-specific=20tools=20=E2=80=94?= =?UTF-8?q?=20short=20balance,=20foreign=20ownership,=20NPS=20holdings=20(?= =?UTF-8?q?Phase=203)=20(#5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Korea-specific tools โ€” short balance, foreign ownership, NPS holdings (Phase 3) Phase 3 of Korean stock support. Unlike Phases 1โ€“2 (all clean DART JSON+key), none of these three live in a clean official keyed API, so each uses a different source: - get_foreign_ownership_kr โ€” Naver mobile JSON (m.stock.naver.com trend), keyless, registered unconditionally. - get_short_balance_kr โ€” KRX Data Marketplace login scrape (bld MDCSTAT30502). KRX now returns "LOGOUT" to anonymous requests, so a member session is required; login flow ported from pykrx (krx-session.ts) with a KRX_COOKIE override for social-login accounts. KRX keys on ISIN, not ticker โ†’ new krx-instrument-registry (bld MDCSTAT01901). Gated by KRX_ID/KRX_PW or KRX_COOKIE. - get_nps_holdings โ€” data.go.kr odcloud dataset 3070507 (year-end, by stock name). Gated by DATA_GO_KR_SERVICE_KEY. All three live-verified (์‚ผ์„ฑ ์™ธ๊ตญ์ธ 48.27%, ๊ณต๋งค๋„ 89/97 non-zero, NPS 1,200์ข…๋ชฉ ์‚ผ์„ฑ ์ง€๋ถ„์œจ 7.26%). typecheck + 84 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) * Address review: README sourcing, ISO date normalization, more tests - README: split the 3 Korea-specific tools out of the "DART ๊ธฐ๋ฐ˜" section into their own table with per-tool source/key; fix OPEN_DART_KEY โ†’ DART_API_KEY. - Normalize tool dates to ISO YYYY-MM-DD via new toIsoDate (KRX YYYY/MM/DD and Naver YYYYMMDD were inconsistent across tools). - Tests: toIsoDate, defaultRange, sessionFromCookie (caught + fixed an empty-key parse bug on leading-space segments), isLogoutBody. Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- CLAUDE.md | 10 +- README.md | 15 +- env.example | 14 ++ src/agent/prompts.ts | 2 +- src/cli.ts | 12 ++ src/data/fetchers/krx-instruments.test.ts | 51 +++++ src/data/fetchers/krx-instruments.ts | 49 +++++ src/data/fetchers/nps-holdings.test.ts | 23 ++ src/data/fetchers/nps-holdings.ts | 104 +++++++++ src/data/krx-instrument-registry.test.ts | 18 ++ src/data/krx-instrument-registry.ts | 117 ++++++++++ src/data/nps-registry.ts | 97 +++++++++ .../get-foreign-ownership-kr.test.ts | 25 +++ .../finance-kr/get-foreign-ownership-kr.ts | 68 ++++++ src/tools/finance-kr/get-nps-holdings.test.ts | 25 +++ src/tools/finance-kr/get-nps-holdings.ts | 87 ++++++++ .../finance-kr/get-short-balance-kr.test.ts | 50 +++++ src/tools/finance-kr/get-short-balance-kr.ts | 97 +++++++++ src/tools/finance-kr/krx-api.test.ts | 17 ++ src/tools/finance-kr/krx-api.ts | 115 ++++++++++ src/tools/finance-kr/krx-session.test.ts | 23 ++ src/tools/finance-kr/krx-session.ts | 204 ++++++++++++++++++ src/tools/finance-kr/naver-api.ts | 63 ++++++ src/tools/finance-kr/utils.test.ts | 58 ++++- src/tools/finance-kr/utils.ts | 40 ++++ src/tools/registry.ts | 45 ++++ 26 files changed, 1420 insertions(+), 9 deletions(-) create mode 100644 src/data/fetchers/krx-instruments.test.ts create mode 100644 src/data/fetchers/krx-instruments.ts create mode 100644 src/data/fetchers/nps-holdings.test.ts create mode 100644 src/data/fetchers/nps-holdings.ts create mode 100644 src/data/krx-instrument-registry.test.ts create mode 100644 src/data/krx-instrument-registry.ts create mode 100644 src/data/nps-registry.ts create mode 100644 src/tools/finance-kr/get-foreign-ownership-kr.test.ts create mode 100644 src/tools/finance-kr/get-foreign-ownership-kr.ts create mode 100644 src/tools/finance-kr/get-nps-holdings.test.ts create mode 100644 src/tools/finance-kr/get-nps-holdings.ts create mode 100644 src/tools/finance-kr/get-short-balance-kr.test.ts create mode 100644 src/tools/finance-kr/get-short-balance-kr.ts create mode 100644 src/tools/finance-kr/krx-api.test.ts create mode 100644 src/tools/finance-kr/krx-api.ts create mode 100644 src/tools/finance-kr/krx-session.test.ts create mode 100644 src/tools/finance-kr/krx-session.ts create mode 100644 src/tools/finance-kr/naver-api.ts diff --git a/CLAUDE.md b/CLAUDE.md index e711b2e5a..24ac065db 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -58,9 +58,13 @@ These bite without warning. Fix once you learn them. - [ ] `get_insider_trades_kr` โ€” ์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ ### Phase 3 โ€” Korea-specific -- [ ] `get_nps_holdings` โ€” ๊ตญ๋ฏผ์—ฐ๊ธˆ ๋ถ„๊ธฐ ๊ณต์‹œ -- [ ] `get_short_balance_kr` โ€” KRX ๊ณต๋งค๋„ ์ž”๊ณ  -- [ ] `get_foreign_ownership_kr` โ€” ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ +- [x] `get_foreign_ownership_kr` โ€” ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ. Source: **Naver mobile JSON** (`m.stock.naver.com/api/stock/{code}/trend`), keyless. Registered unconditionally. +- [x] `get_short_balance_kr` โ€” ๊ณต๋งค๋„ ์ˆœ๋ณด์œ ์ž”๊ณ . Source: **KRX Data Marketplace login scrape** (bld `MDCSTAT30502`). Needs `KRX_ID`/`KRX_PW` (anonymous access returns `LOGOUT` since 2024โ€“25). Tickerโ†’ISIN via `MDCSTAT01901` (`src/data/krx-instrument-registry.ts`). Login flow ported from pykrx in `src/tools/finance-kr/krx-session.ts`. +- [x] `get_nps_holdings` โ€” ๊ตญ๋ฏผ์—ฐ๊ธˆ ๋ณด์œ . Source: **data.go.kr odcloud** dataset 3070507 (year-end snapshot, not quarterly). Needs `DATA_GO_KR_SERVICE_KEY` (Decoded key). No ticker column โ†’ matches by Korean stock name. + +**Phase 3 source notes** (the clean keyed-API assumption from Phases 1โ€“2 did NOT hold): +- Official KRX Open API (`openapi.krx.co.kr`) has neither short nor foreign data; data.go.kr has no short/foreign open API. KRX getJsonData requires a member login now. +- KRX endpoints key on **ISIN** (`isuCd`), not the 6-digit ticker โ†’ the KRX instrument registry exists only for this. ### Phase 4 โ€” Skill adaptation - [ ] `src/skills/dcf` โ€” branch on market (K-IFRS, ๊ฑฐ๋ž˜์„ธ, ๋ฐฐ๋‹น์„ธ) diff --git a/README.md b/README.md index 0d7ea0f98..a032c41d7 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ flowchart TB ### ์‹ ๊ทœ ํˆด (DART ๊ธฐ๋ฐ˜) -`OPEN_DART_KEY`๊ฐ€ ์„ค์ •๋˜๋ฉด ์ž๋™ ๋“ฑ๋ก๋ฉ๋‹ˆ๋‹ค. +`DART_API_KEY`๊ฐ€ ์„ค์ •๋˜๋ฉด ์ž๋™ ๋“ฑ๋ก๋ฉ๋‹ˆ๋‹ค. | ํˆด | ๋Œ€์‘๋˜๋Š” ๋ฏธ๊ตญ ๊ฐœ๋… | ๋ฐ์ดํ„ฐ ์†Œ์Šค | |---|---|---| @@ -85,9 +85,16 @@ flowchart TB | `get_filings_kr` | SEC EDGAR | DART ๊ณต์‹œ ๊ฒ€์ƒ‰ | | `get_large_holders_kr` | 13F (5% ์ด์ƒ ๋ณด์œ ) | DART ๋Œ€๋Ÿ‰๋ณด์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ | | `get_insider_trades_kr` | Form 4 (๋‚ด๋ถ€์ž ๊ฑฐ๋ž˜) | DART ์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ ๋ณด๊ณ  | -| `get_nps_holdings` | (๋ฏธ๊ตญ์—” ์—†์Œ) | ๊ตญ๋ฏผ์—ฐ๊ธˆ ๋ถ„๊ธฐ ๊ณต์‹œ | -| `get_short_balance_kr` | Short interest | KRX ๊ณต๋งค๋„ ์ž”๊ณ  | -| `get_foreign_ownership_kr` | (๋ฏธ๊ตญ์—” ์—†์Œ) | KRX ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ | + +### ์‹ ๊ทœ ํˆด (Korea-specific, ์†Œ์Šค๋ณ„ ํ‚ค) + +DART์— ์—†๋Š” ํ•œ๊ตญ ํŠนํ™” ๋ฐ์ดํ„ฐ. ํˆด๋งˆ๋‹ค ์†Œ์Šคยทํ‚ค๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. + +| ํˆด | ๋Œ€์‘๋˜๋Š” ๋ฏธ๊ตญ ๊ฐœ๋… | ๋ฐ์ดํ„ฐ ์†Œ์Šค | ํ™œ์„ฑํ™” ์กฐ๊ฑด | +|---|---|---|---| +| `get_foreign_ownership_kr` | (๋ฏธ๊ตญ์—” ์—†์Œ) | ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ (Naver) | ํ•ญ์ƒ (ํ‚ค ๋ถˆํ•„์š”) | +| `get_short_balance_kr` | Short interest | KRX ๊ณต๋งค๋„ ์ˆœ๋ณด์œ ์ž”๊ณ  | `KRX_ID`+`KRX_PW` ๋˜๋Š” `KRX_COOKIE` | +| `get_nps_holdings` | (๋ฏธ๊ตญ์—” ์—†์Œ) | ๊ตญ๋ฏผ์—ฐ๊ธˆ ๊ตญ๋‚ด์ฃผ์‹ ํˆฌ์ž์ •๋ณด (data.go.kr) | `DATA_GO_KR_SERVICE_KEY` | ### ์ข…๋ชฉ ์ฝ”๋“œ ํ•ด๊ฒฐ ํ๋ฆ„ diff --git a/env.example b/env.example index c98f2c4a2..77eaf4c21 100644 --- a/env.example +++ b/env.example @@ -21,6 +21,20 @@ FINANCIAL_DATASETS_API_KEY=your-financial-datasets-api-key # Issue at https://opendart.fss.or.kr DART_API_KEY=your-dart-api-key +# Korean ๊ณต๋งค๋„ ์ž”๊ณ  (short interest) โ€” enables get_short_balance_kr. +# Requires a KRX Data Marketplace login (data.krx.co.kr); anonymous access is +# blocked. Either set a native ID/PW, OR (for social/Naver-login accounts that +# have no password) paste the Cookie header from a logged-in browser session +# into KRX_COOKIE. KRX_COOKIE takes precedence but cannot auto-refresh โ€” re-paste +# when it expires. +KRX_ID=your-krx-id +KRX_PW=your-krx-password +# KRX_COOKIE=JSESSIONID=...; (copy from data.krx.co.kr DevTools after logging in) + +# Korean ๊ตญ๋ฏผ์—ฐ๊ธˆ (NPS) holdings โ€” enables get_nps_holdings. +# Use the *Decoded* service key from https://www.data.go.kr (dataset 3070507). +DATA_GO_KR_SERVICE_KEY=your-data-go-kr-service-key + # Web Search API Keys (Exa โ†’ Perplexity โ†’ Tavily โ†’ LangSearch) EXASEARCH_API_KEY=your-exa-api-key PERPLEXITY_API_KEY=your-perplexity-api-key diff --git a/src/agent/prompts.ts b/src/agent/prompts.ts index c1ee2f8d2..425d799eb 100644 --- a/src/agent/prompts.ts +++ b/src/agent/prompts.ts @@ -244,7 +244,7 @@ ${toolDescriptions} ## Tool Usage Policy - Call get_financials or get_market_data ONCE with the full natural language query โ€” they handle multi-company/multi-metric requests internally. Do NOT break up queries into multiple calls. -- 6-digit numeric tickers (e.g. 005930, 035420) are Korean stocks โ€” use get_financials_kr for fundamentals, get_filings_kr for DART disclosures, get_large_holders_kr for 5%-rule major shareholders, get_insider_trades_kr for executive/insider ownership. ASCII tickers (AAPL, MSFT) use the US tools. +- 6-digit numeric tickers (e.g. 005930, 035420) are Korean stocks โ€” use get_financials_kr for fundamentals, get_filings_kr for DART disclosures, get_large_holders_kr for 5%-rule major shareholders, get_insider_trades_kr for executive/insider ownership, get_short_balance_kr for ๊ณต๋งค๋„ ์ž”๊ณ  (short interest), get_foreign_ownership_kr for ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ (foreign ownership), and get_nps_holdings for ๊ตญ๋ฏผ์—ฐ๊ธˆ (National Pension Service) holdings. ASCII tickers (AAPL, MSFT) use the US tools. - Only use web_fetch when headlines are insufficient (need quotes, deal specifics, earnings details). - Tool results are automatically capped. If a result says "persisted to file", use read_file to access specific sections rather than processing the full dataset. - Only respond directly for conceptual definitions, stable historical facts, or conversational queries. diff --git a/src/cli.ts b/src/cli.ts index 70941812e..8fdbacbe0 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -83,6 +83,18 @@ function summarizeToolResult(tool: string, args: Record, result const n = Array.isArray(parsed.data.trades) ? parsed.data.trades.length : 0; return `Found ${n} reports`; } + if (tool === 'get_short_balance_kr') { + const n = Array.isArray(parsed.data.short) ? parsed.data.short.length : 0; + return `Found ${n} days`; + } + if (tool === 'get_foreign_ownership_kr') { + const n = Array.isArray(parsed.data.ownership) ? parsed.data.ownership.length : 0; + return `Found ${n} days`; + } + if (tool === 'get_nps_holdings') { + const n = Array.isArray(parsed.data.holdings) ? parsed.data.holdings.length : 0; + return `Found ${n} holdings`; + } if (tool === 'get_financials' || tool === 'get_financials_kr' || tool === 'get_market_data' || tool === 'stock_screener') { if (keys.length === 0) return 'Done'; return keys.length === 1 ? 'Called 1 data source' : `Called ${keys.length} data sources`; diff --git a/src/data/fetchers/krx-instruments.test.ts b/src/data/fetchers/krx-instruments.test.ts new file mode 100644 index 000000000..cbe114767 --- /dev/null +++ b/src/data/fetchers/krx-instruments.test.ts @@ -0,0 +1,51 @@ +import { describe, it, expect } from 'bun:test'; +import { parseKrxInstruments } from './krx-instruments.js'; + +const SAMPLE = { + OutBlock_1: [ + { + ISU_CD: 'KR7005930003', + ISU_SRT_CD: '005930', + ISU_NM: '์‚ผ์„ฑ์ „์ž๋ณดํ†ต์ฃผ', + ISU_ABBRV: '์‚ผ์„ฑ์ „์ž', + MKT_TP_NM: 'KOSPI', + }, + { + ISU_CD: 'KR7035420009', + ISU_SRT_CD: '035420', + ISU_NM: 'NAVER', + ISU_ABBRV: 'NAVER', + MKT_TP_NM: 'KOSPI', + }, + ], +}; + +describe('parseKrxInstruments', () => { + it('maps ticker, isin, name, and market', () => { + const entries = parseKrxInstruments(SAMPLE); + expect(entries).toHaveLength(2); + expect(entries[0]).toEqual({ + ticker: '005930', + isin: 'KR7005930003', + name: '์‚ผ์„ฑ์ „์ž', + market: 'KOSPI', + }); + }); + + it('drops rows missing a 6-digit ticker or an ISIN', () => { + const entries = parseKrxInstruments({ + OutBlock_1: [ + { ISU_CD: 'KR7005930003', ISU_SRT_CD: '00593', ISU_ABBRV: 'bad ticker' }, + { ISU_SRT_CD: '005930', ISU_ABBRV: 'no isin' }, + { ISU_CD: 'KR7005930003', ISU_SRT_CD: '005930', ISU_ABBRV: 'ok' }, + ], + }); + expect(entries).toHaveLength(1); + expect(entries[0].name).toBe('ok'); + }); + + it('returns [] when OutBlock_1 is absent or empty', () => { + expect(parseKrxInstruments({})).toEqual([]); + expect(parseKrxInstruments({ OutBlock_1: [] })).toEqual([]); + }); +}); diff --git a/src/data/fetchers/krx-instruments.ts b/src/data/fetchers/krx-instruments.ts new file mode 100644 index 000000000..36c0ae9f2 --- /dev/null +++ b/src/data/fetchers/krx-instruments.ts @@ -0,0 +1,49 @@ +/** + * KRX ์ „์ข…๋ชฉ ๊ธฐ๋ณธ์ •๋ณด (all-listed instruments) โ€” provides the ticker โ†” ISIN map. + * + * KRX statistical endpoints key on ISIN (`isuCd`, e.g. KR7005930003), not the + * 6-digit ticker, so tools that hit KRX must resolve the ticker first. This + * fetcher pulls the full listing via bld `MDCSTAT01901` (requires an + * authenticated KRX session โ€” see krx-api/krx-session). + */ +import { krxApi } from '../../tools/finance-kr/krx-api.js'; +import { extractOutBlock } from '../../tools/finance-kr/utils.js'; + +export interface KrxInstrumentEntry { + ticker: string; // 6-digit short code (ISU_SRT_CD) + isin: string; // full ISIN (ISU_CD) + name: string; // abbreviated name (ISU_ABBRV), falling back to ISU_NM + market: string; // KOSPI / KOSDAQ / KONEX (MKT_TP_NM) +} + +function normalize(value: unknown): string { + if (value === undefined || value === null) return ''; + return String(value).trim(); +} + +export function parseKrxInstruments(data: Record): KrxInstrumentEntry[] { + const rows = extractOutBlock(data); + const out: KrxInstrumentEntry[] = []; + for (const raw of rows) { + const row = raw as Record; + const ticker = normalize(row.ISU_SRT_CD); + const isin = normalize(row.ISU_CD); + // Drop rows without a 6-digit ticker or an ISIN (field drift / odd rows). + if (!/^\d{6}$/.test(ticker) || !isin) continue; + out.push({ + ticker, + isin, + name: normalize(row.ISU_ABBRV) || normalize(row.ISU_NM), + market: normalize(row.MKT_TP_NM), + }); + } + return out; +} + +export async function fetchKrxInstruments(): Promise { + const { data } = await krxApi.get('dbms/MDC/STAT/standard/MDCSTAT01901', { + mktId: 'ALL', + segTpCd: 'ALL', + }); + return parseKrxInstruments(data); +} diff --git a/src/data/fetchers/nps-holdings.test.ts b/src/data/fetchers/nps-holdings.test.ts new file mode 100644 index 000000000..fc9315991 --- /dev/null +++ b/src/data/fetchers/nps-holdings.test.ts @@ -0,0 +1,23 @@ +import { describe, it, expect } from 'bun:test'; +import { parseNpsRows } from './nps-holdings.js'; + +describe('parseNpsRows', () => { + it('matches columns by substring (unit/spacing drift tolerant)', () => { + const rows = [ + { + ๋ฒˆํ˜ธ: '1', + ์ข…๋ชฉ๋ช…: '์‚ผ์„ฑ์ „์ž', + 'ํ‰๊ฐ€์•ก(์–ต์›)': '350,000', + '์ž์‚ฐ๊ตฐ ๋‚ด ๋น„์ค‘(%)': '25.1', + '์ง€๋ถ„์œจ(%)': '7.5', + }, + ]; + expect(parseNpsRows(rows)).toEqual([ + { name: '์‚ผ์„ฑ์ „์ž', evalAmount: 350000, weightPct: 25.1, shareRatioPct: 7.5 }, + ]); + }); + + it('skips rows without a name', () => { + expect(parseNpsRows([{ ๋ฒˆํ˜ธ: '2', 'ํ‰๊ฐ€์•ก(์–ต์›)': '10' }])).toEqual([]); + }); +}); diff --git a/src/data/fetchers/nps-holdings.ts b/src/data/fetchers/nps-holdings.ts new file mode 100644 index 000000000..a0edcf857 --- /dev/null +++ b/src/data/fetchers/nps-holdings.ts @@ -0,0 +1,104 @@ +/** + * National Pension Service (๊ตญ๋ฏผ์—ฐ๊ธˆ๊ณต๋‹จ) domestic equity holdings. + * + * Source: data.go.kr dataset 3070507 "๊ตญ๋ฏผ์—ฐ๊ธˆ๊ณต๋‹จ_๊ตญ๋‚ด์ฃผ์‹ ํˆฌ์ž์ •๋ณด", served as + * an odcloud REST JSON API (UTF-8). Year-end snapshot. Columns: ์ข…๋ชฉ๋ช…, + * ํ‰๊ฐ€์•ก(์–ต์›), ์ž์‚ฐ๊ตฐ ๋‚ด ๋น„์ค‘(%), ์ง€๋ถ„์œจ(%). Requires DATA_GO_KR_SERVICE_KEY. + * + * The dataset exposes no ticker column โ€” only the Korean stock name โ€” so the + * tool layer matches a queried ticker to its name. Column keys carry units in + * their header text and have drifted across versions, so parsing matches by + * substring rather than exact key. + */ +import { logger } from '../../utils/logger.js'; +import { parseKrxNumber } from '../../tools/finance-kr/utils.js'; + +const ODCLOUD_BASE = + 'https://api.odcloud.kr/api/3070507/v1/uddi:cc757223-fdc0-45b2-a617-dcbecec3fe1f'; +const PER_PAGE = 1000; +const MAX_PAGES = 20; // safety bound; NPS holds well under PER_PAGE * MAX_PAGES names + +export interface NpsHoldingEntry { + name: string; // ์ข…๋ชฉ๋ช… + evalAmount: number | null; // ํ‰๊ฐ€์•ก (์–ต์›) + weightPct: number | null; // ์ž์‚ฐ๊ตฐ ๋‚ด ๋น„์ค‘ (%) + shareRatioPct: number | null; // ์ง€๋ถ„์œจ (%) +} + +interface OdcloudPage { + currentCount?: number; + totalCount?: number; + data?: Record[]; +} + +function getServiceKey(): string { + return process.env.DATA_GO_KR_SERVICE_KEY || ''; +} + +function normalize(value: unknown): string { + if (value === undefined || value === null) return ''; + return String(value).trim(); +} + +function findKey(row: Record, substrings: string[]): string | undefined { + return Object.keys(row).find((key) => substrings.some((s) => key.includes(s))); +} + +export function parseNpsRows(rows: Record[]): NpsHoldingEntry[] { + const out: NpsHoldingEntry[] = []; + for (const row of rows) { + const nameKey = findKey(row, ['์ข…๋ชฉ๋ช…', '์ข…๋ชฉ']); + const name = nameKey ? normalize(row[nameKey]) : ''; + if (!name) continue; + const evalKey = findKey(row, ['ํ‰๊ฐ€์•ก']); + const weightKey = findKey(row, ['๋น„์ค‘']); + const shareKey = findKey(row, ['์ง€๋ถ„์œจ']); + out.push({ + name, + evalAmount: evalKey ? parseKrxNumber(row[evalKey]) : null, + weightPct: weightKey ? parseKrxNumber(row[weightKey]) : null, + shareRatioPct: shareKey ? parseKrxNumber(row[shareKey]) : null, + }); + } + return out; +} + +export async function fetchNpsHoldings(): Promise { + const serviceKey = getServiceKey(); + if (!serviceKey) { + throw new Error('[NPS] DATA_GO_KR_SERVICE_KEY not set'); + } + + const all: NpsHoldingEntry[] = []; + for (let page = 1; page <= MAX_PAGES; page++) { + const url = new URL(ODCLOUD_BASE); + url.searchParams.set('page', String(page)); + url.searchParams.set('perPage', String(PER_PAGE)); + url.searchParams.set('returnType', 'JSON'); + url.searchParams.set('serviceKey', serviceKey); + + let response: Response; + try { + response = await fetch(url.toString()); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`[NPS] network error (page ${page}) โ€” ${message}`); + throw new Error(`[NPS] request failed: ${message}`); + } + if (!response.ok) { + throw new Error(`[NPS] request failed: ${response.status} ${response.statusText}`); + } + const body = (await response.json().catch(() => { + throw new Error('[NPS] request failed: invalid JSON'); + })) as OdcloudPage; + + const rows = Array.isArray(body.data) ? body.data : []; + all.push(...parseNpsRows(rows)); + + const total = typeof body.totalCount === 'number' ? body.totalCount : undefined; + if (rows.length < PER_PAGE || (total !== undefined && page * PER_PAGE >= total)) { + break; + } + } + return all; +} diff --git a/src/data/krx-instrument-registry.test.ts b/src/data/krx-instrument-registry.test.ts new file mode 100644 index 000000000..0ec8a66fc --- /dev/null +++ b/src/data/krx-instrument-registry.test.ts @@ -0,0 +1,18 @@ +import { describe, it, expect } from 'bun:test'; +import { buildKrxIndex } from './krx-instrument-registry.js'; +import type { KrxInstrumentEntry } from './fetchers/krx-instruments.js'; + +const ENTRIES: KrxInstrumentEntry[] = [ + { ticker: '005930', isin: 'KR7005930003', name: '์‚ผ์„ฑ์ „์ž', market: 'KOSPI' }, + { ticker: '035420', isin: 'KR7035420009', name: 'NAVER', market: 'KOSPI' }, +]; + +describe('buildKrxIndex', () => { + it('indexes by ticker and by name', () => { + const cache = buildKrxIndex(ENTRIES, 123); + expect(cache.fetchedAt).toBe(123); + expect(cache.byTicker.get('005930')?.isin).toBe('KR7005930003'); + expect(cache.byName.get('NAVER')?.ticker).toBe('035420'); + expect(cache.byTicker.get('999999')).toBeUndefined(); + }); +}); diff --git a/src/data/krx-instrument-registry.ts b/src/data/krx-instrument-registry.ts new file mode 100644 index 000000000..a9472df19 --- /dev/null +++ b/src/data/krx-instrument-registry.ts @@ -0,0 +1,117 @@ +/** + * Ticker โ†” ISIN registry for KRX-keyed tools, mirroring ticker-registry.ts. + * + * Cached to `/cache/krx/instruments.json` with a 7-day TTL, an + * in-memory index, in-flight dedup, and stale-fallback when a refresh fails + * (e.g. KRX login unavailable) so tools degrade gracefully. + */ +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { dirname } from 'node:path'; +import { dexterPath } from '../utils/paths.js'; +import { logger } from '../utils/logger.js'; +import { fetchKrxInstruments, type KrxInstrumentEntry } from './fetchers/krx-instruments.js'; + +const REGISTRY_FILE = dexterPath('cache', 'krx', 'instruments.json'); +const DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1000; + +interface RegistryFile { + fetchedAt: string; + entries: KrxInstrumentEntry[]; +} + +interface RegistryCache { + fetchedAt: number; + byTicker: Map; + byName: Map; +} + +let memoryCache: RegistryCache | null = null; +let inflight: Promise | null = null; + +export function buildKrxIndex(entries: KrxInstrumentEntry[], fetchedAt: number): RegistryCache { + const byTicker = new Map(); + const byName = new Map(); + for (const entry of entries) { + if (entry.ticker) byTicker.set(entry.ticker, entry); + if (entry.name) byName.set(entry.name, entry); + } + return { fetchedAt, byTicker, byName }; +} + +function readFromDisk(): RegistryCache | null { + if (!existsSync(REGISTRY_FILE)) return null; + try { + const raw = readFileSync(REGISTRY_FILE, 'utf-8'); + const parsed = JSON.parse(raw) as RegistryFile; + if (!parsed?.entries || !Array.isArray(parsed.entries)) return null; + return buildKrxIndex(parsed.entries, Date.parse(parsed.fetchedAt)); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.warn(`[krx-instrument-registry] failed to read cache โ€” ${message}`); + return null; + } +} + +function writeToDisk(entries: KrxInstrumentEntry[]): string { + const dir = dirname(REGISTRY_FILE); + if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); + const fetchedAt = new Date().toISOString(); + const payload: RegistryFile = { fetchedAt, entries }; + writeFileSync(REGISTRY_FILE, JSON.stringify(payload)); + return fetchedAt; +} + +async function loadRegistry(ttlMs: number): Promise { + const now = Date.now(); + if (memoryCache && now - memoryCache.fetchedAt < ttlMs) { + return memoryCache; + } + + const onDisk = readFromDisk(); + if (onDisk && now - onDisk.fetchedAt < ttlMs) { + memoryCache = onDisk; + return onDisk; + } + + if (inflight) return inflight; + + inflight = (async () => { + try { + const entries = await fetchKrxInstruments(); + const fetchedAt = Date.parse(writeToDisk(entries)); + const cache = buildKrxIndex(entries, fetchedAt); + memoryCache = cache; + return cache; + } catch (error) { + if (onDisk) { + logger.warn( + `[krx-instrument-registry] refresh failed, falling back to stale cache โ€” ${error instanceof Error ? error.message : String(error)}`, + ); + memoryCache = onDisk; + return onDisk; + } + throw error; + } finally { + inflight = null; + } + })(); + + return inflight; +} + +export async function resolveIsin( + ticker: string, + options?: { ttlMs?: number }, +): Promise<{ isin: string; name: string; market: string } | null> { + const normalized = ticker.trim(); + if (!/^\d{6}$/.test(normalized)) return null; + const cache = await loadRegistry(options?.ttlMs ?? DEFAULT_TTL_MS); + const entry = cache.byTicker.get(normalized); + if (!entry) return null; + return { isin: entry.isin, name: entry.name, market: entry.market }; +} + +export function _resetKrxInstrumentRegistryForTests(): void { + memoryCache = null; + inflight = null; +} diff --git a/src/data/nps-registry.ts b/src/data/nps-registry.ts new file mode 100644 index 000000000..f289548a4 --- /dev/null +++ b/src/data/nps-registry.ts @@ -0,0 +1,97 @@ +/** + * Cache for the NPS domestic-equity holdings snapshot, mirroring + * ticker-registry.ts. The dataset updates ~yearly, so a 30-day TTL is ample; + * a failed refresh falls back to the last good snapshot. + */ +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { dirname } from 'node:path'; +import { dexterPath } from '../utils/paths.js'; +import { logger } from '../utils/logger.js'; +import { fetchNpsHoldings, type NpsHoldingEntry } from './fetchers/nps-holdings.js'; + +const REGISTRY_FILE = dexterPath('cache', 'nps', 'holdings.json'); +const DEFAULT_TTL_MS = 30 * 24 * 60 * 60 * 1000; + +interface RegistryFile { + fetchedAt: string; + entries: NpsHoldingEntry[]; +} + +interface RegistryCache { + fetchedAt: number; + entries: NpsHoldingEntry[]; +} + +let memoryCache: RegistryCache | null = null; +let inflight: Promise | null = null; + +function readFromDisk(): RegistryCache | null { + if (!existsSync(REGISTRY_FILE)) return null; + try { + const raw = readFileSync(REGISTRY_FILE, 'utf-8'); + const parsed = JSON.parse(raw) as RegistryFile; + if (!parsed?.entries || !Array.isArray(parsed.entries)) return null; + return { fetchedAt: Date.parse(parsed.fetchedAt), entries: parsed.entries }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.warn(`[nps-registry] failed to read cache โ€” ${message}`); + return null; + } +} + +function writeToDisk(entries: NpsHoldingEntry[]): string { + const dir = dirname(REGISTRY_FILE); + if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); + const fetchedAt = new Date().toISOString(); + const payload: RegistryFile = { fetchedAt, entries }; + writeFileSync(REGISTRY_FILE, JSON.stringify(payload)); + return fetchedAt; +} + +async function loadRegistry(ttlMs: number): Promise { + const now = Date.now(); + if (memoryCache && now - memoryCache.fetchedAt < ttlMs) { + return memoryCache; + } + + const onDisk = readFromDisk(); + if (onDisk && now - onDisk.fetchedAt < ttlMs) { + memoryCache = onDisk; + return onDisk; + } + + if (inflight) return inflight; + + inflight = (async () => { + try { + const entries = await fetchNpsHoldings(); + const fetchedAt = Date.parse(writeToDisk(entries)); + const cache = { fetchedAt, entries }; + memoryCache = cache; + return cache; + } catch (error) { + if (onDisk) { + logger.warn( + `[nps-registry] refresh failed, falling back to stale cache โ€” ${error instanceof Error ? error.message : String(error)}`, + ); + memoryCache = onDisk; + return onDisk; + } + throw error; + } finally { + inflight = null; + } + })(); + + return inflight; +} + +export async function getNpsHoldings(options?: { ttlMs?: number }): Promise { + const cache = await loadRegistry(options?.ttlMs ?? DEFAULT_TTL_MS); + return cache.entries; +} + +export function _resetNpsRegistryForTests(): void { + memoryCache = null; + inflight = null; +} diff --git a/src/tools/finance-kr/get-foreign-ownership-kr.test.ts b/src/tools/finance-kr/get-foreign-ownership-kr.test.ts new file mode 100644 index 000000000..abd9d09b3 --- /dev/null +++ b/src/tools/finance-kr/get-foreign-ownership-kr.test.ts @@ -0,0 +1,25 @@ +import { describe, it, expect } from 'bun:test'; +import { mapForeignRow } from './get-foreign-ownership-kr.js'; + +describe('mapForeignRow', () => { + it('maps and numeric-parses a Naver trend row', () => { + const row = { + bizdate: '20260529', + foreignerHoldRatio: '48.27%', + foreignerPureBuyQuant: '-1,061,741', + organPureBuyQuant: '+5,314,304', + individualPureBuyQuant: '-4,237,361', + closePrice: '317,000', + accumulatedTradingVolume: '32,804,208', + }; + expect(mapForeignRow(row)).toEqual({ + date: '2026-05-29', + foreignHoldRatio: 48.27, + foreignNetBuyQty: -1061741, + orgNetBuyQty: 5314304, + individualNetBuyQty: -4237361, + closePrice: 317000, + tradingVolume: 32804208, + }); + }); +}); diff --git a/src/tools/finance-kr/get-foreign-ownership-kr.ts b/src/tools/finance-kr/get-foreign-ownership-kr.ts new file mode 100644 index 000000000..6c4284176 --- /dev/null +++ b/src/tools/finance-kr/get-foreign-ownership-kr.ts @@ -0,0 +1,68 @@ +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { z } from 'zod'; +import { fetchNaverTrend } from './naver-api.js'; +import { parseKrxNumber, toIsoDate } from './utils.js'; +import { formatToolResult } from '../types.js'; +import { TTL_6H } from '../finance/utils.js'; + +export const GET_FOREIGN_OWNERSHIP_KR_DESCRIPTION = `Retrieves ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ (foreign ownership ratio) for a Korean (KOSPI/KOSDAQ) listed company. Foreign holding is a closely watched flow signal in Korea; there is no direct US equivalent. + +Each daily row includes the foreign holding ratio as a % of shares (foreignHoldRatio), and the day's net-buy quantities for foreigners, institutions, and individuals (foreignNetBuyQty, orgNetBuyQty, individualNetBuyQty), plus close price and volume. Use this for "foreign ownership of X" or to track foreign accumulation/distribution. Accepts a 6-digit ticker (e.g. 005930 for Samsung Electronics). Returns the most recent day first (about a month of history).`; + +const InputSchema = z.object({ + ticker: z + .string() + .regex(/^\d{6}$/, 'Korean ticker must be a 6-digit string (e.g. 005930 for Samsung).') + .describe('6-digit Korean stock ticker (e.g. 005930 for Samsung Electronics).'), + limit: z + .number() + .int() + .min(1) + .max(100) + .default(20) + .describe('Maximum number of days to return (default 20). Returns the most recent first.'), +}); + +export interface ForeignOwnershipRow { + date: string; + foreignHoldRatio: number | null; + foreignNetBuyQty: number | null; + orgNetBuyQty: number | null; + individualNetBuyQty: number | null; + closePrice: number | null; + tradingVolume: number | null; +} + +/** Map a raw Naver mobile-trend row to a friendly, numeric-parsed shape. */ +export function mapForeignRow(raw: Record): ForeignOwnershipRow { + return { + date: toIsoDate(raw.bizdate), + foreignHoldRatio: parseKrxNumber(raw.foreignerHoldRatio), + foreignNetBuyQty: parseKrxNumber(raw.foreignerPureBuyQuant), + orgNetBuyQty: parseKrxNumber(raw.organPureBuyQuant), + individualNetBuyQty: parseKrxNumber(raw.individualPureBuyQuant), + closePrice: parseKrxNumber(raw.closePrice), + tradingVolume: parseKrxNumber(raw.accumulatedTradingVolume), + }; +} + +export const getForeignOwnershipKr = new DynamicStructuredTool({ + name: 'get_foreign_ownership_kr', + description: GET_FOREIGN_OWNERSHIP_KR_DESCRIPTION, + schema: InputSchema, + func: async (input) => { + const ticker = input.ticker.trim(); + const base = { ticker, ownership: [] as ForeignOwnershipRow[] }; + try { + const { rows, url } = await fetchNaverTrend(ticker, { cacheable: true, ttlMs: TTL_6H }); + const ownership = rows + .map(mapForeignRow) + .sort((a, b) => b.date.localeCompare(a.date)) + .slice(0, input.limit); + return formatToolResult({ ...base, ownership }, [url]); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return formatToolResult({ ...base, _error: message }, []); + } + }, +}); diff --git a/src/tools/finance-kr/get-nps-holdings.test.ts b/src/tools/finance-kr/get-nps-holdings.test.ts new file mode 100644 index 000000000..dc850634f --- /dev/null +++ b/src/tools/finance-kr/get-nps-holdings.test.ts @@ -0,0 +1,25 @@ +import { describe, it, expect } from 'bun:test'; +import { matchByName } from './get-nps-holdings.js'; +import type { NpsHoldingEntry } from '../../data/fetchers/nps-holdings.js'; + +const ENTRIES: NpsHoldingEntry[] = [ + { name: '์‚ผ์„ฑ์ „์ž', evalAmount: 350000, weightPct: 25, shareRatioPct: 7.5 }, + { name: '์‚ผ์„ฑ์ „์ž์šฐ', evalAmount: 20000, weightPct: 1.5, shareRatioPct: 5.1 }, + { name: 'NAVER', evalAmount: 30000, weightPct: 2, shareRatioPct: 8.2 }, +]; + +describe('matchByName', () => { + it('matches whitespace-insensitively and either-contains', () => { + const matches = matchByName(ENTRIES, '์‚ผ์„ฑ์ „์ž'); + // Both ์‚ผ์„ฑ์ „์ž and ์‚ผ์„ฑ์ „์ž์šฐ contain the target. + expect(matches.map((m) => m.name)).toEqual(['์‚ผ์„ฑ์ „์ž', '์‚ผ์„ฑ์ „์ž์šฐ']); + }); + + it('matches when the entry name is contained in the query', () => { + expect(matchByName(ENTRIES, 'NAVER Corp').map((m) => m.name)).toEqual(['NAVER']); + }); + + it('returns [] for an empty target', () => { + expect(matchByName(ENTRIES, ' ')).toEqual([]); + }); +}); diff --git a/src/tools/finance-kr/get-nps-holdings.ts b/src/tools/finance-kr/get-nps-holdings.ts new file mode 100644 index 000000000..0c1db27a6 --- /dev/null +++ b/src/tools/finance-kr/get-nps-holdings.ts @@ -0,0 +1,87 @@ +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { z } from 'zod'; +import { getNpsHoldings } from '../../data/nps-registry.js'; +import { resolveTicker } from '../../data/ticker-registry.js'; +import { formatToolResult } from '../types.js'; +import type { NpsHoldingEntry } from '../../data/fetchers/nps-holdings.js'; + +export const GET_NPS_HOLDINGS_DESCRIPTION = `Retrieves ๊ตญ๋ฏผ์—ฐ๊ธˆ๊ณต๋‹จ (National Pension Service, NPS) domestic-equity holdings โ€” Korea's largest institutional investor. There is no direct US equivalent; this is the canonical "what does the national pension fund own" dataset. + +Data is the NPS year-end disclosure (most recent published): per stock, the evaluation amount in ์–ต์› (evalAmount), the weight within the domestic-equity book in % (weightPct), and the stake as a % of the company's shares (shareRatioPct). Provide a Korean stock name (e.g. "์‚ผ์„ฑ์ „์ž") or a 6-digit ticker to look up a specific holding, or omit both to get the largest holdings by value. Note: this is a periodic year-end snapshot, not real-time.`; + +const InputSchema = z.object({ + name: z + .string() + .optional() + .describe('Korean stock name to look up (e.g. ์‚ผ์„ฑ์ „์ž). Preferred when known.'), + ticker: z + .string() + .regex(/^\d{6}$/, 'Korean ticker must be a 6-digit string (e.g. 005930 for Samsung).') + .optional() + .describe('6-digit Korean ticker (e.g. 005930). Resolved to a name when possible.'), + limit: z + .number() + .int() + .min(1) + .max(100) + .default(20) + .describe('Max holdings to return (default 20). For the no-query case, the largest by value.'), +}); + +function normalizeName(value: string): string { + return value.replace(/\s+/g, '').toLowerCase(); +} + +/** Match NPS rows to a target name (whitespace-insensitive, either-contains). */ +export function matchByName(entries: NpsHoldingEntry[], target: string): NpsHoldingEntry[] { + const t = normalizeName(target); + if (!t) return []; + return entries.filter((e) => { + const n = normalizeName(e.name); + return n.includes(t) || t.includes(n); + }); +} + +function byEvalDesc(a: NpsHoldingEntry, b: NpsHoldingEntry): number { + return (b.evalAmount ?? -Infinity) - (a.evalAmount ?? -Infinity); +} + +/** Best-effort ticker โ†’ Korean name via the DART registry; null if unavailable. */ +async function tryResolveName(ticker: string): Promise { + try { + const resolved = await resolveTicker(ticker); + return resolved?.corp_name ?? null; + } catch { + return null; + } +} + +export const getNpsHoldings_tool = new DynamicStructuredTool({ + name: 'get_nps_holdings', + description: GET_NPS_HOLDINGS_DESCRIPTION, + schema: InputSchema, + func: async (input) => { + let entries: NpsHoldingEntry[]; + try { + entries = await getNpsHoldings(); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return formatToolResult({ holdings: [], _error: message }, []); + } + + const target = input.name ?? (input.ticker ? await tryResolveName(input.ticker) : null); + + let holdings: NpsHoldingEntry[]; + if (target) { + holdings = matchByName(entries, target).sort(byEvalDesc).slice(0, input.limit); + } else { + holdings = [...entries].sort(byEvalDesc).slice(0, input.limit); + } + + return formatToolResult({ + source: 'NPS ๊ตญ๋‚ด์ฃผ์‹ ํˆฌ์ž์ •๋ณด (data.go.kr, year-end snapshot)', + query: { name: input.name ?? null, ticker: input.ticker ?? null, resolvedName: target }, + holdings, + }); + }, +}); diff --git a/src/tools/finance-kr/get-short-balance-kr.test.ts b/src/tools/finance-kr/get-short-balance-kr.test.ts new file mode 100644 index 000000000..fbbd667a6 --- /dev/null +++ b/src/tools/finance-kr/get-short-balance-kr.test.ts @@ -0,0 +1,50 @@ +import { describe, it, expect } from 'bun:test'; +import { mapShortBalanceRow, defaultRange } from './get-short-balance-kr.js'; + +describe('mapShortBalanceRow', () => { + it('maps and numeric-parses a MDCSTAT30502 row (date โ†’ ISO)', () => { + const row = { + RPT_DUTY_OCCR_DD: '2020/01/10', + BAL_QTY: '5,489,240', + LIST_SHRS: '5,969,782,550', + BAL_AMT: '326,609,780,000', + MKTCAP: '355,202,061,725,000', + BAL_RTO: '0.09', + }; + expect(mapShortBalanceRow(row)).toEqual({ + date: '2020-01-10', + balanceQty: 5489240, + listedShares: 5969782550, + balanceAmount: 326609780000, + marketCap: 355202061725000, + balanceRatio: 0.09, + }); + }); + + it('tolerates missing fields', () => { + expect(mapShortBalanceRow({ RPT_DUTY_OCCR_DD: '2020/01/10' })).toEqual({ + date: '2020-01-10', + balanceQty: null, + listedShares: null, + balanceAmount: null, + marketCap: null, + balanceRatio: null, + }); + }); +}); + +describe('defaultRange', () => { + it('passes through an explicit start and end', () => { + expect(defaultRange('2026-01-01', '2026-02-01')).toEqual({ + startDate: '2026-01-01', + endDate: '2026-02-01', + }); + }); + + it('computes a ~30-day window before the given end', () => { + expect(defaultRange(undefined, '2026-02-01')).toEqual({ + startDate: '2026-01-02', + endDate: '2026-02-01', + }); + }); +}); diff --git a/src/tools/finance-kr/get-short-balance-kr.ts b/src/tools/finance-kr/get-short-balance-kr.ts new file mode 100644 index 000000000..104b0158d --- /dev/null +++ b/src/tools/finance-kr/get-short-balance-kr.ts @@ -0,0 +1,97 @@ +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { z } from 'zod'; +import { krxApi } from './krx-api.js'; +import { toDartDate, toIsoDate, parseKrxNumber } from './utils.js'; +import { resolveIsin } from '../../data/krx-instrument-registry.js'; +import { formatToolResult } from '../types.js'; +import { TTL_6H } from '../finance/utils.js'; + +export const GET_SHORT_BALANCE_KR_DESCRIPTION = `Retrieves ๊ณต๋งค๋„ ์ˆœ๋ณด์œ ์ž”๊ณ  (short-selling net balance) for a Korean (KOSPI/KOSDAQ) listed company from the Korea Exchange (KRX). This is the Korean equivalent of US short interest. + +Each daily row includes the short balance quantity (balanceQty), the listed shares (listedShares), the short balance amount in KRW (balanceAmount), market cap (marketCap), and the balance ratio as a % of listed shares (balanceRatio). Use this to gauge short pressure or crowding on a name. Accepts a 6-digit ticker (e.g. 005930 for Samsung Electronics) and an optional date range (defaults to the last ~30 days). Returns the most recent day first. + +Note: investors below the 0.01% reporting threshold are not aggregated here, so this reflects reported balances. Requires KRX_ID/KRX_PW credentials.`; + +const InputSchema = z.object({ + ticker: z + .string() + .regex(/^\d{6}$/, 'Korean ticker must be a 6-digit string (e.g. 005930 for Samsung).') + .describe('6-digit Korean stock ticker (e.g. 005930 for Samsung Electronics).'), + startDate: z + .string() + .regex(/^\d{4}-\d{2}-\d{2}$/, 'Use ISO YYYY-MM-DD.') + .optional() + .describe('ISO start date (YYYY-MM-DD). Defaults to ~30 days before the end date.'), + endDate: z + .string() + .regex(/^\d{4}-\d{2}-\d{2}$/, 'Use ISO YYYY-MM-DD.') + .optional() + .describe('ISO end date (YYYY-MM-DD). Defaults to today.'), + limit: z + .number() + .int() + .min(1) + .max(100) + .default(20) + .describe('Maximum number of days to return (default 20). Returns the most recent first.'), +}); + +export interface ShortBalanceRow { + date: string; + balanceQty: number | null; + listedShares: number | null; + balanceAmount: number | null; + marketCap: number | null; + balanceRatio: number | null; +} + +/** Map a raw MDCSTAT30502 row to a friendly, numeric-parsed shape. */ +export function mapShortBalanceRow(raw: Record): ShortBalanceRow { + return { + date: toIsoDate(raw.RPT_DUTY_OCCR_DD), + balanceQty: parseKrxNumber(raw.BAL_QTY), + listedShares: parseKrxNumber(raw.LIST_SHRS), + balanceAmount: parseKrxNumber(raw.BAL_AMT), + marketCap: parseKrxNumber(raw.MKTCAP), + balanceRatio: parseKrxNumber(raw.BAL_RTO), + }; +} + +/** Default to a ~30-day window ending today (ISO strings). */ +export function defaultRange(start?: string, end?: string): { startDate: string; endDate: string } { + const endDate = end ?? new Date().toISOString().slice(0, 10); + if (start) return { startDate: start, endDate }; + const startMs = Date.parse(endDate) - 30 * 24 * 60 * 60 * 1000; + return { startDate: new Date(startMs).toISOString().slice(0, 10), endDate }; +} + +export const getShortBalanceKr = new DynamicStructuredTool({ + name: 'get_short_balance_kr', + description: GET_SHORT_BALANCE_KR_DESCRIPTION, + schema: InputSchema, + func: async (input) => { + const ticker = input.ticker.trim(); + const { startDate, endDate } = defaultRange(input.startDate, input.endDate); + + try { + const resolved = await resolveIsin(ticker); + if (!resolved) { + return formatToolResult({ error: `Ticker ${ticker} not found in KRX instrument registry` }, []); + } + const { data, url } = await krxApi.get( + 'dbms/MDC/STAT/srt/MDCSTAT30502', + { isuCd: resolved.isin, strtDd: toDartDate(startDate), endDd: toDartDate(endDate) }, + { cacheable: true, ttlMs: TTL_6H }, + ); + const rows = Array.isArray(data.OutBlock_1) ? (data.OutBlock_1 as Record[]) : []; + const short = rows + .map(mapShortBalanceRow) + .sort((a, b) => b.date.localeCompare(a.date)) + .slice(0, input.limit); + return formatToolResult({ ticker, isin: resolved.isin, name: resolved.name, short }, [url]); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return formatToolResult({ ticker, short: [] as ShortBalanceRow[], _error: message }, []); + } + }, +}); diff --git a/src/tools/finance-kr/krx-api.test.ts b/src/tools/finance-kr/krx-api.test.ts new file mode 100644 index 000000000..91f5c896e --- /dev/null +++ b/src/tools/finance-kr/krx-api.test.ts @@ -0,0 +1,17 @@ +import { describe, it, expect } from 'bun:test'; +import { isLogoutBody } from './krx-api.js'; + +describe('isLogoutBody', () => { + it('detects the literal LOGOUT body (with surrounding whitespace)', () => { + expect(isLogoutBody('LOGOUT')).toBe(true); + expect(isLogoutBody(' LOGOUT\n')).toBe(true); + }); + + it('treats an HTML page (block/error) as not-authenticated', () => { + expect(isLogoutBody('...')).toBe(true); + }); + + it('returns false for a JSON payload', () => { + expect(isLogoutBody('{"OutBlock_1":[]}')).toBe(false); + }); +}); diff --git a/src/tools/finance-kr/krx-api.ts b/src/tools/finance-kr/krx-api.ts new file mode 100644 index 000000000..a0cf432d6 --- /dev/null +++ b/src/tools/finance-kr/krx-api.ts @@ -0,0 +1,115 @@ +/** + * Authenticated client for KRX Data Marketplace `getJsonData.cmd`. + * + * Mirrors the shape of `dartApi` (./api.ts) but: POST form-encoded, no API key + * (auth is the logged-in session cookie from ./krx-session.ts), and a missing + * `OutBlock_1` is the legitimate "no data" outcome rather than a status code. + * + * KRX answers an unauthenticated/expired request with the literal body + * "LOGOUT" (HTTP 400); we detect that, refresh the session once, and retry. + */ +import { readCache, writeCache, describeRequest } from '../../utils/cache.js'; +import { logger } from '../../utils/logger.js'; +import { getKrxSession, cookieHeader, USER_AGENT, type KrxSession } from './krx-session.js'; + +const DATA_URL = 'https://data.krx.co.kr/comm/bldAttendant/getJsonData.cmd'; +const DATA_REFERER = 'https://data.krx.co.kr/contents/MDC/MDI/outerLoader/index.cmd'; + +export interface KrxApiResponse { + data: Record; + url: string; +} + +export function isLogoutBody(body: string): boolean { + const trimmed = body.trim(); + return trimmed === 'LOGOUT' || trimmed.startsWith('<'); +} + +async function postJsonData( + session: KrxSession, + bld: string, + params: Record, +): Promise { + const body = new URLSearchParams(); + for (const [key, value] of Object.entries(params)) { + if (value !== undefined && value !== null) body.set(key, String(value)); + } + body.set('bld', bld); + + const response = await fetch(DATA_URL, { + method: 'POST', + headers: { + 'User-Agent': USER_AGENT, + Referer: DATA_REFERER, + 'X-Requested-With': 'XMLHttpRequest', + Cookie: cookieHeader(session), + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body, + }); + if (!response.ok && response.status !== 400) { + throw new Error(`[KRX API] request failed: ${response.status} ${response.statusText}`); + } + return response.text(); +} + +export const krxApi = { + /** + * Fetch a KRX statistical block by `bld`. `endpoint` defaults to `bld`, which + * also serves as the cache namespace (cache.ts turns "/" into "_"). + */ + async get( + bld: string, + params: Record, + options?: { cacheable?: boolean; ttlMs?: number }, + ): Promise { + const label = describeRequest(bld, params); + + if (options?.cacheable) { + const cached = readCache(bld, params, options.ttlMs); + if (cached) return cached; + } + + let session = await getKrxSession(); + if (!session) { + throw new Error('[KRX API] KRX_ID/KRX_PW not set or login failed'); + } + + let raw: string; + try { + raw = await postJsonData(session, bld, params); + if (isLogoutBody(raw)) { + // Session likely expired/invalid โ€” re-login once and retry. + session = await getKrxSession({ forceRefresh: true }); + if (!session) { + throw new Error('[KRX API] KRX_ID/KRX_PW not set or login failed'); + } + raw = await postJsonData(session, bld, params); + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`[KRX API] network error: ${label} โ€” ${message}`); + throw new Error(`[KRX API] request failed for ${label}: ${message}`); + } + + if (isLogoutBody(raw)) { + throw new Error( + `[KRX API] not authenticated (LOGOUT) for ${label} โ€” check KRX_ID/KRX_PW, or refresh KRX_COOKIE if using a social-login session`, + ); + } + + let data: Record; + try { + data = JSON.parse(raw) as Record; + } catch { + throw new Error(`[KRX API] request failed: invalid JSON for ${label}`); + } + + // Display/cache URL carries no secret (auth is the session cookie). + const url = `${DATA_URL}?bld=${encodeURIComponent(bld)}`; + if (options?.cacheable) { + writeCache(bld, params, data, url); + } + return { data, url }; + }, +}; diff --git a/src/tools/finance-kr/krx-session.test.ts b/src/tools/finance-kr/krx-session.test.ts new file mode 100644 index 000000000..1ce20b09e --- /dev/null +++ b/src/tools/finance-kr/krx-session.test.ts @@ -0,0 +1,23 @@ +import { describe, it, expect } from 'bun:test'; +import { sessionFromCookie, cookieHeader } from './krx-session.js'; + +describe('sessionFromCookie', () => { + it('parses a Cookie header into a jar and round-trips via cookieHeader', () => { + const session = sessionFromCookie('JSESSIONID=abc123; __smVisitorID=xyz; mdc.client_session=true'); + expect(session.cookies.get('JSESSIONID')).toBe('abc123'); + expect(session.cookies.get('__smVisitorID')).toBe('xyz'); + expect(session.cookies.get('mdc.client_session')).toBe('true'); + expect(cookieHeader(session)).toBe('JSESSIONID=abc123; __smVisitorID=xyz; mdc.client_session=true'); + }); + + it('tolerates a single cookie and stray whitespace', () => { + const session = sessionFromCookie(' JSESSIONID=only '); + expect(session.cookies.get('JSESSIONID')).toBe('only'); + }); + + it('skips malformed segments without a value', () => { + const session = sessionFromCookie('JSESSIONID=ok; garbage; =novalue'); + expect(session.cookies.get('JSESSIONID')).toBe('ok'); + expect(session.cookies.size).toBe(1); + }); +}); diff --git a/src/tools/finance-kr/krx-session.ts b/src/tools/finance-kr/krx-session.ts new file mode 100644 index 000000000..0c6bdb455 --- /dev/null +++ b/src/tools/finance-kr/krx-session.ts @@ -0,0 +1,204 @@ +/** + * KRX Data Marketplace authenticated session. + * + * As of a 2024โ€“2025 anti-crawling change, `data.krx.co.kr` rejects anonymous + * statistical queries (responds with the literal body "LOGOUT"). A logged-in + * member session is required. This module replicates the login flow used by + * the `pykrx` library: + * + * 1. GET MDCCOMS001.cmd โ†’ seed initial JSESSIONID + * 2. GET view/login.jsp?site=mdc โ†’ iframe session init + * 3. POST MDCCOMS001D1.cmd โ†’ actual login ({mbrId, pw}) + * โ””โ”€ _error_code: CD001=ok, CD010=password change required, + * CD011=duplicate login โ†’ retry with skipDup=Y + * + * Credentials come from `KRX_ID` / `KRX_PW`. Bun's `fetch` does not manage + * cookies, so we keep a small in-process cookie jar and resend it. Sessions + * expire after ~1h on KRX's side, so we refresh well before that. + */ +import { logger } from '../../utils/logger.js'; + +const LOGIN_PAGE = 'https://data.krx.co.kr/contents/MDC/COMS/client/MDCCOMS001.cmd'; +const LOGIN_JSP = 'https://data.krx.co.kr/contents/MDC/COMS/client/view/login.jsp?site=mdc'; +const LOGIN_URL = 'https://data.krx.co.kr/contents/MDC/COMS/client/MDCCOMS001D1.cmd'; + +const USER_AGENT = + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'; + +/** Refresh ahead of KRX's ~1h server-side expiry. */ +const SESSION_TTL_MS = 55 * 60 * 1000; + +export interface KrxSession { + /** name=value cookie jar (JSESSIONID, etc.) */ + cookies: Map; + loginAt: number; +} + +let memorySession: KrxSession | null = null; +let inflight: Promise | null = null; + +function getCredentials(): { id: string; pw: string } | null { + const id = process.env.KRX_ID ?? ''; + const pw = process.env.KRX_PW ?? ''; + if (!id || !pw || id.startsWith('your-') || pw.startsWith('your-')) return null; + return { id, pw }; +} + +/** + * Manual session-cookie override (KRX_COOKIE) for accounts that log in via a + * social provider (Naver/Kakao) and thus have no native ID/PW to POST. Paste + * the `Cookie` header from a logged-in browser session on data.krx.co.kr. + * Cannot auto-refresh โ€” the user re-pastes when it expires. + */ +function getCookieOverride(): string | null { + const cookie = process.env.KRX_COOKIE ?? ''; + return cookie && !cookie.startsWith('your-') ? cookie : null; +} + +export function sessionFromCookie(cookie: string): KrxSession { + const cookies = new Map(); + for (const segment of cookie.split(';')) { + const part = segment.trim(); + const eq = part.indexOf('='); + if (eq <= 0) continue; + cookies.set(part.slice(0, eq).trim(), part.slice(eq + 1).trim()); + } + return { cookies, loginAt: Date.now() }; +} + +/** Merge a response's Set-Cookie headers into the jar (name=value only). */ +function absorbCookies(jar: Map, response: Response): void { + // Bun/undici expose getSetCookie(); fall back to the single-header form. + const headers = response.headers as Headers & { getSetCookie?: () => string[] }; + const setCookies = headers.getSetCookie?.() ?? []; + const lines = setCookies.length > 0 ? setCookies : [response.headers.get('set-cookie') ?? '']; + for (const line of lines) { + if (!line) continue; + const pair = line.split(';', 1)[0]; + const eq = pair.indexOf('='); + if (eq <= 0) continue; + jar.set(pair.slice(0, eq).trim(), pair.slice(eq + 1).trim()); + } +} + +export function cookieHeader(session: KrxSession): string { + return [...session.cookies.entries()].map(([k, v]) => `${k}=${v}`).join('; '); +} + +async function warmup(jar: Map): Promise { + const init1 = await fetch(LOGIN_PAGE, { headers: { 'User-Agent': USER_AGENT } }); + absorbCookies(jar, init1); + const init2 = await fetch(LOGIN_JSP, { + headers: { + 'User-Agent': USER_AGENT, + Referer: LOGIN_PAGE, + Cookie: [...jar.entries()].map(([k, v]) => `${k}=${v}`).join('; '), + }, + }); + absorbCookies(jar, init2); +} + +async function postLogin( + jar: Map, + id: string, + pw: string, +): Promise { + const payload: Record = { + mbrNm: '', + telNo: '', + di: '', + certType: '', + mbrId: id, + pw, + }; + + const send = async (): Promise> => { + const resp = await fetch(LOGIN_URL, { + method: 'POST', + headers: { + 'User-Agent': USER_AGENT, + Referer: LOGIN_PAGE, + Cookie: [...jar.entries()].map(([k, v]) => `${k}=${v}`).join('; '), + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams(payload), + }); + absorbCookies(jar, resp); + return (await resp.json().catch(() => ({}))) as Record; + }; + + let data = await send(); + let code = typeof data._error_code === 'string' ? data._error_code : ''; + // CD011: duplicate login โ€” retry forcing the other session out. + if (code === 'CD011') { + payload.skipDup = 'Y'; + data = await send(); + code = typeof data._error_code === 'string' ? data._error_code : ''; + } + return code; +} + +async function login(): Promise { + const creds = getCredentials(); + if (!creds) return null; + + const jar = new Map(); + try { + await warmup(jar); + const code = await postLogin(jar, creds.id, creds.pw); + if (code !== 'CD001') { + logger.error(`[KRX session] login failed โ€” _error_code=${code || 'unknown'}`); + return null; + } + return { cookies: jar, loginAt: Date.now() }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`[KRX session] login error โ€” ${message}`); + return null; + } +} + +/** + * Return a valid logged-in session, reusing the in-memory one until it nears + * expiry. Returns null when credentials are unset or login fails (callers + * surface this as a graceful tool error). Concurrent calls share one login. + */ +export async function getKrxSession(options?: { forceRefresh?: boolean }): Promise { + // A manually-supplied cookie takes precedence (social-login accounts). It + // can't be refreshed server-side, so we just (re)wrap it as-is. + const cookieOverride = getCookieOverride(); + if (cookieOverride) { + if (!memorySession || options?.forceRefresh) { + memorySession = sessionFromCookie(cookieOverride); + } + return memorySession; + } + + const now = Date.now(); + if ( + !options?.forceRefresh && + memorySession && + now - memorySession.loginAt < SESSION_TTL_MS + ) { + return memorySession; + } + if (inflight) return inflight; + + inflight = (async () => { + try { + const session = await login(); + memorySession = session; + return session; + } finally { + inflight = null; + } + })(); + return inflight; +} + +export function _resetKrxSessionForTests(): void { + memorySession = null; + inflight = null; +} + +export { USER_AGENT }; diff --git a/src/tools/finance-kr/naver-api.ts b/src/tools/finance-kr/naver-api.ts new file mode 100644 index 000000000..28fc9935c --- /dev/null +++ b/src/tools/finance-kr/naver-api.ts @@ -0,0 +1,63 @@ +/** + * Minimal client for Naver Finance's mobile JSON API. + * + * KRX no longer serves foreign-ownership data without a member login, and + * there is no clean official open API for it. Naver's mobile endpoint exposes + * the daily foreign holding ratio (and investor net-buy quantities) keyed by + * the 6-digit ticker as plain JSON โ€” no key, no HTML scraping. + */ +import { readCache, writeCache } from '../../utils/cache.js'; +import { logger } from '../../utils/logger.js'; + +const BASE_URL = 'https://m.stock.naver.com/api/stock'; +const USER_AGENT = + 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Safari/604.1'; + +export interface NaverTrendResult { + rows: Record[]; + url: string; +} + +/** + * Fetch the investor/foreign daily trend for a 6-digit ticker. The endpoint + * returns a bare JSON array of daily rows (most recent first). + */ +export async function fetchNaverTrend( + ticker: string, + options?: { cacheable?: boolean; ttlMs?: number }, +): Promise { + const endpoint = '/naver/trend'; + const params = { ticker }; + + if (options?.cacheable) { + const cached = readCache(endpoint, params, options.ttlMs); + if (cached) { + const rows = cached.data.rows; + return { rows: Array.isArray(rows) ? (rows as Record[]) : [], url: cached.url }; + } + } + + const url = `${BASE_URL}/${ticker}/trend`; + let response: Response; + try { + response = await fetch(url, { headers: { 'User-Agent': USER_AGENT, Accept: 'application/json' } }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`[Naver API] network error: ${ticker} โ€” ${message}`); + throw new Error(`[Naver API] request failed for ${ticker}: ${message}`); + } + + if (!response.ok) { + throw new Error(`[Naver API] request failed: ${response.status} ${response.statusText}`); + } + + const json = (await response.json().catch(() => { + throw new Error(`[Naver API] request failed: invalid JSON for ${ticker}`); + })) as unknown; + const rows = Array.isArray(json) ? (json as Record[]) : []; + + if (options?.cacheable) { + writeCache(endpoint, params, { rows }, url); + } + return { rows, url }; +} diff --git a/src/tools/finance-kr/utils.test.ts b/src/tools/finance-kr/utils.test.ts index ce8fae7f1..e97c0cee4 100644 --- a/src/tools/finance-kr/utils.test.ts +++ b/src/tools/finance-kr/utils.test.ts @@ -1,5 +1,12 @@ import { describe, it, expect } from 'bun:test'; -import { toDartDate, sortByRceptDtDesc, isNoDataError } from './utils.js'; +import { + toDartDate, + toIsoDate, + sortByRceptDtDesc, + isNoDataError, + parseKrxNumber, + extractOutBlock, +} from './utils.js'; describe('toDartDate', () => { it('strips dashes from an ISO date', () => { @@ -7,6 +14,21 @@ describe('toDartDate', () => { }); }); +describe('toIsoDate', () => { + it('converts KRX YYYY/MM/DD', () => { + expect(toIsoDate('2026/05/27')).toBe('2026-05-27'); + }); + + it('converts Naver YYYYMMDD', () => { + expect(toIsoDate('20260529')).toBe('2026-05-29'); + }); + + it('passes blanks/odd values through', () => { + expect(toIsoDate('')).toBe(''); + expect(toIsoDate(null)).toBe(''); + }); +}); + describe('sortByRceptDtDesc', () => { it('orders by rcept_dt descending and caps to limit', () => { const list = [ @@ -39,3 +61,37 @@ describe('isNoDataError', () => { expect(isNoDataError('[DART API] foo โ€” status=020 (์‚ฌ์šฉํ•œ๋„์ดˆ๊ณผ)')).toBe(false); }); }); + +describe('parseKrxNumber', () => { + it('strips comma grouping', () => { + expect(parseKrxNumber('5,489,240')).toBe(5489240); + }); + + it('strips a leading sign and a trailing percent', () => { + expect(parseKrxNumber('+5,314,304')).toBe(5314304); + expect(parseKrxNumber('48.27%')).toBe(48.27); + }); + + it('treats blanks and the "-" placeholder as null', () => { + expect(parseKrxNumber('')).toBeNull(); + expect(parseKrxNumber('-')).toBeNull(); + expect(parseKrxNumber(null)).toBeNull(); + expect(parseKrxNumber(undefined)).toBeNull(); + }); + + it('passes through plain numbers', () => { + expect(parseKrxNumber(0.09)).toBe(0.09); + }); +}); + +describe('extractOutBlock', () => { + it('returns the array under the default key', () => { + expect(extractOutBlock({ OutBlock_1: [{ a: 1 }] })).toEqual([{ a: 1 }]); + }); + + it('returns [] when the block is missing or not an array', () => { + expect(extractOutBlock({})).toEqual([]); + expect(extractOutBlock(null)).toEqual([]); + expect(extractOutBlock({ OutBlock_1: 'nope' })).toEqual([]); + }); +}); diff --git a/src/tools/finance-kr/utils.ts b/src/tools/finance-kr/utils.ts index 36467a1a4..6660c3120 100644 --- a/src/tools/finance-kr/utils.ts +++ b/src/tools/finance-kr/utils.ts @@ -7,6 +7,17 @@ export function toDartDate(isoDate: string): string { return isoDate.replace(/-/g, ''); } +/** + * Normalize the various Korean date shapes to ISO `YYYY-MM-DD`: + * KRX returns `YYYY/MM/DD`, Naver returns `YYYYMMDD`. Empty/odd values pass + * through unchanged. + */ +export function toIsoDate(value: unknown): string { + const s = String(value ?? '').trim(); + if (/^\d{8}$/.test(s)) return `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)}`; + return s.replace(/\//g, '-'); +} + /** * DART's majorstock/elestock endpoints return the full reporting history with * no server-side date filter. Sort by `rcept_dt` (YYYYMMDD string) descending @@ -29,3 +40,32 @@ export function sortByRceptDtDesc( export function isNoDataError(message: string): boolean { return message.includes('status=013'); } + +/** + * KRX (and Naver) return numbers as comma-grouped strings, sometimes with a + * leading sign or a trailing `%` (e.g. "5,489,240", "+5,314,304", "48.27%"). + * Parse to a plain number; return null for blanks and the "-" placeholder. + */ +export function parseKrxNumber(value: unknown): number | null { + if (value === null || value === undefined) return null; + const s = String(value) + .trim() + .replace(/,/g, '') + .replace(/%$/, '') + .replace(/^\+/, ''); + if (s === '' || s === '-') return null; + const n = Number(s); + return Number.isFinite(n) ? n : null; +} + +/** + * KRX getJsonData responses wrap rows in an `OutBlock_1` array (some endpoints + * use other keys). Missing/empty is a valid "no data" outcome, not an error. + */ +export function extractOutBlock( + data: Record | null | undefined, + key = 'OutBlock_1', +): unknown[] { + const block = data?.[key]; + return Array.isArray(block) ? block : []; +} diff --git a/src/tools/registry.ts b/src/tools/registry.ts index a825c43f5..fc2aadbed 100644 --- a/src/tools/registry.ts +++ b/src/tools/registry.ts @@ -15,6 +15,9 @@ import { createGetFinancialsKr, GET_FINANCIALS_KR_DESCRIPTION } from './finance- import { getFilingsKr, GET_FILINGS_KR_DESCRIPTION } from './finance-kr/get-filings-kr.js'; import { getLargeHoldersKr, GET_LARGE_HOLDERS_KR_DESCRIPTION } from './finance-kr/get-large-holders-kr.js'; import { getInsiderTradesKr, GET_INSIDER_TRADES_KR_DESCRIPTION } from './finance-kr/get-insider-trades-kr.js'; +import { getShortBalanceKr, GET_SHORT_BALANCE_KR_DESCRIPTION } from './finance-kr/get-short-balance-kr.js'; +import { getForeignOwnershipKr, GET_FOREIGN_OWNERSHIP_KR_DESCRIPTION } from './finance-kr/get-foreign-ownership-kr.js'; +import { getNpsHoldings_tool, GET_NPS_HOLDINGS_DESCRIPTION } from './finance-kr/get-nps-holdings.js'; import { GET_MARKET_DATA_DESCRIPTION } from './finance/get-market-data.js'; import { READ_FILINGS_DESCRIPTION } from './finance/read-filings.js'; import { SCREEN_STOCKS_DESCRIPTION } from './finance/screen-stocks.js'; @@ -213,6 +216,48 @@ export function getToolRegistry(model: string): RegisteredTool[] { }); } + // ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ comes from Naver's keyless JSON API โ€” always available. + tools.push({ + name: 'get_foreign_ownership_kr', + tool: getForeignOwnershipKr, + description: GET_FOREIGN_OWNERSHIP_KR_DESCRIPTION, + compactDescription: 'Korean ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ (foreign ownership ratio), daily, for 6-digit tickers.', + concurrencySafe: true, + }); + + // ๊ณต๋งค๋„ ์ž”๊ณ  requires a KRX Data Marketplace login โ€” either a native ID/PW + // or a pasted session cookie (KRX_COOKIE) for social-login accounts. + const hasKrxIdPw = + !!process.env.KRX_ID && + !!process.env.KRX_PW && + !process.env.KRX_ID.startsWith('your-') && + !process.env.KRX_PW.startsWith('your-'); + const hasKrxCookie = + !!process.env.KRX_COOKIE && !process.env.KRX_COOKIE.startsWith('your-'); + if (hasKrxIdPw || hasKrxCookie) { + tools.push({ + name: 'get_short_balance_kr', + tool: getShortBalanceKr, + description: GET_SHORT_BALANCE_KR_DESCRIPTION, + compactDescription: 'Korean ๊ณต๋งค๋„ ์ž”๊ณ  (short-selling balance / short interest), daily, for 6-digit tickers.', + concurrencySafe: true, + }); + } + + // ๊ตญ๋ฏผ์—ฐ๊ธˆ holdings come from data.go.kr (odcloud) with a service key. + if ( + process.env.DATA_GO_KR_SERVICE_KEY && + !process.env.DATA_GO_KR_SERVICE_KEY.startsWith('your-') + ) { + tools.push({ + name: 'get_nps_holdings', + tool: getNpsHoldings_tool, + description: GET_NPS_HOLDINGS_DESCRIPTION, + compactDescription: 'Korean National Pension Service (๊ตญ๋ฏผ์—ฐ๊ธˆ) equity holdings by stock name or 6-digit ticker.', + concurrencySafe: true, + }); + } + if (process.env.X_BEARER_TOKEN) { tools.push({ name: 'x_search', From 72e202383af01464f7d387c96c33da6abc724297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SH=20KWON=20=F0=9F=87=B0=F0=9F=87=B7?= Date: Sat, 30 May 2026 06:11:38 +0900 Subject: [PATCH 06/70] =?UTF-8?q?Adapt=20skills=20for=20Korean=20market=20?= =?UTF-8?q?=E2=80=94=20DCF=20market-branch=20+=20=EB=AC=BC=EC=A0=81?= =?UTF-8?q?=EB=B6=84=ED=95=A0=20skill=20(Phase=204)=20(#6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adapt skills for Korean market โ€” DCF market-branch + ๋ฌผ์ ๋ถ„ํ•  skill (Phase 4) Phase 4 (skill adaptation) of Korean stock support. Pure markdown โ€” skills auto-discovered by src/skills/registry.ts, no registry/prompt/cli.ts change. - dcf-valuation skill now branches on market (Step 0): 6-digit ticker โ†’ KR path using get_financials_kr, ~3% ๊ตญ๊ณ ์ฑ„ risk-free, ~22% K-IFRS corporate tax, ~2% terminal growth, KRW. New sector-wacc-kr.md. ๊ฑฐ๋ž˜์„ธ/๋ฐฐ๋‹น์„ธ surface as an investor-level "์„ธํ›„ ์‹คํ˜„์ˆ˜์ต๋ฅ " caveat, not in the intrinsic-value math. Kept a single skill (branches internally) so write-memo's dcf-valuation invocation keeps working. - New skill kr-spinoff-analysis: ๋ฌผ์ ๋ถ„ํ• /์ธ์ ๋ถ„ํ•  events from the parent shareholder's POV (dilution / holding-co discount / double-counting) via get_filings_kr material disclosures. - registry.test.ts guard asserting all four builtin skills are discovered. bun test: 99 pass; typecheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) * Fix KR DCF skill: correct 2026 ๊ฑฐ๋ž˜์„ธ rate + Step 6 sensitivity override Review follow-ups on PR #6: - ์ฆ๊ถŒ๊ฑฐ๋ž˜์„ธ was stated as ~0.18% "as of 2026" (the 2024 transitional rate). Because ๊ธˆ์œตํˆฌ์ž์†Œ๋“์„ธ was abolished, the rate reverted up for 2026: KOSPI ~0.20% all-in (0.05% ๊ฑฐ๋ž˜์„ธ + 0.15% ๋†ํŠน์„ธ), KOSDAQ 0.20%. - Add the KR terminal-growth override (1.5/2.0/2.5) to Step 6 itself, where the sensitivity matrix is built โ€” previously only stated in Step 4 and Step 8. Co-Authored-By: Claude Opus 4.8 (1M context) * Translate KR skill content to Korean KR-facing skill prose โ†’ Korean for Korean users. Identifiers preserved: frontmatter name (dcf-valuation, kr-spinoff-analysis), tool names (get_financials_kr โ€ฆ), and the sector-wacc-kr.md link stay as-is. - kr-spinoff/SKILL.md: full Korean (100% KR-only skill) - dcf/sector-wacc-kr.md: full Korean - dcf/SKILL.md: ๐Ÿ‡ฐ๐Ÿ‡ท KR override block *content* โ†’ Korean; US path and shared scaffolding stay English (the skill serves US stocks too) bun test (registry) 4 pass; typecheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) * Translate DCF skill fully to Korean (US path + shared scaffolding) The agent's whole flow targets Korean users, so translate the DCF skill end-to-end โ€” Step 0, US path, shared mechanics (Steps 2/5/7), output format โ€” not just the KR override blocks. Also translate the US sector-wacc.md companion. Machine-facing literals kept in English by design: frontmatter name, tool names (get_financials โ€ฆ), JSON field names (free_cash_flow โ€ฆ), file links, and the example query strings passed to the US get_financials tool (English-oriented router). Description keeps English trigger terms alongside Korean so English DCF queries still match. bun test (registry) 4 pass; typecheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) * Translate README fully to Korean Whole README โ†’ Korean for Korean users. Headings translated and the table-of-contents anchors updated to match. Code blocks, commands, URLs, env var names, badges, image tags, and the mermaid diagrams (already Korean) left intact. Co-Authored-By: Claude Opus 4.8 (1M context) * README: replace top architecture PNG with mermaid + add usage examples - Convert the static architecture screenshot at the top of the README to a maintainable mermaid flowchart (User โ†’ Agent Loop {LLM/Tools/ Scratchpad} โ†’ APIs, plus Evaluation Layer). - Add a "๐Ÿ’ก ์‚ฌ์šฉ ์˜ˆ์‹œ" section with concrete, agent-optimized example queries grouped by capability (financials, DCF, DART filings/ownership, Korea-specific data, spin-off analysis, cross-market, memo/sentiment), with key-requirement notes. TOC updated. Co-Authored-By: Claude Opus 4.8 (1M context) * Restructure README for a cleaner reading flow The structure had grown messy: the legal disclaimer sat above the overview, setup was split across three sections (Prerequisites / Install / Run) with Bun install duplicated, env keys were scattered (and missing the Korean ones), and the cross-market example was duplicated. - Reorder: title โ†’ architecture โ†’ badges โ†’ Overview โ†’ ๐Ÿ‡ฐ๐Ÿ‡ท โ†’ Usage โ†’ Quick Start โ†’ Evaluate/Debug/WhatsApp โ†’ Contribute โ†’ License โ†’ Disclaimer (moved to the bottom). - Merge Prerequisites + Install + Run into one "๐Ÿš€ ๋น ๋ฅธ ์‹œ์ž‘" with a single consolidated .env block that now includes the Korean keys (DART / KRX / data.go.kr), web search, and X. - Fix env var bug: OPEN_DART_KEY โ†’ DART_API_KEY (matches the code). - De-duplicate the cross-market example (folded into Usage Examples). - TOC updated to the new order. Co-Authored-By: Claude Opus 4.8 (1M context) * Update README.md * README: move architecture diagram into Overview, tidy TOC On top of the manual edits (tagline split, WhatsApp/Contribute sections removed): - Move the architecture mermaid from the very top into ๊ฐœ์š” as an "### ์•„ํ‚คํ…์ฒ˜" subsection โ€” the diagram no longer precedes what the project even is. - Clean the TOC: drop the dead links to the removed WhatsApp/Contribute sections, and group ํ‰๊ฐ€/๋””๋ฒ„๊น… under one "๐Ÿ”ง ๋” ์•Œ์•„๋ณด๊ธฐ" section. TOC is now 7 entries that all resolve. Co-Authored-By: Claude Opus 4.8 (1M context) * README: use original architecture image instead of mermaid The Korean additions don't change the high-level architecture (they only add tools under Tools/Subagents and data sources under APIs/Other), so the original diagram is still accurate. Swap the redrawn mermaid back to the polished source image; the ๐Ÿ‡ฐ๐Ÿ‡ท "๋™์ž‘ ๋ฐฉ์‹" mermaid still covers the Korea-specific routing detail. Co-Authored-By: Claude Opus 4.8 (1M context) * Update README.md * README: remove the Korea-section mermaid diagrams Drop the ๐Ÿ‡ฐ๐Ÿ‡ท "๋™์ž‘ ๋ฐฉ์‹" flowchart and the "์ข…๋ชฉ ์ฝ”๋“œ ํ•ด๊ฒฐ ํ๋ฆ„" sequence diagram; keep the explanatory text (routing, TickerRegistry caching). Co-Authored-By: Claude Opus 4.8 (1M context) * README: add measured Dexter vs claude.ai comparison section Real head-to-head (2026-05) on 5 identical questions: Dexter (GPT-5.5 + finance harness) vs claude.ai (Opus 4.8 + web search). Both frontier models, so the section isolates the harness โ€” primary-source tools (DART/KRX/Naver) + KR skills โ€” as the differentiator. Honest: notes where claude.ai did better (Samsung P&L) and Dexter's stale hardcoded risk-free rate. ChatGPT-free results dropped as not meaningful. TOC updated. Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- CLAUDE.md | 6 +- README.md | 360 +++++++++++++++---------------- src/skills/dcf/SKILL.md | 177 ++++++++------- src/skills/dcf/sector-wacc-kr.md | 51 +++++ src/skills/dcf/sector-wacc.md | 66 +++--- src/skills/kr-spinoff/SKILL.md | 89 ++++++++ src/skills/registry.test.ts | 34 +++ 7 files changed, 490 insertions(+), 293 deletions(-) create mode 100644 src/skills/dcf/sector-wacc-kr.md create mode 100644 src/skills/kr-spinoff/SKILL.md create mode 100644 src/skills/registry.test.ts diff --git a/CLAUDE.md b/CLAUDE.md index 24ac065db..af00696d8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -67,8 +67,10 @@ These bite without warning. Fix once you learn them. - KRX endpoints key on **ISIN** (`isuCd`), not the 6-digit ticker โ†’ the KRX instrument registry exists only for this. ### Phase 4 โ€” Skill adaptation -- [ ] `src/skills/dcf` โ€” branch on market (K-IFRS, ๊ฑฐ๋ž˜์„ธ, ๋ฐฐ๋‹น์„ธ) -- [ ] New skill: ์žฌ๋ฒŒ ๊ทธ๋ฃน ๊ตฌ์กฐ / ๋ฌผ์ ๋ถ„ํ•  ๋ถ„์„ +- [x] `src/skills/dcf` โ€” branches on market. 6-digit ticker โ†’ KR path (`get_financials_kr`, ~22% K-IFRS corporate tax, ~3% ๊ตญ๊ณ ์ฑ„ risk-free, ~2% terminal growth, KRW). New `sector-wacc-kr.md`. ๊ฑฐ๋ž˜์„ธ/๋ฐฐ๋‹น์„ธ surface as an investor-level "์„ธํ›„ ์‹คํ˜„์ˆ˜์ต๋ฅ " caveat in output, not in the intrinsic-value math. +- [x] New skill: `src/skills/kr-spinoff` (`kr-spinoff-analysis`) โ€” ๋ฌผ์ ๋ถ„ํ• /์ธ์ ๋ถ„ํ•  event analysis from parent-shareholder POV (`get_filings_kr` ์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ  search โ†’ dilution / holding-co discount / double-counting). ์žฌ๋ฒŒ ๊ทธ๋ฃน ๋งคํ•‘์€ ๋ณด์กฐ(web_search)๋กœ ์ถ•์†Œ. + +**Phase 4 notes**: skills are pure markdown, auto-discovered by `src/skills/registry.ts` โ€” no registry/prompt/`cli.ts` code change. DCF stays a single `dcf-valuation` skill (branches internally) so `write-memo`'s `dcf-valuation` call keeps working. Guard test: `src/skills/registry.test.ts`. ### Decided - No `aliases.json`. LLM's training data covers common Korean tickers; resolver handles canonical ticker โ†’ corp_code only. diff --git a/README.md b/README.md index a032c41d7..4929d3b5f 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,42 @@ # Dexter ๐Ÿค– -Dexter is an autonomous financial research agent that thinks, plans, and learns as it works. It performs analysis using task planning, self-reflection, and real-time market data. Think Claude Code, but built specifically for financial research. - -Screenshot 2026-04-02 at 4 16 57โ€ฏPM - -## Table of Contents - -- [๐Ÿ‘‹ Overview](#-overview) -- [๐Ÿ‡ฐ๐Ÿ‡ท ํ•œ๊ตญ ์ฃผ์‹ ๋ฆฌ์„œ์น˜](#-ํ•œ๊ตญ-์ฃผ์‹-๋ฆฌ์„œ์น˜) -- [โœ… Prerequisites](#-prerequisites) -- [๐Ÿ’ป How to Install](#-how-to-install) -- [๐Ÿš€ How to Run](#-how-to-run) -- [๐Ÿ“Š How to Evaluate](#-how-to-evaluate) -- [๐Ÿ› How to Debug](#-how-to-debug) -- [๐Ÿ“ฑ How to Use with WhatsApp](#-how-to-use-with-whatsapp) -- [๐Ÿค How to Contribute](#-how-to-contribute) -- [๐Ÿ“„ License](#-license) - -## โš ๏ธ Disclaimer - -This project is for **educational, entertainment, and informational purposes only**. It is not intended for real trading or investment. - -- Not financial, investment, tax, or legal advice -- No guarantees of accuracy, completeness, or fitness for any purpose -- Outputs may be incorrect, incomplete, or out of date -- Creator and contributors assume no liability for any financial losses or damages -- Consult a licensed financial advisor before making investment decisions -- Past performance does not indicate future results - -By using this software, you agree to use it solely for learning and informational purposes and accept all risks associated with its use. - -## ๐Ÿ‘‹ Overview - -Dexter takes complex financial questions and turns them into clear, step-by-step research plans. It runs those tasks using live market data, checks its own work, and refines the results until it has a confident, data-backed answer. - -**Key Capabilities:** -- **Intelligent Task Planning**: Automatically decomposes complex queries into structured research steps -- **Autonomous Execution**: Selects and executes the right tools to gather financial data -- **Self-Validation**: Checks its own work and iterates until tasks are complete -- **Real-Time Financial Data**: Access to income statements, balance sheets, and cash flow statements -- **Safety Features**: Built-in loop detection and step limits to prevent runaway execution +Dexter๋Š” ์ž‘์—…ํ•˜๋ฉด์„œ ์Šค์Šค๋กœ ์‚ฌ๊ณ ํ•˜๊ณ , ๊ณ„ํšํ•˜๊ณ , ํ•™์Šตํ•˜๋Š” ์ž์œจํ˜• ๊ธˆ์œต ๋ฆฌ์„œ์น˜ ์—์ด์ „ํŠธ์ž…๋‹ˆ๋‹ค. +์ž‘์—… ๊ณ„ํš(task planning), ์ž๊ธฐ ์ ๊ฒ€(self-reflection), ์‹ค์‹œ๊ฐ„ ์‹œ์žฅ ๋ฐ์ดํ„ฐ๋ฅผ ํ™œ์šฉํ•ด ๋ถ„์„์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. Claude Code๋ฅผ ๋– ์˜ฌ๋ฆฌ๋˜, ๊ธˆ์œต ๋ฆฌ์„œ์น˜์— ํŠนํ™”๋˜๋„๋ก ๋งŒ๋“ค์–ด์ง„ ๋„๊ตฌ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. [![Twitter Follow](https://img.shields.io/twitter/follow/virattt?style=social)](https://twitter.com/virattt) [![Discord](https://img.shields.io/badge/Discord-Join%20Server-5865F2?style=social&logo=discord)](https://discord.gg/jpGHv2XB6T) -Screenshot 2026-02-18 at 12 21 25โ€ฏPM +Dexter ์•„ํ‚คํ…์ฒ˜ +## ๋ชฉ์ฐจ -## ๐Ÿ‡ฐ๐Ÿ‡ท ํ•œ๊ตญ ์ฃผ์‹ ๋ฆฌ์„œ์น˜ - -Dexter๋Š” ๋ฏธ๊ตญ ์ฃผ์‹๊ณผ ํ•œ๊ตญ ์ฃผ์‹์„ **ํ•˜๋‚˜์˜ ์—์ด์ „ํŠธ์—์„œ ๋™์‹œ์—** ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์‹œ์žฅ์„ ๋ช…์‹œํ•  ํ•„์š” ์—†์ด, LLM์ด ํ‹ฐ์ปค ํ˜•ํƒœ์™€ ํšŒ์‚ฌ๋ช… ์–ธ์–ด๋ฅผ ๋ณด๊ณ  ์•Œ๋งž์€ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ๊ณจ๋ผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. +- [๐Ÿ‘‹ ๊ฐœ์š”](#-๊ฐœ์š”) +- [๐Ÿ‡ฐ๐Ÿ‡ท ํ•œ๊ตญ ์ฃผ์‹ ๋ฆฌ์„œ์น˜](#-ํ•œ๊ตญ-์ฃผ์‹-๋ฆฌ์„œ์น˜) +- [๐Ÿ’ก ์‚ฌ์šฉ ์˜ˆ์‹œ](#-์‚ฌ์šฉ-์˜ˆ์‹œ) +- [๐Ÿ†š ์›น ์ฑ—๋ด‡๊ณผ ๋น„๊ต](#-์›น-์ฑ—๋ด‡๊ณผ-๋น„๊ต) +- [๐Ÿš€ ๋น ๋ฅธ ์‹œ์ž‘](#-๋น ๋ฅธ-์‹œ์ž‘) +- [๐Ÿ”ง ๋” ์•Œ์•„๋ณด๊ธฐ](#-๋”-์•Œ์•„๋ณด๊ธฐ) +- [๐Ÿ“„ ๋ผ์ด์„ ์Šค](#-๋ผ์ด์„ ์Šค) +- [โš ๏ธ ๋ฉด์ฑ… ์กฐํ•ญ](#๏ธ-๋ฉด์ฑ…-์กฐํ•ญ) -### ๋™์ž‘ ๋ฐฉ์‹ +## ๐Ÿ‘‹ ๊ฐœ์š” -```mermaid -flowchart TB - Q[์‚ฌ์šฉ์ž ์ฟผ๋ฆฌ] --> LLM{LLM ๋ผ์šฐํŒ…} +Dexter๋Š” ๋ณต์žกํ•œ ๊ธˆ์œต ์งˆ๋ฌธ์„ ๋ฐ›์•„ ๋ช…ํ™•ํ•œ ๋‹จ๊ณ„๋ณ„ ๋ฆฌ์„œ์น˜ ๊ณ„ํš์œผ๋กœ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค. ๊ทธ ์ž‘์—…์„ ์‹ค์‹œ๊ฐ„ ์‹œ์žฅ ๋ฐ์ดํ„ฐ๋กœ ์‹คํ–‰ํ•˜๊ณ , ์ž์‹ ์˜ ์ž‘์—…์„ ์ ๊ฒ€ํ•˜๋ฉฐ, ๋ฐ์ดํ„ฐ๋กœ ๋’ท๋ฐ›์นจ๋˜๋Š” ํ™•์‹  ์žˆ๋Š” ๋‹ต์— ์ด๋ฅผ ๋•Œ๊นŒ์ง€ ๊ฒฐ๊ณผ๋ฅผ ๋‹ค๋“ฌ์Šต๋‹ˆ๋‹ค. - LLM -->|6์ž๋ฆฌ ์ˆซ์ž ํ‹ฐ์ปค
ํ•œ๊ตญ์–ด ํšŒ์‚ฌ๋ช…| KR[KR ํˆด] - LLM -->|์•ŒํŒŒ๋ฒณ ํ‹ฐ์ปค
์˜๋ฌธ ํšŒ์‚ฌ๋ช…| US[US ํˆด] - LLM -->|ํฌ๋กœ์Šค๋งˆ์ผ“ ๋น„๊ต| Both[์–‘์ชฝ ๋ชจ๋‘ ํ˜ธ์ถœ] +**ํ•ต์‹ฌ ์—ญ๋Ÿ‰:** +- **์ง€๋Šฅํ˜• ์ž‘์—… ๊ณ„ํš**: ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๋ฅผ ๊ตฌ์กฐํ™”๋œ ๋ฆฌ์„œ์น˜ ๋‹จ๊ณ„๋กœ ์ž๋™ ๋ถ„ํ•ด +- **์ž์œจ ์‹คํ–‰**: ๊ธˆ์œต ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•  ์•Œ๋งž์€ ํˆด์„ ์„ ํƒํ•ด ์‹คํ–‰ +- **์ž๊ธฐ ๊ฒ€์ฆ**: ์ž์‹ ์˜ ์ž‘์—…์„ ์ ๊ฒ€ํ•˜๊ณ  ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋ฐ˜๋ณต +- **์‹ค์‹œ๊ฐ„ ๊ธˆ์œต ๋ฐ์ดํ„ฐ**: ์†์ต๊ณ„์‚ฐ์„œยท์žฌ๋ฌด์ƒํƒœํ‘œยทํ˜„๊ธˆํ๋ฆ„ํ‘œ ์ ‘๊ทผ +- **๋ฏธ๊ตญยทํ•œ๊ตญ ๋™์‹œ ์ง€์›**: ํ•˜๋‚˜์˜ ์—์ด์ „ํŠธ์—์„œ US/KR ์ข…๋ชฉ์„ ํ•จ๊ป˜ ๋‹ค๋ฃจ๊ณ  ๊ต์ฐจ ๋น„๊ต +- **์•ˆ์ „์žฅ์น˜**: ๋ฌดํ•œ ๋ฃจํ”„ ๊ฐ์ง€์™€ ๋‹จ๊ณ„ ์ œํ•œ์„ ๋‚ด์žฅํ•ด ํญ์ฃผ ์‹คํ–‰ ๋ฐฉ์ง€ - KR --> DART[(DART API
๊ณต์‹œยท์žฌ๋ฌด)] - KR --> KRX[(KRX
์‹œ์„ธยท๊ณต๋งค๋„)] +### ์•„ํ‚คํ…์ฒ˜ - US --> FD[(Financial Datasets
์žฌ๋ฌดยท์‹œ์„ธ)] - US --> SEC[(SEC EDGAR
๊ณต์‹œ)] +Dexter TUI - Both --> DART - Both --> FD +## ๐Ÿ‡ฐ๐Ÿ‡ท ํ•œ๊ตญ ์ฃผ์‹ ๋ฆฌ์„œ์น˜ - DART --> Out[์ข…ํ•ฉ ๋‹ต๋ณ€] - KRX --> Out - FD --> Out - SEC --> Out -``` +Dexter๋Š” ๋ฏธ๊ตญ ์ฃผ์‹๊ณผ ํ•œ๊ตญ ์ฃผ์‹์„ **ํ•˜๋‚˜์˜ ์—์ด์ „ํŠธ์—์„œ ๋™์‹œ์—** ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์‹œ์žฅ์„ ๋ช…์‹œํ•  ํ•„์š” ์—†์ด, LLM์ด ํ‹ฐ์ปค ํ˜•ํƒœ์™€ ํšŒ์‚ฌ๋ช… ์–ธ์–ด๋ฅผ ๋ณด๊ณ  ์•Œ๋งž์€ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ๊ณจ๋ผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. (ํ•œ๊ตญ ๋ฐ์ดํ„ฐ ํ™œ์„ฑํ™”๋Š” [๋น ๋ฅธ ์‹œ์ž‘](#-๋น ๋ฅธ-์‹œ์ž‘)์˜ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์ฐธ๊ณ .) ### ์‹ ๊ทœ ํˆด (DART ๊ธฐ๋ฐ˜) @@ -100,151 +63,193 @@ DART์— ์—†๋Š” ํ•œ๊ตญ ํŠนํ™” ๋ฐ์ดํ„ฐ. ํˆด๋งˆ๋‹ค ์†Œ์Šคยทํ‚ค๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. LLM์ด "์‚ผ์„ฑ์ „์ž" ๊ฐ™์€ ์ž์—ฐ์–ด ์ž…๋ ฅ์„ ๋ฐ›์œผ๋ฉด, ํˆด ๋‚ด๋ถ€์—์„œ **ํ‹ฐ์ปค โ†’ corp_code** ๋ณ€ํ™˜์„ ๊ฑฐ์ณ DART API๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. -```mermaid -sequenceDiagram - participant U as ์‚ฌ์šฉ์ž - participant L as LLM - participant T as get_financials_kr - participant R as TickerRegistry
(์บ์‹œ) - participant D as DART API - - U->>L: "์‚ผ์„ฑ์ „์ž ์žฌ๋ฌด ์–ด๋•Œ?" - L->>L: ํ•œ๊ตญ ์ข…๋ชฉ ์ธ์‹
โ†’ 005930์œผ๋กœ ์ •๊ทœํ™” - L->>T: get_financials_kr({ticker: "005930"}) - T->>R: resolve("005930") - R-->>T: corp_code: "00126380" - T->>D: ์‚ฌ์—…๋ณด๊ณ ์„œ ์กฐํšŒ (corp_code) - D-->>T: ์žฌ๋ฌด ๋ฐ์ดํ„ฐ - T-->>L: ์ •ํ˜•ํ™”๋œ ๊ฒฐ๊ณผ - L-->>U: ๋ถ„์„ ๋‹ต๋ณ€ ์ž‘์„ฑ -``` - `TickerRegistry`๋Š” DART์˜ ๋งˆ์Šคํ„ฐ ํŒŒ์ผ(`corpCode.xml`)์„ **์ฒซ ์‹คํ–‰ ์‹œ ํ•œ ๋ฒˆ ๋‹ค์šด๋กœ๋“œ**ํ•ด `.dexter/cache/`์— ์ €์žฅํ•˜๊ณ , 7์ผ๋งˆ๋‹ค ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค. ์‹ ๊ทœ ์ƒ์žฅยท์‚ฌ๋ช… ๋ณ€๊ฒฝยท๋ฌผ์ ๋ถ„ํ• ์ด ์ž๋™ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. -### ์„ค์ • - -`.env`์— ๋‹ค์Œ ํ•ญ๋ชฉ ์ถ”๊ฐ€: - -```bash -# ํ•œ๊ตญ ์ฃผ์‹ (ํ•„์ˆ˜) -OPEN_DART_KEY=your-dart-api-key -``` - -DART API ํ‚ค๋Š” [opendart.fss.or.kr](https://opendart.fss.or.kr/uss/umt/cmm/EgovMberInsertView.do)์—์„œ ๋ฌด๋ฃŒ ๋ฐœ๊ธ‰. ์ผ 10,000๊ฑด ํ˜ธ์ถœ ํ•œ๋„. - ### ํ•œ๊ตญ ์‹œ์žฅ ๊ณ ์œ  ์ฒ˜๋ฆฌ - **K-IFRS ์žฌ๋ฌด์ œํ‘œ** โ€” ์—ฐ๊ฒฐ/๋ณ„๋„ ๋‘˜ ๋‹ค ์กฐํšŒ. ์˜์—…์ด์ต ์ •์˜๊ฐ€ ๋ฏธ๊ตญ GAAP์™€ ๋ฏธ๋ฌ˜ํ•˜๊ฒŒ ๋‹ค๋ฆ„ - **๋‹จ์œ„** โ€” ์›(KRW), ๋ฐฑ๋งŒ์›/์–ต/์กฐ ๋‹จ์œ„๋กœ ์ž๋™ ํฌ๋งทํŒ… - **๊ฑฐ๋ž˜์‹œ๊ฐ„** โ€” 09:00โ€“15:30 KST ๊ธฐ์ค€ - **์ƒํ•˜ํ•œ๊ฐ€** โ€” ยฑ30% ์ผ๊ฐ„ ๋ณ€๋™ ์ œํ•œ ๊ณ ๋ ค +- **DCF ์ž๋™ ๋ถ„๊ธฐ** โ€” 6์ž๋ฆฌ ํ‹ฐ์ปค๋ฉด DCF ์Šคํ‚ฌ์ด ํ•œ๊ตญ ๊ฒฝ๋กœ๋กœ ์ „ํ™˜(ํ•œ๊ตญ ๋ฒ•์ธ์„ธ์œจยท๊ตญ๊ณ ์ฑ„ ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌยทKRW). ๊ฑฐ๋ž˜์„ธยท๋ฐฐ๋‹น์„ธ๋Š” ํˆฌ์ž์ž ์„ธํ›„ ์‹คํ˜„์ˆ˜์ต๋ฅ  ์ฃผ์˜์‚ฌํ•ญ์œผ๋กœ ํ‘œ๊ธฐ - **์žฌ๋ฒŒ ๊ทธ๋ฃน ๊ตฌ์กฐ** โ€” ์‚ผ์„ฑยทํ˜„๋Œ€์ฐจยทSKยทLG ๋“ฑ ๊ทธ๋ฃน ๋‚ด ์ง€๋ถ„ ๊ด€๊ณ„ ๋ถ„์„ ๊ฐ€๋Šฅ -- **๋ฌผ์ ๋ถ„ํ•  ์ด๋ ฅ** โ€” LGํ™”ํ•™โ†’LG์—๋„ˆ์ง€์†”๋ฃจ์…˜ ๊ฐ™์€ ๋ถ„ํ•  ์ด๋ฒคํŠธ ์ถ”์  +- **๋ฌผ์ ๋ถ„ํ•  ๋ถ„์„** โ€” LGํ™”ํ•™โ†’LG์—๋„ˆ์ง€์†”๋ฃจ์…˜ ๊ฐ™์€ ๋ถ„ํ•  ์ด๋ฒคํŠธ๋ฅผ ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ ๊ด€์ (ํฌ์„ยท์ง€์ฃผ์‚ฌ ๋””์Šค์นด์šดํŠธ)์—์„œ ํ‰๊ฐ€ํ•˜๋Š” ์ „์šฉ ์Šคํ‚ฌ + +## ๐Ÿ’ก ์‚ฌ์šฉ ์˜ˆ์‹œ -### ํฌ๋กœ์Šค๋งˆ์ผ“ ์ฟผ๋ฆฌ ์˜ˆ์‹œ +์•„๋ž˜๋Š” Dexter์— ๋ฐ”๋กœ ๋˜์งˆ ์ˆ˜ ์žˆ๋Š” ๊ตฌ์ฒด์ ์ธ ์ฟผ๋ฆฌ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค. ์‹œ์žฅ(๋ฏธ๊ตญ/ํ•œ๊ตญ)์„ ๋”ฐ๋กœ ์ง€์ •ํ•  ํ•„์š” ์—†์ด, ํ‹ฐ์ปค ํ˜•ํƒœ์™€ ์–ธ์–ด๋ฅผ ๋ณด๊ณ  ์—์ด์ „ํŠธ๊ฐ€ ์•Œ๋งž์€ ํˆดยท์Šคํ‚ฌ๋กœ ๋ผ์šฐํŒ…ํ•ฉ๋‹ˆ๋‹ค. + +### ๊ธฐ๋ณธ ์žฌ๋ฌด ๋ถ„์„ ``` -> TSMC์™€ ์‚ผ์„ฑ์ „์ž ํŒŒ์šด๋“œ๋ฆฌ ์‚ฌ์—… ๋น„๊ตํ•ด์ค˜ +> ์‚ผ์„ฑ์ „์ž ์ตœ๊ทผ 5๋…„ ๋งค์ถœ๊ณผ ์˜์—…์ด์ต ์ถ”์„ธ ์ •๋ฆฌํ•ด์ค˜ +> ํ˜„๋Œ€์ฐจ(005380) ์—ฐ๊ฒฐ ๊ธฐ์ค€ ์ž‰์—ฌํ˜„๊ธˆํ๋ฆ„ 5๋…„์น˜ ๋ณด์—ฌ์ค˜ +> AAPL ์ตœ๊ทผ 4๊ฐœ ๋ถ„๊ธฐ ์˜์—…์ด์ต๋ฅ  ์ถ”์ด ์•Œ๋ ค์ค˜ ``` -LLM์ด `get_financials` (TSMC, US) + `get_financials_kr` (์‚ผ์„ฑ์ „์ž, KR)๋ฅผ ๋ชจ๋‘ ํ˜ธ์ถœํ•˜๊ณ , ๋‹จ์œ„ยทํšŒ๊ณ„๊ธฐ์ค€ ์ฐจ์ด๋ฅผ ๋ณด์ •ํ•ด์„œ ๋น„๊ต ํ‘œ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +### ๋ฐธ๋ฅ˜์—์ด์…˜ (DCF) +``` +> ์‚ผ์„ฑ์ „์ž DCF๋กœ ์ ์ •์ฃผ๊ฐ€ ๊ณ„์‚ฐํ•˜๊ณ  ํ˜„์žฌ๊ฐ€๋ž‘ ๋น„๊ตํ•ด์ค˜ +> ๋„ค์ด๋ฒ„(035420) ๋‚ด์žฌ๊ฐ€์น˜ ๊ตฌํ•˜๊ณ  WACCยทํ„ฐ๋ฏธ๋„ ์„ฑ์žฅ๋ฅ  ๋ฏผ๊ฐ๋„ํ‘œ๊นŒ์ง€ ๋ณด์—ฌ์ค˜ +> NVDA ์ ์ •๊ฐ€์น˜ ๋ถ„์„ํ•ด์ค˜ +``` -## โœ… Prerequisites +> 6์ž๋ฆฌ ํ•œ๊ตญ ํ‹ฐ์ปค๋ฉด DCF ์Šคํ‚ฌ์ด ์ž๋™์œผ๋กœ ํ•œ๊ตญ ๊ฒฝ๋กœ(K-IFRSยท๋ฒ•์ธ์„ธ์œจยท๊ตญ๊ณ ์ฑ„ ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌยทKRW)๋กœ ๋ถ„๊ธฐํ•ฉ๋‹ˆ๋‹ค. -- [Bun](https://bun.com) runtime (v1.0 or higher) -- OpenAI API key (get [here](https://platform.openai.com/api-keys)) -- Financial Datasets API key (get [here](https://financialdatasets.ai)) -- Exa API key (get [here](https://exa.ai)) - optional, for web search +### ๊ณต์‹œ ยท ์†Œ์œ ๊ตฌ์กฐ (DART) -#### Installing Bun +``` +> ์นด์นด์˜ค ์ตœ๊ทผ 1๋…„ ์ฃผ์š” ๊ณต์‹œ ์ •๋ฆฌํ•ด์ค˜ +> ์‚ผ์„ฑ์ „์ž 5% ์ด์ƒ ๋Œ€๋Ÿ‰๋ณด์œ  ์ฃผ์ฃผ ๋ชฉ๋ก ๋ณด์—ฌ์ค˜ +> ์—์ฝ”ํ”„๋กœ ์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ ์ตœ๊ทผ ์ง€๋ถ„ ๋ณ€๋™ ์•Œ๋ ค์ค˜ +``` -If you don't have Bun installed, you can install it using curl: +### ํ•œ๊ตญ ํŠนํ™” ๋ฐ์ดํ„ฐ -**macOS/Linux:** -```bash -curl -fsSL https://bun.com/install | bash ``` - -**Windows:** -```bash -powershell -c "irm bun.sh/install.ps1|iex" +> ์—์ฝ”ํ”„๋กœ๋น„์—  ๊ณต๋งค๋„ ์ˆœ๋ณด์œ ์ž”๊ณ  ์ถ”์ด ์–ด๋•Œ? # KRX ํ‚ค ํ•„์š” +> ์‚ผ์„ฑ์ „์ž ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ ์ตœ๊ทผ ์ถ”์„ธ ๋ณด์—ฌ์ค˜ +> ๊ตญ๋ฏผ์—ฐ๊ธˆ์ด ๋ณด์œ ํ•œ ์‚ผ์„ฑ์ „์ž ์ง€๋ถ„์œจ ์•Œ๋ ค์ค˜ # data.go.kr ํ‚ค ํ•„์š” ``` -After installation, restart your terminal and verify Bun is installed: -```bash -bun --version +### ๋ฌผ์ ๋ถ„ํ•  ยท ์ด๋ฒคํŠธ ๋ถ„์„ + +``` +> LGํ™”ํ•™ ๋ฌผ์ ๋ถ„ํ• ์ด ๊ธฐ์กด ์ฃผ์ฃผ๊ฐ€์น˜์— ์–ด๋–ค ์˜ํ–ฅ์ด์—ˆ๋Š”์ง€ ๋ถ„์„ํ•ด์ค˜ +> ์นด์นด์˜ค ์žํšŒ์‚ฌ ์ชผ๊ฐœ๊ธฐ ์ƒ์žฅ์ด ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ์— ๋ฏธ์นœ ์˜ํ–ฅ ํ‰๊ฐ€ํ•ด์ค˜ ``` -## ๐Ÿ’ป How to Install +### ํฌ๋กœ์Šค๋งˆ์ผ“ ๋น„๊ต -1. Clone the repository: -```bash -git clone https://github.com/virattt/dexter.git -cd dexter +``` +> TSMC์™€ ์‚ผ์„ฑ์ „์ž ํŒŒ์šด๋“œ๋ฆฌ ์‚ฌ์—… ์žฌ๋ฌด๋กœ ๋น„๊ตํ•ด์ค˜ +> ์—”๋น„๋””์•„์™€ SKํ•˜์ด๋‹‰์Šค HBM ๊ด€๋ จ ์‹ค์  ๋น„๊ต ๋ถ„์„ํ•ด์ค˜ ``` -2. Install dependencies with Bun: -```bash -bun install +> ํฌ๋กœ์Šค๋งˆ์ผ“ ์ฟผ๋ฆฌ๋Š” `get_financials`(US)์™€ `get_financials_kr`(KR)๋ฅผ ๋ชจ๋‘ ํ˜ธ์ถœํ•˜๊ณ , ๋‹จ์œ„ยทํšŒ๊ณ„๊ธฐ์ค€ ์ฐจ์ด๋ฅผ ๋ณด์ •ํ•ด ๋น„๊ต ํ‘œ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +### ํˆฌ์ž ๋ฉ”๋ชจ ยท ์„ผํ‹ฐ๋จผํŠธ + +``` +> ์‚ผ์„ฑ์ „์ž ๋งค์ˆ˜ ๋…ผ๊ฑฐ๋ฅผ ํˆฌ์ž ๋ฉ”๋ชจ๋กœ ์ž‘์„ฑํ•ด์ค˜ +> ํ…Œ์Šฌ๋ผ์— ๋Œ€ํ•œ X(ํŠธ์œ„ํ„ฐ) ์—ฌ๋ก  ๋ถ„์„ํ•ด์ค˜ ``` -3. Set up your environment variables: -```bash -# Copy the example environment file -cp env.example .env +> ์ผ๋ถ€ ํ•œ๊ตญ ํŠนํ™” ํˆด(๊ณต๋งค๋„ยท๊ตญ๋ฏผ์—ฐ๊ธˆ)๊ณผ ์›น/X ๊ฒ€์ƒ‰์€ ํ•ด๋‹น API ํ‚ค๊ฐ€ ์„ค์ •๋ผ ์žˆ์–ด์•ผ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. [๋น ๋ฅธ ์‹œ์ž‘](#-๋น ๋ฅธ-์‹œ์ž‘) ์ฐธ๊ณ . -# Edit .env and add your API keys (if using cloud providers) -# OPENAI_API_KEY=your-openai-api-key -# ANTHROPIC_API_KEY=your-anthropic-api-key (optional) -# GOOGLE_API_KEY=your-google-api-key (optional) -# XAI_API_KEY=your-xai-api-key (optional) -# OPENROUTER_API_KEY=your-openrouter-api-key (optional) +## ๐Ÿ†š ์›น ์ฑ—๋ด‡๊ณผ ๋น„๊ต -# Institutional-grade market data for agents -# FINANCIAL_DATASETS_API_KEY=your-financial-datasets-api-key +2026๋…„ 5์›”, ๋™์ผํ•œ ์งˆ๋ฌธ 5๊ฐœ๋ฅผ **Dexter(LLM: GPT-5.5)** ์™€ **claude.ai(Opus 4.8, ์›น๊ฒ€์ƒ‰ ์‚ฌ์šฉ)** ์— ๋˜์ ธ ์‹ค์ธก ๋น„๊ตํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค ํ”„๋Ÿฐํ‹ฐ์–ด ๋ชจ๋ธ์ด๋ฏ€๋กœ ์ฐจ์ด๋ฅผ ๋งŒ๋“œ๋Š” ๊ฑด ๋ชจ๋ธ์ด ์•„๋‹ˆ๋ผ **๊ธˆ์œต ํ•˜๋„ค์Šค โ€” 1์ฐจ ์ถœ์ฒ˜ ๋„๊ตฌ(DARTยทKRXยทNaver) + ํ•œ๊ตญ ์‹œ์žฅ ์Šคํ‚ฌ** ์ž…๋‹ˆ๋‹ค. *(์•„๋ž˜ ์ˆ˜์น˜๋Š” ๊ฐ ๋„๊ตฌ๊ฐ€ ๋ฐ˜ํ™˜ํ•œ ๊ทธ๋Œ€๋กœ์ด๋ฉฐ, ๋ณ„๋„ ์ •๋ฐ€ ๊ฐ์‚ฌ๋Š” ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.)* -# (Optional) If using Ollama locally -# OLLAMA_BASE_URL=http://127.0.0.1:11434 +| ์งˆ๋ฌธ | claude.ai (Opus 4.8 + ์›น๊ฒ€์ƒ‰) | Dexter (GPT-5.5 + ๊ธˆ์œต ํ•˜๋„ค์Šค) | +|---|---|---| +| ์‚ผ์„ฑ์ „์ž ๋งค์ถœ/์˜์—…์ด์ต | 333.6์กฐ / 43.6์กฐ, ๋‰ด์Šค ์ถœ์ฒ˜ โœ“ (๋‹จ "1Q 57์กฐ" ํ™˜๊ฐ ํ˜ผ์ž…) | DART ์‚ฌ์—…๋ณด๊ณ ์„œ ์ง์ ‘ ํ™•์ธ โ†’ ์†์ต ์ถ”์ถœ ๋ถˆ์•ˆ์ •ํ•˜์ž **์ถ”์ • ๊ฑฐ๋ถ€(๋ณด๋ฅ˜)** | +| ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ | 48.42% (05-20), ๋‰ด์Šค ๊ธฐ๋ฐ˜ | **48.27% (05-29)** ์ผ์ž๋ณ„ + ์ˆœ๋งค๋„๋Ÿ‰, Naver ์ง์ ‘ โ€” ๋” ์ตœ์‹  | +| ๋„ค์ด๋ฒ„ DCF | 161,700์›, ์ถฉ์‹ค(SOTPยท์ปจ์„ผ์„œ์Šค), ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ 4.15% | ~200,000์›, **KR ์ž๋™ ๋ถ„๊ธฐ**(ํ„ฐ๋ฏธ๋„ 2.0%ยท์„ธํ›„ ์‹คํ˜„์ˆ˜์ต๋ฅ  caption), ์‹ค์‹œ์„ธ ๊ธฐ๋ฐ˜ | +| LGํ™”ํ•™ ๋ฌผ์ ๋ถ„ํ•  | ๋‹ค์ถœ์ฒ˜ยทํ’๋ถ€ํ•œ ์„œ์ˆ  | DART ๊ณต์‹œ 9๊ฑด ์ง์ ‘ ์ธ์šฉ + ๊ตฌ์กฐ ๋ถ„์„ | +| ์—์ฝ”ํ”„๋กœ๋น„์—  ๊ณต๋งค๋„ | 1๊ฐœ ์‹œ์ (5/07)๋งŒ + "KRX์„œ ์กฐํšŒ" ์•ˆ๋‚ด | **18์ผ ์ผ๋ณ„ ์ž”๊ณ ์œจยท๊ธˆ์•กยท์ˆ˜๋Ÿ‰**(KRX ์ง์ ‘) | -# Web Search (Exa preferred, Tavily fallback) -# EXASEARCH_API_KEY=your-exa-api-key -# TAVILY_API_KEY=your-tavily-api-key +**Dexter๊ฐ€ ๋งŒ๋“  ์ฐจ์ด** +- **1์ฐจ ์ถœ์ฒ˜ ์ง์ ‘** โ€” DART(์žฌ๋ฌดยท๊ณต์‹œ)ยทKRX(๊ณต๋งค๋„)ยทNaver(์™ธ๊ตญ์ธ)๋ฅผ API๋กœ ์ง์ ‘ ํ˜ธ์ถœ โ†’ ๋‰ด์Šค(2์ฐจ)๋ณด๋‹ค ์ •๋ฐ€ยท์ตœ์‹ ยท์ผ๋ณ„ +- **๊ตฌ์กฐํ™” ์‹œ๊ณ„์—ด** โ€” ๊ณต๋งค๋„ 18์ผ์น˜, ์™ธ๊ตญ์ธ ์ผ์ž๋ณ„์ฒ˜๋Ÿผ ๋‰ด์Šค์—” ์—†๋Š” ๋ฐ์ดํ„ฐ +- **๊ฐ์‚ฌ ๊ฐ€๋Šฅ** โ€” ๋ชจ๋“  ํˆด ํ˜ธ์ถœ์ด ์Šคํฌ๋ž˜์น˜ํŒจ๋“œ์— ๊ธฐ๋ก โ†’ ๊ฐ™์€ ์งˆ๋ฌธ์ด๋ฉด ๊ฐ™์€ ๋ฐ์ดํ„ฐ ๊ฒฝ๋กœ +- **KR ์ž๋™ ํŠœ๋‹** โ€” 6์ž๋ฆฌ ํ‹ฐ์ปค๋ฉด DCF๊ฐ€ K-IFRSยทKRWยท์„ธํ›„ ์‹คํ˜„์ˆ˜์ต๋ฅ  ๊ฒฝ๋กœ๋กœ ๋ถ„๊ธฐ +- **ํ™˜๊ฐ๋ณด๋‹ค ์ •์ง** โ€” ๋ฐ์ดํ„ฐ๋ฅผ ๋ชป ์–ป์œผ๋ฉด ๋งŒ๋“ค์–ด๋‚ด์ง€ ์•Š๊ณ  "๋ชป ์–ป๋Š”๋‹ค"๊ณ  ํ•จ + +**์ •์งํ•œ ํ•œ๊ณ„** โ€” Dexter๊ฐ€ ํ•ญ์ƒ ์ด๊ธฐ๋Š” ๊ฑด ์•„๋‹™๋‹ˆ๋‹ค: +- ์ด๋ฒˆ ์‚ผ์„ฑ ์†์ต์€ claude.ai๊ฐ€ ์ถœ์ฒ˜์™€ ํ•จ๊ป˜ ๋‹ตํ•œ ๋ฐ˜๋ฉด Dexter๋Š” ์ถ”์ถœ ๋ถˆ์•ˆ์ •์œผ๋กœ ๋ณด๋ฅ˜ โ€” ์ปค๋ฒ„๋ฆฌ์ง€๋Š” ์•„์ง ์™„์ „์น˜ ์•Š์Œ +- DCF ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ๋ฅผ Dexter ์Šคํ‚ฌ์€ 3.0%๋กœ ๊ณ ์ •, claude.ai๋Š” ๊ฒ€์ƒ‰์œผ๋กœ 4.15% ๋ฐ˜์˜ โ†’ ํ•˜๋“œ์ฝ”๋”ฉ ๊ฐ€์ •์ด ๋‚ก์„ ์ˆ˜ ์žˆ์Œ(๊ฐœ์„  ์˜ˆ์ •) +- ์›น๊ฒ€์ƒ‰ ํ”„๋Ÿฐํ‹ฐ์–ด ๋ชจ๋ธ์€ ๊ฐ•๋ ฅํ•ฉ๋‹ˆ๋‹ค. Dexter์˜ ์šฐ์œ„๋Š” "๋” ๋˜‘๋˜‘ํ•จ"์ด ์•„๋‹ˆ๋ผ **1์ฐจ ์ถœ์ฒ˜ยท๊ตฌ์กฐํ™”ยท๊ฐ์‚ฌ๊ฐ€๋Šฅยทํ•œ๊ตญ ํŠœ๋‹**์— ์žˆ์Šต๋‹ˆ๋‹ค + +## ๐Ÿš€ ๋น ๋ฅธ ์‹œ์ž‘ + +### ์‚ฌ์ „ ์ค€๋น„ + +- [Bun](https://bun.com) ๋Ÿฐํƒ€์ž„ (v1.0 ์ด์ƒ) +- **OpenAI API ํ‚ค** ([๋ฐœ๊ธ‰](https://platform.openai.com/api-keys)) โ€” ๋˜๋Š” ๋‹ค๋ฅธ LLM ์ œ๊ณต์ž +- **Financial Datasets API ํ‚ค** ([๋ฐœ๊ธ‰](https://financialdatasets.ai)) โ€” ๋ฏธ๊ตญ ์‹œ์žฅ ๋ฐ์ดํ„ฐ +- **DART API ํ‚ค** ([๋ฐœ๊ธ‰](https://opendart.fss.or.kr), ๋ฌด๋ฃŒ, ์ผ 10,000๊ฑด) โ€” ํ•œ๊ตญ ์‹œ์žฅ ๋ฐ์ดํ„ฐ +- (์„ ํƒ) Exa(์›น ๊ฒ€์ƒ‰), KRXยทdata.go.kr(ํ•œ๊ตญ ํŠนํ™” ๋ฐ์ดํ„ฐ), X(์„ผํ‹ฐ๋จผํŠธ) ํ‚ค + +Bun์ด ์—†๋‹ค๋ฉด: + +```bash +# macOS/Linux +curl -fsSL https://bun.com/install | bash + +# Windows +powershell -c "irm bun.sh/install.ps1|iex" ``` -## ๐Ÿš€ How to Run +์„ค์น˜ ํ›„ ํ„ฐ๋ฏธ๋„์„ ์žฌ์‹œ์ž‘ํ•˜๊ณ  ํ™•์ธ: `bun --version` + +### ์„ค์น˜ -Run Dexter in interactive mode: ```bash -bun start +git clone https://github.com/virattt/dexter.git +cd dexter +bun install ``` -Or with watch mode for development: +### ํ™˜๊ฒฝ ๋ณ€์ˆ˜ + +`env.example`์„ ๋ณต์‚ฌํ•ด `.env`๋ฅผ ๋งŒ๋“ค๊ณ  ํ•„์š”ํ•œ ํ‚ค๋ฅผ ์ฑ„์›๋‹ˆ๋‹ค. (`your-`๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฐ’์€ ๋ฏธ์„ค์ •์œผ๋กœ ๊ฐ„์ฃผ๋ฉ๋‹ˆ๋‹ค.) + ```bash -bun dev +cp env.example .env ``` -## ๐Ÿ“Š How to Evaluate +```bash +# --- LLM (์ตœ์†Œ ํ•˜๋‚˜ ํ•„์ˆ˜) --- +OPENAI_API_KEY=your-openai-api-key +# ANTHROPIC_API_KEY=... (์„ ํƒ) +# GOOGLE_API_KEY=... (์„ ํƒ) +# XAI_API_KEY=... / OPENROUTER_API_KEY=... (์„ ํƒ) +# OLLAMA_BASE_URL=http://127.0.0.1:11434 (๋กœ์ปฌ LLM) + +# --- ๋ฏธ๊ตญ ์‹œ์žฅ ๋ฐ์ดํ„ฐ --- +FINANCIAL_DATASETS_API_KEY=your-financial-datasets-api-key + +# --- ํ•œ๊ตญ ์‹œ์žฅ --- +DART_API_KEY=your-dart-api-key # ์žฌ๋ฌดยท๊ณต์‹œ (ํ•„์ˆ˜: KR ํŽ€๋”๋ฉ˜ํ„ธ/๊ณต์‹œ) +# KRX_ID=your-krx-id # ๊ณต๋งค๋„ ์ž”๊ณ  (data.krx.co.kr ๋กœ๊ทธ์ธ) +# KRX_PW=your-krx-password +# KRX_COOKIE=JSESSIONID=...; # ์†Œ์…œ ๋กœ๊ทธ์ธ ๊ณ„์ •์€ ๋ธŒ๋ผ์šฐ์ € ์ฟ ํ‚ค ๋ถ™์—ฌ๋„ฃ๊ธฐ +# DATA_GO_KR_SERVICE_KEY=... # ๊ตญ๋ฏผ์—ฐ๊ธˆ ๋ณด์œ  (data.go.kr, Decoded ํ‚ค) + +# --- ์›น ๊ฒ€์ƒ‰ (์„ ํƒ, Exa ์šฐ์„  โ†’ Tavily ํด๋ฐฑ) --- +# EXASEARCH_API_KEY=your-exa-api-key +# TAVILY_API_KEY=your-tavily-api-key -Dexter includes an evaluation suite that tests the agent against a dataset of financial questions. Evals use LangSmith for tracking and an LLM-as-judge approach for scoring correctness. +# --- X/ํŠธ์œ„ํ„ฐ ์„ผํ‹ฐ๋จผํŠธ (์„ ํƒ) --- +# X_BEARER_TOKEN=your-X-bearer-token +``` + +### ์‹คํ–‰ -**Run on all questions:** ```bash -bun run src/evals/run.ts +bun start # ๋Œ€ํ™”ํ˜• ๋ชจ๋“œ +bun dev # ๊ฐœ๋ฐœ์šฉ watch ๋ชจ๋“œ ``` -**Run on a random sample of data:** +## ๐Ÿ”ง ๋” ์•Œ์•„๋ณด๊ธฐ + +### ๐Ÿ“Š ํ‰๊ฐ€ + +Dexter์—๋Š” ๊ธˆ์œต ์งˆ๋ฌธ ๋ฐ์ดํ„ฐ์…‹์œผ๋กœ ์—์ด์ „ํŠธ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋Š” ํ‰๊ฐ€(eval) ์Šค์œ„ํŠธ๊ฐ€ ํฌํ•จ๋ผ ์žˆ์Šต๋‹ˆ๋‹ค. ํ‰๊ฐ€๋Š” ์ถ”์ ์— LangSmith๋ฅผ, ์ •๋‹ต ์ฑ„์ ์— LLM-as-judge ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + ```bash -bun run src/evals/run.ts --sample 10 +bun run src/evals/run.ts # ์ „์ฒด ์งˆ๋ฌธ +bun run src/evals/run.ts --sample 10 # ๋ฌด์ž‘์œ„ ์ƒ˜ํ”Œ 10๊ฐœ ``` -The eval runner displays a real-time UI showing progress, current question, and running accuracy statistics. Results are logged to LangSmith for analysis. +ํ‰๊ฐ€ ๋Ÿฌ๋„ˆ๋Š” ์ง„ํ–‰ ์ƒํ™ฉ, ํ˜„์žฌ ์งˆ๋ฌธ, ์‹ค์‹œ๊ฐ„ ์ •ํ™•๋„ ํ†ต๊ณ„๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” UI๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ๋Š” ๋ถ„์„์„ ์œ„ํ•ด LangSmith์— ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค. -## ๐Ÿ› How to Debug +### ๐Ÿ› ๋””๋ฒ„๊น… -Dexter logs all tool calls to a scratchpad file for debugging and history tracking. Each query creates a new JSONL file in `.dexter/scratchpad/`. +Dexter๋Š” ๋””๋ฒ„๊น…๊ณผ ์ด๋ ฅ ์ถ”์ ์„ ์œ„ํ•ด ๋ชจ๋“  ํˆด ํ˜ธ์ถœ์„ ์Šคํฌ๋ž˜์น˜ํŒจ๋“œ(scratchpad) ํŒŒ์ผ์— ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ์ฟผ๋ฆฌ๋Š” `.dexter/scratchpad/`์— ์ƒˆ JSONL ํŒŒ์ผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. -**Scratchpad location:** ``` .dexter/scratchpad/ โ”œโ”€โ”€ 2026-01-30-111400_9a8f10723f79.jsonl @@ -252,46 +257,31 @@ Dexter logs all tool calls to a scratchpad file for debugging and history tracki โ””โ”€โ”€ ... ``` -Each file contains newline-delimited JSON entries tracking: -- **init**: The original query -- **tool_result**: Each tool call with arguments, raw result, and LLM summary -- **thinking**: Agent reasoning steps +๊ฐ ํŒŒ์ผ์€ ๋‹ค์Œ์„ ์ถ”์ ํ•˜๋Š” ์ค„๋ฐ”๊ฟˆ ๊ตฌ๋ถ„(JSONL) ํ•ญ๋ชฉ์„ ๋‹ด์Šต๋‹ˆ๋‹ค: +- **init**: ์›๋ณธ ์ฟผ๋ฆฌ +- **tool_result**: ์ธ์žยท์›์‹œ ๊ฒฐ๊ณผยทLLM ์š”์•ฝ์„ ํฌํ•จํ•œ ๊ฐ ํˆด ํ˜ธ์ถœ +- **thinking**: ์—์ด์ „ํŠธ์˜ ์ถ”๋ก  ๋‹จ๊ณ„ -**Example scratchpad entry:** +**์Šคํฌ๋ž˜์น˜ํŒจ๋“œ ํ•ญ๋ชฉ ์˜ˆ์‹œ:** ```json {"type":"tool_result","timestamp":"2026-01-30T11:14:05.123Z","toolName":"get_income_statements","args":{"ticker":"AAPL","period":"annual","limit":5},"result":{...},"llmSummary":"Retrieved 5 years of Apple annual income statements showing revenue growth from $274B to $394B"} ``` -This makes it easy to inspect exactly what data the agent gathered and how it interpreted results. - -## ๐Ÿ“ฑ How to Use with WhatsApp - -Chat with Dexter through WhatsApp by linking your phone to the gateway. Messages you send to yourself are processed by Dexter and responses are sent back to the same chat. - -**Quick start:** -```bash -# Link your WhatsApp account (scan QR code) -bun run gateway:login - -# Start the gateway -bun run gateway -``` - -Then open WhatsApp, go to your own chat (message yourself), and ask Dexter a question. - -For detailed setup instructions, configuration options, and troubleshooting, see the [WhatsApp Gateway README](src/gateway/channels/whatsapp/README.md). +๋•๋ถ„์— ์—์ด์ „ํŠธ๊ฐ€ ์ •ํ™•ํžˆ ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ–ˆ๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ด์„ํ–ˆ๋Š”์ง€ ์†์‰ฝ๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -## ๐Ÿค How to Contribute +## ๐Ÿ“„ ๋ผ์ด์„ ์Šค -1. Fork the repository -2. Create a feature branch -3. Commit your changes -4. Push to the branch -5. Create a Pull Request +์ด ํ”„๋กœ์ ํŠธ๋Š” MIT ๋ผ์ด์„ ์Šค๋กœ ๋ฐฐํฌ๋ฉ๋‹ˆ๋‹ค. -**Important**: Please keep your pull requests small and focused. This will make it easier to review and merge. +## โš ๏ธ ๋ฉด์ฑ… ์กฐํ•ญ +์ด ํ”„๋กœ์ ํŠธ๋Š” **๊ต์œกยท์˜ค๋ฝยท์ •๋ณด ์ œ๊ณต ๋ชฉ์ ์œผ๋กœ๋งŒ** ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. ์‹ค์ œ ๊ฑฐ๋ž˜๋‚˜ ํˆฌ์ž๋ฅผ ์œ„ํ•œ ๊ฒƒ์ด ์•„๋‹™๋‹ˆ๋‹ค. -## ๐Ÿ“„ License +- ๊ธˆ์œตยทํˆฌ์žยท์„ธ๋ฌดยท๋ฒ•๋ฅ  ์ž๋ฌธ์ด ์•„๋‹™๋‹ˆ๋‹ค +- ์ •ํ™•์„ฑยท์™„์ „์„ฑยทํŠน์ • ๋ชฉ์  ์ ํ•ฉ์„ฑ์„ ๋ณด์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค +- ์ถœ๋ ฅ ๊ฒฐ๊ณผ๊ฐ€ ๋ถ€์ •ํ™•ํ•˜๊ฑฐ๋‚˜ ๋ถˆ์™„์ „ํ•˜๊ฑฐ๋‚˜ ์‹œ์ ์ด ์ง€๋‚œ ๊ฒƒ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค +- ์ œ์ž‘์ž์™€ ๊ธฐ์—ฌ์ž๋Š” ์–ด๋– ํ•œ ๊ธˆ์ „์  ์†์‹ค์ด๋‚˜ ์†ํ•ด์— ๋Œ€ํ•ด์„œ๋„ ์ฑ…์ž„์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค +- ํˆฌ์ž ๊ฒฐ์ •์„ ๋‚ด๋ฆฌ๊ธฐ ์ „์— ๋ฉดํ—ˆ๋ฅผ ๋ณด์œ ํ•œ ๊ธˆ์œต ์ „๋ฌธ๊ฐ€์™€ ์ƒ๋‹ดํ•˜์„ธ์š” +- ๊ณผ๊ฑฐ ์„ฑ๊ณผ๊ฐ€ ๋ฏธ๋ž˜ ์ˆ˜์ต์„ ๋ณด์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค -This project is licensed under the MIT License. +์ด ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ, ๊ท€ํ•˜๋Š” ํ•™์Šต ๋ฐ ์ •๋ณด ์ œ๊ณต ๋ชฉ์ ์œผ๋กœ๋งŒ ์‚ฌ์šฉํ•  ๊ฒƒ์— ๋™์˜ํ•˜๋ฉฐ ์‚ฌ์šฉ์— ๋”ฐ๋ฅธ ๋ชจ๋“  ์œ„ํ—˜์„ ๊ฐ์ˆ˜ํ•ฉ๋‹ˆ๋‹ค. diff --git a/src/skills/dcf/SKILL.md b/src/skills/dcf/SKILL.md index 918131eb2..6b0bdbb44 100644 --- a/src/skills/dcf/SKILL.md +++ b/src/skills/dcf/SKILL.md @@ -1,123 +1,154 @@ --- name: dcf-valuation -description: Performs discounted cash flow (DCF) valuation analysis to estimate intrinsic value per share. Triggers when user asks for fair value, intrinsic value, DCF, valuation, "what is X worth", price target, undervalued/overvalued analysis, or wants to compare current price to fundamental value. +description: ์ฃผ๋‹น ๋‚ด์žฌ๊ฐ€์น˜๋ฅผ ์ถ”์ •ํ•˜๋Š” DCF(ํ˜„๊ธˆํ๋ฆ„ํ• ์ธ) ๋ฐธ๋ฅ˜์—์ด์…˜ ๋ถ„์„์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์ ์ •์ฃผ๊ฐ€ยท๋‚ด์žฌ๊ฐ€์น˜ยทDCFยท๋ฐธ๋ฅ˜์—์ด์…˜ยท"์–ผ๋งˆ๊ฐ€ ์ ์ •ํ•œ๊ฐ€"ยท๋ชฉํ‘œ์ฃผ๊ฐ€ยท์ €ํ‰๊ฐ€/๊ณ ํ‰๊ฐ€ ๋ถ„์„, ๋˜๋Š” ํ˜„์žฌ๊ฐ€๋ฅผ ํŽ€๋”๋ฉ˜ํ„ธ ๊ฐ€์น˜์™€ ๋น„๊ตํ•˜๋ ค ํ•  ๋•Œ ํŠธ๋ฆฌ๊ฑฐ. (Triggers on fair value, intrinsic value, DCF, valuation, price target, undervalued/overvalued.) --- -# DCF Valuation Skill +# DCF ๋ฐธ๋ฅ˜์—์ด์…˜ ์Šคํ‚ฌ -## Workflow Checklist +## ์›Œํฌํ”Œ๋กœ ์ฒดํฌ๋ฆฌ์ŠคํŠธ -Copy and track progress: +๋ณต์‚ฌํ•ด์„œ ์ง„ํ–‰ ์ƒํ™ฉ์„ ์ถ”์ ํ•˜๋ผ: ``` -DCF Analysis Progress: -- [ ] Step 1: Gather financial data -- [ ] Step 2: Calculate FCF growth rate -- [ ] Step 3: Estimate discount rate (WACC) -- [ ] Step 4: Project future cash flows (Years 1-5 + Terminal) -- [ ] Step 5: Calculate present value and fair value per share -- [ ] Step 6: Run sensitivity analysis -- [ ] Step 7: Validate results -- [ ] Step 8: Present results with caveats +DCF ๋ถ„์„ ์ง„ํ–‰: +- [ ] Step 0: ์‹œ์žฅ ๊ฐ์ง€(US vs KR) ๋ฐ ๊ฒฝ๋กœ ์„ ํƒ +- [ ] Step 1: ์žฌ๋ฌด ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ +- [ ] Step 2: FCF ์„ฑ์žฅ๋ฅ  ๊ณ„์‚ฐ +- [ ] Step 3: ํ• ์ธ์œจ(WACC) ์ถ”์ • +- [ ] Step 4: ๋ฏธ๋ž˜ ํ˜„๊ธˆํ๋ฆ„ ํˆฌ์˜(1~5๋…„ + ํ„ฐ๋ฏธ๋„) +- [ ] Step 5: ํ˜„์žฌ๊ฐ€์น˜ ๋ฐ ์ฃผ๋‹น ์ ์ •๊ฐ€์น˜ ๊ณ„์‚ฐ +- [ ] Step 6: ๋ฏผ๊ฐ๋„ ๋ถ„์„ +- [ ] Step 7: ๊ฒฐ๊ณผ ๊ฒ€์ฆ +- [ ] Step 8: ์ฃผ์˜์‚ฌํ•ญ๊ณผ ํ•จ๊ป˜ ๊ฒฐ๊ณผ ์ œ์‹œ ``` -## Step 1: Gather Financial Data +## Step 0: ์‹œ์žฅ ๊ฐ์ง€ -Call the `get_financials` tool with these queries: +๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ธฐ ์ „์— ์–ด๋А ๊ฒฝ๋กœ๋ฅผ ๋”ฐ๋ฅผ์ง€ ๊ฒฐ์ •ํ•˜๋ผ: -### 1.1 Cash Flow History -**Query:** `"[TICKER] annual cash flow statements for the last 5 years"` +- **KR ๊ฒฝ๋กœ** โ€” ํ‹ฐ์ปค๊ฐ€ 6์ž๋ฆฌ ์ˆซ์ž(์˜ˆ: `005930`, `035420`)์ด๊ฑฐ๋‚˜ ํšŒ์‚ฌ๊ฐ€ ํ•œ๊ตญ์–ด ์ด๋ฆ„(์‚ผ์„ฑ์ „์ž, ๋„ค์ด๋ฒ„)์œผ๋กœ ์‹๋ณ„๋˜๋ฉด. ํ•œ๊ตญ ์ƒ์žฅ์‚ฌ๋Š” **K-IFRS**๋กœ ๋ณด๊ณ ํ•˜๊ณ  **KRW**๋กœ ๊ฑฐ๋ž˜๋œ๋‹ค. +- **US ๊ฒฝ๋กœ** โ€” ํ‹ฐ์ปค๊ฐ€ ASCII ์‹ฌ๋ณผ(`AAPL`, `MSFT`)์ด๋ฉด. US GAAP๋กœ ๋ณด๊ณ ํ•˜๊ณ  USD๋กœ ๊ฑฐ๋ž˜๋œ๋‹ค. -**Extract:** `free_cash_flow`, `net_cash_flow_from_operations`, `capital_expenditure` +์•„๋ž˜ ๊ฐ ๋‹จ๊ณ„๋Š” **US ๊ธฐ๋ณธ๊ฐ’**์„ ์ œ์‹œํ•˜๊ณ , ๋‹ค๋ฅธ ๊ฒฝ์šฐ **๐Ÿ‡ฐ๐Ÿ‡ท KR override**๋ฅผ ๋‘”๋‹ค. override๋Š” KR ๊ฒฝ๋กœ์—์„œ๋งŒ ๋”ฐ๋ฅด๊ณ , ๊ทธ ์™ธ์—๋Š” US ๊ธฐ๋ณธ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ผ. -**Fallback:** If `free_cash_flow` missing, calculate: `net_cash_flow_from_operations - capital_expenditure` +## Step 1: ์žฌ๋ฌด ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ -### 1.2 Financial Metrics -**Query:** `"[TICKER] financial metrics snapshot"` +**US ๊ฒฝ๋กœ** โ€” `get_financials` ๋„๊ตฌ๋ฅผ ๋‹ค์Œ ์ฟผ๋ฆฌ๋กœ ํ˜ธ์ถœํ•œ๋‹ค: -**Extract:** `market_cap`, `enterprise_value`, `free_cash_flow_growth`, `revenue_growth`, `return_on_invested_capital`, `debt_to_equity`, `free_cash_flow_per_share` +> **๐Ÿ‡ฐ๐Ÿ‡ท KR override:** ๋Œ€์‹  `get_financials_kr`๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค(DART ์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ๋กœ ๋ผ์šฐํŒ…). ์ž์—ฐ์–ด ์ฟผ๋ฆฌ ํ•˜๋‚˜๋ฉด ์ถฉ๋ถ„ํ•˜๋‹ค. ์˜ˆ: `"005930 ์ตœ๊ทผ 5๋…„ ์—ฐ๊ฒฐ ์žฌ๋ฌด์ œํ‘œ ํ˜„๊ธˆํ๋ฆ„ยท์†์ตยท์žฌ๋ฌด์ƒํƒœํ‘œ"`. ๊ทธ๋‹ค์Œ ํ˜„์žฌ๊ฐ€๋Š” `get_market_data`๊ฐ€ ํ•œ๊ตญ ํ‹ฐ์ปค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๊ทธ๊ฑธ ์“ฐ๊ณ , ์•„๋‹ˆ๋ฉด `web_search`/`get_foreign_ownership_kr`๋กœ ์ตœ๊ทผ ์ข…๊ฐ€๋ฅผ ๋ณด์™„ํ•œ๋‹ค. +> +> DART ๊ณ„์ • ๋ผ๋ฒจ(`account_nm`)์€ ํšŒ์‚ฌยท์—ฐ๋„๋งˆ๋‹ค ๋‹ค๋ฅด๋‹ค โ€” **์ •ํ™• ์ผ์น˜ ๊ธˆ์ง€**. ์˜์—…์ˆ˜์ต vs ๋งค์ถœ์•ก, ๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค) ์ ‘๋ฏธ์‚ฌ ๋“ฑ. ๋ถ€๋ถ„ ๋ฌธ์ž์—ด / ํ‘œ์ค€ `account_id`(์žˆ์„ ๋•Œ)๋กœ ๋งค์นญํ•˜๊ณ  ๊ธฐ๊ฐ„ ๊ฐ„ ์ •ํ•ฉ์„ฑ์„ ๋งž์ถ˜๋‹ค. -### 1.3 Balance Sheet -**Query:** `"[TICKER] latest balance sheet"` +### 1.1 ํ˜„๊ธˆํ๋ฆ„ ์ด๋ ฅ +**์ฟผ๋ฆฌ:** `"[TICKER] annual cash flow statements for the last 5 years"` -**Extract:** `total_debt`, `cash_and_equivalents`, `current_investments`, `outstanding_shares` +**์ถ”์ถœ:** `free_cash_flow`, `net_cash_flow_from_operations`, `capital_expenditure` -**Fallback:** If `current_investments` missing, use 0 +**ํด๋ฐฑ:** `free_cash_flow`๊ฐ€ ์—†์œผ๋ฉด ๊ณ„์‚ฐ: `net_cash_flow_from_operations - capital_expenditure` -### 1.4 Current Price -Call the `get_market_data` tool: +### 1.2 ์žฌ๋ฌด ์ง€ํ‘œ +**์ฟผ๋ฆฌ:** `"[TICKER] financial metrics snapshot"` -**Query:** `"[TICKER] price snapshot"` +**์ถ”์ถœ:** `market_cap`, `enterprise_value`, `free_cash_flow_growth`, `revenue_growth`, `return_on_invested_capital`, `debt_to_equity`, `free_cash_flow_per_share` -**Extract:** `price` +### 1.3 ์žฌ๋ฌด์ƒํƒœํ‘œ +**์ฟผ๋ฆฌ:** `"[TICKER] latest balance sheet"` -### 1.5 Company Facts -Call the `get_financials` tool: +**์ถ”์ถœ:** `total_debt`, `cash_and_equivalents`, `current_investments`, `outstanding_shares` -**Query:** `"[TICKER] company facts"` +**ํด๋ฐฑ:** `current_investments`๊ฐ€ ์—†์œผ๋ฉด 0 ์‚ฌ์šฉ -**Extract:** `sector`, `industry`, `market_cap` +### 1.4 ํ˜„์žฌ๊ฐ€ +`get_market_data` ๋„๊ตฌ๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค: -**Use:** Determine appropriate WACC range from [sector-wacc.md](sector-wacc.md) +**์ฟผ๋ฆฌ:** `"[TICKER] price snapshot"` -## Step 2: Calculate FCF Growth Rate +**์ถ”์ถœ:** `price` -Calculate 5-year FCF CAGR from cash flow history. +### 1.5 ํšŒ์‚ฌ ์ •๋ณด(Company Facts) +`get_financials` ๋„๊ตฌ๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค: -**Cross-validate with:** `free_cash_flow_growth` (YoY), `revenue_growth` +**์ฟผ๋ฆฌ:** `"[TICKER] company facts"` -**Growth rate selection:** -- Stable FCF history โ†’ Use CAGR with 10-20% haircut -- **Cap at 15%** (sustained higher growth is rare) +**์ถ”์ถœ:** `sector`, `industry`, `market_cap` -## Step 3: Estimate Discount Rate (WACC) +**์šฉ๋„:** [sector-wacc.md](sector-wacc.md)์—์„œ ์ ์ ˆํ•œ WACC ๋ ˆ์ธ์ง€ ๊ฒฐ์ • -**Use the `sector` from company facts** to select the appropriate base WACC range from [sector-wacc.md](sector-wacc.md). +> **๐Ÿ‡ฐ๐Ÿ‡ท KR override:** `get_financials_kr`๋Š” ๋ฏธ๊ตญ์‹ `sector` ํ•„๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๋Š”๋‹ค. ํšŒ์‚ฌ์˜ ์ฃผ๋ ฅ ์‚ฌ์—…(๋ฐ˜๋„์ฒด, ์ž๋™์ฐจ, 2์ฐจ์ „์ง€, ๋ฐ”์ด์˜ค, ๊ธˆ์œต, ํ†ต์‹ , ์œ ํ‹ธ๋ฆฌํ‹ฐ, ์†Œ๋น„์žฌ โ€ฆ)์œผ๋กœ ์„นํ„ฐ๋ฅผ ์ถ”๋ก ํ•˜๊ณ  [sector-wacc-kr.md](sector-wacc-kr.md)์—์„œ WACC ๋ ˆ์ธ์ง€๋ฅผ ์ฝ์–ด๋ผ. -**Default assumptions:** -- Risk-free rate: 4% -- Equity risk premium: 5-6% -- Cost of debt: 5-6% pre-tax (~4% after-tax at 30% tax rate) +## Step 2: FCF ์„ฑ์žฅ๋ฅ  ๊ณ„์‚ฐ -Calculate WACC using `debt_to_equity` for capital structure weights. +ํ˜„๊ธˆํ๋ฆ„ ์ด๋ ฅ์—์„œ 5๋…„ FCF CAGR๋ฅผ ๊ณ„์‚ฐํ•œ๋‹ค. -**Reasonableness check:** WACC should be 2-4% below `return_on_invested_capital` for value-creating companies. +**๊ต์ฐจ ๊ฒ€์ฆ:** `free_cash_flow_growth`(YoY), `revenue_growth` -**Sector adjustments:** Apply adjustment factors from [sector-wacc.md](sector-wacc.md) based on company-specific characteristics. +**์„ฑ์žฅ๋ฅ  ์„ ํƒ:** +- ์•ˆ์ •์  FCF ์ด๋ ฅ โ†’ CAGR์— 10~20% ํ• ์ธ(haircut) ์ ์šฉ +- **15%๋กœ ์ƒํ•œ** (์ง€์†์  ๊ณ ์„ฑ์žฅ์€ ๋“œ๋ฌผ๋‹ค) -## Step 4: Project Future Cash Flows +## Step 3: ํ• ์ธ์œจ(WACC) ์ถ”์ • -**Years 1-5:** Apply growth rate with 5% annual decay (multiply growth rate by 0.95, 0.90, 0.85, 0.80 for years 2-5). This reflects competitive dynamics. +**ํšŒ์‚ฌ ์ •๋ณด์˜ `sector`๋ฅผ ์‚ฌ์šฉ**ํ•ด [sector-wacc.md](sector-wacc.md)์—์„œ ์ ์ ˆํ•œ ๊ธฐ์ค€ WACC ๋ ˆ์ธ์ง€๋ฅผ ์„ ํƒํ•œ๋‹ค. -**Terminal value:** Use Gordon Growth Model with 2.5% terminal growth (GDP proxy). +**๊ธฐ๋ณธ ๊ฐ€์ •(US):** +- ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ: 4% +- ์‹œ์žฅ ์œ„ํ—˜ํ”„๋ฆฌ๋ฏธ์—„: 5~6% +- ๋ถ€์ฑ„๋น„์šฉ: ์„ธ์ „ 5~6% (์„ธ์œจ 30% ๊ธฐ์ค€ ์„ธํ›„ ~4%) -## Step 5: Calculate Present Value +์ž๋ณธ๊ตฌ์กฐ ๊ฐ€์ค‘์น˜๋Š” `debt_to_equity`๋กœ WACC๋ฅผ ๊ณ„์‚ฐํ•œ๋‹ค. -Discount all FCFs โ†’ sum for Enterprise Value โ†’ subtract Net Debt โ†’ divide by `outstanding_shares` for fair value per share. +> **๐Ÿ‡ฐ๐Ÿ‡ท KR override โ€” ํ•œ๊ตญ ์‹œ์žฅ ์ž…๋ ฅ๊ฐ’ ์‚ฌ์šฉ:** +> - ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ: **~3%** (10๋…„ ๊ตญ๊ณ ์ฑ„), 4% ์•„๋‹˜ +> - ์‹œ์žฅ ์œ„ํ—˜ํ”„๋ฆฌ๋ฏธ์—„: 5~7% (์žฌ๋ฒŒ ๊ณ„์—ดยท์ƒํ˜ธ์ถœ์ž ์ข…๋ชฉ์€ ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ/์ง€๋ฐฐ๊ตฌ์กฐ ์šฐ๋ ค๋กœ ์ƒ๋‹จ์— ๊ฐ€๊น๊ฒŒ) +> - ๋ถ€์ฑ„๋น„์šฉ: ์„ธ์ „ ์‹œ์žฅ๊ธˆ๋ฆฌ, **์„ธํ›„๋Š” ๋ฒ•์ธ์„ธ์œจ ~22% ์ ์šฉ** (K-IFRS ์‹คํšจ; ์ง€๋ฐฉ์†Œ๋“์„ธ ํฌํ•จ marginal ์ตœ๋Œ€ ~24~26%) โ€” **30% ์•„๋‹˜** +> - ๊ธฐ์ค€ WACC ๋ ˆ์ธ์ง€๋Š” [sector-wacc-kr.md](sector-wacc-kr.md) ์ฐธ์กฐ -## Step 6: Sensitivity Analysis +**ํ•ฉ๋ฆฌ์„ฑ ์ ๊ฒ€:** ๊ฐ€์น˜ ์ฐฝ์ถœ ๊ธฐ์—…์€ WACC๊ฐ€ `return_on_invested_capital`๋ณด๋‹ค 2~4% ๋‚ฎ์•„์•ผ ํ•œ๋‹ค. -Create 3ร—3 matrix: WACC (base ยฑ1%) vs terminal growth (2.0%, 2.5%, 3.0%). +**์„นํ„ฐ ์กฐ์ •:** [sector-wacc.md](sector-wacc.md)(US) ๋˜๋Š” [sector-wacc-kr.md](sector-wacc-kr.md)(KR)์˜ ์กฐ์ • ์ธ์ž๋ฅผ ๊ธฐ์—…๋ณ„ ํŠน์„ฑ์— ๋”ฐ๋ผ ์ ์šฉํ•œ๋‹ค. -## Step 7: Validate Results +## Step 4: ๋ฏธ๋ž˜ ํ˜„๊ธˆํ๋ฆ„ ํˆฌ์˜ -Before presenting, verify these sanity checks: +**1~5๋…„์ฐจ:** ์„ฑ์žฅ๋ฅ ์— ์—ฐ 5% ๊ฐ์‡  ์ ์šฉ(2~5๋…„์ฐจ์— ์„ฑ์žฅ๋ฅ ์„ 0.95, 0.90, 0.85, 0.80๋ฐฐ). ๊ฒฝ์Ÿ ๋™ํ•™์„ ๋ฐ˜์˜ํ•œ๋‹ค. -1. **EV comparison**: Calculated EV should be within 30% of reported `enterprise_value` - - If off by >30%, revisit WACC or growth assumptions +**ํ„ฐ๋ฏธ๋„ ๊ฐ€์น˜:** Gordon ์„ฑ์žฅ ๋ชจํ˜•์œผ๋กœ 2.5% ํ„ฐ๋ฏธ๋„ ์„ฑ์žฅ๋ฅ (GDP ํ”„๋ก์‹œ) ์‚ฌ์šฉ. -2. **Terminal value ratio**: Terminal value should be 50-80% of total EV for mature companies - - If >90%, growth rate may be too high - - If <40%, near-term projections may be aggressive +> **๐Ÿ‡ฐ๐Ÿ‡ท KR override:** ํ„ฐ๋ฏธ๋„ ์„ฑ์žฅ๋ฅ  **~2.0%** ์‚ฌ์šฉ(ํ•œ๊ตญ์˜ ๋‚ฎ์€ ์ž ์žฌ GDP ์„ฑ์žฅ๋ฅ ). Step 6 ๋ฏผ๊ฐ๋„ ๊ทธ๋ฆฌ๋“œ๋„ ์ด๋ฅผ ์ค‘์‹ฌ์œผ๋กœ โ€” ๋ฏธ๊ตญ์˜ 2.0/2.5/3.0 ๋Œ€์‹  ํ„ฐ๋ฏธ๋„ ์„ฑ์žฅ๋ฅ ์„ **1.5% / 2.0% / 2.5%**๋กœ ๋ณ€ํ™”์‹œํ‚จ๋‹ค. -3. **Per-share cross-check**: Compare to `free_cash_flow_per_share ร— 15-25` as rough sanity check +## Step 5: ํ˜„์žฌ๊ฐ€์น˜ ๊ณ„์‚ฐ -If validation fails, reconsider assumptions before presenting results. +๋ชจ๋“  FCF๋ฅผ ํ• ์ธ โ†’ ํ•ฉ์‚ฐํ•ด ๊ธฐ์—…๊ฐ€์น˜(Enterprise Value) โ†’ ์ˆœ๋ถ€์ฑ„(Net Debt) ์ฐจ๊ฐ โ†’ `outstanding_shares`๋กœ ๋‚˜๋ˆ  ์ฃผ๋‹น ์ ์ •๊ฐ€์น˜๋ฅผ ๊ตฌํ•œ๋‹ค. -## Step 8: Output Format +## Step 6: ๋ฏผ๊ฐ๋„ ๋ถ„์„ -Present a structured summary including: -1. **Valuation Summary**: Current price vs. fair value, upside/downside percentage -2. **Key Inputs Table**: All assumptions with their sources -3. **Projected FCF Table**: 5-year projections with present values -4. **Sensitivity Matrix**: 3ร—3 grid varying WACC (ยฑ1%) and terminal growth (2.0%, 2.5%, 3.0%) -5. **Caveats**: Standard DCF limitations plus company-specific risks +3ร—3 ํ–‰๋ ฌ ์ƒ์„ฑ: WACC(๊ธฐ์ค€ ยฑ1%) vs ํ„ฐ๋ฏธ๋„ ์„ฑ์žฅ๋ฅ (2.0%, 2.5%, 3.0%). + +> **๐Ÿ‡ฐ๐Ÿ‡ท KR override:** ํ„ฐ๋ฏธ๋„ ์„ฑ์žฅ๋ฅ  ์ถ• = **1.5% / 2.0% / 2.5%** (ํ•œ๊ตญ์˜ ~2.0% ํ„ฐ๋ฏธ๋„ ์„ฑ์žฅ๋ฅ  ์ค‘์‹ฌ). + +## Step 7: ๊ฒฐ๊ณผ ๊ฒ€์ฆ + +์ œ์‹œ ์ „์— ๋‹ค์Œ ํ•ฉ๋ฆฌ์„ฑ ์ ๊ฒ€์„ ํ™•์ธํ•˜๋ผ: + +1. **EV ๋น„๊ต**: ๊ณ„์‚ฐ๋œ EV๋Š” ๋ณด๊ณ ๋œ `enterprise_value`์˜ 30% ์ด๋‚ด์—ฌ์•ผ ํ•œ๋‹ค + - 30% ์ดˆ๊ณผ๋กœ ๋ฒ—์–ด๋‚˜๋ฉด WACC๋‚˜ ์„ฑ์žฅ ๊ฐ€์ •์„ ์žฌ๊ฒ€ํ†  + +2. **ํ„ฐ๋ฏธ๋„ ๊ฐ€์น˜ ๋น„์ค‘**: ์„ฑ์ˆ™ ๊ธฐ์—…์€ ํ„ฐ๋ฏธ๋„ ๊ฐ€์น˜๊ฐ€ ์ „์ฒด EV์˜ 50~80%์—ฌ์•ผ ํ•œ๋‹ค + - 90% ์ดˆ๊ณผ๋ฉด ์„ฑ์žฅ๋ฅ ์ด ๋„ˆ๋ฌด ๋†’์„ ์ˆ˜ ์žˆ์Œ + - 40% ๋ฏธ๋งŒ์ด๋ฉด ๋‹จ๊ธฐ ํˆฌ์˜์ด ๊ณต๊ฒฉ์ ์ผ ์ˆ˜ ์žˆ์Œ + +3. **์ฃผ๋‹น ๊ต์ฐจ ์ ๊ฒ€**: `free_cash_flow_per_share ร— 15~25`์™€ ๋น„๊ตํ•ด ๋Œ€๋žต์  ํ•ฉ๋ฆฌ์„ฑ ์ ๊ฒ€ + +๊ฒ€์ฆ์— ์‹คํŒจํ•˜๋ฉด ๊ฒฐ๊ณผ๋ฅผ ์ œ์‹œํ•˜๊ธฐ ์ „์— ๊ฐ€์ •์„ ์žฌ๊ณ ํ•˜๋ผ. + +## Step 8: ์ถœ๋ ฅ ํ˜•์‹ + +๋‹ค์Œ์„ ํฌํ•จํ•œ ๊ตฌ์กฐํ™”๋œ ์š”์•ฝ์„ ์ œ์‹œํ•œ๋‹ค: +1. **๋ฐธ๋ฅ˜์—์ด์…˜ ์š”์•ฝ**: ํ˜„์žฌ๊ฐ€ vs ์ ์ •๊ฐ€์น˜, ์ƒ์Šน/ํ•˜๋ฝ ์—ฌ๋ ฅ ํผ์„ผํŠธ +2. **ํ•ต์‹ฌ ์ž…๋ ฅ๊ฐ’ ํ‘œ**: ๋ชจ๋“  ๊ฐ€์ •๊ณผ ์ถœ์ฒ˜ +3. **ํˆฌ์˜ FCF ํ‘œ**: 5๋…„ ํˆฌ์˜๊ณผ ํ˜„์žฌ๊ฐ€์น˜ +4. **๋ฏผ๊ฐ๋„ ํ–‰๋ ฌ**: WACC(ยฑ1%)์™€ ํ„ฐ๋ฏธ๋„ ์„ฑ์žฅ๋ฅ (2.0%, 2.5%, 3.0%)์„ ๋ณ€ํ™”์‹œํ‚จ 3ร—3 ๊ทธ๋ฆฌ๋“œ +5. **์ฃผ์˜์‚ฌํ•ญ**: ํ‘œ์ค€ DCF ํ•œ๊ณ„ + ๊ธฐ์—…๋ณ„ ๋ฆฌ์Šคํฌ + +> **๐Ÿ‡ฐ๐Ÿ‡ท KR override:** +> - ๋ชจ๋“  ๊ฐ’์„ **KRW**๋กœ ํ‘œ๊ธฐ(์ฃผ๋‹น ๋‚ด์žฌ๊ฐ€์น˜, ์‹œ์ด). ์ฃผ๋‹น๊ฐ€์น˜ ํ•ฉ๋ฆฌ์„ฑ ์ ๊ฒ€๋„ KRW๋กœ. +> - ๋ฏผ๊ฐ๋„ ํ–‰๋ ฌ ํ„ฐ๋ฏธ๋„ ์„ฑ์žฅ๋ฅ  ์ถ•: **1.5% / 2.0% / 2.5%**. +> - ์งง์€ **"์„ธํ›„ ์‹คํ˜„์ˆ˜์ต๋ฅ  ์ฃผ์˜"** ์บก์…˜ ์ถ”๊ฐ€: DCF ์ ์ •๊ฐ€์น˜๋Š” ๊ธฐ์—…์˜ ๋‚ด์žฌ๊ฐ€์น˜(ํˆฌ์ž์ž ์„ธ์ „)๋‹ค. ํˆฌ์ž์ž๊ฐ€ ์ˆ˜์ต์„ *์‹คํ˜„*ํ•  ๋•Œ ์ฆ๊ถŒ๊ฑฐ๋ž˜์„ธ(2026๋…„ ๊ธฐ์ค€ ๋งค๋„๊ธˆ์•ก์˜ ~0.20%: KOSPI 0.05% ๊ฑฐ๋ž˜์„ธ + 0.15% ๋†์–ด์ดŒํŠน๋ณ„์„ธ, KOSDAQ 0.20%)์™€ ๋ฐฐ๋‹น์†Œ๋“์„ธ(๊ฑฐ์ฃผ์ž 15.4% ์›์ฒœ์ง•์ˆ˜, ์™ธ๊ตญ์ธ ~22% ๋˜๋Š” ์กฐ์„ธ์กฐ์•ฝ ์„ธ์œจ)๊ฐ€ ์„ธํ›„ ์‹คํ˜„์ˆ˜์ต์„ ๊นŽ๋Š”๋‹ค. ์ด๋Š” ์œ„์—์„œ ๊ณ„์‚ฐํ•œ ๋‚ด์žฌ๊ฐ€์น˜๋ฅผ **๋ฐ”๊พธ์ง€ ์•Š์œผ๋ฉฐ** ๊ทธ ์œ„์— ์–นํžˆ๋Š” ํˆฌ์ž์ž ์ฐจ์›์˜ ์กฐ์ •์ด๋‹ค. diff --git a/src/skills/dcf/sector-wacc-kr.md b/src/skills/dcf/sector-wacc-kr.md new file mode 100644 index 000000000..464d2003d --- /dev/null +++ b/src/skills/dcf/sector-wacc-kr.md @@ -0,0 +1,51 @@ +# ์„นํ„ฐ๋ณ„ WACC ์กฐ์ • โ€” ํ•œ๊ตญ (KOSPI / KOSDAQ) + +ํ•œ๊ตญ ์ƒ์žฅ์‚ฌ์˜ ์ถœ๋ฐœ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ , ๊ธฐ์—…๋ณ„ ์š”์ธ์œผ๋กœ ์กฐ์ •ํ•˜๋ผ. ์•„๋ž˜ ๋ ˆ์ธ์ง€๋Š” ์ด๋ฏธ ํ•œ๊ตญ ์‹œ์žฅ +์ž…๋ ฅ๊ฐ’ โ€” ~3% ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ(10๋…„ ๊ตญ๊ณ ์ฑ„)์™€ 5~7% ์‹œ์žฅ ์œ„ํ—˜ํ”„๋ฆฌ๋ฏธ์—„ โ€” ์„ ๊ฐ€์ •ํ•˜๋ฏ€๋กœ, ๋ฏธ๊ตญ ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ๋ฅผ +์œ„์— ๋‹ค์‹œ ์–น์ง€ ๋งˆ๋ผ. + +## ์„นํ„ฐ ํŒ๋ณ„ + +`get_financials_kr`๋Š” ํ‘œ์ค€ํ™”๋œ `sector` ํ•„๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๋Š”๋‹ค. ํšŒ์‚ฌ์˜ ์ฃผ๋ ฅ ์‚ฌ์—…(์‚ฌ์—… ์˜์—ญ, +KRX ์—…์ข…๋ถ„๋ฅ˜)์œผ๋กœ ์„นํ„ฐ๋ฅผ ์ถ”๋ก ํ•ด ์•„๋ž˜ ํ‘œ์— ๋งค์นญํ•˜๋ผ. + +## ์„นํ„ฐ๋ณ„ WACC + +| ์„นํ„ฐ | ์ผ๋ฐ˜์  WACC ๋ ˆ์ธ์ง€ | ๋น„๊ณ  | +|------|------------------|------| +| ๋ฐ˜๋„์ฒด | 8-11% | ๋†’์€ ๊ฒฝ๊ธฐ๋ฏผ๊ฐ๋„, ์ž๋ณธ์ง‘์•ฝ์  (์‚ผ์„ฑ์ „์ž, SKํ•˜์ด๋‹‰์Šค) | +| ITยท์ธํ„ฐ๋„ทยท์†Œํ”„ํŠธ์›จ์–ด | 9-12% | ์„ฑ์žฅ ๋‹จ๊ณ„, ์„ฑ์ˆ™๋„ ํ‰๊ฐ€ ํ•„์š” (๋„ค์ด๋ฒ„, ์นด์นด์˜ค) | +| ์ž๋™์ฐจยท๋ถ€ํ’ˆ | 8-10% | ๊ฒฝ๊ธฐ๋ฏผ๊ฐ, ์ˆ˜์ถœ ํ™˜์œจ ๋ฏผ๊ฐ (ํ˜„๋Œ€์ฐจ, ๊ธฐ์•„) | +| 2์ฐจ์ „์ง€ | 9-12% | ์ž๋ณธ์ง‘์•ฝ์  ์„ฑ์žฅ, ์ˆ˜์š” ๋ถˆํ™•์‹ค์„ฑ (LG์—๋„ˆ์ง€์†”๋ฃจ์…˜, ์‚ผ์„ฑSDI) | +| ๋ฐ”์ด์˜คยท์ œ์•ฝ | 10-13% | ํŒŒ์ดํ”„๋ผ์ธยท๊ทœ์ œ ๋ฆฌ์Šคํฌ, ์ ์ž๊ธฐ์—…์€ ๋” ๋†’๊ฒŒ | +| ๊ธˆ์œต | 8-10% | ์‚ฌ์—…๋ชจ๋ธ์— ๋ ˆ๋ฒ„๋ฆฌ์ง€ ๋‚ด์žฌ (์€ํ–‰ยท์ฆ๊ถŒยท๋ณดํ—˜) | +| ํ†ต์‹  | 7-9% | ๋ฐฉ์–ด์ , ๊ทœ์ œ, ๋ฐฐ๋‹น ์ค‘์‹ฌ (SKT, KT) | +| ์œ ํ‹ธ๋ฆฌํ‹ฐ | 6-8% | ๊ทœ์ œ ์š”๊ธˆ, ๊ธˆ๋ฆฌ ๋ฏผ๊ฐ (ํ•œ๊ตญ์ „๋ ฅ) | +| ํ™”ํ•™ยท์†Œ์žฌ | 8-10% | ๊ฒฝ๊ธฐ๋ฏผ๊ฐ, ์›์ž์žฌ ์—ฐ๋™ | +| ์ฒ ๊ฐ•ยท์กฐ์„  | 9-11% | ๊นŠ์€ ๊ฒฝ๊ธฐ์ˆœํ™˜, ๊ธ€๋กœ๋ฒŒ ์ˆ˜์š” ๋…ธ์ถœ | +| ์†Œ๋น„์žฌยท์œ ํ†ต | 8-10% | ๋‚ด์ˆ˜, ํ•„์ˆ˜์†Œ๋น„์žฌ๋Š” ๋‚ฎ๊ฒŒ / ์ž„์˜์†Œ๋น„์žฌ๋Š” ๋†’๊ฒŒ | +| ๊ฑด์„ค | 9-11% | ํ”„๋กœ์ ํŠธยท๋ถ€๋™์‚ฐ ์‚ฌ์ดํด ๋…ธ์ถœ | +| ์ง€์ฃผ์‚ฌ | 9-12% | ์ง€์ฃผ์‚ฌ / ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ ์ ์šฉ (์•„๋ž˜ ์ฐธ์กฐ) | + +## ์กฐ์ • ์ธ์ž + +๊ฐ€์‚ฐ: +- **๊ณ ๋ถ€์ฑ„ (D/E > 1.5)**: +1~2% +- **์†Œํ˜•์ฃผ (์‹œ์ด < 1์กฐ์› / ~$700M)**: +1~2% +- **์ง€๋ฐฐ๊ตฌ์กฐ / ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ** (์žฌ๋ฒŒ ๊ณ„์—ด, ๋ถˆํˆฌ๋ช…ํ•œ ํŠน์ˆ˜๊ด€๊ณ„์ž ๊ฑฐ๋ž˜): +0.5~1.5% +- **์ˆœํ™˜์ถœ์ž ๋…ธ์ถœ**: +0.5~1% +- **์ง€์ฃผ์‚ฌ ๊ตฌ์กฐ (๋”๋ธ”์นด์šดํŒ… ๋ฆฌ์Šคํฌ)**: +1~2% +- **ํŠน์ • ๊ณ ๊ฐ / ๋‹จ์ผ ๋งค์ˆ˜์ž ์˜์กด**: +0.5~1% + +๊ฐ์‚ฐ: +- **ํ•ด์ž๋ฅผ ๊ฐ–์ถ˜ ์‹œ์žฅ ๋ฆฌ๋”**: -0.5~1% +- **๋ฐ˜๋ณตยท๊ตฌ๋… ๋งค์ถœ ๋ชจ๋ธ**: -0.5~1% +- **ํˆฌ์ž๋“ฑ๊ธ‰ ์‹ ์šฉ๋“ฑ๊ธ‰ (KR ๊ธฐ์ค€ AA- ์ด์ƒ)**: -0.5% +- **๋ณ€๋™์„ฑ ๋‚ฎ์€ ๊พธ์ค€ํ•œ ๋ฐฐ๋‹น์ฃผ**: -0.5% + +## ํ•ฉ๋ฆฌ์„ฑ ์ ๊ฒ€ + +- ๊ฐ€์น˜ ์ฐฝ์ถœ ๊ธฐ์—…์€ ๋ณดํ†ต WACC๊ฐ€ ROIC๋ณด๋‹ค 2~4% ๋‚ฎ์•„์•ผ ํ•œ๋‹ค. +- WACC > ROIC์ด๋ฉด ๊ฐ€์น˜๋ฅผ ํ›ผ์†ํ•˜๊ณ  ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค. +- ๊ฐ€๋Šฅํ•˜๋ฉด KOSPI ๋™์ข… ์—…๊ณ„์™€ ๊ต์ฐจ ํ™•์ธํ•˜๋ผ. +- ๋ถ€์ฑ„๋น„์šฉ์€ **~22% ๋ฒ•์ธ์„ธ์œจ**(K-IFRS ์‹คํšจ; ๋ฏธ๊ตญ 30% ์•„๋‹˜) ๊ธฐ์ค€ ์„ธํ›„๋กœ ์ ์šฉํ•œ๋‹ค. diff --git a/src/skills/dcf/sector-wacc.md b/src/skills/dcf/sector-wacc.md index 2be81fbe1..eb9fd5ecb 100644 --- a/src/skills/dcf/sector-wacc.md +++ b/src/skills/dcf/sector-wacc.md @@ -1,43 +1,43 @@ -# Sector WACC Adjustments +# ์„นํ„ฐ๋ณ„ WACC ์กฐ์ • (US) -Use these typical WACC ranges as starting points, then adjust based on company-specific factors. +์•„๋ž˜ ์ผ๋ฐ˜์  WACC ๋ ˆ์ธ์ง€๋ฅผ ์ถœ๋ฐœ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ , ๊ธฐ์—…๋ณ„ ์š”์ธ์— ๋”ฐ๋ผ ์กฐ์ •ํ•˜๋ผ. -## Determining Company Sector +## ํšŒ์‚ฌ ์„นํ„ฐ ํŒ๋ณ„ -Use `get_financials` with query `"[TICKER] company facts"` to retrieve the company's `sector`. Match the returned sector to the table below. +`get_financials`๋ฅผ `"[TICKER] company facts"` ์ฟผ๋ฆฌ๋กœ ํ˜ธ์ถœํ•ด ํšŒ์‚ฌ์˜ `sector`๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค. ๋ฐ˜ํ™˜๋œ ์„นํ„ฐ๋ฅผ ์•„๋ž˜ ํ‘œ์— ๋งค์นญํ•˜๋ผ. -## WACC by Sector +## ์„นํ„ฐ๋ณ„ WACC -| Sector | Typical WACC Range | Notes | -|--------|-------------------|-------| -| Communication Services | 8-10% | Mix of stable telecom and growth media | -| Consumer Discretionary | 8-10% | Cyclical exposure | -| Consumer Staples | 7-8% | Defensive, stable demand | -| Energy | 9-11% | Commodity price exposure | -| Financials | 8-10% | Leverage already in business model | -| Health Care | 8-10% | Regulatory and pipeline risk | -| Industrials | 8-9% | Moderate cyclicality | -| Information Technology | 8-12% | Assess growth stage; higher for high-growth | -| Materials | 8-10% | Cyclical, commodity exposure | -| Real Estate | 7-9% | Interest rate sensitivity | -| Utilities | 6-7% | Regulated, stable cash flows | +| ์„นํ„ฐ | ์ผ๋ฐ˜์  WACC ๋ ˆ์ธ์ง€ | ๋น„๊ณ  | +|------|------------------|------| +| Communication Services | 8-10% | ์•ˆ์ •์  ํ†ต์‹ ๊ณผ ์„ฑ์žฅ ๋ฏธ๋””์–ด ํ˜ผ์žฌ | +| Consumer Discretionary | 8-10% | ๊ฒฝ๊ธฐ ๋ฏผ๊ฐ ๋…ธ์ถœ | +| Consumer Staples | 7-8% | ๋ฐฉ์–ด์ , ์•ˆ์ •์  ์ˆ˜์š” | +| Energy | 9-11% | ์›์ž์žฌ ๊ฐ€๊ฒฉ ๋…ธ์ถœ | +| Financials | 8-10% | ์‚ฌ์—…๋ชจ๋ธ์— ๋ ˆ๋ฒ„๋ฆฌ์ง€ ๋‚ด์žฌ | +| Health Care | 8-10% | ๊ทœ์ œยทํŒŒ์ดํ”„๋ผ์ธ ๋ฆฌ์Šคํฌ | +| Industrials | 8-9% | ์ค‘๊ฐ„ ์ˆ˜์ค€์˜ ๊ฒฝ๊ธฐ์ˆœํ™˜์„ฑ | +| Information Technology | 8-12% | ์„ฑ์žฅ ๋‹จ๊ณ„ ํ‰๊ฐ€; ๊ณ ์„ฑ์žฅ์€ ๋” ๋†’๊ฒŒ | +| Materials | 8-10% | ๊ฒฝ๊ธฐ์ˆœํ™˜, ์›์ž์žฌ ๋…ธ์ถœ | +| Real Estate | 7-9% | ๊ธˆ๋ฆฌ ๋ฏผ๊ฐ | +| Utilities | 6-7% | ๊ทœ์ œ, ์•ˆ์ •์  ํ˜„๊ธˆํ๋ฆ„ | -## Adjustment Factors +## ์กฐ์ • ์ธ์ž -Add to base WACC: -- **High debt (D/E > 1.5)**: +1-2% -- **Small cap (< $2B market cap)**: +1-2% -- **Emerging markets exposure**: +1-3% -- **Concentrated customer base**: +0.5-1% -- **Regulatory uncertainty**: +0.5-1.5% +๊ธฐ์ค€ WACC์— ๊ฐ€์‚ฐ: +- **๊ณ ๋ถ€์ฑ„ (D/E > 1.5)**: +1~2% +- **์†Œํ˜•์ฃผ (์‹œ์ด < $2B)**: +1~2% +- **์‹ ํฅ์‹œ์žฅ ๋…ธ์ถœ**: +1~3% +- **๊ณ ๊ฐ ๊ธฐ๋ฐ˜ ์ง‘์ค‘**: +0.5~1% +- **๊ทœ์ œ ๋ถˆํ™•์‹ค์„ฑ**: +0.5~1.5% -Subtract from base WACC: -- **Market leader with moat**: -0.5-1% -- **Recurring revenue model**: -0.5-1% -- **Investment grade credit rating**: -0.5% +๊ธฐ์ค€ WACC์—์„œ ๊ฐ์‚ฐ: +- **ํ•ด์ž๋ฅผ ๊ฐ–์ถ˜ ์‹œ์žฅ ๋ฆฌ๋”**: -0.5~1% +- **๋ฐ˜๋ณต ๋งค์ถœ ๋ชจ๋ธ**: -0.5~1% +- **ํˆฌ์ž๋“ฑ๊ธ‰ ์‹ ์šฉ๋“ฑ๊ธ‰**: -0.5% -## Reasonableness Checks +## ํ•ฉ๋ฆฌ์„ฑ ์ ๊ฒ€ -- WACC should typically be 2-4% below ROIC for value-creating companies -- If calculated WACC > ROIC, the company may be destroying value -- Compare to sector peers if available +- ๊ฐ€์น˜ ์ฐฝ์ถœ ๊ธฐ์—…์€ ๋ณดํ†ต WACC๊ฐ€ ROIC๋ณด๋‹ค 2~4% ๋‚ฎ์•„์•ผ ํ•œ๋‹ค +- ๊ณ„์‚ฐ๋œ WACC > ROIC์ด๋ฉด ๊ฐ€์น˜๋ฅผ ํ›ผ์†ํ•˜๊ณ  ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค +- ๊ฐ€๋Šฅํ•˜๋ฉด ์„นํ„ฐ ๋™์ข… ์—…๊ณ„์™€ ๋น„๊ตํ•˜๋ผ diff --git a/src/skills/kr-spinoff/SKILL.md b/src/skills/kr-spinoff/SKILL.md new file mode 100644 index 000000000..a401801ba --- /dev/null +++ b/src/skills/kr-spinoff/SKILL.md @@ -0,0 +1,89 @@ +--- +name: kr-spinoff-analysis +description: ํ•œ๊ตญ ์ƒ์žฅ์‚ฌ์˜ ๋ฌผ์ ๋ถ„ํ• ยท์ธ์ ๋ถ„ํ•  ์ด๋ฒคํŠธ๋ฅผ ๋ถ„์„ํ•˜๊ณ , ๊ธฐ์กด ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ ๊ด€์ ์˜ ๊ฐ€์น˜ ์˜ํ–ฅ(์ง€์ฃผ์‚ฌ ๋””์Šค์นด์šดํŠธ, ๋”๋ธ”์นด์šดํŒ…, ํ•ต์‹ฌ ์„ฑ์žฅ์‚ฌ์—… ์ดํƒˆ, ์ชผ๊ฐœ๊ธฐ ์ƒ์žฅ์— ๋”ฐ๋ฅธ ํฌ์„)์„ ํ‰๊ฐ€ํ•œ๋‹ค. 6์ž๋ฆฌ ํ•œ๊ตญ ํ‹ฐ์ปค๋‚˜ ํ•œ๊ตญ ํšŒ์‚ฌ๋ช…์ด ๋ฌผ์ ๋ถ„ํ• ยท์ธ์ ๋ถ„ํ• ยท๋ถ„ํ• ยท์žํšŒ์‚ฌ ์ƒ์žฅยท์ชผ๊ฐœ๊ธฐ ์ƒ์žฅยทspin-off ๋˜๋Š” "๋ถ„ํ• ์ด ์ฃผ์ฃผ๊ฐ€์น˜์— ์–ด๋• ๋‚˜" ๋“ฑ๊ณผ ํ•จ๊ป˜ ์–ธ๊ธ‰๋  ๋•Œ ํŠธ๋ฆฌ๊ฑฐ. +--- + +# ํ•œ๊ตญ ๊ธฐ์—…๋ถ„ํ• (๋ฌผ์ ๋ถ„ํ•  / ์ธ์ ๋ถ„ํ• ) ๋ถ„์„ ์Šคํ‚ฌ + +์ด ์Šคํ‚ฌ์˜ ๋ชฉํ‘œ๋Š” ๊ณต์‹œ๋ฅผ ๋‚˜์—ดํ•˜๋Š” ๊ฒƒ์ด **์•„๋‹ˆ๋‹ค**. *์ด ๋ถ„ํ• ์ด **๊ธฐ์กด ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ**์—๊ฒŒ ๋“์ด์—ˆ๋Š”์ง€ +์‹ค์ด์—ˆ๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ์™œ ๊ทธ๋Ÿฐ์ง€*์— ๋‹ตํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๊ฒฐ๋ก (ํ‰๊ฐ€)์„ ๋จผ์ € ์ œ์‹œํ•˜๋ผ. + +## ๋ฐฐ๊ฒฝ โ€” ๋‘ ๊ฐ€์ง€ ๋ถ„ํ•  ์œ ํ˜• (์–ด๋А ์ชฝ์ธ์ง€ ๋จผ์ € ํŒ๋‹จ) + +- **๋ฌผ์ ๋ถ„ํ• **: ๋ชจํšŒ์‚ฌ๊ฐ€ ์‚ฌ์—…๋ถ€๋ฅผ **100% ์žํšŒ์‚ฌ**๋กœ ์‹ ์„คํ•œ๋‹ค. ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ๋Š” ๊ทธ ์žํšŒ์‚ฌ๋ฅผ + *๊ฐ„์ ‘์ ์œผ๋กœ๋งŒ* ์†Œ์œ ํ•œ๋‹ค. ๋Œ€ํ‘œ์  ๋ฆฌ์Šคํฌ๋Š” **์ชผ๊ฐœ๊ธฐ ์ƒ์žฅ** โ€” ์ดํ›„ ์žํšŒ์‚ฌ๊ฐ€ IPOํ•˜๋ฉด์„œ ์™ธ๋ถ€ + ํˆฌ์ž์ž์—๊ฒŒ ์‹ ์ฃผ๋ฅผ ํŒ”์•„ ์ž๋ณธ์„ ์กฐ๋‹ฌํ•˜๊ณ , ์ด๋Š” ๋ชจํšŒ์‚ฌ์˜ ์ง€๋ถ„์„ **ํฌ์„**์‹œํ‚ค๋ฉฐ ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ๋ฅผ + **์ง€์ฃผ์‚ฌ ๋””์Šค์นด์šดํŠธ**์™€ **๋”๋ธ”์นด์šดํŒ…**(๊ฐ™์€ ์‚ฌ์—…์ด ๋‘ ๋ฒˆ ํ‰๊ฐ€๋˜์ง€๋งŒ ๋ชจํšŒ์‚ฌ๋Š” ๋ถ€๋ถ„ํ•ฉ ๋Œ€๋น„ ํ• ์ธ + ๊ฑฐ๋ž˜)์— ๋…ธ์ถœ์‹œํ‚จ๋‹ค. ๊ธฐ์กด ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ์—๊ฒŒ ์ผ๋ฐ˜์ ์œผ๋กœ **๋ถ€์ •์ **. +- **์ธ์ ๋ถ„ํ• **: ์‹ ์„ค ๋ฒ•์ธ์˜ ์ฃผ์‹์ด ๊ธฐ์กด ์ฃผ์ฃผ์—๊ฒŒ **์ง€๋ถ„์œจ๋Œ€๋กœ** ๋ฐฐ๋ถ„๋œ๋‹ค. ์ฃผ์ฃผ๊ฐ€ ์–‘์ชฝ์„ ์ง์ ‘ + ์†Œ์œ ํ•˜๋ฏ€๋กœ ๋ถ„ํ•  ์‹œ์ ์—๋Š” ๋Œ€์ฒด๋กœ **๊ฐ€์น˜ ์ค‘๋ฆฝ์ **์ด๋‹ค(์ดํ›„ ๋ฆฌ๋ ˆ์ดํŒ…์œผ๋กœ ๊ฐ€์น˜๊ฐ€ ์›€์ง์ผ ์ˆ˜๋Š” ์žˆ์Œ). + +๋Œ€ํ‘œ์  ๊ฒฝ๊ณ„ ์‚ฌ๋ก€๋Š” **LGํ™”ํ•™ โ†’ LG์—๋„ˆ์ง€์†”๋ฃจ์…˜**(2020๋…„ ๋ฌผ์ ๋ถ„ํ• , 2022๋…„ IPO): LGํ™”ํ•™ ์ฃผ์ฃผ๋Š” +๋ฐฐํ„ฐ๋ฆฌ ์‚ฌ์—…์— ๋Œ€ํ•œ ๊ฐ„์ ‘ ์ง€๋ถ„์ด ํฌ์„๋๊ณ , LGํ™”ํ•™์€ ์ง€์ฃผ์‚ฌํ˜• ๋””์Šค์นด์šดํŠธ๋กœ ๋ฆฌ๋ ˆ์ดํŒ…๋๋‹ค. ์ด๋ฅผ ๊ธฐ์ค€ +ํŒจํ„ด์œผ๋กœ ์‚ผ์•„๋ผ. + +## ์›Œํฌํ”Œ๋กœ ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +``` +KR ๋ถ„ํ•  ๋ถ„์„ ์ง„ํ–‰: +- [ ] Step 1: ๋Œ€์ƒ ์ข…๋ชฉ ํ™•์ธ ๋ฐ ๋ถ„ํ•  ์ด๋ฒคํŠธ ํƒ์ƒ‰ +- [ ] Step 2: ๋ถ„ํ•  ์œ ํ˜• ๋ถ„๋ฅ˜(๋ฌผ์  vs ์ธ์ ) ๋ฐ ํƒ€์ž„๋ผ์ธ ๊ตฌ์„ฑ +- [ ] Step 3: ๋ถ„ํ•  ํ›„ ์ง€๋ถ„ / ์ง€๋ฐฐ๊ตฌ์กฐ ๋งคํ•‘ +- [ ] Step 4: ๊ธฐ์กด ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ์— ๋Œ€ํ•œ ๊ฐ€์น˜ ์˜ํ–ฅ ํ‰๊ฐ€ +- [ ] Step 5: (์„ ํƒ) ์žฌ๋ฌด๋กœ ์ •๋Ÿ‰ํ™” +- [ ] Step 6: ํ‰๊ฐ€ + ์ฃผ์˜์‚ฌํ•ญ ์ œ์‹œ +``` + +## Step 1: ๋ถ„ํ•  ์ด๋ฒคํŠธ ํƒ์ƒ‰ + +๋Œ€์ƒ ํ‹ฐ์ปค๋กœ `get_filings_kr`๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. ๋ถ„ํ•  ๊ณต์‹œ๋Š” **์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ**๋กœ ์ œ์ถœ๋˜๋ฏ€๋กœ +`filing_type: "material"`์„ ์‚ฌ์šฉํ•œ๋‹ค. ๋ถ„ํ• ์€ ์ˆ˜๋…„ ์ „ ์ผ์ธ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์œผ๋ฏ€๋กœ `start_date`๋กœ ๊ธฐ๊ฐ„์„ +๋„“๊ฒŒ(์˜ˆ: 5~10๋…„ ์ „) ์ง€์ •ํ•œ๋‹ค โ€” ๊ธฐ๋ณธ ๋ฒ”์œ„๋Š” ~1๋…„๋ฟ์ด๋‹ค. + +`report_nm`์—์„œ ๋‹ค์Œ์„ ์Šค์บ”ํ•œ๋‹ค: `๋ถ„ํ• `, `๋ฌผ์ ๋ถ„ํ• `, `์ธ์ ๋ถ„ํ• `, `ํšŒ์‚ฌ๋ถ„ํ• `, `๋ถ„ํ• ํ•ฉ๋ณ‘`, ๊ทธ๋ฆฌ๊ณ  +์žํšŒ์‚ฌ IPO(`์ฆ๊ถŒ์‹ ๊ณ ์„œ` / `๋ฐœํ–‰๊ณต์‹œ`, ์ชผ๊ฐœ๊ธฐ ์ƒ์žฅ์ด ์˜์‹ฌ๋˜๋ฉด `filing_type: "issuance"`). ์ ‘์ˆ˜์ผ +(`rcept_dt`)๊ณผ ์‹ ์„ค ๋ฒ•์ธ๋ช…์„ ๊ธฐ๋กํ•œ๋‹ค. + +์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฏธ ์ด๋ฒคํŠธ๋ฅผ ์ง€๋ชฉํ–ˆ๋‹ค๋ฉด(์˜ˆ: "LGํ™”ํ•™ ๋ฌผ์ ๋ถ„ํ• ") ๋งน๋ชฉ์ ์œผ๋กœ ๊ฒ€์ƒ‰ํ•˜์ง€ ๋ง๊ณ  ๊ณต์‹œ์—์„œ +ํ™•์ธํ•˜๊ณ , ๊ณต์‹œ ๊ธฐ๋ก์ด ๋ถ€์กฑํ•˜๋ฉด ์ด์‚ฌํšŒ ๊ฒฐ์˜ / ์Šน์ธ์ผ์„ `web_search`๋กœ ๋ณด์™„ํ•œ๋‹ค. + +## Step 2: ๋ถ„๋ฅ˜ ๋ฐ ํƒ€์ž„๋ผ์ธ ๊ตฌ์„ฑ + +**๋ฌผ์ ๋ถ„ํ• **์ธ์ง€ **์ธ์ ๋ถ„ํ• **์ธ์ง€ ๋ช…์‹œํ•˜๋ผ โ€” ์ด๊ฒƒ์ด ์ „์ฒด ํ‰๊ฐ€๋ฅผ ์ขŒ์šฐํ•œ๋‹ค. ์งง์€ ํƒ€์ž„๋ผ์ธ์„ +๊ตฌ์„ฑํ•œ๋‹ค: ์ด์‚ฌํšŒ ๊ฒฐ์˜ โ†’ ์ฃผ์ฃผ์ดํšŒ(๋ถ„ํ•  ์Šน์ธ) โ†’ ๋ถ„ํ• ๊ธฐ์ผ(ํšจ๋ ฅ ๋ฐœ์ƒ์ผ) โ†’ ์žํšŒ์‚ฌ IPO์ผ(์žˆ๋Š” ๊ฒฝ์šฐ). + +## Step 3: ๋ถ„ํ•  ํ›„ ์ง€๋ถ„๊ตฌ์กฐ ๋งคํ•‘ (๋ณด์กฐ) + +๋ชจํšŒ์‚ฌ์— ๋Œ€ํ•ด `get_large_holders_kr`(5%๋ฃฐ ๋Œ€๋Ÿ‰๋ณด์œ )์™€ `get_insider_trades_kr`(์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ)๋ฅผ +์‚ฌ์šฉํ•ด ๋ชจํšŒ์‚ฌ์˜ ์‹ ์„ค ๋ฒ•์ธ ์ง€๋ถ„๊ณผ ๊ทธ ์œ„์˜ ์ง€๋ฐฐ๊ฐ€๋ฌธ / ์ง€์ฃผ์‚ฌ ์ฒด์ธ์„ ํ™•์ธํ•œ๋‹ค. ๋” ๋„“์€ ๊ทธ๋ฃน ๋งฅ๋ฝ +(์žฌ๋ฒŒ ๊ทธ๋ฃน ๊ตฌ์กฐ, ์ง€์ฃผ์‚ฌ ์œ„์น˜)์€ `web_search`๋ฅผ ์“ด๋‹ค โ€” ๊ทธ๋ฃน ์ „์ฒด ๋งคํ•‘์€ ๋ฒ”์œ„ ๋ฐ–์ด๋ฉฐ, ๊ฐ€์น˜ ์˜ํ–ฅ +์„ค๋ช…์— ํ•„์š”ํ•œ ๋งŒํผ๋งŒ ๊ฐ€์ ธ์˜จ๋‹ค. + +## Step 4: ๊ธฐ์กด ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ์— ๋Œ€ํ•œ ๊ฐ€์น˜ ์˜ํ–ฅ ํ‰๊ฐ€ + +ํ•ต์‹ฌ์ด๋‹ค. **๋ฌผ์ ๋ถ„ํ•  + IPO** ํŒจํ„ด์ด๋ฉด ๋‹ค์Œ์„ ํ‰๊ฐ€ํ•œ๋‹ค: +- **๊ฐ„์ ‘ ์ง€๋ถ„ ํฌ์„**: IPO์—์„œ ๋ชจํšŒ์‚ฌ๊ฐ€ ์žํšŒ์‚ฌ ์ง€๋ถ„์„ ์–ผ๋งˆ๋‚˜ ๋‚ด์คฌ๋‚˜? +- **์ง€์ฃผ์‚ฌ / ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ**: ๋ชจํšŒ์‚ฌ๊ฐ€ ์ด์ œ ๋ถ€๋ถ„ํ•ฉ(sum-of-parts) ์ดํ•˜๋กœ ๊ฑฐ๋ž˜๋˜๋‚˜? +- **๋”๋ธ”์นด์šดํŒ…**: ํ•ต์‹ฌ ์‚ฌ์—…์ด ์ƒ์žฅ ์žํšŒ์‚ฌ์™€ ๋ชจํšŒ์‚ฌ ์–‘์ชฝ์—์„œ ํ‰๊ฐ€๋˜๊ณ  ์žˆ๋‚˜? +- **์„ฑ์žฅ ์—”์ง„ ์ƒ์‹ค**: ๋ชจํšŒ์‚ฌ์˜ ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ์„ฑ์žฅํ•˜๋˜ ์‚ฌ์—…๋ถ€๊ฐ€ ์ง์ ‘ ๋ณด์œ  ๋ฒ•์ธ์—์„œ ๋น ์ ธ๋‚˜๊ฐ”๋‚˜? +- **IPO ์กฐ๋‹ฌ์ž๊ธˆ ์šฉ๋„**: ์ž๊ธˆ์ด ์žํšŒ์‚ฌ๋กœ ๊ฐ€๋‚˜(๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ๋Š” ์ง์ ‘ ์ˆ˜ํ˜œ ์—†์Œ), ๋ชจํšŒ์‚ฌ๋กœ ๊ฐ€๋‚˜? + +**์ธ์ ๋ถ„ํ• **์ด๋ฉด ๋ถ„ํ•  ์‹œ์ ์—๋Š” ๋Œ€์ฒด๋กœ ์ค‘๋ฆฝ์ž„์„ ๋ฐํžˆ๊ณ , ์ดํ›„ ๋ฆฌ๋ ˆ์ดํŒ…์„ ์‚ดํ•€๋‹ค. + +## Step 5: (์„ ํƒ) ์žฌ๋ฌด๋กœ ์ •๋Ÿ‰ํ™” + +`get_financials_kr`๋กœ ๋ชจํšŒ์‚ฌ์˜ ์—ฐ๊ฒฐ vs ๋ณ„๋„ ์ˆ˜์น˜๋ฅผ, ๊ฐ€๋Šฅํ•˜๋ฉด ๋ถ„ํ•  ์ „ํ›„ ์„ธ๊ทธ๋จผํŠธ ๊ธฐ์—ฌ๋„๋ฅผ ๋น„๊ตํ•ด +โ€” ์ง์ ‘ ๋ณด์œ  ๋ฒ•์ธ์—์„œ ์–ผ๋งˆ๋‚˜ ๋งŽ์€ ์ด์ต ์ฐฝ์ถœ๋ ฅ์ด ๋น ์ ธ๋‚˜๊ฐ”๋Š”์ง€ ๋ณด์—ฌ์ค€๋‹ค. + +> DART ๊ณ„์ • ๋ผ๋ฒจ(`account_nm`)์€ ํšŒ์‚ฌ / ์—ฐ๋„๋งˆ๋‹ค ๋‹ค๋ฅด๋‹ค โ€” ๋ถ€๋ถ„ ๋ฌธ์ž์—ด / `account_id`๋กœ ๋งค์นญํ•˜๊ณ  +> ์ •ํ™• ์ผ์น˜๋Š” ์ ˆ๋Œ€ ์“ฐ์ง€ ๋งˆ๋ผ. + +## Step 6: ์ถœ๋ ฅ ํ˜•์‹ + +๋‹ค์Œ์„ ์ œ์‹œํ•œ๋‹ค: +1. **ํ‰๊ฐ€**(1~2๋ฌธ์žฅ): ๊ธฐ์กด ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ์—๊ฒŒ ์ˆœ๋“ / ์ค‘๋ฆฝ / ์ˆœ์‹ค, ๊ทธ๋ฆฌ๊ณ  ๊ฐ€์žฅ ํฐ ์ด์œ  ํ•˜๋‚˜. +2. **์ด๋ฒคํŠธ ์š”์•ฝ**: ๋ถ„ํ•  ์œ ํ˜•, ํƒ€์ž„๋ผ์ธ, ๊ด€๋ จ ๋ฒ•์ธ. +3. **์ง€๋ถ„ ์˜ํ–ฅ**: ํฌ์„๊ณผ ์ง€๋ฐฐ์ฒด์ธ ํšจ๊ณผ(๊ฐ€๋Šฅํ•˜๋ฉด ์ˆ˜์น˜ ํฌํ•จ). +4. **๊ฐ€์น˜ ์˜ํ–ฅ**: Step 4์˜ ๋””์Šค์นด์šดํŠธ / ๋”๋ธ”์นด์šดํŒ… / ์„ฑ์žฅ์ƒ์‹ค ๋ถ„์„. +5. **์ฃผ์˜์‚ฌํ•ญ**: ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ๋Š” ๋ถ€๋ถ„์ ์œผ๋กœ ๊ตฌ์กฐ์ ์ด๋ฉฐ, ์†Œ์ˆ˜์ฃผ์ฃผ ๋ณดํ˜ธ ๊ทœ์ •์ด ๊ฐ•ํ™”๋ผ ์™”๋‹ค + (์˜ˆ: 2022๋…„ ์ดํ›„ ์ชผ๊ฐœ๊ธฐ ์ƒ์žฅ์— ๋Œ€ํ•œ ๊ทœ์ œ ๋ฐ˜๋ฐœ) โ€” ํƒ€์ด๋ฐ๊ณผ ๊ด€๋ จ ์žˆ์œผ๋ฉด ์–ธ๊ธ‰. diff --git a/src/skills/registry.test.ts b/src/skills/registry.test.ts new file mode 100644 index 000000000..a8a90e1a4 --- /dev/null +++ b/src/skills/registry.test.ts @@ -0,0 +1,34 @@ +import { describe, it, expect, afterEach } from 'bun:test'; +import { discoverSkills, getSkill, clearSkillCache } from './registry.js'; + +afterEach(() => { + clearSkillCache(); +}); + +describe('discoverSkills', () => { + it('discovers all builtin skills with valid frontmatter', () => { + const names = discoverSkills().map((s) => s.name); + for (const expected of ['dcf-valuation', 'write-memo', 'x-research', 'kr-spinoff-analysis']) { + expect(names).toContain(expected); + } + }); + + it('gives every discovered skill a non-empty description', () => { + for (const skill of discoverSkills()) { + expect(skill.description.length).toBeGreaterThan(0); + } + }); +}); + +describe('getSkill', () => { + it('loads the KR split skill instructions', () => { + const skill = getSkill('kr-spinoff-analysis'); + expect(skill).toBeDefined(); + expect(skill?.instructions).toContain('๋ฌผ์ ๋ถ„ํ• '); + }); + + it('references the KR sector WACC file from the dcf skill', () => { + const skill = getSkill('dcf-valuation'); + expect(skill?.instructions).toContain('sector-wacc-kr.md'); + }); +}); From 5ed1495bd01d34548beedb84b6fafff98e5c3623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SH=20KWON=20=F0=9F=87=B0=F0=9F=87=B7?= Date: Sun, 31 May 2026 04:42:26 +0900 Subject: [PATCH 07/70] KR financials: normalized summary-first + cross-signal research playbook (#7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * KR financials: normalized summary-first + cross-signal research playbook Make Korean stock research produce a differentiated, data-grounded answer instead of a generic chatbot-style summary. - get_financials_kr: return a normalized per-period summary (revenue, operating profit, net income, EPS, assets/liabilities/equity, cash flow, capex + margins, ROE, FCF, YoY) in KRW instead of an 80-100KB raw DART dump. Raw line items are persisted to a pretty-printed file (rawLineItemsFile) for drill-down. Fixes the model failing to surface earnings: the old single-line payload blew the size cap and read_file could not page it, so revenue/OP/NI never reached the answer. Match by stable account_id then exact account_nm (IS+CIS for single-statement filers); no substring matching (avoids ์ˆœ์˜์—…์ด์ต -> ์˜์—…์ด์ต contamination). - System prompt: add a Korean research playbook -- gather broadly for open-ended questions, synthesize foreign flows / shorts / holders / filings into ONE thesis, treat ๋Œ€๋Ÿ‰๋ณด์œ  as a valuation modifier. Scope the broad sweep so narrow / skill-driven queries (DCF, ๋ฌผ์ ๋ถ„ํ• ) gather only what they need. - registry: guard web_search providers against `your-` placeholder keys (a copied env.example was registering a web_search tool that 401'd on every call). - DCF / kr-spinoff skills: read the new normalized summary fields. - README: document the normalized summary + cross-signal synthesis; correct the now-stale benchmark row (์‚ผ์„ฑ ์†์ต ๋ณด๋ฅ˜ -> reliable extraction). - Add normalize-financials unit tests and scripts/kr-eval.ts (a probe runner). Verified: typecheck + 113 tests; live before/after on ์‚ผ์„ฑ์ „์ž, SKํ•˜์ด๋‹‰์Šค, KB๊ธˆ์œต (bank/holdco fallback), ๋„ค์ด๋ฒ„ DCF, LGํ™”ํ•™ ๋ฌผ์ ๋ถ„ํ• . Co-Authored-By: Claude Opus 4.8 (1M context) * Address review: bound raw-file cache, harden prune, DRY DART gate Follow-up to the summary-first change, applying code-review findings. - get-business-report: prune kr-financials-*.json dumps to the newest RAW_FILE_KEEP (was unbounded across sessions). Harden the cleanup โ€” sanitize corp_code in the path (only externally-sourced component), never delete the file just written (keepName guard), and tolerate files vanishing under concurrent calls (per-file + per-unlink try/catch). The agent's call_*.txt persists are out of scope by prefix filter. - prompts: gate the KR research section with checkApiKeyExists('DART_API_KEY') instead of an inline process.env check โ€” DRY, also honors .env, and treats a whitespace-only key as absent. - kr-eval: maxIterations 12 -> 10 so probe runs match app behavior. - normalize: document ROE basis (total net income / total equity, incl. NCI). - tests: prune safety invariants (preserves call_*.txt + the current file, bounded count, empty-dir no-op) and quarterly YoY-null when prior is absent. typecheck + 116 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) * docs: correct stale CLAUDE.md note on final-answer flow CLAUDE.md claimed the final answer is "a separate LLM call with no tools bound". The agent loop has no such pass โ€” tools stay bound on every call and the answer is just the text from the turn where the model stops emitting tool calls (handleDirectResponse). Clarify that answer quality is governed entirely by the system prompt. Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- CLAUDE.md | 2 +- README.md | 10 +- scripts/kr-eval.ts | 62 ++++ src/agent/prompts.ts | 48 +++ src/skills/dcf/SKILL.md | 9 +- src/skills/kr-spinoff/SKILL.md | 10 +- .../finance-kr/get-business-report.test.ts | 51 +++ src/tools/finance-kr/get-financials-kr.ts | 2 +- .../finance-kr/normalize-financials.test.ts | 129 +++++++ src/tools/finance-kr/normalize-financials.ts | 343 ++++++++++++++++++ .../sub-tools/get-business-report.ts | 96 ++++- src/tools/registry.ts | 12 +- 12 files changed, 755 insertions(+), 19 deletions(-) create mode 100644 scripts/kr-eval.ts create mode 100644 src/tools/finance-kr/get-business-report.test.ts create mode 100644 src/tools/finance-kr/normalize-financials.test.ts create mode 100644 src/tools/finance-kr/normalize-financials.ts diff --git a/CLAUDE.md b/CLAUDE.md index af00696d8..d8f2f244b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,7 +36,7 @@ These bite without warning. Fix once you learn them. ## Key contracts -- **Agent loop**: `src/agent/agent.ts`, default `maxIterations: 10`. Scratchpad (`scratchpad.ts`) is single source of truth for tool results within a query. Final answer is a separate LLM call **with no tools bound**. +- **Agent loop**: `src/agent/agent.ts`, default `maxIterations: 10`. Scratchpad (`scratchpad.ts`) is single source of truth for tool results within a query. The final answer is the text from the turn where the model stops emitting tool calls (`handleDirectResponse`) โ€” tools stay bound on every call; there is **no separate no-tools finalization pass**. Answer quality is therefore governed entirely by the system prompt. - **Slash commands**: append to `SLASH_COMMANDS` in `src/commands/index.ts` + add a case in `handleSlashCommand` in `src/cli.ts`. Help text lives next to that switch. - **Skills**: drop a directory with `SKILL.md` under `src/skills/`. Auto-discovered; no code change needed. Each skill runs at most once per query. - **Anthropic provider**: uses explicit `cache_control` on system prompt for prompt caching. Don't break that path. diff --git a/README.md b/README.md index 4929d3b5f..d36302bc4 100644 --- a/README.md +++ b/README.md @@ -67,13 +67,14 @@ LLM์ด "์‚ผ์„ฑ์ „์ž" ๊ฐ™์€ ์ž์—ฐ์–ด ์ž…๋ ฅ์„ ๋ฐ›์œผ๋ฉด, ํˆด ๋‚ด๋ถ€์—์„œ ** ### ํ•œ๊ตญ ์‹œ์žฅ ๊ณ ์œ  ์ฒ˜๋ฆฌ -- **K-IFRS ์žฌ๋ฌด์ œํ‘œ** โ€” ์—ฐ๊ฒฐ/๋ณ„๋„ ๋‘˜ ๋‹ค ์กฐํšŒ. ์˜์—…์ด์ต ์ •์˜๊ฐ€ ๋ฏธ๊ตญ GAAP์™€ ๋ฏธ๋ฌ˜ํ•˜๊ฒŒ ๋‹ค๋ฆ„ +- **K-IFRS ์žฌ๋ฌด์ œํ‘œ** โ€” ์—ฐ๊ฒฐ/๋ณ„๋„ ๋‘˜ ๋‹ค ์กฐํšŒ, **๋งค์ถœยท์˜์—…์ด์ตยท์ˆœ์ด์ตยท๋งˆ์ง„ยทROEยทFCFยทYoY๋กœ ์ •๊ทœํ™”ํ•œ ์š”์•ฝ**์„ ๋ฐ˜ํ™˜(์›์‹œ ๋ผ์ธ์•„์ดํ…œ์€ ํŒŒ์ผ๋กœ ๋ณด์กดํ•ด ํ•„์š” ์‹œ ๋“œ๋ฆด๋‹ค์šด). ์˜์—…์ด์ต ์ •์˜๊ฐ€ ๋ฏธ๊ตญ GAAP์™€ ๋ฏธ๋ฌ˜ํ•˜๊ฒŒ ๋‹ค๋ฆ„ - **๋‹จ์œ„** โ€” ์›(KRW), ๋ฐฑ๋งŒ์›/์–ต/์กฐ ๋‹จ์œ„๋กœ ์ž๋™ ํฌ๋งทํŒ… - **๊ฑฐ๋ž˜์‹œ๊ฐ„** โ€” 09:00โ€“15:30 KST ๊ธฐ์ค€ - **์ƒํ•˜ํ•œ๊ฐ€** โ€” ยฑ30% ์ผ๊ฐ„ ๋ณ€๋™ ์ œํ•œ ๊ณ ๋ ค - **DCF ์ž๋™ ๋ถ„๊ธฐ** โ€” 6์ž๋ฆฌ ํ‹ฐ์ปค๋ฉด DCF ์Šคํ‚ฌ์ด ํ•œ๊ตญ ๊ฒฝ๋กœ๋กœ ์ „ํ™˜(ํ•œ๊ตญ ๋ฒ•์ธ์„ธ์œจยท๊ตญ๊ณ ์ฑ„ ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌยทKRW). ๊ฑฐ๋ž˜์„ธยท๋ฐฐ๋‹น์„ธ๋Š” ํˆฌ์ž์ž ์„ธํ›„ ์‹คํ˜„์ˆ˜์ต๋ฅ  ์ฃผ์˜์‚ฌํ•ญ์œผ๋กœ ํ‘œ๊ธฐ - **์žฌ๋ฒŒ ๊ทธ๋ฃน ๊ตฌ์กฐ** โ€” ์‚ผ์„ฑยทํ˜„๋Œ€์ฐจยทSKยทLG ๋“ฑ ๊ทธ๋ฃน ๋‚ด ์ง€๋ถ„ ๊ด€๊ณ„ ๋ถ„์„ ๊ฐ€๋Šฅ - **๋ฌผ์ ๋ถ„ํ•  ๋ถ„์„** โ€” LGํ™”ํ•™โ†’LG์—๋„ˆ์ง€์†”๋ฃจ์…˜ ๊ฐ™์€ ๋ถ„ํ•  ์ด๋ฒคํŠธ๋ฅผ ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ ๊ด€์ (ํฌ์„ยท์ง€์ฃผ์‚ฌ ๋””์Šค์นด์šดํŠธ)์—์„œ ํ‰๊ฐ€ํ•˜๋Š” ์ „์šฉ ์Šคํ‚ฌ +- **๊ต์ฐจ์‹ ํ˜ธ ์ข…ํ•ฉ** โ€” ์žฌ๋ฌด(์ •๊ทœํ™” ์†์ต)ยท์™ธ๊ตญ์ธ ์ˆ˜๊ธ‰ยท๊ณต๋งค๋„ยท๋Œ€๋Ÿ‰๋ณด์œ ยท๊ณต์‹œ๋ฅผ ๋‹จ์ˆœ ๋‚˜์—ด์ด ์•„๋‹ˆ๋ผ **ํ•˜๋‚˜์˜ ํˆฌ์ž thesis๋กœ ์—ฎ์–ด** ์ œ์‹œ(์ˆ˜๊ธ‰ยท์‹ค์ ยท์ง€๋ฐฐ๊ตฌ์กฐ๊ฐ€ ๊ฐ™์€ ๋ฐฉํ–ฅ์ธ์ง€ ์ถฉ๋Œ์ธ์ง€ ํŒ๋‹จ, ๋Œ€๋Ÿ‰๋ณด์œ  ์ง‘์ค‘์€ ๋ฐธ๋ฅ˜์—์ด์…˜ ์š”์ธ์œผ๋กœ ์ฒ˜๋ฆฌ) ## ๐Ÿ’ก ์‚ฌ์šฉ ์˜ˆ์‹œ @@ -140,17 +141,18 @@ LLM์ด "์‚ผ์„ฑ์ „์ž" ๊ฐ™์€ ์ž์—ฐ์–ด ์ž…๋ ฅ์„ ๋ฐ›์œผ๋ฉด, ํˆด ๋‚ด๋ถ€์—์„œ ** ## ๐Ÿ†š ์›น ์ฑ—๋ด‡๊ณผ ๋น„๊ต -2026๋…„ 5์›”, ๋™์ผํ•œ ์งˆ๋ฌธ 5๊ฐœ๋ฅผ **Dexter(LLM: GPT-5.5)** ์™€ **claude.ai(Opus 4.8, ์›น๊ฒ€์ƒ‰ ์‚ฌ์šฉ)** ์— ๋˜์ ธ ์‹ค์ธก ๋น„๊ตํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค ํ”„๋Ÿฐํ‹ฐ์–ด ๋ชจ๋ธ์ด๋ฏ€๋กœ ์ฐจ์ด๋ฅผ ๋งŒ๋“œ๋Š” ๊ฑด ๋ชจ๋ธ์ด ์•„๋‹ˆ๋ผ **๊ธˆ์œต ํ•˜๋„ค์Šค โ€” 1์ฐจ ์ถœ์ฒ˜ ๋„๊ตฌ(DARTยทKRXยทNaver) + ํ•œ๊ตญ ์‹œ์žฅ ์Šคํ‚ฌ** ์ž…๋‹ˆ๋‹ค. *(์•„๋ž˜ ์ˆ˜์น˜๋Š” ๊ฐ ๋„๊ตฌ๊ฐ€ ๋ฐ˜ํ™˜ํ•œ ๊ทธ๋Œ€๋กœ์ด๋ฉฐ, ๋ณ„๋„ ์ •๋ฐ€ ๊ฐ์‚ฌ๋Š” ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.)* +2026๋…„ 5์›”, ๋™์ผํ•œ ์งˆ๋ฌธ 5๊ฐœ๋ฅผ **Dexter(LLM: GPT-5.5)** ์™€ **claude.ai(Opus 4.8, ์›น๊ฒ€์ƒ‰ ์‚ฌ์šฉ)** ์— ๋˜์ ธ ์‹ค์ธก ๋น„๊ตํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค ํ”„๋Ÿฐํ‹ฐ์–ด ๋ชจ๋ธ์ด๋ฏ€๋กœ ์ฐจ์ด๋ฅผ ๋งŒ๋“œ๋Š” ๊ฑด ๋ชจ๋ธ์ด ์•„๋‹ˆ๋ผ **๊ธˆ์œต ํ•˜๋„ค์Šค โ€” 1์ฐจ ์ถœ์ฒ˜ ๋„๊ตฌ(DARTยทKRXยทNaver) + ํ•œ๊ตญ ์‹œ์žฅ ์Šคํ‚ฌ** ์ž…๋‹ˆ๋‹ค. *(์•„๋ž˜ ์ˆ˜์น˜๋Š” ๊ฐ ๋„๊ตฌ๊ฐ€ ๋ฐ˜ํ™˜ํ•œ ๊ทธ๋Œ€๋กœ์ด๋ฉฐ, ๋ณ„๋„ ์ •๋ฐ€ ๊ฐ์‚ฌ๋Š” ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์‚ผ์„ฑ ์†์ต ํ–‰์€ ์žฌ๋ฌด ์ •๊ทœํ™” ๊ฐœ์„  ์ดํ›„ ์žฌ์ธก์ •.)* | ์งˆ๋ฌธ | claude.ai (Opus 4.8 + ์›น๊ฒ€์ƒ‰) | Dexter (GPT-5.5 + ๊ธˆ์œต ํ•˜๋„ค์Šค) | |---|---|---| -| ์‚ผ์„ฑ์ „์ž ๋งค์ถœ/์˜์—…์ด์ต | 333.6์กฐ / 43.6์กฐ, ๋‰ด์Šค ์ถœ์ฒ˜ โœ“ (๋‹จ "1Q 57์กฐ" ํ™˜๊ฐ ํ˜ผ์ž…) | DART ์‚ฌ์—…๋ณด๊ณ ์„œ ์ง์ ‘ ํ™•์ธ โ†’ ์†์ต ์ถ”์ถœ ๋ถˆ์•ˆ์ •ํ•˜์ž **์ถ”์ • ๊ฑฐ๋ถ€(๋ณด๋ฅ˜)** | +| ์‚ผ์„ฑ์ „์ž ๋งค์ถœ/์˜์—…์ด์ต | 333.6์กฐ / 43.6์กฐ, ๋‰ด์Šค ์ถœ์ฒ˜ โœ“ (๋‹จ "1Q 57์กฐ" ํ™˜๊ฐ ํ˜ผ์ž…) | **333.6์กฐ / 43.6์กฐ (+10.9% / +33.2% YoY)** + ๋งˆ์ง„ยทROEยทFCF, DART ๋ณด๊ณ ์„œ๋ฅผ ์ •๊ทœํ™” ์š”์•ฝ์œผ๋กœ ์ง์ ‘ ์ถ”์ถœ | | ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ | 48.42% (05-20), ๋‰ด์Šค ๊ธฐ๋ฐ˜ | **48.27% (05-29)** ์ผ์ž๋ณ„ + ์ˆœ๋งค๋„๋Ÿ‰, Naver ์ง์ ‘ โ€” ๋” ์ตœ์‹  | | ๋„ค์ด๋ฒ„ DCF | 161,700์›, ์ถฉ์‹ค(SOTPยท์ปจ์„ผ์„œ์Šค), ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ 4.15% | ~200,000์›, **KR ์ž๋™ ๋ถ„๊ธฐ**(ํ„ฐ๋ฏธ๋„ 2.0%ยท์„ธํ›„ ์‹คํ˜„์ˆ˜์ต๋ฅ  caption), ์‹ค์‹œ์„ธ ๊ธฐ๋ฐ˜ | | LGํ™”ํ•™ ๋ฌผ์ ๋ถ„ํ•  | ๋‹ค์ถœ์ฒ˜ยทํ’๋ถ€ํ•œ ์„œ์ˆ  | DART ๊ณต์‹œ 9๊ฑด ์ง์ ‘ ์ธ์šฉ + ๊ตฌ์กฐ ๋ถ„์„ | | ์—์ฝ”ํ”„๋กœ๋น„์—  ๊ณต๋งค๋„ | 1๊ฐœ ์‹œ์ (5/07)๋งŒ + "KRX์„œ ์กฐํšŒ" ์•ˆ๋‚ด | **18์ผ ์ผ๋ณ„ ์ž”๊ณ ์œจยท๊ธˆ์•กยท์ˆ˜๋Ÿ‰**(KRX ์ง์ ‘) | **Dexter๊ฐ€ ๋งŒ๋“  ์ฐจ์ด** +- **๊ตฌ์กฐํ™” ์žฌ๋ฌด ์š”์•ฝ** โ€” DART ์›์‹œ ๋ผ์ธ์•„์ดํ…œ์„ ๋งค์ถœยท์˜์—…์ด์ตยท์ˆœ์ด์ตยท๋งˆ์ง„ยทROEยทFCFยทYoY๋กœ ์ •๊ทœํ™”ํ•ด ์†์ต์„ ์ผ๊ด€๋˜๊ฒŒ ์ถ”์ถœ(๋Œ€์šฉ๋Ÿ‰ ์›๋ณธ์„ ๋ชป ์ฝ์–ด ๋ณด๋ฅ˜ํ•˜๋˜ ๋ฌธ์ œ ํ•ด๊ฒฐ). ์™ธ๊ตญ์ธยท๊ณต๋งค๋„ยท๋Œ€๋Ÿ‰๋ณด์œ ยท๊ตญ๋ฏผ์—ฐ๊ธˆ๊ณผ **๊ต์ฐจ ์ข…ํ•ฉ**ํ•ด ๋‹จ์ผ thesis๋กœ ์ œ์‹œ - **1์ฐจ ์ถœ์ฒ˜ ์ง์ ‘** โ€” DART(์žฌ๋ฌดยท๊ณต์‹œ)ยทKRX(๊ณต๋งค๋„)ยทNaver(์™ธ๊ตญ์ธ)๋ฅผ API๋กœ ์ง์ ‘ ํ˜ธ์ถœ โ†’ ๋‰ด์Šค(2์ฐจ)๋ณด๋‹ค ์ •๋ฐ€ยท์ตœ์‹ ยท์ผ๋ณ„ - **๊ตฌ์กฐํ™” ์‹œ๊ณ„์—ด** โ€” ๊ณต๋งค๋„ 18์ผ์น˜, ์™ธ๊ตญ์ธ ์ผ์ž๋ณ„์ฒ˜๋Ÿผ ๋‰ด์Šค์—” ์—†๋Š” ๋ฐ์ดํ„ฐ - **๊ฐ์‚ฌ ๊ฐ€๋Šฅ** โ€” ๋ชจ๋“  ํˆด ํ˜ธ์ถœ์ด ์Šคํฌ๋ž˜์น˜ํŒจ๋“œ์— ๊ธฐ๋ก โ†’ ๊ฐ™์€ ์งˆ๋ฌธ์ด๋ฉด ๊ฐ™์€ ๋ฐ์ดํ„ฐ ๊ฒฝ๋กœ @@ -158,7 +160,7 @@ LLM์ด "์‚ผ์„ฑ์ „์ž" ๊ฐ™์€ ์ž์—ฐ์–ด ์ž…๋ ฅ์„ ๋ฐ›์œผ๋ฉด, ํˆด ๋‚ด๋ถ€์—์„œ ** - **ํ™˜๊ฐ๋ณด๋‹ค ์ •์ง** โ€” ๋ฐ์ดํ„ฐ๋ฅผ ๋ชป ์–ป์œผ๋ฉด ๋งŒ๋“ค์–ด๋‚ด์ง€ ์•Š๊ณ  "๋ชป ์–ป๋Š”๋‹ค"๊ณ  ํ•จ **์ •์งํ•œ ํ•œ๊ณ„** โ€” Dexter๊ฐ€ ํ•ญ์ƒ ์ด๊ธฐ๋Š” ๊ฑด ์•„๋‹™๋‹ˆ๋‹ค: -- ์ด๋ฒˆ ์‚ผ์„ฑ ์†์ต์€ claude.ai๊ฐ€ ์ถœ์ฒ˜์™€ ํ•จ๊ป˜ ๋‹ตํ•œ ๋ฐ˜๋ฉด Dexter๋Š” ์ถ”์ถœ ๋ถˆ์•ˆ์ •์œผ๋กœ ๋ณด๋ฅ˜ โ€” ์ปค๋ฒ„๋ฆฌ์ง€๋Š” ์•„์ง ์™„์ „์น˜ ์•Š์Œ +- ํ‘œ์ค€ ์ œ์กฐ์—…์€ ์†์ต์„ ์ •๊ทœํ™” ์š”์•ฝ์œผ๋กœ ๋งค๋„๋Ÿฝ๊ฒŒ ์ถ”์ถœํ•˜์ง€๋งŒ, ์€ํ–‰ยท์ง€์ฃผ์‚ฌ์ฒ˜๋Ÿผ ๋น„ํ‘œ์ค€ K-IFRS ๋ผ๋ฒจ(์˜์—…์ˆ˜์ต ๋“ฑ)์„ ์“ฐ๋Š” ๊ธฐ์—…์€ ์ผ๋ถ€ ์ง€ํ‘œ๊ฐ€ ๋น„์–ด raw ๋ผ์ธ์•„์ดํ…œ ๋“œ๋ฆด๋‹ค์šด์œผ๋กœ ๋ณด์™„ โ€” ์ปค๋ฒ„๋ฆฌ์ง€๊ฐ€ ๊ท ์ผํ•˜์ง„ ์•Š์Œ - DCF ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ๋ฅผ Dexter ์Šคํ‚ฌ์€ 3.0%๋กœ ๊ณ ์ •, claude.ai๋Š” ๊ฒ€์ƒ‰์œผ๋กœ 4.15% ๋ฐ˜์˜ โ†’ ํ•˜๋“œ์ฝ”๋”ฉ ๊ฐ€์ •์ด ๋‚ก์„ ์ˆ˜ ์žˆ์Œ(๊ฐœ์„  ์˜ˆ์ •) - ์›น๊ฒ€์ƒ‰ ํ”„๋Ÿฐํ‹ฐ์–ด ๋ชจ๋ธ์€ ๊ฐ•๋ ฅํ•ฉ๋‹ˆ๋‹ค. Dexter์˜ ์šฐ์œ„๋Š” "๋” ๋˜‘๋˜‘ํ•จ"์ด ์•„๋‹ˆ๋ผ **1์ฐจ ์ถœ์ฒ˜ยท๊ตฌ์กฐํ™”ยท๊ฐ์‚ฌ๊ฐ€๋Šฅยทํ•œ๊ตญ ํŠœ๋‹**์— ์žˆ์Šต๋‹ˆ๋‹ค diff --git a/scripts/kr-eval.ts b/scripts/kr-eval.ts new file mode 100644 index 000000000..d9fb8553d --- /dev/null +++ b/scripts/kr-eval.ts @@ -0,0 +1,62 @@ +// KR research eval runner โ€” fire a single query at the agent and print the tool +// trace + final answer. For manual before/after comparison and regression probing. +// +// bun run scripts/kr-eval.ts "์‚ผ์„ฑ์ „์ž ์ง€๊ธˆ ํˆฌ์ž ๊ด€์ ์—์„œ ์–ด๋•Œ?" +// KR_RUNNER_MODEL=gpt-5.5 bun run scripts/kr-eval.ts "" +// +// Requires the same env as the app (DART_API_KEY, an LLM provider key, etc.). +import { config } from 'dotenv'; +config({ quiet: true }); + +import { Agent } from '../src/agent/agent.js'; + +const query = process.argv.slice(2).join(' ').trim(); +if (!query) { + console.error('Usage: bun run scripts/kr-eval.ts ""'); + process.exit(1); +} + +const preview = (s: string, n = 320) => + s.length > n ? s.slice(0, n).replace(/\n/g, ' ') + ` โ€ฆ[${s.length} chars]` : s.replace(/\n/g, ' '); + +const agent = await Agent.create({ + model: process.env.KR_RUNNER_MODEL ?? 'gpt-5.5', + channel: 'cli', + memoryEnabled: false, + maxIterations: 10, +}); + +console.log('QUERY:', query); +console.log('='.repeat(80)); + +let finalAnswer = ''; +const toolsCalled: string[] = []; + +for await (const ev of agent.run(query)) { + switch (ev.type) { + case 'tool_start': + toolsCalled.push(ev.tool); + console.log(`\n[tool_start] ${ev.tool} ${JSON.stringify(ev.args).slice(0, 200)}`); + break; + case 'tool_end': + console.log(`[tool_end] ${ev.tool} (${ev.duration}ms) -> ${preview(ev.result)}`); + break; + case 'tool_error': + console.log(`[tool_error] ${ev.tool} -> ${ev.error}`); + break; + case 'thinking': + console.log(`[thinking] ${preview(ev.message, 200)}`); + break; + case 'done': + finalAnswer = ev.answer; + console.log('\n' + '='.repeat(80)); + console.log(`DONE iterations=${ev.iterations} time=${ev.totalTime}ms tokens=${JSON.stringify(ev.tokenUsage)}`); + console.log(`tools (in order): ${toolsCalled.join(' -> ') || '(none)'}`); + break; + } +} + +console.log('\n' + '#'.repeat(80) + '\nFINAL ANSWER:\n'); +console.log(finalAnswer); +console.log('#'.repeat(80)); +process.exit(0); diff --git a/src/agent/prompts.ts b/src/agent/prompts.ts index 425d799eb..6d5109a5e 100644 --- a/src/agent/prompts.ts +++ b/src/agent/prompts.ts @@ -1,4 +1,5 @@ import { buildCompactToolDescriptions } from '../tools/registry.js'; +import { checkApiKeyExists } from '../utils/env.js'; import { buildSkillMetadataSection, discoverSkills } from '../skills/index.js'; import { readFile } from 'node:fs/promises'; import { dirname, join } from 'node:path'; @@ -85,6 +86,51 @@ ${skillList} - Do not invoke a skill that has already been invoked for the current query`; } +/** + * Korea-specific research playbook. Only included when DART-backed KR tools are + * active (DART_API_KEY present). These first-party data sources are the agent's + * edge over generic assistants; without explicit synthesis guidance the model + * defaults to a generic per-metric summary indistinguishable from a chatbot. + */ +function buildKoreanResearchSection(): string { + if (!checkApiKeyExists('DART_API_KEY')) { + return ''; + } + + return `## Korean Stock Research (6-digit tickers โ€” your edge over generic assistants) + +You have first-party Korean data a general chatbot does NOT have: exact DART K-IFRS +financials, daily foreign-investor flows, short-balance ratios, NPS holdings, 5%-rule +filings, insider reports. A generic "good company, watch the price" answer wastes them. +For any analyze / ์–ด๋•Œ / ํ‰๊ฐ€ / ๋งค์ˆ˜ยท๋งค๋„ ํŒ๋‹จ question on a Korean stock: + +- GATHER broadly ONLY for open-ended questions (analyze / ์–ด๋•Œ / ํ‰๊ฐ€ / ๋งค์ˆ˜ยท๋งค๋„): call + get_financials_kr (multi-year) AND get_foreign_ownership_kr, get_large_holders_kr, + get_filings_kr (recent material), plus get_short_balance_kr / get_nps_holdings when available โ€” + they run concurrently. For a NARROW ask (DCFยท๋‹จ์ผ ์ง€ํ‘œยทํŠน์ • ๊ณต์‹œ/์ด๋ฒคํŠธ) or when a skill is + driving the query, gather ONLY what that task needs; do NOT run the full sweep. +- GROUND the answer in concrete, dated, numeric signals only you can see โ€” not a textbook + description. Always state, with numbers + YoY: actual revenue / operating profit / net + income / margins / ROE from get_financials_kr's \`summary\` (never give an investment view + without earnings); ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ and recent net-buy trend (foreign vs ๊ธฐ๊ด€ vs ๊ฐœ์ธ); + ๊ณต๋งค๋„ ์ž”๊ณ ๋น„์ค‘ level + direction (โ‰ˆ0% = no bearish positioning / no squeeze fuel; rising = + building short pressure); NPS / 5%-rule / insider direction as smart-money signals. +- SYNTHESIZE the signals into ONE integrated thesis, not separate bullets. Three strands: + ์ˆ˜๊ธ‰(์™ธ๊ตญ์ธยท๊ณต๋งค๋„ยท๊ธฐ๊ด€ ๋ฐฉํ–ฅ) ยท ์‹ค์ (๋งค์ถœ/์ด์ต/๋งˆ์ง„/ROE) ยท ์ง€๋ฐฐ๊ตฌ์กฐ(๋Œ€๋Ÿ‰๋ณด์œ  ์ง‘์ค‘ยท๊ณ„์—ด ์ง€๋ถ„). + State explicitly whether they agree or conflict and which dominates โ€” e.g. + "์™ธ๊ตญ์ธ ์ˆœ๋งค๋„ + ๊ณต๋งค๋„ 0% + ๊ธฐ๊ด€ ์ˆœ๋งค์ˆ˜ = ๊ณ ์  ์ฐจ์ต์‹คํ˜„์„ ๊ตญ๋‚ด๊ฐ€ ํก์ˆ˜; ๋‹จ ์‚ผ์„ฑ๋ฌผ์‚ฐ 19.7% ์ˆœํ™˜์ถœ์ž = ๋ฐฐ๋‹นยท๋ถ„ํ•  ์ œ์•ฝ". +- TREAT ๋Œ€๋Ÿ‰๋ณด์œ (get_large_holders_kr) as a valuation modifier, not a footnote: ๊ณ„์—ด์‚ฌยท์žํšŒ์‚ฌ ์ง€๋ถ„์ด + ํฌ๋ฉด ์ง€๋ฐฐ๊ตฌ์กฐยท์ˆœํ™˜์ถœ์žยท๋ฐฐ๋‹น์ •์ฑ… ์ œ์•ฝ, ์ฐฝ์—…์ฃผยทํŠน์ˆ˜๊ด€๊ณ„ ์ง‘์ค‘์€ ์Šน๊ณ„ ๋ฆฌ์Šคํฌ/๋ช…ํ™•์„ฑ. ๋‹จ์ผ์ฃผ์ฃผ >15% ๋˜๋Š” + ๊ณ„์—ด ํ•ฉ์‚ฐ >30%๋ฉด ๊ตฌ์กฐ์  ์š”์ธ์œผ๋กœ ๋ช…์‹œ(์˜ˆ: "์ง€๋ฐฐ๊ตฌ์กฐ ํ• ์ธ ์ •๋‹นํ™”", "๋ถ„ํ• ยท๋ฐฐ๋ถ„ ๋ถˆํ™•์‹ค์„ฑ"). ๊ทธ ๋ฐ–์—: + ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ, ๋ฌผ์ /์ธ์ ๋ถ„ํ•  ์†Œ์•ก์ฃผ์ฃผ ์˜ํ–ฅ, ์ง€์ฃผ์‚ฌ ํ• ์ธ, ๊ฑฐ๋ž˜์„ธยท๋ฐฐ๋‹น์„ธ ์„ธํ›„ ์ˆ˜์ต. +- CLOSE with an evidence-anchored verdict and specific triggers tied to YOUR data + (e.g. "์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ์ด 48%๋Œ€์—์„œ ์žฌ์ƒ์Šน ์ „ํ™˜ ์‹œ"), not generic ones. + +"Concise" here means signal-dense: spend words on what only this data reveals; skip +background the user already knows. If a source is unavailable, say so briefly and proceed โ€” +never pad with generic narrative to fill the gap.`; +} + function buildMemorySection(memoryFiles: string[], memoryContext?: string | null): string { const fileListSection = memoryFiles.length > 0 ? `\nMemory files on disk: ${memoryFiles.join(', ')}` @@ -249,6 +295,8 @@ ${toolDescriptions} - Tool results are automatically capped. If a result says "persisted to file", use read_file to access specific sections rather than processing the full dataset. - Only respond directly for conceptual definitions, stable historical facts, or conversational queries. +${buildKoreanResearchSection()} + ${buildSkillsSection()} ${buildMemorySection(memoryFiles ?? [], memoryContext)} diff --git a/src/skills/dcf/SKILL.md b/src/skills/dcf/SKILL.md index 6b0bdbb44..bd4461225 100644 --- a/src/skills/dcf/SKILL.md +++ b/src/skills/dcf/SKILL.md @@ -36,7 +36,14 @@ DCF ๋ถ„์„ ์ง„ํ–‰: > **๐Ÿ‡ฐ๐Ÿ‡ท KR override:** ๋Œ€์‹  `get_financials_kr`๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค(DART ์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ๋กœ ๋ผ์šฐํŒ…). ์ž์—ฐ์–ด ์ฟผ๋ฆฌ ํ•˜๋‚˜๋ฉด ์ถฉ๋ถ„ํ•˜๋‹ค. ์˜ˆ: `"005930 ์ตœ๊ทผ 5๋…„ ์—ฐ๊ฒฐ ์žฌ๋ฌด์ œํ‘œ ํ˜„๊ธˆํ๋ฆ„ยท์†์ตยท์žฌ๋ฌด์ƒํƒœํ‘œ"`. ๊ทธ๋‹ค์Œ ํ˜„์žฌ๊ฐ€๋Š” `get_market_data`๊ฐ€ ํ•œ๊ตญ ํ‹ฐ์ปค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๊ทธ๊ฑธ ์“ฐ๊ณ , ์•„๋‹ˆ๋ฉด `web_search`/`get_foreign_ownership_kr`๋กœ ์ตœ๊ทผ ์ข…๊ฐ€๋ฅผ ๋ณด์™„ํ•œ๋‹ค. > -> DART ๊ณ„์ • ๋ผ๋ฒจ(`account_nm`)์€ ํšŒ์‚ฌยท์—ฐ๋„๋งˆ๋‹ค ๋‹ค๋ฅด๋‹ค โ€” **์ •ํ™• ์ผ์น˜ ๊ธˆ์ง€**. ์˜์—…์ˆ˜์ต vs ๋งค์ถœ์•ก, ๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค) ์ ‘๋ฏธ์‚ฌ ๋“ฑ. ๋ถ€๋ถ„ ๋ฌธ์ž์—ด / ํ‘œ์ค€ `account_id`(์žˆ์„ ๋•Œ)๋กœ ๋งค์นญํ•˜๊ณ  ๊ธฐ๊ฐ„ ๊ฐ„ ์ •ํ•ฉ์„ฑ์„ ๋งž์ถ˜๋‹ค. +> **์ถœ๋ ฅ์€ `periods[].summary` ์— ์ •๊ทœํ™”๋ผ ์žˆ๋‹ค(KRW).** ๋ผ๋ฒจ์„ ์ง์ ‘ ํŒŒ์‹ฑํ•˜์ง€ ๋ง๊ณ  ์ด ํ•„๋“œ๋ฅผ ์ฝ์–ด๋ผ: +> - ํ˜„๊ธˆํ๋ฆ„: `cashFlow.operating`(=์˜์—…ํ˜„๊ธˆํ๋ฆ„), `cashFlow.capex`, `ratios.freeCashFlow`(=์˜์—…CFโˆ’|capex|). `free_cash_flow`๊ฐ€ ์—†์œผ๋ฉด ์ด ๊ฐ’์„ ์“ด๋‹ค. +> - ์†์ต: `incomeStatement.revenue / operatingProfit / netIncome`, `ratios.revenueYoYPct`. +> - ์žฌ๋ฌด์ƒํƒœํ‘œ: `balanceSheet.totalLiabilities`(์ˆœ๋ถ€์ฑ„ ๊ทผ์‚ฌ; ๊ฐ€๋Šฅํ•˜๋ฉด ์ด์ž๋ถ€์ฑ„๋งŒ ๋ณ„๋„ ํ™•์ธ), `balanceSheet.cashAndEquivalents`, `balanceSheet.totalEquity`. +> - ๊ฐ metric์€ `{ current, prior, label, display }` ๊ตฌ์กฐ์ด๋ฉฐ `current`(๋‹น๊ธฐ)๋ฅผ ์“ด๋‹ค. ๋ถ„๊ธฐยท๋ฐ˜๊ธฐ ์†์ต/ํ˜„๊ธˆํ๋ฆ„์€ ๋ˆ„์ (YTD)์ž„์„ `summary.basis`๊ฐ€ ์•Œ๋ ค์ค€๋‹ค. +> - ๋ฐœํ–‰์ฃผ์‹์ˆ˜(`outstanding_shares`)๋Š” summary์— ์—†๋‹ค โ€” `get_short_balance_kr`์˜ `listedShares` ๋˜๋Š” `web_search`๋กœ ๋ณด์™„ํ•œ๋‹ค. +> +> summary๊ฐ€ ๋น„์–ด ์žˆ์œผ๋ฉด(์€ํ–‰ยท์ง€์ฃผ์‚ฌ ๋“ฑ ๋น„ํ‘œ์ค€ ๋ผ๋ฒจ) `rawLineItemsFile` ์„ `read_file`๋กœ ์—ด์–ด ์ง์ ‘ ์ฐพ๋Š”๋‹ค. `account_nm`์€ ํšŒ์‚ฌยท์—ฐ๋„๋งˆ๋‹ค ๋‹ฌ๋ผ **์ •ํ™• ์ผ์น˜ ๊ธˆ์ง€** โ€” ๋ถ€๋ถ„ ๋ฌธ์ž์—ด/`account_id`๋กœ ๋งค์นญํ•œ๋‹ค. ### 1.1 ํ˜„๊ธˆํ๋ฆ„ ์ด๋ ฅ **์ฟผ๋ฆฌ:** `"[TICKER] annual cash flow statements for the last 5 years"` diff --git a/src/skills/kr-spinoff/SKILL.md b/src/skills/kr-spinoff/SKILL.md index a401801ba..12dd4737b 100644 --- a/src/skills/kr-spinoff/SKILL.md +++ b/src/skills/kr-spinoff/SKILL.md @@ -72,11 +72,13 @@ KR ๋ถ„ํ•  ๋ถ„์„ ์ง„ํ–‰: ## Step 5: (์„ ํƒ) ์žฌ๋ฌด๋กœ ์ •๋Ÿ‰ํ™” -`get_financials_kr`๋กœ ๋ชจํšŒ์‚ฌ์˜ ์—ฐ๊ฒฐ vs ๋ณ„๋„ ์ˆ˜์น˜๋ฅผ, ๊ฐ€๋Šฅํ•˜๋ฉด ๋ถ„ํ•  ์ „ํ›„ ์„ธ๊ทธ๋จผํŠธ ๊ธฐ์—ฌ๋„๋ฅผ ๋น„๊ตํ•ด -โ€” ์ง์ ‘ ๋ณด์œ  ๋ฒ•์ธ์—์„œ ์–ผ๋งˆ๋‚˜ ๋งŽ์€ ์ด์ต ์ฐฝ์ถœ๋ ฅ์ด ๋น ์ ธ๋‚˜๊ฐ”๋Š”์ง€ ๋ณด์—ฌ์ค€๋‹ค. +`get_financials_kr`๋กœ ๋ชจํšŒ์‚ฌ์˜ ์—ฐ๊ฒฐ(CFS) vs ๋ณ„๋„(OFS) ์ˆ˜์น˜๋ฅผ ๋น„๊ตํ•ด โ€” ์ง์ ‘ ๋ณด์œ  ๋ฒ•์ธ์—์„œ ์–ผ๋งˆ๋‚˜ +๋งŽ์€ ์ด์ต ์ฐฝ์ถœ๋ ฅ์ด ๋น ์ ธ๋‚˜๊ฐ”๋Š”์ง€ ๋ณด์—ฌ์ค€๋‹ค. `fs_div`๊ฐ€ ๋‹ค๋ฅธ ๋‘ ๋ฒˆ์˜ ํ˜ธ์ถœ์ด ํ•„์š”ํ•˜๋‹ค. -> DART ๊ณ„์ • ๋ผ๋ฒจ(`account_nm`)์€ ํšŒ์‚ฌ / ์—ฐ๋„๋งˆ๋‹ค ๋‹ค๋ฅด๋‹ค โ€” ๋ถ€๋ถ„ ๋ฌธ์ž์—ด / `account_id`๋กœ ๋งค์นญํ•˜๊ณ  -> ์ •ํ™• ์ผ์น˜๋Š” ์ ˆ๋Œ€ ์“ฐ์ง€ ๋งˆ๋ผ. +> ์ถœ๋ ฅ์€ `periods[].summary`์— ์ •๊ทœํ™”๋ผ ์žˆ๋‹ค(KRW). `incomeStatement.revenue / operatingProfit / netIncome`, +> `balanceSheet.totalAssets / totalEquity`๋ฅผ ์—ฐ๊ฒฐยท๋ณ„๋„ ๊ฐ๊ฐ์—์„œ ์ฝ์–ด ๋น„๊ตํ•˜๋ผ. ๊ฐ ๊ฐ’์€ `{ current, prior }`. +> summary๊ฐ€ ๋น„์–ด ์žˆ์œผ๋ฉด `rawLineItemsFile`์„ `read_file`๋กœ ์—ฐ๋‹ค. `account_nm`์€ ํšŒ์‚ฌยท์—ฐ๋„๋งˆ๋‹ค ๋‹ฌ๋ผ +> ์ •ํ™• ์ผ์น˜ ๊ธˆ์ง€ โ€” ๋ถ€๋ถ„ ๋ฌธ์ž์—ด/`account_id`๋กœ ๋งค์นญํ•œ๋‹ค. ## Step 6: ์ถœ๋ ฅ ํ˜•์‹ diff --git a/src/tools/finance-kr/get-business-report.test.ts b/src/tools/finance-kr/get-business-report.test.ts new file mode 100644 index 000000000..2de92ce58 --- /dev/null +++ b/src/tools/finance-kr/get-business-report.test.ts @@ -0,0 +1,51 @@ +import { describe, it, expect } from 'bun:test'; +import { mkdtempSync, writeFileSync, readdirSync, utimesSync, rmSync } from 'fs'; +import { tmpdir } from 'os'; +import { join } from 'path'; +import { pruneRawFinancialFiles, RAW_FILE_KEEP } from './sub-tools/get-business-report.js'; + +describe('pruneRawFinancialFiles', () => { + it('keeps newest RAW_FILE_KEEP dumps, preserves call_*.txt and the current file', () => { + const dir = mkdtempSync(join(tmpdir(), 'kr-prune-')); + try { + // Agent persist (different naming) โ€” must never be deleted. + writeFileSync(join(dir, 'call_abc123.txt'), 'persist'); + + // RAW_FILE_KEEP + 5 dumps with strictly increasing mtimes (i=0 oldest). + const extra = 5; + for (let i = 0; i < RAW_FILE_KEEP + extra; i++) { + const name = `kr-financials-x${i}-11011-CFS-2024_2024.json`; + writeFileSync(join(dir, name), '{}'); + const t = 1_000_000 + i; + utimesSync(join(dir, name), t, t); + } + + // The file just written this call โ€” intentionally given the OLDEST mtime to + // prove it is kept via the keepName guard, not via recency. + const current = 'kr-financials-current-11011-CFS-2025_2025.json'; + writeFileSync(join(dir, current), '{}'); + utimesSync(join(dir, current), 500_000, 500_000); + + pruneRawFinancialFiles(dir, current); + + const left = readdirSync(dir); + expect(left).toContain('call_abc123.txt'); // agent persist untouched + expect(left).toContain(current); // current kept despite oldest mtime + expect(left).not.toContain('kr-financials-x0-11011-CFS-2024_2024.json'); // oldest dropped + const dumps = left.filter((f) => f.startsWith('kr-financials-')); + expect(dumps.length).toBeLessThanOrEqual(RAW_FILE_KEEP + 1); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it('is a no-op on an empty directory', () => { + const dir = mkdtempSync(join(tmpdir(), 'kr-prune-empty-')); + try { + expect(() => pruneRawFinancialFiles(dir, 'kr-financials-none.json')).not.toThrow(); + expect(readdirSync(dir).length).toBe(0); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); +}); diff --git a/src/tools/finance-kr/get-financials-kr.ts b/src/tools/finance-kr/get-financials-kr.ts index 0626e30ed..a1d433e47 100644 --- a/src/tools/finance-kr/get-financials-kr.ts +++ b/src/tools/finance-kr/get-financials-kr.ts @@ -30,7 +30,7 @@ Intelligent meta-tool for retrieving Korean (KOSPI/KOSDAQ) listed company financ - Call ONCE with the complete natural language query โ€” the router handles internal complexity - Ticker must be the 6-digit Korean code (e.g. 005930, not "005930.KS" or "์‚ผ์„ฑ์ „์ž") - Handles year inference: "์ž‘๋…„", "์˜ฌํ•ด", "์ง€๋‚œ ๋ถ„๊ธฐ" โ€” converts to bsns_year + reprt_code -- Returns raw DART line items under K-IFRS account names (Korean labels) +- Returns a normalized per-period \`summary\` (revenue, operating profit, net income, EPS, assets/liabilities/equity, cash flow, capex, plus margins, ROE, FCF, YoY) in KRW. Full raw DART line items are saved to \`rawLineItemsFile\` for drill-down into non-standard accounts. `.trim(); const KR_SUB_TOOLS: StructuredToolInterface[] = [getBusinessReport]; diff --git a/src/tools/finance-kr/normalize-financials.test.ts b/src/tools/finance-kr/normalize-financials.test.ts new file mode 100644 index 000000000..a77afbbec --- /dev/null +++ b/src/tools/finance-kr/normalize-financials.test.ts @@ -0,0 +1,129 @@ +import { describe, it, expect } from 'bun:test'; +import { + parseAmount, + formatKrw, + findMetric, + summarizePeriod, + ACCOUNT_SPECS, + type DartRow, +} from './normalize-financials.js'; + +// Samsung-like annual consolidated (CFS) line items, with the ifrs-full_ProfitLoss +// trap duplicated under CF โ€” netIncome must resolve to the IS row, not the CF row. +const annualList: DartRow[] = [ + { sj_div: 'BS', account_id: 'ifrs-full_Assets', account_nm: '์ž์‚ฐ์ด๊ณ„', thstrm_amount: '514,531,948,000,000', frmtrm_amount: '448,424,507,000,000' }, + { sj_div: 'BS', account_id: 'ifrs-full_Liabilities', account_nm: '๋ถ€์ฑ„์ด๊ณ„', thstrm_amount: '92,228,115,000,000', frmtrm_amount: '93,674,903,000,000' }, + { sj_div: 'BS', account_id: 'ifrs-full_Equity', account_nm: '์ž๋ณธ์ด๊ณ„', thstrm_amount: '422,303,833,000,000', frmtrm_amount: '354,749,604,000,000' }, + { sj_div: 'BS', account_id: 'ifrs-full_CashAndCashEquivalents', account_nm: 'ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ', thstrm_amount: '73,000,000,000,000', frmtrm_amount: '57,000,000,000,000' }, + { sj_div: 'IS', account_id: 'ifrs-full_Revenue', account_nm: '๋งค์ถœ์•ก', thstrm_amount: '300,870,903,000,000', frmtrm_amount: '258,935,494,000,000' }, + { sj_div: 'IS', account_id: 'dart_OperatingIncomeLoss', account_nm: '์˜์—…์ด์ต', thstrm_amount: '32,725,961,000,000', frmtrm_amount: '6,566,976,000,000' }, + { sj_div: 'IS', account_id: 'ifrs-full_ProfitLoss', account_nm: '๋‹น๊ธฐ์ˆœ์ด์ต', thstrm_amount: '34,451,351,000,000', frmtrm_amount: '15,487,100,000,000' }, + { sj_div: 'CF', account_id: 'ifrs-full_ProfitLoss', account_nm: '๋‹น๊ธฐ์ˆœ์ด์ต', thstrm_amount: '99,999,999,999,999', frmtrm_amount: '0' }, + { sj_div: 'IS', account_id: 'ifrs-full_BasicEarningsLossPerShare', account_nm: '๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต(์†์‹ค)', thstrm_amount: '5,062', frmtrm_amount: '2,131' }, + { sj_div: 'CF', account_id: 'ifrs-full_CashFlowsFromUsedInOperatingActivities', account_nm: '์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„', thstrm_amount: '70,000,000,000,000', frmtrm_amount: '44,000,000,000,000' }, + { sj_div: 'CF', account_id: 'ifrs-full_CashFlowsFromUsedInInvestingActivities', account_nm: 'ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„', thstrm_amount: '-50,000,000,000,000', frmtrm_amount: '-30,000,000,000,000' }, + { sj_div: 'CF', account_id: 'ifrs-full_CashFlowsFromUsedInFinancingActivities', account_nm: '์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„', thstrm_amount: '-10,000,000,000,000', frmtrm_amount: '-9,000,000,000,000' }, + { sj_div: 'CF', account_id: 'ifrs-full_PurchaseOfPropertyPlantAndEquipmentClassifiedAsInvestingActivities', account_nm: '์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“', thstrm_amount: '-40,000,000,000,000', frmtrm_amount: '-35,000,000,000,000' }, +]; + +describe('parseAmount', () => { + it('strips commas and parses signed integers', () => { + expect(parseAmount('300,870,903,000,000')).toBe(300870903000000); + expect(parseAmount('-9,000,000,000,000')).toBe(-9000000000000); + expect(parseAmount('5,062')).toBe(5062); + }); + it('returns null for empty / dash / nullish', () => { + expect(parseAmount('')).toBeNull(); + expect(parseAmount('-')).toBeNull(); + expect(parseAmount(null)).toBeNull(); + expect(parseAmount(undefined)).toBeNull(); + }); +}); + +describe('formatKrw', () => { + it('formats ์กฐ / ์–ต with sign', () => { + expect(formatKrw(300870903000000)).toBe('300.9์กฐ'); + expect(formatKrw(40000000000000)).toBe('40.0์กฐ'); + expect(formatKrw(-9000000000000)).toBe('-9.0์กฐ'); + expect(formatKrw(1500000000)).toBe('15์–ต'); + expect(formatKrw(null)).toBeNull(); + }); +}); + +describe('findMetric โ€” sj_div discipline', () => { + it('resolves netIncome from the IS row, not the duplicate CF ProfitLoss row', () => { + const m = findMetric(annualList, ACCOUNT_SPECS.netIncome); + expect(m.current).toBe(34451351000000); + expect(m.current).not.toBe(99999999999999); + }); + it('falls back to CIS when there is no IS statement (single-statement filer)', () => { + const cisOnly: DartRow[] = [ + { sj_div: 'CIS', account_id: 'ifrs-full_Revenue', account_nm: '์˜์—…์ˆ˜์ต', thstrm_amount: '1,000,000,000', frmtrm_amount: '900,000,000' }, + ]; + const m = findMetric(cisOnly, ACCOUNT_SPECS.revenue); + expect(m.current).toBe(1000000000); + expect(m.label).toBe('์˜์—…์ˆ˜์ต'); + }); + it('falls back to account_nm when account_id is absent', () => { + const noId: DartRow[] = [ + { sj_div: 'IS', account_id: '-', account_nm: '๋งค์ถœ์•ก', thstrm_amount: '500', frmtrm_amount: '400' }, + ]; + const m = findMetric(noId, ACCOUNT_SPECS.revenue); + expect(m.current).toBe(500); + }); + it('does NOT substring-match a different account (์ˆœ์˜์—…์ด์ต / ๋ฐ˜์˜์ „ ์˜์—…์ด์ต โ‰  ์˜์—…์ด์ต)', () => { + const bankIs: DartRow[] = [ + { sj_div: 'CIS', account_id: '-ํ‘œ์ค€๊ณ„์ •์ฝ”๋“œ ๋ฏธ์‚ฌ์šฉ-', account_nm: '์‹ ์šฉ์†์‹ค์ถฉ๋‹น๊ธˆ ๋ฐ˜์˜์ „ ์˜์—…์ด์ต', thstrm_amount: '10,880,589,000,000' }, + { sj_div: 'CIS', account_id: '-', account_nm: '์ˆœ์˜์—…์ด์ต', thstrm_amount: '5,000,000,000,000' }, + ]; + expect(findMetric(bankIs, ACCOUNT_SPECS.operatingProfit).current).toBeNull(); + }); +}); + +describe('summarizePeriod', () => { + const s = summarizePeriod(annualList, { bsns_year: 2025, report_type: 'annual', fs_div: 'CFS' }); + + it('surfaces income statement with display + YoY', () => { + expect(s.incomeStatement.revenue.current).toBe(300870903000000); + expect(s.incomeStatement.revenue.display).toBe('300.9์กฐ'); + expect(s.ratios.revenueYoYPct).toBe(16.2); + }); + it('keeps EPS at per-share scale (์›, not ์กฐ)', () => { + expect(s.incomeStatement.eps.current).toBe(5062); + expect(s.incomeStatement.eps.display).toBe('5,062์›'); + }); + it('computes margins and ROE (annual)', () => { + expect(s.ratios.operatingMarginPct).toBe(10.9); + expect(s.ratios.roePct).toBe(8.2); + expect(s.ratios.debtToEquityPct).toBe(21.8); + }); + it('computes FCF as operating CF minus |capex|', () => { + expect(s.ratios.freeCashFlow).toBe(30000000000000); + expect(s.ratios.freeCashFlowDisplay).toBe('30.0์กฐ'); + }); + it('carries report metadata + basis note', () => { + expect(s.fs_div).toBe('CFS'); + expect(s.unit).toBe('KRW'); + expect(s.basis).toContain('์—ฐ๊ฐ„'); + }); + it('omits ROE for non-annual reports (YTD net income would mislead)', () => { + const q = summarizePeriod(annualList, { bsns_year: 2026, report_type: 'quarterly_1', fs_div: 'CFS' }); + expect(q.ratios.roePct).toBeNull(); + expect(q.ratios.operatingMarginPct).toBe(10.9); // ratios of two YTD flows stay valid + }); + it('nulls YoY when the prior period is absent (quarterly frmtrm undefined)', () => { + const q1: DartRow[] = [ + { sj_div: 'IS', account_id: 'ifrs-full_Revenue', account_nm: '๋งค์ถœ์•ก', thstrm_amount: '133,873,444,000,000' }, + ]; + const s2 = summarizePeriod(q1, { bsns_year: 2026, report_type: 'quarterly_1', fs_div: 'CFS' }); + expect(s2.incomeStatement.revenue.current).toBe(133873444000000); + expect(s2.incomeStatement.revenue.prior).toBeNull(); + expect(s2.ratios.revenueYoYPct).toBeNull(); + }); + it('returns null metrics (not throw) when accounts are missing', () => { + const empty = summarizePeriod([], { bsns_year: 2025, report_type: 'annual', fs_div: 'CFS' }); + expect(empty.incomeStatement.revenue.current).toBeNull(); + expect(empty.ratios.operatingMarginPct).toBeNull(); + expect(empty.ratios.freeCashFlow).toBeNull(); + }); +}); diff --git a/src/tools/finance-kr/normalize-financials.ts b/src/tools/finance-kr/normalize-financials.ts new file mode 100644 index 000000000..d61175ed1 --- /dev/null +++ b/src/tools/finance-kr/normalize-financials.ts @@ -0,0 +1,343 @@ +/** + * Normalize raw DART fnlttSinglAcntAll line items into a compact, in-context + * financial summary. + * + * Why this exists: get_financials_kr previously returned the full raw DART payload + * (80โ€“100KB per period, all statements, raw K-IFRS labels). It blew past the + * tool-result size cap โ†’ got persisted to disk โ†’ the model only saw a balance-sheet + * preview and could not page into the income statement (single-line JSON exceeds the + * read_file per-line limit). Result: the model never surfaced revenue/operating + * profit/net income and fell back on generic narrative. This module produces a small + * normalized summary that always fits in-context, so earnings reliably reach the model. + * + * Matching is by stable `account_id` first (labels vary by company/year โ€” see the + * ๋งค์ถœ์•ก vs ์˜์—…์ˆ˜์ต problem), with `account_nm` fallbacks. Income-statement accounts + * are searched under both IS and CIS sj_div because single-statement filers report + * P&L under the comprehensive-income statement only. + */ + +export type ReportType = 'annual' | 'semiannual' | 'quarterly_1' | 'quarterly_3'; +export type FsDiv = 'CFS' | 'OFS'; + +/** One raw DART line item from fnlttSinglAcntAll.json. */ +export interface DartRow { + account_id?: string; + account_nm?: string; + sj_div?: string; + thstrm_amount?: string; + frmtrm_amount?: string; + [key: string]: unknown; +} + +/** A resolved metric: current/prior raw KRW amounts plus the matched label. */ +export interface MetricVal { + current: number | null; + prior: number | null; + /** account_nm actually matched โ€” for traceability when labels vary. */ + label: string | null; + /** Human-readable current value (์กฐ/์–ต for amounts, ์› for per-share). */ + display: string | null; +} + +export interface FinancialSummary { + bsns_year: number; + report_type: ReportType; + fs_div: FsDiv; + unit: 'KRW'; + /** What thstrm/frmtrm mean for this report type (YTD-cumulative caveats etc.). */ + basis: string; + incomeStatement: { + revenue: MetricVal; + operatingProfit: MetricVal; + netIncome: MetricVal; + controllingNetIncome: MetricVal; + eps: MetricVal; + }; + balanceSheet: { + totalAssets: MetricVal; + totalLiabilities: MetricVal; + totalEquity: MetricVal; + cashAndEquivalents: MetricVal; + }; + cashFlow: { + operating: MetricVal; + investing: MetricVal; + financing: MetricVal; + capex: MetricVal; + }; + ratios: { + operatingMarginPct: number | null; + netMarginPct: number | null; + roePct: number | null; + debtToEquityPct: number | null; + revenueYoYPct: number | null; + operatingProfitYoYPct: number | null; + netIncomeYoYPct: number | null; + freeCashFlow: number | null; + freeCashFlowDisplay: string | null; + }; +} + +type Statement = 'IS' | 'BS' | 'CF'; +type MetricKind = 'amount' | 'eps'; + +interface AccountSpec { + /** sj_div values to search, in preference order. */ + sjDivs: string[]; + /** Stable account_id values, in preference order. */ + accountIds: string[]; + /** account_nm fallbacks when account_id is absent or unmatched. */ + accountNms: string[]; + kind: MetricKind; + statement: Statement; +} + +/** account_id mapping verified against real DART payloads (Samsung 005930, CFS). */ +export const ACCOUNT_SPECS: Record = { + revenue: { + sjDivs: ['IS', 'CIS'], + accountIds: ['ifrs-full_Revenue'], + accountNms: ['๋งค์ถœ์•ก', '์˜์—…์ˆ˜์ต', '์ˆ˜์ต(๋งค์ถœ์•ก)'], + kind: 'amount', + statement: 'IS', + }, + operatingProfit: { + sjDivs: ['IS', 'CIS'], + accountIds: ['dart_OperatingIncomeLoss'], + accountNms: ['์˜์—…์ด์ต', '์˜์—…์ด์ต(์†์‹ค)'], + kind: 'amount', + statement: 'IS', + }, + netIncome: { + // ifrs-full_ProfitLoss also appears under CF/CIS/SCE โ€” sj_div ordering keeps us on the P&L. + sjDivs: ['IS', 'CIS'], + accountIds: ['ifrs-full_ProfitLoss'], + accountNms: ['๋‹น๊ธฐ์ˆœ์ด์ต', '๋ถ„๊ธฐ์ˆœ์ด์ต', '๋ฐ˜๊ธฐ์ˆœ์ด์ต', '๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)'], + kind: 'amount', + statement: 'IS', + }, + controllingNetIncome: { + sjDivs: ['IS', 'CIS'], + accountIds: ['ifrs-full_ProfitLossAttributableToOwnersOfParent'], + accountNms: ['์ง€๋ฐฐ๊ธฐ์—… ์†Œ์œ ์ฃผ์ง€๋ถ„', '์ง€๋ฐฐ๊ธฐ์—…์˜ ์†Œ์œ ์ฃผ์—๊ฒŒ ๊ท€์†๋˜๋Š” ๋‹น๊ธฐ์ˆœ์ด์ต'], + kind: 'amount', + statement: 'IS', + }, + eps: { + sjDivs: ['IS', 'CIS'], + accountIds: ['ifrs-full_BasicEarningsLossPerShare', 'ifrs-full_DilutedEarningsLossPerShare'], + accountNms: ['๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต', '๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต(์†์‹ค)', 'ํฌ์„์ฃผ๋‹น์ด์ต', 'ํฌ์„์ฃผ๋‹น์ด์ต(์†์‹ค)'], + kind: 'eps', + statement: 'IS', + }, + totalAssets: { + sjDivs: ['BS'], + accountIds: ['ifrs-full_Assets'], + accountNms: ['์ž์‚ฐ์ด๊ณ„'], + kind: 'amount', + statement: 'BS', + }, + totalLiabilities: { + sjDivs: ['BS'], + accountIds: ['ifrs-full_Liabilities'], + accountNms: ['๋ถ€์ฑ„์ด๊ณ„'], + kind: 'amount', + statement: 'BS', + }, + totalEquity: { + // Prefer total equity (incl. NCI) over owners'-only. + sjDivs: ['BS'], + accountIds: ['ifrs-full_Equity', 'ifrs-full_EquityAttributableToOwnersOfParent'], + accountNms: ['์ž๋ณธ์ด๊ณ„', '์ง€๋ฐฐ๊ธฐ์—…์˜ ์†Œ์œ ์ฃผ์—๊ฒŒ ๊ท€์†๋˜๋Š” ์ž๋ณธ'], + kind: 'amount', + statement: 'BS', + }, + cashAndEquivalents: { + sjDivs: ['BS'], + accountIds: ['ifrs-full_CashAndCashEquivalents'], + accountNms: ['ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ'], + kind: 'amount', + statement: 'BS', + }, + cfo: { + sjDivs: ['CF'], + accountIds: ['ifrs-full_CashFlowsFromUsedInOperatingActivities'], + accountNms: ['์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„', '์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„'], + kind: 'amount', + statement: 'CF', + }, + cfi: { + sjDivs: ['CF'], + accountIds: ['ifrs-full_CashFlowsFromUsedInInvestingActivities'], + accountNms: ['ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„', 'ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„'], + kind: 'amount', + statement: 'CF', + }, + cff: { + sjDivs: ['CF'], + accountIds: ['ifrs-full_CashFlowsFromUsedInFinancingActivities'], + accountNms: ['์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„', '์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„'], + kind: 'amount', + statement: 'CF', + }, + capex: { + sjDivs: ['CF'], + accountIds: ['ifrs-full_PurchaseOfPropertyPlantAndEquipmentClassifiedAsInvestingActivities'], + accountNms: ['์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“', '์œ ํ˜•์ž์‚ฐ์˜ ์ฆ๊ฐ€'], + kind: 'amount', + statement: 'CF', + }, +}; + +const BASIS_NOTES: Record = { + annual: '์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.', + semiannual: '๋ฐ˜๊ธฐ ๋ˆ„์ (thstrm=๋‹น๋ฐ˜๊ธฐ ๋ˆ„์ , frmtrm=์ „๋…„ ๋™๊ธฐ ๋ˆ„์ ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„์€ 6๊ฐœ์›” YTD.', + quarterly_1: '1๋ถ„๊ธฐ ๋ˆ„์ (thstrm=๋‹น๊ธฐ, frmtrm=์ „๋…„ ๋™๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„์€ YTD; BS๋Š” ๋ถ„๊ธฐ๋ง vs ์ „๋…„๋ง.', + quarterly_3: '3๋ถ„๊ธฐ ๋ˆ„์ (9๊ฐœ์›” YTD, ๋ถ„๊ธฐ ๋‹จ๋… ์•„๋‹˜). ์†์ตยทํ˜„๊ธˆํ๋ฆ„์€ ๋ˆ„์ ; ๋ถ„๊ธฐ ๋‹จ๋…๊ฐ’์€ ๋ณ„๋„ ๊ณ„์‚ฐ ํ•„์š”.', +}; + +/** Parse a DART numeric string ("-1,234" / "" / "-") into a number or null. */ +export function parseAmount(raw: unknown): number | null { + if (raw === null || raw === undefined) return null; + const s = String(raw).trim().replace(/,/g, ''); + if (s === '' || s === '-') return null; + const n = Number(s); + return Number.isFinite(n) ? n : null; +} + +/** Collapse whitespace for tolerant account_nm comparison. */ +function squash(s: string): string { + return s.replace(/\s+/g, ''); +} + +/** Format a KRW amount as ์กฐ/์–ต (rounded), preserving sign. */ +export function formatKrw(n: number | null): string | null { + if (n === null) return null; + const abs = Math.abs(n); + const sign = n < 0 ? '-' : ''; + if (abs >= 1e12) return `${sign}${(abs / 1e12).toFixed(1)}์กฐ`; + if (abs >= 1e8) return `${sign}${Math.round(abs / 1e8).toLocaleString('en-US')}์–ต`; + return n.toLocaleString('en-US'); +} + +function formatDisplay(value: number | null, kind: MetricKind): string | null { + if (value === null) return null; + if (kind === 'eps') return `${value.toLocaleString('en-US')}์›`; + return formatKrw(value); +} + +const EMPTY_METRIC: MetricVal = { current: null, prior: null, label: null, display: null }; + +/** + * Resolve one metric from the line-item list per its AccountSpec. + * + * Matching is account_id first, then EXACT account_nm (whitespace-insensitive). We + * deliberately do NOT do substring matching: e.g. "์˜์—…์ด์ต" is a substring of the + * bank line "์‹ ์šฉ์†์‹ค์ถฉ๋‹น๊ธˆ ๋ฐ˜์˜์ „ ์˜์—…์ด์ต" and of "์ˆœ์˜์—…์ด์ต", which would silently + * resolve operatingProfit to the wrong figure and poison every margin. When no exact + * match exists (banks/holdcos with non-standard labels), the metric stays null and the + * model drills into rawLineItemsFile instead. + */ +export function findMetric(list: DartRow[], spec: AccountSpec): MetricVal { + for (const sjDiv of spec.sjDivs) { + const rows = list.filter((r) => r.sj_div === sjDiv); + if (rows.length === 0) continue; + + // 1. Stable account_id (preference-ordered). + for (const id of spec.accountIds) { + const row = rows.find((r) => r.account_id === id); + if (row) return toMetric(row, spec.kind); + } + // 2. Exact account_nm (whitespace-insensitive) โ€” no substring matching, see above. + for (const nm of spec.accountNms) { + const target = squash(nm); + const row = rows.find((r) => r.account_nm && squash(r.account_nm) === target); + if (row) return toMetric(row, spec.kind); + } + } + return { ...EMPTY_METRIC }; +} + +function toMetric(row: DartRow, kind: MetricKind): MetricVal { + const current = parseAmount(row.thstrm_amount); + return { + current, + prior: parseAmount(row.frmtrm_amount), + label: row.account_nm ?? null, + display: formatDisplay(current, kind), + }; +} + +function yoyPct(m: MetricVal): number | null { + if (m.current === null || m.prior === null || m.prior === 0) return null; + return round1(((m.current - m.prior) / Math.abs(m.prior)) * 100); +} + +function ratioPct(numer: number | null, denom: number | null): number | null { + if (numer === null || denom === null || denom === 0) return null; + return round1((numer / denom) * 100); +} + +function round1(n: number): number { + return Math.round(n * 10) / 10; +} + +export interface SummarizeOpts { + bsns_year: number; + report_type: ReportType; + fs_div: FsDiv; +} + +/** Build the normalized summary for one reporting period from its raw line items. */ +export function summarizePeriod(list: DartRow[], opts: SummarizeOpts): FinancialSummary { + const m = (key: keyof typeof ACCOUNT_SPECS): MetricVal => findMetric(list, ACCOUNT_SPECS[key]); + + const revenue = m('revenue'); + const operatingProfit = m('operatingProfit'); + const netIncome = m('netIncome'); + const controllingNetIncome = m('controllingNetIncome'); + const eps = m('eps'); + const totalAssets = m('totalAssets'); + const totalLiabilities = m('totalLiabilities'); + const totalEquity = m('totalEquity'); + const cashAndEquivalents = m('cashAndEquivalents'); + const cfo = m('cfo'); + const cfi = m('cfi'); + const cff = m('cff'); + const capex = m('capex'); + + // FCF = operating cash flow โˆ’ capex. capex (์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“) is reported as a cash + // outflow; treat it as a use regardless of reported sign. + const freeCashFlow = + cfo.current === null || capex.current === null + ? null + : cfo.current - Math.abs(capex.current); + + // ROE mixes a flow (net income) with a stock (equity); only meaningful over a full + // year. Quarterly/semiannual net income is YTD-cumulative, so skip to avoid a + // misleadingly low partial-year ROE. Basis: total net income / total equity (both + // incl. non-controlling interests for CFS). + const roePct = opts.report_type === 'annual' ? ratioPct(netIncome.current, totalEquity.current) : null; + + return { + bsns_year: opts.bsns_year, + report_type: opts.report_type, + fs_div: opts.fs_div, + unit: 'KRW', + basis: BASIS_NOTES[opts.report_type], + incomeStatement: { revenue, operatingProfit, netIncome, controllingNetIncome, eps }, + balanceSheet: { totalAssets, totalLiabilities, totalEquity, cashAndEquivalents }, + cashFlow: { operating: cfo, investing: cfi, financing: cff, capex }, + ratios: { + operatingMarginPct: ratioPct(operatingProfit.current, revenue.current), + netMarginPct: ratioPct(netIncome.current, revenue.current), + roePct, + debtToEquityPct: ratioPct(totalLiabilities.current, totalEquity.current), + revenueYoYPct: yoyPct(revenue), + operatingProfitYoYPct: yoyPct(operatingProfit), + netIncomeYoYPct: yoyPct(netIncome), + freeCashFlow, + freeCashFlowDisplay: formatKrw(freeCashFlow), + }, + }; +} diff --git a/src/tools/finance-kr/sub-tools/get-business-report.ts b/src/tools/finance-kr/sub-tools/get-business-report.ts index a5dffb7b1..816ad3896 100644 --- a/src/tools/finance-kr/sub-tools/get-business-report.ts +++ b/src/tools/finance-kr/sub-tools/get-business-report.ts @@ -1,9 +1,12 @@ import { DynamicStructuredTool } from '@langchain/core/tools'; import { z } from 'zod'; +import { existsSync, mkdirSync, writeFileSync, readdirSync, statSync, unlinkSync } from 'fs'; import { dartApi } from '../api.js'; import { resolveTicker } from '../../../data/ticker-registry.js'; +import { dexterPath } from '../../../utils/paths.js'; import { formatToolResult } from '../../types.js'; import { TTL_24H } from '../../finance/utils.js'; +import { summarizePeriod, type DartRow } from '../normalize-financials.js'; const REPORT_TYPE_CODES = { annual: '11011', // ์‚ฌ์—…๋ณด๊ณ ์„œ @@ -51,11 +54,46 @@ function defaultYear(): number { return new Date().getUTCFullYear() - 1; } +export const RAW_FILE_KEEP = 30; + +/** + * Keep .dexter/tool-results bounded: drop all but the most recent N raw dumps. + * Scoped to our own kr-financials-*.json files (never the agent's call_*.txt + * persists), never deletes the file just written (keepName), and tolerates files + * vanishing under concurrent calls. + */ +export function pruneRawFinancialFiles(dir: string, keepName: string): void { + try { + const entries = readdirSync(dir) + .filter((f) => f.startsWith('kr-financials-') && f.endsWith('.json') && f !== keepName) + .map((f) => { + try { + return { f, mtime: statSync(`${dir}/${f}`).mtimeMs }; + } catch { + return null; // vanished between readdir and stat + } + }) + .filter((e): e is { f: string; mtime: number } => e !== null) + .sort((a, b) => b.mtime - a.mtime); + for (const { f } of entries.slice(RAW_FILE_KEEP)) { + try { + unlinkSync(`${dir}/${f}`); + } catch { + // already removed by a concurrent call + } + } + } catch { + // best-effort cleanup + } +} + export const getBusinessReport = new DynamicStructuredTool({ name: 'get_business_report_kr', description: 'Fetches a Korean listed company\'s ์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ (annual/semiannual/quarterly statements) from DART. ' + - 'Returns full income statement, balance sheet, and cash flow line items as reported under K-IFRS.', + 'Returns a normalized per-period summary of key K-IFRS metrics โ€” revenue, operating profit, net income, EPS, ' + + 'assets/liabilities/equity, cash flow, capex, plus margins, ROE, FCF, and YoY โ€” in KRW. Full raw line items are ' + + 'persisted to rawLineItemsFile for drill-down.', schema: InputSchema, func: async (input) => { const ticker = input.ticker.trim(); @@ -92,9 +130,55 @@ export const getBusinessReport = new DynamicStructuredTool({ ); const urls = results.map((r) => r.url).filter(Boolean); - const successful = results.filter((r) => r.error === null); + const successful = results + .filter((r) => r.error === null) + .sort((a, b) => b.year - a.year); const failed = results.filter((r) => r.error !== null); + const periods = successful.map((r) => + summarizePeriod(r.list as DartRow[], { + bsns_year: r.year, + report_type: input.report_type, + fs_div: input.fs_div, + }), + ); + + // Persist full raw line items (pretty-printed so read_file can page them) for the + // rare drill-down into accounts the summary doesn't map โ€” banks/holding companies + // use labels (์˜์—…์ˆ˜์ต etc.) that may not match. The in-context payload stays small. + let rawLineItemsFile: string | undefined; + if (successful.length > 0) { + try { + const dir = dexterPath('tool-results'); + if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); + const years = successful.map((r) => r.year); + // corp_code is the only externally-sourced path component โ€” sanitize it so a + // malformed registry value can't write outside the results dir. + const safeCorp = resolved.corp_code.replace(/[^a-zA-Z0-9_-]/g, '_'); + const fileName = `kr-financials-${safeCorp}-${reprt_code}-${input.fs_div}-${Math.min(...years)}_${Math.max(...years)}.json`; + rawLineItemsFile = `${dir}/${fileName}`; + writeFileSync( + rawLineItemsFile, + JSON.stringify( + { + ticker, + corp_code: resolved.corp_code, + corp_name: resolved.corp_name, + report_type: input.report_type, + fs_div: input.fs_div, + periods: successful.map((r) => ({ bsns_year: r.year, list: r.list })), + }, + null, + 2, + ), + 'utf-8', + ); + pruneRawFinancialFiles(dir, fileName); + } catch { + rawLineItemsFile = undefined; // best-effort; the summary is the source of truth + } + } + return formatToolResult( { ticker, @@ -102,7 +186,13 @@ export const getBusinessReport = new DynamicStructuredTool({ corp_name: resolved.corp_name, report_type: input.report_type, fs_div: input.fs_div, - periods: successful.map((r) => ({ bsns_year: r.year, list: r.list })), + periods, + ...(rawLineItemsFile + ? { + rawLineItemsFile, + note: 'periods[].summary holds normalized key metrics (KRW). Use read_file on rawLineItemsFile only for accounts not in the summary.', + } + : {}), ...(failed.length > 0 ? { _errors: failed.map((r) => ({ year: r.year, error: r.error })) } : {}), }, urls, diff --git a/src/tools/registry.ts b/src/tools/registry.ts index fc2aadbed..be78e3572 100644 --- a/src/tools/registry.ts +++ b/src/tools/registry.ts @@ -3,7 +3,7 @@ import { createGetFinancials, createGetMarketData, createReadFilings, createScre import { exaSearch, perplexitySearch, tavilySearch, langSearch, WEB_SEARCH_DESCRIPTION, xSearchTool, X_SEARCH_DESCRIPTION } from './search/index.js'; import { createWebSearchTool, type WebSearchProvider } from './search/web-search.js'; import { getSetting } from '../utils/config.js'; -import type { SearchProviderId } from '../utils/env.js'; +import { checkApiKeyExists, type SearchProviderId } from '../utils/env.js'; import { skillTool, SKILL_TOOL_DESCRIPTION } from './skill.js'; import { webFetchTool, WEB_FETCH_DESCRIPTION } from './fetch/web-fetch.js'; import { browserTool, BROWSER_DESCRIPTION } from './browser/browser.js'; @@ -153,17 +153,19 @@ export function getToolRegistry(model: string): RegisteredTool[] { // Build web_search as a fallback chain over whichever providers have keys configured. // The user's preferred provider (set via /search) is tried first; the others act as fallbacks. + // checkApiKeyExists ignores empty and `your-` placeholder values โ€” otherwise a + // copied env.example registers a web_search tool that 401s on every call. const allWebSearchProviders: WebSearchProvider[] = []; - if (process.env.EXASEARCH_API_KEY) { + if (checkApiKeyExists('EXASEARCH_API_KEY')) { allWebSearchProviders.push({ id: 'exa', name: 'Exa', tool: exaSearch }); } - if (process.env.PERPLEXITY_API_KEY) { + if (checkApiKeyExists('PERPLEXITY_API_KEY')) { allWebSearchProviders.push({ id: 'perplexity', name: 'Perplexity', tool: perplexitySearch }); } - if (process.env.TAVILY_API_KEY) { + if (checkApiKeyExists('TAVILY_API_KEY')) { allWebSearchProviders.push({ id: 'tavily', name: 'Tavily', tool: tavilySearch }); } - if (process.env.LANGSEARCH_API_KEY) { + if (checkApiKeyExists('LANGSEARCH_API_KEY')) { allWebSearchProviders.push({ id: 'langsearch', name: 'LangSearch', tool: langSearch }); } From 8eb246b97f44109c71811425125c74558ba315c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SH=20KWON=20=F0=9F=87=B0=F0=9F=87=B7?= Date: Sun, 31 May 2026 20:30:44 +0900 Subject: [PATCH 08/70] KR eval harness: fixed question bank + record/replay + LLM-judge scoring (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * KR eval harness: fixed question bank + record/replay + LLM-judge scoring Extend the single-query seed (scripts/kr-eval.ts, unchanged) into a fixed-question + scorer harness for the Korean-stock agent path. scripts/kr-eval/ questions.ts โ€” 12-question bank + rubric metadata (expected/required tools, judge dimensions per question) replay.ts โ€” record (dump done.toolCalls โ†’ fixture) + replay (fixture-backed tool stubs via the Agent transformTools hook); match policy + _replay_miss sentinel. Records at the tool-output layer (free, since done.toolCalls already carries {tool,args,result}) rather than the fetch layer (no KRX cookie/ZIP/login reconstruction). judge.ts โ€” per-dimension LLM judges via callLlm + Zod {score,comment} (earnings_yoy / cross_signal / governance / grounding), grounded in the Korean playbook in src/agent/prompts.ts scorer.ts โ€” pure deterministic tool-firing gate + combine + aggregate runner.ts โ€” entrypoint: mode (live/record/replay), env-gated skip, replay dummy-env shim, run agent, score, report report.ts โ€” console report *.test.ts โ€” scorer + replay unit tests (16 tests, no network/LLM) src change (only production touch): optional transformTools hook on AgentConfig, applied in Agent.create() (default no-op) โ€” the eval seam replay uses to swap live KR tools for fixture stubs while the agent LLM still routes freely. package.json: kr-eval / kr-eval:record / kr-eval:replay scripts. Verified: bun run typecheck (src) + standalone tsc (scripts) clean; full suite 132 pass / 0 fail; kr-eval 16 pass / 0 fail; offline runner smoke test. Co-Authored-By: Claude Opus 4.8 (1M context) * KR eval: classify replay misses as inconclusive + add grounding to earnings Qs First live run surfaced two things on samsung-fundamentals: - replay diverged because the live agent took a tool path (read_file) the record run didn't, so the call had no fixture (replayMiss). A miss means the data wasn't fully pinned, so the score isn't comparable to a baseline. - the recorded get_financials_kr data has prior:null (no prior-year figures), yet the live answer scored earnings_yoy=1.0 โ€” i.e. the YoY was likely ungrounded. That question had no grounding dimension to catch it. Changes: - scorer: a result with replayMisses is marked `inconclusive` (new status), excluded from pass/fail counts, dimension stats, and the exit code โ€” so a non-faithful replay no longer reads as a false regression. report.ts renders INCONC + prompts to re-record the fixture. - questions: add `grounding` to samsung-fundamentals and cross-signal-alteogen so the judge cross-checks that YoY/figures are actually supported by tool data. Separately noted for follow-up: get_financials_kr returning prior:null for quarterly line items (DART frmtrm not mapped?) โ€” a possible KR-financials bug, outside this harness. Co-Authored-By: Claude Opus 4.8 (1M context) * KR financials: read quarterly prior-year from frmtrm_q_amount (fixes null YoY) get_financials_kr returned prior:null for every quarterly/semiannual P&L line, so revenue/operating-profit/net-income YoY was silently uncomputable โ€” and the agent would fill the gap from memory (ungrounded YoY). Surfaced by the KR eval harness on samsung-fundamentals. Root cause: for ๋ถ„๊ธฐ/๋ฐ˜๊ธฐ reports DART leaves frmtrm_amount empty on flow statements (IS/CIS/CF) and carries the prior-year SAME period in frmtrm_q_amount (์ „๋…„ ๋™๊ธฐ ๋ˆ„์ ). The annual report and the quarterly balance sheet have no _q field. toMetric only read frmtrm_amount. Fix: prefer frmtrm_q_amount, fall back to frmtrm_amount โ€” correct for all cases (annual โ†’ frmtrm_amount; quarterly flows โ†’ frmtrm_q_amount; quarterly BS โ†’ frmtrm_amount = ์ „๋…„๋ง). Verified against the real Samsung 2026 Q1 payload: revenue 133.9์กฐ vs 79.1์กฐ (+69.2%), OP +756%, NI +474% YoY. Co-Authored-By: Claude Opus 4.8 (1M context) * KR eval: first full fixture baseline + rubric calibration (12/12) Recorded all 12 questions live (KR_EVAL_MODE=record) โ†’ committed fixtures as the replay/regression baseline (156K total; small because we record tool outputs, not raw HTTP โ€” no corp-code ZIPs). The first full run scored 9/12 and surfaced a rubric mis-calibration (mine, not the agent's): the cross_signal dimension (which requires weaving ์ˆ˜๊ธ‰+์‹ค์ +์ง€๋ฐฐ๊ตฌ์กฐ into one thesis) was applied to single-signal questions that only gather one signal, and governance (deep ์ง€์ฃผ์‚ฌ/์Šน๊ณ„ valuation) to a factual ownership-change lookup. The genuine synthesis/governance questions scored well (samsung-synthesis cross_signal 0.86, alteogen 0.94, large-holders 0.92, spinoff 1.00). Recalibrated dimensions to match question intent: - foreign-flow-hynix: cross_signal โ†’ grounding - institutional-vs-foreign: cross_signal โ†’ grounding - insider-trades-celltrion: governance โ†’ grounding cross_signal/governance now apply only to questions that actually call for synthesis/analysis. Live re-run of the three: PASS (grounding 0.88/1.00/0.82). Also confirms the frmtrm_q_amount fix (a42fcc4): samsung-fundamentals now passes earnings_yoy=1.00 AND grounding=1.00 (previously the YoY was ungrounded). Note: fixtures hold a point-in-time market snapshot; re-record to refresh. * KR eval: address PR #8 review (digest budget, replay match, cleanups) - judge.toolDigest: split the budget EVENLY across tool calls instead of greedy first-fit, so a later tool's data is never dropped wholesale โ€” fixes biased grounding/cross_signal scoring on multi-tool answers. Add judge.test.ts. - replay.resolve: before falling back to the first recorded call, match on the entity key (ticker) so a multi-ticker tool can't return another ticker's data when only secondary args differ. - scorer: drop the unused compositeScore field (computed, never surfaced). - runner: record mode now exits 0 regardless of rubric scores (it's a capture action); pass/fail exit code applies to live/replay only. Comment the repeat>1 tool-firing caveat. - fixtures: drop meta.recordedAt so re-recording only diffs when data changes. - tsconfig.scripts.json + fold a scripts typecheck into `typecheck` so CI (bun run typecheck) now covers scripts/, which the project tsconfig excludes. typecheck (src + scripts) clean; bun test 137 pass / 0 fail. Co-Authored-By: Claude Opus 4.8 (1M context) * KR eval: relax governance threshold to 0.65 for synthesis questions Across 6 replay runs, governance on the two synthesis questions (samsung-synthesis, cross-signal-alteogen) clustered 0.70โ€“0.85 โ€” distinct from dedicated governance questions (large-holders 0.92+, spinoff 0.98). In a synthesis answer governance is one strand, not the focus, so the judge consistently scores it lower. The default 0.70 left two zero-margin cells (governance==0.70). Per-question threshold 0.65 adds a buffer without lowering the bar where governance IS the focus. Insurance, not a fix-for-failure: in 6 runs governance never dipped below 0.70, so both thresholds would have passed every run; this removes the boundary coin-flip. Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- package.json | 6 +- .../default/cross-signal-alteogen.json | 81 ++++++++ .../fixtures/default/filings-hyundai.json | 19 ++ .../fixtures/default/foreign-flow-hynix.json | 17 ++ .../default/insider-trades-celltrion.json | 17 ++ .../default/institutional-vs-foreign.json | 17 ++ .../default/large-holders-samsung.json | 17 ++ .../kr-eval/fixtures/default/nps-samsung.json | 17 ++ scripts/kr-eval/fixtures/default/nps-top.json | 16 ++ .../default/samsung-fundamentals.json | 16 ++ .../fixtures/default/samsung-synthesis.json | 74 +++++++ .../default/short-balance-ecopro.json | 17 ++ .../fixtures/default/spinoff-lgchem.json | 57 +++++ scripts/kr-eval/judge.test.ts | 29 +++ scripts/kr-eval/judge.ts | 116 +++++++++++ scripts/kr-eval/questions.ts | 147 +++++++++++++ scripts/kr-eval/replay.test.ts | 71 +++++++ scripts/kr-eval/replay.ts | 137 +++++++++++++ scripts/kr-eval/report.ts | 56 +++++ scripts/kr-eval/runner.ts | 194 ++++++++++++++++++ scripts/kr-eval/scorer.test.ts | 154 ++++++++++++++ scripts/kr-eval/scorer.ts | 179 ++++++++++++++++ src/agent/agent.ts | 3 +- src/agent/types.ts | 7 + .../finance-kr/normalize-financials.test.ts | 20 +- src/tools/finance-kr/normalize-financials.ts | 13 +- tsconfig.scripts.json | 8 + 27 files changed, 1501 insertions(+), 4 deletions(-) create mode 100644 scripts/kr-eval/fixtures/default/cross-signal-alteogen.json create mode 100644 scripts/kr-eval/fixtures/default/filings-hyundai.json create mode 100644 scripts/kr-eval/fixtures/default/foreign-flow-hynix.json create mode 100644 scripts/kr-eval/fixtures/default/insider-trades-celltrion.json create mode 100644 scripts/kr-eval/fixtures/default/institutional-vs-foreign.json create mode 100644 scripts/kr-eval/fixtures/default/large-holders-samsung.json create mode 100644 scripts/kr-eval/fixtures/default/nps-samsung.json create mode 100644 scripts/kr-eval/fixtures/default/nps-top.json create mode 100644 scripts/kr-eval/fixtures/default/samsung-fundamentals.json create mode 100644 scripts/kr-eval/fixtures/default/samsung-synthesis.json create mode 100644 scripts/kr-eval/fixtures/default/short-balance-ecopro.json create mode 100644 scripts/kr-eval/fixtures/default/spinoff-lgchem.json create mode 100644 scripts/kr-eval/judge.test.ts create mode 100644 scripts/kr-eval/judge.ts create mode 100644 scripts/kr-eval/questions.ts create mode 100644 scripts/kr-eval/replay.test.ts create mode 100644 scripts/kr-eval/replay.ts create mode 100644 scripts/kr-eval/report.ts create mode 100644 scripts/kr-eval/runner.ts create mode 100644 scripts/kr-eval/scorer.test.ts create mode 100644 scripts/kr-eval/scorer.ts create mode 100644 tsconfig.scripts.json diff --git a/package.json b/package.json index a0325ad89..4b479c2ac 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,13 @@ "dev": "bun --watch run src/index.tsx", "gateway": "tsx src/gateway/index.ts run", "gateway:login": "tsx src/gateway/index.ts login", - "typecheck": "tsc --noEmit", + "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.scripts.json", + "typecheck:scripts": "tsc --noEmit -p tsconfig.scripts.json", "test": "bun test", "test:watch": "bun test --watch", + "kr-eval": "bun run scripts/kr-eval/runner.ts", + "kr-eval:record": "KR_EVAL_MODE=record bun run scripts/kr-eval/runner.ts", + "kr-eval:replay": "KR_EVAL_MODE=replay bun run scripts/kr-eval/runner.ts", "postinstall": "playwright install chromium" }, "dependencies": { diff --git a/scripts/kr-eval/fixtures/default/cross-signal-alteogen.json b/scripts/kr-eval/fixtures/default/cross-signal-alteogen.json new file mode 100644 index 000000000..4bc47d200 --- /dev/null +++ b/scripts/kr-eval/fixtures/default/cross-signal-alteogen.json @@ -0,0 +1,81 @@ +{ + "id": "cross-signal-alteogen", + "query": "์•Œํ…Œ์˜ค์  (196170) ํˆฌ์ž ๋งค๋ ฅ ์ข…ํ•ฉ ํ‰๊ฐ€ํ•ด์ค˜ (์ˆ˜๊ธ‰ยท์‹ค์ ยท์ง€๋ฐฐ๊ตฌ์กฐ).", + "meta": { + "model": "gpt-5.5" + }, + "toolCalls": [ + { + "tool": "memory_search", + "args": { + "query": "์‚ฌ์šฉ์ž ํˆฌ์ž ๋ชฉํ‘œ ์œ„ํ—˜์„ฑํ–ฅ ํฌ์ง€์…˜ ์ œํ•œ ํ•œ๊ตญ์ฃผ์‹ ์•Œํ…Œ์˜ค์   196170 ๊ด€๋ จ ์„ ํ˜ธ" + }, + "result": "{\"data\":{\"results\":[]}}" + }, + { + "tool": "get_nps_holdings", + "args": { + "ticker": "196170", + "limit": 10 + }, + "result": "{\"data\":{\"source\":\"NPS ๊ตญ๋‚ด์ฃผ์‹ ํˆฌ์ž์ •๋ณด (data.go.kr, year-end snapshot)\",\"query\":{\"name\":null,\"ticker\":\"196170\",\"resolvedName\":\"์•Œํ…Œ์˜ค์  \"},\"holdings\":[{\"name\":\"์•Œํ…Œ์˜ค์  \",\"evalAmount\":3615,\"weightPct\":0.26,\"shareRatioPct\":2.19}]}}" + }, + { + "tool": "get_large_holders_kr", + "args": { + "ticker": "196170", + "limit": 30 + }, + "result": "{\"data\":{\"ticker\":\"196170\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"holders\":[{\"rcept_no\":\"20260407003590\",\"rcept_dt\":\"2026-04-07\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"๋ฐ•์ˆœ์žฌ\",\"stkqy\":\"10,931,550\",\"stkqy_irds\":\"-30,500\",\"stkrt\":\"20.40\",\"stkrt_irds\":\"-0.14\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"ํŠน๋ณ„๊ด€๊ณ„์ž ๋ณ€๋™ (์ž„์›์˜ ํ‡ด์ž„)\"},{\"rcept_no\":\"20250403003117\",\"rcept_dt\":\"2025-04-03\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"๋ฐ•์ˆœ์žฌ\",\"stkqy\":\"10,962,050\",\"stkqy_irds\":\"-95,430\",\"stkrt\":\"20.54\",\"stkrt_irds\":\"-0.26\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"ํŠน๋ณ„๊ด€๊ณ„์ž ๋ณ€๋™ (์ž„์›์˜ ์‹ ๊ทœ ์ž„ ๋ฐ ํ‡ด์ž„)\"},{\"rcept_no\":\"20241121000172\",\"rcept_dt\":\"2024-11-21\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"report_tp\":\"์•ฝ์‹\",\"repror\":\"ํ˜•์ธ์šฐ\",\"stkqy\":\"2,724,000\",\"stkqy_irds\":\"62,000\",\"stkrt\":\"5.11\",\"stkrt_irds\":\"0.12\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"์žฅ๋‚ด ๋งค์ˆ˜๋กœ ์ธํ•œ ์ง€๋ถ„์œจ ๋ณ€๋™ \"},{\"rcept_no\":\"20241030000276\",\"rcept_dt\":\"2024-10-30\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"report_tp\":\"์•ฝ์‹\",\"repror\":\"ํ˜•์ธ์šฐ\",\"stkqy\":\"2,662,000\",\"stkqy_irds\":\"962,000\",\"stkrt\":\"4.99\",\"stkrt_irds\":\"-1.06\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"๋ฌด์ƒ์ฆ์ž,์ง€๋ถ„๋ณ€๋™,์ด๋ฐœํ–‰์ฃผ์‹์ˆ˜ ์ฆ๊ฐ€๋กœ ์ธํ•œ ์ง€๋ถ„์œจ ๋ณ€๋™\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/majorstock.json?corp_code=00989619\"]}" + }, + { + "tool": "get_foreign_ownership_kr", + "args": { + "ticker": "196170", + "limit": 30 + }, + "result": "{\"data\":{\"ticker\":\"196170\",\"ownership\":[{\"date\":\"2026-05-29\",\"foreignHoldRatio\":14.69,\"foreignNetBuyQty\":-52380,\"orgNetBuyQty\":63376,\"individualNetBuyQty\":-11103,\"closePrice\":369000,\"tradingVolume\":524776},{\"date\":\"2026-05-28\",\"foreignHoldRatio\":14.78,\"foreignNetBuyQty\":-59350,\"orgNetBuyQty\":28932,\"individualNetBuyQty\":29399,\"closePrice\":369500,\"tradingVolume\":474757},{\"date\":\"2026-05-27\",\"foreignHoldRatio\":14.82,\"foreignNetBuyQty\":124100,\"orgNetBuyQty\":177641,\"individualNetBuyQty\":-304662,\"closePrice\":386500,\"tradingVolume\":948649},{\"date\":\"2026-05-26\",\"foreignHoldRatio\":14.29,\"foreignNetBuyQty\":-75626,\"orgNetBuyQty\":28015,\"individualNetBuyQty\":40719,\"closePrice\":365500,\"tradingVolume\":464199},{\"date\":\"2026-05-22\",\"foreignHoldRatio\":14.45,\"foreignNetBuyQty\":148954,\"orgNetBuyQty\":51563,\"individualNetBuyQty\":-195745,\"closePrice\":364500,\"tradingVolume\":748378},{\"date\":\"2026-05-21\",\"foreignHoldRatio\":14.07,\"foreignNetBuyQty\":-29380,\"orgNetBuyQty\":-23872,\"individualNetBuyQty\":49797,\"closePrice\":351500,\"tradingVolume\":444911},{\"date\":\"2026-05-20\",\"foreignHoldRatio\":14.15,\"foreignNetBuyQty\":-47664,\"orgNetBuyQty\":47417,\"individualNetBuyQty\":224,\"closePrice\":359500,\"tradingVolume\":436772},{\"date\":\"2026-05-19\",\"foreignHoldRatio\":14.24,\"foreignNetBuyQty\":-42540,\"orgNetBuyQty\":67674,\"individualNetBuyQty\":-29331,\"closePrice\":366500,\"tradingVolume\":754483},{\"date\":\"2026-05-18\",\"foreignHoldRatio\":14.34,\"foreignNetBuyQty\":-66312,\"orgNetBuyQty\":-13072,\"individualNetBuyQty\":82558,\"closePrice\":357500,\"tradingVolume\":490438},{\"date\":\"2026-05-15\",\"foreignHoldRatio\":14.57,\"foreignNetBuyQty\":-78600,\"orgNetBuyQty\":5850,\"individualNetBuyQty\":73006,\"closePrice\":369000,\"tradingVolume\":706370}]},\"sourceUrls\":[\"https://m.stock.naver.com/api/stock/196170/trend\"]}" + }, + { + "tool": "get_insider_trades_kr", + "args": { + "ticker": "196170", + "limit": 30 + }, + "result": "{\"data\":{\"ticker\":\"196170\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"trades\":[{\"rcept_no\":\"20250521000476\",\"rcept_dt\":\"2025-05-21\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"repror\":\"์ „ํƒœ์—ฐ\",\"isu_exctv_rgist_at\":\"๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"๋ถ€์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"7,200\",\"sp_stock_lmp_irds_cnt\":\"7,200\",\"sp_stock_lmp_rate\":\"0.01\",\"sp_stock_lmp_irds_rate\":\"0.01\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/elestock.json?corp_code=00989619\"]}" + }, + { + "tool": "get_filings_kr", + "args": { + "ticker": "196170", + "filing_type": "material", + "limit": 30, + "start_date": "2025-01-01", + "end_date": "2026-05-31" + }, + "result": "{\"data\":{\"ticker\":\"196170\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"filings\":[{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์œ ์ƒ์ฆ์ž๊ฒฐ์ •)\",\"rcept_no\":\"20250204000409\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20250204\",\"rm\":\"\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/list.json?corp_code=00989619&bgn_de=20250101&end_de=20260531&pblntf_ty=B&page_count=30&page_no=1&sort=date&sort_mth=desc\"]}" + }, + { + "tool": "get_short_balance_kr", + "args": { + "ticker": "196170", + "limit": 30 + }, + "result": "{\"data\":{\"ticker\":\"196170\",\"isin\":\"KR7196170005\",\"name\":\"์•Œํ…Œ์˜ค์  \",\"short\":[{\"date\":\"2026-05-27\",\"balanceQty\":1205800,\"listedShares\":53586343,\"balanceAmount\":466041700000,\"marketCap\":20711121569500,\"balanceRatio\":2.25},{\"date\":\"2026-05-26\",\"balanceQty\":1286670,\"listedShares\":53586343,\"balanceAmount\":470277885000,\"marketCap\":19585808366500,\"balanceRatio\":2.4},{\"date\":\"2026-05-22\",\"balanceQty\":1243707,\"listedShares\":53586343,\"balanceAmount\":453331201500,\"marketCap\":19532222023500,\"balanceRatio\":2.32},{\"date\":\"2026-05-21\",\"balanceQty\":1333634,\"listedShares\":53586343,\"balanceAmount\":468772351000,\"marketCap\":18835599564500,\"balanceRatio\":2.49},{\"date\":\"2026-05-20\",\"balanceQty\":1321033,\"listedShares\":53586343,\"balanceAmount\":474911363500,\"marketCap\":19264290308500,\"balanceRatio\":2.47},{\"date\":\"2026-05-19\",\"balanceQty\":1305095,\"listedShares\":53586343,\"balanceAmount\":478317317500,\"marketCap\":19639394709500,\"balanceRatio\":2.44},{\"date\":\"2026-05-18\",\"balanceQty\":1329259,\"listedShares\":53586343,\"balanceAmount\":475210092500,\"marketCap\":19157117622500,\"balanceRatio\":2.48},{\"date\":\"2026-05-15\",\"balanceQty\":1305448,\"listedShares\":53533843,\"balanceAmount\":481710312000,\"marketCap\":19753988067000,\"balanceRatio\":2.44},{\"date\":\"2026-05-14\",\"balanceQty\":1283139,\"listedShares\":53533843,\"balanceAmount\":494008515000,\"marketCap\":20610529555000,\"balanceRatio\":2.4},{\"date\":\"2026-05-13\",\"balanceQty\":1444301,\"listedShares\":53533843,\"balanceAmount\":511282554000,\"marketCap\":18950980422000,\"balanceRatio\":2.7},{\"date\":\"2026-05-12\",\"balanceQty\":1312876,\"listedShares\":53533843,\"balanceAmount\":449003592000,\"marketCap\":18308574306000,\"balanceRatio\":2.45},{\"date\":\"2026-05-11\",\"balanceQty\":1392048,\"listedShares\":53533843,\"balanceAmount\":452415600000,\"marketCap\":17398498975000,\"balanceRatio\":2.6},{\"date\":\"2026-05-08\",\"balanceQty\":1386297,\"listedShares\":53533843,\"balanceAmount\":472034128500,\"marketCap\":18228273541500,\"balanceRatio\":2.59},{\"date\":\"2026-05-07\",\"balanceQty\":1347885,\"listedShares\":53533843,\"balanceAmount\":480521002500,\"marketCap\":19084815029500,\"balanceRatio\":2.52},{\"date\":\"2026-05-06\",\"balanceQty\":1327836,\"listedShares\":53533843,\"balanceAmount\":482668386000,\"marketCap\":19459551930500,\"balanceRatio\":2.48},{\"date\":\"2026-05-04\",\"balanceQty\":1301448,\"listedShares\":53533843,\"balanceAmount\":485440104000,\"marketCap\":19968123439000,\"balanceRatio\":2.43}]},\"sourceUrls\":[\"https://data.krx.co.kr/comm/bldAttendant/getJsonData.cmd?bld=dbms%2FMDC%2FSTAT%2Fsrt%2FMDCSTAT30502\"]}" + }, + { + "tool": "get_financials_kr", + "args": { + "query": "์•Œํ…Œ์˜ค์   196170 ์ตœ๊ทผ 3~5๋…„ ์—ฐ๊ฐ„ ๋ฐ ์ตœ๊ทผ ๋ถ„๊ธฐ K-IFRS ์š”์•ฝ: ๋งค์ถœ์•ก, ์˜์—…์ด์ต, ์ˆœ์ด์ต, ์˜์—…์ด์ต๋ฅ , ์ˆœ์ด์ต๋ฅ , ROE, ๋ถ€์ฑ„๋น„์œจ, ํ˜„๊ธˆํ๋ฆ„" + }, + "result": "{\"data\":{\"get_business_report_kr_196170\":{\"ticker\":\"196170\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"report_type\":\"quarterly_1\",\"fs_div\":\"CFS\",\"periods\":[{\"bsns_year\":2026,\"report_type\":\"quarterly_1\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"1๋ถ„๊ธฐ ๋ˆ„์ (thstrm=๋‹น๊ธฐ, frmtrm=์ „๋…„ ๋™๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„์€ YTD; BS๋Š” ๋ถ„๊ธฐ๋ง vs ์ „๋…„๋ง.\",\"incomeStatement\":{\"revenue\":{\"current\":71598550624,\"prior\":83696077912,\"label\":\"์ˆ˜์ต(๋งค์ถœ์•ก)\",\"display\":\"716์–ต\"},\"operatingProfit\":{\"current\":39300649498,\"prior\":61017565621,\"label\":\"์˜์—…์ด์ต(์†์‹ค)\",\"display\":\"393์–ต\"},\"netIncome\":{\"current\":71344275231,\"prior\":83037817151,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"713์–ต\"},\"controllingNetIncome\":{\"current\":71697430092,\"prior\":83176892415,\"label\":\"์ง€๋ฐฐ๊ธฐ์—…์˜ ์†Œ์œ ์ฃผ์—๊ฒŒ ๊ท€์†๋˜๋Š” ๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"717์–ต\"},\"eps\":{\"current\":1340,\"prior\":1561,\"label\":\"๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต(์†์‹ค)\",\"display\":\"1,340์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":729469655232,\"prior\":698742517170,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"7,295์–ต\"},\"totalLiabilities\":{\"current\":216611535645,\"prior\":244299577022,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"2,166์–ต\"},\"totalEquity\":{\"current\":512858119587,\"prior\":454442940148,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"5,129์–ต\"},\"cashAndEquivalents\":{\"current\":42276215274,\"prior\":66380706542,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"423์–ต\"}},\"cashFlow\":{\"operating\":{\"current\":20990009464,\"prior\":-4818704171,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"210์–ต\"},\"investing\":{\"current\":-45527020941,\"prior\":-122430965346,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-455์–ต\"},\"financing\":{\"current\":-97920386,\"prior\":153406458502,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-97,920,386\"},\"capex\":{\"current\":13857283233,\"prior\":875102242,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"139์–ต\"}},\"ratios\":{\"operatingMarginPct\":54.9,\"netMarginPct\":99.6,\"roePct\":null,\"debtToEquityPct\":42.2,\"revenueYoYPct\":-14.5,\"operatingProfitYoYPct\":-35.6,\"netIncomeYoYPct\":-14.1,\"freeCashFlow\":7132726231,\"freeCashFlowDisplay\":\"71์–ต\"}}],\"rawLineItemsFile\":\".dexter/tool-results/kr-financials-00989619-11013-CFS-2026_2026.json\",\"note\":\"periods[].summary holds normalized key metrics (KRW). Use read_file on rawLineItemsFile only for accounts not in the summary.\"}},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2025&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2024&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2023&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2022&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2021&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2026&reprt_code=11013&fs_div=CFS\"]}" + }, + { + "tool": "get_financials_kr", + "args": { + "query": "์•Œํ…Œ์˜ค์   196170 2021 2022 2023 2024 2025 ์‚ฌ์—…๋ณด๊ณ ์„œ ์—ฐ๊ฒฐ K-IFRS ์—ฐ๊ฐ„ ์š”์•ฝ ๋งค์ถœ์•ก ์˜์—…์ด์ต ๋‹น๊ธฐ์ˆœ์ด์ต ์˜์—…์ด์ต๋ฅ  ์ˆœ์ด์ต๋ฅ  ROE ๋ถ€์ฑ„๋น„์œจ" + }, + "result": "{\"data\":{\"get_business_report_kr_196170\":{\"ticker\":\"196170\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"periods\":[{\"bsns_year\":2025,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":215858918957,\"prior\":102854620800,\"label\":\"์ˆ˜์ต(๋งค์ถœ์•ก)\",\"display\":\"2,159์–ต\"},\"operatingProfit\":{\"current\":106900077097,\"prior\":25403990856,\"label\":\"์˜์—…์ด์ต(์†์‹ค)\",\"display\":\"1,069์–ต\"},\"netIncome\":{\"current\":145172981930,\"prior\":60690355931,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"1,452์–ต\"},\"controllingNetIncome\":{\"current\":141666024710,\"prior\":62252318998,\"label\":\"์ง€๋ฐฐ๊ธฐ์—…์˜ ์†Œ์œ ์ฃผ์—๊ฒŒ ๊ท€์†๋˜๋Š” ๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"1,417์–ต\"},\"eps\":{\"current\":2653,\"prior\":1172,\"label\":\"๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต(์†์‹ค)\",\"display\":\"2,653์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":698742517170,\"prior\":409011974213,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"6,987์–ต\"},\"totalLiabilities\":{\"current\":244299577022,\"prior\":134920913122,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"2,443์–ต\"},\"totalEquity\":{\"current\":454442940148,\"prior\":274091061091,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"4,544์–ต\"},\"cashAndEquivalents\":{\"current\":66380706542,\"prior\":18993906684,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"664์–ต\"}},\"cashFlow\":{\"operating\":{\"current\":124127150186,\"prior\":53044297258,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"1,241์–ต\"},\"investing\":{\"current\":-231120222423,\"prior\":-107771450399,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-2,311์–ต\"},\"financing\":{\"current\":155645065971,\"prior\":43258945323,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"1,556์–ต\"},\"capex\":{\"current\":23006600667,\"prior\":2718373772,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"230์–ต\"}},\"ratios\":{\"operatingMarginPct\":49.5,\"netMarginPct\":67.3,\"roePct\":31.9,\"debtToEquityPct\":53.8,\"revenueYoYPct\":109.9,\"operatingProfitYoYPct\":320.8,\"netIncomeYoYPct\":139.2,\"freeCashFlow\":101120549519,\"freeCashFlowDisplay\":\"1,011์–ต\"}},{\"bsns_year\":2024,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":102854620800,\"prior\":96522928887,\"label\":\"์ˆ˜์ต(๋งค์ถœ์•ก)\",\"display\":\"1,029์–ต\"},\"operatingProfit\":{\"current\":25403990856,\"prior\":-9736838487,\"label\":\"์˜์—…์ด์ต(์†์‹ค)\",\"display\":\"254์–ต\"},\"netIncome\":{\"current\":60690355931,\"prior\":-3582208686,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"607์–ต\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":1172,\"prior\":-65,\"label\":\"๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต(์†์‹ค)\",\"display\":\"1,172์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":409011974213,\"prior\":256099994583,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"4,090์–ต\"},\"totalLiabilities\":{\"current\":134920913122,\"prior\":108258260247,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"1,349์–ต\"},\"totalEquity\":{\"current\":274091061091,\"prior\":147841734336,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"2,741์–ต\"},\"cashAndEquivalents\":{\"current\":18993906684,\"prior\":30199528819,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"190์–ต\"}},\"cashFlow\":{\"operating\":{\"current\":53044297258,\"prior\":-7765575368,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"530์–ต\"},\"investing\":{\"current\":-107771450399,\"prior\":-9343034472,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-1,078์–ต\"},\"financing\":{\"current\":43258945323,\"prior\":1736653090,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"433์–ต\"},\"capex\":{\"current\":2718373772,\"prior\":1219312216,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"27์–ต\"}},\"ratios\":{\"operatingMarginPct\":24.7,\"netMarginPct\":59,\"roePct\":22.1,\"debtToEquityPct\":49.2,\"revenueYoYPct\":6.6,\"operatingProfitYoYPct\":360.9,\"netIncomeYoYPct\":1794.2,\"freeCashFlow\":50325923486,\"freeCashFlowDisplay\":\"503์–ต\"}},{\"bsns_year\":2023,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":96522928887,\"prior\":28806344498,\"label\":\"์ˆ˜์ต(๋งค์ถœ์•ก)\",\"display\":\"965์–ต\"},\"operatingProfit\":{\"current\":-9736838487,\"prior\":-29373680278,\"label\":\"์˜์—…์ด์ต(์†์‹ค)\",\"display\":\"-97์–ต\"},\"netIncome\":{\"current\":-3582208686,\"prior\":-10077904116,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"-36์–ต\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":-65,\"prior\":-159,\"label\":\"๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต(์†์‹ค)\",\"display\":\"-65์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":256099994583,\"prior\":245490136097,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"2,561์–ต\"},\"totalLiabilities\":{\"current\":108258260247,\"prior\":100108123094,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"1,083์–ต\"},\"totalEquity\":{\"current\":147841734336,\"prior\":145382013003,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"1,478์–ต\"},\"cashAndEquivalents\":{\"current\":30199528819,\"prior\":45828916763,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"302์–ต\"}},\"cashFlow\":{\"operating\":{\"current\":-7765575368,\"prior\":-17874625308,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-78์–ต\"},\"investing\":{\"current\":-9343034472,\"prior\":50213127538,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-93์–ต\"},\"financing\":{\"current\":1736653090,\"prior\":2918943647,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"17์–ต\"},\"capex\":{\"current\":1219312216,\"prior\":6423379588,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"12์–ต\"}},\"ratios\":{\"operatingMarginPct\":-10.1,\"netMarginPct\":-3.7,\"roePct\":-2.4,\"debtToEquityPct\":73.2,\"revenueYoYPct\":235.1,\"operatingProfitYoYPct\":66.9,\"netIncomeYoYPct\":64.5,\"freeCashFlow\":-8984887584,\"freeCashFlowDisplay\":\"-90์–ต\"}},{\"bsns_year\":2022,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":28806344498,\"prior\":38745944027,\"label\":\"์ˆ˜์ต(๋งค์ถœ์•ก)\",\"display\":\"288์–ต\"},\"operatingProfit\":{\"current\":-29373680278,\"prior\":-15192014489,\"label\":\"์˜์—…์ด์ต(์†์‹ค)\",\"display\":\"-294์–ต\"},\"netIncome\":{\"current\":-10077904116,\"prior\":-11587524002,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"-101์–ต\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":-159,\"prior\":-124,\"label\":\"๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต(์†์‹ค)\",\"display\":\"-159์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":245490136097,\"prior\":258318546360,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"2,455์–ต\"},\"totalLiabilities\":{\"current\":100108123094,\"prior\":126791463008,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"1,001์–ต\"},\"totalEquity\":{\"current\":145382013003,\"prior\":131527083352,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"1,454์–ต\"},\"cashAndEquivalents\":{\"current\":45828916763,\"prior\":10539625956,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"458์–ต\"}},\"cashFlow\":{\"operating\":{\"current\":-17874625308,\"prior\":-9460530126,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-179์–ต\"},\"investing\":{\"current\":50213127538,\"prior\":-125919778791,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"502์–ต\"},\"financing\":{\"current\":2918943647,\"prior\":107003154810,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"29์–ต\"},\"capex\":{\"current\":6423379588,\"prior\":13585166384,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"64์–ต\"}},\"ratios\":{\"operatingMarginPct\":-102,\"netMarginPct\":-35,\"roePct\":-6.9,\"debtToEquityPct\":68.9,\"revenueYoYPct\":-25.7,\"operatingProfitYoYPct\":-93.3,\"netIncomeYoYPct\":13,\"freeCashFlow\":-24298004896,\"freeCashFlowDisplay\":\"-243์–ต\"}},{\"bsns_year\":2021,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":41123115244,\"prior\":42425842457,\"label\":\"์ˆ˜์ต(๋งค์ถœ์•ก)\",\"display\":\"411์–ต\"},\"operatingProfit\":{\"current\":-12795830212,\"prior\":-58168688,\"label\":\"์˜์—…์ด์ต(์†์‹ค)\",\"display\":\"-128์–ต\"},\"netIncome\":{\"current\":-9191339725,\"prior\":-18274586780,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"-92์–ต\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":-92,\"prior\":-377,\"label\":\"๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต(์†์‹ค)\",\"display\":\"-92์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":260337559420,\"prior\":159206928125,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"2,603์–ต\"},\"totalLiabilities\":{\"current\":126414291791,\"prior\":98625173163,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"1,264์–ต\"},\"totalEquity\":{\"current\":133923267629,\"prior\":60581754962,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"1,339์–ต\"},\"cashAndEquivalents\":{\"current\":10539625956,\"prior\":38922455345,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"105์–ต\"}},\"cashFlow\":{\"operating\":{\"current\":-9419517066,\"prior\":1512244562,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-94์–ต\"},\"investing\":{\"current\":-125960791851,\"prior\":-42916978884,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-1,260์–ต\"},\"financing\":{\"current\":107003154810,\"prior\":76085158900,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"1,070์–ต\"},\"capex\":{\"current\":13585166384,\"prior\":5121455610,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"136์–ต\"}},\"ratios\":{\"operatingMarginPct\":-31.1,\"netMarginPct\":-22.4,\"roePct\":-6.9,\"debtToEquityPct\":94.4,\"revenueYoYPct\":-3.1,\"operatingProfitYoYPct\":-21897.8,\"netIncomeYoYPct\":49.7,\"freeCashFlow\":-23004683450,\"freeCashFlowDisplay\":\"-230์–ต\"}}],\"rawLineItemsFile\":\".dexter/tool-results/kr-financials-00989619-11011-CFS-2021_2025.json\",\"note\":\"periods[].summary holds normalized key metrics (KRW). Use read_file on rawLineItemsFile only for accounts not in the summary.\"}},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2025&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2024&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2023&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2022&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2021&reprt_code=11011&fs_div=CFS\"]}" + } + ] +} diff --git a/scripts/kr-eval/fixtures/default/filings-hyundai.json b/scripts/kr-eval/fixtures/default/filings-hyundai.json new file mode 100644 index 000000000..8486cb9b5 --- /dev/null +++ b/scripts/kr-eval/fixtures/default/filings-hyundai.json @@ -0,0 +1,19 @@ +{ + "id": "filings-hyundai", + "query": "ํ˜„๋Œ€์ฐจ(005380) ์ตœ๊ทผ ์ฃผ์š” ๊ณต์‹œ ๋ญ ์žˆ์—ˆ์–ด?", + "meta": { + "model": "gpt-5.5" + }, + "toolCalls": [ + { + "tool": "get_filings_kr", + "args": { + "ticker": "005380", + "limit": 20, + "start_date": "2025-05-31", + "end_date": "2026-05-31" + }, + "result": "{\"data\":{\"ticker\":\"005380\",\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"filings\":[{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ ๊ณ„ํš(์ž์œจ๊ณต์‹œ) (2025๋…„ ์ดํ–‰ํ˜„ํ™ฉ)\",\"rcept_no\":\"20260529802007\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260529\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"ํŠน์ˆ˜๊ด€๊ณ„์ธ์œผ๋กœ๋ถ€ํ„ฐ๊ธฐํƒ€์œ ๊ฐ€์ฆ๊ถŒ๋งค์ˆ˜\",\"rcept_no\":\"20260529002065\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260529\",\"rm\":\"๊ณต\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๋Œ€๊ทœ๋ชจ๊ธฐ์—…์ง‘๋‹จํ˜„ํ™ฉ๊ณต์‹œ[์—ฐ1ํšŒ๊ณต์‹œ๋ฐ1/4๋ถ„๊ธฐ์šฉ(๋Œ€ํ‘œํšŒ์‚ฌ)]\",\"rcept_no\":\"20260529001880\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260529\",\"rm\":\"๊ณต\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๋Œ€๊ทœ๋ชจ๊ธฐ์—…์ง‘๋‹จํ˜„ํ™ฉ๊ณต์‹œ[์—ฐ1ํšŒ(๋™์ผ์ธ์šฉ)]\",\"rcept_no\":\"20260529001416\",\"flr_nm\":\"์ •์˜์„ \",\"rcept_dt\":\"20260529\",\"rm\":\"๊ณต\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"[๊ธฐ์žฌ์ •์ •]ํƒ€๋ฒ•์ธ์ฃผ์‹๋ฐ์ถœ์ž์ฆ๊ถŒ์ทจ๋“๊ฒฐ์ •(์ž์œจ๊ณต์‹œ) \",\"rcept_no\":\"20260521800573\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260521\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๊ธฐ์—…์„ค๋ช…ํšŒ(IR)๊ฐœ์ตœ(์•ˆ๋‚ด๊ณต์‹œ) \",\"rcept_no\":\"20260520800521\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260520\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๊ธฐ์—…์„ค๋ช…ํšŒ(IR)๊ฐœ์ตœ(์•ˆ๋‚ด๊ณต์‹œ) \",\"rcept_no\":\"20260518800599\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260518\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๋ถ„๊ธฐ๋ณด๊ณ ์„œ (2026.03)\",\"rcept_no\":\"20260515002418\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260515\",\"rm\":\"\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"ํŠน์ˆ˜๊ด€๊ณ„์ธ์œผ๋กœ๋ถ€ํ„ฐ๊ธฐํƒ€์œ ๊ฐ€์ฆ๊ถŒ๋งค์ˆ˜\",\"rcept_no\":\"20260511000643\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260511\",\"rm\":\"๊ณต\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260511000484\",\"flr_nm\":\"๊น€์˜ํ›ˆ\",\"rcept_dt\":\"20260511\",\"rm\":\"\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๊ธฐ์—…์„ค๋ช…ํšŒ(IR)๊ฐœ์ตœ(์•ˆ๋‚ด๊ณต์‹œ) \",\"rcept_no\":\"20260506800556\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260506\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"์˜์—…(์ž ์ •)์‹ค์ (๊ณต์ •๊ณต์‹œ) \",\"rcept_no\":\"20260504800532\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260504\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"ํ’๋ฌธ๋˜๋Š”๋ณด๋„์—๋Œ€ํ•œํ•ด๋ช…(๋ฏธํ™•์ •) \",\"rcept_no\":\"20260430801200\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260430\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260430000968\",\"flr_nm\":\"์ด์„ฑํ˜ธ\",\"rcept_dt\":\"20260430\",\"rm\":\"\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๊ธฐ์—…์„ค๋ช…ํšŒ(IR)๊ฐœ์ตœ(์•ˆ๋‚ด๊ณต์‹œ) \",\"rcept_no\":\"20260429800396\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260429\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"[๊ธฐ์žฌ์ •์ •]์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260428000498\",\"flr_nm\":\"๊น€์ค€์—ฝ\",\"rcept_dt\":\"20260428\",\"rm\":\"\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"[๊ธฐ์žฌ์ •์ •]์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260428000487\",\"flr_nm\":\"๊น€์ค€์—ฝ\",\"rcept_dt\":\"20260428\",\"rm\":\"\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"ํƒ€๋ฒ•์ธ์ฃผ์‹๋ฐ์ถœ์ž์ฆ๊ถŒ์ทจ๋“๊ฒฐ์ •(์ž์œจ๊ณต์‹œ) \",\"rcept_no\":\"20260424800636\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260424\",\"rm\":\"์œ ์ •\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๊ณต์ •๊ฑฐ๋ž˜์ž์œจ์ค€์ˆ˜ํ”„๋กœ๊ทธ๋žจ์šด์˜ํ˜„ํ™ฉ(์•ˆ๋‚ด๊ณต์‹œ) \",\"rcept_no\":\"20260423800640\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260423\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"์•ฝ๊ด€์—์˜ํ•œ๊ธˆ์œต๊ฑฐ๋ž˜์‹œ๊ณ„์—ด๊ธˆ์œตํšŒ์‚ฌ์˜๊ฑฐ๋ž˜์ƒ๋Œ€๋ฐฉ์˜๊ณต์‹œ\",\"rcept_no\":\"20260423000562\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260423\",\"rm\":\"๊ณต\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/list.json?corp_code=00164742&bgn_de=20250531&end_de=20260531&page_count=20&page_no=1&sort=date&sort_mth=desc\"]}" + } + ] +} diff --git a/scripts/kr-eval/fixtures/default/foreign-flow-hynix.json b/scripts/kr-eval/fixtures/default/foreign-flow-hynix.json new file mode 100644 index 000000000..eb9bd56bd --- /dev/null +++ b/scripts/kr-eval/fixtures/default/foreign-flow-hynix.json @@ -0,0 +1,17 @@ +{ + "id": "foreign-flow-hynix", + "query": "SKํ•˜์ด๋‹‰์Šค(000660) ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจํ•˜๊ณ  ์ตœ๊ทผ ์ˆœ๋งค์ˆ˜ ํ๋ฆ„ ์–ด๋•Œ?", + "meta": { + "model": "gpt-5.5" + }, + "toolCalls": [ + { + "tool": "get_foreign_ownership_kr", + "args": { + "ticker": "000660", + "limit": 30 + }, + "result": "{\"data\":{\"ticker\":\"000660\",\"ownership\":[{\"date\":\"2026-05-29\",\"foreignHoldRatio\":51.44,\"foreignNetBuyQty\":-437330,\"orgNetBuyQty\":-42513,\"individualNetBuyQty\":473879,\"closePrice\":2333000,\"tradingVolume\":7066332},{\"date\":\"2026-05-28\",\"foreignHoldRatio\":51.5,\"foreignNetBuyQty\":-784789,\"orgNetBuyQty\":22893,\"individualNetBuyQty\":725316,\"closePrice\":2289000,\"tradingVolume\":6229499},{\"date\":\"2026-05-27\",\"foreignHoldRatio\":51.65,\"foreignNetBuyQty\":-18203,\"orgNetBuyQty\":529897,\"individualNetBuyQty\":-481602,\"closePrice\":2243000,\"tradingVolume\":7545783},{\"date\":\"2026-05-26\",\"foreignHoldRatio\":51.62,\"foreignNetBuyQty\":-108495,\"orgNetBuyQty\":386967,\"individualNetBuyQty\":-271309,\"closePrice\":2052000,\"tradingVolume\":4903591},{\"date\":\"2026-05-22\",\"foreignHoldRatio\":51.61,\"foreignNetBuyQty\":-158042,\"orgNetBuyQty\":152990,\"individualNetBuyQty\":1150,\"closePrice\":1941000,\"tradingVolume\":3135190},{\"date\":\"2026-05-21\",\"foreignHoldRatio\":51.66,\"foreignNetBuyQty\":-419491,\"orgNetBuyQty\":738512,\"individualNetBuyQty\":-337068,\"closePrice\":1940000,\"tradingVolume\":5096690},{\"date\":\"2026-05-20\",\"foreignHoldRatio\":51.76,\"foreignNetBuyQty\":-704213,\"orgNetBuyQty\":240569,\"individualNetBuyQty\":447712,\"closePrice\":1745000,\"tradingVolume\":5535123},{\"date\":\"2026-05-19\",\"foreignHoldRatio\":51.87,\"foreignNetBuyQty\":-1128221,\"orgNetBuyQty\":-122347,\"individualNetBuyQty\":1224285,\"closePrice\":1745000,\"tradingVolume\":4575855},{\"date\":\"2026-05-18\",\"foreignHoldRatio\":52.06,\"foreignNetBuyQty\":-559756,\"orgNetBuyQty\":267189,\"individualNetBuyQty\":292885,\"closePrice\":1840000,\"tradingVolume\":6481608},{\"date\":\"2026-05-15\",\"foreignHoldRatio\":52.14,\"foreignNetBuyQty\":-1391973,\"orgNetBuyQty\":-497490,\"individualNetBuyQty\":1855635,\"closePrice\":1819000,\"tradingVolume\":7485233}]},\"sourceUrls\":[\"https://m.stock.naver.com/api/stock/000660/trend\"]}" + } + ] +} diff --git a/scripts/kr-eval/fixtures/default/insider-trades-celltrion.json b/scripts/kr-eval/fixtures/default/insider-trades-celltrion.json new file mode 100644 index 000000000..c13fca352 --- /dev/null +++ b/scripts/kr-eval/fixtures/default/insider-trades-celltrion.json @@ -0,0 +1,17 @@ +{ + "id": "insider-trades-celltrion", + "query": "์ตœ๊ทผ ์…€ํŠธ๋ฆฌ์˜จ(068270) ์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ ์†Œ์œ  ๋ณ€๋™ ์žˆ์—ˆ์–ด?", + "meta": { + "model": "gpt-5.5" + }, + "toolCalls": [ + { + "tool": "get_insider_trades_kr", + "args": { + "ticker": "068270", + "limit": 20 + }, + "result": "{\"data\":{\"ticker\":\"068270\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"trades\":[{\"rcept_no\":\"20260529002003\",\"rcept_dt\":\"2026-05-29\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"์ตœ๋ณ‘์„œ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ „๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"0\",\"sp_stock_lmp_irds_cnt\":\"-3,000\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260430001724\",\"rcept_dt\":\"2026-04-30\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"์žฅ์†Œ์šฉ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ด์‚ฌ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"0\",\"sp_stock_lmp_irds_cnt\":\"-3,000\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260430001708\",\"rcept_dt\":\"2026-04-30\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"์‹ ์„ ๋ฏธ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"8,232\",\"sp_stock_lmp_irds_cnt\":\"1,279\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260430001711\",\"rcept_dt\":\"2026-04-30\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"์ด์ˆ˜์˜\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"๋ถ€์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"16,315\",\"sp_stock_lmp_irds_cnt\":\"1,087\",\"sp_stock_lmp_rate\":\"0.01\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260430001713\",\"rcept_dt\":\"2026-04-30\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"์–‘์„ฑ์šฑ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ „๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"7,000\",\"sp_stock_lmp_irds_cnt\":\"3,040\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260430001714\",\"rcept_dt\":\"2026-04-30\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"์‹ ๋ฏผ์ฒ \",\"isu_exctv_rgist_at\":\"๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"17,300\",\"sp_stock_lmp_irds_cnt\":\"4,664\",\"sp_stock_lmp_rate\":\"0.01\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260430001717\",\"rcept_dt\":\"2026-04-30\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"์ด์‘์„ญ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"6,744\",\"sp_stock_lmp_irds_cnt\":\"1,173\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260430001718\",\"rcept_dt\":\"2026-04-30\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"์ด์ค€์›\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"5,184\",\"sp_stock_lmp_irds_cnt\":\"1,000\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260430001726\",\"rcept_dt\":\"2026-04-30\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"์žฅ์†Œ์šฉ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ด์‚ฌ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"2,000\",\"sp_stock_lmp_irds_cnt\":\"2,000\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260430001729\",\"rcept_dt\":\"2026-04-30\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"์ •์•ˆ๋‚˜\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ด์‚ฌ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"7,832\",\"sp_stock_lmp_irds_cnt\":\"1,129\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260430001730\",\"rcept_dt\":\"2026-04-30\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"์กฐ์ข…๋ฌธ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ „๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"18,126\",\"sp_stock_lmp_irds_cnt\":\"2,173\",\"sp_stock_lmp_rate\":\"0.01\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260430001734\",\"rcept_dt\":\"2026-04-30\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"์†ก์ˆ˜์€\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ „๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"24,181\",\"sp_stock_lmp_irds_cnt\":\"2,173\",\"sp_stock_lmp_rate\":\"0.01\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260430001735\",\"rcept_dt\":\"2026-04-30\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"๊น€์˜ํ™˜\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ด์‚ฌ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"2,305\",\"sp_stock_lmp_irds_cnt\":\"1,149\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260430001739\",\"rcept_dt\":\"2026-04-30\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"์žฅ์ง€๋ฏธ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ „๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"0\",\"sp_stock_lmp_irds_cnt\":\"-350\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260430001742\",\"rcept_dt\":\"2026-04-30\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"๊ถŒ์ˆ˜์ง„\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ด์‚ฌ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"8,180\",\"sp_stock_lmp_irds_cnt\":\"1,151\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260430001744\",\"rcept_dt\":\"2026-04-30\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"์ตœ๋ณ‘์„œ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ „๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"3,000\",\"sp_stock_lmp_irds_cnt\":\"3,000\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260430001747\",\"rcept_dt\":\"2026-04-30\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"๊ฐ•๊ฒฝ๋‘\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ด์‚ฌ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"1,800\",\"sp_stock_lmp_irds_cnt\":\"950\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260319001263\",\"rcept_dt\":\"2026-03-19\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"์ด์˜๋‚จ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ด์‚ฌ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"5,340\",\"sp_stock_lmp_irds_cnt\":\"-2,500\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260225005849\",\"rcept_dt\":\"2026-02-25\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"๋ฌธ๋Œ€๋งŒ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ด์‚ฌ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"381\",\"sp_stock_lmp_irds_cnt\":\"381\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260225003884\",\"rcept_dt\":\"2026-02-25\",\"corp_code\":\"00413046\",\"corp_name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"repror\":\"์…€ํŠธ๋ฆฌ์˜จ์Šคํ‚จํ์–ด\",\"isu_exctv_rgist_at\":\"-\",\"isu_exctv_ofcps\":\"-\",\"isu_main_shrholdr\":\"์‚ฌ์‹ค์ƒ์ง€๋ฐฐ์ฃผ์ฃผ\",\"sp_stock_lmp_cnt\":\"4,837,305\",\"sp_stock_lmp_irds_cnt\":\"39,621\",\"sp_stock_lmp_rate\":\"2.09\",\"sp_stock_lmp_irds_rate\":\"0.01\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/elestock.json?corp_code=00413046\"]}" + } + ] +} diff --git a/scripts/kr-eval/fixtures/default/institutional-vs-foreign.json b/scripts/kr-eval/fixtures/default/institutional-vs-foreign.json new file mode 100644 index 000000000..a0bd3e68f --- /dev/null +++ b/scripts/kr-eval/fixtures/default/institutional-vs-foreign.json @@ -0,0 +1,17 @@ +{ + "id": "institutional-vs-foreign", + "query": "์˜ค๋Š˜ ์‚ผ์„ฑ์ „์ž ๊ธฐ์ค€์œผ๋กœ ๊ธฐ๊ด€ยท์™ธ๊ตญ์ธยท๊ฐœ์ธ ๋ˆ„๊ฐ€ ์‚ฌ๊ณ  ํŒ”์•˜์–ด?", + "meta": { + "model": "gpt-5.5" + }, + "toolCalls": [ + { + "tool": "get_foreign_ownership_kr", + "args": { + "ticker": "005930", + "limit": 5 + }, + "result": "{\"data\":{\"ticker\":\"005930\",\"ownership\":[{\"date\":\"2026-05-29\",\"foreignHoldRatio\":48.27,\"foreignNetBuyQty\":-1061741,\"orgNetBuyQty\":5314304,\"individualNetBuyQty\":-4237361,\"closePrice\":317000,\"tradingVolume\":32804208},{\"date\":\"2026-05-28\",\"foreignHoldRatio\":48.29,\"foreignNetBuyQty\":-5173125,\"orgNetBuyQty\":-1613562,\"individualNetBuyQty\":6500347,\"closePrice\":299500,\"tradingVolume\":30195334},{\"date\":\"2026-05-27\",\"foreignHoldRatio\":48.39,\"foreignNetBuyQty\":-274173,\"orgNetBuyQty\":1550904,\"individualNetBuyQty\":-1105685,\"closePrice\":307000,\"tradingVolume\":33916688},{\"date\":\"2026-05-26\",\"foreignHoldRatio\":48.37,\"foreignNetBuyQty\":1891734,\"orgNetBuyQty\":1513469,\"individualNetBuyQty\":-3263161,\"closePrice\":299000,\"tradingVolume\":23441371},{\"date\":\"2026-05-22\",\"foreignHoldRatio\":48.32,\"foreignNetBuyQty\":-3419695,\"orgNetBuyQty\":-307631,\"individualNetBuyQty\":3602972,\"closePrice\":292500,\"tradingVolume\":18395274}]},\"sourceUrls\":[\"https://m.stock.naver.com/api/stock/005930/trend\"]}" + } + ] +} diff --git a/scripts/kr-eval/fixtures/default/large-holders-samsung.json b/scripts/kr-eval/fixtures/default/large-holders-samsung.json new file mode 100644 index 000000000..9fafb7587 --- /dev/null +++ b/scripts/kr-eval/fixtures/default/large-holders-samsung.json @@ -0,0 +1,17 @@ +{ + "id": "large-holders-samsung", + "query": "์‚ผ์„ฑ์ „์ž ๋Œ€๋Ÿ‰๋ณด์œ (5%๋ฃฐ) ํ˜„ํ™ฉํ•˜๊ณ  ์ง€๋ฐฐ๊ตฌ์กฐ ํ•จ์˜ ์ •๋ฆฌํ•ด์ค˜.", + "meta": { + "model": "gpt-5.5" + }, + "toolCalls": [ + { + "tool": "get_large_holders_kr", + "args": { + "ticker": "005930", + "limit": 30 + }, + "result": "{\"data\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"holders\":[{\"rcept_no\":\"20260522000589\",\"rcept_dt\":\"2026-05-22\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,513,080\",\"stkqy_irds\":\"-48,004\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"49,359,000\",\"ctr_stkrt\":\"0.84\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260504000421\",\"rcept_dt\":\"2026-05-04\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,561,084\",\"stkqy_irds\":\"-10,417\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"63,584,000\",\"ctr_stkrt\":\"1.09\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260424000758\",\"rcept_dt\":\"2026-04-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,571,501\",\"stkqy_irds\":\"-8,476\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"67,703,000\",\"ctr_stkrt\":\"1.16\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260417000682\",\"rcept_dt\":\"2026-04-17\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,579,977\",\"stkqy_irds\":\"2,778\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"67,703,000\",\"ctr_stkrt\":\"1.16\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260413002934\",\"rcept_dt\":\"2026-04-13\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,577,199\",\"stkqy_irds\":\"-22,378,550\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.13\",\"ctr_stkqy\":\"80,237,000\",\"ctr_stkrt\":\"1.37\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\\n- ์‹ ํƒ์ฃผ์‹ ์ฒ˜๋ถ„\"},{\"rcept_no\":\"20260130000730\",\"rcept_dt\":\"2026-01-30\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,173,955,749\",\"stkqy_irds\":\"35,644\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"95,237,000\",\"ctr_stkrt\":\"1.61\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260123000544\",\"rcept_dt\":\"2026-01-23\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,173,920,105\",\"stkqy_irds\":\"-187,402\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"103,134,000\",\"ctr_stkrt\":\"1.74\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260116000451\",\"rcept_dt\":\"2026-01-16\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,174,107,507\",\"stkqy_irds\":\"-11,934\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"103,134,000\",\"ctr_stkrt\":\"1.74\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ์œ ๊ฐ€์ฆ๊ถŒ์‹ ํƒ(๋ณด์œ ํ˜•ํƒœ ๋ณ€๊ฒฝ ํฌํ•จ)\"},{\"rcept_no\":\"20260109000680\",\"rcept_dt\":\"2026-01-09\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,174,119,441\",\"stkqy_irds\":\"-386,875\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"-0.01\",\"ctr_stkqy\":\"88,134,000\",\"ctr_stkrt\":\"1.49\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20251107000558\",\"rcept_dt\":\"2025-11-07\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,174,506,316\",\"stkqy_irds\":\"-17,733,231\",\"stkrt\":\"19.84\",\"stkrt_irds\":\"-0.30\",\"ctr_stkqy\":\"86,267,000\",\"ctr_stkrt\":\"1.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\\n- ์‹ ํƒ์ฃผ์‹ ์ฒ˜๋ถ„\"},{\"rcept_no\":\"20251031000575\",\"rcept_dt\":\"2025-10-31\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,239,547\",\"stkqy_irds\":\"-52,021\",\"stkrt\":\"20.14\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"145,670,929\",\"ctr_stkrt\":\"2.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20251024000542\",\"rcept_dt\":\"2025-10-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,291,568\",\"stkqy_irds\":\"-60,260\",\"stkrt\":\"20.14\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"145,670,929\",\"ctr_stkrt\":\"2.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20251017000512\",\"rcept_dt\":\"2025-10-17\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,351,828\",\"stkqy_irds\":\"-168,900\",\"stkrt\":\"20.14\",\"stkrt_irds\":\"-0.01\",\"ctr_stkqy\":\"145,670,929\",\"ctr_stkrt\":\"2.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\\n- ์œ ๊ฐ€์ฆ๊ถŒ์‹ ํƒ(๋ณด์œ ํ˜•ํƒœ ๋ณ€๊ฒฝ ํฌํ•จ)\"},{\"rcept_no\":\"20250919000589\",\"rcept_dt\":\"2025-09-19\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,520,728\",\"stkqy_irds\":\"-45,660\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250905000524\",\"rcept_dt\":\"2025-09-05\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,566,388\",\"stkqy_irds\":\"-77,185\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250814004144\",\"rcept_dt\":\"2025-08-14\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,643,573\",\"stkqy_irds\":\"-92,241\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250801001081\",\"rcept_dt\":\"2025-08-01\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,735,814\",\"stkqy_irds\":\"9,551\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250725000756\",\"rcept_dt\":\"2025-07-25\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,726,263\",\"stkqy_irds\":\"4,560\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250718000569\",\"rcept_dt\":\"2025-07-18\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,721,703\",\"stkqy_irds\":\"-164,817\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250620000604\",\"rcept_dt\":\"2025-06-20\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,886,520\",\"stkqy_irds\":\"-191,832\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250523000612\",\"rcept_dt\":\"2025-05-23\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,078,352\",\"stkqy_irds\":\"-876\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250516000386\",\"rcept_dt\":\"2025-05-16\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,079,228\",\"stkqy_irds\":\"-29,835\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.01\",\"ctr_stkqy\":\"152,224,392\",\"ctr_stkrt\":\"2.57\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250509000826\",\"rcept_dt\":\"2025-05-09\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,109,063\",\"stkqy_irds\":\"-32,842\",\"stkrt\":\"20.16\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"152,224,392\",\"ctr_stkrt\":\"2.57\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250425000921\",\"rcept_dt\":\"2025-04-25\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,141,905\",\"stkqy_irds\":\"-9,066\",\"stkrt\":\"20.16\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250418000638\",\"rcept_dt\":\"2025-04-18\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,150,971\",\"stkqy_irds\":\"144,693\",\"stkrt\":\"20.16\",\"stkrt_irds\":\"0.01\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250321001642\",\"rcept_dt\":\"2025-03-21\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,006,278\",\"stkqy_irds\":\"-10,682\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250307001130\",\"rcept_dt\":\"2025-03-07\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,016,960\",\"stkqy_irds\":\"-77,470\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"0.16\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250214002629\",\"rcept_dt\":\"2025-02-14\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,094,430\",\"stkqy_irds\":\"-4,963,824\",\"stkrt\":\"19.99\",\"stkrt_irds\":\"-0.08\",\"ctr_stkqy\":\"95,423,993\",\"ctr_stkrt\":\"1.60\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250124000900\",\"rcept_dt\":\"2025-01-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,198,080,226\",\"stkqy_irds\":\"-38,791\",\"stkrt\":\"20.07\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"95,423,993\",\"ctr_stkrt\":\"1.60\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250124000908\",\"rcept_dt\":\"2025-01-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,198,058,254\",\"stkqy_irds\":\"-21,972\",\"stkrt\":\"20.07\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"95,423,993\",\"ctr_stkrt\":\"1.60\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/majorstock.json?corp_code=00126380\"]}" + } + ] +} diff --git a/scripts/kr-eval/fixtures/default/nps-samsung.json b/scripts/kr-eval/fixtures/default/nps-samsung.json new file mode 100644 index 000000000..5f3563600 --- /dev/null +++ b/scripts/kr-eval/fixtures/default/nps-samsung.json @@ -0,0 +1,17 @@ +{ + "id": "nps-samsung", + "query": "๊ตญ๋ฏผ์—ฐ๊ธˆ์ด ์‚ผ์„ฑ์ „์ž ์–ผ๋งˆ๋‚˜ ๋“ค๊ณ  ์žˆ์–ด?", + "meta": { + "model": "gpt-5.5" + }, + "toolCalls": [ + { + "tool": "get_nps_holdings", + "args": { + "ticker": "005930", + "limit": 5 + }, + "result": "{\"data\":{\"source\":\"NPS ๊ตญ๋‚ด์ฃผ์‹ ํˆฌ์ž์ •๋ณด (data.go.kr, year-end snapshot)\",\"query\":{\"name\":null,\"ticker\":\"005930\",\"resolvedName\":\"์‚ผ์„ฑ์ „์ž\"},\"holdings\":[{\"name\":\"์‚ผ์„ฑ์ „์ž\",\"evalAmount\":230421,\"weightPct\":16.7,\"shareRatioPct\":7.26},{\"name\":\"์‚ผ์„ฑ์ „์ž์šฐ\",\"evalAmount\":1982,\"weightPct\":0.14,\"shareRatioPct\":0.54}]}}" + } + ] +} diff --git a/scripts/kr-eval/fixtures/default/nps-top.json b/scripts/kr-eval/fixtures/default/nps-top.json new file mode 100644 index 000000000..1101175d9 --- /dev/null +++ b/scripts/kr-eval/fixtures/default/nps-top.json @@ -0,0 +1,16 @@ +{ + "id": "nps-top", + "query": "๊ตญ๋ฏผ์—ฐ๊ธˆ ๋ณด์œ  ๋น„์ค‘ ์ƒ์œ„ ์ข…๋ชฉ ์•Œ๋ ค์ค˜.", + "meta": { + "model": "gpt-5.5" + }, + "toolCalls": [ + { + "tool": "get_nps_holdings", + "args": { + "limit": 20 + }, + "result": "{\"data\":{\"source\":\"NPS ๊ตญ๋‚ด์ฃผ์‹ ํˆฌ์ž์ •๋ณด (data.go.kr, year-end snapshot)\",\"query\":{\"name\":null,\"ticker\":null,\"resolvedName\":null},\"holdings\":[{\"name\":\"์‚ผ์„ฑ์ „์ž\",\"evalAmount\":230421,\"weightPct\":16.7,\"shareRatioPct\":7.26},{\"name\":\"SKํ•˜์ด๋‹‰์Šค\",\"evalAmount\":95627,\"weightPct\":6.93,\"shareRatioPct\":7.55},{\"name\":\"LG์—๋„ˆ์ง€์†”๋ฃจ์…˜\",\"evalAmount\":51706,\"weightPct\":3.75,\"shareRatioPct\":6.35},{\"name\":\"์‚ผ์„ฑ๋ฐ”์ด์˜ค๋กœ์ง์Šค\",\"evalAmount\":46721,\"weightPct\":3.39,\"shareRatioPct\":6.92},{\"name\":\"ํ˜„๋Œ€์ฐจ\",\"evalAmount\":33529,\"weightPct\":2.43,\"shareRatioPct\":7.55},{\"name\":\"๊ธฐ์•„\",\"evalAmount\":28589,\"weightPct\":2.07,\"shareRatioPct\":7.14},{\"name\":\"NAVER\",\"evalAmount\":28134,\"weightPct\":2.04,\"shareRatioPct\":8.94},{\"name\":\"์…€ํŠธ๋ฆฌ์˜จ\",\"evalAmount\":27624,\"weightPct\":2,\"shareRatioPct\":6.79},{\"name\":\"KB๊ธˆ์œต\",\"evalAmount\":27405,\"weightPct\":1.99,\"shareRatioPct\":8.4},{\"name\":\"์‹ ํ•œ์ง€์ฃผ\",\"evalAmount\":20547,\"weightPct\":1.49,\"shareRatioPct\":8.56},{\"name\":\"ํ˜„๋Œ€๋ชจ๋น„์Šค\",\"evalAmount\":19937,\"weightPct\":1.44,\"shareRatioPct\":9.06},{\"name\":\"HDํ˜„๋Œ€์ค‘๊ณต์—…\",\"evalAmount\":16837,\"weightPct\":1.22,\"shareRatioPct\":6.6},{\"name\":\"POSCOํ™€๋”ฉ์Šค\",\"evalAmount\":15628,\"weightPct\":1.13,\"shareRatioPct\":7.46},{\"name\":\"ํ•˜๋‚˜๊ธˆ์œต์ง€์ฃผ\",\"evalAmount\":15081,\"weightPct\":1.09,\"shareRatioPct\":9.25},{\"name\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"evalAmount\":14852,\"weightPct\":1.08,\"shareRatioPct\":7.28},{\"name\":\"LGํ™”ํ•™\",\"evalAmount\":14546,\"weightPct\":1.05,\"shareRatioPct\":8.25},{\"name\":\"์‚ผ์„ฑ์ƒ๋ช…\",\"evalAmount\":13400,\"weightPct\":0.97,\"shareRatioPct\":7.07},{\"name\":\"๋ฉ”๋ฆฌ์ธ ๊ธˆ์œต์ง€์ฃผ\",\"evalAmount\":13180,\"weightPct\":0.95,\"shareRatioPct\":6.65},{\"name\":\"์‚ผ์„ฑSDI\",\"evalAmount\":12598,\"weightPct\":0.91,\"shareRatioPct\":7.41},{\"name\":\"์‚ผ์„ฑํ™”์žฌ\",\"evalAmount\":11794,\"weightPct\":0.85,\"shareRatioPct\":6.94}]}}" + } + ] +} diff --git a/scripts/kr-eval/fixtures/default/samsung-fundamentals.json b/scripts/kr-eval/fixtures/default/samsung-fundamentals.json new file mode 100644 index 000000000..48d983d54 --- /dev/null +++ b/scripts/kr-eval/fixtures/default/samsung-fundamentals.json @@ -0,0 +1,16 @@ +{ + "id": "samsung-fundamentals", + "query": "์‚ผ์„ฑ์ „์ž(005930) ์ตœ๊ทผ ์‹ค์  ๋งค์ถœ์ด๋ž‘ ์˜์—…์ด์ต YoY๋กœ ์•Œ๋ ค์ค˜.", + "meta": { + "model": "gpt-5.5" + }, + "toolCalls": [ + { + "tool": "get_financials_kr", + "args": { + "query": "์‚ผ์„ฑ์ „์ž 005930 ์ตœ๊ทผ ์‹ค์  ๋งค์ถœ์•ก ์˜์—…์ด์ต ์ „๋…„๋™๊ธฐ๋Œ€๋น„ YoY ์ตœ๊ทผ ๋ถ„๊ธฐ K-IFRS ์—ฐ๊ฒฐ ๊ธฐ์ค€" + }, + "result": "{\"data\":{\"get_business_report_kr_005930\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_type\":\"quarterly_1\",\"fs_div\":\"CFS\",\"periods\":[{\"bsns_year\":2026,\"report_type\":\"quarterly_1\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"1๋ถ„๊ธฐ ๋ˆ„์ (thstrm=๋‹น๊ธฐ, frmtrm=์ „๋…„ ๋™๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„์€ YTD; BS๋Š” ๋ถ„๊ธฐ๋ง vs ์ „๋…„๋ง.\",\"incomeStatement\":{\"revenue\":{\"current\":133873444000000,\"prior\":79140503000000,\"label\":\"๋งค์ถœ์•ก\",\"display\":\"133.9์กฐ\"},\"operatingProfit\":{\"current\":57232797000000,\"prior\":6685272000000,\"label\":\"์˜์—…์ด์ต\",\"display\":\"57.2์กฐ\"},\"netIncome\":{\"current\":47225272000000,\"prior\":8222878000000,\"label\":\"๋ถ„๊ธฐ์ˆœ์ด์ต\",\"display\":\"47.2์กฐ\"},\"controllingNetIncome\":{\"current\":47101190000000,\"prior\":8028407000000,\"label\":\"์ง€๋ฐฐ๊ธฐ์—… ์†Œ์œ ์ฃผ์ง€๋ถ„\",\"display\":\"47.1์กฐ\"},\"eps\":{\"current\":7123,\"prior\":1192,\"label\":\"๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต(์†์‹ค)\",\"display\":\"7,123์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":633339604000000,\"prior\":566942110000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"633.3์กฐ\"},\"totalLiabilities\":{\"current\":146703628000000,\"prior\":130621773000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"146.7์กฐ\"},\"totalEquity\":{\"current\":486635976000000,\"prior\":436320337000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"486.6์กฐ\"},\"cashAndEquivalents\":{\"current\":73306751000000,\"prior\":57856378000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"73.3์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":40274106000000,\"prior\":16580866000000,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"40.3์กฐ\"},\"investing\":{\"current\":-21456000000000,\"prior\":-6431187000000,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-21.5์กฐ\"},\"financing\":{\"current\":-5455268000000,\"prior\":-11341720000000,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-5.5์กฐ\"},\"capex\":{\"current\":17127003000000,\"prior\":12127934000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"17.1์กฐ\"}},\"ratios\":{\"operatingMarginPct\":42.8,\"netMarginPct\":35.3,\"roePct\":null,\"debtToEquityPct\":30.1,\"revenueYoYPct\":69.2,\"operatingProfitYoYPct\":756.1,\"netIncomeYoYPct\":474.3,\"freeCashFlow\":23147103000000,\"freeCashFlowDisplay\":\"23.1์กฐ\"}}],\"rawLineItemsFile\":\".dexter/tool-results/kr-financials-00126380-11013-CFS-2026_2026.json\",\"note\":\"periods[].summary holds normalized key metrics (KRW). Use read_file on rawLineItemsFile only for accounts not in the summary.\"}},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00126380&bsns_year=2026&reprt_code=11013&fs_div=CFS\"]}" + } + ] +} diff --git a/scripts/kr-eval/fixtures/default/samsung-synthesis.json b/scripts/kr-eval/fixtures/default/samsung-synthesis.json new file mode 100644 index 000000000..8bf729fa4 --- /dev/null +++ b/scripts/kr-eval/fixtures/default/samsung-synthesis.json @@ -0,0 +1,74 @@ +{ + "id": "samsung-synthesis", + "query": "์‚ผ์„ฑ์ „์ž ์ง€๊ธˆ ํˆฌ์ž ๊ด€์ ์—์„œ ์–ด๋•Œ?", + "meta": { + "model": "gpt-5.5" + }, + "toolCalls": [ + { + "tool": "memory_search", + "args": { + "query": "์‚ฌ์šฉ์ž์˜ ํˆฌ์ž ๋ชฉํ‘œ ์œ„ํ—˜์„ฑํ–ฅ ํฌํŠธํด๋ฆฌ์˜ค ๋ณด์œ ์ข…๋ชฉ ์‚ผ์„ฑ์ „์ž ํˆฌ์ž ์„ ํ˜ธ ๋งค์ˆ˜ ๋งค๋„ ๊ธฐ์ค€" + }, + "result": "{\"data\":{\"results\":[]}}" + }, + { + "tool": "get_foreign_ownership_kr", + "args": { + "ticker": "005930", + "limit": 30 + }, + "result": "{\"data\":{\"ticker\":\"005930\",\"ownership\":[{\"date\":\"2026-05-29\",\"foreignHoldRatio\":48.27,\"foreignNetBuyQty\":-1061741,\"orgNetBuyQty\":5314304,\"individualNetBuyQty\":-4237361,\"closePrice\":317000,\"tradingVolume\":32804208},{\"date\":\"2026-05-28\",\"foreignHoldRatio\":48.29,\"foreignNetBuyQty\":-5173125,\"orgNetBuyQty\":-1613562,\"individualNetBuyQty\":6500347,\"closePrice\":299500,\"tradingVolume\":30195334},{\"date\":\"2026-05-27\",\"foreignHoldRatio\":48.39,\"foreignNetBuyQty\":-274173,\"orgNetBuyQty\":1550904,\"individualNetBuyQty\":-1105685,\"closePrice\":307000,\"tradingVolume\":33916688},{\"date\":\"2026-05-26\",\"foreignHoldRatio\":48.37,\"foreignNetBuyQty\":1891734,\"orgNetBuyQty\":1513469,\"individualNetBuyQty\":-3263161,\"closePrice\":299000,\"tradingVolume\":23441371},{\"date\":\"2026-05-22\",\"foreignHoldRatio\":48.32,\"foreignNetBuyQty\":-3419695,\"orgNetBuyQty\":-307631,\"individualNetBuyQty\":3602972,\"closePrice\":292500,\"tradingVolume\":18395274},{\"date\":\"2026-05-21\",\"foreignHoldRatio\":48.39,\"foreignNetBuyQty\":3672423,\"orgNetBuyQty\":2608486,\"individualNetBuyQty\":-5931378,\"closePrice\":299500,\"tradingVolume\":36168689},{\"date\":\"2026-05-20\",\"foreignHoldRatio\":48.33,\"foreignNetBuyQty\":-5733354,\"orgNetBuyQty\":2756787,\"individualNetBuyQty\":2765915,\"closePrice\":276000,\"tradingVolume\":35662077},{\"date\":\"2026-05-19\",\"foreignHoldRatio\":48.42,\"foreignNetBuyQty\":-9263047,\"orgNetBuyQty\":1418906,\"individualNetBuyQty\":7818601,\"closePrice\":275500,\"tradingVolume\":30767569},{\"date\":\"2026-05-18\",\"foreignHoldRatio\":48.61,\"foreignNetBuyQty\":-4486093,\"orgNetBuyQty\":2803397,\"individualNetBuyQty\":1689597,\"closePrice\":281000,\"tradingVolume\":33555214},{\"date\":\"2026-05-15\",\"foreignHoldRatio\":48.69,\"foreignNetBuyQty\":-8855741,\"orgNetBuyQty\":-1529944,\"individualNetBuyQty\":10257346,\"closePrice\":270500,\"tradingVolume\":38075487}]},\"sourceUrls\":[\"https://m.stock.naver.com/api/stock/005930/trend\"]}" + }, + { + "tool": "get_large_holders_kr", + "args": { + "ticker": "005930", + "limit": 30 + }, + "result": "{\"data\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"holders\":[{\"rcept_no\":\"20260522000589\",\"rcept_dt\":\"2026-05-22\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,513,080\",\"stkqy_irds\":\"-48,004\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"49,359,000\",\"ctr_stkrt\":\"0.84\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260504000421\",\"rcept_dt\":\"2026-05-04\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,561,084\",\"stkqy_irds\":\"-10,417\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"63,584,000\",\"ctr_stkrt\":\"1.09\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260424000758\",\"rcept_dt\":\"2026-04-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,571,501\",\"stkqy_irds\":\"-8,476\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"67,703,000\",\"ctr_stkrt\":\"1.16\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260417000682\",\"rcept_dt\":\"2026-04-17\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,579,977\",\"stkqy_irds\":\"2,778\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"67,703,000\",\"ctr_stkrt\":\"1.16\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260413002934\",\"rcept_dt\":\"2026-04-13\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,577,199\",\"stkqy_irds\":\"-22,378,550\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.13\",\"ctr_stkqy\":\"80,237,000\",\"ctr_stkrt\":\"1.37\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\\n- ์‹ ํƒ์ฃผ์‹ ์ฒ˜๋ถ„\"},{\"rcept_no\":\"20260130000730\",\"rcept_dt\":\"2026-01-30\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,173,955,749\",\"stkqy_irds\":\"35,644\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"95,237,000\",\"ctr_stkrt\":\"1.61\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260123000544\",\"rcept_dt\":\"2026-01-23\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,173,920,105\",\"stkqy_irds\":\"-187,402\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"103,134,000\",\"ctr_stkrt\":\"1.74\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260116000451\",\"rcept_dt\":\"2026-01-16\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,174,107,507\",\"stkqy_irds\":\"-11,934\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"103,134,000\",\"ctr_stkrt\":\"1.74\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ์œ ๊ฐ€์ฆ๊ถŒ์‹ ํƒ(๋ณด์œ ํ˜•ํƒœ ๋ณ€๊ฒฝ ํฌํ•จ)\"},{\"rcept_no\":\"20260109000680\",\"rcept_dt\":\"2026-01-09\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,174,119,441\",\"stkqy_irds\":\"-386,875\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"-0.01\",\"ctr_stkqy\":\"88,134,000\",\"ctr_stkrt\":\"1.49\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20251107000558\",\"rcept_dt\":\"2025-11-07\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,174,506,316\",\"stkqy_irds\":\"-17,733,231\",\"stkrt\":\"19.84\",\"stkrt_irds\":\"-0.30\",\"ctr_stkqy\":\"86,267,000\",\"ctr_stkrt\":\"1.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\\n- ์‹ ํƒ์ฃผ์‹ ์ฒ˜๋ถ„\"},{\"rcept_no\":\"20251031000575\",\"rcept_dt\":\"2025-10-31\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,239,547\",\"stkqy_irds\":\"-52,021\",\"stkrt\":\"20.14\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"145,670,929\",\"ctr_stkrt\":\"2.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20251024000542\",\"rcept_dt\":\"2025-10-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,291,568\",\"stkqy_irds\":\"-60,260\",\"stkrt\":\"20.14\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"145,670,929\",\"ctr_stkrt\":\"2.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20251017000512\",\"rcept_dt\":\"2025-10-17\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,351,828\",\"stkqy_irds\":\"-168,900\",\"stkrt\":\"20.14\",\"stkrt_irds\":\"-0.01\",\"ctr_stkqy\":\"145,670,929\",\"ctr_stkrt\":\"2.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\\n- ์œ ๊ฐ€์ฆ๊ถŒ์‹ ํƒ(๋ณด์œ ํ˜•ํƒœ ๋ณ€๊ฒฝ ํฌํ•จ)\"},{\"rcept_no\":\"20250919000589\",\"rcept_dt\":\"2025-09-19\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,520,728\",\"stkqy_irds\":\"-45,660\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250905000524\",\"rcept_dt\":\"2025-09-05\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,566,388\",\"stkqy_irds\":\"-77,185\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250814004144\",\"rcept_dt\":\"2025-08-14\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,643,573\",\"stkqy_irds\":\"-92,241\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250801001081\",\"rcept_dt\":\"2025-08-01\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,735,814\",\"stkqy_irds\":\"9,551\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250725000756\",\"rcept_dt\":\"2025-07-25\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,726,263\",\"stkqy_irds\":\"4,560\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250718000569\",\"rcept_dt\":\"2025-07-18\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,721,703\",\"stkqy_irds\":\"-164,817\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250620000604\",\"rcept_dt\":\"2025-06-20\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,886,520\",\"stkqy_irds\":\"-191,832\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250523000612\",\"rcept_dt\":\"2025-05-23\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,078,352\",\"stkqy_irds\":\"-876\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250516000386\",\"rcept_dt\":\"2025-05-16\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,079,228\",\"stkqy_irds\":\"-29,835\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.01\",\"ctr_stkqy\":\"152,224,392\",\"ctr_stkrt\":\"2.57\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250509000826\",\"rcept_dt\":\"2025-05-09\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,109,063\",\"stkqy_irds\":\"-32,842\",\"stkrt\":\"20.16\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"152,224,392\",\"ctr_stkrt\":\"2.57\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250425000921\",\"rcept_dt\":\"2025-04-25\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,141,905\",\"stkqy_irds\":\"-9,066\",\"stkrt\":\"20.16\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250418000638\",\"rcept_dt\":\"2025-04-18\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,150,971\",\"stkqy_irds\":\"144,693\",\"stkrt\":\"20.16\",\"stkrt_irds\":\"0.01\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250321001642\",\"rcept_dt\":\"2025-03-21\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,006,278\",\"stkqy_irds\":\"-10,682\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250307001130\",\"rcept_dt\":\"2025-03-07\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,016,960\",\"stkqy_irds\":\"-77,470\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"0.16\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250214002629\",\"rcept_dt\":\"2025-02-14\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,094,430\",\"stkqy_irds\":\"-4,963,824\",\"stkrt\":\"19.99\",\"stkrt_irds\":\"-0.08\",\"ctr_stkqy\":\"95,423,993\",\"ctr_stkrt\":\"1.60\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250124000900\",\"rcept_dt\":\"2025-01-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,198,080,226\",\"stkqy_irds\":\"-38,791\",\"stkrt\":\"20.07\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"95,423,993\",\"ctr_stkrt\":\"1.60\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250124000908\",\"rcept_dt\":\"2025-01-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,198,058,254\",\"stkqy_irds\":\"-21,972\",\"stkrt\":\"20.07\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"95,423,993\",\"ctr_stkrt\":\"1.60\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/majorstock.json?corp_code=00126380\"]}" + }, + { + "tool": "get_filings_kr", + "args": { + "ticker": "005930", + "filing_type": "material", + "limit": 20, + "start_date": "2025-01-01", + "end_date": "2026-05-31" + }, + "result": "{\"data\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"filings\":[{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ฒ˜๋ถ„๊ฒฐ์ •)\",\"rcept_no\":\"20260318001203\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20260318\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ทจ๋“๊ฒฐ์ •)\",\"rcept_no\":\"20260318001062\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20260318\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ฒ˜๋ถ„๊ฒฐ์ •)\",\"rcept_no\":\"20260129000004\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20260129\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ทจ๋“๊ฒฐ์ •)\",\"rcept_no\":\"20260129000003\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20260129\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ฒ˜๋ถ„๊ฒฐ์ •)\",\"rcept_no\":\"20260126000001\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20260126\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ทจ๋“๊ฒฐ์ •)\",\"rcept_no\":\"20260107000715\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20260107\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ฒ˜๋ถ„๊ฒฐ์ •)\",\"rcept_no\":\"20251030000502\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20251030\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ฒ˜๋ถ„๊ฒฐ์ •)\",\"rcept_no\":\"20250725000794\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20250725\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ทจ๋“๊ฒฐ์ •)\",\"rcept_no\":\"20250708000011\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20250708\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ฒ˜๋ถ„๊ฒฐ์ •)\",\"rcept_no\":\"20250523000601\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20250523\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"[๊ธฐ์žฌ์ •์ •]์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ทจ๋“๊ฒฐ์ •)\",\"rcept_no\":\"20250218001596\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20250218\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ทจ๋“๊ฒฐ์ •)\",\"rcept_no\":\"20250218000019\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20250218\",\"rm\":\"์ •\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(ํ•ด์™ธ์ฆ๊ถŒ์‹œ์žฅ์ฃผ๊ถŒ๋“ฑ์ƒ์žฅ)\",\"rcept_no\":\"20250213001817\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20250213\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(ํ•ด์™ธ์ฆ๊ถŒ์‹œ์žฅ์ฃผ๊ถŒ๋“ฑ์ƒ์žฅํ์ง€)\",\"rcept_no\":\"20250213001808\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20250213\",\"rm\":\"\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/list.json?corp_code=00126380&bgn_de=20250101&end_de=20260531&pblntf_ty=B&page_count=20&page_no=1&sort=date&sort_mth=desc\"]}" + }, + { + "tool": "get_nps_holdings", + "args": { + "ticker": "005930", + "limit": 5 + }, + "result": "{\"data\":{\"source\":\"NPS ๊ตญ๋‚ด์ฃผ์‹ ํˆฌ์ž์ •๋ณด (data.go.kr, year-end snapshot)\",\"query\":{\"name\":null,\"ticker\":\"005930\",\"resolvedName\":\"์‚ผ์„ฑ์ „์ž\"},\"holdings\":[{\"name\":\"์‚ผ์„ฑ์ „์ž\",\"evalAmount\":230421,\"weightPct\":16.7,\"shareRatioPct\":7.26},{\"name\":\"์‚ผ์„ฑ์ „์ž์šฐ\",\"evalAmount\":1982,\"weightPct\":0.14,\"shareRatioPct\":0.54}]}}" + }, + { + "tool": "get_insider_trades_kr", + "args": { + "ticker": "005930", + "limit": 20 + }, + "result": "{\"data\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"trades\":[{\"rcept_no\":\"20260529001627\",\"rcept_dt\":\"2026-05-29\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"๊น€๊ฒฝ์„\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"237\",\"sp_stock_lmp_irds_cnt\":\"1\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260526000244\",\"rcept_dt\":\"2026-05-26\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์กฐ๋ฏธ์„ \",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"3,409\",\"sp_stock_lmp_irds_cnt\":\"120\",\"sp_stock_lmp_rate\":\"0\",\"sp_stock_lmp_irds_rate\":\"0\"},{\"rcept_no\":\"20260526000141\",\"rcept_dt\":\"2026-05-26\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์‹ ์šฉ์šฐ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"836\",\"sp_stock_lmp_irds_cnt\":\"50\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260522000164\",\"rcept_dt\":\"2026-05-22\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์–€์†ํ—จ๋ฆญ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"1,437\",\"sp_stock_lmp_irds_cnt\":\"500\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260522000036\",\"rcept_dt\":\"2026-05-22\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์–€์†ํ—จ๋ฆญ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"937\",\"sp_stock_lmp_irds_cnt\":\"600\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.0\"},{\"rcept_no\":\"20260513000081\",\"rcept_dt\":\"2026-05-13\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์‹ ์Šน์ฒ \",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"๋ถ€์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"2,105\",\"sp_stock_lmp_irds_cnt\":\"-2,200\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260511000085\",\"rcept_dt\":\"2026-05-11\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์ด๊ทœ์ฒ \",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"243\",\"sp_stock_lmp_irds_cnt\":\"0\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260508000866\",\"rcept_dt\":\"2026-05-08\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์ด๋™๊ฑด\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"๋ถ€์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"45\",\"sp_stock_lmp_irds_cnt\":\"45\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260429000319\",\"rcept_dt\":\"2026-04-29\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"ํ•˜์ง€ํ›ˆ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"1,726\",\"sp_stock_lmp_irds_cnt\":\"52\",\"sp_stock_lmp_rate\":\"0\",\"sp_stock_lmp_irds_rate\":\"0\"},{\"rcept_no\":\"20260423000164\",\"rcept_dt\":\"2026-04-23\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์ž„์ง€์šด\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"2,164\",\"sp_stock_lmp_irds_cnt\":\"230\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260417000440\",\"rcept_dt\":\"2026-04-17\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"๊น€๋ฏผ์šฐ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"1,430\",\"sp_stock_lmp_irds_cnt\":\"-550\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260406003839\",\"rcept_dt\":\"2026-04-06\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์•„ํฌํ…Œ์ปค๋งˆ๋ ์œ \",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"1,555\",\"sp_stock_lmp_irds_cnt\":\"-631\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260403003368\",\"rcept_dt\":\"2026-04-03\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์—ฐ์ง€ํ˜„\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"2,678\",\"sp_stock_lmp_irds_cnt\":\"173\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260331000460\",\"rcept_dt\":\"2026-03-31\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์†ก๊ธฐ์žฌ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"3,415\",\"sp_stock_lmp_irds_cnt\":\"397\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260330000333\",\"rcept_dt\":\"2026-03-30\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"๊น€๋ณดํ˜„\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"๋ถ€์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"4,466\",\"sp_stock_lmp_irds_cnt\":\"164\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260330000506\",\"rcept_dt\":\"2026-03-30\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์กฐ์„ฑํฌ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"3,000\",\"sp_stock_lmp_irds_cnt\":\"2,910\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260319001393\",\"rcept_dt\":\"2026-03-20\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์ด์Šน๋ฏผ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"166\",\"sp_stock_lmp_irds_cnt\":\"166\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260317000158\",\"rcept_dt\":\"2026-03-17\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์กฐ์˜์ค€\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"๋ถ€์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"9,460\",\"sp_stock_lmp_irds_cnt\":\"2,885\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260313000315\",\"rcept_dt\":\"2026-03-13\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์˜ค๋ฌธ์šฑ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"๋ถ€์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"25,000\",\"sp_stock_lmp_irds_cnt\":\"500\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260313001248\",\"rcept_dt\":\"2026-03-13\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์†ก์žฌํ˜\",\"isu_exctv_rgist_at\":\"๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"17,100\",\"sp_stock_lmp_irds_cnt\":\"20\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/elestock.json?corp_code=00126380\"]}" + }, + { + "tool": "get_short_balance_kr", + "args": { + "ticker": "005930", + "limit": 30 + }, + "result": "{\"data\":{\"ticker\":\"005930\",\"isin\":\"KR7005930003\",\"name\":\"์‚ผ์„ฑ์ „์ž\",\"short\":[{\"date\":\"2026-05-27\",\"balanceQty\":0,\"listedShares\":5846278608,\"balanceAmount\":0,\"marketCap\":1794807532656000,\"balanceRatio\":0},{\"date\":\"2026-05-26\",\"balanceQty\":0,\"listedShares\":5846278608,\"balanceAmount\":0,\"marketCap\":1748037303792000,\"balanceRatio\":0},{\"date\":\"2026-05-22\",\"balanceQty\":0,\"listedShares\":5846278608,\"balanceAmount\":0,\"marketCap\":1710036492840000,\"balanceRatio\":0},{\"date\":\"2026-05-21\",\"balanceQty\":0,\"listedShares\":5846278608,\"balanceAmount\":0,\"marketCap\":1750960443096000,\"balanceRatio\":0},{\"date\":\"2026-05-20\",\"balanceQty\":94881,\"listedShares\":5846278608,\"balanceAmount\":26187156000,\"marketCap\":1613572895808000,\"balanceRatio\":0},{\"date\":\"2026-05-19\",\"balanceQty\":179226,\"listedShares\":5846278608,\"balanceAmount\":49376763000,\"marketCap\":1610649756504000,\"balanceRatio\":0},{\"date\":\"2026-05-18\",\"balanceQty\":221813,\"listedShares\":5846278608,\"balanceAmount\":62329453000,\"marketCap\":1642804288848000,\"balanceRatio\":0},{\"date\":\"2026-05-15\",\"balanceQty\":243518,\"listedShares\":5846278608,\"balanceAmount\":65871619000,\"marketCap\":1581418363464000,\"balanceRatio\":0},{\"date\":\"2026-05-14\",\"balanceQty\":316122,\"listedShares\":5846278608,\"balanceAmount\":93572112000,\"marketCap\":1730498467968000,\"balanceRatio\":0.01},{\"date\":\"2026-05-13\",\"balanceQty\":296168,\"listedShares\":5846278608,\"balanceAmount\":84111712000,\"marketCap\":1660343124672000,\"balanceRatio\":0.01},{\"date\":\"2026-05-12\",\"balanceQty\":163909,\"listedShares\":5846278608,\"balanceAmount\":45730611000,\"marketCap\":1631111731632000,\"balanceRatio\":0},{\"date\":\"2026-05-11\",\"balanceQty\":240481,\"listedShares\":5846278608,\"balanceAmount\":68657325500,\"marketCap\":1669112542584000,\"balanceRatio\":0},{\"date\":\"2026-05-08\",\"balanceQty\":289206,\"listedShares\":5846278608,\"balanceAmount\":77651811000,\"marketCap\":1569725806248000,\"balanceRatio\":0},{\"date\":\"2026-05-07\",\"balanceQty\":198717,\"listedShares\":5846278608,\"balanceAmount\":53951665500,\"marketCap\":1587264642072000,\"balanceRatio\":0},{\"date\":\"2026-05-06\",\"balanceQty\":146558,\"listedShares\":5846278608,\"balanceAmount\":38984428000,\"marketCap\":1555110109728000,\"balanceRatio\":0},{\"date\":\"2026-05-04\",\"balanceQty\":26128,\"listedShares\":5846278608,\"balanceAmount\":6074760000,\"marketCap\":1359259776360000,\"balanceRatio\":0}]},\"sourceUrls\":[\"https://data.krx.co.kr/comm/bldAttendant/getJsonData.cmd?bld=dbms%2FMDC%2FSTAT%2Fsrt%2FMDCSTAT30502\"]}" + }, + { + "tool": "get_financials_kr", + "args": { + "query": "์‚ผ์„ฑ์ „์ž 005930 ์ตœ๊ทผ 5๋…„ ์—ฐ๊ฐ„ ๋ฐ ์ตœ๊ทผ ๋ถ„๊ธฐ K-IFRS ๋งค์ถœ ์˜์—…์ด์ต ์ˆœ์ด์ต ์˜์—…์ด์ต๋ฅ  ์ˆœ์ด์ต๋ฅ  ROE ๋ถ€์ฑ„๋น„์œจ ํ˜„๊ธˆํ๋ฆ„ ์š”์•ฝ YoY ํฌํ•จ" + }, + "result": "{\"data\":{\"get_business_report_kr_005930\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_type\":\"quarterly_1\",\"fs_div\":\"CFS\",\"periods\":[{\"bsns_year\":2026,\"report_type\":\"quarterly_1\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"1๋ถ„๊ธฐ ๋ˆ„์ (thstrm=๋‹น๊ธฐ, frmtrm=์ „๋…„ ๋™๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„์€ YTD; BS๋Š” ๋ถ„๊ธฐ๋ง vs ์ „๋…„๋ง.\",\"incomeStatement\":{\"revenue\":{\"current\":133873444000000,\"prior\":79140503000000,\"label\":\"๋งค์ถœ์•ก\",\"display\":\"133.9์กฐ\"},\"operatingProfit\":{\"current\":57232797000000,\"prior\":6685272000000,\"label\":\"์˜์—…์ด์ต\",\"display\":\"57.2์กฐ\"},\"netIncome\":{\"current\":47225272000000,\"prior\":8222878000000,\"label\":\"๋ถ„๊ธฐ์ˆœ์ด์ต\",\"display\":\"47.2์กฐ\"},\"controllingNetIncome\":{\"current\":47101190000000,\"prior\":8028407000000,\"label\":\"์ง€๋ฐฐ๊ธฐ์—… ์†Œ์œ ์ฃผ์ง€๋ถ„\",\"display\":\"47.1์กฐ\"},\"eps\":{\"current\":7123,\"prior\":1192,\"label\":\"๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต(์†์‹ค)\",\"display\":\"7,123์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":633339604000000,\"prior\":566942110000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"633.3์กฐ\"},\"totalLiabilities\":{\"current\":146703628000000,\"prior\":130621773000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"146.7์กฐ\"},\"totalEquity\":{\"current\":486635976000000,\"prior\":436320337000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"486.6์กฐ\"},\"cashAndEquivalents\":{\"current\":73306751000000,\"prior\":57856378000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"73.3์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":40274106000000,\"prior\":16580866000000,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"40.3์กฐ\"},\"investing\":{\"current\":-21456000000000,\"prior\":-6431187000000,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-21.5์กฐ\"},\"financing\":{\"current\":-5455268000000,\"prior\":-11341720000000,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-5.5์กฐ\"},\"capex\":{\"current\":17127003000000,\"prior\":12127934000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"17.1์กฐ\"}},\"ratios\":{\"operatingMarginPct\":42.8,\"netMarginPct\":35.3,\"roePct\":null,\"debtToEquityPct\":30.1,\"revenueYoYPct\":69.2,\"operatingProfitYoYPct\":756.1,\"netIncomeYoYPct\":474.3,\"freeCashFlow\":23147103000000,\"freeCashFlowDisplay\":\"23.1์กฐ\"}}],\"rawLineItemsFile\":\".dexter/tool-results/kr-financials-00126380-11013-CFS-2026_2026.json\",\"note\":\"periods[].summary holds normalized key metrics (KRW). Use read_file on rawLineItemsFile only for accounts not in the summary.\"}},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00126380&bsns_year=2025&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00126380&bsns_year=2024&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00126380&bsns_year=2023&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00126380&bsns_year=2022&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00126380&bsns_year=2021&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00126380&bsns_year=2026&reprt_code=11013&fs_div=CFS\"]}" + } + ] +} diff --git a/scripts/kr-eval/fixtures/default/short-balance-ecopro.json b/scripts/kr-eval/fixtures/default/short-balance-ecopro.json new file mode 100644 index 000000000..b976a2899 --- /dev/null +++ b/scripts/kr-eval/fixtures/default/short-balance-ecopro.json @@ -0,0 +1,17 @@ +{ + "id": "short-balance-ecopro", + "query": "์—์ฝ”ํ”„๋กœ๋น„์— (247540) ๊ณต๋งค๋„ ์ž”๊ณ ๋น„์ค‘ ์ง€๊ธˆ ์–ด๋А ์ˆ˜์ค€์ด์•ผ?", + "meta": { + "model": "gpt-5.5" + }, + "toolCalls": [ + { + "tool": "get_short_balance_kr", + "args": { + "ticker": "247540", + "limit": 20 + }, + "result": "{\"data\":{\"ticker\":\"247540\",\"isin\":\"KR7247540008\",\"name\":\"์—์ฝ”ํ”„๋กœ๋น„์— \",\"short\":[{\"date\":\"2026-05-27\",\"balanceQty\":2384672,\"listedShares\":97830434,\"balanceAmount\":509127472000,\"marketCap\":20886797659000,\"balanceRatio\":2.44},{\"date\":\"2026-05-26\",\"balanceQty\":2479889,\"listedShares\":97830434,\"balanceAmount\":545575580000,\"marketCap\":21522695480000,\"balanceRatio\":2.53},{\"date\":\"2026-05-22\",\"balanceQty\":2606871,\"listedShares\":97830434,\"balanceAmount\":563084136000,\"marketCap\":21131373744000,\"balanceRatio\":2.66},{\"date\":\"2026-05-21\",\"balanceQty\":2734333,\"listedShares\":97830434,\"balanceAmount\":533194935000,\"marketCap\":19076934630000,\"balanceRatio\":2.79},{\"date\":\"2026-05-20\",\"balanceQty\":2763541,\"listedShares\":97830434,\"balanceAmount\":488317694700,\"marketCap\":17286637687800,\"balanceRatio\":2.82},{\"date\":\"2026-05-19\",\"balanceQty\":2799134,\"listedShares\":97830434,\"balanceAmount\":510562041600,\"marketCap\":17844271161600,\"balanceRatio\":2.86},{\"date\":\"2026-05-18\",\"balanceQty\":2727216,\"listedShares\":97830434,\"balanceAmount\":519261926400,\"marketCap\":18626914633600,\"balanceRatio\":2.79},{\"date\":\"2026-05-15\",\"balanceQty\":2694595,\"listedShares\":97830434,\"balanceAmount\":513320347500,\"marketCap\":18636697677000,\"balanceRatio\":2.75},{\"date\":\"2026-05-14\",\"balanceQty\":2663111,\"listedShares\":97830434,\"balanceAmount\":556590199000,\"marketCap\":20446560706000,\"balanceRatio\":2.72},{\"date\":\"2026-05-13\",\"balanceQty\":2617061,\"listedShares\":97830434,\"balanceAmount\":515822723100,\"marketCap\":19282378541400,\"balanceRatio\":2.68},{\"date\":\"2026-05-12\",\"balanceQty\":2527091,\"listedShares\":97830434,\"balanceAmount\":519317200500,\"marketCap\":20104154187000,\"balanceRatio\":2.58},{\"date\":\"2026-05-11\",\"balanceQty\":2467434,\"listedShares\":97830434,\"balanceAmount\":547770348000,\"marketCap\":21718356348000,\"balanceRatio\":2.52},{\"date\":\"2026-05-08\",\"balanceQty\":2381594,\"listedShares\":97830434,\"balanceAmount\":565628575000,\"marketCap\":23234728075000,\"balanceRatio\":2.43},{\"date\":\"2026-05-07\",\"balanceQty\":2468960,\"listedShares\":97830434,\"balanceAmount\":581440080000,\"marketCap\":23039067207000,\"balanceRatio\":2.52},{\"date\":\"2026-05-06\",\"balanceQty\":2590202,\"listedShares\":97830434,\"balanceAmount\":591861157000,\"marketCap\":22354254169000,\"balanceRatio\":2.65},{\"date\":\"2026-05-04\",\"balanceQty\":2669587,\"listedShares\":97830434,\"balanceAmount\":575295998500,\"marketCap\":21082458527000,\"balanceRatio\":2.73}]},\"sourceUrls\":[\"https://data.krx.co.kr/comm/bldAttendant/getJsonData.cmd?bld=dbms%2FMDC%2FSTAT%2Fsrt%2FMDCSTAT30502\"]}" + } + ] +} diff --git a/scripts/kr-eval/fixtures/default/spinoff-lgchem.json b/scripts/kr-eval/fixtures/default/spinoff-lgchem.json new file mode 100644 index 000000000..89d509842 --- /dev/null +++ b/scripts/kr-eval/fixtures/default/spinoff-lgchem.json @@ -0,0 +1,57 @@ +{ + "id": "spinoff-lgchem", + "query": "LGํ™”ํ•™(051910) ๋ฌผ์ ๋ถ„ํ• ์ด ์†Œ์•ก์ฃผ์ฃผํ•œํ…Œ ์–ด๋–ค ์˜ํ–ฅ์ด์—ˆ์–ด?", + "meta": { + "model": "gpt-5.5" + }, + "toolCalls": [ + { + "tool": "skill", + "args": { + "skill": "kr-spinoff-analysis", + "args": "LGํ™”ํ•™ 051910 ๋ฌผ์ ๋ถ„ํ•  ์†Œ์•ก์ฃผ์ฃผ ์˜ํ–ฅ" + }, + "result": "## Skill: kr-spinoff-analysis\n\n**Arguments provided:** LGํ™”ํ•™ 051910 ๋ฌผ์ ๋ถ„ํ•  ์†Œ์•ก์ฃผ์ฃผ ์˜ํ–ฅ\n\n# ํ•œ๊ตญ ๊ธฐ์—…๋ถ„ํ• (๋ฌผ์ ๋ถ„ํ•  / ์ธ์ ๋ถ„ํ• ) ๋ถ„์„ ์Šคํ‚ฌ\n\n์ด ์Šคํ‚ฌ์˜ ๋ชฉํ‘œ๋Š” ๊ณต์‹œ๋ฅผ ๋‚˜์—ดํ•˜๋Š” ๊ฒƒ์ด **์•„๋‹ˆ๋‹ค**. *์ด ๋ถ„ํ• ์ด **๊ธฐ์กด ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ**์—๊ฒŒ ๋“์ด์—ˆ๋Š”์ง€\n์‹ค์ด์—ˆ๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ์™œ ๊ทธ๋Ÿฐ์ง€*์— ๋‹ตํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๊ฒฐ๋ก (ํ‰๊ฐ€)์„ ๋จผ์ € ์ œ์‹œํ•˜๋ผ.\n\n## ๋ฐฐ๊ฒฝ โ€” ๋‘ ๊ฐ€์ง€ ๋ถ„ํ•  ์œ ํ˜• (์–ด๋А ์ชฝ์ธ์ง€ ๋จผ์ € ํŒ๋‹จ)\n\n- **๋ฌผ์ ๋ถ„ํ• **: ๋ชจํšŒ์‚ฌ๊ฐ€ ์‚ฌ์—…๋ถ€๋ฅผ **100% ์žํšŒ์‚ฌ**๋กœ ์‹ ์„คํ•œ๋‹ค. ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ๋Š” ๊ทธ ์žํšŒ์‚ฌ๋ฅผ\n *๊ฐ„์ ‘์ ์œผ๋กœ๋งŒ* ์†Œ์œ ํ•œ๋‹ค. ๋Œ€ํ‘œ์  ๋ฆฌ์Šคํฌ๋Š” **์ชผ๊ฐœ๊ธฐ ์ƒ์žฅ** โ€” ์ดํ›„ ์žํšŒ์‚ฌ๊ฐ€ IPOํ•˜๋ฉด์„œ ์™ธ๋ถ€\n ํˆฌ์ž์ž์—๊ฒŒ ์‹ ์ฃผ๋ฅผ ํŒ”์•„ ์ž๋ณธ์„ ์กฐ๋‹ฌํ•˜๊ณ , ์ด๋Š” ๋ชจํšŒ์‚ฌ์˜ ์ง€๋ถ„์„ **ํฌ์„**์‹œํ‚ค๋ฉฐ ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ๋ฅผ\n **์ง€์ฃผ์‚ฌ ๋””์Šค์นด์šดํŠธ**์™€ **๋”๋ธ”์นด์šดํŒ…**(๊ฐ™์€ ์‚ฌ์—…์ด ๋‘ ๋ฒˆ ํ‰๊ฐ€๋˜์ง€๋งŒ ๋ชจํšŒ์‚ฌ๋Š” ๋ถ€๋ถ„ํ•ฉ ๋Œ€๋น„ ํ• ์ธ\n ๊ฑฐ๋ž˜)์— ๋…ธ์ถœ์‹œํ‚จ๋‹ค. ๊ธฐ์กด ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ์—๊ฒŒ ์ผ๋ฐ˜์ ์œผ๋กœ **๋ถ€์ •์ **.\n- **์ธ์ ๋ถ„ํ• **: ์‹ ์„ค ๋ฒ•์ธ์˜ ์ฃผ์‹์ด ๊ธฐ์กด ์ฃผ์ฃผ์—๊ฒŒ **์ง€๋ถ„์œจ๋Œ€๋กœ** ๋ฐฐ๋ถ„๋œ๋‹ค. ์ฃผ์ฃผ๊ฐ€ ์–‘์ชฝ์„ ์ง์ ‘\n ์†Œ์œ ํ•˜๋ฏ€๋กœ ๋ถ„ํ•  ์‹œ์ ์—๋Š” ๋Œ€์ฒด๋กœ **๊ฐ€์น˜ ์ค‘๋ฆฝ์ **์ด๋‹ค(์ดํ›„ ๋ฆฌ๋ ˆ์ดํŒ…์œผ๋กœ ๊ฐ€์น˜๊ฐ€ ์›€์ง์ผ ์ˆ˜๋Š” ์žˆ์Œ).\n\n๋Œ€ํ‘œ์  ๊ฒฝ๊ณ„ ์‚ฌ๋ก€๋Š” **LGํ™”ํ•™ โ†’ LG์—๋„ˆ์ง€์†”๋ฃจ์…˜**(2020๋…„ ๋ฌผ์ ๋ถ„ํ• , 2022๋…„ IPO): LGํ™”ํ•™ ์ฃผ์ฃผ๋Š”\n๋ฐฐํ„ฐ๋ฆฌ ์‚ฌ์—…์— ๋Œ€ํ•œ ๊ฐ„์ ‘ ์ง€๋ถ„์ด ํฌ์„๋๊ณ , LGํ™”ํ•™์€ ์ง€์ฃผ์‚ฌํ˜• ๋””์Šค์นด์šดํŠธ๋กœ ๋ฆฌ๋ ˆ์ดํŒ…๋๋‹ค. ์ด๋ฅผ ๊ธฐ์ค€\nํŒจํ„ด์œผ๋กœ ์‚ผ์•„๋ผ.\n\n## ์›Œํฌํ”Œ๋กœ ์ฒดํฌ๋ฆฌ์ŠคํŠธ\n\n```\nKR ๋ถ„ํ•  ๋ถ„์„ ์ง„ํ–‰:\n- [ ] Step 1: ๋Œ€์ƒ ์ข…๋ชฉ ํ™•์ธ ๋ฐ ๋ถ„ํ•  ์ด๋ฒคํŠธ ํƒ์ƒ‰\n- [ ] Step 2: ๋ถ„ํ•  ์œ ํ˜• ๋ถ„๋ฅ˜(๋ฌผ์  vs ์ธ์ ) ๋ฐ ํƒ€์ž„๋ผ์ธ ๊ตฌ์„ฑ\n- [ ] Step 3: ๋ถ„ํ•  ํ›„ ์ง€๋ถ„ / ์ง€๋ฐฐ๊ตฌ์กฐ ๋งคํ•‘\n- [ ] Step 4: ๊ธฐ์กด ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ์— ๋Œ€ํ•œ ๊ฐ€์น˜ ์˜ํ–ฅ ํ‰๊ฐ€\n- [ ] Step 5: (์„ ํƒ) ์žฌ๋ฌด๋กœ ์ •๋Ÿ‰ํ™”\n- [ ] Step 6: ํ‰๊ฐ€ + ์ฃผ์˜์‚ฌํ•ญ ์ œ์‹œ\n```\n\n## Step 1: ๋ถ„ํ•  ์ด๋ฒคํŠธ ํƒ์ƒ‰\n\n๋Œ€์ƒ ํ‹ฐ์ปค๋กœ `get_filings_kr`๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. ๋ถ„ํ•  ๊ณต์‹œ๋Š” **์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ**๋กœ ์ œ์ถœ๋˜๋ฏ€๋กœ\n`filing_type: \"material\"`์„ ์‚ฌ์šฉํ•œ๋‹ค. ๋ถ„ํ• ์€ ์ˆ˜๋…„ ์ „ ์ผ์ธ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์œผ๋ฏ€๋กœ `start_date`๋กœ ๊ธฐ๊ฐ„์„\n๋„“๊ฒŒ(์˜ˆ: 5~10๋…„ ์ „) ์ง€์ •ํ•œ๋‹ค โ€” ๊ธฐ๋ณธ ๋ฒ”์œ„๋Š” ~1๋…„๋ฟ์ด๋‹ค.\n\n`report_nm`์—์„œ ๋‹ค์Œ์„ ์Šค์บ”ํ•œ๋‹ค: `๋ถ„ํ• `, `๋ฌผ์ ๋ถ„ํ• `, `์ธ์ ๋ถ„ํ• `, `ํšŒ์‚ฌ๋ถ„ํ• `, `๋ถ„ํ• ํ•ฉ๋ณ‘`, ๊ทธ๋ฆฌ๊ณ \n์žํšŒ์‚ฌ IPO(`์ฆ๊ถŒ์‹ ๊ณ ์„œ` / `๋ฐœํ–‰๊ณต์‹œ`, ์ชผ๊ฐœ๊ธฐ ์ƒ์žฅ์ด ์˜์‹ฌ๋˜๋ฉด `filing_type: \"issuance\"`). ์ ‘์ˆ˜์ผ\n(`rcept_dt`)๊ณผ ์‹ ์„ค ๋ฒ•์ธ๋ช…์„ ๊ธฐ๋กํ•œ๋‹ค.\n\n์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฏธ ์ด๋ฒคํŠธ๋ฅผ ์ง€๋ชฉํ–ˆ๋‹ค๋ฉด(์˜ˆ: \"LGํ™”ํ•™ ๋ฌผ์ ๋ถ„ํ• \") ๋งน๋ชฉ์ ์œผ๋กœ ๊ฒ€์ƒ‰ํ•˜์ง€ ๋ง๊ณ  ๊ณต์‹œ์—์„œ\nํ™•์ธํ•˜๊ณ , ๊ณต์‹œ ๊ธฐ๋ก์ด ๋ถ€์กฑํ•˜๋ฉด ์ด์‚ฌํšŒ ๊ฒฐ์˜ / ์Šน์ธ์ผ์„ `web_search`๋กœ ๋ณด์™„ํ•œ๋‹ค.\n\n## Step 2: ๋ถ„๋ฅ˜ ๋ฐ ํƒ€์ž„๋ผ์ธ ๊ตฌ์„ฑ\n\n**๋ฌผ์ ๋ถ„ํ• **์ธ์ง€ **์ธ์ ๋ถ„ํ• **์ธ์ง€ ๋ช…์‹œํ•˜๋ผ โ€” ์ด๊ฒƒ์ด ์ „์ฒด ํ‰๊ฐ€๋ฅผ ์ขŒ์šฐํ•œ๋‹ค. ์งง์€ ํƒ€์ž„๋ผ์ธ์„\n๊ตฌ์„ฑํ•œ๋‹ค: ์ด์‚ฌํšŒ ๊ฒฐ์˜ โ†’ ์ฃผ์ฃผ์ดํšŒ(๋ถ„ํ•  ์Šน์ธ) โ†’ ๋ถ„ํ• ๊ธฐ์ผ(ํšจ๋ ฅ ๋ฐœ์ƒ์ผ) โ†’ ์žํšŒ์‚ฌ IPO์ผ(์žˆ๋Š” ๊ฒฝ์šฐ).\n\n## Step 3: ๋ถ„ํ•  ํ›„ ์ง€๋ถ„๊ตฌ์กฐ ๋งคํ•‘ (๋ณด์กฐ)\n\n๋ชจํšŒ์‚ฌ์— ๋Œ€ํ•ด `get_large_holders_kr`(5%๋ฃฐ ๋Œ€๋Ÿ‰๋ณด์œ )์™€ `get_insider_trades_kr`(์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ)๋ฅผ\n์‚ฌ์šฉํ•ด ๋ชจํšŒ์‚ฌ์˜ ์‹ ์„ค ๋ฒ•์ธ ์ง€๋ถ„๊ณผ ๊ทธ ์œ„์˜ ์ง€๋ฐฐ๊ฐ€๋ฌธ / ์ง€์ฃผ์‚ฌ ์ฒด์ธ์„ ํ™•์ธํ•œ๋‹ค. ๋” ๋„“์€ ๊ทธ๋ฃน ๋งฅ๋ฝ\n(์žฌ๋ฒŒ ๊ทธ๋ฃน ๊ตฌ์กฐ, ์ง€์ฃผ์‚ฌ ์œ„์น˜)์€ `web_search`๋ฅผ ์“ด๋‹ค โ€” ๊ทธ๋ฃน ์ „์ฒด ๋งคํ•‘์€ ๋ฒ”์œ„ ๋ฐ–์ด๋ฉฐ, ๊ฐ€์น˜ ์˜ํ–ฅ\n์„ค๋ช…์— ํ•„์š”ํ•œ ๋งŒํผ๋งŒ ๊ฐ€์ ธ์˜จ๋‹ค.\n\n## Step 4: ๊ธฐ์กด ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ์— ๋Œ€ํ•œ ๊ฐ€์น˜ ์˜ํ–ฅ ํ‰๊ฐ€\n\nํ•ต์‹ฌ์ด๋‹ค. **๋ฌผ์ ๋ถ„ํ•  + IPO** ํŒจํ„ด์ด๋ฉด ๋‹ค์Œ์„ ํ‰๊ฐ€ํ•œ๋‹ค:\n- **๊ฐ„์ ‘ ์ง€๋ถ„ ํฌ์„**: IPO์—์„œ ๋ชจํšŒ์‚ฌ๊ฐ€ ์žํšŒ์‚ฌ ์ง€๋ถ„์„ ์–ผ๋งˆ๋‚˜ ๋‚ด์คฌ๋‚˜?\n- **์ง€์ฃผ์‚ฌ / ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ**: ๋ชจํšŒ์‚ฌ๊ฐ€ ์ด์ œ ๋ถ€๋ถ„ํ•ฉ(sum-of-parts) ์ดํ•˜๋กœ ๊ฑฐ๋ž˜๋˜๋‚˜?\n- **๋”๋ธ”์นด์šดํŒ…**: ํ•ต์‹ฌ ์‚ฌ์—…์ด ์ƒ์žฅ ์žํšŒ์‚ฌ์™€ ๋ชจํšŒ์‚ฌ ์–‘์ชฝ์—์„œ ํ‰๊ฐ€๋˜๊ณ  ์žˆ๋‚˜?\n- **์„ฑ์žฅ ์—”์ง„ ์ƒ์‹ค**: ๋ชจํšŒ์‚ฌ์˜ ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ์„ฑ์žฅํ•˜๋˜ ์‚ฌ์—…๋ถ€๊ฐ€ ์ง์ ‘ ๋ณด์œ  ๋ฒ•์ธ์—์„œ ๋น ์ ธ๋‚˜๊ฐ”๋‚˜?\n- **IPO ์กฐ๋‹ฌ์ž๊ธˆ ์šฉ๋„**: ์ž๊ธˆ์ด ์žํšŒ์‚ฌ๋กœ ๊ฐ€๋‚˜(๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ๋Š” ์ง์ ‘ ์ˆ˜ํ˜œ ์—†์Œ), ๋ชจํšŒ์‚ฌ๋กœ ๊ฐ€๋‚˜?\n\n**์ธ์ ๋ถ„ํ• **์ด๋ฉด ๋ถ„ํ•  ์‹œ์ ์—๋Š” ๋Œ€์ฒด๋กœ ์ค‘๋ฆฝ์ž„์„ ๋ฐํžˆ๊ณ , ์ดํ›„ ๋ฆฌ๋ ˆ์ดํŒ…์„ ์‚ดํ•€๋‹ค.\n\n## Step 5: (์„ ํƒ) ์žฌ๋ฌด๋กœ ์ •๋Ÿ‰ํ™”\n\n`get_financials_kr`๋กœ ๋ชจํšŒ์‚ฌ์˜ ์—ฐ๊ฒฐ(CFS) vs ๋ณ„๋„(OFS) ์ˆ˜์น˜๋ฅผ ๋น„๊ตํ•ด โ€” ์ง์ ‘ ๋ณด์œ  ๋ฒ•์ธ์—์„œ ์–ผ๋งˆ๋‚˜\n๋งŽ์€ ์ด์ต ์ฐฝ์ถœ๋ ฅ์ด ๋น ์ ธ๋‚˜๊ฐ”๋Š”์ง€ ๋ณด์—ฌ์ค€๋‹ค. `fs_div`๊ฐ€ ๋‹ค๋ฅธ ๋‘ ๋ฒˆ์˜ ํ˜ธ์ถœ์ด ํ•„์š”ํ•˜๋‹ค.\n\n> ์ถœ๋ ฅ์€ `periods[].summary`์— ์ •๊ทœํ™”๋ผ ์žˆ๋‹ค(KRW). `incomeStatement.revenue / operatingProfit / netIncome`,\n> `balanceSheet.totalAssets / totalEquity`๋ฅผ ์—ฐ๊ฒฐยท๋ณ„๋„ ๊ฐ๊ฐ์—์„œ ์ฝ์–ด ๋น„๊ตํ•˜๋ผ. ๊ฐ ๊ฐ’์€ `{ current, prior }`.\n> summary๊ฐ€ ๋น„์–ด ์žˆ์œผ๋ฉด `rawLineItemsFile`์„ `read_file`๋กœ ์—ฐ๋‹ค. `account_nm`์€ ํšŒ์‚ฌยท์—ฐ๋„๋งˆ๋‹ค ๋‹ฌ๋ผ\n> ์ •ํ™• ์ผ์น˜ ๊ธˆ์ง€ โ€” ๋ถ€๋ถ„ ๋ฌธ์ž์—ด/`account_id`๋กœ ๋งค์นญํ•œ๋‹ค.\n\n## Step 6: ์ถœ๋ ฅ ํ˜•์‹\n\n๋‹ค์Œ์„ ์ œ์‹œํ•œ๋‹ค:\n1. **ํ‰๊ฐ€**(1~2๋ฌธ์žฅ): ๊ธฐ์กด ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ์—๊ฒŒ ์ˆœ๋“ / ์ค‘๋ฆฝ / ์ˆœ์‹ค, ๊ทธ๋ฆฌ๊ณ  ๊ฐ€์žฅ ํฐ ์ด์œ  ํ•˜๋‚˜.\n2. **์ด๋ฒคํŠธ ์š”์•ฝ**: ๋ถ„ํ•  ์œ ํ˜•, ํƒ€์ž„๋ผ์ธ, ๊ด€๋ จ ๋ฒ•์ธ.\n3. **์ง€๋ถ„ ์˜ํ–ฅ**: ํฌ์„๊ณผ ์ง€๋ฐฐ์ฒด์ธ ํšจ๊ณผ(๊ฐ€๋Šฅํ•˜๋ฉด ์ˆ˜์น˜ ํฌํ•จ).\n4. **๊ฐ€์น˜ ์˜ํ–ฅ**: Step 4์˜ ๋””์Šค์นด์šดํŠธ / ๋”๋ธ”์นด์šดํŒ… / ์„ฑ์žฅ์ƒ์‹ค ๋ถ„์„.\n5. **์ฃผ์˜์‚ฌํ•ญ**: ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ๋Š” ๋ถ€๋ถ„์ ์œผ๋กœ ๊ตฌ์กฐ์ ์ด๋ฉฐ, ์†Œ์ˆ˜์ฃผ์ฃผ ๋ณดํ˜ธ ๊ทœ์ •์ด ๊ฐ•ํ™”๋ผ ์™”๋‹ค\n (์˜ˆ: 2022๋…„ ์ดํ›„ ์ชผ๊ฐœ๊ธฐ ์ƒ์žฅ์— ๋Œ€ํ•œ ๊ทœ์ œ ๋ฐ˜๋ฐœ) โ€” ํƒ€์ด๋ฐ๊ณผ ๊ด€๋ จ ์žˆ์œผ๋ฉด ์–ธ๊ธ‰." + }, + { + "tool": "get_filings_kr", + "args": { + "ticker": "051910", + "filing_type": "material", + "start_date": "2020-01-01", + "end_date": "2022-12-31", + "limit": 50 + }, + "result": "{\"data\":{\"ticker\":\"051910\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"filings\":[{\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"stock_code\":\"051910\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ฒ˜๋ถ„๊ฒฐ์ •)\",\"rcept_no\":\"20221123000333\",\"flr_nm\":\"LGํ™”ํ•™\",\"rcept_dt\":\"20221123\",\"rm\":\"\"},{\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"stock_code\":\"051910\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ฒ˜๋ถ„๊ฒฐ์ •)\",\"rcept_no\":\"20211022000367\",\"flr_nm\":\"LGํ™”ํ•™\",\"rcept_dt\":\"20211022\",\"rm\":\"\"},{\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"stock_code\":\"051910\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(ํšŒ์‚ฌ๋ถ„ํ• ๊ฒฐ์ •)\",\"rcept_no\":\"20200917000127\",\"flr_nm\":\"LGํ™”ํ•™\",\"rcept_dt\":\"20200917\",\"rm\":\"\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/list.json?corp_code=00356361&bgn_de=20200101&end_de=20221231&pblntf_ty=B&page_count=50&page_no=1&sort=date&sort_mth=desc\"]}" + }, + { + "tool": "get_large_holders_kr", + "args": { + "ticker": "051910", + "limit": 20 + }, + "result": "{\"data\":{\"ticker\":\"051910\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"holders\":[{\"rcept_no\":\"20260305001433\",\"rcept_dt\":\"2026-03-05\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"report_tp\":\"์•ฝ์‹\",\"repror\":\"BlackRockFundAdvisors\",\"stkqy\":\"3,539,585\",\"stkqy_irds\":\"57,870\",\"stkrt\":\"5.01\",\"stkrt_irds\":\"0.08\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"- ๋‹จ์ˆœํˆฌ์ž๋ชฉ์ ์œผ๋กœ ์žฅ๋‚ด์—์„œ ๋ฐœํ–‰ํšŒ์‚ฌ์˜ ์ฃผ์‹ ๋งค์ˆ˜\"},{\"rcept_no\":\"20250401002135\",\"rcept_dt\":\"2025-04-01\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"report_tp\":\"์•ฝ์‹\",\"repror\":\"๊ตญ๋ฏผ์—ฐ๊ธˆ๊ณต๋‹จ\",\"stkqy\":\"6,096,881\",\"stkqy_irds\":\"735,235\",\"stkrt\":\"8.64\",\"stkrt_irds\":\"1.04\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"๋‹จ์ˆœ์ถ”๊ฐ€์ทจ๋“/์ฒ˜๋ถ„\"},{\"rcept_no\":\"20250307000710\",\"rcept_dt\":\"2025-03-07\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"LG\",\"stkqy\":\"24,691,077\",\"stkqy_irds\":\"642,241\",\"stkrt\":\"34.98\",\"stkrt_irds\":\"0.91\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\"},{\"rcept_no\":\"20241129001948\",\"rcept_dt\":\"2024-11-29\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"LG\",\"stkqy\":\"24,048,836\",\"stkqy_irds\":\"493,879\",\"stkrt\":\"34.07\",\"stkrt_irds\":\"0.7\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\"},{\"rcept_no\":\"20240819000186\",\"rcept_dt\":\"2024-08-19\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"report_tp\":\"์•ฝ์‹\",\"repror\":\"๊ตญ๋ฏผ์—ฐ๊ธˆ๊ณต๋‹จ\",\"stkqy\":\"5,361,646\",\"stkqy_irds\":\"16,737\",\"stkrt\":\"7.60\",\"stkrt_irds\":\"0.03\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"๋‹จ์ˆœํˆฌ์ž๋ชฉ์ ์—์„œ ์ผ๋ฐ˜ํˆฌ์ž๋ชฉ์ ์œผ๋กœ ๋ณด์œ ๋ชฉ์  ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20240805000259\",\"rcept_dt\":\"2024-08-05\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"report_tp\":\"์•ฝ์‹\",\"repror\":\"๊ตญ๋ฏผ์—ฐ๊ธˆ๊ณต๋‹จ\",\"stkqy\":\"5,344,909\",\"stkqy_irds\":\"149,763\",\"stkrt\":\"7.57\",\"stkrt_irds\":\"0.21\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"์ผ๋ฐ˜ํˆฌ์ž๋ชฉ์ ์—์„œ ๋‹จ์ˆœํˆฌ์ž๋ชฉ์ ์œผ๋กœ ๋ณด์œ ๋ชฉ์  ๋ณ€๊ฒฝ\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/majorstock.json?corp_code=00356361\"]}" + }, + { + "tool": "get_financials_kr", + "args": { + "query": "LGํ™”ํ•™ 051910 2019 2020 2021 2022 2023 ์—ฐ๊ฒฐ ๋ฐ ๋ณ„๋„ ์žฌ๋ฌด์ œํ‘œ ๋งค์ถœ ์˜์—…์ด์ต ์ˆœ์ด์ต ์ž์‚ฐ ์ž๋ณธ ์š”์•ฝ" + }, + "result": "{\"data\":{\"get_business_report_kr_051910\":{\"ticker\":\"051910\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"report_type\":\"annual\",\"fs_div\":\"OFS\",\"periods\":[{\"bsns_year\":2023,\"report_type\":\"annual\",\"fs_div\":\"OFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":19947377000000,\"prior\":22637157000000,\"label\":\"๋งค์ถœ\",\"display\":\"19.9์กฐ\"},\"operatingProfit\":{\"current\":-109149000000,\"prior\":1052276000000,\"label\":\"์˜์—…์ด์ต\",\"display\":\"-1,091์–ต\"},\"netIncome\":{\"current\":228126000000,\"prior\":3006969000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต\",\"display\":\"2,281์–ต\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":2910,\"prior\":38573,\"label\":\"๋ณดํ†ต์ฃผ ๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต\",\"display\":\"2,910์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":34093584000000,\"prior\":32872824000000,\"label\":\"์ž์‚ฐ ์ด๊ณ„\",\"display\":\"34.1์กฐ\"},\"totalLiabilities\":{\"current\":13688372000000,\"prior\":11799396000000,\"label\":\"๋ถ€์ฑ„ ์ด๊ณ„\",\"display\":\"13.7์กฐ\"},\"totalEquity\":{\"current\":20405212000000,\"prior\":21073428000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"20.4์กฐ\"},\"cashAndEquivalents\":{\"current\":2096074000000,\"prior\":1323209000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"2.1์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":2104000000000,\"prior\":1387508000000,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"2.1์กฐ\"},\"investing\":{\"current\":-2912170000000,\"prior\":61821000000,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-2.9์กฐ\"},\"financing\":{\"current\":1587984000000,\"prior\":-1185101000000,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"1.6์กฐ\"},\"capex\":{\"current\":2371302000000,\"prior\":1732944000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"2.4์กฐ\"}},\"ratios\":{\"operatingMarginPct\":-0.5,\"netMarginPct\":1.1,\"roePct\":1.1,\"debtToEquityPct\":67.1,\"revenueYoYPct\":-11.9,\"operatingProfitYoYPct\":-110.4,\"netIncomeYoYPct\":-92.4,\"freeCashFlow\":-267302000000,\"freeCashFlowDisplay\":\"-2,673์–ต\"}},{\"bsns_year\":2022,\"report_type\":\"annual\",\"fs_div\":\"OFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":23403601000000,\"prior\":20471044000000,\"label\":\"๋งค์ถœ\",\"display\":\"23.4์กฐ\"},\"operatingProfit\":{\"current\":1070050000000,\"prior\":3019160000000,\"label\":\"์˜์—…์ด์ต\",\"display\":\"1.1์กฐ\"},\"netIncome\":{\"current\":3006969000000,\"prior\":2514263000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"3.0์กฐ\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":32872824000000,\"prior\":30929152000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"32.9์กฐ\"},\"totalLiabilities\":{\"current\":11799396000000,\"prior\":12144275000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"11.8์กฐ\"},\"totalEquity\":{\"current\":21073428000000,\"prior\":18784877000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"21.1์กฐ\"},\"cashAndEquivalents\":{\"current\":1323209000000,\"prior\":1056665000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"1.3์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":1387508000000,\"prior\":4041768000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"1.4์กฐ\"},\"investing\":{\"current\":61821000000,\"prior\":-3055452000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"618์–ต\"},\"financing\":{\"current\":-1185101000000,\"prior\":-790329000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-1.2์กฐ\"},\"capex\":{\"current\":-1732944000000,\"prior\":-1990446000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-1.7์กฐ\"}},\"ratios\":{\"operatingMarginPct\":4.6,\"netMarginPct\":12.8,\"roePct\":14.3,\"debtToEquityPct\":56,\"revenueYoYPct\":14.3,\"operatingProfitYoYPct\":-64.6,\"netIncomeYoYPct\":19.6,\"freeCashFlow\":-345436000000,\"freeCashFlowDisplay\":\"-3,454์–ต\"}},{\"bsns_year\":2021,\"report_type\":\"annual\",\"fs_div\":\"OFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":20471044000000,\"prior\":20830009000000,\"label\":\"๋งค์ถœ\",\"display\":\"20.5์กฐ\"},\"operatingProfit\":{\"current\":3019160000000,\"prior\":1114437000000,\"label\":\"์˜์—…์ด์ต (์†์‹ค)\",\"display\":\"3.0์กฐ\"},\"netIncome\":{\"current\":2514263000000,\"prior\":-98223000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"2.5์กฐ\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":30929152000000,\"prior\":25850150000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"30.9์กฐ\"},\"totalLiabilities\":{\"current\":12144275000000,\"prior\":8886645000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"12.1์กฐ\"},\"totalEquity\":{\"current\":18784877000000,\"prior\":16963505000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"18.8์กฐ\"},\"cashAndEquivalents\":{\"current\":1056665000000,\"prior\":852668000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"1.1์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":4041768000000,\"prior\":5236679000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"4.0์กฐ\"},\"investing\":{\"current\":-3055452000000,\"prior\":-4391344000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-3.1์กฐ\"},\"financing\":{\"current\":-790329000000,\"prior\":-989022000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-7,903์–ต\"},\"capex\":{\"current\":-1990446000000,\"prior\":-2187196000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-2.0์กฐ\"}},\"ratios\":{\"operatingMarginPct\":14.7,\"netMarginPct\":12.3,\"roePct\":13.4,\"debtToEquityPct\":64.6,\"revenueYoYPct\":-1.7,\"operatingProfitYoYPct\":170.9,\"netIncomeYoYPct\":2659.7,\"freeCashFlow\":2051322000000,\"freeCashFlowDisplay\":\"2.1์กฐ\"}},{\"bsns_year\":2020,\"report_type\":\"annual\",\"fs_div\":\"OFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":20830009000000,\"prior\":21809592000000,\"label\":\"๋งค์ถœ\",\"display\":\"20.8์กฐ\"},\"operatingProfit\":{\"current\":1114437000000,\"prior\":624165000000,\"label\":\"์˜์—…์ด์ต (์†์‹ค)\",\"display\":\"1.1์กฐ\"},\"netIncome\":{\"current\":-98223000000,\"prior\":365924000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"-982์–ต\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":25850150000000,\"prior\":27027086000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"25.9์กฐ\"},\"totalLiabilities\":{\"current\":8886645000000,\"prior\":10566856000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"8.9์กฐ\"},\"totalEquity\":{\"current\":16963505000000,\"prior\":16460230000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"17.0์กฐ\"},\"cashAndEquivalents\":{\"current\":852668000000,\"prior\":1002263000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"8,527์–ต\"}},\"cashFlow\":{\"operating\":{\"current\":5236679000000,\"prior\":1943151000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"5.2์กฐ\"},\"investing\":{\"current\":-4391344000000,\"prior\":-4862288000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-4.4์กฐ\"},\"financing\":{\"current\":-989022000000,\"prior\":2240321000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-9,890์–ต\"},\"capex\":{\"current\":-2187196000000,\"prior\":-2800070000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-2.2์กฐ\"}},\"ratios\":{\"operatingMarginPct\":5.4,\"netMarginPct\":-0.5,\"roePct\":-0.6,\"debtToEquityPct\":52.4,\"revenueYoYPct\":-4.5,\"operatingProfitYoYPct\":78.5,\"netIncomeYoYPct\":-126.8,\"freeCashFlow\":3049483000000,\"freeCashFlowDisplay\":\"3.0์กฐ\"}},{\"bsns_year\":2019,\"report_type\":\"annual\",\"fs_div\":\"OFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":22312561000000,\"prior\":23315318000000,\"label\":\"๋งค์ถœ\",\"display\":\"22.3์กฐ\"},\"operatingProfit\":{\"current\":582425000000,\"prior\":1953618000000,\"label\":\"์˜์—…์ด์ต (์†์‹ค)\",\"display\":\"5,824์–ต\"},\"netIncome\":{\"current\":365924000000,\"prior\":1581064000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"3,659์–ต\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":27027086000000,\"prior\":23730578000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"27.0์กฐ\"},\"totalLiabilities\":{\"current\":10566856000000,\"prior\":7180672000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"10.6์กฐ\"},\"totalEquity\":{\"current\":16460230000000,\"prior\":16549906000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"16.5์กฐ\"},\"cashAndEquivalents\":{\"current\":1002263000000,\"prior\":1690862000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"1.0์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":1943151000000,\"prior\":2024847000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"1.9์กฐ\"},\"investing\":{\"current\":-4862288000000,\"prior\":-3017036000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-4.9์กฐ\"},\"financing\":{\"current\":2240321000000,\"prior\":1046184000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"2.2์กฐ\"},\"capex\":{\"current\":-2800070000000,\"prior\":-2515595000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-2.8์กฐ\"}},\"ratios\":{\"operatingMarginPct\":2.6,\"netMarginPct\":1.6,\"roePct\":2.2,\"debtToEquityPct\":64.2,\"revenueYoYPct\":-4.3,\"operatingProfitYoYPct\":-70.2,\"netIncomeYoYPct\":-76.9,\"freeCashFlow\":-856919000000,\"freeCashFlowDisplay\":\"-8,569์–ต\"}}],\"rawLineItemsFile\":\".dexter/tool-results/kr-financials-00356361-11011-OFS-2019_2023.json\",\"note\":\"periods[].summary holds normalized key metrics (KRW). Use read_file on rawLineItemsFile only for accounts not in the summary.\"}},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2023&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2022&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2021&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2020&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2019&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2023&reprt_code=11011&fs_div=OFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2022&reprt_code=11011&fs_div=OFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2021&reprt_code=11011&fs_div=OFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2020&reprt_code=11011&fs_div=OFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2019&reprt_code=11011&fs_div=OFS\"]}" + }, + { + "tool": "get_financials_kr", + "args": { + "query": "LGํ™”ํ•™ 051910 ์—ฐ๊ฒฐ์žฌ๋ฌด์ œํ‘œ CFS 2019 2020 2021 2022 2023 ๋งค์ถœ ์˜์—…์ด์ต ์ˆœ์ด์ต ์ž์‚ฐ ์ž๋ณธ ์š”์•ฝ" + }, + "result": "{\"data\":{\"get_business_report_kr_051910\":{\"ticker\":\"051910\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"periods\":[{\"bsns_year\":2023,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":55249785000000,\"prior\":50983251000000,\"label\":\"๋งค์ถœ\",\"display\":\"55.2์กฐ\"},\"operatingProfit\":{\"current\":2529196000000,\"prior\":2979354000000,\"label\":\"์˜์—…์ด์ต\",\"display\":\"2.5์กฐ\"},\"netIncome\":{\"current\":2053425000000,\"prior\":2195518000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต\",\"display\":\"2.1์กฐ\"},\"controllingNetIncome\":{\"current\":1337812000000,\"prior\":1845363000000,\"label\":\"์ง€๋ฐฐ์ฃผ์ฃผ์— ๊ท€์†๋˜๋Š” ์ˆœ์ด์ต\",\"display\":\"1.3์กฐ\"},\"eps\":{\"current\":17086,\"prior\":23670,\"label\":\"๋ณดํ†ต์ฃผ ๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต\",\"display\":\"17,086์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":77466694000000,\"prior\":67973823000000,\"label\":\"์ž์‚ฐ ์ด๊ณ„\",\"display\":\"77.5์กฐ\"},\"totalLiabilities\":{\"current\":36528509000000,\"prior\":30492696000000,\"label\":\"๋ถ€์ฑ„ ์ด๊ณ„\",\"display\":\"36.5์กฐ\"},\"totalEquity\":{\"current\":40938185000000,\"prior\":37481127000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"40.9์กฐ\"},\"cashAndEquivalents\":{\"current\":9084899000000,\"prior\":8497882000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"9.1์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":7536462000000,\"prior\":569880000000,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"7.5์กฐ\"},\"investing\":{\"current\":-13169629000000,\"prior\":-9229217000000,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-13.2์กฐ\"},\"financing\":{\"current\":6157504000000,\"prior\":13331856000000,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"6.2์กฐ\"},\"capex\":{\"current\":-12959891000000,\"prior\":-8406288000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-13.0์กฐ\"}},\"ratios\":{\"operatingMarginPct\":4.6,\"netMarginPct\":3.7,\"roePct\":5,\"debtToEquityPct\":89.2,\"revenueYoYPct\":8.4,\"operatingProfitYoYPct\":-15.1,\"netIncomeYoYPct\":-6.5,\"freeCashFlow\":-5423429000000,\"freeCashFlowDisplay\":\"-5.4์กฐ\"}},{\"bsns_year\":2022,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":51864888000000,\"prior\":42599284000000,\"label\":\"๋งค์ถœ\",\"display\":\"51.9์กฐ\"},\"operatingProfit\":{\"current\":2995698000000,\"prior\":5026370000000,\"label\":\"์˜์—…์ด์ต\",\"display\":\"3.0์กฐ\"},\"netIncome\":{\"current\":2195518000000,\"prior\":3953904000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"2.2์กฐ\"},\"controllingNetIncome\":{\"current\":1845363000000,\"prior\":3669787000000,\"label\":\"์ง€๋ฐฐ๊ธฐ์—…์˜ ์†Œ์œ ์ฃผ์ง€๋ถ„\",\"display\":\"1.8์กฐ\"},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":67973823000000,\"prior\":51135302000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"68.0์กฐ\"},\"totalLiabilities\":{\"current\":30492696000000,\"prior\":27925314000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"30.5์กฐ\"},\"totalEquity\":{\"current\":37481127000000,\"prior\":23209988000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"37.5์กฐ\"},\"cashAndEquivalents\":{\"current\":8497882000000,\"prior\":3760834000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"8.5์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":569880000000,\"prior\":5509816000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"5,699์–ต\"},\"investing\":{\"current\":-9229217000000,\"prior\":-5349305000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-9.2์กฐ\"},\"financing\":{\"current\":13331856000000,\"prior\":123670000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"13.3์กฐ\"},\"capex\":{\"current\":-8406288000000,\"prior\":-5773593000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-8.4์กฐ\"}},\"ratios\":{\"operatingMarginPct\":5.8,\"netMarginPct\":4.2,\"roePct\":5.9,\"debtToEquityPct\":81.4,\"revenueYoYPct\":21.8,\"operatingProfitYoYPct\":-40.4,\"netIncomeYoYPct\":-44.5,\"freeCashFlow\":-7836408000000,\"freeCashFlowDisplay\":\"-7.8์กฐ\"}},{\"bsns_year\":2021,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":42654722000000,\"prior\":30058872000000,\"label\":\"๋งค์ถœ\",\"display\":\"42.7์กฐ\"},\"operatingProfit\":{\"current\":5025466000000,\"prior\":1805359000000,\"label\":\"์˜์—…์ด์ต (์†์‹ค)\",\"display\":\"5.0์กฐ\"},\"netIncome\":{\"current\":3953904000000,\"prior\":682408000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"4.0์กฐ\"},\"controllingNetIncome\":{\"current\":3669787000000,\"prior\":512634000000,\"label\":\"์ง€๋ฐฐ๊ธฐ์—…์˜ ์†Œ์œ ์ฃผ์ง€๋ถ„\",\"display\":\"3.7์กฐ\"},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":51135302000000,\"prior\":41388894000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"51.1์กฐ\"},\"totalLiabilities\":{\"current\":27925314000000,\"prior\":22598454000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"27.9์กฐ\"},\"totalEquity\":{\"current\":23209988000000,\"prior\":18790440000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"23.2์กฐ\"},\"cashAndEquivalents\":{\"current\":3760834000000,\"prior\":3274250000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"3.8์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":5509816000000,\"prior\":5840832000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"5.5์กฐ\"},\"investing\":{\"current\":-5349305000000,\"prior\":-5296375000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-5.3์กฐ\"},\"financing\":{\"current\":123670000000,\"prior\":938069000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"1,237์–ต\"},\"capex\":{\"current\":-5773593000000,\"prior\":-5535791000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-5.8์กฐ\"}},\"ratios\":{\"operatingMarginPct\":11.8,\"netMarginPct\":9.3,\"roePct\":17,\"debtToEquityPct\":120.3,\"revenueYoYPct\":41.9,\"operatingProfitYoYPct\":178.4,\"netIncomeYoYPct\":479.4,\"freeCashFlow\":-263777000000,\"freeCashFlowDisplay\":\"-2,638์–ต\"}},{\"bsns_year\":2020,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":30076510000000,\"prior\":27353078000000,\"label\":\"๋งค์ถœ\",\"display\":\"30.1์กฐ\"},\"operatingProfit\":{\"current\":1798159000000,\"prior\":825415000000,\"label\":\"์˜์—…์ด์ต (์†์‹ค)\",\"display\":\"1.8์กฐ\"},\"netIncome\":{\"current\":682408000000,\"prior\":376104000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"6,824์–ต\"},\"controllingNetIncome\":{\"current\":512634000000,\"prior\":313365000000,\"label\":\"์ง€๋ฐฐ๊ธฐ์—…์˜ ์†Œ์œ ์ฃผ์ง€๋ถ„\",\"display\":\"5,126์–ต\"},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":41388894000000,\"prior\":34024426000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"41.4์กฐ\"},\"totalLiabilities\":{\"current\":22598454000000,\"prior\":16640637000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"22.6์กฐ\"},\"totalEquity\":{\"current\":18790440000000,\"prior\":17383789000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"18.8์กฐ\"},\"cashAndEquivalents\":{\"current\":3274250000000,\"prior\":1888631000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"3.3์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":5840832000000,\"prior\":3121101000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"5.8์กฐ\"},\"investing\":{\"current\":-5296375000000,\"prior\":-6111441000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-5.3์กฐ\"},\"financing\":{\"current\":938069000000,\"prior\":2300645000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"9,381์–ต\"},\"capex\":{\"current\":-5535791000000,\"prior\":-6238203000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-5.5์กฐ\"}},\"ratios\":{\"operatingMarginPct\":6,\"netMarginPct\":2.3,\"roePct\":3.6,\"debtToEquityPct\":120.3,\"revenueYoYPct\":10,\"operatingProfitYoYPct\":117.8,\"netIncomeYoYPct\":81.4,\"freeCashFlow\":305041000000,\"freeCashFlowDisplay\":\"3,050์–ต\"}},{\"bsns_year\":2019,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":28625042000000,\"prior\":28183013000000,\"label\":\"๋งค์ถœ\",\"display\":\"28.6์กฐ\"},\"operatingProfit\":{\"current\":895648000000,\"prior\":2246070000000,\"label\":\"์˜์—…์ด์ต (์†์‹ค)\",\"display\":\"8,956์–ต\"},\"netIncome\":{\"current\":376104000000,\"prior\":1519312000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"3,761์–ต\"},\"controllingNetIncome\":{\"current\":313365000000,\"prior\":1472608000000,\"label\":\"์ง€๋ฐฐ๊ธฐ์—…์˜ ์†Œ์œ ์ฃผ์ง€๋ถ„\",\"display\":\"3,134์–ต\"},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":34024426000000,\"prior\":28944137000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"34.0์กฐ\"},\"totalLiabilities\":{\"current\":16640637000000,\"prior\":11622009000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"16.6์กฐ\"},\"totalEquity\":{\"current\":17383789000000,\"prior\":17322128000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"17.4์กฐ\"},\"cashAndEquivalents\":{\"current\":1888631000000,\"prior\":2513702000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"1.9์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":3121101000000,\"prior\":2124969000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"3.1์กฐ\"},\"investing\":{\"current\":-6111441000000,\"prior\":-3639046000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-6.1์กฐ\"},\"financing\":{\"current\":2300645000000,\"prior\":1793848000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"2.3์กฐ\"},\"capex\":{\"current\":-6238203000000,\"prior\":-4219355000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-6.2์กฐ\"}},\"ratios\":{\"operatingMarginPct\":3.1,\"netMarginPct\":1.3,\"roePct\":2.2,\"debtToEquityPct\":95.7,\"revenueYoYPct\":1.6,\"operatingProfitYoYPct\":-60.1,\"netIncomeYoYPct\":-75.2,\"freeCashFlow\":-3117102000000,\"freeCashFlowDisplay\":\"-3.1์กฐ\"}}],\"rawLineItemsFile\":\".dexter/tool-results/kr-financials-00356361-11011-CFS-2019_2023.json\",\"note\":\"periods[].summary holds normalized key metrics (KRW). Use read_file on rawLineItemsFile only for accounts not in the summary.\"}},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2023&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2022&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2021&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2020&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2019&reprt_code=11011&fs_div=CFS\"]}" + }, + { + "tool": "get_market_data", + "args": { + "query": "LGํ™”ํ•™ 051910 stock price around 2020-09-17, 2021-01 peak, 2022-01-27 LG Energy Solution IPO, current or 2023 year-end performance" + }, + "result": "{\"data\":{\"_errors\":[{\"tool\":\"get_stock_prices\",\"args\":{\"ticker\":\"051910\",\"start_date\":\"2020-09-14\",\"end_date\":\"2020-09-21\",\"interval\":\"day\"},\"error\":\"[Financial Datasets API] request failed: 400 Bad Request\"},{\"tool\":\"get_stock_prices\",\"args\":{\"ticker\":\"051910\",\"start_date\":\"2021-01-01\",\"end_date\":\"2021-01-31\",\"interval\":\"day\"},\"error\":\"[Financial Datasets API] request failed: 400 Bad Request\"},{\"tool\":\"get_stock_prices\",\"args\":{\"ticker\":\"051910\",\"start_date\":\"2022-01-24\",\"end_date\":\"2022-01-31\",\"interval\":\"day\"},\"error\":\"[Financial Datasets API] request failed: 400 Bad Request\"},{\"tool\":\"get_stock_prices\",\"args\":{\"ticker\":\"051910\",\"start_date\":\"2023-01-01\",\"end_date\":\"2023-12-31\",\"interval\":\"day\"},\"error\":\"[Financial Datasets API] request failed: 400 Bad Request\"},{\"tool\":\"get_stock_price\",\"args\":{\"ticker\":\"051910\"},\"error\":\"[Financial Datasets API] request failed: 400 Bad Request\"}]}}" + } + ] +} diff --git a/scripts/kr-eval/judge.test.ts b/scripts/kr-eval/judge.test.ts new file mode 100644 index 000000000..e5c758d21 --- /dev/null +++ b/scripts/kr-eval/judge.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from 'bun:test'; +import { toolDigest } from './judge.js'; + +describe('toolDigest', () => { + it('returns a placeholder when no tools were called', () => { + expect(toolDigest([])).toBe('(no tools were called)'); + }); + + it('represents EVERY tool even when results exceed the budget', () => { + // 4 tools, each result far larger than the per-call slice โ€” none should be dropped. + const calls = ['a', 'b', 'c', 'd'].map((t) => ({ + tool: `tool_${t}`, + args: { ticker: '005930' }, + result: 'x'.repeat(10_000), + })); + const digest = toolDigest(calls, 4000); + for (const t of ['tool_a', 'tool_b', 'tool_c', 'tool_d']) { + expect(digest).toContain(`### ${t}(`); + } + // each oversized result is truncated (carries the โ€ฆ[N chars] marker) + expect(digest).toContain('chars]'); + }); + + it('does not truncate results that fit within the per-call budget', () => { + const digest = toolDigest([{ tool: 'get_filings_kr', args: { ticker: '005380' }, result: '{"filings":3}' }]); + expect(digest).toContain('{"filings":3}'); + expect(digest).not.toContain('chars]'); + }); +}); diff --git a/scripts/kr-eval/judge.ts b/scripts/kr-eval/judge.ts new file mode 100644 index 000000000..6ba551266 --- /dev/null +++ b/scripts/kr-eval/judge.ts @@ -0,0 +1,116 @@ +// Per-dimension LLM judges. Mirrors src/evals/run.ts's correctnessEvaluator +// (callLlm + a Zod {score,comment} schema), but scores the KR-specific +// dimensions from the Korean playbook in src/agent/prompts.ts. The agent's own +// answer is what's under test; this is the judge for answer quality. + +import { z } from 'zod'; +import { callLlm } from '../../src/model/llm.js'; +import type { DimensionId } from './questions.js'; +import type { RawDimension } from './scorer.js'; + +export const DEFAULT_JUDGE_MODEL = 'gpt-5.5'; + +const DimensionScoreSchema = z.object({ + score: z.number().min(0).max(1), + comment: z.string(), +}); + +const JUDGE_SYSTEM_PROMPT = + 'You are a meticulous equity-research reviewer scoring a Korean-stock research ' + + 'answer on ONE dimension. Be strict and concrete. Output a score in [0,1] and a ' + + 'one-sentence comment citing specific evidence (or its absence) from the answer.'; + +/** What each dimension rewards. Grounded in src/agent/prompts.ts (Korean playbook). */ +const DIMENSION_RUBRICS: Record = { + earnings_yoy: + 'DIMENSION = ์‹ค์ (์†์ต) with YoY. Reward the answer ONLY if it states revenue (๋งค์ถœ), ' + + '์˜์—…์ด์ต, ์ˆœ์ด์ต, margins, or ROE with concrete numbers AND a year-over-year (YoY / ์ „๋…„๋Œ€๋น„ / ' + + '์ „๋…„๋™๊ธฐ) comparison, grounded in the financial figures the tools returned. Score 1.0 for dated, ' + + 'numeric YoY figures; ~0.5 if numbers but no YoY (or vice-versa); 0 if vague or missing.', + cross_signal: + 'DIMENSION = ๊ต์ฐจ์‹ ํ˜ธ ์ข…ํ•ฉ (cross-signal synthesis). Reward the answer ONLY if it weaves ์ˆ˜๊ธ‰' + + '(์™ธ๊ตญ์ธยท๊ณต๋งค๋„ยท๊ธฐ๊ด€ ๋ฐฉํ–ฅ) ยท ์‹ค์ (๋งค์ถœ/์ด์ต/๋งˆ์ง„/ROE) ยท ์ง€๋ฐฐ๊ตฌ์กฐ(๋Œ€๋Ÿ‰๋ณด์œ ยท๊ณ„์—ด ์ง€๋ถ„) into ONE integrated ' + + 'investment thesis where the signals are explicitly cross-referenced (do they agree or conflict?) ' + + 'and it ends with an evidence-anchored verdict and specific triggers. Score low for three ' + + 'disconnected bullet lists with no synthesis, even if each bullet is individually correct.', + governance: + 'DIMENSION = ์ง€๋ฐฐ๊ตฌ์กฐ (governance) as a valuation factor. Reward the answer if it treats ๋Œ€๋Ÿ‰๋ณด์œ /' + + '๊ณ„์—ด ์ง€๋ถ„/๋ฌผ์ ๋ถ„ํ• ยท์ธ์ ๋ถ„ํ•  as valuation-relevant (์ˆœํ™˜์ถœ์ž, ์Šน๊ณ„, ์ง€์ฃผ์‚ฌ ํ• ์ธ, ์†Œ์•ก์ฃผ์ฃผ ์˜ํ–ฅ, ๋”๋ธ”์นด์šดํŒ…) ' + + 'rather than a throwaway footnote. Score 0 if governance is ignored or merely name-dropped.', + grounding: + 'DIMENSION = grounding / no-hallucination. Reward the answer if its specific figures, dates, and ' + + 'claims are supported by (or directly derivable from) the TOOL RESULTS below. Penalize any number, ' + + 'name, or fact that does not appear in the tool data. NOTE: YoY %s computed from two reported ' + + 'figures are legitimately derived and acceptable. Score 1.0 if fully grounded; drop sharply for ' + + 'each unsupported quantitative claim.', +}; + +const preview = (s: string, n = 1500): string => + s.length > n ? s.slice(0, n).replace(/\s+/g, ' ') + ` โ€ฆ[${s.length} chars]` : s.replace(/\s+/g, ' '); + +/** + * Compact, token-bounded digest of the tool results for the judge to ground against. + * The budget is split EVENLY across calls (not greedy first-fit) so a later tool's + * data is never dropped wholesale โ€” every tool the agent used is represented, which + * matters for grounding/cross_signal judging of multi-tool answers. + */ +export function toolDigest( + toolCalls: ReadonlyArray<{ tool: string; args: Record; result: string }>, + maxTotal = 12000, +): string { + if (toolCalls.length === 0) return '(no tools were called)'; + const perCall = Math.max(400, Math.floor(maxTotal / toolCalls.length)); + return toolCalls + .map((c) => `### ${c.tool}(${preview(JSON.stringify(c.args), 200)})\n${preview(c.result, perCall)}`) + .join('\n\n'); +} + +export async function judgeDimension( + dimension: DimensionId, + args: { question: string; answer: string; digest: string; model: string }, +): Promise { + const prompt = `${DIMENSION_RUBRICS[dimension]} + +QUESTION (Korean): +${args.question} + +ANSWER UNDER TEST: +${args.answer} + +TOOL RESULTS (what the agent saw): +${args.digest} + +Score this single dimension now.`; + + try { + const res = await callLlm(prompt, { + model: args.model, + systemPrompt: JUDGE_SYSTEM_PROMPT, + outputSchema: DimensionScoreSchema, + }); + // With outputSchema, callLlm returns the parsed structured object as `response` + // (typed loosely as AIMessage|string upstream). Re-parse to validate at runtime โ€” + // same pattern as src/tools/finance/screen-stocks.ts; a bad shape lands in catch. + const parsed = DimensionScoreSchema.parse(res.response); + return { id: dimension, score: parsed.score, comment: parsed.comment }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { id: dimension, score: 0, comment: `judge error: ${message}` }; + } +} + +/** Score every dimension applicable to a question (concurrently). */ +export async function judgeAll(args: { + dimensions: DimensionId[]; + question: string; + answer: string; + toolCalls: ReadonlyArray<{ tool: string; args: Record; result: string }>; + model: string; +}): Promise { + const digest = toolDigest(args.toolCalls); + return Promise.all( + args.dimensions.map((d) => + judgeDimension(d, { question: args.question, answer: args.answer, digest, model: args.model }), + ), + ); +} diff --git a/scripts/kr-eval/questions.ts b/scripts/kr-eval/questions.ts new file mode 100644 index 000000000..6297b28f0 --- /dev/null +++ b/scripts/kr-eval/questions.ts @@ -0,0 +1,147 @@ +// The KR research question bank + rubric metadata. Each entry pins which KR +// tools the agent should reach for (deterministic gate) and which answer-quality +// dimensions an LLM judge should score (see judge.ts). Adding a question here is +// all that's needed to extend the harness. + +/** Answer-quality dimensions scored by the LLM judge (judge.ts). */ +export type DimensionId = 'earnings_yoy' | 'cross_signal' | 'governance' | 'grounding'; + +export interface KrEvalQuestion { + /** Stable id โ€” also the fixture filename (scripts/kr-eval/fixtures//.json). */ + id: string; + /** The user query fired at the agent. */ + query: string; + /** Primary ticker (for fixture/debug context; not used for scoring). */ + ticker?: string; + /** Tools that SHOULD fire โ€” scored as coverage (soft). */ + expectedTools: string[]; + /** Subset of expectedTools that MUST fire โ€” a hard gate (defaults to expectedTools if omitted). */ + requiredTools?: string[]; + /** Which judge dimensions apply to this question. */ + dimensions: DimensionId[]; + /** Per-dimension pass threshold override (default DEFAULT_THRESHOLD in scorer.ts). */ + thresholds?: Partial>; + notes?: string; +} + +export const KR_EVAL_QUESTIONS: KrEvalQuestion[] = [ + { + id: 'samsung-fundamentals', + query: '์‚ผ์„ฑ์ „์ž(005930) ์ตœ๊ทผ ์‹ค์  ๋งค์ถœ์ด๋ž‘ ์˜์—…์ด์ต YoY๋กœ ์•Œ๋ ค์ค˜.', + ticker: '005930', + expectedTools: ['get_financials_kr'], + requiredTools: ['get_financials_kr'], + dimensions: ['earnings_yoy', 'grounding'], + notes: '๋‹จ์ผ ์ข…๋ชฉ ํŽ€๋”๋ฉ˜ํ„ธ โ€” ์†์ต + ์ „๋…„๋Œ€๋น„ ์ˆซ์ž๊ฐ€ ํ•ต์‹ฌ. grounding์œผ๋กœ YoY๊ฐ€ ํˆด ๋ฐ์ดํ„ฐ์— ์‹ค์ œ ๊ทผ๊ฑฐํ•˜๋Š”์ง€ ๊ต์ฐจ๊ฒ€์ฆ.', + }, + { + id: 'samsung-synthesis', + query: '์‚ผ์„ฑ์ „์ž ์ง€๊ธˆ ํˆฌ์ž ๊ด€์ ์—์„œ ์–ด๋•Œ?', + ticker: '005930', + expectedTools: [ + 'get_financials_kr', + 'get_foreign_ownership_kr', + 'get_large_holders_kr', + 'get_filings_kr', + 'get_short_balance_kr', + ], + requiredTools: ['get_financials_kr', 'get_foreign_ownership_kr'], + dimensions: ['earnings_yoy', 'cross_signal', 'governance', 'grounding'], + // governance is one STRAND of a synthesis answer (not the focus), so the judge + // consistently lands it ~0.70โ€“0.78 โ€” relax the bar vs. dedicated governance + // questions (large-holders/spinoff score 0.92+ at the default 0.70). + thresholds: { governance: 0.65 }, + notes: '๊ต์ฐจ์‹ ํ˜ธ ์ข…ํ•ฉ โ€” ์ˆ˜๊ธ‰ยท์‹ค์ ยท์ง€๋ฐฐ๊ตฌ์กฐ๋ฅผ ํ•˜๋‚˜์˜ thesis๋กœ.', + }, + { + id: 'foreign-flow-hynix', + query: 'SKํ•˜์ด๋‹‰์Šค(000660) ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจํ•˜๊ณ  ์ตœ๊ทผ ์ˆœ๋งค์ˆ˜ ํ๋ฆ„ ์–ด๋•Œ?', + ticker: '000660', + expectedTools: ['get_foreign_ownership_kr'], + requiredTools: ['get_foreign_ownership_kr'], + dimensions: ['grounding'], + notes: '๋‹จ์ผ ์‹ ํ˜ธ(์™ธ๊ตญ์ธ ์ˆ˜๊ธ‰) ์งˆ๋ฌธ โ€” ์ง€๋ถ„์œจยท์ˆœ๋งค์ˆ˜ ์ˆ˜์น˜๊ฐ€ ํˆด ๋ฐ์ดํ„ฐ์— ๊ทผ๊ฑฐํ•˜๋Š”์ง€(grounding)๋กœ ์ฑ„์ . ๊ต์ฐจ์‹ ํ˜ธ thesis๋Š” synthesis ์งˆ๋ฌธ์—์„œ๋งŒ ์š”๊ตฌ.', + }, + { + id: 'short-balance-ecopro', + query: '์—์ฝ”ํ”„๋กœ๋น„์— (247540) ๊ณต๋งค๋„ ์ž”๊ณ ๋น„์ค‘ ์ง€๊ธˆ ์–ด๋А ์ˆ˜์ค€์ด์•ผ?', + ticker: '247540', + expectedTools: ['get_short_balance_kr'], + requiredTools: ['get_short_balance_kr'], + dimensions: ['grounding'], + notes: 'KRX ๊ณต๋งค๋„ โ€” KRX ์ž๊ฒฉ์ฆ๋ช… ์—†์œผ๋ฉด skip.', + }, + { + id: 'institutional-vs-foreign', + query: '์˜ค๋Š˜ ์‚ผ์„ฑ์ „์ž ๊ธฐ์ค€์œผ๋กœ ๊ธฐ๊ด€ยท์™ธ๊ตญ์ธยท๊ฐœ์ธ ๋ˆ„๊ฐ€ ์‚ฌ๊ณ  ํŒ”์•˜์–ด?', + ticker: '005930', + expectedTools: ['get_foreign_ownership_kr'], + requiredTools: ['get_foreign_ownership_kr'], + dimensions: ['grounding'], + notes: '๋‹จ์ผ ์‹ ํ˜ธ(๋‹น์ผ ๋งค๋งค๋™ํ–ฅ) ์งˆ๋ฌธ โ€” Naver trend์˜ ๊ธฐ๊ด€/์™ธ๊ตญ์ธ/๊ฐœ์ธ ์ˆœ๋งค์ˆ˜ ์ˆ˜์น˜ ๊ทผ๊ฑฐ(grounding)๋กœ ์ฑ„์ .', + }, + { + id: 'large-holders-samsung', + query: '์‚ผ์„ฑ์ „์ž ๋Œ€๋Ÿ‰๋ณด์œ (5%๋ฃฐ) ํ˜„ํ™ฉํ•˜๊ณ  ์ง€๋ฐฐ๊ตฌ์กฐ ํ•จ์˜ ์ •๋ฆฌํ•ด์ค˜.', + ticker: '005930', + expectedTools: ['get_large_holders_kr'], + requiredTools: ['get_large_holders_kr'], + dimensions: ['governance'], + notes: '๋Œ€๋Ÿ‰๋ณด์œ  โ†’ ์ˆœํ™˜์ถœ์ž/๊ณ„์—ด ์ง€๋ถ„ ํ•จ์˜.', + }, + { + id: 'insider-trades-celltrion', + query: '์ตœ๊ทผ ์…€ํŠธ๋ฆฌ์˜จ(068270) ์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ ์†Œ์œ  ๋ณ€๋™ ์žˆ์—ˆ์–ด?', + ticker: '068270', + expectedTools: ['get_insider_trades_kr'], + requiredTools: ['get_insider_trades_kr'], + dimensions: ['grounding'], + notes: '์‚ฌ์‹ค ์กฐํšŒ("๋ณ€๋™ ์žˆ์—ˆ์–ด?") โ€” ์†Œ์œ  ๋ณ€๋™ ์ˆ˜์น˜๋ฅผ ์ •ํ™•ํžˆ ๊ทผ๊ฑฐ ์žˆ๊ฒŒ ๋ณด๊ณ ํ•˜๋Š”์ง€(grounding)๋กœ ์ฑ„์ . ๊นŠ์€ ์ง€๋ฐฐ๊ตฌ์กฐ valuation ๋ถ„์„์€ large-holders/spinoff ์งˆ๋ฌธ์ด ๋‹ด๋‹น.', + }, + { + id: 'spinoff-lgchem', + query: 'LGํ™”ํ•™(051910) ๋ฌผ์ ๋ถ„ํ• ์ด ์†Œ์•ก์ฃผ์ฃผํ•œํ…Œ ์–ด๋–ค ์˜ํ–ฅ์ด์—ˆ์–ด?', + ticker: '051910', + expectedTools: ['get_filings_kr'], + requiredTools: ['get_filings_kr'], + dimensions: ['governance'], + notes: '๋ฌผ์ ๋ถ„ํ•  ์ด๋ฒคํŠธ โ€” ์†Œ์•ก์ฃผ์ฃผ/์ง€์ฃผ์‚ฌ ํ• ์ธ ๊ด€์ . skill(kr-spinoff)๋„ ๊ฐ€๋Šฅ.', + }, + { + id: 'nps-samsung', + query: '๊ตญ๋ฏผ์—ฐ๊ธˆ์ด ์‚ผ์„ฑ์ „์ž ์–ผ๋งˆ๋‚˜ ๋“ค๊ณ  ์žˆ์–ด?', + ticker: '005930', + expectedTools: ['get_nps_holdings'], + requiredTools: ['get_nps_holdings'], + dimensions: ['grounding'], + notes: 'data.go.kr ์„œ๋น„์Šคํ‚ค ์—†์œผ๋ฉด skip. ์—ฐ๋ง ์Šค๋ƒ…์ƒท.', + }, + { + id: 'nps-top', + query: '๊ตญ๋ฏผ์—ฐ๊ธˆ ๋ณด์œ  ๋น„์ค‘ ์ƒ์œ„ ์ข…๋ชฉ ์•Œ๋ ค์ค˜.', + expectedTools: ['get_nps_holdings'], + requiredTools: ['get_nps_holdings'], + dimensions: ['grounding'], + notes: 'NPS ์ƒ์œ„ ๋ณด์œ  โ€” ์ข…๋ชฉ๋ช… ๋งค์นญ.', + }, + { + id: 'filings-hyundai', + query: 'ํ˜„๋Œ€์ฐจ(005380) ์ตœ๊ทผ ์ฃผ์š” ๊ณต์‹œ ๋ญ ์žˆ์—ˆ์–ด?', + ticker: '005380', + expectedTools: ['get_filings_kr'], + requiredTools: ['get_filings_kr'], + dimensions: ['grounding'], + notes: '๊ณต์‹œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๊ฒ€์ƒ‰.', + }, + { + id: 'cross-signal-alteogen', + query: '์•Œํ…Œ์˜ค์  (196170) ํˆฌ์ž ๋งค๋ ฅ ์ข…ํ•ฉ ํ‰๊ฐ€ํ•ด์ค˜ (์ˆ˜๊ธ‰ยท์‹ค์ ยท์ง€๋ฐฐ๊ตฌ์กฐ).', + ticker: '196170', + expectedTools: ['get_financials_kr', 'get_foreign_ownership_kr', 'get_large_holders_kr'], + requiredTools: ['get_financials_kr'], + dimensions: ['earnings_yoy', 'cross_signal', 'governance', 'grounding'], + // See samsung-synthesis: governance is a strand here, not the focus. + thresholds: { governance: 0.65 }, + notes: 'KOSDAQ ๊ต์ฐจ์‹ ํ˜ธ ์ข…ํ•ฉ.', + }, +]; diff --git a/scripts/kr-eval/replay.test.ts b/scripts/kr-eval/replay.test.ts new file mode 100644 index 000000000..ed4e94552 --- /dev/null +++ b/scripts/kr-eval/replay.test.ts @@ -0,0 +1,71 @@ +import { describe, it, expect } from 'bun:test'; +import { DynamicStructuredTool, type StructuredToolInterface } from '@langchain/core/tools'; +import { z } from 'zod'; +import { canonicalize, createReplayer, type QuestionFixture } from './replay.js'; + +describe('canonicalize', () => { + it('is stable under key reordering', () => { + expect(canonicalize({ a: 1, b: 2 })).toBe(canonicalize({ b: 2, a: 1 })); + }); + + it('sorts keys recursively', () => { + expect(canonicalize({ x: { c: 1, a: 2 } })).toBe('{"x":{"a":2,"c":1}}'); + }); + + it('treats undefined/empty args as {}', () => { + const noArgs: unknown = undefined; + expect(canonicalize(noArgs ?? {})).toBe('{}'); + }); +}); + +// Two real-looking tools: one recorded (get_filings_kr), one not (get_short_balance_kr). +function realTools(): StructuredToolInterface[] { + const schema = z.object({ ticker: z.string() }); + return [ + new DynamicStructuredTool({ + name: 'get_filings_kr', + description: 'filings', + schema, + func: async () => 'LIVE_SHOULD_NOT_RUN', + }), + new DynamicStructuredTool({ + name: 'get_short_balance_kr', + description: 'short', + schema, + func: async () => 'LIVE_SHOULD_NOT_RUN', + }), + ]; +} + +const fixture: QuestionFixture = { + id: 't', + query: 'q', + toolCalls: [ + { tool: 'get_filings_kr', args: { ticker: '005930' }, result: 'RESULT_SAMSUNG' }, + { tool: 'get_filings_kr', args: { ticker: '000660' }, result: 'RESULT_HYNIX' }, + ], +}; + +describe('createReplayer', () => { + it('returns the exact recorded result for matching args', async () => { + const { transformTools } = createReplayer(fixture); + const stubs = transformTools(realTools()); + const filings = stubs.find((t) => t.name === 'get_filings_kr')!; + expect(await filings.invoke({ ticker: '000660' })).toBe('RESULT_HYNIX'); + expect(await filings.invoke({ ticker: '005930' })).toBe('RESULT_SAMSUNG'); + }); + + it('falls back to the first recorded call when args do not match', async () => { + const { transformTools } = createReplayer(fixture); + const filings = transformTools(realTools()).find((t) => t.name === 'get_filings_kr')!; + expect(await filings.invoke({ ticker: '999999' })).toBe('RESULT_SAMSUNG'); + }); + + it('returns a _replay_miss sentinel and records the miss for unrecorded tools', async () => { + const replayer = createReplayer(fixture); + const short = replayer.transformTools(realTools()).find((t) => t.name === 'get_short_balance_kr')!; + const out = await short.invoke({ ticker: '005930' }); + expect(JSON.parse(out as string)._replay_miss).toBe(true); + expect(replayer.misses).toContain('get_short_balance_kr'); + }); +}); diff --git a/scripts/kr-eval/replay.ts b/scripts/kr-eval/replay.ts new file mode 100644 index 000000000..cd72da006 --- /dev/null +++ b/scripts/kr-eval/replay.ts @@ -0,0 +1,137 @@ +// Record/replay at the tool-output layer. +// +// A live run's `done.toolCalls` already carries `{tool,args,result}` for every +// call, so recording is just dumping that array to a per-question fixture file. +// Replay swaps each real tool for a fixture-backed stub (via the Agent's +// `transformTools` hook) so the agent's LLM still chooses tools freely but gets +// byte-identical recorded outputs โ€” pinning market data without touching fetch, +// KRX login, or cookies. The agent LLM stays live; only the data is pinned. + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { DynamicStructuredTool, type StructuredToolInterface } from '@langchain/core/tools'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const FIXTURES_ROOT = path.join(__dirname, 'fixtures'); + +/** One recorded tool call โ€” mirrors the shape of `done.toolCalls` entries. */ +export interface RecordedToolCall { + tool: string; + args: Record; + result: string; +} + +export interface QuestionFixture { + id: string; + query: string; + meta?: { model?: string }; + toolCalls: RecordedToolCall[]; +} + +/** Stable JSON: sort object keys recursively so arg ordering can't change the key. */ +export function canonicalize(value: unknown): string { + return JSON.stringify(sortKeys(value)); +} + +function sortKeys(value: unknown): unknown { + if (Array.isArray(value)) return value.map(sortKeys); + if (value && typeof value === 'object') { + const out: Record = {}; + for (const key of Object.keys(value as Record).sort()) { + out[key] = sortKeys((value as Record)[key]); + } + return out; + } + return value; +} + +export function scenarioDir(scenario: string): string { + return path.join(FIXTURES_ROOT, scenario); +} + +export function fixturePath(scenario: string, id: string): string { + return path.join(scenarioDir(scenario), `${id}.json`); +} + +export function saveFixture(scenario: string, fixture: QuestionFixture): string { + const dir = scenarioDir(scenario); + fs.mkdirSync(dir, { recursive: true }); + const file = fixturePath(scenario, fixture.id); + fs.writeFileSync(file, JSON.stringify(fixture, null, 2) + '\n', 'utf-8'); + return file; +} + +export function loadFixture(scenario: string, id: string): QuestionFixture { + const file = fixturePath(scenario, id); + if (!fs.existsSync(file)) { + throw new Error( + `No fixture for "${id}" at ${file}. Record it first: KR_EVAL_MODE=record bun run scripts/kr-eval/runner.ts --question ${id}`, + ); + } + return JSON.parse(fs.readFileSync(file, 'utf-8')) as QuestionFixture; +} + +/** Sentinel result returned when the agent calls a tool with no recorded output. */ +export function replayMissResult(toolName: string): string { + return JSON.stringify({ data: {}, _replay_miss: true, tool: toolName }); +} + +export interface Replayer { + /** Pass as AgentConfig.transformTools โ€” replaces every real tool with a stub. */ + transformTools: (tools: StructuredToolInterface[]) => StructuredToolInterface[]; + /** Tool names the agent called in replay that had no recorded output (populated during the run). */ + misses: string[]; +} + +/** + * Build a replayer for one question's fixture. Every tool is stubbed so no live + * network can happen in replay. Match policy per call: + * 1. exact canonical-args match โ†’ that recorded result + * 2. else first recorded call of the same tool (KR questions are single-ticker) + * 3. else a `_replay_miss` sentinel, and the tool name is pushed to `misses` + */ +export function createReplayer(fixture: QuestionFixture): Replayer { + const misses: string[] = []; + + // Index recorded calls by tool name, preserving order. + const byTool = new Map(); + for (const call of fixture.toolCalls) { + const list = byTool.get(call.tool) ?? []; + list.push(call); + byTool.set(call.tool, list); + } + + const resolve = (toolName: string, input: unknown): string => { + const calls = byTool.get(toolName); + if (!calls || calls.length === 0) { + misses.push(toolName); + return replayMissResult(toolName); + } + const args = (input ?? {}) as Record; + // 1. exact args match. + const exact = calls.find((c) => canonicalize(c.args) === canonicalize(args)); + if (exact) return exact.result; + // 2. match on the entity key (ticker) so a multi-ticker tool doesn't return + // another ticker's data when only secondary args (limit, dates) differ. + if (args.ticker !== undefined) { + const byTicker = calls.find((c) => c.args?.ticker === args.ticker); + if (byTicker) return byTicker.result; + } + // 3. fall back to the first recorded call (KR questions are single-entity). + return calls[0].result; + }; + + const transformTools = (tools: StructuredToolInterface[]): StructuredToolInterface[] => + tools.map( + (real) => + new DynamicStructuredTool({ + name: real.name, + description: real.description, + schema: real.schema, + func: async (input: Record): Promise => resolve(real.name, input), + }), + ); + + return { transformTools, misses }; +} diff --git a/scripts/kr-eval/report.ts b/scripts/kr-eval/report.ts new file mode 100644 index 000000000..950d8c93d --- /dev/null +++ b/scripts/kr-eval/report.ts @@ -0,0 +1,56 @@ +// Console formatting for the eval report. Mirrors the separator style of the +// single-query seed (scripts/kr-eval.ts) โ€” plain console output, not app logging. + +import type { AggregateReport, QuestionResult } from './scorer.js'; + +const BAR = '='.repeat(80); + +function fmtPct(n: number): string { + return `${(n * 100).toFixed(0)}%`; +} + +function questionLine(r: QuestionResult): string { + if (r.skipped) return ` SKIP ${r.id.padEnd(26)} โ€” ${r.skipped}`; + const dims = r.dimensions.map((d) => `${d.id}=${d.score.toFixed(2)}${d.pass ? '' : 'โœ—'}`).join(' '); + const missing = r.missingRequired.length ? ` missingReq=[${r.missingRequired.join(',')}]` : ''; + const misses = r.replayMisses.length ? ` replayMiss=[${[...new Set(r.replayMisses)].join(',')}]` : ''; + if (r.inconclusive) { + return ` INCONC ${r.id.padEnd(26)} tools=${fmtPct(r.toolsScore)} ${dims}${missing}${misses}`; + } + const tag = r.pass ? 'PASS ' : 'FAIL '; + return ` ${tag} ${r.id.padEnd(26)} tools=${fmtPct(r.toolsScore)} ${dims}${missing}${misses}`; +} + +export function printReport(report: AggregateReport): void { + console.log('\n' + BAR); + console.log( + `KR EVAL โ€” mode=${report.meta.mode} agent=${report.meta.agentModel} judge=${report.meta.judgeModel}`, + ); + console.log(BAR); + + for (const r of report.results) { + console.log(questionLine(r)); + if (r.inconclusive) { + console.log(` โ†ณ ${r.inconclusive} (re-record this question's fixture)`); + } else if (!r.skipped) { + for (const d of r.dimensions) { + if (!d.pass) console.log(` โ†ณ ${d.id}: ${d.comment}`); + } + } + } + + console.log('\n' + '-'.repeat(80)); + console.log( + `Passed ${report.passed}/${report.ran} (skipped ${report.skipped}, inconclusive ${report.inconclusive})`, + ); + console.log(`Tool-fire rate: ${fmtPct(report.toolFireRate)}`); + const dimKeys = Object.keys(report.byDimension); + if (dimKeys.length > 0) { + console.log('By dimension (mean / pass-rate):'); + for (const key of dimKeys) { + const d = report.byDimension[key as keyof typeof report.byDimension]; + if (d) console.log(` ${key.padEnd(16)} mean=${d.mean.toFixed(2)} pass=${fmtPct(d.passRate)}`); + } + } + console.log(BAR); +} diff --git a/scripts/kr-eval/runner.ts b/scripts/kr-eval/runner.ts new file mode 100644 index 000000000..7f668fc36 --- /dev/null +++ b/scripts/kr-eval/runner.ts @@ -0,0 +1,194 @@ +// KR eval harness entrypoint โ€” runs the question bank through the REAL agent and +// scores each `done` event (tool-firing gate + LLM-judge dimensions). +// +// bun run scripts/kr-eval/runner.ts # live (hits DART/KRX/Naver/...) +// KR_EVAL_MODE=record bun run scripts/kr-eval/runner.ts # live + write fixtures +// KR_EVAL_MODE=replay bun run scripts/kr-eval/runner.ts # replay recorded tool outputs +// +// Flags: --question --scenario --model --judge-model --repeat N +// Agent model defaults to KR_RUNNER_MODEL (like the seed); judge to KR_EVAL_JUDGE_MODEL. +import { config } from 'dotenv'; +config({ quiet: true }); + +import type { StructuredToolInterface } from '@langchain/core/tools'; +import { Agent } from '../../src/agent/agent.js'; +import { getToolRegistry } from '../../src/tools/registry.js'; +import { KR_EVAL_QUESTIONS, type KrEvalQuestion } from './questions.js'; +import { createReplayer, loadFixture, saveFixture } from './replay.js'; +import { judgeAll } from './judge.js'; +import { DEFAULT_JUDGE_MODEL } from './judge.js'; +import { + aggregate, + buildQuestionResult, + firedToolNames, + requiredTools, + skippedResult, + type QuestionResult, + type RawDimension, +} from './scorer.js'; +import { printReport } from './report.js'; + +type Mode = 'live' | 'record' | 'replay'; +type ToolCall = { tool: string; args: Record; result: string }; + +interface RunCtx { + mode: Mode; + scenario: string; + agentModel: string; + judgeModel: string; + repeat: number; +} + +function getFlag(name: string): string | undefined { + const i = process.argv.indexOf(`--${name}`); + return i !== -1 ? process.argv[i + 1] : undefined; +} + +/** In replay, set dummy KR keys (if absent) so env-gated tools still register. */ +function ensureReplayEnv(): void { + for (const key of ['DART_API_KEY', 'KRX_ID', 'KRX_PW', 'DATA_GO_KR_SERVICE_KEY']) { + const v = process.env[key]; + if (!v || v.startsWith('your-')) process.env[key] = 'replay'; + } +} + +async function runOnce( + q: KrEvalQuestion, + ctx: RunCtx, +): Promise<{ answer: string; toolCalls: ToolCall[]; replayMisses: string[] }> { + let transformTools: ((tools: StructuredToolInterface[]) => StructuredToolInterface[]) | undefined; + let misses: string[] = []; + + if (ctx.mode === 'replay') { + const replayer = createReplayer(loadFixture(ctx.scenario, q.id)); + transformTools = replayer.transformTools; + misses = replayer.misses; + } + + const agent = await Agent.create({ + model: ctx.agentModel, + channel: 'cli', + memoryEnabled: false, + maxIterations: 10, + transformTools, + }); + + let answer = ''; + let toolCalls: ToolCall[] = []; + for await (const ev of agent.run(q.query)) { + if (ev.type === 'done') { + answer = ev.answer; + toolCalls = ev.toolCalls as ToolCall[]; + } + } + + if (ctx.mode === 'record') { + const file = saveFixture(ctx.scenario, { + id: q.id, + query: q.query, + meta: { model: ctx.agentModel }, + toolCalls, + }); + console.log(` โ†ณ recorded ${toolCalls.length} tool calls โ†’ ${file}`); + } + + return { answer, toolCalls, replayMisses: misses }; +} + +async function evaluateQuestion(q: KrEvalQuestion, ctx: RunCtx): Promise { + const runs: { firedTools: string[]; rawDims: RawDimension[]; replayMisses: string[] }[] = []; + + for (let i = 0; i < ctx.repeat; i++) { + const { answer, toolCalls, replayMisses } = await runOnce(q, ctx); + const rawDims = await judgeAll({ + dimensions: q.dimensions, + question: q.query, + answer, + toolCalls, + model: ctx.judgeModel, + }); + runs.push({ firedTools: firedToolNames(toolCalls), rawDims, replayMisses }); + } + + // Average judge scores across repeats. Tool-firing / replayMisses are taken from + // the first run only; with repeat>1 they under-represent if runs diverge (N=1 default). + const avgDims: RawDimension[] = q.dimensions.map((id) => { + const scores = runs.map((r) => r.rawDims.find((d) => d.id === id)?.score ?? 0); + const avg = scores.reduce((a, b) => a + b, 0) / scores.length; + const comment = runs[0].rawDims.find((d) => d.id === id)?.comment ?? ''; + return { id, score: avg, comment: ctx.repeat > 1 ? `[avgร—${ctx.repeat}] ${comment}` : comment }; + }); + + return buildQuestionResult({ + question: q, + firedTools: runs[0].firedTools, + rawDimensions: avgDims, + replayMisses: runs[0].replayMisses, + }); +} + +async function main(): Promise { + const mode = (process.env.KR_EVAL_MODE as Mode) || 'live'; + const ctx: RunCtx = { + mode, + scenario: getFlag('scenario') ?? 'default', + agentModel: getFlag('model') ?? process.env.KR_RUNNER_MODEL ?? 'gpt-5.5', + judgeModel: getFlag('judge-model') ?? process.env.KR_EVAL_JUDGE_MODEL ?? DEFAULT_JUDGE_MODEL, + repeat: Math.max(1, parseInt(getFlag('repeat') ?? '1', 10) || 1), + }; + + if (mode === 'replay') ensureReplayEnv(); + + const only = getFlag('question'); + const questions = only ? KR_EVAL_QUESTIONS.filter((q) => q.id === only) : KR_EVAL_QUESTIONS; + if (questions.length === 0) { + console.error(only ? `No question with id "${only}".` : 'No questions defined.'); + process.exit(1); + } + + const registered = new Set(getToolRegistry(ctx.agentModel).map((t) => t.name)); + + console.log( + `KR EVAL starting โ€” mode=${ctx.mode} agent=${ctx.agentModel} judge=${ctx.judgeModel} ` + + `questions=${questions.length} repeat=${ctx.repeat}`, + ); + + const results: QuestionResult[] = []; + for (let i = 0; i < questions.length; i++) { + const q = questions[i]; + const missing = requiredTools(q).filter((t) => !registered.has(t)); + + // Live/record can't run a question whose required tools aren't registered. + if (mode !== 'replay' && missing.length > 0) { + console.log(`\nโ–ถ [${i + 1}/${questions.length}] ${q.id} โ€” SKIP (needs ${missing.join(', ')})`); + results.push(skippedResult(q, `n/a โ€” requires ${missing.join(', ')} (not registered)`)); + continue; + } + + console.log(`\nโ–ถ [${i + 1}/${questions.length}] ${q.id}: ${q.query}`); + try { + results.push(await evaluateQuestion(q, ctx)); + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + console.log(` โ†ณ error: ${msg}`); + results.push(skippedResult(q, `error: ${msg}`)); + } + } + + const report = aggregate(results, { + mode: ctx.mode, + agentModel: ctx.agentModel, + judgeModel: ctx.judgeModel, + }); + printReport(report); + + // Non-zero exit when an active question failed โ€” a CI/regression signal. record + // mode is a capture action, so it always exits 0 regardless of rubric scores. + const failed = report.ran > 0 && report.passed < report.ran; + process.exit(ctx.mode !== 'record' && failed ? 1 : 0); +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/scripts/kr-eval/scorer.test.ts b/scripts/kr-eval/scorer.test.ts new file mode 100644 index 000000000..74351a930 --- /dev/null +++ b/scripts/kr-eval/scorer.test.ts @@ -0,0 +1,154 @@ +import { describe, it, expect } from 'bun:test'; +import type { KrEvalQuestion } from './questions.js'; +import { + aggregate, + buildQuestionResult, + DEFAULT_THRESHOLD, + firedToolNames, + scoreTools, + skippedResult, + type RawDimension, +} from './scorer.js'; + +const baseQ: KrEvalQuestion = { + id: 'q1', + query: '์‚ผ์„ฑ์ „์ž ์–ด๋•Œ?', + expectedTools: ['get_financials_kr', 'get_foreign_ownership_kr'], + requiredTools: ['get_financials_kr'], + dimensions: ['earnings_yoy'], +}; + +describe('firedToolNames', () => { + it('dedupes while preserving first-seen order', () => { + const fired = firedToolNames([ + { tool: 'a' }, + { tool: 'b' }, + { tool: 'a' }, + ]); + expect(fired).toEqual(['a', 'b']); + }); +}); + +describe('scoreTools', () => { + it('full coverage โ†’ toolsScore 1, nothing missing', () => { + const s = scoreTools(baseQ, ['get_financials_kr', 'get_foreign_ownership_kr']); + expect(s.toolsScore).toBe(1); + expect(s.missingExpected).toEqual([]); + expect(s.missingRequired).toEqual([]); + }); + + it('partial coverage โ†’ fractional score; required still met', () => { + const s = scoreTools(baseQ, ['get_financials_kr']); + expect(s.toolsScore).toBe(0.5); + expect(s.missingExpected).toEqual(['get_foreign_ownership_kr']); + expect(s.missingRequired).toEqual([]); + }); + + it('required tool absent โ†’ reported as missingRequired', () => { + const s = scoreTools(baseQ, ['get_foreign_ownership_kr']); + expect(s.missingRequired).toEqual(['get_financials_kr']); + }); + + it('requiredTools defaults to expectedTools when omitted', () => { + const q: KrEvalQuestion = { ...baseQ, requiredTools: undefined }; + const s = scoreTools(q, ['get_financials_kr']); + expect(s.missingRequired).toEqual(['get_foreign_ownership_kr']); + }); +}); + +describe('buildQuestionResult', () => { + const dims = (score: number): RawDimension[] => [ + { id: 'earnings_yoy', score, comment: 'c' }, + ]; + + it('passes when required tools fired and every dimension clears threshold', () => { + const r = buildQuestionResult({ + question: baseQ, + firedTools: ['get_financials_kr', 'get_foreign_ownership_kr'], + rawDimensions: dims(0.9), + }); + expect(r.pass).toBe(true); + expect(r.dimensions[0].pass).toBe(true); + }); + + it('hard-fails when a required tool is missing, regardless of judge score', () => { + const r = buildQuestionResult({ + question: baseQ, + firedTools: ['get_foreign_ownership_kr'], + rawDimensions: dims(1), + }); + expect(r.pass).toBe(false); + expect(r.missingRequired).toEqual(['get_financials_kr']); + }); + + it('fails when a dimension is below threshold', () => { + const r = buildQuestionResult({ + question: baseQ, + firedTools: ['get_financials_kr', 'get_foreign_ownership_kr'], + rawDimensions: dims(DEFAULT_THRESHOLD - 0.01), + }); + expect(r.pass).toBe(false); + expect(r.dimensions[0].pass).toBe(false); + }); + + it('honors a per-question threshold override', () => { + const q: KrEvalQuestion = { ...baseQ, thresholds: { earnings_yoy: 0.5 } }; + const r = buildQuestionResult({ + question: q, + firedTools: ['get_financials_kr', 'get_foreign_ownership_kr'], + rawDimensions: dims(0.6), + }); + expect(r.dimensions[0].pass).toBe(true); + expect(r.pass).toBe(true); + }); + + it('marks the result inconclusive when replay had uncovered tool calls', () => { + const r = buildQuestionResult({ + question: baseQ, + firedTools: ['get_financials_kr', 'get_foreign_ownership_kr'], + rawDimensions: dims(1), + replayMisses: ['read_file', 'read_file'], + }); + expect(r.inconclusive).toContain('read_file'); + // dedupes the uncovered tool names in the reason + expect(r.inconclusive?.match(/read_file/g)?.length).toBe(1); + }); +}); + +describe('aggregate', () => { + it('counts passes/skips and computes per-dimension stats', () => { + const passing = buildQuestionResult({ + question: baseQ, + firedTools: ['get_financials_kr', 'get_foreign_ownership_kr'], + rawDimensions: [{ id: 'earnings_yoy', score: 0.8, comment: 'c' }], + }); + const failing = buildQuestionResult({ + question: { ...baseQ, id: 'q2' }, + firedTools: ['get_financials_kr', 'get_foreign_ownership_kr'], + rawDimensions: [{ id: 'earnings_yoy', score: 0.2, comment: 'c' }], + }); + const skipped = skippedResult({ ...baseQ, id: 'q3' }, 'n/a'); + const inconclusive = buildQuestionResult({ + question: { ...baseQ, id: 'q4' }, + firedTools: ['get_financials_kr', 'get_foreign_ownership_kr'], + rawDimensions: [{ id: 'earnings_yoy', score: 0.1, comment: 'c' }], + replayMisses: ['read_file'], + }); + + const report = aggregate([passing, failing, skipped, inconclusive], { + mode: 'replay', + agentModel: 'm', + judgeModel: 'j', + }); + + // inconclusive (q4) is excluded from ran/passed and from dimension stats, + // so its low score doesn't drag the earnings_yoy mean below 0.5. + expect(report.ran).toBe(2); + expect(report.passed).toBe(1); + expect(report.skipped).toBe(1); + expect(report.inconclusive).toBe(1); + expect(report.toolFireRate).toBe(1); + expect(report.byDimension.earnings_yoy?.mean).toBeCloseTo(0.5, 5); + expect(report.byDimension.earnings_yoy?.passRate).toBe(0.5); + }); +}); diff --git a/scripts/kr-eval/scorer.ts b/scripts/kr-eval/scorer.ts new file mode 100644 index 000000000..0375c22c3 --- /dev/null +++ b/scripts/kr-eval/scorer.ts @@ -0,0 +1,179 @@ +// Scoring: a deterministic tool-firing gate (pure, computed from the agent's +// tool calls) combined with per-dimension LLM-judge scores (judge.ts). Pure and +// side-effect-free so it's unit-testable without network or an LLM. + +import type { DimensionId, KrEvalQuestion } from './questions.js'; + +export const DEFAULT_THRESHOLD = 0.7; + +/** Raw judge output before thresholding (what judge.ts returns). */ +export interface RawDimension { + id: DimensionId; + score: number; + comment: string; +} + +export interface DimensionResult extends RawDimension { + pass: boolean; +} + +export interface ToolsScore { + firedTools: string[]; + missingExpected: string[]; + missingRequired: string[]; + toolsScore: number; +} + +export interface QuestionResult { + id: string; + query: string; + /** Set when the question's required tools aren't registered in this env. */ + skipped?: string; + /** + * Set when replay wasn't faithful โ€” the live agent called tool(s) with no + * recorded output (replayMisses). The score isn't comparable, so the result is + * excluded from pass/fail rather than counted as a regression. + */ + inconclusive?: string; + firedTools: string[]; + missingExpected: string[]; + missingRequired: string[]; + toolsScore: number; + /** Tools the agent called in replay with no recorded output. */ + replayMisses: string[]; + dimensions: DimensionResult[]; + pass: boolean; +} + +/** The `requiredTools` for a question, defaulting to its full `expectedTools`. */ +export function requiredTools(q: KrEvalQuestion): string[] { + return q.requiredTools ?? q.expectedTools; +} + +/** Distinct tool names from a list of tool calls, preserving first-seen order. */ +export function firedToolNames(toolCalls: ReadonlyArray<{ tool: string }>): string[] { + const seen = new Set(); + const out: string[] = []; + for (const { tool } of toolCalls) { + if (!seen.has(tool)) { + seen.add(tool); + out.push(tool); + } + } + return out; +} + +export function scoreTools(q: KrEvalQuestion, firedTools: string[]): ToolsScore { + const fired = new Set(firedTools); + const missingExpected = q.expectedTools.filter((t) => !fired.has(t)); + const missingRequired = requiredTools(q).filter((t) => !fired.has(t)); + const toolsScore = + q.expectedTools.length === 0 + ? 1 + : (q.expectedTools.length - missingExpected.length) / q.expectedTools.length; + return { firedTools, missingExpected, missingRequired, toolsScore }; +} + +function thresholdFor(q: KrEvalQuestion, id: DimensionId): number { + return q.thresholds?.[id] ?? DEFAULT_THRESHOLD; +} + +function mean(nums: number[]): number { + return nums.length === 0 ? 0 : nums.reduce((a, b) => a + b, 0) / nums.length; +} + +export function buildQuestionResult(args: { + question: KrEvalQuestion; + firedTools: string[]; + rawDimensions: RawDimension[]; + replayMisses?: string[]; +}): QuestionResult { + const { question, firedTools, rawDimensions, replayMisses = [] } = args; + const tools = scoreTools(question, firedTools); + + const dimensions: DimensionResult[] = rawDimensions.map((d) => ({ + ...d, + pass: d.score >= thresholdFor(question, d.id), + })); + + const dimsPass = dimensions.every((d) => d.pass); + const pass = tools.missingRequired.length === 0 && dimsPass; + + // A replay with uncovered tool calls didn't fully pin the data โ†’ the score is + // not comparable. Flag inconclusive so it's excluded from pass/fail counts. + const uncovered = [...new Set(replayMisses)]; + const inconclusive = + uncovered.length > 0 ? `replay not faithful โ€” uncovered tool calls: ${uncovered.join(', ')}` : undefined; + + return { + id: question.id, + query: question.query, + inconclusive, + firedTools: tools.firedTools, + missingExpected: tools.missingExpected, + missingRequired: tools.missingRequired, + toolsScore: tools.toolsScore, + replayMisses, + dimensions, + pass, + }; +} + +export function skippedResult(question: KrEvalQuestion, reason: string): QuestionResult { + return { + id: question.id, + query: question.query, + skipped: reason, + firedTools: [], + missingExpected: [], + missingRequired: [], + toolsScore: 0, + replayMisses: [], + dimensions: [], + pass: false, + }; +} + +export interface AggregateReport { + ran: number; + passed: number; + skipped: number; + inconclusive: number; + byDimension: Partial>; + toolFireRate: number; + results: QuestionResult[]; + meta: { mode: string; agentModel: string; judgeModel: string }; +} + +export function aggregate( + results: QuestionResult[], + meta: AggregateReport['meta'], +): AggregateReport { + // "active" = scored runs: not skipped (couldn't run) and not inconclusive + // (replay wasn't faithful). Both are excluded from pass/fail and dim stats. + const active = results.filter((r) => !r.skipped && !r.inconclusive); + const skipped = results.filter((r) => r.skipped).length; + const inconclusive = results.filter((r) => !r.skipped && r.inconclusive).length; + + const byDimension: AggregateReport['byDimension'] = {}; + const dimIds = new Set(); + for (const r of active) for (const d of r.dimensions) dimIds.add(d.id); + for (const id of dimIds) { + const dims = active.flatMap((r) => r.dimensions.filter((d) => d.id === id)); + byDimension[id] = { + mean: mean(dims.map((d) => d.score)), + passRate: dims.length === 0 ? 0 : dims.filter((d) => d.pass).length / dims.length, + }; + } + + return { + ran: active.length, + passed: active.filter((r) => r.pass).length, + skipped, + inconclusive, + byDimension, + toolFireRate: mean(active.map((r) => r.toolsScore)), + results, + meta, + }; +} diff --git a/src/agent/agent.ts b/src/agent/agent.ts index 262c4db2f..9ddce7967 100644 --- a/src/agent/agent.ts +++ b/src/agent/agent.ts @@ -71,7 +71,8 @@ export class Agent { static async create(config: AgentConfig = {}): Promise { const model = config.model ?? DEFAULT_MODEL; - const tools = getTools(model); + const baseTools = getTools(model); + const tools = config.transformTools ? config.transformTools(baseTools) : baseTools; const concurrencyMap = getToolConcurrencyMap(model); const soulContent = await loadSoulDocument(); const rulesContent = await loadRulesDocument(); diff --git a/src/agent/types.ts b/src/agent/types.ts index 66a0d9cff..d58ec2383 100644 --- a/src/agent/types.ts +++ b/src/agent/types.ts @@ -1,3 +1,4 @@ +import type { StructuredToolInterface } from '@langchain/core/tools'; import type { GroupContext } from './prompts.js'; import type { MessageQueue } from '../utils/message-queue.js'; @@ -58,6 +59,12 @@ export interface AgentConfig { memoryEnabled?: boolean; /** Message queue for mid-run injection of new user messages. */ messageQueue?: MessageQueue; + /** + * Optional transform applied to the resolved tool list before binding. + * A test/eval seam โ€” e.g. the KR eval harness swaps live KR tools for + * fixture-backed stubs in replay mode. Defaults to identity. + */ + transformTools?: (tools: StructuredToolInterface[]) => StructuredToolInterface[]; } /** diff --git a/src/tools/finance-kr/normalize-financials.test.ts b/src/tools/finance-kr/normalize-financials.test.ts index a77afbbec..334e9c575 100644 --- a/src/tools/finance-kr/normalize-financials.test.ts +++ b/src/tools/finance-kr/normalize-financials.test.ts @@ -111,7 +111,25 @@ describe('summarizePeriod', () => { expect(q.ratios.roePct).toBeNull(); expect(q.ratios.operatingMarginPct).toBe(10.9); // ratios of two YTD flows stay valid }); - it('nulls YoY when the prior period is absent (quarterly frmtrm undefined)', () => { + it('reads prior-year same period from frmtrm_q_amount on quarterly reports', () => { + // Real DART shape for a 1๋ถ„๊ธฐ๋ณด๊ณ ์„œ P&L: frmtrm_amount is empty, the prior-year + // Q1 figure lives in frmtrm_q_amount. Samsung 2026 Q1 actuals. + const q1: DartRow[] = [ + { + sj_div: 'IS', + account_id: 'ifrs-full_Revenue', + account_nm: '๋งค์ถœ์•ก', + thstrm_amount: '133,873,444,000,000', + frmtrm_amount: '', + frmtrm_q_amount: '79,140,503,000,000', + }, + ]; + const s2 = summarizePeriod(q1, { bsns_year: 2026, report_type: 'quarterly_1', fs_div: 'CFS' }); + expect(s2.incomeStatement.revenue.current).toBe(133873444000000); + expect(s2.incomeStatement.revenue.prior).toBe(79140503000000); + expect(s2.ratios.revenueYoYPct).toBe(69.2); + }); + it('nulls YoY only when both frmtrm and frmtrm_q are absent', () => { const q1: DartRow[] = [ { sj_div: 'IS', account_id: 'ifrs-full_Revenue', account_nm: '๋งค์ถœ์•ก', thstrm_amount: '133,873,444,000,000' }, ]; diff --git a/src/tools/finance-kr/normalize-financials.ts b/src/tools/finance-kr/normalize-financials.ts index d61175ed1..c4472c955 100644 --- a/src/tools/finance-kr/normalize-financials.ts +++ b/src/tools/finance-kr/normalize-financials.ts @@ -26,6 +26,11 @@ export interface DartRow { sj_div?: string; thstrm_amount?: string; frmtrm_amount?: string; + /** + * ์ „๋…„ ๋™๊ธฐ ๋ˆ„์  โ€” present only on quarterly/semiannual flow statements (IS/CIS/CF). + * DART leaves frmtrm_amount empty there and puts the prior-year SAME period here. + */ + frmtrm_q_amount?: string; [key: string]: unknown; } @@ -260,9 +265,15 @@ export function findMetric(list: DartRow[], spec: AccountSpec): MetricVal { function toMetric(row: DartRow, kind: MetricKind): MetricVal { const current = parseAmount(row.thstrm_amount); + // Prior-year same period. On quarterly/semiannual flow statements DART leaves + // frmtrm_amount empty and carries ์ „๋…„ ๋™๊ธฐ in frmtrm_q_amount; on annual reports + // (and the quarterly balance sheet) there is no _q field, so fall back to + // frmtrm_amount (์ „๊ธฐ / ์ „๋…„๋ง). Without this, quarterly YoY is silently null. + const priorQ = parseAmount(row.frmtrm_q_amount); + const prior = priorQ !== null ? priorQ : parseAmount(row.frmtrm_amount); return { current, - prior: parseAmount(row.frmtrm_amount), + prior, label: row.account_nm ?? null, display: formatDisplay(current, kind), }; diff --git a/tsconfig.scripts.json b/tsconfig.scripts.json new file mode 100644 index 000000000..0e4653f92 --- /dev/null +++ b/tsconfig.scripts.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "rootDir": "." + }, + "include": ["scripts/**/*"] +} From b18890e26fd09a733275a543fd6dad7eeab210b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SH=20KWON=20=F0=9F=87=B0=F0=9F=87=B7?= Date: Mon, 1 Jun 2026 16:25:58 +0900 Subject: [PATCH 09/70] =?UTF-8?q?read=5Ffilings=5Fkr:=20DART=20=EC=82=AC?= =?UTF-8?q?=EC=97=85=EB=B3=B4=EA=B3=A0=EC=84=9C=20narrative=20extraction?= =?UTF-8?q?=20(=EC=82=AC=EC=97=85=EC=9D=98=20=EB=82=B4=EC=9A=A9=C2=B7?= =?UTF-8?q?=EC=9C=84=ED=97=98=EA=B4=80=EB=A6=AC=C2=B7MD&A)=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Korean analog of US read_filings โ€” qualitative narrative (์‚ฌ์—…์˜ ๋‚ด์šฉ/์œ„ํ—˜๊ด€๋ฆฌ/MD&A) from DART document.xml. Cache-by-rcept_no, cross-category dedup, fast-tier internal LLMs (READ_FILINGS_KR_MODEL override). 165 tests, grounding validated. --- README.md | 11 + .../default/business-overview-samsung.json | 16 + .../fixtures/default/key-risks-samsung.json | 16 + scripts/kr-eval/questions.ts | 18 + src/agent/prompts.ts | 2 +- src/cli.ts | 4 + .../dsd-005930-business-section.xml | 633 ++++++++++++++++++ src/tools/finance-kr/api.ts | 55 ++ src/tools/finance-kr/dart-document.test.ts | 159 +++++ src/tools/finance-kr/dart-document.ts | 282 ++++++++ src/tools/finance-kr/read-filings-kr.test.ts | 93 +++ src/tools/finance-kr/read-filings-kr.ts | 509 ++++++++++++++ src/tools/registry.test.ts | 30 + src/tools/registry.ts | 9 + 14 files changed, 1836 insertions(+), 1 deletion(-) create mode 100644 scripts/kr-eval/fixtures/default/business-overview-samsung.json create mode 100644 scripts/kr-eval/fixtures/default/key-risks-samsung.json create mode 100644 src/tools/finance-kr/__fixtures__/dsd-005930-business-section.xml create mode 100644 src/tools/finance-kr/dart-document.test.ts create mode 100644 src/tools/finance-kr/dart-document.ts create mode 100644 src/tools/finance-kr/read-filings-kr.test.ts create mode 100644 src/tools/finance-kr/read-filings-kr.ts create mode 100644 src/tools/registry.test.ts diff --git a/README.md b/README.md index d36302bc4..9fc680094 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ Dexter๋Š” ๋ฏธ๊ตญ ์ฃผ์‹๊ณผ ํ•œ๊ตญ ์ฃผ์‹์„ **ํ•˜๋‚˜์˜ ์—์ด์ „ํŠธ์—์„œ ๋™ |---|---|---| | `get_financials_kr` | `get_financials` (10-K/10-Q) | DART ์‚ฌ์—…ยท๋ฐ˜๊ธฐยท๋ถ„๊ธฐ๋ณด๊ณ ์„œ | | `get_filings_kr` | SEC EDGAR | DART ๊ณต์‹œ ๊ฒ€์ƒ‰ | +| `read_filings_kr` | `read_filings` (10-K ๋ณธ๋ฌธ) | DART ์‚ฌ์—…๋ณด๊ณ ์„œ ๋ณธ๋ฌธ (์‚ฌ์—…์˜ ๋‚ด์šฉยท์œ„ํ—˜๊ด€๋ฆฌยทMD&A) | | `get_large_holders_kr` | 13F (5% ์ด์ƒ ๋ณด์œ ) | DART ๋Œ€๋Ÿ‰๋ณด์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ | | `get_insider_trades_kr` | Form 4 (๋‚ด๋ถ€์ž ๊ฑฐ๋ž˜) | DART ์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ ๋ณด๊ณ  | @@ -68,6 +69,7 @@ LLM์ด "์‚ผ์„ฑ์ „์ž" ๊ฐ™์€ ์ž์—ฐ์–ด ์ž…๋ ฅ์„ ๋ฐ›์œผ๋ฉด, ํˆด ๋‚ด๋ถ€์—์„œ ** ### ํ•œ๊ตญ ์‹œ์žฅ ๊ณ ์œ  ์ฒ˜๋ฆฌ - **K-IFRS ์žฌ๋ฌด์ œํ‘œ** โ€” ์—ฐ๊ฒฐ/๋ณ„๋„ ๋‘˜ ๋‹ค ์กฐํšŒ, **๋งค์ถœยท์˜์—…์ด์ตยท์ˆœ์ด์ตยท๋งˆ์ง„ยทROEยทFCFยทYoY๋กœ ์ •๊ทœํ™”ํ•œ ์š”์•ฝ**์„ ๋ฐ˜ํ™˜(์›์‹œ ๋ผ์ธ์•„์ดํ…œ์€ ํŒŒ์ผ๋กœ ๋ณด์กดํ•ด ํ•„์š” ์‹œ ๋“œ๋ฆด๋‹ค์šด). ์˜์—…์ด์ต ์ •์˜๊ฐ€ ๋ฏธ๊ตญ GAAP์™€ ๋ฏธ๋ฌ˜ํ•˜๊ฒŒ ๋‹ค๋ฆ„ +- **๋ณด๊ณ ์„œ ๋ณธ๋ฌธ ์ •์„ฑ ๋ถ„์„** โ€” ์‚ฌ์—…๋ณด๊ณ ์„œ์˜ ใ€ŒII. ์‚ฌ์—…์˜ ๋‚ด์šฉใ€ยท์œ„ํ—˜๊ด€๋ฆฌยท๊ฒฝ์˜์ง„๋‹จ(MD&A) ๋ณธ๋ฌธ์„ ์ถ”์ถœยท์š”์•ฝํ•ด "์‚ฌ์—…์ด ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑ๋ผ ์žˆ๋‚˜", "์ฃผ์š” ๋ฆฌ์Šคํฌ๊ฐ€ ๋ญ์•ผ" ๊ฐ™์€ ์ •์„ฑ ์งˆ๋ฌธ์— ๋ณธ๋ฌธ ๊ทผ๊ฑฐ๋กœ ๋‹ต๋ณ€(์ˆซ์ž ํ‘œ๋Š” `get_financials_kr`๋กœ ๋ผ์šฐํŒ…). ํ•œ๊ตญ ๋ณด๊ณ ์„œ์—” SEC 10-K์˜ ์œ„ํ—˜์š”์†Œ(Item 1A) ๊ฐ™์€ ๋‹จ์ผ ์„น์…˜์ด ์—†์–ด, ํฉ์–ด์ง„ ์œ„ํ—˜๊ด€๋ฆฌยทํˆฌ์ž์ž ๋ณดํ˜ธ ํ•ญ๋ชฉ์„ ๋ชจ์•„ ์ข…ํ•ฉ - **๋‹จ์œ„** โ€” ์›(KRW), ๋ฐฑ๋งŒ์›/์–ต/์กฐ ๋‹จ์œ„๋กœ ์ž๋™ ํฌ๋งทํŒ… - **๊ฑฐ๋ž˜์‹œ๊ฐ„** โ€” 09:00โ€“15:30 KST ๊ธฐ์ค€ - **์ƒํ•˜ํ•œ๊ฐ€** โ€” ยฑ30% ์ผ๊ฐ„ ๋ณ€๋™ ์ œํ•œ ๊ณ ๋ ค @@ -106,6 +108,14 @@ LLM์ด "์‚ผ์„ฑ์ „์ž" ๊ฐ™์€ ์ž์—ฐ์–ด ์ž…๋ ฅ์„ ๋ฐ›์œผ๋ฉด, ํˆด ๋‚ด๋ถ€์—์„œ ** > ์—์ฝ”ํ”„๋กœ ์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ ์ตœ๊ทผ ์ง€๋ถ„ ๋ณ€๋™ ์•Œ๋ ค์ค˜ ``` +### ๋ณด๊ณ ์„œ ๋ณธ๋ฌธ ๋ถ„์„ (DART ์‚ฌ์—…๋ณด๊ณ ์„œ) + +``` +> ์‚ผ์„ฑ์ „์ž ์‚ฌ์—…์ด ์–ด๋–ค ์‚ฌ์—…๋ถ€๋ฌธ์œผ๋กœ ๊ตฌ์„ฑ๋ผ ์žˆ์–ด? +> ์‚ผ์„ฑ์ „์ž ์ฃผ์š” ๋ฆฌ์Šคํฌ๊ฐ€ ๋ญ์•ผ? +> ๋„ค์ด๋ฒ„(035420) ๊ฒฝ์˜์ง„๋‹จ(MD&A)์—์„œ ๊ฒฝ์˜์ง„์ด ๊ฐ•์กฐํ•œ ํฌ์ธํŠธ ์ •๋ฆฌํ•ด์ค˜ +``` + ### ํ•œ๊ตญ ํŠนํ™” ๋ฐ์ดํ„ฐ ``` @@ -219,6 +229,7 @@ DART_API_KEY=your-dart-api-key # ์žฌ๋ฌดยท๊ณต์‹œ (ํ•„์ˆ˜: KR ํŽ€๋” # KRX_PW=your-krx-password # KRX_COOKIE=JSESSIONID=...; # ์†Œ์…œ ๋กœ๊ทธ์ธ ๊ณ„์ •์€ ๋ธŒ๋ผ์šฐ์ € ์ฟ ํ‚ค ๋ถ™์—ฌ๋„ฃ๊ธฐ # DATA_GO_KR_SERVICE_KEY=... # ๊ตญ๋ฏผ์—ฐ๊ธˆ ๋ณด์œ  (data.go.kr, Decoded ํ‚ค) +# READ_FILINGS_KR_MODEL=... # (์„ ํƒ) read_filings_kr ๋‚ด๋ถ€ ์š”์•ฝ ๋ชจ๋ธ ์˜ค๋ฒ„๋ผ์ด๋“œ. ๊ธฐ๋ณธ์€ provider์˜ fast ๋ชจ๋ธ(์ €๋น„์šฉ) # --- ์›น ๊ฒ€์ƒ‰ (์„ ํƒ, Exa ์šฐ์„  โ†’ Tavily ํด๋ฐฑ) --- # EXASEARCH_API_KEY=your-exa-api-key diff --git a/scripts/kr-eval/fixtures/default/business-overview-samsung.json b/scripts/kr-eval/fixtures/default/business-overview-samsung.json new file mode 100644 index 000000000..0ffb68d34 --- /dev/null +++ b/scripts/kr-eval/fixtures/default/business-overview-samsung.json @@ -0,0 +1,16 @@ +{ + "id": "business-overview-samsung", + "query": "์‚ผ์„ฑ์ „์ž(005930) ์‚ฌ์—…๋ณด๊ณ ์„œ ๋ณด๋ฉด ์‚ฌ์—…์ด ์–ด๋–ค ์‚ฌ์—…๋ถ€๋ฌธ์œผ๋กœ ๊ตฌ์„ฑ๋ผ ์žˆ์–ด?", + "meta": { + "model": "gpt-5.5" + }, + "toolCalls": [ + { + "tool": "read_filings_kr", + "args": { + "query": "์‚ผ์„ฑ์ „์ž 005930 ์ตœ๊ทผ ์‚ฌ์—…๋ณด๊ณ ์„œ ์‚ฌ์—…์˜ ๋‚ด์šฉ ์‚ฌ์—…๋ถ€๋ฌธ ๊ตฌ์„ฑ" + }, + "result": "{\"data\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_nm\":\"์‚ฌ์—…๋ณด๊ณ ์„œ (2025.12)\",\"rcept_no\":\"20260310002820\",\"sections_found\":[\"business\"],\"summary\":\"์‚ผ์„ฑ์ „์ž(005930)์˜ ์ตœ๊ทผ ์‚ฌ์—…๋ณด๊ณ ์„œ(2025.12, DART ์ ‘์ˆ˜๋ฒˆํ˜ธ 20260310002820)์ƒ ์‚ฌ์—…๋ถ€๋ฌธ์€ ํฌ๊ฒŒ **DX, DS, SDC, Harman**์œผ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค. ํšŒ์‚ฌ๋Š” ๋ณธ์‚ฌ๋ฅผ ๊ฑฐ์ ์œผ๋กœ ๊ตญ๋‚ด์™ธ ์ƒ์‚ฐยทํŒ๋งคยท์—ฐ๊ตฌ ๋ฒ•์ธ ๋“ฑ **308๊ฐœ ์ข…์†๊ธฐ์—…**์„ ํ†ตํ•ด ๊ธ€๋กœ๋ฒŒ ์ „์ž ์‚ฌ์—…์„ ์˜์œ„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n| ์‚ฌ์—…๋ถ€๋ฌธ | ์ฃผ์š” ์‚ฌ์—… ๋‚ด์šฉ | ์ฃผ์š” ์ œํ’ˆยท์„œ๋น„์Šค | 2025๋…„ ๋งค์ถœ |\\n|---|---|---|---:|\\n| **DX(Device eXperience)** | ์™„์ œํ’ˆ, ์ฆ‰ Set ์‚ฌ์—… ์ค‘์‹ฌ | TV, ๋ชจ๋‹ˆํ„ฐ, ๋ƒ‰์žฅ๊ณ , ์„ธํƒ๊ธฐ, ์—์–ด์ปจ, ์Šค๋งˆํŠธํฐ, ๋„คํŠธ์›Œํฌ์‹œ์Šคํ…œ, PC ๋“ฑ | **187์กฐ 9,673์–ต์›** / **56.3%** |\\n| **DS(Device Solutions)** | ๋ฐ˜๋„์ฒด ๋ถ€ํ’ˆ ์‚ฌ์—… | DRAM, NAND Flash, ๋ชจ๋ฐ”์ผAP, ์ด๋ฏธ์ง€์„ผ์„œ, Foundry ๋“ฑ | **130์กฐ 1,282์–ต์›** / **39.0%** |\\n| **SDC** | ๋””์Šคํ”Œ๋ ˆ์ด ํŒจ๋„ ์‚ฌ์—… | ์Šค๋งˆํŠธํฐ์šฉ OLED ํŒจ๋„, ํด๋”๋ธ”ยทํƒœ๋ธ”๋ฆฟยท๋…ธํŠธ๋ถยทAuto์šฉ OLED, QD-OLED ๋“ฑ | **29์กฐ 8,417์–ต์›** / **8.9%** |\\n| **Harman** | ์ „์žฅ ๋ฐ ์˜ค๋””์˜ค ์‚ฌ์—… | ๋””์ง€ํ„ธ ์ฝ•ํ•, ์นด์˜ค๋””์˜ค, ํ…”๋ ˆ๋งคํ‹ฑ์Šค, ํฌํ„ฐ๋ธ”ยท์‚ฌ์šด๋“œ๋ฐ” ์Šคํ”ผ์ปค, ์ปจ์Šˆ๋จธ์˜ค๋””์˜ค ๋“ฑ | **15์กฐ 7,833์–ต์›** / **4.7%** |\\n\\nํ•ต์‹ฌ์ ์œผ๋กœ ๋ณด๋ฉด, ์‚ผ์„ฑ์ „์ž๋Š” **DX ๋ถ€๋ฌธ์ด ๋งค์ถœ์˜ ์ ˆ๋ฐ˜ ์ด์ƒ์„ ์ฐจ์ง€ํ•˜๋Š” ์™„์ œํ’ˆ ์ค‘์‹ฌ ์ถ•**์ด๊ณ , **DS ๋ถ€๋ฌธ์ด ๋ฉ”๋ชจ๋ฆฌยท์‹œ์Šคํ…œ๋ฐ˜๋„์ฒดยทํŒŒ์šด๋“œ๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ํ•ต์‹ฌ ๋ถ€ํ’ˆ ์ถ•**์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์— **SDC๋Š” OLED ์ค‘์‹ฌ์˜ ๋””์Šคํ”Œ๋ ˆ์ด**, **Harman์€ ์ „์žฅ๋ถ€ํ’ˆ๊ณผ ์˜ค๋””์˜ค**๋ฅผ ๋‹ด๋‹นํ•˜๋ฉฐ ์‚ฌ์—… ํฌํŠธํด๋ฆฌ์˜ค๋ฅผ ๋ณด์™„ํ•ฉ๋‹ˆ๋‹ค.\\n\\n์‚ฌ์—… ์„ฑ๊ฒฉ๋ณ„๋กœ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n1. **Set ์‚ฌ์—…: DX ๋ถ€๋ฌธ** \\n TV, ์Šค๋งˆํŠธํฐ, ๊ฐ€์ „, PC, ๋„คํŠธ์›Œํฌ์‹œ์Šคํ…œ ๋“ฑ ์†Œ๋น„์žยท๊ธฐ์—…์šฉ ์™„์ œํ’ˆ์„ ์ƒ์‚ฐยทํŒ๋งคํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์—…๋ณด๊ณ ์„œ์ƒ DX ๋ถ€๋ฌธ์€ 2025๋…„ ๋งค์ถœ ๊ธฐ์ค€ ์ตœ๋Œ€ ๋ถ€๋ฌธ์ž…๋‹ˆ๋‹ค.\\n\\n2. **๋ถ€ํ’ˆ ์‚ฌ์—…: DS ๋ถ€๋ฌธ ๋ฐ SDC** \\n DS ๋ถ€๋ฌธ์€ DRAM, NAND Flash, ๋ชจ๋ฐ”์ผAP ๋“ฑ ๋ฐ˜๋„์ฒด๋ฅผ ์ƒ์‚ฐยทํŒ๋งคํ•˜๋ฉฐ, SDC๋Š” ์Šค๋งˆํŠธํฐ์šฉ OLED ํŒจ๋„ ๋“ฑ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ DS ๋ถ€๋ฌธ์€ AI ์ธํ”„๋ผ ํ™•๋Œ€์— ๋”ฐ๋ฅธ ์„œ๋ฒ„ํ–ฅ ๋ฉ”๋ชจ๋ฆฌ, HBM, ๊ณ ์šฉ๋Ÿ‰ DDR5, ์„œ๋ฒ„ SSD, Foundry ์„ ๋‹จ๊ณต์ • ๋“ฑ์„ ์ฃผ์š” ์„ฑ์žฅ์ถ•์œผ๋กœ ์ œ์‹œํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n3. **์ „์žฅยท์˜ค๋””์˜ค ์‚ฌ์—…: Harman** \\n Harman์€ ๋””์ง€ํ„ธ ์ฝ•ํ•, ์นด์˜ค๋””์˜ค ๋“ฑ ์ „์žฅ์ œํ’ˆ๊ณผ ํฌํ„ฐ๋ธ” ์Šคํ”ผ์ปค, ์‚ฌ์šด๋“œ๋ฐ” ๋“ฑ ์˜ค๋””์˜ค ์ œํ’ˆ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ์ „์žฅ๋ถ€ํ’ˆ์—์„œ๋Š” ์ฐจ๋Ÿ‰ ๋‚ด ๊ฒฝํ—˜, SDV ๋Œ€์‘, ADAS ํ†ตํ•ฉ ์ปจํŠธ๋กค๋Ÿฌ ๋“ฑ์ด ์ฃผ์š” ๋ฐฉํ–ฅ์œผ๋กœ ์–ธ๊ธ‰๋ฉ๋‹ˆ๋‹ค.\\n\\n์ง€์—ญ ์šด์˜ ๊ตฌ์กฐ๋„ ์‚ฌ์—…๋ถ€๋ฌธ๋ณ„๋กœ ๋‚˜๋‰ฉ๋‹ˆ๋‹ค. DX ๋ถ€๋ฌธ์€ ํ•ด์™ธ **9๊ฐœ ์ง€์—ญ์ด๊ด„**, DS ๋ถ€๋ฌธ์€ ํ•ด์™ธ **5๊ฐœ ์ง€์—ญ์ด๊ด„**์„ ๋‘๊ณ  ์žˆ์œผ๋ฉฐ, SDC์™€ Harman ์‚ฐํ•˜ ์ข…์†๊ธฐ์—…๊นŒ์ง€ ํฌํ•จํ•ด ๊ธ€๋กœ๋ฒŒ ์ƒ์‚ฐยทํŒ๋งคยท์—ฐ๊ตฌ ์ฒด๊ณ„๋ฅผ ์šด์˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\",\"rawSectionsFile\":\".dexter/tool-results/kr-filing-00126380-20260310002820.json\",\"note\":\"summary is grounded in the report narrative. Use read_file on rawSectionsFile for the full extracted sections.\"},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/document.xml?rcept_no=20260310002820\",\"https://opendart.fss.or.kr/api/list.json?corp_code=00126380&bgn_de=20241201&end_de=20260531&pblntf_ty=A&page_count=100&page_no=1&sort=date&sort_mth=desc\"]}" + } + ] +} diff --git a/scripts/kr-eval/fixtures/default/key-risks-samsung.json b/scripts/kr-eval/fixtures/default/key-risks-samsung.json new file mode 100644 index 000000000..c364ecbfd --- /dev/null +++ b/scripts/kr-eval/fixtures/default/key-risks-samsung.json @@ -0,0 +1,16 @@ +{ + "id": "key-risks-samsung", + "query": "์‚ผ์„ฑ์ „์ž ์ฃผ์š” ๋ฆฌ์Šคํฌ๊ฐ€ ๋ญ์•ผ?", + "meta": { + "model": "gpt-5.5" + }, + "toolCalls": [ + { + "tool": "read_filings_kr", + "args": { + "query": "์‚ผ์„ฑ์ „์ž 005930 ์ตœ๊ทผ ์‚ฌ์—…๋ณด๊ณ ์„œ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ ์ฃผ์š” ๋ฆฌ์Šคํฌ: ์‚ฌ์—…์˜ ๋‚ด์šฉ ์œ„ํ—˜๊ด€๋ฆฌ ๋ฐ ํŒŒ์ƒ๊ฑฐ๋ž˜, ํˆฌ์ž์ž ๋ณดํ˜ธ ๊ด€๋ จ ์œ„ํ—˜ ์š”์•ฝ" + }, + "result": "{\"data\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_nm\":\"์‚ฌ์—…๋ณด๊ณ ์„œ (2025.12)\",\"rcept_no\":\"20260310002820\",\"sections_found\":[\"risks\"],\"summary\":\"์•„๋ž˜ ์š”์•ฝ์€ ์ œ๊ณต๋œ ์‚ผ์„ฑ์ „์ž 2025.12 ์‚ฌ์—…๋ณด๊ณ ์„œ ๋ฐœ์ทŒ ๋‚ด์šฉ์— ๊ทผ๊ฑฐํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ฐœ์ทŒ์—๋Š” โ€œ์‚ฌ์—…์˜ ๋‚ด์šฉโ€์ƒ ์ œํ’ˆยท์‚ฌ์—…๋ถ€๋ณ„ ์˜์—… ๋ฆฌ์Šคํฌ๋Š” ํฌํ•จ๋˜์–ด ์žˆ์ง€ ์•Š์•„, ํ•ด๋‹น ๋ถ€๋ถ„์€ ํ™•์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ โ€œ์œ„ํ—˜๊ด€๋ฆฌ ๋ฐ ํŒŒ์ƒ๊ฑฐ๋ž˜โ€, โ€œ์šฐ๋ฐœ๋ถ€์ฑ„ยท์ œ์žฌโ€, โ€œ๋…น์ƒ‰๊ฒฝ์˜โ€ ๋“ฑ ํˆฌ์ž์ž ๋ณดํ˜ธ ๊ด€์ ์˜ ์ฃผ์š” ๋ฆฌ์Šคํฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •๋ฆฌ๋ฉ๋‹ˆ๋‹ค.\\n\\n## 1) ์žฌ๋ฌด์œ„ํ—˜: ํ™˜์œจยท๊ธˆ๋ฆฌยท์ฃผ๊ฐ€ยท์‹ ์šฉยท์œ ๋™์„ฑ ๋ฆฌ์Šคํฌ\\n\\n์‚ผ์„ฑ์ „์ž๋Š” ๊ธ€๋กœ๋ฒŒ ์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•ด **์‹œ์žฅ์œ„ํ—˜, ์‹ ์šฉ์œ„ํ—˜, ์œ ๋™์„ฑ์œ„ํ—˜**์— ๋…ธ์ถœ๋˜์–ด ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ๊ธ€๋กœ๋ฒŒ ์žฌ๋ฌด๊ด€๋ฆฌ๊ธฐ์ค€, ๊ณ ๊ฐยท๊ฑฐ๋ž˜์„  ์žฌ๋ฌด์œ„ํ—˜ ์ธก์ •, ํ™˜ํ—ท์ง€, ์ž๊ธˆ์ˆ˜์ง€ ์ ๊ฒ€ ๋“ฑ์„ ํ†ตํ•ด ๊ด€๋ฆฌํ•œ๋‹ค๊ณ  ๊ณต์‹œํ–ˆ์Šต๋‹ˆ๋‹ค.\\n\\n### ํ™˜์œจ๋ณ€๋™์œ„ํ—˜\\n- ๊ธ€๋กœ๋ฒŒ ๊ฑฐ๋ž˜๋กœ ์ธํ•ด ๊ธฐ๋Šฅํ†ตํ™”์™€ ๋‹ค๋ฅธ ํ†ตํ™” ๊ฑฐ๋ž˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉฐ, ์ฃผ์š” ๋…ธ์ถœ ํ†ตํ™”๋Š” **USD, EUR ๋“ฑ**์ž…๋‹ˆ๋‹ค.\\n- ํšŒ์‚ฌ๋Š” ํ†ตํ™”๋ณ„ ์ž์‚ฐยท๋ถ€์ฑ„ ๊ทœ๋ชจ๋ฅผ ์ผ์น˜์‹œํ‚ค๊ณ , ์ˆ˜์ถœ์ž…ยท์˜ˆ๊ธˆยท์ฐจ์ž… ๊ฑฐ๋ž˜์—์„œ ์ž…๊ธˆยท์ง€์ถœ ํ†ตํ™”๋ฅผ ๋งž์ถฐ ํ™˜ํฌ์ง€์…˜ ๋ฐœ์ƒ์„ ์–ต์ œํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n- ์ผ๋ถ€ ๋ฐœ์ƒํ•œ ํ™˜ํฌ์ง€์…˜์€ **์ฑ„๊ถŒ๋งค๊ฐ, ์„ ๋ฌผํ™˜** ๋“ฑ์„ ํ™œ์šฉํ•ด ์ถ•์†Œํ•ฉ๋‹ˆ๋‹ค.\\n- ๋‹ค๋งŒ ํšŒ์‚ฌ๋„ โ€œํ™˜์œจ๋ณ€๋™์œ„ํ—˜์„ ๊ฐ์†Œ์‹œํ‚ค์ง€๋งŒ ์™„์ „ํžˆ ์ œ๊ฑฐํ•˜์ง€๋Š” ์•Š์„ ์ˆ˜ ์žˆ๋‹คโ€๊ณ  ๋ช…์‹œํ•˜๊ณ  ์žˆ์–ด, ํ™˜์œจ ๊ธ‰๋ณ€ ์‹œ ์†์ต ๋ณ€๋™ ๊ฐ€๋Šฅ์„ฑ์€ ๋‚จ์•„ ์žˆ์Šต๋‹ˆ๋‹ค.\\n- ํˆฌ๊ธฐ์  ์™ธํ™˜๊ฑฐ๋ž˜๋Š” ์—„๊ฒฉํžˆ ๊ธˆ์ง€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n### ์ด์ž์œจ๋ณ€๋™์œ„ํ—˜\\n- ์˜ˆ๊ธˆ ๋ฐ ๋ณ€๋™๊ธˆ๋ฆฌ๋ถ€ ์ฐจ์ž…๊ธˆ์—์„œ ์ด์ž์ˆ˜์ตยท๋น„์šฉ ๋ณ€๋™ ์œ„ํ—˜์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.\\n- ์‚ผ์„ฑ์ „์ž๋Š” ๋‚ด๋ถ€์ž๊ธˆ ๊ณต์œ ๋ฅผ ํ†ตํ•ด ์™ธ๋ถ€์ฐจ์ž…๊ธˆ์„ ์ตœ์†Œํ™”ํ•จ์œผ๋กœ์จ ๊ธˆ์œต๋น„์šฉ๊ณผ ๋ถˆํ™•์‹ค์„ฑ์„ ์ค„์ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n### ์ฃผ๊ฐ€๋ณ€๋™์œ„ํ—˜\\n- ์ „๋žต์  ๋ชฉ์  ๋“ฑ์œผ๋กœ ์ง€๋ถ„์ƒํ’ˆ์— ํˆฌ์žํ•˜๊ณ  ์žˆ์–ด ์ƒ์žฅ์ฃผ์‹ ๊ฐ€๊ฒฉ ๋ณ€๋™์— ๋…ธ์ถœ๋ฉ๋‹ˆ๋‹ค.\\n- 2025๋…„ ๋ง ๊ธฐ์ค€ ์ง€๋ถ„์ƒํ’ˆ ์ƒ์žฅ์ฃผ์‹ ๊ฐ€๊ฒฉ์ด 1% ๋ณ€๋™ํ•  ๊ฒฝ์šฐ:\\n - ๊ธฐํƒ€ํฌ๊ด„์†์ต ์˜ํ–ฅ: **132,067๋ฐฑ๋งŒ์›**\\n - ๋‹น๊ธฐ์†์ต ์˜ํ–ฅ: **1,460๋ฐฑ๋งŒ์›**\\n- ์ „๊ธฐ ๋Œ€๋น„ ์˜ํ–ฅ ๊ทœ๋ชจ๊ฐ€ ์ปค์ง„ ์ ์€, ๋ณด์œ  ์ง€๋ถ„์ƒํ’ˆ์˜ ๊ณต์ •๊ฐ€์น˜ ๋ณ€๋™์ด ์ž๋ณธ ๋ฐ ์†์ต์— ๋ฏธ์น˜๋Š” ๋ฏผ๊ฐ๋„๊ฐ€ ํ™•๋Œ€๋˜์—ˆ์Œ์„ ์‹œ์‚ฌํ•ฉ๋‹ˆ๋‹ค.\\n\\n### ์‹ ์šฉ์œ„ํ—˜\\n- ๊ณ ๊ฐ ๋˜๋Š” ๊ฑฐ๋ž˜์ƒ๋Œ€๋ฐฉ์ด ๊ณ„์•ฝ์ƒ ์˜๋ฌด๋ฅผ ์ดํ–‰ํ•˜์ง€ ๋ชปํ•  ์œ„ํ—˜์ž…๋‹ˆ๋‹ค.\\n- ์‚ผ์„ฑ์ „์ž๋Š” ๊ณ ๊ฐยท๊ฑฐ๋ž˜์ƒ๋Œ€๋ฐฉ์˜ ์žฌ๋ฌด์ƒํƒœ์™€ ๊ณผ๊ฑฐ ๊ฒฝํ—˜ ๋“ฑ์„ ๋ฐ˜์˜ํ•ด ์‹ ์šฉ๋„๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ํ‰๊ฐ€ํ•˜๊ณ , ์‹ ์šฉํ•œ๋„๋ฅผ ์„ค์ •ยท๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.\\n- ์œ„ํ—˜๊ตญ๊ฐ€ ์†Œ์žฌ ๊ฑฐ๋ž˜์ฒ˜์˜ ๋งค์ถœ์ฑ„๊ถŒ์€ ๋ณดํ—˜ํ•œ๋„ ๋‚ด์—์„œ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.\\n- ๊ธˆ์œต๊ธฐ๊ด€ ๊ฑฐ๋ž˜๋Š” ์›์น™์ ์œผ๋กœ **๊ตญ์ œ ์‹ ์šฉ๋“ฑ๊ธ‰ S&P A ์ด์ƒ ์€ํ–‰**๊ณผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\\n\\n### ์œ ๋™์„ฑ์œ„ํ—˜\\n- ๊ธˆ์œต๋ถ€์ฑ„ ๊ด€๋ จ ์˜๋ฌด ์ดํ–‰์— ์–ด๋ ค์›€์„ ๊ฒช์„ ์œ„ํ—˜์ž…๋‹ˆ๋‹ค.\\n- ์ฃผ์š” ์œ ๋™์„ฑ ์ˆ˜์š”๋Š” **์ƒ์‚ฐ, ์—ฐ๊ตฌ๊ฐœ๋ฐœ ํˆฌ์ž, ์šด์ „์ž๋ณธ, ๋ฐฐ๋‹น** ๋“ฑ์ž…๋‹ˆ๋‹ค.\\n- ๋Œ€๊ทœ๋ชจ ํˆฌ์ž๊ฐ€ ๋งŽ์€ ์‚ฌ์—… ํŠน์„ฑ์ƒ ์ ์ • ์œ ๋™์„ฑ ์œ ์ง€๊ฐ€ ์ค‘์š”ํ•˜๋‹ค๊ณ  ๊ณต์‹œํ–ˆ์Šต๋‹ˆ๋‹ค.\\n- ๊ถŒ์—ญ๋ณ„ **Cash Pooling**์„ ํ†ตํ•ด ์ž๊ธˆ ๋ถ€์กฑ ๋ฒ•์ธ๊ณผ ์ž‰์—ฌ ๋ฒ•์ธ ๊ฐ„ ์ž๊ธˆ์„ ๊ณต์œ ํ•˜๊ณ , ๊ธˆ์œต๋น„์šฉ ์ ˆ๊ฐ ๋ฐ ์œ ๋™์„ฑ์œ„ํ—˜ ์™„ํ™”๋ฅผ ์ถ”์ง„ํ•ฉ๋‹ˆ๋‹ค.\\n- 2025๋…„ ๋ง ํ˜„์žฌ ๊ตญ์ œ์‹ ์šฉํ‰๊ฐ€๊ธฐ๊ด€์œผ๋กœ๋ถ€ํ„ฐ **Moodyโ€™s Aa2, S&P AA-** ๋“ฑ ํˆฌ์ž์ ๊ฒฉ๋“ฑ๊ธ‰์„ ๋ถ€์—ฌ๋ฐ›์•„ ํ•„์š” ์‹œ ์ž๋ณธ์‹œ์žฅ์„ ํ†ตํ•œ ์ž๊ธˆ์กฐ๋‹ฌ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๊ณ  ์„ค๋ช…ํ–ˆ์Šต๋‹ˆ๋‹ค.\\n\\n## 2) ํŒŒ์ƒ์ƒํ’ˆ ๋ฐ ์˜ต์…˜ ๊ด€๋ จ ๋ฆฌ์Šคํฌ\\n\\n### ํ†ตํ™”์„ ๋„ ๊ฑฐ๋ž˜\\n- ์‚ผ์„ฑ์ „์ž๋Š” ํ™˜์œจ๋ณ€๋™ ์œ„ํ—˜๊ด€๋ฆฌ ๋ชฉ์ ์ƒ ์žฅ๋ถ€ํ†ตํ™”๊ฐ€ ์•„๋‹Œ ์™ธํ™” ํฌ์ง€์…˜์— ๋Œ€ํ•ด **ํ†ตํ™”์„ ๋„(Currency Forward)** ์ƒํ’ˆ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.\\n- 2025๋…„ ๋ง ํ˜„์žฌ **USD, EUR, JPY ๋“ฑ ์ด 33๊ฐœ ํ†ตํ™”**์— ๋Œ€ํ•ด **3,360๊ฑด**์˜ ํ†ตํ™”์„ ๋„ ๊ฑฐ๋ž˜๋ฅผ ์ฒด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.\\n- ์ด๋Š” ํ™˜์œ„ํ—˜ ๊ด€๋ฆฌ ๋ชฉ์ ์ด์ง€๋งŒ, ํŒŒ์ƒ์ƒํ’ˆ ํŠน์„ฑ์ƒ ํ™˜์œจ ๋ณ€๋™ ๋ฐ ๊ฑฐ๋ž˜์ƒ๋Œ€๋ฐฉ ๊ด€๋ จ ์œ„ํ—˜์ด ์™„์ „ํžˆ ์ œ๊ฑฐ๋˜๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค.\\n\\n### ๋ ˆ์ธ๋ณด์šฐ๋กœ๋ณดํ‹ฑ์Šค ์ฝœ์˜ต์…˜\\n- ์‚ผ์„ฑ์ „์ž๋Š” ๋ ˆ์ธ๋ณด์šฐ๋กœ๋ณดํ‹ฑ์Šคใˆœ ๋ฐ ์ตœ๋Œ€์ฃผ์ฃผ ๋“ฑ๊ณผ์˜ ์ฃผ์ฃผ๊ฐ„ ๊ณ„์•ฝ์— ๋”ฐ๋ผ ์ผ์ • ๊ธฐ๊ฐ„ ์ตœ๋Œ€์ฃผ์ฃผ ๋“ฑ์ด ๋ณด์œ ํ•œ ์ฃผ์‹์„ ๋งค๋„ํ•˜๋„๋ก ์ฒญ๊ตฌํ•  ๊ถŒ๋ฆฌ๋ฅผ ๋ณด์œ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n- 2024๋…„ 12์›” 31์ผ ์ด์‚ฌํšŒ์—์„œ ๋ ˆ์ธ๋ณด์šฐ๋กœ๋ณดํ‹ฑ์Šค ์ตœ๋Œ€์ฃผ์ฃผ ๋“ฑ 6์ธ์ด ๋ณด์œ ํ•œ **394๋งŒ์ฃผ** ์ธ์ˆ˜ ์ฝœ์˜ต์…˜ ํ–‰์‚ฌ๋ฅผ ์˜๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.\\n- ์ธ์ˆ˜๋Œ€๊ฐ€๋Š” **267,463๋ฐฑ๋งŒ์›**์ด๋ฉฐ, 2025๋…„ 3์›” 12์ผ ์ง€๋ถ„์ธ์ˆ˜ ์ ˆ์ฐจ๋ฅผ ์™„๋ฃŒํ–ˆ์Šต๋‹ˆ๋‹ค.\\n- 2025๋…„ ๋ง ํ˜„์žฌ ๋ณด์œ  ์ฝœ์˜ต์…˜์˜ ํ–‰์‚ฌ๊ฐ€์•ก์€ ์‹œ๊ฐ€๋กœ, ๊ณต์ •๊ฐ€์น˜๋ฅผ ๋ณ„๋„๋กœ ํ‰๊ฐ€ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.\\n\\n### ์‚ผ์„ฑ๋””์Šคํ”Œ๋ ˆ์ด์˜ ํ’‹์˜ต์…˜ ๋ฐ ์ฃผ์‹์„ ๋„๊ณ„์•ฝ\\n- ์‚ผ์„ฑ๋””์Šคํ”Œ๋ ˆ์ดใˆœ๋Š” Corning ์ง€๋ถ„์ฆ๊ถŒ ์ผ๋ถ€๋ฅผ Corning์— ๋งค๊ฐํ•  ์ˆ˜ ์žˆ๋Š” ํ’‹์˜ต์…˜์„ ๋ณด์œ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n- ๋˜ํ•œ CSOT๊ฐ€ ์ƒ์žฅ๊ธฐํ•œ ๋‚ด ๋น„์ƒ์žฅ ์‹œ, ๋ณด์œ  CSOT ์ง€๋ถ„์ฆ๊ถŒ ์ „๋ถ€ ๋˜๋Š” ์ผ๋ถ€๋ฅผ TCL์— ๋งค๊ฐํ•  ์ˆ˜ ์žˆ๋Š” ํ’‹์˜ต์…˜์„ ๋ณด์œ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n- ํ•ด๋‹น ํ’‹์˜ต์…˜๋“ค์˜ ๊ณต์ •๊ฐ€์น˜๋Š” 2025๋…„ ๋ง ํ˜„์žฌ ํ•œ์˜ํšŒ๊ณ„๋ฒ•์ธ์ด ํ‰๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.\\n- ์‚ผ์„ฑ๋””์Šคํ”Œ๋ ˆ์ด๋Š” 2025๋…„ 12์›” ๋ณต์ˆ˜์˜ ๊ธˆ์œต๊ธฐ๊ด€๊ณผ ์‚ผ์„ฑ์ „์ž ๋ณดํ†ต์ฃผ๋ฅผ ๊ธฐ์ดˆ์ž์‚ฐ์œผ๋กœ ํ•˜๋Š” **์ฐจ์•ก๊ฒฐ์ œํ˜• ์ฃผ์‹์„ ๋„๊ณ„์•ฝ**๋„ ์ฒด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.\\n\\n## 3) ์†Œ์†กยท๋ถ„์Ÿยท์กฐ์‚ฌ ๊ด€๋ จ ์šฐ๋ฐœ ๋ฆฌ์Šคํฌ\\n\\n- ์‚ผ์„ฑ์ „์ž๋Š” ์ •์ƒ์ ์ธ ์˜์—…๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•œ ๋‹ค์ˆ˜์˜ ์†Œ์†ก, ๋ถ„์Ÿ, ๊ทœ์ œ๊ธฐ๊ด€ ์กฐ์‚ฌ๋ฅผ ์ง„ํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค.\\n- ์ž์›์˜ ์œ ์ถœ๊ธˆ์•ก๊ณผ ์‹œ๊ธฐ๋Š” ๋ถˆํ™•์‹คํ•ฉ๋‹ˆ๋‹ค.\\n- ๊ฒฝ์˜์ง„์€ ์ด๋Ÿฌํ•œ ์†Œ์†ก ๋“ฑ์˜ ๊ฒฐ๊ณผ๊ฐ€ ํšŒ์‚ฌ ์žฌ๋ฌด์ƒํƒœ์— ์ค‘์š”ํ•œ ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š์„ ๊ฒƒ์œผ๋กœ ํŒ๋‹จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n- ๋‹ค๋งŒ ํˆฌ์ž์ž ๊ด€์ ์—์„œ๋Š” ๊ฒฐ๊ณผ์™€ ์‹œ๊ธฐ๊ฐ€ ๋ถˆํ™•์‹คํ•œ ์šฐ๋ฐœ ๋ฆฌ์Šคํฌ๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.\\n- ๋˜ํ•œ ์‚ผ์„ฑ์ „์žใˆœ, ์‚ผ์„ฑ๋””์Šคํ”Œ๋ ˆ์ดใˆœ ๋“ฑ์€ ๋ถ„ํ• ๋œ ์‚ผ์„ฑ๋””์Šคํ”Œ๋ ˆ์ดใˆœ ๋“ฑ์˜ ๋ถ„ํ•  ์ „ ์ฑ„๋ฌด์— ๋Œ€ํ•ด ์—ฐ๋Œ€ํ•˜์—ฌ ๋ณ€์ œํ•  ์ฑ…์ž„์ด ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n## 4) ์ œ์žฌยท์ค€๋ฒ• ๋ฆฌ์Šคํฌ\\n\\n์‚ผ์„ฑ์ „์ž ๋ฐ ์ข…์†ํšŒ์‚ฌ๋Š” ์‚ฐ์—…์•ˆ์ „, ๊ณต์ •๊ฑฐ๋ž˜, ๊ฐœ์ธ์ •๋ณด, ํ™˜๊ฒฝ, ์ˆ˜์ถœํ†ต์ œ, ์†Œ๋น„์ž๋ณดํ˜ธ, ์„ธ๋ฌด, ์ œ์žฌ ๋Œ€์ƒ๊ตญ ๊ฑฐ๋ž˜, ๊ด‘๊ณ , ๋ฐ˜๋…์  ๋“ฑ ๋‹ค์–‘ํ•œ ์˜์—ญ์—์„œ ์ œ์žฌ๋ฅผ ๋ฐ›์€ ๋‚ด์—ญ์ด ๊ณต์‹œ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐœ๋ณ„ ๊ธˆ์•ก์€ ๋Œ€๋ถ€๋ถ„ ํšŒ์‚ฌ ๊ทœ๋ชจ ๋Œ€๋น„ ์ œํ•œ์ ์œผ๋กœ ๋ณด์ด๋‚˜, ๋ฐ˜๋ณต ๋ฐœ์ƒ ์‹œ ๋‚ด๋ถ€ํ†ต์ œยทํ‰ํŒ ๋ฆฌ์Šคํฌ๋กœ ์—ฐ๊ฒฐ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n์ฃผ์š” ์‚ฌ๋ก€๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.\\n\\n### ์‚ฐ์—…์•ˆ์ „ยทํ™˜๊ฒฝยท์œ„ํ—˜๋ฌผ ๊ด€๋ จ\\n- ์‚ผ์„ฑ์ „์ž๋Š” ๊ตฌ ์‚ฐ์—…์•ˆ์ „๋ณด๊ฑด๋ฒ• ๋ฐ ์‚ฐ์—…์•ˆ์ „๋ณด๊ฑด๋ฒ• ์œ„๋ฐ˜์œผ๋กœ ๋ฒŒ๊ธˆ **15๋ฐฑ๋งŒ์›**์„ ํ™•์ •ยท๋‚ฉ๋ถ€ํ–ˆ์Šต๋‹ˆ๋‹ค.\\n- ๊ตฌ๋ฏธ์‚ฌ์—…์žฅ ์œ„ํ—˜๋ฌผ์•ˆ์ „๊ด€๋ฆฌ๋ฒ• ์œ„๋ฐ˜์œผ๋กœ ์กฐ์น˜๋ช…๋ น์„ ๋ฐ›๊ณ  ์œ„ํ—˜๋ฌผ์ทจ๊ธ‰์†Œ ์„ค์น˜ํ—ˆ๊ฐ€์‹ ์ฒญ ๋“ฑ ์กฐ์น˜๋ฅผ ์™„๋ฃŒํ–ˆ์Šต๋‹ˆ๋‹ค.\\n- ๊ด‘์ฃผ์‚ฌ์—…์žฅ์€ ๋Œ€๊ธฐํ™˜๊ฒฝ๋ณด์ „๋ฒ• ์œ„๋ฐ˜์œผ๋กœ ๊ฒฝ๊ณ  ๋ฐ ๊ณผํƒœ๋ฃŒ **2๋ฐฑ๋งŒ์›**์„ ๋ถ€๊ณผ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.\\n- ๊ธฐํฅยทํ™”์„ฑ์‚ฌ์—…์žฅ ๊ด€๋ จ ์‚ฐ์—…์•ˆ์ „๋ณด๊ฑด๋ฒ• ์œ„๋ฐ˜ ๊ณผํƒœ๋ฃŒ, ์›์ž๋ ฅ์•ˆ์ „๋ฒ• ์œ„๋ฐ˜ ๊ณผํƒœ๋ฃŒ ๋“ฑ๋„ ๊ณต์‹œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.\\n- ์‚ผ์„ฑ๋””์Šคํ”Œ๋ ˆ์ด๋Š” ํ™”ํ•™๋ฌผ์งˆ๊ด€๋ฆฌ๋ฒ• ์œ„๋ฐ˜ ์‹œ์ •์กฐ์น˜, ์‚ฐ์—…์•ˆ์ „๋ณด๊ฑด๋ฒ• ์œ„๋ฐ˜ ๋ถ€๋ถ„์ž‘์—…์ค‘์ง€ ๋ช…๋ น ๋“ฑ์„ ๋ฐ›์•˜๊ณ  ์‹œ์ •ํ–ˆ๋‹ค๊ณ  ๊ณต์‹œํ–ˆ์Šต๋‹ˆ๋‹ค.\\n\\n### ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ\\n- ์‚ผ์„ฑ์ „์ž๋Š” ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ๋ฒ• ์œ„๋ฐ˜์œผ๋กœ ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ์œ„์›ํšŒ๋กœ๋ถ€ํ„ฐ ์‹œ์ •์กฐ์น˜, ๊ณผ์ง•๊ธˆ **875.6๋ฐฑ๋งŒ์›**, ๊ณผํƒœ๋ฃŒ **2.4๋ฐฑ๋งŒ์›**์„ ๋ถ€๊ณผ๋ฐ›์•„ ๋‚ฉ๋ถ€ํ–ˆ์Šต๋‹ˆ๋‹ค.\\n- 2025๋…„ 12์›”์—๋„ ๊ฐœ์ธ์ •๋ณด ํŒŒ๊ธฐ ๋ฐ ์ •์ •ยท์‚ญ์ œ ๊ด€๋ จ ์œ„๋ฐ˜์œผ๋กœ ์‹œ์ •์กฐ์น˜์™€ **9.6๋ฐฑ๋งŒ์›**์„ ๋ถ€๊ณผ๋ฐ›์•„ ๋‚ฉ๋ถ€ํ–ˆ์Šต๋‹ˆ๋‹ค.\\n\\n### ๊ณต์ •๊ฑฐ๋ž˜ยทํ•˜๋„๊ธ‰ยท๊ด‘๊ณ ยท์†Œ๋น„์ž๋ณดํ˜ธ\\n- ์‚ผ์„ฑ์ „์ž๋Š” 2024๋…„ ๊ณต์ •๊ฑฐ๋ž˜์œ„์›ํšŒ๋กœ๋ถ€ํ„ฐ ๋Œ€๋ฆฌ์ ๊ฑฐ๋ž˜ ๊ณต์ •ํ™”์— ๊ด€ํ•œ ๋ฒ•๋ฅ  ์œ„๋ฐ˜ ํ˜์˜๋กœ ์‹œ์ •๋ช…๋ น์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.\\n- ์ผ๋ถ€ ์ข…์†ํšŒ์‚ฌ๋Š” ํ•˜๋„๊ธ‰๊ฑฐ๋ž˜ ๊ณต์‹œ ์˜๋ฌด ์œ„๋ฐ˜ ๊ณผํƒœ๋ฃŒ๋ฅผ ๋ถ€๊ณผ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.\\n- ํ•ด์™ธ ์ข…์†ํšŒ์‚ฌ์—์„œ๋Š” ํ—ˆ์œ„๊ด‘๊ณ , ๋ฒ•์  ๋ณด์ฆ๊ธฐ๊ฐ„ ๋‚ด ์ˆ˜๋ฆฌ ์˜๋ฌด ์œ„๋ฐ˜, ์†Œ๋น„์ž ์ œํ’ˆ์ˆ˜๋ฆฌ ๊ฑฐ๋ถ€ ๋“ฑ ์†Œ๋น„์ž๋ณดํ˜ธ ๊ด€๋ จ ์ œ์žฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.\\n- SETK๋Š” ํœด๋Œ€ํฐ ํŒ๋งค๋ชจ๋ธ ํŠน์ • ๊ธฐ๋Šฅ ํ—ˆ์œ„๊ด‘๊ณ ๋กœ ํŠ€๋ฅดํ‚ค์˜ˆ ๋‹น๊ตญ์˜ ๊ณผ์ง•๊ธˆ์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.\\n- SEMAG๋Š” ์žฌํŒ๋งค๊ฐ€๊ฒฉ ์œ ์ง€ ๋“ฑ ๊ณต์ •๊ฒฝ์Ÿ ์œ„๋ฐ˜์œผ๋กœ ํŠ€๋‹ˆ์ง€ ๋ฐ˜๋…์ ์œ„์›ํšŒ๋กœ๋ถ€ํ„ฐ ๊ณผ์ง•๊ธˆ์„ ๋ถ€๊ณผ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.\\n\\n### ์ˆ˜์ถœํ†ต์ œยท์ œ์žฌยท๊ด€์„ธ ๊ด€๋ จ\\n- ์‚ผ์„ฑ๋ฉ”๋””์Šจใˆœ๋Š” ์ƒํ™ฉํ—ˆ๊ฐ€ ๋Œ€์ƒ ์ผ๋ถ€ ํ’ˆ๋ชฉ์„ ์‚ฌ์ „ ํ—ˆ๊ฐ€ ์—†์ด ์ˆ˜์ถœํ•ด ์‚ฐ์—…ํ†ต์ƒ์ž์›๋ถ€ ๊ต์œก๋ช…๋ น์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.\\n- Harman์€ ๋ฐ˜๋คํ•‘ ๊ด€์„ธ๊ฐ€ ์ž˜๋ชป ์ง€๊ธ‰๋˜์—ˆ์Œ์„ ์ธ์ง€ํ–ˆ์œผ๋‚˜ ์ž๋ฐœ์  ์ž์ฒด ๋ณด๊ณ ๋ฅผ ํ•˜์ง€ ์•Š์•„ ๋ฏธ๊ตญ ๋ฒ•๋ฌด๋ถ€๋กœ๋ถ€ํ„ฐ ํ—ˆ์œ„์ฒญ๊ตฌ๋ฒ• ์œ„๋ฐ˜ ๋ฒŒ๊ธˆ **USD 11,800,000**์„ ๋ถ€๊ณผ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.\\n- Harman์€ ํŠน์ • ๊ฑฐ๋ž˜๊ฐ€ ๋ฏธ๊ตญ ์ œ์žฌ ๋Œ€์ƒ๊ตญ๊ฐ€์™€ ๊ด€๋ จ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Œ์„ ์ž๋ฐœ์ ์œผ๋กœ ๋ณด๊ณ ํ–ˆ์œผ๋‚˜, ํ•ด์™ธ์ž์‚ฐํ†ต์ œ๊ตญ์œผ๋กœ๋ถ€ํ„ฐ ๊ณผํƒœ๋ฃŒ **USD 1,454,145**๋ฅผ ๋ถ€๊ณผ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.\\n\\n## 5) ํ™˜๊ฒฝยทํƒ„์†Œ ๊ด€๋ จ ๋ฆฌ์Šคํฌ ๋ฐ ๊ด€๋ฆฌ\\n\\n- ์‚ผ์„ฑ์ „์ž๋Š” ์ •๋ถ€์˜ ์ €ํƒ„์†Œ ๋…น์ƒ‰์„ฑ์žฅ ์ •์ฑ…์— ๋”ฐ๋ผ ์˜จ์‹ค๊ฐ€์Šค ๋ฐฐ์ถœ๋Ÿ‰๊ณผ ์—๋„ˆ์ง€ ์‚ฌ์šฉ๋Ÿ‰์„ ์ •๋ถ€์— ์‹ ๊ณ ํ•˜๊ณ , ์ง€์†๊ฐ€๋Šฅ๋ณด๊ณ ์„œ ๋“ฑ์„ ํ†ตํ•ด ๊ณต๊ฐœํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n- 2025๋…„ ๋ง ๋ณ„๋„ ๊ธฐ์ค€์œผ๋กœ ์ˆ˜์›, ๊ตฌ๋ฏธ, ๊ธฐํฅยทํ™”์„ฑ, ํ‰ํƒ, ์˜จ์–‘, ์ฒœ์•ˆ์‚ฌ์—…์žฅ์ด ๋…น์ƒ‰๊ธฐ์—…์œผ๋กœ ์ง€์ •๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.\\n- 2025๋…„ 12์›” ๋ง ๊ธฐ์ค€ ์ด **6๊ฑด**์˜ ๋…น์ƒ‰๊ธฐ์ˆ  ์œ ํšจ ์ธ์ฆ๊ณผ, ํ•ด๋‹น ๊ธฐ์ˆ ์ด ์ ์šฉ๋œ **121๊ฐœ ๋ชจ๋ธ**์˜ ๋…น์ƒ‰๊ธฐ์ˆ ์ œํ’ˆ ํ™•์ธ ์ธ์ฆ์„ ๋ณด์œ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n- ์‚ผ์„ฑ์ „์ž๋Š” ์˜จ์‹ค๊ฐ€์Šค ์—๋„ˆ์ง€ ๋ชฉํ‘œ๊ด€๋ฆฌ์—…์ฒด์ด์ž, 2015๋…„๋ถ€ํ„ฐ ์˜จ์‹ค๊ฐ€์Šค ๋ฐฐ์ถœ๊ถŒ ํ• ๋‹น๋Œ€์ƒ์—…์ฒด์— ํ•ด๋‹นํ•ฉ๋‹ˆ๋‹ค.\\n- ๋”ฐ๋ผ์„œ ํƒ„์†Œ๋ฐฐ์ถœ ๊ทœ์ œ, ์—๋„ˆ์ง€ ์‚ฌ์šฉ๋Ÿ‰ ๊ด€๋ฆฌ, ๋ฐฐ์ถœ๊ถŒ ๊ด€๋ จ ์ œ๋„ ๋ณ€ํ™”๋Š” ์ง€์†์ ์ธ ๊ด€๋ฆฌ ๋Œ€์ƒ์ž…๋‹ˆ๋‹ค.\\n\\n## ์ข…ํ•ฉ ํŒ๋‹จ\\n\\n์ œ๊ณต ๋ฐœ์ทŒ ๊ธฐ์ค€์œผ๋กœ ๋ณผ ๋•Œ ์‚ผ์„ฑ์ „์ž์˜ ํ•ต์‹ฌ ๋ฆฌ์Šคํฌ๋Š” ํฌ๊ฒŒ ์„ธ ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค.\\n\\n1. **๊ธ€๋กœ๋ฒŒ ์˜์—… ๊ธฐ๋ฐ˜์˜ ๊ธˆ์œต์‹œ์žฅ ๋ฆฌ์Šคํฌ** \\n USD, EUR ๋“ฑ ํ™˜์œจ, ๊ธˆ๋ฆฌ, ์ง€๋ถ„์ƒํ’ˆ ์ฃผ๊ฐ€ ๋ณ€๋™์— ๋…ธ์ถœ๋˜์–ด ์žˆ์œผ๋ฉฐ, ํ†ตํ™”์„ ๋„์™€ ์ž๊ธˆ๊ด€๋ฆฌ ์ฒด๊ณ„๋กœ ์™„ํ™”ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n2. **๋Œ€๊ทœ๋ชจ ํˆฌ์ž๊ธฐ์—… ํŠน์„ฑ์ƒ ์œ ๋™์„ฑยท์ž๋ณธ๊ด€๋ฆฌ ์ค‘์š”์„ฑ** \\n ์ƒ์‚ฐยท์—ฐ๊ตฌ๊ฐœ๋ฐœยท์šด์ „์ž๋ณธยท๋ฐฐ๋‹น ์ˆ˜์š”๊ฐ€ ํฌ๊ธฐ ๋•Œ๋ฌธ์— ์ ์ • ์œ ๋™์„ฑ ๊ด€๋ฆฌ๊ฐ€ ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค. ๋‹ค๋งŒ Cash Pooling, ๋ณธ์‚ฌ ์ง€๊ธ‰๋ณด์ฆ ๊ธฐ๋ฐ˜ ํ•ด์™ธ ์ฐจ์ž…ํ•œ๋„, Aa2/AA- ์‹ ์šฉ๋“ฑ๊ธ‰์€ ์™„์ถฉ ์š”์ธ์ž…๋‹ˆ๋‹ค.\\n\\n3. **์ค€๋ฒ•ยท๊ทœ์ œยทํ‰ํŒ ๋ฆฌ์Šคํฌ์˜ ๋ฒ”์œ„ ํ™•๋Œ€** \\n ์‚ฐ์—…์•ˆ์ „, ๊ฐœ์ธ์ •๋ณด, ๊ณต์ •๊ฑฐ๋ž˜, ํ™˜๊ฒฝ, ์ˆ˜์ถœํ†ต์ œ, ํ•ด์™ธ ์ œ์žฌยท๊ด€์„ธ, ์†Œ๋น„์ž๋ณดํ˜ธ ๋“ฑ ๋‹ค์–‘ํ•œ ์˜์—ญ์—์„œ ์ œ์žฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์€ ๋ฒŒ๊ธˆยท๊ณผํƒœ๋ฃŒ ๋‚ฉ๋ถ€ ๋ฐ ์žฌ๋ฐœ๋ฐฉ์ง€์ฑ… ์‹œํ–‰์œผ๋กœ ๊ณต์‹œ๋˜์–ด ์žˆ์œผ๋‚˜, ํˆฌ์ž์ž ๋ณดํ˜ธ ๊ด€์ ์—์„œ๋Š” ๋‚ด๋ถ€ํ†ต์ œ์™€ ๊ธ€๋กœ๋ฒŒ ๊ทœ์ œ ๋Œ€์‘ ์—ญ๋Ÿ‰์„ ์ง€์† ์ ๊ฒ€ํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.\",\"rawSectionsFile\":\".dexter/tool-results/kr-filing-00126380-20260310002820.json\",\"note\":\"summary is grounded in the report narrative. Use read_file on rawSectionsFile for the full extracted sections.\"},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/document.xml?rcept_no=20260310002820\",\"https://opendart.fss.or.kr/api/list.json?corp_code=00126380&bgn_de=20241201&end_de=20260531&pblntf_ty=A&page_count=100&page_no=1&sort=date&sort_mth=desc\"]}" + } + ] +} diff --git a/scripts/kr-eval/questions.ts b/scripts/kr-eval/questions.ts index 6297b28f0..16f905fbd 100644 --- a/scripts/kr-eval/questions.ts +++ b/scripts/kr-eval/questions.ts @@ -34,6 +34,24 @@ export const KR_EVAL_QUESTIONS: KrEvalQuestion[] = [ dimensions: ['earnings_yoy', 'grounding'], notes: '๋‹จ์ผ ์ข…๋ชฉ ํŽ€๋”๋ฉ˜ํ„ธ โ€” ์†์ต + ์ „๋…„๋Œ€๋น„ ์ˆซ์ž๊ฐ€ ํ•ต์‹ฌ. grounding์œผ๋กœ YoY๊ฐ€ ํˆด ๋ฐ์ดํ„ฐ์— ์‹ค์ œ ๊ทผ๊ฑฐํ•˜๋Š”์ง€ ๊ต์ฐจ๊ฒ€์ฆ.', }, + { + id: 'business-overview-samsung', + query: '์‚ผ์„ฑ์ „์ž(005930) ์‚ฌ์—…๋ณด๊ณ ์„œ ๋ณด๋ฉด ์‚ฌ์—…์ด ์–ด๋–ค ์‚ฌ์—…๋ถ€๋ฌธ์œผ๋กœ ๊ตฌ์„ฑ๋ผ ์žˆ์–ด?', + ticker: '005930', + expectedTools: ['read_filings_kr'], + requiredTools: ['read_filings_kr'], + dimensions: ['grounding'], + notes: '์‚ฌ์—…๋ณด๊ณ ์„œ ใ€ŒII. ์‚ฌ์—…์˜ ๋‚ด์šฉใ€ ๋‚ด๋Ÿฌํ‹ฐ๋ธŒ ์ถ”์ถœ โ†’ ์‚ฌ์—…๋ถ€๋ฌธ/์ œํ’ˆ ๊ตฌ์„ฑ. ํ™˜๊ฐ ์—†์ด ๋ณธ๋ฌธ์— ๊ทผ๊ฑฐํ•ด ์„ค๋ช…ํ•˜๋Š”์ง€(grounding)๋กœ ์ฑ„์ .', + }, + { + id: 'key-risks-samsung', + query: '์‚ผ์„ฑ์ „์ž ์ฃผ์š” ๋ฆฌ์Šคํฌ๊ฐ€ ๋ญ์•ผ?', + ticker: '005930', + expectedTools: ['read_filings_kr'], + requiredTools: ['read_filings_kr'], + dimensions: ['grounding'], + notes: '๋ฆฌ์Šคํฌ๋Š” ๋‹จ์ผ ์„น์…˜์ด ์•„๋‹ˆ๋ผ II.5 ์œ„ํ—˜๊ด€๋ฆฌ + XI ํˆฌ์ž์ž๋ณดํ˜ธ๋กœ ๋ถ„์‚ฐ โ†’ read_filings_kr๊ฐ€ ๋ณธ๋ฌธ ๊ทผ๊ฑฐ๋กœ ์ข…ํ•ฉํ•˜๋Š”์ง€(grounding). ์šด์˜/์žฌ๋ฌด ๋ฆฌ์Šคํฌ ๋‚ด๋Ÿฌํ‹ฐ๋ธŒ๋ผ governance(์ง€๋ฐฐ๊ตฌ์กฐ valuation) ์ฐจ์›์€ ์ œ์™ธ.', + }, { id: 'samsung-synthesis', query: '์‚ผ์„ฑ์ „์ž ์ง€๊ธˆ ํˆฌ์ž ๊ด€์ ์—์„œ ์–ด๋•Œ?', diff --git a/src/agent/prompts.ts b/src/agent/prompts.ts index 6d5109a5e..b0f1afd8a 100644 --- a/src/agent/prompts.ts +++ b/src/agent/prompts.ts @@ -290,7 +290,7 @@ ${toolDescriptions} ## Tool Usage Policy - Call get_financials or get_market_data ONCE with the full natural language query โ€” they handle multi-company/multi-metric requests internally. Do NOT break up queries into multiple calls. -- 6-digit numeric tickers (e.g. 005930, 035420) are Korean stocks โ€” use get_financials_kr for fundamentals, get_filings_kr for DART disclosures, get_large_holders_kr for 5%-rule major shareholders, get_insider_trades_kr for executive/insider ownership, get_short_balance_kr for ๊ณต๋งค๋„ ์ž”๊ณ  (short interest), get_foreign_ownership_kr for ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ (foreign ownership), and get_nps_holdings for ๊ตญ๋ฏผ์—ฐ๊ธˆ (National Pension Service) holdings. ASCII tickers (AAPL, MSFT) use the US tools. +- 6-digit numeric tickers (e.g. 005930, 035420) are Korean stocks โ€” use get_financials_kr for fundamentals, get_filings_kr for DART disclosures, get_large_holders_kr for 5%-rule major shareholders, get_insider_trades_kr for executive/insider ownership, get_short_balance_kr for ๊ณต๋งค๋„ ์ž”๊ณ  (short interest), get_foreign_ownership_kr for ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ (foreign ownership), get_nps_holdings for ๊ตญ๋ฏผ์—ฐ๊ธˆ (National Pension Service) holdings, and read_filings_kr for the report narrative (์‚ฌ์—… ๊ตฌ์„ฑยท์ฃผ์š” ๋ฆฌ์Šคํฌยท๊ฒฝ์˜์ง„๋‹จ MD&A from DART ์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ). ASCII tickers (AAPL, MSFT) use the US tools. - Only use web_fetch when headlines are insufficient (need quotes, deal specifics, earnings details). - Tool results are automatically capped. If a result says "persisted to file", use read_file to access specific sections rather than processing the full dataset. - Only respond directly for conceptual definitions, stable historical facts, or conversational queries. diff --git a/src/cli.ts b/src/cli.ts index 8fdbacbe0..588a77e05 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -75,6 +75,10 @@ function summarizeToolResult(tool: string, args: Record, result const n = Array.isArray(parsed.data.filings) ? parsed.data.filings.length : 0; return `Found ${n} filings`; } + if (tool === 'read_filings_kr') { + const n = Array.isArray(parsed.data.sections_found) ? parsed.data.sections_found.length : 0; + return `Read ${n} section${n !== 1 ? 's' : ''}`; + } if (tool === 'get_large_holders_kr') { const n = Array.isArray(parsed.data.holders) ? parsed.data.holders.length : 0; return `Found ${n} holders`; diff --git a/src/tools/finance-kr/__fixtures__/dsd-005930-business-section.xml b/src/tools/finance-kr/__fixtures__/dsd-005930-business-section.xml new file mode 100644 index 000000000..c9c8d7ae0 --- /dev/null +++ b/src/tools/finance-kr/__fixtures__/dsd-005930-business-section.xml @@ -0,0 +1,633 @@ + + +II. ์‚ฌ์—…์˜ ๋‚ด์šฉ +

+ + + +1. ์‚ฌ์—…์˜ ๊ฐœ์š” +

+

๋‹น์‚ฌ๋Š” ๋ณธ์‚ฌ๋ฅผ ๊ฑฐ์ ์œผ๋กœ ํ•œ๊ตญ๊ณผ DX ๋ถ€๋ฌธ ์‚ฐํ•˜ ํ•ด์™ธ 9๊ฐœ ์ง€์—ญ์ด๊ด„ ๋ฐ DS ๋ถ€๋ฌธ ์‚ฐํ•˜ ํ•ด์™ธ 5๊ฐœ ์ง€์—ญ์ด๊ด„์˜ ์ƒ์‚ฐใ†ํŒ๋งค๋ฒ•์ธ, SDC ๋ฐ Harman ์‚ฐํ•˜ ์ข…์†๊ธฐ์—… ๋“ฑ 308๊ฐœ์˜ ์ข…์†๊ธฐ์—…์œผ๋กœ ๊ตฌ์„ฑ๋œ ๊ธ€๋กœ๋ฒŒ ์ „์ž ๊ธฐ์—…์ž…๋‹ˆ๋‹ค.

+

์‚ฌ์—…๋ณ„๋กœ ๋ณด๋ฉด, Set ์‚ฌ์—…์€ DX(Device eXperience) ๋ถ€๋ฌธ์ด TV๋ฅผ ๋น„๋กฏํ•˜์—ฌ ๋ชจ๋‹ˆํ„ฐ, ๋ƒ‰์žฅ๊ณ , ์„ธํƒ๊ธฐ, ์—์–ด์ปจ, ์Šค๋งˆํŠธํฐ, ๋„คํŠธ์›Œํฌ์‹œ์Šคํ…œ, PC ๋“ฑ์„ ์ƒ์‚ฐใ†ํŒ๋งคํ•˜๋ฉฐ, ๋ถ€ํ’ˆ ์‚ฌ์—…์€ DS(Device Solutions) ๋ถ€๋ฌธ์—์„œ DRAM, NAND Flash, ๋ชจ๋ฐ”์ผAP ๋“ฑ์˜ ์ œํ’ˆ์„ ์ƒ์‚ฐใ†ํŒ๋งคํ•˜๊ณ , SDC๊ฐ€ ์Šค๋งˆํŠธํฐ์šฉ OLED ํŒจ๋„ ๋“ฑ์„ ์ƒ์‚ฐใ†ํŒ๋งคํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.๋˜ํ•œ, Harman์—์„œ๋Š” ๋””์ง€ํ„ธ ์ฝ•ํ•(Digital Cockpit), ์นด์˜ค๋””์˜ค ๋“ฑ ์ „์žฅ์ œํ’ˆ๊ณผ ํฌํ„ฐ๋ธ”/์‚ฌ์šด๋“œ๋ฐ” ์Šคํ”ผ์ปค ๋“ฑ ์ปจ์Šˆ๋จธ์˜ค๋””์˜ค ์ œํ’ˆ ๋“ฑ์„ ๊ฐœ๋ฐœ, ์ƒ์‚ฐ, ํŒ๋งคํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.โ˜ž ๋ถ€๋ฌธ๋ณ„ ์‚ฌ์—…์— ๊ด€ํ•œ ์ž์„ธํ•œ ์‚ฌํ•ญ์€ '7. ๊ธฐํƒ€ ์ฐธ๊ณ ์‚ฌํ•ญ'์˜ '๋‹ค. ์‚ฌ์—…๋ถ€๋ฌธ๋ณ„ ํ˜„ํ™ฉ'๊ณผ '๋ผ. ์‚ฌ์—…๋ถ€๋ฌธ๋ณ„ ์š”์•ฝ ์žฌ๋ฌด ํ˜„ํ™ฉ' ํ•ญ๋ชฉ์„ ์ฐธ๊ณ ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

+

์ง€์—ญ๋ณ„๋กœ ๋ณด๋ฉด, ๊ตญ๋‚ด์—์„œ๋Š” DX ๋ถ€๋ฌธ ๋ฐ DS ๋ถ€๋ฌธ ๋“ฑ์„ ์ด๊ด„ํ•˜๋Š” ๋ณธ์‚ฌ์™€ 34๊ฐœ์˜ ์ข…์†๊ธฐ์—…์ด ์‚ฌ์—…์„ ์šด์˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณธ์‚ฌ๋Š” ์ˆ˜์›, ๊ตฌ๋ฏธ, ๊ด‘์ฃผ, ๊ธฐํฅ, ํ™”์„ฑ, ํ‰ํƒ์‚ฌ์—…์žฅ ๋“ฑ์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์œผ๋ฉฐ, ๊ตญ๋‚ด ์ข…์†๊ธฐ์—…์€ ๋””์Šคํ”Œ๋ ˆ์ด ํŒจ๋„์„ ์ƒ์‚ฐํ•˜๋Š” ์‚ผ์„ฑ๋””์Šคํ”Œ๋ ˆ์ดใˆœ์™€ ๊ตญ๋‚ด ๋Œ€๋ฆฌ์  ํŒ๋งค๋ฅผ ์ „๋‹ดํ•˜๋Š” ์‚ผ์„ฑ์ „์žํŒ๋งคใˆœ, ์ œํ’ˆ ์ˆ˜๋ฆฌ ์„œ๋น„์Šค๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์‚ผ์„ฑ์ „์ž์„œ๋น„์Šคใˆœ, ์ œํ’ˆ ์šด์†ก์„ ๋‹ด๋‹นํ•˜๋Š” ์‚ผ์„ฑ์ „์ž๋กœ์ง€ํ…ใˆœ ๋“ฑ์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

+

+

ํ•ด์™ธ(๋ฏธ์ฃผ, ์œ ๋Ÿฝใ†CIS, ์ค‘๋™ใ†์•„ํ”„๋ฆฌ์นด, ์•„์‹œ์•„ ๋“ฑ์ง€)์—์„œ๋Š” ์ƒ์‚ฐ, ํŒ๋งค, ์—ฐ๊ตฌํ™œ๋™ ๋“ฑ์„ ๋‹ด๋‹นํ•˜๋Š” 274๊ฐœ์˜ ๋น„์ƒ์žฅ ์ข…์†๊ธฐ์—…์ด ์šด์˜๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.๋ฏธ์ฃผ์—๋Š” TV, ์Šค๋งˆํŠธํฐ ๋“ฑ Set์ œํ’ˆ์˜ ๋ฏธ๊ตญํŒ๋งค๋ฅผ ๋‹ด๋‹นํ•˜๋Š” SEA(New Jersey, USA),TV ์ƒ์‚ฐ์„ ๋‹ด๋‹นํ•˜๋Š” SII(California, USA), ๋ฐ˜๋„์ฒดใ†๋””์Šคํ”Œ๋ ˆ์ด ํŒจ๋„ ํŒ๋งค๋ฅผ ๋‹ด๋‹นํ•˜๋Š” SSI(California, USA), ๋ฐ˜๋„์ฒด ์ƒ์‚ฐ์„ ๋‹ด๋‹นํ•˜๋Š” SAS(Texas, USA), Set์ œํ’ˆ ๋ณตํ•ฉ ์ƒ์‚ฐ๋ฒ•์ธ SEDA(Brazil), ์ „์žฅ๋ถ€ํ’ˆ์‚ฌ์—… ๋“ฑ์„ ๋‹ด๋‹นํ•˜๋Š” Harman(Connecticut, USA)๋“ฑ์„ ํฌํ•จํ•˜์—ฌ ์ด 66๊ฐœ์˜ ํŒ๋งคใ†์ƒ์‚ฐ ๋“ฑ์„ ๋‹ด๋‹นํ•˜๋Š” ๋ฒ•์ธ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

+

์œ ๋Ÿฝใ†CIS์—๋Š” SEUK(UK), SEG(Germany), SEF(France), SEI(Italy) ๋“ฑ Set์ œํ’ˆ ํŒ๋งค๋ฒ•์ธ๊ณผ SEH(Hungary) ๋“ฑ TV ์ƒ์‚ฐ๋ฒ•์ธ, ๊ฐ€์ „์ œํ’ˆ ์ƒ์‚ฐ๋ฒ•์ธ SEPM(Poland) ๋“ฑ์„ ํฌํ•จํ•˜์—ฌ ์ด 114๊ฐœ์˜ ๋ฒ•์ธ์ด ์šด์˜๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.์ค‘๋™ใ†์•„ํ”„๋ฆฌ์นด์—๋Š” SGE(UAE), SSA(South Africa) ๋“ฑ Set์ œํ’ˆ ํŒ๋งค๋ฒ•์ธ๊ณผ SEEG(Egypt), SSAP(South Africa) ๋“ฑ TV ์ƒ์‚ฐ๋ฒ•์ธ์„ ํฌํ•จํ•œ ์ด 21๊ฐœ ๋ฒ•์ธ์ด ์šด์˜๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.์•„์‹œ์•„(์ค‘๊ตญ ์ œ์™ธ)์—๋Š” SESP(Singapore), SEAU(Australia), SEPCO(Philippines), SME(Malaysia) ๋“ฑ ํŒ๋งค๋ฒ•์ธ๊ณผ ์Šค๋งˆํŠธํฐ ๋“ฑ ์ƒ์‚ฐ๋ฒ•์ธ SEVใ†SEVT(Vietnam), TV ๋“ฑ ์ƒ์‚ฐ๋ฒ•์ธ SEHC(Vietnam), ๋””์Šคํ”Œ๋ ˆ์ด ํŒจ๋„ ์ƒ์‚ฐ๋ฒ•์ธ SDV(Vietnam), ๋ณตํ•ฉ ์ƒ์‚ฐ๋ฒ•์ธ SIEL(India) ๋“ฑ์„ ํฌํ•จํ•œ ์ด 36๊ฐœ์˜ ๋ฒ•์ธ์ด ์šด์˜๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.์ค‘๊ตญ์—๋Š” SCIC(Beijing), SEHK(Hong Kong) ๋“ฑ Set์ œํ’ˆ ํŒ๋งค๋ฒ•์ธ๊ณผ SSS(Shanghai),SSCX(Xian) ๋“ฑ ๋ฐ˜๋„์ฒดใ†๋””์Šคํ”Œ๋ ˆ์ด ํŒจ๋„ ํŒ๋งค๋ฒ•์ธ, SSEC(Suzhou) ๋“ฑ Set์ œํ’ˆ ์ƒ์‚ฐ๋ฒ•์ธ, SCS(Xian) ๋“ฑ ๋ฐ˜๋„์ฒด ์ƒ์‚ฐ๋ฒ•์ธ์„ ํฌํ•จํ•˜์—ฌ ์ด 37๊ฐœ์˜ ๋ฒ•์ธ์ด ์šด์˜๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

2025๋…„ ๋‹น์‚ฌ์˜ ๋งค์ถœ์€ 333์กฐ 6,059์–ต์›์œผ๋กœ ์ „๋…„ ๋™๊ธฐ ๋Œ€๋น„ 10.9% ์ฆ๊ฐ€ํ•˜์˜€์œผ๋ฉฐ, ์ฃผ์š” ๋งค์ถœ์ฒ˜๋Š” Alphabet, Apple, Deutsche Telekom, Hong Kong Techtronics, Supreme Electronics ๋“ฑ(์•ŒํŒŒ๋ฒณ์ˆœ)์ด ์žˆ์Šต๋‹ˆ๋‹ค.

+

+ +
+ + +2. ์ฃผ์š” ์ œํ’ˆ ๋ฐ ์„œ๋น„์Šค +

+ +

+๊ฐ€. ์ฃผ์š” ์ œํ’ˆ ๋งค์ถœ +๋‹น์‚ฌ๋Š” TV, ๋ƒ‰์žฅ๊ณ , ์„ธํƒ๊ธฐ, ์—์–ด์ปจ, ์Šค๋งˆํŠธํฐ ๋“ฑ ์™„์ œํ’ˆ๊ณผ DRAM, NAND Flash, ๋ชจ๋ฐ”์ผAP ๋“ฑ ๋ฐ˜๋„์ฒด ๋ถ€ํ’ˆ ๋ฐ ์Šค๋งˆํŠธํฐ์šฉ OLED ํŒจ๋„ ๋“ฑ์„ ์ƒ์‚ฐใ†ํŒ๋งคํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์•„์šธ๋Ÿฌ Harman์„ ํ†ตํ•ด ๋””์ง€ํ„ธ ์ฝ•ํ•, ์นด์˜ค๋””์˜ค, ํฌํ„ฐ๋ธ” ์Šคํ”ผ์ปค ๋“ฑ์„ ์ƒ์‚ฐใ†ํŒ๋งคํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.2025๋…„(์ œ57๊ธฐ) ๋งค์ถœ์€ DX ๋ถ€๋ฌธ์ด 187์กฐ 9,673์–ต์›(56.3%), DS ๋ถ€๋ฌธ์ด 130์กฐ 1,282์–ต์›(39%)์ด๋ฉฐ, SDC๊ฐ€ 29์กฐ 8,417์–ต์›(8.9%), Harman์€ 15์กฐ 7,833์–ต์›(4.7%)์ž…๋‹ˆ๋‹ค. +

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+

+ +

+๋‚˜. ์ฃผ์š” ์ œํ’ˆ ๋“ฑ์˜ ๊ฐ€๊ฒฉ ๋ณ€๋™ ํ˜„ํ™ฉ +2025๋…„ TV์˜ ํ‰๊ท  ํŒ๋งค๊ฐ€๊ฒฉ์€ ์ „๋…„ ์—ฐ๊ฐ„ ํ‰๊ท  ๋Œ€๋น„ ์•ฝ 5% ํ•˜๋ฝํ•˜์˜€์œผ๋ฉฐ, ์Šค๋งˆํŠธํฐ์€ ์ „๋…„ ์—ฐ๊ฐ„ ํ‰๊ท  ๋Œ€๋น„ ์•ฝ 3% ํ•˜๋ฝํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ฉ”๋ชจ๋ฆฌ ํ‰๊ท  ํŒ๋งค๊ฐ€๊ฒฉ์€ ์ „๋…„ ์—ฐ๊ฐ„ ํ‰๊ท  ๋Œ€๋น„ ์•ฝ 14% ์ƒ์Šนํ•˜์˜€์œผ๋ฉฐ, ์Šค๋งˆํŠธํฐ์šฉ OLED ํŒจ๋„์€ ์•ฝ 6% ํ•˜๋ฝํ•˜์˜€์Šต๋‹ˆ๋‹ค. ํ•œํŽธ ๋””์ง€ํ„ธ ์ฝ•ํ•์˜ ํ‰๊ท  ํŒ๋งค๊ฐ€๊ฒฉ์€ ์ „๋…„ ์—ฐ๊ฐ„ ํ‰๊ท ๊ณผ ์œ ์‚ฌํ•œ ์ˆ˜์ค€์ž…๋‹ˆ๋‹ค +

+ +
+ + +3. ์›์žฌ๋ฃŒ ๋ฐ ์ƒ์‚ฐ์„ค๋น„ +

+ +

+๊ฐ€. ์ฃผ์š” ์›์žฌ๋ฃŒ ํ˜„ํ™ฉ +๋‹น์‚ฌ์˜ ์ฃผ์š” ์›์žฌ๋ฃŒ๋กœ DX ๋ถ€๋ฌธ์€ ๋ชจ๋ฐ”์ผAP ์†”๋ฃจ์…˜, Camera Module ๋“ฑ์„ Qualcomm, ์‚ผ์„ฑ์ „๊ธฐใˆœ ๋“ฑ์—์„œ ๊ณต๊ธ‰๋ฐ›๊ณ  ์žˆ์œผ๋ฉฐ, TVใ†๋ชจ๋‹ˆํ„ฐ์šฉ ๋””์Šคํ”Œ๋ ˆ์ด ํŒจ๋„ ๋“ฑ์„ CSOT ๋“ฑ์œผ๋กœ๋ถ€ํ„ฐ ๊ณต๊ธ‰๋ฐ›๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. DS ๋ถ€๋ฌธ์€ Chemical, Wafer ๋“ฑ์„ ์†”๋ธŒ๋ ˆ์ธใˆœ, SILTRONIC ๋“ฑ์œผ๋กœ๋ถ€ํ„ฐ, SDC๋Š” FPCA, Cover Glass ๋“ฑ์„ ใˆœ๋น„์—์ด์น˜, Apple ๋“ฑ์œผ๋กœ๋ถ€ํ„ฐ ๊ณต๊ธ‰๋ฐ›๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. Harman์€ MCU(Micro Controller Unit)๋ฅผ ํฌํ•จํ•œ SOC(System-On-Chip), ํ†ต์‹  ๋ชจ๋“ˆ ๋“ฑ์„ NVIDIA, WNC ๋“ฑ์—์„œ ๊ณต๊ธ‰๋ฐ›๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+

+ + +

+๋‚˜. ์ฃผ์š” ์›์žฌ๋ฃŒ ๊ฐ€๊ฒฉ ๋ณ€๋™ ์ถ”์ด +DX ๋ถ€๋ฌธ์˜ ์ฃผ์š” ์›์žฌ๋ฃŒ์ธ ๋ชจ๋ฐ”์ผAP ์†”๋ฃจ์…˜ ๊ฐ€๊ฒฉ์€ ์ „๋…„ ์—ฐ๊ฐ„ํ‰๊ท  ๋Œ€๋น„ ์•ฝ 4% ์ƒ์Šนํ•˜์˜€๊ณ , Camera Module ๊ฐ€๊ฒฉ์€ ์ „๋…„ ์—ฐ๊ฐ„ํ‰๊ท ๊ณผ ์œ ์‚ฌํ•œ ์ˆ˜์ค€์ด๋ฉฐ, TVใ†๋ชจ๋‹ˆํ„ฐ์šฉ ๋””์Šคํ”Œ๋ ˆ์ด ํŒจ๋„ ๊ฐ€๊ฒฉ์€ ์•ฝ 3% ํ•˜๋ฝํ•˜์˜€์Šต๋‹ˆ๋‹ค. DS ๋ถ€๋ฌธ์˜ ์ฃผ์š” ์›์žฌ๋ฃŒ ์ค‘ ๋ฐ˜๋„์ฒด Wafer ๊ฐ€๊ฒฉ์€ ์ „๋…„ ์—ฐ๊ฐ„ํ‰๊ท  ๋Œ€๋น„ ์•ฝ 10% ํ•˜๋ฝํ•˜์˜€์œผ๋ฉฐ, SDC์˜ FPCA ๊ฐ€๊ฒฉ์€ ์•ฝ 6% ์ƒ์Šน, ๊ฐ•ํ™”์œ ๋ฆฌ์šฉ Cover Glass ๊ฐ€๊ฒฉ์€ ์•ฝ 12% ์ƒ์Šนํ•˜์˜€์Šต๋‹ˆ๋‹ค. Harman์˜ ์›์žฌ๋ฃŒ ์ค‘ SOC(System-On-Chip) ๊ฐ€๊ฒฉ์€ ์ „๋…„ ์—ฐ๊ฐ„ํ‰๊ท  ๋Œ€๋น„ ์•ฝ 1% ํ•˜๋ฝํ•˜์˜€์œผ๋ฉฐ, ํ†ต์‹  ๋ชจ๋“ˆ์€ ์•ฝ 1% ์ƒ์Šนํ•˜์˜€์Šต๋‹ˆ๋‹ค. +

+ +
[table omitted in fixture]
+

+ +

+๋‹ค. ์ƒ์‚ฐ๋Šฅ๋ ฅ, ์ƒ์‚ฐ์‹ค์ , ๊ฐ€๋™๋ฅ  +(์ƒ์‚ฐ๋Šฅ๋ ฅ) +

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+

DX ๋ถ€๋ฌธ์˜ ์ฃผ์š” ํ’ˆ๋ชฉ๋ณ„ ์ƒ์‚ฐ๋Šฅ๋ ฅ์€ 'ํ‰๊ท  ๋ผ์ธ ์ˆ˜'ร—'์‹œ๊ฐ„๋‹น ํ‰๊ท  ์ƒ์‚ฐ์‹ค์ 'ร—'์ผ ํ‰๊ท  ๊ฐ€๋™์‹œ๊ฐ„'ร—'ํ‘œ์ค€ ๊ฐ€๋™์ผ์ˆ˜'๋กœ ์‚ฐ์ถœํ•˜์˜€์œผ๋ฉฐ, DS ๋ถ€๋ฌธ์˜ ๋ฉ”๋ชจ๋ฆฌ ์ƒ์‚ฐ๋Šฅ๋ ฅ์€ 1Gbํ™˜์‚ฐ ๊ธฐ์ค€์œผ๋กœ ํ™˜์‚ฐ์ƒ์‚ฐ์‹ค์ (ํŒจํ‚ค์ง€OUT ๊ธฐ์ค€)์„ ๊ฐ€๋™๋ฅ ๋กœ ๋‚˜๋ˆ ์„œ ์ƒ์‚ฐ๋Šฅ๋ ฅ์„ ์‚ฐ์ถœํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. SDC์˜ ๊ฒฝ์šฐ ๋ผ์ธ๋ณ„ ์ƒ์‚ฐ ๊ฐ€๋Šฅํ•œ ์ œํ’ˆ์˜ ์ด ๋ฉด์ ์„ 8์„ธ๋Œ€ Glass(2,200ร—2,500mm)๋กœ ํ™˜์‚ฐํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, Harman์˜ ๋””์ง€ํ„ธ ์ฝ•ํ• ์ƒ์‚ฐ๋Šฅ๋ ฅ์€ '๊ฐ ๊ฑฐ๋ž˜์ฒ˜ใ†์ œํ’ˆ๋ณ„ ์ƒ์‚ฐ์…€(์กฐ๋ฆฝ, ํ…Œ์ŠคํŠธ) ์ˆ˜'ร—'๊ฐ ์ƒ์‚ฐ์…€๋ณ„ ์‹œ๊ฐ„๋‹น ์ƒ์‚ฐ๋Šฅ๋ ฅ'ร—'1์ผ ํ‘œ์ค€ ์ƒ์‚ฐ์‹œ๊ฐ„'ร—'ํ‘œ์ค€์ƒ์‚ฐ์ผ์ˆ˜'๋กœ ์‚ฐ์ถœํ•˜์˜€์Šต๋‹ˆ๋‹ค.

+ + +

+(์ƒ์‚ฐ์‹ค์ ) +2025๋…„(์ œ57๊ธฐ) DX ๋ถ€๋ฌธ์˜ TV, ๋ชจ๋‹ˆํ„ฐ ๋“ฑ ์ƒ์‚ฐ์‹ค์ ์€ 44,361์ฒœ๋Œ€์ด๋ฉฐ ๋ฉ•์‹œ์ฝ”, ๋ฒ ํŠธ๋‚จ, ๋ธŒ๋ผ์งˆ, ํ—๊ฐ€๋ฆฌ ๋“ฑ ์„ธ๊ณ„ ๊ฐ ์ง€์—ญ์—์„œ ์ƒ์‚ฐ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์Šค๋งˆํŠธํฐ ๋“ฑ ์ƒ์‚ฐ์‹ค์ ์€ 214,259์ฒœ๋Œ€์ด๋ฉฐ ํ•œ๊ตญ(๊ตฌ๋ฏธ), ๋ฒ ํŠธ๋‚จ, ์ธ๋„, ๋ธŒ๋ผ์งˆ ๋“ฑ ์ง€์—ญ์—์„œ ์ƒ์‚ฐ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. DS ๋ถ€๋ฌธ์˜ ๋ฉ”๋ชจ๋ฆฌ ์ƒ์‚ฐ์‹ค์ ์€ 2,245,908๋ฐฑ๋งŒ๊ฐœ(1Gb ํ™˜์‚ฐ ๊ธฐ์ค€)์ด๋ฉฐ ํ•œ๊ตญ(ํ™”์„ฑ, ํ‰ํƒ ๋“ฑ), ์ค‘๊ตญ์—์„œ ์ƒ์‚ฐํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. SDC์˜ ๋””์Šคํ”Œ๋ ˆ์ด ํŒจ๋„ ์ƒ์‚ฐ์‹ค์ ์€ 1,851์ฒœ๊ฐœ(8์„ธ๋Œ€ Glass ํ™˜์‚ฐ ๊ธฐ์ค€)์ด๋ฉฐ ํ•œ๊ตญ(์ฒœ์•ˆ, ์•„์‚ฐ) ๋“ฑ์—์„œ ์ƒ์‚ฐ ์ค‘์ž…๋‹ˆ๋‹ค. Harman์˜ ๋””์ง€ํ„ธ ์ฝ•ํ• ์ƒ์‚ฐ์‹ค์ ์€ 5,897์ฒœ๊ฐœ์ด๋ฉฐ, ์ฃผ๋กœ ๋ฉ•์‹œ์ฝ”, ํ—๊ฐ€๋ฆฌ, ์ค‘๊ตญ ๋“ฑ์—์„œ ์ƒ์‚ฐ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+

+ + +

+(๊ฐ€๋™๋ฅ ) +๋‹น์‚ฌ DX ๋ถ€๋ฌธ์˜ 2025๋…„(์ œ57๊ธฐ) ๊ฐ€๋™๋ฅ ์€ ์ƒ์‚ฐ๋Šฅ๋ ฅ ๋Œ€๋น„ ์ƒ์‚ฐ์‹ค์ ์œผ๋กœ ์‚ฐ์ถœํ•˜์˜€์œผ๋ฉฐ, TV, ๋ชจ๋‹ˆํ„ฐ ๋“ฑ์€ 78.8%, ์Šค๋งˆํŠธํฐ ๋“ฑ์€ 79.3%์ž…๋‹ˆ๋‹ค. +

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+

DS ๋ถ€๋ฌธ์˜ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์—…๊ณผ SDC์˜ ๋””์Šคํ”Œ๋ ˆ์ด ํŒจ๋„ ์‚ฌ์—…์€ 24์‹œ๊ฐ„ 3๊ต๋Œ€ ์ž‘์—…์„ ์‹ค์‹œํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, 2025๋…„(์ œ57๊ธฐ) ๊ฐ€๋™์ผ์€ ํœด์ผ์„ ํฌํ•จํ•˜์—ฌ ์ด 365์ผ์ž…๋‹ˆ๋‹ค. ๊ฐ€๋™๋ฅ ์€ ์ƒ์‚ฐ๋ผ์ธ๋ณ„ ๊ฐ€๋™ ๊ฐ€๋Šฅ์‹œ๊ฐ„ (๊ฐ€๋™์ผ ร—์ƒ์‚ฐ๋ผ์ธ ์ˆ˜ ร—24์‹œ๊ฐ„) ๋Œ€๋น„ ์‹ค์ œ ๊ฐ€๋™ ์‹œ๊ฐ„์œผ๋กœ ์‚ฐ์ถœํ•˜์˜€์Šต๋‹ˆ๋‹ค.

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+

Harman์˜ 2025๋…„(์ œ57๊ธฐ) ๊ฐ€๋™๋ฅ ์€ ์ƒ์‚ฐ๋Šฅ๋ ฅ ๋Œ€๋น„ ์ƒ์‚ฐ์‹ค์ ์œผ๋กœ ์‚ฐ์ถœํ•˜์˜€์œผ๋ฉฐ 75.4%์ž…๋‹ˆ๋‹ค.

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+

+ + +

+๋ผ. ์ƒ์‚ฐ์„ค๋น„ ๋ฐ ํˆฌ์ž ํ˜„ํ™ฉ ๋“ฑ + +(์ƒ์‚ฐ๊ณผ ์˜์—…์— ์ค‘์š”ํ•œ ์‹œ์„ค ๋ฐ ์„ค๋น„ ๋“ฑ)๋‹น์‚ฌ๋Š” ์ˆ˜์›์‚ฌ์—…์žฅ์„ ๋น„๋กฏํ•˜์—ฌ ๊ตฌ๋ฏธ, ๊ด‘์ฃผ, ํ™”์„ฑ, ํ‰ํƒ, ์•„์‚ฐ ๋“ฑ ๊ตญ๋‚ด์‚ฌ์—…์žฅ ๋ฐ ๋ถ๋ฏธ, ์œ ๋Ÿฝ, ์ค‘๊ตญ ๋“ฑ ํ•ด์™ธ DX ๋ถ€๋ฌธ ์‚ฐํ•˜ 9๊ฐœ ์ง€์—ญ์ด๊ด„๊ณผ DS ๋ถ€๋ฌธ ์‚ฐํ•˜ 5๊ฐœ ์ง€์—ญ์ด๊ด„, SDC ๋ฐ Harman ์‚ฐํ•˜ ์ข…์†๊ธฐ์—… ๋“ฑ์—์„œ ์ฃผ์š” ์ œํ’ˆ์˜ ์ƒ์‚ฐ, ๊ฐœ๋ฐœ, ๋งˆ์ผ€ํŒ…, ์˜์—… ๋“ฑ์˜ ์‚ฌ์—… ํ™œ๋™์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+

+

๋‹น์‚ฌ์˜ ์‹œ์„ค ๋ฐ ์„ค๋น„๋Š” ํ† ์ง€, ๊ฑด๋ฌผ ๋ฐ ๊ตฌ์ถ•๋ฌผ, ๊ธฐ๊ณ„์žฅ์น˜, ๊ฑด์„ค์ค‘์ธ์ž์‚ฐ ๋“ฑ์ด ์žˆ์œผ๋ฉฐ, 2025๋…„๋ง ํ˜„์žฌ ์žฅ๋ถ€๊ธˆ์•ก์€ 215์กฐ 3,047์–ต์›์œผ๋กœ ์ „๋…„๋ง ๋Œ€๋น„ 9์กฐ 3,595์–ต์› ์ฆ๊ฐ€ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ + +

+(์‹œ์„คํˆฌ์ž ํ˜„ํ™ฉ) +2025๋…„ DS ๋ถ€๋ฌธ ๋ฐ SDC ๋“ฑ์˜ ์ฒจ๋‹จ๊ณต์ • ์ฆ์„คใ†์ „ํ™˜๊ณผ ์ธํ”„๋ผ ํˆฌ์ž๋ฅผ ์ค‘์‹ฌ์œผ๋กœ 52.7์กฐ์›์˜ ์‹œ์„คํˆฌ์ž๊ฐ€ ์ด๋ฃจ์–ด์กŒ์Šต๋‹ˆ๋‹ค. ๋‹น์‚ฌ๋Š” ๋ฉ”๋ชจ๋ฆฌ ์ฐจ์„ธ๋Œ€ ๊ธฐ์ˆ  ๊ฒฝ์Ÿ๋ ฅ ๊ฐ•ํ™” ๋ฐ ์ค‘์žฅ๊ธฐ ์ˆ˜์š” ๋Œ€๋น„๋ฅผ ์œ„ํ•œ ํˆฌ์ž๋ฅผ ์ง€์† ์ถ”์ง„ํ•˜์˜€๊ณ , ์‹œ์Šคํ…œ ๋ฐ˜๋„์ฒด๋Š” Advanced ๋…ธ๋“œ CAPA ํ™•๋ณด๋ฅผ ์œ„ํ•œ ํˆฌ์ž๋„ ์ง„ํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋‚ด์‹ค์„ ๋‹ค์ง€๋Š” ํ™œ๋™์„ ํ†ตํ•ด ํˆฌ์ž ํšจ์œจ์„ฑ ์ œ๊ณ ์—๋„ ์ง‘์ค‘ํ•  ๊ณ„ํš์ž…๋‹ˆ๋‹ค. +

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +
+ + +4. ๋งค์ถœ ๋ฐ ์ˆ˜์ฃผ์ƒํ™ฉ +

+ +

+๊ฐ€. ๋งค์ถœ์‹ค์ 2025๋…„(์ œ57๊ธฐ) ๋งค์ถœ์€ 333์กฐ 6,059์–ต์›์œผ๋กœ ์ „๋…„ ๋™๊ธฐ ๋Œ€๋น„ 10.9% ์ฆ๊ฐ€ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๋ถ€๋ฌธ๋ณ„๋กœ๋Š” ์ „๋…„ ๋™๊ธฐ ๋Œ€๋น„ DX ๋ถ€๋ฌธ์ด 7.5% ์ฆ๊ฐ€, DS ๋ถ€๋ฌธ์ด 17.2% ์ฆ๊ฐ€, SDC๋Š” 2.3% ์ฆ๊ฐ€ํ•˜์˜€์œผ๋ฉฐ, Harman์€ 10.6% ์ฆ๊ฐ€ํ•˜์˜€์Šต๋‹ˆ๋‹ค. +

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +

(1) ์ฃผ์š” ์ œํ’ˆ๋ณ„ ๋งค์ถœ์‹ค์ 

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +

+(2) ๋งค์ถœ์œ ํ˜•๋ณ„ ๋งค์ถœ์‹ค์  +

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +

+(3) ์ฃผ์š” ์ง€์—ญ๋ณ„ ๋งค์ถœ ํ˜„ํ™ฉ +

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +

๋‚˜. ํŒ๋งค๊ฒฝ๋กœ ๋“ฑ

+ +

+(1) ๊ตญ๋‚ด +

+ +
[table omitted in fixture]
+

+

(2) ํ•ด์™ธ

+ +
[table omitted in fixture]
+

+

(3) ํŒ๋งค๊ฒฝ๋กœ๋ณ„ ๋งค์ถœ์•ก ๋น„์ค‘

+ +
[table omitted in fixture]
+ + +

+๋‹ค. ํŒ๋งค๋ฐฉ๋ฒ• ๋ฐ ์กฐ๊ฑด +(1) ๊ตญ๋‚ด +

+ +
[table omitted in fixture]
+

+

(2) ํ•ด์™ธ

+ +
[table omitted in fixture]
+

+

๋ผ. ํŒ๋งค์ „๋žต

+

ใ†ํ”„๋ฆฌ๋ฏธ์—„ ์ œํ’ˆ์„ ์ค‘์‹ฌ์œผ๋กœ ํ•œ ์‹œ์žฅ ์šฐ์œ„ ๊ฐ•ํ™”

+

ใ†๋ธŒ๋žœ๋“œ, ์ œํ’ˆ, ์„œ๋น„์Šค๋ฅผ ํ†ตํ•ด ๊ณ ๊ฐ์—๊ฒŒ ์ฐจ๋ณ„ํ™”๋œ ๊ฐ€์น˜ ์ œ๊ณต

+

ใ†๊ณ ๊ฐใ†์‹œ์žฅ ์ค‘์‹ฌ์˜ ์ˆ˜์š” ์ด‰๋ฐœ ๋งˆ์ผ€ํŒ… ๊ฐ•ํ™”

+ +

๋งˆ. ์ฃผ์š” ๋งค์ถœ์ฒ˜

+ +

+2025๋…„ ๋‹น์‚ฌ์˜ ์ฃผ์š” ๋งค์ถœ์ฒ˜๋Š” Alphabet, Apple, Deutsche Telekom, Hong Kong Techtronics, Supreme Electronics ๋“ฑ(์•ŒํŒŒ๋ฒณ์ˆœ) ์ž…๋‹ˆ๋‹ค. ๋‹น์‚ฌ์˜ ์ฃผ์š” 5๋Œ€ ๋งค์ถœ์ฒ˜์— ๋Œ€ํ•œ ๋งค์ถœ๋น„์ค‘์€ ์ „์ฒด ๋งค์ถœ์•ก ๋Œ€๋น„ ์•ฝ 15% ์ˆ˜์ค€์ž…๋‹ˆ๋‹ค. +๋ฐ”. ์ˆ˜์ฃผ์ƒํ™ฉ +

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +
+ + +5. ์œ„ํ—˜๊ด€๋ฆฌ ๋ฐ ํŒŒ์ƒ๊ฑฐ๋ž˜ +

+ +

+๊ฐ€. ์žฌ๋ฌด์œ„ํ—˜๊ด€๋ฆฌ์ •์ฑ…๋‹น์‚ฌ๋Š” ์˜์—…ํ™œ๋™์—์„œ ํŒŒ์ƒ๋˜๋Š” ์‹œ์žฅ์œ„ํ—˜, ์‹ ์šฉ์œ„ํ—˜, ์œ ๋™์„ฑ์œ„ํ—˜ ๋“ฑ์„ ์ตœ์†Œํ™”ํ•˜๋Š”๋ฐ ์ค‘์ ์„ ๋‘๊ณ  ์žฌ๋ฌด์œ„ํ—˜์„ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ์œ„ํ•ด ๊ฐ๊ฐ์˜ ์œ„ํ—˜์š”์ธ์— ๋Œ€ํ•ด ๋ฉด๋ฐ€ํ•˜๊ฒŒ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ๋Œ€์‘ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +

+

์žฌ๋ฌด์œ„ํ—˜์€ ๊ธ€๋กœ๋ฒŒ ์žฌ๋ฌด๊ด€๋ฆฌ๊ธฐ์ค€์„ ์ˆ˜๋ฆฝํ•˜๊ณ  ๊ณ ๊ฐ๊ณผ ๊ฑฐ๋ž˜์„ ์— ๋Œ€ํ•œ ์ฃผ๊ธฐ์ ์ธ ์žฌ๋ฌด์œ„ํ—˜ ์ธก์ •, ํ™˜ํ—ท์ง€ ๋ฐ ์ž๊ธˆ์ˆ˜์ง€ ์ ๊ฒ€ ๋“ฑ์„ ํ†ตํ•ด ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

+

ํ•œํŽธ, ํ•ด์™ธ ์ฃผ์š” ๊ถŒ์—ญ๋ณ„๋กœ ์ง€์—ญ๊ธˆ์œต์„ผํ„ฐ(๋ฏธ๊ตญ, ์˜๊ตญ, ์‹ฑ๊ฐ€ํฌ๋ฅด, ์ค‘๊ตญ, ๋ธŒ๋ผ์งˆ, UAE)์—์„œ ํ™˜์œจ๋ณ€๋™ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ํ™˜๊ฑฐ๋ž˜ ๋Œ€ํ–‰์„ ํ†ตํ•ด ํ™˜์œจ๋ณ€๋™์œ„ํ—˜์„ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ๊ถŒ์—ญ๋‚ด ์ž๊ธˆ์„ ํ†ตํ•ฉ์šด์šฉํ•˜์—ฌ ์œ ๋™์„ฑ์œ„ํ—˜์— ๋Œ€์‘ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

+

๋‹น์‚ฌ์˜ ์žฌ๋ฌด์œ„ํ—˜๊ด€๋ฆฌ์˜ ์ฃผ์š” ๋Œ€์ƒ์ธ ์ž์‚ฐ์€ ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ, ๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ, ์ƒ๊ฐํ›„์›๊ฐ€๊ธˆ์œต์ž์‚ฐ, ๋งค์ถœ์ฑ„๊ถŒ ๋“ฑ์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์œผ๋ฉฐ, ๋ถ€์ฑ„๋Š” ๋งค์ž…์ฑ„๋ฌด, ์ฐจ์ž…๊ธˆ ๋“ฑ์œผ๋กœ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

+

+ + +

+๋‚˜. ์ฃผ์š” ์žฌ๋ฌด์œ„ํ—˜๊ด€๋ฆฌ +(1) ์‹œ์žฅ์œ„ํ—˜ +

+

+

(ํ™˜์œจ๋ณ€๋™์œ„ํ—˜)

+

๋‹น์‚ฌ๋Š” ๊ธ€๋กœ๋ฒŒ ์˜์—…ํ™œ๋™์„ ์ˆ˜ํ–‰ํ•จ์— ๋”ฐ๋ผ ๊ธฐ๋Šฅํ†ตํ™”์™€ ๋‹ค๋ฅธ ํ†ตํ™”๋กœ๋„ ๊ฑฐ๋ž˜๋ฅผ ํ•˜๊ณ  ์žˆ์–ด ํ™˜์œจ๋ณ€๋™์œ„ํ—˜์— ๋…ธ์ถœ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ™˜์œจ๋ณ€๋™์œ„ํ—˜์— ๋…ธ์ถœ๋˜๋Š” ์ฃผ์š” ํ†ตํ™”๋กœ๋Š” USD, EUR ๋“ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.๋‹น์‚ฌ๋Š” ํ†ตํ™”๋ณ„ ์ž์‚ฐ๊ณผ ๋ถ€์ฑ„๊ทœ๋ชจ๋ฅผ ์ผ์น˜ํ•˜๋Š” ์ˆ˜์ค€์œผ๋กœ ์œ ์ง€ํ•˜์—ฌ ํ™˜์œจ๋ณ€๋™ ์˜ํ–ฅ์„ ์ตœ์†Œํ™”ํ•˜๋Š”๋ฐ ์ฃผ๋ ฅํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ์ˆ˜์ถœ์ž… ๋“ฑ์˜ ๊ฒฝ์ƒ๊ฑฐ๋ž˜ ๋ฐ ์˜ˆ๊ธˆ, ์ฐจ์ž… ๋“ฑ์˜ ๊ธˆ์œต๊ฑฐ๋ž˜ ๋ฐœ์ƒ์‹œ ๊ธฐ๋Šฅํ†ตํ™”๋กœ ๊ฑฐ๋ž˜ํ•˜๊ฑฐ๋‚˜ ์ž…๊ธˆ ๋ฐ ์ง€์ถœ ํ†ตํ™”๋ฅผ ์ผ์น˜์‹œํ‚ด์œผ๋กœ์จ ํ™˜ํฌ์ง€์…˜ ๋ฐœ์ƒ์„ ์ตœ๋Œ€ํ•œ ์–ต์ œํ•˜๊ณ  ์žˆ์œผ๋‚˜, ์ผ๋ถ€ ๋ฐœ์ƒ๋œ ํ™˜ํฌ์ง€์…˜์€ ์ฑ„๊ถŒ๋งค๊ฐ, ์„ ๋ฌผํ™˜ ๋“ฑ์„ ํ™œ์šฉํ•˜์—ฌ ํ™˜๋ณ€๋™์˜ํ–ฅ์„ ์ถ•์†Œํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋‹น์‚ฌ๊ฐ€ ๋…ธ์ถœ๋œ ํ™˜์œจ๋ณ€๋™์œ„ํ—˜์„ ๊ฐ์†Œ์‹œํ‚ค์ง€๋งŒ ์™„์ „ํžˆ ์ œ๊ฑฐํ•˜์ง€๋Š” ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ๋‹น์‚ฌ๋Š” ํšจ์œจ์ ์ธ ํ™˜์œจ๋ณ€๋™์œ„ํ—˜ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด ํ™˜์œ„ํ—˜์„ ์ฃผ๊ธฐ์ ์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ํ‰๊ฐ€ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ ํˆฌ๊ธฐ์  ์™ธํ™˜๊ฑฐ๋ž˜๋Š” ์—„๊ฒฉํžˆ ๊ธˆ์ง€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

๋ณด๊ณ ๊ธฐ๊ฐ„์ข…๋ฃŒ์ผ ํ˜„์žฌ ๊ธฐ๋Šฅํ†ตํ™”์™€ ๋‹ค๋ฅธ ์ฃผ์š” ์™ธํ™”๋กœ ํ‘œ์‹œ๋œ ๊ธˆ์œต์ž์‚ฐ ๋ฐ ๊ธˆ์œต๋ถ€์ฑ„์— ๋Œ€ํ•˜์—ฌ ํ™˜์œจ์ด 5% ๋ณ€๋™ํ•  ๊ฒฝ์šฐ ๋‹น๊ธฐ์†์ต(๋ฒ•์ธ์„ธํšจ๊ณผ ๋ฐ˜์˜ ์ „)์— ๋ฏธ์น˜๋Š” ์˜ํ–ฅ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +

(์ด์ž์œจ๋ณ€๋™์œ„ํ—˜)

+

์ด์ž์œจ๋ณ€๋™์œ„ํ—˜์€ ์‹œ์žฅ๊ธˆ๋ฆฌ ๋ณ€๋™์œผ๋กœ ์ธํ•ด ํˆฌ์ž ๋ฐ ์žฌ๋ฌด ํ™œ๋™์œผ๋กœ๋ถ€ํ„ฐ ๋ฐœ์ƒํ•˜๋Š” ์ด์ž์ˆ˜์ต, ๋น„์šฉ์˜ ํ˜„๊ธˆํ๋ฆ„์ด ๋ณ€๋™๋  ์œ„ํ—˜์œผ๋กœ์„œ, ์ฃผ๋กœ ์˜ˆ๊ธˆ ๋ฐ ๋ณ€๋™๊ธˆ๋ฆฌ๋ถ€ ์ฐจ์ž…๊ธˆ์—์„œ๋น„๋กฏ๋ฉ๋‹ˆ๋‹ค. ๋‹น์‚ฌ๋Š” ๋‚ด๋ถ€์ž๊ธˆ ๊ณต์œ ๋ฅผ ํ†ตํ•ด ์™ธ๋ถ€์ฐจ์ž…๊ธˆ์„ ์ตœ์†Œํ™”ํ•˜์—ฌ ์ด์ž์œจ ๋ณ€๋™์œผ๋กœ ์ธํ•œ ๊ธˆ์œต๋น„์šฉ๊ณผ ๋ถˆํ™•์‹ค์„ฑ์„ ์ตœ์†Œํ™”ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

๋ณด๊ณ ๊ธฐ๊ฐ„์ข…๋ฃŒ์ผ ํ˜„์žฌ ๋ณ€๋™๊ธˆ๋ฆฌ๋ถ€ ๊ธˆ์œต์ž์‚ฐ ๋ฐ ๊ธˆ์œต๋ถ€์ฑ„์— ๋Œ€ํ•˜์—ฌ ์ด์ž์œจ์ด 1%p ๋ณ€๋™ํ•  ๊ฒฝ์šฐ ๋‹น๊ธฐ์†์ต(๋ฒ•์ธ์„ธํšจ๊ณผ ๋ฐ˜์˜ ์ „)์— ๋ฏธ์น˜๋Š” ์˜ํ–ฅ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+

+ +

+(์ฃผ๊ฐ€๋ณ€๋™์œ„ํ—˜) +๋‹น์‚ฌ๋Š” ์ „๋žต์  ๋ชฉ์  ๋“ฑ์œผ๋กœ ๊ธฐํƒ€ํฌ๊ด„์†์ต-๊ณต์ •๊ฐ€์น˜๊ธˆ์œต์ž์‚ฐ ๋ฐ ๋‹น๊ธฐ์†์ต-๊ณต์ •๊ฐ€์น˜๊ธˆ์œต์ž์‚ฐ์œผ๋กœ ๋ถ„๋ฅ˜๋œ ์ง€๋ถ„์ƒํ’ˆ์— ํˆฌ์žํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•˜์—ฌ ์งใ†๊ฐ„์ ‘์  ํˆฌ์ž์ˆ˜๋‹จ์„ ์ด์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.2025๋…„๋ง ํ˜„์žฌ ์ง€๋ถ„์ƒํ’ˆ(์ƒ์žฅ์ฃผ์‹)์˜ ์ฃผ๊ฐ€๊ฐ€ 1% ๋ณ€๋™ํ•  ๊ฒฝ์šฐ ๊ธฐํƒ€ํฌ๊ด„์†์ต(๋ฒ•์ธ์„ธํšจ๊ณผ ๋ฐ˜์˜ ์ „)์— ๋ฏธ์น˜๋Š” ์˜ํ–ฅ์€ 132,067๋ฐฑ๋งŒ์›(์ „๊ธฐ: 76,865๋ฐฑ๋งŒ์›)์ด๋ฉฐ, ๋‹น๊ธฐ์†์ต(๋ฒ•์ธ์„ธํšจ๊ณผ ๋ฐ˜์˜ ์ „)์— ๋ฏธ์น˜๋Š” ์˜ํ–ฅ์€ 1,460๋ฐฑ๋งŒ์›(์ „๊ธฐ: 862๋ฐฑ๋งŒ์›)์ž…๋‹ˆ๋‹ค. +

+ +

(2) ์‹ ์šฉ์œ„ํ—˜

+

์‹ ์šฉ์œ„ํ—˜์€ ํ†ต์ƒ์ ์ธ ๊ฑฐ๋ž˜ ๋ฐ ํˆฌ์ž ํ™œ๋™์—์„œ ๋ฐœ์ƒํ•˜๋ฉฐ ๊ณ ๊ฐ ๋˜๋Š” ๊ฑฐ๋ž˜์ƒ๋Œ€๋ฐฉ์ด ๊ณ„์•ฝ์กฐ๊ฑด์ƒ ์˜๋ฌด์‚ฌํ•ญ์„ ์ง€ํ‚ค์ง€ ๋ชปํ•˜์˜€์„ ๋•Œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์‹ ์šฉ์œ„ํ—˜์„ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•˜์—ฌ ๊ณ ๊ฐ๊ณผ ๊ฑฐ๋ž˜์ƒ๋Œ€๋ฐฉ์˜ ์žฌ๋ฌด์ƒํƒœ์™€ ๊ณผ๊ฑฐ ๊ฒฝํ—˜ ๋ฐ ๊ธฐํƒ€ ์š”์†Œ๋ฅผ ๊ณ ๋ คํ•˜์—ฌ ์ฃผ๊ธฐ์ ์œผ๋กœ ์žฌ๋ฌด์‹ ์šฉ๋„๋ฅผ ํ‰๊ฐ€ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ๊ณ ๊ฐ๊ณผ ๊ฑฐ๋ž˜์ƒ๋Œ€๋ฐฉ ๊ฐ๊ฐ์— ๋Œ€ํ•œ ์‹ ์šฉํ•œ๋„๋ฅผ ์„ค์ •ใ†๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์œ„ํ—˜๊ตญ๊ฐ€์— ์†Œ์žฌํ•œ ๊ฑฐ๋ž˜์ฒ˜์˜ ๋งค์ถœ์ฑ„๊ถŒ์€ ๋ณดํ—˜ํ•œ๋„ ๋‚ด์—์„œ ์ ์ ˆํ•˜๊ฒŒ ์œ„ํ—˜์ด ๊ด€๋ฆฌ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.์‹ ์šฉ์œ„ํ—˜์€ ๊ธˆ์œต๊ธฐ๊ด€๊ณผ์˜ ๊ฑฐ๋ž˜์—์„œ๋„ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ํ•ด๋‹น ๊ฑฐ๋ž˜๋Š” ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ, ๊ฐ์ข… ์˜ˆ๊ธˆ ๊ทธ๋ฆฌ๊ณ  ํŒŒ์ƒ๊ธˆ์œต์ƒํ’ˆ ๋“ฑ์˜ ๊ธˆ์œต์ƒํ’ˆ ๊ฑฐ๋ž˜๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์œ„ํ—˜์„ ์ค„์ด๊ธฐ ์œ„ํ•ด, ๋‹น์‚ฌ๋Š” ๊ตญ์ œ ์‹ ์šฉ๋“ฑ๊ธ‰์ด ๋†’์€ ์€ํ–‰(S&P A ์ด์ƒ)์— ๋Œ€ํ•ด์„œ๋งŒ ๊ฑฐ๋ž˜๋ฅผํ•˜๋Š” ๊ฒƒ์„ ์›์น™์œผ๋กœ ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ๊ธฐ์กด์— ๊ฑฐ๋ž˜๊ฐ€ ์—†๋Š” ๊ธˆ์œต๊ธฐ๊ด€๊ณผ์˜ ์‹ ๊ทœ ๊ฑฐ๋ž˜๋Š” ํ•ด๋‹น ๊ธฐ๊ด€์— ๋Œ€ํ•œ ๊ฑด์ „์„ฑ ํ‰๊ฐ€ ๋“ฑ์„ ๊ฑฐ์ณ ์‹œํ–‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹น์‚ฌ๊ฐ€ ์ฒด๊ฒฐํ•˜๋Š” ๊ธˆ์œต๊ณ„์•ฝ์€ ๋ถ€์ฑ„๋น„์œจ ์ œํ•œ ์กฐํ•ญ, ๋‹ด๋ณด์ œ๊ณต, ์ฐจ์ž…๊ธˆ ํšŒ์ˆ˜ ๋“ฑ์˜ ์ œ์•ฝ์กฐ๊ฑด์ด ์—†๋Š” ๊ณ„์•ฝ์„ ์œ„์ฃผ๋กœ ์ฒด๊ฒฐํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ๊ธฐํƒ€์˜ ๊ฒฝ์šฐ ๋ณ„๋„์˜ ์Šน์ธ์„ ๋ฐ›์•„ ๊ฑฐ๋ž˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.๋‹น์‚ฌ์˜ ๊ธˆ์œต์ž์‚ฐ ์žฅ๋ถ€๊ธˆ์•ก์€ ์†์ƒ์ฐจ์† ์ฐจ๊ฐ ํ›„ ๊ธˆ์•ก์œผ๋กœ ๋‹น์‚ฌ์˜ ์‹ ์šฉ์œ„ํ—˜ ์ตœ๋Œ€๋…ธ์ถœ์•ก์€ ๊ธˆ์œต์ž์‚ฐ ์žฅ๋ถ€๊ธˆ์•ก๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.

+ +

(3) ์œ ๋™์„ฑ์œ„ํ—˜

+

+

์œ ๋™์„ฑ์œ„ํ—˜์ด๋ž€ ๋‹น์‚ฌ๊ฐ€ ๊ธˆ์œต๋ถ€์ฑ„์™€ ๊ด€๋ จ๋œ ๋ชจ๋“  ์˜๋ฌด๋ฅผ ์ดํ–‰ํ•˜๋Š” ๋ฐ ์–ด๋ ค์›€์„ ๊ฒช๊ฒŒ ๋  ์œ„ํ—˜์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๋‹น์‚ฌ์˜ ์ฃผ์š” ์œ ๋™์„ฑ ๊ณต๊ธ‰์›์€ ์˜์—…์—์„œ ์ฐฝ์ถœํ•œ ํ˜„๊ธˆ ๋ฐ ์ž๋ณธ์‹œ์žฅ ๋ฐ ๊ธˆ์œต๊ธฐ๊ด€์—์„œ ์กฐ๋‹ฌํ•œ ์ž๊ธˆ์ด๋ฉฐ, ์ฃผ์š” ์œ ๋™์„ฑ ์ˆ˜์š”๋Š” ์ƒ์‚ฐ, ์—ฐ๊ตฌ๊ฐœ๋ฐœ์„ ์œ„ํ•œ ํˆฌ์ž๋ฅผ ๋น„๋กฏํ•ด, ์šด์ „์ž๋ณธ, ๋ฐฐ๋‹น ๋“ฑ์— ์žˆ์Šต๋‹ˆ๋‹ค. ๋Œ€๊ทœ๋ชจ ํˆฌ์ž๊ฐ€ ๋งŽ์€ ๋‹น์‚ฌ์˜ ์‚ฌ์—… ํŠน์„ฑ์ƒ ์ ์ • ์œ ๋™์„ฑ์˜ ์œ ์ง€๊ฐ€ ๋งค์šฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ๋‹น์‚ฌ๋Š” ์ฃผ๊ธฐ์ ์ธ ์ž๊ธˆ์ˆ˜์ง€ ์˜ˆ์ธก, ํ•„์š” ํ˜„๊ธˆ์ˆ˜์ค€ ์‚ฐ์ • ๋ฐ ์ˆ˜์ž…, ์ง€์ถœ ๊ด€๋ฆฌ ๋“ฑ์„ ํ†ตํ•˜์—ฌ ์ ์ • ์œ ๋™์„ฑ์„ ์œ ์ง€ใ†๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

๋‹น์‚ฌ๋Š” ๊ถŒ์—ญ๋ณ„๋กœ Cash Pooling์„ ๊ตฌ์ถ•ํ•˜์—ฌ ๊ถŒ์—ญ ๋‚ด ๊ฐœ๋ณ„ ๋ฒ•์ธ์˜ ์ž๊ธˆ์ด ๋ถ€์กฑํ•œ ๊ฒฝ์šฐ์—๋„ ์œ ๋™์„ฑ์œ„ํ—˜์— ํšจ์œจ์ ์œผ๋กœ ๋Œ€์‘ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. Cash Pooling์€ ์ž๊ธˆ ๋ถ€์กฑ ํšŒ์‚ฌ์™€ ์ž๊ธˆ ์ž‰์—ฌ ํšŒ์‚ฌ๊ฐ„ ์ž๊ธˆ์„ ๊ณต์œ ํ•˜๋Š” ์‹œ์Šคํ…œ์œผ๋กœ ๊ฐœ๋ณ„ํšŒ์‚ฌ์˜ ์œ ๋™์„ฑ์œ„ํ—˜์„ ์ตœ์†Œํ™”ํ•˜๊ณ  ์ž๊ธˆ์šด์šฉ ๋ถ€๋‹ด์„ ๊ฒฝ๊ฐ์‹œํ‚ค๋ฉฐ, ๊ธˆ์œต๋น„์šฉ์„ ์ ˆ๊ฐํ•˜๋Š” ๋“ฑ์˜ ํšจ๊ณผ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

+

+

๋˜ํ•œ ๋Œ€๊ทœ๋ชจ ์œ ๋™์„ฑ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•˜์—ฌ ๋ณธ์‚ฌ์˜ ์ง€๊ธ‰๋ณด์ฆ์„ ํ†ตํ•ด ํ•ด์™ธ ์ข…์†๊ธฐ์—…์ด ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ฐจ์ž…ํ•œ๋„๋ฅผ ํ™•๋ณดํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ๋ณด๊ณ ๊ธฐ๊ฐ„์ข…๋ฃŒ์ผ ํ˜„์žฌ ๊ตญ์ œ์‹ ์šฉํ‰๊ฐ€๊ธฐ๊ด€์ธ Moody's๋กœ๋ถ€ํ„ฐ Aa2, S&P๋กœ๋ถ€ํ„ฐ AA- ๋“ฑ ํˆฌ์ž์ ๊ฒฉ๋“ฑ๊ธ‰์„ ๋ถ€์—ฌ๋ฐ›๊ณ  ์žˆ์–ด, ํ•„์š”์‹œ ์ž๋ณธ์‹œ์žฅ์„ ํ†ตํ•ด ์ ๊ธฐ์— ์ž๊ธˆ์กฐ๋‹ฌ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

+ +

๋ณด๊ณ ๊ธฐ๊ฐ„์ข…๋ฃŒ์ผ ํ˜„์žฌ ๊ธˆ์œต๋ถ€์ฑ„์— ๋Œ€ํ•˜์—ฌ ๋ณด๊ณ ๊ธฐ๊ฐ„์ข…๋ฃŒ์ผ๋กœ๋ถ€ํ„ฐ ๊ณ„์•ฝ๋งŒ๊ธฐ์ผ๊นŒ์ง€์˜ ์ž”์—ฌ๋งŒ๊ธฐ์— ๋”ฐ๋ฅธ ๋งŒ๊ธฐ๋ถ„์„์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

+

1) ์ œ57๊ธฐ๋ง

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+

+

2) ์ œ56๊ธฐ๋ง

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+

(4) ์ž๋ณธ์œ„ํ—˜

+

+

๋‹น์‚ฌ์˜ ์ž๋ณธ๊ด€๋ฆฌ ๋ชฉ์ ์€ ๊ณ„์†๊ธฐ์—…์œผ๋กœ์„œ ์ฃผ์ฃผ ๋ฐ ์ดํ•ด๊ด€๊ณ„์ž์—๊ฒŒ ์ด์ต์„ ์ง€์†์ ์œผ๋กœ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ์„ ๋ณดํ˜ธํ•˜๊ณ  ๊ฑด์ „ํ•œ ์ž๋ณธ๊ตฌ์กฐ๋ฅผ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‹น์‚ฌ๋Š” ์‹ ์šฉ๋“ฑ๊ธ‰, ๋ถ€์ฑ„๋น„์œจ ๋“ฑ์˜ ์ง€ํ‘œ๋ฅผ ์ด์šฉํ•˜์—ฌ ์ž๋ณธ์œ„ํ—˜์„ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

๋ณด๊ณ ๊ธฐ๊ฐ„์ข…๋ฃŒ์ผ ํ˜„์žฌ ๋‹น์‚ฌ์˜ ๋ถ€์ฑ„๋น„์œจ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +

๋‹ค. ํŒŒ์ƒ์ƒํ’ˆ ๋ฐ ํ’‹๋ฐฑ์˜ต์…˜ ๋“ฑ ๊ฑฐ๋ž˜ ํ˜„ํ™ฉ

+

+

๋‹น์‚ฌ๋Š” ํ™˜์œจ๋ณ€๋™ ์œ„ํ—˜๊ด€๋ฆฌ ๋ชฉ์ ์ƒ ์žฅ๋ถ€ ํ†ตํ™”๊ฐ€ ์•„๋‹Œ ์™ธํ™” ํฌ์ง€์…˜์— ๋Œ€ํ•˜์—ฌ ํ†ตํ™”์„ ๋„(Currency Forward) ์ƒํ’ˆ์„ ๋งค๋งคํ•˜์—ฌ ํ—ท์ง€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ†ตํ™”์„ ๋„ ์ƒํ’ˆ์€ ๋งค๋งค๋ชฉ์  ๋ฐ ์œ„ํ—˜ํšŒํ”ผ๋ชฉ์ ์œผ๋กœ ์€ํ–‰์„ ํ†ตํ•˜์—ฌ ๋งค์ˆ˜ ๋˜๋Š” ๋งค๋„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

2025๋…„๋ง ํ˜„์žฌ ๋‹น์‚ฌ๋Š” USD, EUR, JPY ๋“ฑ ์ด 33๊ฐœ ํ†ตํ™”์— ๋Œ€ํ•˜์—ฌ 3,360๊ฑด์˜ ํ†ตํ™”์„ ๋„ ๊ฑฐ๋ž˜๋ฅผ ์ฒด๊ฒฐํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์ž์‚ฐใ†๋ถ€์ฑ„ ์žฅ๋ถ€๊ธˆ์•ก ๋ฐ ๊ด€๋ จ์†์ต์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+

๊ทธ๋ฆฌ๊ณ  ๋‹น์‚ฌ๋Š” ๋ ˆ์ธ๋ณด์šฐ๋กœ๋ณดํ‹ฑ์Šคใˆœ ๋ฐ ๊ทธ ์ตœ๋Œ€์ฃผ์ฃผ ๋“ฑ๊ณผ ์ฒด๊ฒฐํ•œ ์ฃผ์ฃผ๊ฐ„ ๊ณ„์•ฝ์— ๋”ฐ๋ผ,์ผ์ • ๊ธฐ๊ฐ„ ๋™์•ˆ ์ตœ๋Œ€์ฃผ์ฃผ ๋“ฑ์—๊ฒŒ ์ตœ๋Œ€์ฃผ์ฃผ ๋“ฑ์ด ๋ณด์œ ํ•˜๋Š” ์ฃผ์‹์˜ ์ „๋ถ€ ๋˜๋Š” ์ผ๋ถ€๋ฅผ ๋‹น์‚ฌ ๋˜๋Š” ๋‹น์‚ฌ๊ฐ€ ์ง€์ •ํ•œ ์ œ3์ž์—๊ฒŒ ๋งค๋„ํ•  ๊ฒƒ์„ ์ฒญ๊ตฌํ•  ๊ถŒ๋ฆฌ๋ฅผ ๋ณด์œ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹น์‚ฌ๊ฐ€ ๋ณด์œ ํ•œ ์˜ต์…˜ ์ค‘ 2024๋…„ 12์›” 31์ผ ์ด์‚ฌํšŒ์—์„œ ๋ ˆ์ธ๋ณด์šฐ๋กœ๋ณดํ‹ฑ์Šคใˆœ์˜ ์ตœ๋Œ€์ฃผ์ฃผ ๋“ฑ 6์ธ์ด ๋ณด์œ ํ•œ ์ง€๋ถ„ 394๋งŒ์ฃผ๋ฅผ ์ธ์ˆ˜ํ•˜๋Š” ์ฝœ์˜ต์…˜์˜ ํ–‰์‚ฌ๋ฅผ ์˜๊ฒฐํ•˜์˜€๊ณ , ์ธ์ˆ˜๋Œ€๊ฐ€๋Š” 267,463๋ฐฑ๋งŒ์›์ด๋ฉฐ, 2025๋…„ 3์›” 12์ผ์— ์ง€๋ถ„์ธ์ˆ˜ ์ ˆ์ฐจ๋ฅผ ์™„๋ฃŒํ•˜์˜€์Šต๋‹ˆ๋‹ค. 2025๋…„๋ง ํ˜„์žฌ ๋ณด์œ ํ•œ ์ฝœ์˜ต์…˜์˜ ํ–‰์‚ฌ๊ฐ€์•ก์€ ์‹œ๊ฐ€๋กœ ์ฝœ์˜ต์…˜์˜ ๊ณต์ •๊ฐ€์น˜๋ฅผ ๋ณ„๋„๋กœ ํ‰๊ฐ€ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.๋˜ํ•œ, ์ข…์†ํšŒ์‚ฌ์ธ ์‚ผ์„ฑ๋””์Šคํ”Œ๋ ˆ์ดใˆœ๋Š” Corning Incorporated์™€ 2021๋…„ 4์›” 8์ผ ์‹คํ–‰๋œ ์ฃผ์‹๋งค๋งค๊ณ„์•ฝ์— ๋”ฐ๋ผ, ๋ณด์œ  ์ค‘์ธ Corning ์ง€๋ถ„์ฆ๊ถŒ ์ผ๋ถ€๋ฅผ Corning์—๊ฒŒ ๋งค๊ฐํ• ์ˆ˜ ์žˆ๋Š” ํ’‹์˜ต์…˜์„ ๋ณด์œ ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, TCL Technology Group Corporation (TCL) ๋ฐ TCL China Star Optoelectronics Technology Co. Ltd. (CSOT)์™€ 2021๋…„ 4์›” 1์ผ ์‹คํ–‰๋œ ์ฃผ์ฃผ๊ฐ„ ๊ณ„์•ฝ์— ๋”ฐ๋ผ CSOT๊ฐ€ ์ƒ์žฅ๊ธฐํ•œ ๋‚ด ๋น„์ƒ์žฅ ์‹œ, ์‚ผ์„ฑ๋””์Šคํ”Œ๋ ˆ์ดใˆœ๊ฐ€ ๋ณด์œ  ์ค‘์ธ CSOT ์ง€๋ถ„์ฆ๊ถŒ ์ „๋ถ€ ๋˜๋Š” ์ผ๋ถ€๋ฅผ TCL์—๊ฒŒ ๋งค๊ฐํ•  ์ˆ˜ ์žˆ๋Š” ํ’‹์˜ต์…˜์„ ๋ณด์œ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. 2025๋…„๋ง ํ˜„์žฌ ์ƒ๊ธฐ ํ’‹์˜ต์…˜๋“ค์˜ ๊ณต์ •๊ฐ€์น˜๋Š” ํ•œ์˜ํšŒ๊ณ„๋ฒ•์ธ์ด ํ‰๊ฐ€ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ข…์†ํšŒ์‚ฌ์ธ ์‚ผ์„ฑ๋””์Šคํ”Œ๋ ˆ์ดใˆœ๋Š” 2025๋…„ 12์›” ๋ณต์ˆ˜์˜ ๊ธˆ์œต๊ธฐ๊ด€๊ณผ ๋‹น์‚ฌ ๋ณดํ†ต์ฃผ๋ฅผ ๊ธฐ์ดˆ์ž์‚ฐ์œผ๋กœ ํ•˜๋Š” ์ฐจ์•ก๊ฒฐ์ œํ˜• ์ฃผ์‹์„ ๋„๊ณ„์•ฝ์„ ์ฒด๊ฒฐํ•˜์˜€์Šต๋‹ˆ๋‹ค.

+

+
+ + +6. ์ฃผ์š”๊ณ„์•ฝ ๋ฐ ์—ฐ๊ตฌ๊ฐœ๋ฐœํ™œ๋™ +

+

๊ฐ€. ๊ฒฝ์˜์ƒ์˜ ์ฃผ์š” ๊ณ„์•ฝ ๋“ฑ

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ + +

+๋‚˜. ์—ฐ๊ตฌ๊ฐœ๋ฐœํ™œ๋™์˜ ๊ฐœ์š” ๋ฐ ์—ฐ๊ตฌ๊ฐœ๋ฐœ๋น„์šฉ +๋‹น์‚ฌ๋Š” ๊ณ ๊ฐ์˜ ์š”๊ตฌ๋ฅผ ๋จผ์ € ํŒŒ์•…ํ•˜๊ณ  ๋ฐœ์ƒ์˜ ์ „ํ™˜์„ ํ†ตํ•ด ์ฐฝ์กฐ์ ์ด๊ณ  ํ˜์‹ ์ ์ธ ์ œํ’ˆ,๋ฏธ๋ž˜๋ฅผ ์„ ๋„ํ•˜๋Š” ๊ธฐ์ˆ ์„ ์ง€์†์ ์œผ๋กœ ์ฐฝ์ถœํ•˜์—ฌ ์„ธ๊ณ„ ์‹œ์žฅ์„ ์„ ๋„ํ•ด ๋‚˜๊ฐ€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์„ธ๊ณ„ IT์—…๊ณ„์—์„œ์˜ ์œ„์ƒ์„ ๋”์šฑ ๊ตณ๊ฑดํžˆ ํ•˜๊ธฐ ์œ„ํ•ด ์ฐจ์„ธ๋Œ€ ๊ธฐ์ˆ ๊ณผ ์›์ฒœ๊ธฐ์ˆ ์„ ํ™•๋ณดํ•˜์—ฌ ์„ธ๊ณ„ ์‚ฐ์—… ๊ธฐ์ˆ ์„ ์ด๋„๋Š” ์ง„์ •ํ•œ ์„ ๋„ ๊ธฐ์—…(Leading Company)์ด ๋˜๋„๋ก ์ตœ์„ ์„ ๋‹คํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.2025๋…„(์ œ57๊ธฐ) ๋‹น์‚ฌ์˜ ์—ฐ๊ตฌ๊ฐœ๋ฐœ๋น„์šฉ์€ 37์กฐ 7,548์–ต์›์ด๋ฉฐ, ์ด ์ค‘ ์ •๋ถ€๋ณด์กฐ๊ธˆ์œผ๋กœ ์ฐจ๊ฐ๋œ ์—ฐ๊ตฌ๊ฐœ๋ฐœ๋น„๋ฅผ ์ œ์™ธํ•˜๊ณ  37์กฐ 7,404์–ต์›์„ ๋‹น๊ธฐ๋น„์šฉ์œผ๋กœ ํšŒ๊ณ„์ฒ˜๋ฆฌํ•˜์˜€์Šต๋‹ˆ๋‹ค. +

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ + +

+๋‹ค. ์—ฐ๊ตฌ๊ฐœ๋ฐœ ์กฐ์ง ๋ฐ ์šด์˜ +(๊ตญ๋‚ด)๋‹น์‚ฌ๋Š” ์—ฐ๊ตฌ๊ฐœ๋ฐœ ์กฐ์ง์„ ๊ธฐ์ˆ  ์ƒ์šฉํ™” ์‹œ๊ธฐ์— ๋”ฐ๋ผ 3๋‹จ๊ณ„๋กœ ์ฒด๊ณ„ํ™”ํ•˜์—ฌ ์šด์˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.ํ–ฅํ›„ 1~2๋…„ ๋‚ด ์‹œ์žฅ์— ์„ ๋ณด์ผ ์ƒํ’ˆํ™” ๊ธฐ์ˆ ์€ ๊ฐ ๋ถ€๋ฌธ ์‚ฐํ•˜ ์‚ฌ์—…๋ถ€ ๊ฐœ๋ฐœํŒ€์—์„œ ๊ฐœ๋ฐœํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, 3~5๋…„ ๋‚ด ์ค‘์žฅ๊ธฐ ๋ฏธ๋ž˜ ์œ ๋ง ๊ธฐ์ˆ ์€ Samsung Research, ๋ฐ˜๋„์ฒด์—ฐ๊ตฌ์†Œ ๋“ฑ์˜ ๊ฐ ๋ถ€๋ฌธ ์—ฐ๊ตฌ์†Œ์—์„œ ๊ฐœ๋ฐœํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ๋ฏธ๋ž˜ ์„ฑ์žฅ์—”์ง„์— ํ•„์š”ํ•œ ํ•ต์‹ฌ ์š”์†Œ ๊ธฐ์ˆ ์€ ๋‹น์‚ฌ์˜ ์ข…ํ•ฉ์—ฐ๊ตฌ์†Œ์ธ SAIT์—์„œ ์„ ํ–‰ ๊ฐœ๋ฐœํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. SAIT๋Š” ์ „์‚ฌ ์ฐจ์›์—์„œ ์œ ๋ง ์„ฑ์žฅ ๋ถ„์•ผ์— ๋Œ€ํ•œ ์—ฐ๊ตฌ๊ฐœ๋ฐœ ๋ฐฉํ–ฅ ์ œ์‹œ์™€ ์ฃผ๋ ฅ ์‚ฌ์—…์˜ ๊ธฐ์ˆ  ๊ฒฝ์Ÿ๋ ฅ ๊ฐ•ํ™”, ์ฐฝ์˜์  R&D ์ฒด์ œ๋ฅผ ๊ตฌ์ถ•ํ•˜๊ธฐ ์œ„ํ•œ ์—ฐ๊ตฌ๊ฐœ๋ฐœ ํ™œ๋™์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +

+ +

+(ํ•ด์™ธ)๋ฏธ๊ตญ(SRA, SFI), ์šฐํฌ๋ผ์ด๋‚˜(SRUKR), ๋Ÿฌ์‹œ์•„(SRR), ์ผ๋ณธ(SRJ), ์ค‘๊ตญ(SRC-Beijing, SRC-Nanjing, SRC-Guangzhou, SRC-Shenzhen, SSCR), ์ธ๋„(SRI-Bangalore, SRI-Delhi), ๋ฐฉ๊ธ€๋ผ๋ฐ์‹œ(SRBD), ์ด์Šค๋ผ์—˜(SIRC, SRIL) ๋“ฑ์˜ ์ง€์—ญ์— ์—ฐ๊ตฌ๊ฐœ๋ฐœ ์กฐ์ง์„ ์šด์˜ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ ์ œํ’ˆ ๊ฐœ๋ฐœ ๋ฐ ๊ธฐ์ดˆ ๊ธฐ์ˆ  ์—ฐ๊ตฌ ๋“ฑ์„ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +

+

+ +

+ + +์—ฐ๊ตฌ๊ฐœ๋ฐœ์กฐ์ง๋„.jpg +์—ฐ๊ตฌ๊ฐœ๋ฐœ์กฐ์ง๋„ + +

+ + +

+๋ผ. ์—ฐ๊ตฌ๊ฐœ๋ฐœ์‹ค์  +2025๋…„ ์ค‘ ๋‹น์‚ฌ์˜ ๊ฐ ๋ถ€๋ฌธ๋ณ„ ์ฃผ์š” ์—ฐ๊ตฌ๊ฐœ๋ฐœ ์‹ค์ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. +

+

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +
+ + +7. ๊ธฐํƒ€ ์ฐธ๊ณ ์‚ฌํ•ญ +

+

๊ฐ€. ์ง€์ ์žฌ์‚ฐ๊ถŒ ๊ด€๋ จ

+

๋‹น์‚ฌ๋Š” R&D ํ™œ๋™์˜ ์ง€์ ์žฌ์‚ฐํ™”์—๋„ ์ง‘์ค‘ํ•˜์—ฌ 1984๋…„ ์ตœ์ดˆ๋กœ ๋ฏธ๊ตญ์— ํŠนํ—ˆ๋ฅผ ๋“ฑ๋กํ•œ์ด๋ž˜ ํ˜„์žฌ ์„ธ๊ณ„์ ์œผ๋กœ ์ด 281,857๊ฑด์˜ ํŠนํ—ˆ๋ฅผ ๋ณด์œ ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ํŠนํžˆ ๋ฏธ๊ตญ์—์„œ์˜ ๋ถ„์Ÿ์— ํšจ๊ณผ์ ์œผ๋กœ ๋Œ€์‘ํ•˜๊ณ ์ž ๋ฏธ๊ตญ์—์„œ ๊ฐ€์žฅ ๋งŽ์€ ํŠนํ—ˆ๋ฅผ ๋ณด์œ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+

2025๋…„ ์ค‘ ์ด 38์กฐ์›์˜ R&Dํˆฌ์ž๋ฅผ ํ†ตํ•ด ๊ตญ๋‚ด ํŠนํ—ˆ 10,639๊ฑด, ๋ฏธ๊ตญ ํŠนํ—ˆ 10,347๊ฑด ๋“ฑ์„ ๋“ฑ๋กํ•˜์˜€์Šต๋‹ˆ๋‹ค.

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+

์ด ์ง€์ ์žฌ์‚ฐ๊ถŒ์€ ๋Œ€๋ถ€๋ถ„ ์Šค๋งˆํŠธํฐ, ์Šค๋งˆํŠธ TV, ๋ฉ”๋ชจ๋ฆฌ, System LSI ๋“ฑ์— ๊ด€ํ•œ ํŠนํ—ˆ๋กœ์„œ ๋‹น์‚ฌ ์ „๋žต์‚ฌ์—… ์ œํ’ˆ์— ์“ฐ์ด๊ฑฐ๋‚˜ ํ–ฅํ›„ ํ™œ์šฉ๋  ์˜ˆ์ •์ด๋ฉฐ, ์‚ฌ์—… ๋ณดํ˜ธ์˜ ์—ญํ• ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์œ ์‚ฌ ๊ธฐ์ˆ ใ†ํŠนํ—ˆ๊ฐ€ ๋‚œ๋ฆฝํ•˜๋Š” ์ƒํ™ฉ์—์„œ ๊ฒฝ์Ÿ์‚ฌ ๊ฒฌ์ œ์˜ ์—ญํ• ๋„ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ๋ฏธ๋ž˜ ์‹ ๊ธฐ์ˆ  ๊ด€๋ จ ์„ ํ–‰ ํŠนํ—ˆ ํ™•๋ณด๋ฅผ ํ†ตํ•˜์—ฌ ํ–ฅํ›„ ์‹ ๊ทœ ์‚ฌ์—… ์ง„์ถœ ์‹œ ์‚ฌ์—… ๋ณดํ˜ธ์˜ ์—ญํ• ์ด ๊ธฐ๋Œ€๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹น์‚ฌ๋Š” Google(2014.1์›” ์ฒด๊ฒฐ), Nokia(2023.1์›”), Qualcomm(2022.7์›”), Huawei(2022.7์›”) ๋“ฑ๊ณผ์˜ ํŠนํ—ˆ ๋ผ์ด์„ ์Šค ์ฒด๊ฒฐ์„ ํ†ตํ•˜์—ฌ, ๋ชจ๋ฐ”์ผ, ๋ฐ˜๋„์ฒด ๋“ฑ ๋‹น์‚ฌ ์ฃผ๋ ฅ์‚ฌ์—… ๋ฐ ์‹ ์‚ฌ์—… ๋ถ„์•ผ์—์„œ ๊ด‘๋ฒ”์œ„ํ•œ ํŠนํ—ˆ ๋ณดํ˜ธ๋ง์„ ํ™•๋ณดํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.๋‹น์‚ฌ๋Š” ์ƒ๊ธฐ ํŠนํ—ˆ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์Šค๋งˆํŠธํฐ, ์Šค๋งˆํŠธ TV ๋“ฑ์— ์ ์šฉ๋œ ๋‹น์‚ฌ ๊ณ ์œ  ๋””์ž์ธ์„๋ณดํ˜ธํ•˜๊ณ ์ž ๋””์ž์ธํŠนํ—ˆ ํ™•๋ณด๋„ ๊ฐ•ํ™”ํ•˜์—ฌ, 2025๋…„ ์ค‘ ๋ฏธ๊ตญ์—์„œ 462๊ฑด์˜ ๋””์ž์ธํŠนํ—ˆ(Design Patent)๋ฅผ ์ทจ๋“ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

+ +

๋‚˜. ํ™˜๊ฒฝ ๊ด€๋ จ ๊ทœ์ œ์‚ฌํ•ญ

+

+

๋‹น์‚ฌ๋Š” ๋ฒ•๋ฅ ์—์„œ ์ •ํ•˜๊ณ  ์žˆ๋Š” ๊ฐ์ข… ์ œํ’ˆ ํ™˜๊ฒฝ๊ทœ์ œ์™€ ์‚ฌ์—…์žฅ๊ด€๋ฆฌ ํ™˜๊ฒฝ๊ทœ์ œ๋ฅผ ์ฒ ์ €ํ•˜๊ฒŒ ์ค€์ˆ˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์ •๋ถ€์˜ ์ €ํƒ„์†Œ ๋…น์ƒ‰์„ฑ์žฅ ์ •์ฑ…์— ๋ถ€์‘ํ•˜์—ฌ ๊ด€๋ จ ๋ฒ•๊ทœ์—์„œ์š”๊ตฌํ•˜๋Š” ๋ฐ”์— ๋”ฐ๋ผ ์‚ฌ์—…์žฅ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜จ์‹ค๊ฐ€์Šค ๋ฐฐ์ถœ๋Ÿ‰๊ณผ ์—๋„ˆ์ง€ ์‚ฌ์šฉ๋Ÿ‰์„ ์ •๋ถ€์— ์‹ ๊ณ ํ•˜๊ณ  ์ง€์†๊ฐ€๋Šฅ๋ณด๊ณ ์„œ ๋“ฑ์„ ํ†ตํ•˜์—ฌ ์ดํ•ด๊ด€๊ณ„์ž์—๊ฒŒ ๊ด€๋ จ ์ •๋ณด๋ฅผ ํˆฌ๋ช…ํ•˜๊ฒŒ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+ +

+(์ œํ’ˆ ํ™˜๊ฒฝ๊ทœ์ œ ์ค€์ˆ˜) +

+

์ „์ž์ œํ’ˆ์€ ์ผ๋ฐ˜์ ์œผ๋กœ ์†Œ๋น„์ž๊ฐ€ ์ง์ ‘ ํœด๋Œ€ํ•˜๊ฑฐ๋‚˜, ๊ฐ€์ •์— ์„ค์น˜ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด ์‚ฌ์šฉ์ž ๊ฑด๊ฐ•๊ณผ ์•ˆ์ „์— ์งใ†๊ฐ„์ ‘์ ์ธ ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ด€๋ จ ํ™˜๊ฒฝ๊ทœ์ œ๊ฐ€ ๊ฐ•ํ™”๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•ด ๋‹น์‚ฌ๋Š” ๋ถ€ํ’ˆ ๋ฐ ์ œํ’ˆ์˜ ๊ฐœ๋ฐœ๋‹จ๊ณ„๋ถ€ํ„ฐ ์ œ์กฐ, ์œ ํ†ต, ์‚ฌ์šฉ, ํ๊ธฐ ๋“ฑ ์ œํ’ˆ์˜ ์ „ ๊ณผ์ •(Life Cycle)์— ๊ฑธ์ณ ํ™˜๊ฒฝ์˜ํ–ฅ์„ ์ตœ์†Œํ™”ํ•˜๊ธฐ ์œ„ํ•œ ํ™œ๋™์„ ํ•˜๊ณ ์žˆ์Šต๋‹ˆ๋‹ค. ์œ ํ•ด๋ฌผ์งˆ์„ ์ œ๊ฑฐํ•œ ๋ถ€ํ’ˆ ๊ณต๊ธ‰์„ ์œ„ํ•ด ํ˜‘๋ ฅํšŒ์‚ฌ ์—์ฝ”ํŒŒํŠธ๋„ˆ ์ธ์ฆ ์ œ๋„๋ฅผ ์šด์˜ํ•˜๊ณ  ๊ฐœ๋ฐœ๋‹จ๊ณ„์—์„œ ์ œํ’ˆ์˜ ์นœํ™˜๊ฒฝ ์š”์†Œ(์ž์› ์‚ฌ์šฉ ์ ˆ๊ฐ, ์—๋„ˆ์ง€ ์ ˆ์•ฝ, ์œ ํ•ด ๋ฌผ์งˆ ์ €๊ฐ, ์นœํ™˜๊ฒฝ์†Œ์žฌ ์ ์šฉ ๋“ฑ)๋ฅผ ์ œํ’ˆ์— ๋ฐ˜์˜ํ•˜๊ธฐ ์œ„ํ•ด ์—์ฝ”๋””์ž์ธ ํ‰๊ฐ€ ์ œ๋„๋ฅผ ์šด์˜ํ•˜๋ฉฐ, ์ œํ’ˆ ์‚ฌ์šฉ ํ›„ ๋ฐœ์ƒํ•˜๋Š” ํ์ „์ž์ œํ’ˆ์„ ํšŒ์ˆ˜ํ•˜์—ฌ ์žฌํ™œ์šฉํ•˜๋Š” ํ์ œํ’ˆ ํšŒ์ˆ˜ใ†์žฌํ™œ์šฉ์‹œ์Šคํ…œ์„ ์œ ๋Ÿฝ, ๋ถ๋ฏธ, ํ•œ๊ตญ, ์ธ๋„ ๋“ฑ ๊ฐ๊ตญ์—์„œ ์šด์˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ํ™œ๋™์€ ์ „๊ธฐใ†์ „์ž ์ œํ’ˆ ๊ด€๋ จ ๊ตญ๋‚ด์™ธ ํ™˜๊ฒฝ ๋ฒ•๊ทœ๋ฅผ ์ค€์ˆ˜ํ•  ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ํšŒ์‚ฌ์™€ ์ œํ’ˆ์˜ ์ฐจ๋ณ„ํ™” ์š”์†Œ๋กœ ๊ธฐ์—ฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

๊ฐ๊ตญ์˜ ๊ด€๋ จ ๋ฒ•๋ฅ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. 1. ํ์ œํ’ˆ ํšŒ์ˆ˜ใ†์žฌํ™œ์šฉ๋ฒ• (์˜ˆ: EU WEEE Directive)

+

2. ์œ ํ•ด๋ฌผ์งˆ ์‚ฌ์šฉ ์ œํ•œ (์˜ˆ: EU RoHS Directive, REACH Regulation)

+

3. ์ œํ’ˆ ์—๋„ˆ์ง€ ์†Œ๋น„ํšจ์œจ ๊ทœ์ • (์˜ˆ: EU ErP Directive)

+ +

(์‚ฌ์—…์žฅ๊ด€๋ฆฌ ํ™˜๊ฒฝ๊ทœ์ œ ์ค€์ˆ˜)

+

์ œํ’ˆ ์ƒ์‚ฐ๊ณผ์ •์—์„œ ๋ฐœ์ƒ๋˜๋Š” ์˜ค์—ผ๋ฌผ์งˆ์˜ ๋ฐฐ์ถœ์„ ์ตœ์†Œํ™”ํ•˜๊ธฐ ์œ„ํ•˜์—ฌ ๋Œ€๊ธฐ์˜ค์—ผ๋ฐฉ์ง€์‹œ์„ค, ์ˆ˜์งˆ์˜ค์—ผ๋ฐฉ์ง€์‹œ์„ค, ํ๊ธฐ๋ฌผ์ฒ˜๋ฆฌ์‹œ์„ค๊ณผ ๊ฐ™์€ ํ™˜๊ฒฝ์˜ค์—ผ๋ฐฉ์ง€์‹œ์„ค์„ ์„ค์น˜ํ•˜์—ฌ ์šด์˜ํ•จ์œผ๋กœ์จ ์ฃผ๋ณ€ ํ™˜๊ฒฝ์— ๋Œ€ํ•œ ์˜ํ–ฅ์„ ์ตœ์†Œํ™” ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์‚ฌ์—…์žฅ์˜ ํ™˜๊ฒฝ๊ด€๋ฆฌ๋Š”๊ด€๋ จ ๋ถ€์ฒ˜, ์ง€๋ฐฉ์ž์น˜๋‹จ์ฒด์˜ ๊ด€๋ฆฌ๊ฐ๋…์„ ๋ฐ›๊ณ  ์žˆ์œผ๋ฉฐ, ๊ตญ๋‚ด ๋ฐ ๊ธ€๋กœ๋ฒŒ ์ „ ์ƒ์‚ฐ ์‚ฌ์—…์žฅ์€ ๊ตญ์ œ ํ™˜๊ฒฝ์•ˆ์ „๋ณด๊ฑด๊ฒฝ์˜์‹œ์Šคํ…œ ์ธ์ฆ(ISO 14001, ISO 45001)์„ ์ทจ๋“ํ•˜์—ฌ ๋ฒ•๊ทœ์ค€์ˆ˜ ๋ฐ ์ž์œจ๊ด€๋ฆฌ ์ฒด์ œ๋ฅผ ๊ฐ•ํ™”ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

์ฃผ์š”ํ•œ ๊ด€๋ จ ๋ฒ•๋ฅ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.1. ํ™˜๊ฒฝ์˜ค์—ผ๋ฌผ์งˆ ๋ฐฐ์ถœ ๊ทœ์ œ:ใ€Œ๋ฌผํ™˜๊ฒฝ๋ณด์ „๋ฒ•ใ€ใ€Œ๋Œ€๊ธฐํ™˜๊ฒฝ๋ณด์ „๋ฒ•ใ€ใ€Œํ๊ธฐ๋ฌผ๊ด€๋ฆฌ๋ฒ•ใ€ ใ€Œ์†Œ์Œใ†์ง„๋™๊ด€๋ฆฌ๋ฒ•ใ€ใ€Œํ™˜๊ฒฝ์˜ํ–ฅํ‰๊ฐ€๋ฒ•ใ€๋“ฑ

+

2. ์˜จ์‹ค๊ฐ€์Šค ๋ฐฐ์ถœ ๊ด€๋ฆฌ:ใ€Œ์˜จ์‹ค๊ฐ€์Šค ๋ฐฐ์ถœ๊ถŒ์˜ ํ• ๋‹น ๋ฐ ๊ฑฐ๋ž˜์— ๊ด€ํ•œ ๋ฒ•๋ฅ ใ€ ใ€Œ๊ธฐํ›„์œ„๊ธฐ๋Œ€์‘์„ ์œ„ํ•œ ํƒ„์†Œ์ค‘๋ฆฝใ†๋…น์ƒ‰์„ฑ์žฅ๊ธฐ๋ณธ๋ฒ•ใ€๋“ฑ3. ๊ธฐํƒ€ ์‚ฌ์—…์žฅํ™˜๊ฒฝ๊ด€๋ฆฌ๋ฒ•:ใ€Œํ™”ํ•™๋ฌผ์งˆ๊ด€๋ฆฌ๋ฒ•ใ€ ใ€Œํ™”ํ•™๋ฌผ์งˆ์˜ ๋“ฑ๋ก ๋ฐ ํ‰๊ฐ€ ๋“ฑ์— ๊ด€ํ•œ ๋ฒ•๋ฅ ใ€ ใ€Œ์•…์ทจ๋ฐฉ์ง€๋ฒ•ใ€ใ€Œํ† ์–‘ํ™˜๊ฒฝ๋ณด์ „๋ฒ•ใ€๋“ฑ

+ + +

+(์˜จ์‹ค๊ฐ€์Šค ๋ฐฐ์ถœ๋Ÿ‰ ๋ฐ ์—๋„ˆ์ง€ ์‚ฌ์šฉ๋Ÿ‰ ๊ด€๋ฆฌ) +๋‹น์‚ฌ๋Š” ๊ตญ๋‚ด์—์„œใ€Œ๊ธฐํ›„์œ„๊ธฐ ๋Œ€์‘์„ ์œ„ํ•œ ํƒ„์†Œ์ค‘๋ฆฝใ†๋…น์ƒ‰์„ฑ์žฅ๊ธฐ๋ณธ๋ฒ•ใ€์ œ27์กฐ(๊ด€๋ฆฌ์—…์ฒด์˜ ์˜จ์‹ค๊ฐ€์Šค ๋ชฉํ‘œ๊ด€๋ฆฌ)์— ๋”ฐ๋ฅธ ์˜จ์‹ค๊ฐ€์Šค ์—๋„ˆ์ง€ ๋ชฉํ‘œ๊ด€๋ฆฌ์—…์ฒด์— ํ•ด๋‹น๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋™๋ฒ• ์ œ27์กฐ 3ํ•ญ ๋ฐ ์˜จ์‹ค๊ฐ€์Šค ๋ฐฐ์ถœ๊ถŒ์˜ ํ• ๋‹น ๋ฐ ๊ฑฐ๋ž˜์— ๊ด€ํ•œ ๋ฒ•๋ฅ  ์ œ24์กฐ(๋ฐฐ์ถœ๋Ÿ‰์˜ ๋ณด๊ณ  ๋ฐ ๊ฒ€์ฆ)์— ๋”ฐ๋ผ ์ œ3์ž ๊ฒ€์ฆ์„ ๋งˆ์นœ ๋‹น์‚ฌ์˜ ์˜จ์‹ค๊ฐ€์Šค ๋ฐฐ์ถœ๋Ÿ‰๊ณผ ์—๋„ˆ์ง€ ์‚ฌ์šฉ๋Ÿ‰์„ 2011๋…„ 5์›”๋ถ€ํ„ฐ ์ •๋ถ€ ๋‹น๊ตญ์— ์‹ ๊ณ ํ•˜๊ณ  ์ดํ•ด๊ด€๊ณ„์ž์—๊ฒŒ ๊ณต๊ฐœํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.์ •๋ถ€์— ์‹ ๊ณ ๋œ ๋‹น์‚ฌ์˜ ๊ตญ๋‚ด ์˜จ์‹ค๊ฐ€์Šค ๋ฐฐ์ถœ๋Ÿ‰ ๋ฐ ์—๋„ˆ์ง€ ์‚ฌ์šฉ๋Ÿ‰์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. +

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+

+

๋‹น์‚ฌ๋Š” 2015๋…„๋ถ€ํ„ฐใ€Œ์˜จ์‹ค๊ฐ€์Šค ๋ฐฐ์ถœ๊ถŒ์˜ ํ• ๋‹น ๋ฐ ๊ฑฐ๋ž˜์— ๊ด€ํ•œ ๋ฒ•๋ฅ ใ€์ œ8์กฐ(ํ• ๋‹น๋Œ€์ƒ์—…์ฒด์˜ ์ง€์ • ๋ฐ ์ง€์ •์ทจ์†Œ)์— ๋”ฐ๋ผ ์˜จ์‹ค๊ฐ€์Šค ๋ฐฐ์ถœ๊ถŒ ํ• ๋‹น ๋Œ€์ƒ์—…์ฒด์— ํ•ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

+ + +

+๋‹ค. ์‚ฌ์—…๋ถ€๋ฌธ๋ณ„ ํ˜„ํ™ฉ + +[DX ๋ถ€๋ฌธ] (์‚ฐ์—…์˜ ํŠน์„ฑ ๋“ฑ) + +

+

1) TV์‚ฐ์—…TV ์‚ฐ์—…์€ 1926๋…„ ํ‘๋ฐฑ TV, 1954๋…„ ์ปฌ๋Ÿฌ TV ๋“ฑ์žฅ ์ดํ›„ ์ง€๊ธˆ๊นŒ์ง€, ํ‰๋ฉด TV (LCD),

+

์Šค๋งˆํŠธ TV, ์ดˆ๋Œ€ํ˜• TV, OLED, QLED, Micro LED, RGB ๋“ฑ์œผ๋กœ ๋ฐœ์ „ํ•˜๋ฉด์„œ ์ตœ๊ณ  ํ™”์งˆ์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•œ ํ˜์‹ ํ™œ๋™๋“ค์ด ์ง€์†์ ์œผ๋กœ ์ผ์–ด๋‚˜๋Š” ๋ถ„์•ผ์ž…๋‹ˆ๋‹ค.

+

+

TV์—๋Š” ์นฉ ์„ค๊ณ„, ํšŒ๋กœ๊ฐœ๋ฐœ, ํ™”์งˆ/์‹ ํ˜ธ์ฒ˜๋ฆฌ, AI ํ™”์งˆ ๊ธฐ์ˆ  ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ํ”Œ๋žซํผ๊ณผ

+

์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ž˜ ์ž‘๋™๋˜๋„๋ก ํ•˜๋Š” ์šด์˜์ฒด๊ณ„(Operating System)์™€ ๊ฐ์ข… S/W ๊ธฐ์ˆ ๋“ค์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ํŠนํžˆ AI ๊ธฐ์ˆ ์˜ ์„ฑ์žฅ์œผ๋กœ ์ธํ•ด TV ํ™”๋ฉด์— ๋‚˜์˜ค๋Š” ์ƒํ’ˆ๊ด€๋ จ ์ •๋ณด๋ฅผ

+

์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฒ€์ƒ‰ํ•˜๊ฑฐ๋‚˜ ๊ด€๋ จ๋œ ์ฝ˜ํ…์ธ ๋“ค์„ ์ถ”์ฒœ๋ฐ›์„ ์ˆ˜๋„ ์žˆ๊ณ , ์‚ฌ์šฉ์ž ์ทจํ–ฅ์— ๋งž๋Š”

+

์˜ˆ์ˆ  ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ๋Œ€ํ™” ๋ฐฉ์‹์œผ๋กœ ๊ธฐ๊ธฐ๋ฅผ ์ œ์–ดํ•˜๋Š” ์ˆ˜์ค€๊นŒ์ง€ ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

+

TV ์—…๊ณ„๋Š” OLED, QLED, MiniLED, Micro LED ๋ฐ ์‹ ๊ทœ RGB ๊ธฐ์ˆ ์„ ์ ์šฉํ•ด ์ตœ๊ณ  ํ™”์งˆ์„ ๊ตฌํ˜„ํ•˜๋Š” ์ œํ’ˆ ๊ฒฝ์Ÿ์„ ์น˜์—ดํ•˜๊ฒŒ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ณผ๊ฑฐ 55~65์ธ์น˜ ๋Œ€๊ฐ€ ์ฃผ๋ ฅ์ด๋˜ TV ์‹œ์žฅ์€ 75~85์ธ์น˜ ๋Œ€๊ฐ€ ์ฃผ๋„ํ•˜๋Š” ์‹œ๋Œ€๋ฅผ ๋„˜์–ด์„œ 98, 100, 115, 130์ธ์น˜ ๊ธ‰์˜ ์ดˆ๋Œ€ํ˜• ์ œํ’ˆ๋“ค์ด ์„ฑ์žฅ์„ ์ด๋Œ์–ด ๊ฐˆ ๊ฒƒ์œผ๋กœ ์ „๋ง๋ฉ๋‹ˆ๋‹ค. ์ด์— ๋”ฐ๋ผ ๊ธฐ์กด ์ผ๋ฐ˜ ํ™”์งˆ์„ ์ดˆ๋Œ€ํ˜• ๊ณ ํ™”์งˆ๋กœ ์ž๋™ ์—…์Šค์ผ€์ผํ•˜๋Š” ๊ธฐ์ˆ ๋“ค๋„ ์ œํ’ˆ์— ์ ์šฉ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

+

TV ์—…๊ณ„๋Š” AI ๊ธฐ์ˆ ๋„ ๋Œ€๊ฑฐ ์ฑ„ํƒํ•˜์—ฌ ๋ฏธ๋ž˜ํ˜• TV์˜ ๋ชจ์Šต์„ ๋งŒ๋“ค์–ด ๋‚˜๊ฐ€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

์—…์ฒด๋“ค๋งˆ๋‹ค AI ๊ธฐ์ˆ ์„ ์ œํ’ˆ์— ์ ์šฉํ•ด ํ™”์งˆ๊ณผ ์Œ์งˆ์„ ์ตœ์ ํ™” ํ•ด์ฃผ๊ณ  ์ฝ˜ํ…์ธ  ์ถ”์ฒœ๊ณผ ์‹ค์‹œ๊ฐ„ ๋ฒˆ์—ญ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ์Šคํฌ์ธ /์˜ˆ์ˆ /๊ฒŒ์ž„ ๋“ฑ ์ผ์ƒ์ ์ธ ์ฝ˜ํ…์ธ  ์†Œ๋น„ ๊ณผ์ •์—์„œ ์ฆ‰๊ฐ์ ์ธ ์ •๋ณด ํƒ์ƒ‰ ๋ฐ ์ž๋™ ๋งž์ถคํ˜• ์กฐ์ • ๊ธฐ๋Šฅ ๋“ฑ ๊ธฐ์ˆ ์  ์ˆ˜์น˜๋ณด๋‹ค๋Š” ์†Œ๋น„์ž ๊ฒฝํ—˜ ์ค‘์‹ฌ์œผ๋กœ ํŽธ์˜๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์™€ ๋”๋ถˆ์–ด ๋‹ค์–‘ํ•œ OS์˜ ํ™•์žฅ ๋ฐ ๊ด‘๊ณ  ํ”Œ๋žซํผ ๋“ฑ์„ ํ†ตํ•ด H/W ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ S/W์™€ ์„œ๋น„์Šค ๋ถ„์•ผ์—์„œ๋„ TV ์—…๊ณ„๋Š” ์„ฑ์žฅํ•ด ๊ฐ€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

+

ํ•œํŽธ TV ์‚ฌ์šฉ์ž๋“ค์˜ ์ธ์‹๋„ ์ง€๋‚œ ์ˆ˜๋…„๊ฐ„์˜ ํŒฌ๋ฐ๋ฏน ์‹œ๊ธฐ๋ฅผ ๊ฑฐ์น˜๋ฉด์„œ ํฌ๊ฒŒ ๋ณ€ํ™”ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋“ค์€ TV๋ฅผ ํ†ตํ•ด ์˜ํ™”๋ฅผ ๋ณด๊ณ  ๋‰ด์Šค๋ฅผ ์‹œ์ฒญํ•˜๋Š” ๊ฒƒ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ์ „์„ธ๊ณ„ ๋ฏธ์ˆ ๊ด€์˜ ์˜ˆ์ˆ ์ž‘ํ’ˆ๋“ค์„ ๋‹ค์šด๋กœ๋“œ๋ฐ›์•„์„œ ๊ฐ์ƒํ•œ๋‹ค๊ฑฐ๋‚˜, ๊ฒŒ์ž„๊ณผ ํ—ฌ์Šค์ผ€์–ด, ํ™”์ƒํšŒ์˜์™€ ์˜จ๋ผ์ธ ๊ต์œก๊ณผ ๊ฐ™์€ ์ƒˆ๋กœ์šด ์„œ๋น„์Šค๋ฅผ ์ฒดํ—˜ํ•˜๋Š”๋ฐ ๊ธ์ •์ ์ธ ๋ฐ˜์‘์„ ๋ณด์ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ž๊ธฐ๋งŒ์˜ ๊ฐœ์„ฑ์„ ์ถ”๊ตฌํ•˜๋Š” ๊ตฌ๋งค์ž๋“ค์„ ์œ„ํ•ด ๋””์ž์ธ๊ณผ ์ด๋™์„ค์น˜์„ฑ, ์ฃผ๋ณ€๊ธฐ๊ธฐ๋“ค๊ณผ์˜ ์—ฐ๋™์„ฑ์„ ๊ฐ•์กฐํ•œ ๋ผ์ดํ”„์Šคํƒ€์ผ ์ œํ’ˆ๊ตฐ๋„ ๋งค๋…„ ์ƒˆ๋กญ๊ฒŒ ์ถœ์‹œ๋˜๋ฉด์„œ ํ‹ˆ์ƒˆ ์‹œ์žฅ์ด ๋น ๋ฅด๊ฒŒ ํ™•๋Œ€๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

+

2) ๋ชจ๋ฐ”์ผ ์‚ฐ์—…

+

๋ชจ๋ฐ”์ผ ์‚ฐ์—…์€ 1980๋…„๋Œ€ ์ดˆ ์Œ์„ฑํ†ตํ™”๋งŒ ๊ฐ€๋Šฅํ–ˆ๋˜ 1์„ธ๋Œ€ ์•„๋‚ ๋กœ๊ทธ ๋ฐฉ์‹์œผ๋กœ ์‹œ์ž‘ํ•˜์—ฌ ์Œ์„ฑ ๋ฐ ๋ฌธ์ž ๋ฉ”์‹œ์ง€ ์ „์†ก์ด ๊ฐ€๋Šฅํ•œ CDMA์™€ GSM์˜ 2์„ธ๋Œ€ ๋””์ง€ํ„ธ ๋ฐฉ์‹์„ ๊ฑฐ์ณ, ์Œ์„ฑ ๋ฐ์ดํ„ฐ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ์‚ฌ์ง„, ๋™์˜์ƒ๊ณผ ๊ฐ™์€ ๋ฉ€ํ‹ฐ๋ฏธ๋””์–ด ๋ฐ์ดํ„ฐ๊นŒ์ง€ ์ „์†ก ๊ฐ€๋Šฅํ•œ WCDMA ๋“ฑ์˜ 3์„ธ๋Œ€ ์ด๋™ํ†ต์‹ ์œผ๋กœ ๋ฐœ์ „ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ดํ›„ ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ์˜ ์ดˆ๊ณ ์† ์ „์†ก์ด ๊ฐ€๋Šฅํ•œ 4์„ธ๋Œ€ LTE ์„œ๋น„์Šค๊ฐ€ ์ „ ์„ธ๊ณ„๋กœ ํ™•์‚ฐ๋˜์—ˆ์œผ๋ฉฐ, 2019๋…„ ์ดˆ 5์„ธ๋Œ€ ์ด๋™ํ†ต์‹  5G ์„œ๋น„์Šค๊ฐ€ ํ•œ๊ตญ๊ณผ ๋ฏธ๊ตญ์„ ์‹œ์ž‘์œผ๋กœ ๊ธ€๋กœ๋ฒŒ ์ƒ์šฉํ™” ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. 5G ์Šค๋งˆํŠธํฐ์€ 2020๋…„ 2.7์–ต๋Œ€์—์„œ 2025๋…„์—๋Š” 9.7์–ต๋Œ€๋กœ ํŒ๋งค๊ฐ€ ํ™•๋Œ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ 5G์˜ํ™•์‚ฐ์€ AIใ† XRใ†์ž์œจ์ฃผํ–‰ ๋“ฑ 5G ์œตํ•ฉ ์„œ๋น„์Šค ๋ฐ ๊ธฐ๋ฐ˜ ์‚ฐ์—…์˜ ์ƒํƒœ๊ณ„ ์กฐ์„ฑ์„ ๊ฐ€์†ํ™”ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค(์ถœ์ฒ˜: TechInsights 2026.02).

+

+

์Šค๋งˆํŠธํฐ ์‹œ์žฅ์€ 2007๋…„ ์ดํ›„ ํฐ ํญ์œผ๋กœ ์„ฑ์žฅํ•˜์—ฌ 2025๋…„ ์ „์ฒด ํœด๋Œ€ํฐ ํŒ๋งค๋Ÿ‰ ์ค‘ ์Šค๋งˆํŠธํฐ์˜ ๋น„์ค‘์€ 88% ์ˆ˜์ค€, ํ”ผ์ฒ˜ํฐ์˜ ๋น„์ค‘์€ ์ผ๋ถ€ ์‹ ํฅ ์‹œ์žฅ์—์„œ์˜ ์ˆ˜์š”๋กœ ์ธํ•ด 12% ์ˆ˜์ค€์ž…๋‹ˆ๋‹ค(์ถœ์ฒ˜: TechInsights 2026.02). ์Šค๋งˆํŠธํฐ ์‹œ์žฅ์ด ์„ฑ์ˆ™ํ•จ์— ๋”ฐ๋ผ ๊ณ ์„ฑ๋Šฅ AP, ๊ณ ํ™”์งˆ Display, ํด๋”๋ธ” ํผํŒฉํ„ฐ, ๋ฉ€ํ‹ฐ์นด๋ฉ”๋ผ, ์„ผ์„œ, ๋ฐฉ์ˆ˜ใ†๋ฐฉ์ง„, ์ƒ์ฒด ์ธ์‹ ๋“ฑ๊ณผ ๊ฐ™์€ ํ•˜๋“œ์›จ์–ด๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ํ”Œ๋žซํผ ๊ธฐ๋ฐ˜์˜ Application, UX, Game, Media, DigitalWallet, AI, ๋ณด์•ˆ ๋“ฑ ์ „๋ฐ˜์ ์ธ ์†Œํ”„ํŠธ์›จ์–ด ๊ธฐ๋ฐ˜ ๊ฒฝํ—˜ ๊ฒฝ์Ÿ๋ ฅ์˜ ์ค‘์š”์„ฑ์ด ๋”์šฑ ๋ถ€๊ฐ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+ + +

+(๊ตญ๋‚ด์™ธ ์‹œ์žฅ์—ฌ๊ฑด ๋“ฑ)1) TV์‚ฐ์—…TV ์‹œ์žฅ ์ˆ˜์š”๋Š” ๋ณดํ˜ธ๋ฌด์—ญ ๊ธฐ์กฐ ํ™•์‚ฐ ๋“ฑ ๊ธ€๋กœ๋ฒŒ ๊ฒฝ๊ธฐ ๋ถˆํ™•์‹ค์„ฑ์ด ํ™•๋Œ€๋˜๋ฉด์„œ ์ „๋…„๊ณผ ๋™๋“ฑ ์ˆ˜์ค€์ธ 2.1์–ต๋Œ€๋ฅผ ๊ธฐ๋กํ•  ๊ฒƒ์œผ๋กœ ์ „๋ง๋ฉ๋‹ˆ๋‹ค(์ถœ์ฒ˜: Omdia 2025.12). +

+

< TV ์‹œ์žฅ์ ์œ ์œจ ์ถ”์ด >

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+

+

2) ๋ชจ๋ฐ”์ผ ์‚ฐ์—…์Šค๋งˆํŠธํฐ ์‹œ์žฅ์€ ๋ณดํ˜ธ๋ฌด์—ญ ๊ธฐ์กฐ ํ™•์‚ฐ ๋“ฑ ๋ถˆ์•ˆ์ •ํ•œ ๋Œ€์™ธํ™˜๊ฒฝ ์†์—์„œ 2024๋…„ 12.2์–ต๋Œ€์—์„œ 2025๋…„ 12.5์–ต๋Œ€ ๊ทœ๋ชจ๋กœ ์„ฑ์žฅํ•˜์˜€์Šต๋‹ˆ๋‹ค. 2026๋…„์€ ์ฃผ์š” ๋ถ€ํ’ˆ ๊ฐ€๊ฒฉ ์ƒ์Šน ๋ถ€๋‹ด์œผ๋กœ ์‹œ์žฅ ๊ทœ๋ชจ๊ฐ€ 11.9์–ต๋Œ€ ์ˆ˜์ค€์œผ๋กœ ๊ฐ์†Œํ•  ์ „๋ง์ด๋‚˜, AI ๊ธฐ๋Šฅ ๊ฐœ์„ , ํด๋”๋ธ” ์‹œ์žฅ ํ™•๋Œ€ ๋“ฑ์œผ๋กœ ํ”„๋ฆฌ๋ฏธ์—„ ์ˆ˜์š”๋Š” ์œ ์ง€๋  ๊ฒƒ์œผ๋กœ ์ „๋ง๋ฉ๋‹ˆ๋‹ค.

+

ํƒœ๋ธ”๋ฆฟ ์‹œ์žฅ์€ ๊ต์ฒด ์ฃผ๊ธฐ๊ฐ€ ์„ฑ์ˆ™ ๋‹จ๊ณ„์— ์ง„์ž…ํ•จ์— ๋”ฐ๋ผ 2024๋…„ 1.5์–ต๋Œ€์—์„œ 2025๋…„ 1.6์–ต๋Œ€๋กœ ์†Œํญ ์„ฑ์žฅํ•˜์˜€์œผ๋‚˜, 2026๋…„์€ ๋ถ€ํ’ˆ๊ฐ€ ์ƒ์Šน์— ๋”ฐ๋ฅธ ํ‰๊ท ํŒ๋งค๊ฐ€๊ฒฉ ์ƒ์Šน์œผ๋กœ ์ˆ˜์š”๊ฐ€ 1.4์–ต๋Œ€ ์ˆ˜์ค€์œผ๋กœ ๊ฐ์†Œํ•  ์ „๋ง์ž…๋‹ˆ๋‹ค.(์ถœ์ฒ˜: TechInsights 2026.02).

+

< ์Šค๋งˆํŠธํฐ ์‹œ์žฅ์ ์œ ์œจ ์ถ”์ด >

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ + +

+(์˜์—…์˜ ๊ฐœํ™ฉ ๋“ฑ)1) TV์‚ฐ์—…๋‹น์‚ฌ๋Š” 2006๋…„ ์ „์„ธ๊ณ„ TV ํŒ๋งค 1์œ„๋ฅผ ๋‹ฌ์„ฑํ•œ ์ดํ›„ 2025๋…„๊นŒ์ง€ 20๋…„ ์—ฐ์†์œผ๋กœ TV ์ตœ๊ณ  ์ž๋ฆฌ๋ฅผ ์ง€ํ‚ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ธฐ๊ฐ„๋™์•ˆ ๊ณต๊ฒฉ์ ์œผ๋กœ ์˜์—…,๋งˆ์ผ€ํŒ… ํ™œ๋™์„ ์ „๊ฐœํ•˜์—ฌ ์‹œ์žฅ ์ ์œ ์œจ์„ 2006๋…„๋ง ๊ธฐ์ค€ 14.2%์—์„œ 2025๋…„๋ง ๊ธฐ์ค€ ์•ฝ 29%๋กœ ๋‘๋ฐฐ ์ฆ๊ฐ€์‹œ์ผฐ์Šต๋‹ˆ๋‹ค. +

+

+

๊ธ€๋กœ๋ฒŒ TV ์‹œ์žฅ์€ 75์ธ์น˜ ์ด์ƒ ๋Œ€ํ˜• TV์™€ $1,000 ๋ณด๊ธ‰ํ˜• TV๊ตฐ์ด ์„ฑ์žฅ์„ธ๋ฅผ ์ฃผ๋„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์— ๋”ฐ๋ผ ์—…์ฒด๋“ค๊ฐ„ ๊ฒฝ์Ÿ์ด ์ ์  ๋” ์น˜์—ดํ•ด ์ง€๊ณ  ์žˆ๊ณ , ํŒจ๋„๊ฐ€๊ฒฉ ์ƒ์Šน์„ธ๋„ ์ง€์† ์ค‘์ด์–ด์„œ ์ˆ˜์ต์„ฑ ํ™•๋ณด์— ์–ด๋ ค์›€์ด ๊ฐ€์ค‘๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

+

๋‹น์‚ฌ๋Š” ์–ด๋ ค์šด ๊ฒฝ์˜ํ™˜๊ฒฝ์—์„œ๋„ ์‹œ์žฅ์ ์œ ์œจ์„ ํ™•๋Œ€ํ•˜๊ณ  ์ˆ˜์ต์„ฑ์„ ์ œ๊ณ ํ•˜๊ธฐ ์œ„ํ•ด, 2025๋…„๋„์—๋Š” AI TV ๋ผ์ธ์—…์„ ๊ธฐ์กด Neo QLED์™€ OLED ์—์„œ QLED์™€ ๋” ํ”„๋ ˆ์ž„์œผ๋กœ ํ™•๋Œ€ํ•˜๊ณ  ์ดˆ๋Œ€ํ˜• TV, Q์‹œ๋ฆฌ์ฆˆ ์‚ฌ์šด๋“œ๋ฐ” ๋“ฑ ๋‹ค์–‘ํ•œ ์นดํ…Œ๊ณ ๋ฆฌ์—์„œ ์‹ ์ œํ’ˆ์„ ์ถœ์‹œํ•˜์˜€์Šต๋‹ˆ๋‹ค.

+

AI TV๋Š” ์ž‘๋…„ 9๊ฐœ ์‹œ๋ฆฌ์ฆˆ 34๊ฐœ ๋ชจ๋ธ์—์„œ ์˜ฌํ•ด 11๊ฐœ ์‹œ๋ฆฌ์ฆˆ 58๊ฐœ ๋ชจ๋ธ๋กœ ๋ผ์ธ์—…์ด ํฌ๊ฒŒ ๋Š˜์–ด๋‚˜์„œ ์†Œ๋น„์ž ์„ ํƒ์˜ ํญ์ด ๋„“์–ด์กŒ์Šต๋‹ˆ๋‹ค. ์‚ผ์„ฑ AI TV๋Š” 'AI ํ™ˆ' , 'AI์–ด์‹œ์Šคํ„ดํŠธ' , 'AI์‹œ์ฒญ ์ตœ์ ํ™”' ์˜ ์„ธ๊ฐ€์ง€ ํ•ต์‹ฌ AI ๊ธฐ๋Šฅ๋“ค์„ ์ œํ’ˆ์— ์ฑ„์šฉํ•˜์—ฌ ์†Œ๋น„์ž๋“ค์—๊ฒŒ ํŽธ๋ฆฌํ•œ ์Šค๋งˆํŠธํ™ˆ ์‚ฌ์šฉ ๊ฒฝํ—˜์„ ๋น„๋กฏ, ์‹ค์‹œ๊ฐ„ ๋ฒˆ์—ญ ๋ฐ ์˜์ƒ์ •๋ณด ๊ฒ€์ƒ‰, AI๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ ๋œ ํ™”์งˆ๊ณผ ์‚ฌ์šด๋“œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

+

+

2025๋…„ํ˜• Neo QLED 8K๋Š” 2๊ฐœ ์‹œ๋ฆฌ์ฆˆ (QN990F, QN900F), 7๊ฐœ ๋ชจ๋ธ์„ ์ถœ์‹œํ–ˆ์Šต๋‹ˆ๋‹ค. Neo QLED 4K๋Š” ์ด 4๊ฐœ ์‹œ๋ฆฌ์ฆˆ (QN90F, QN85F, QN80F, QN70F), 22๊ฐœ ๋ชจ๋ธ๋กœ ๋ผ์ธ์—…์„ ๊ตฌ์„ฑํ–ˆ๊ณ , ๊ธฐ์กด ์ตœ๋Œ€ ํ™”๋ฉด ํฌ๊ธฐ์˜€๋˜ 98์ธ์น˜์—์„œ ์˜ฌํ•ด 100์ธ์น˜, 115์ธ์น˜ ์ œํ’ˆ์„ ์ƒˆ๋กญ๊ฒŒ ์ถ”๊ฐ€ํ•ด ์ดˆ๋Œ€ํ˜• TV ์‹œ์žฅ ํ™•๋Œ€์— ๋”์šฑ ์ฃผ๋ ฅํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

+

+

์‚ผ์„ฑ OLED ๋Š” 3๊ฐœ ์‹œ๋ฆฌ์ฆˆ์—์„œ 14๊ฐœ ๋ชจ๋ธ์„ ์ถœ์‹œํ•˜์—ฌ 42์ธ์น˜๋ถ€ํ„ฐ 83์ธ์น˜๊นŒ์ง€ ๋ผ์ธ์—…์„ ํ™•๋Œ€ํ–ˆ์œผ๋ฉฐ, ํŠธ๋ฆฌํ”Œ ๋ ˆ์ด์ € ๊ธฐ์ˆ ๋กœ ์•ฝ 43cm์˜ ์งง์€ ๊ฑฐ๋ฆฌ์—์„œ๋„ ์ตœ๋Œ€ 100์ธ์น˜ ๋Œ€ํ™”๋ฉด์„ ํˆฌ์‚ฌํ•  ์ˆ˜ ์žˆ๋Š” ์ดˆ๋‹จ์ดˆ์  ํ”„๋กœ์ ํ„ฐ '๋” ํ”„๋ฆฌ๋ฏธ์–ด5' ๋„ ์ถœ์‹œํ•˜์˜€์Šต๋‹ˆ๋‹ค.

+

+

12๋…„ ์—ฐ์† ๊ธ€๋กœ๋ฒŒ ํŒ๋งค 1์œ„๋ฅผ ๊ธฐ๋กํ•œ ์‚ผ์„ฑ ์‚ฌ์šด๋“œ๋ฐ”๋Š” ์„ฑ๋Šฅ์ด ํ•œ์ธต ํ–ฅ์ƒ๋์Šต๋‹ˆ๋‹ค. 2025๋…„ํ˜• 'Q์‹œ๋ฆฌ์ฆˆ ์‚ฌ์šด๋“œ๋ฐ” (HW-Q990F, HW-Q930F, HW-Q800F, HW-QS700F)' ๋Š” ์„œ๋ธŒ์šฐํผ์˜ ํฌ๊ธฐ๋ฅผ ๊ธฐ์กด ๋Œ€๋น„ ์ ˆ๋ฐ˜์ด์ƒ ์ค„์ด๋ฉด์„œ๋„ ๊ฐ•๋ ฅํ•œ ์ €์Œ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. ์˜ฌํ•ด ์ƒˆ๋กญ๊ฒŒ ์ถœ์‹œ๋œ '์ปจ๋ฒ„ํ„ฐ๋ธ” ์‚ฌ์šด๋“œ๋ฐ”(HW-QS700F)'๋Š” ์‚ฌ์šด๋“œ๋ฐ”์— ๋‚ด์žฅ๋œ ์ž์ด๋กœ ์„ผ์„œ๊ฐ€ ์„ค์น˜ ๋ฐœํ–ฅ์„ ๊ฐ์ง€ํ•ด ์ž๋™์œผ๋กœ ์‚ฌ์šด๋“œ๋ฅผ ์ตœ์ ํ™”ํ•ด ์ค๋‹ˆ๋‹ค.

+

+

์ด์™€ ํ•จ๊ป˜, ๊ธฐ์กด '๋” ํ”„๋ ˆ์ž„' ์—์„œ๋งŒ ์ง€์›๋˜๋˜ '์‚ผ์„ฑ ์•„ํŠธ ์Šคํ† ์–ด'๊ฐ€ 2025๋…„ํ˜• Neo QLED์™€ QLED์ œํ’ˆ์œผ๋กœ๊นŒ์ง€ ํ™•๋Œ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์‚ผ์„ฑ ์•„ํŠธ ์Šคํ† ์–ด๋Š” ์‚ผ์„ฑ TV ์ „์šฉ ์˜ˆ์ˆ ์ž‘ํ’ˆ ๊ตฌ๋… ์„œ๋น„์Šค๋กœ ์•ฝ 70๊ฐœ์˜ ๊ธ€๋กœ๋ฒŒ ์œ ๋ช… ๋ฏธ์ˆ ๊ด€, ๋ฐ•๋ฌผ๊ด€ ๋“ฑ๊ณผ ํ˜‘๋ ฅํ•ด 3,000๊ฐœ ์ด์ƒ์˜ ์„ธ๊ณ„์ ์ธ ์ž‘ํ’ˆ์„ ์ œ๊ณตํ•ด ์ผ์ƒ์—์„œ ์‰ฝ๊ฒŒ ์˜ˆ์ˆ ์„ ์ ‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

+

+

๋˜ 2025๋…„ํ˜• Neo QLED 8K (QN990F) ์ œํ’ˆ๊ณผ ์˜ฌํ•ด ์ƒˆ๋กญ๊ฒŒ ์„ ๋ณด์ธ ๋” ํ”„๋ ˆ์ž„ ํ”„๋กœ (The Frame Pro)์—๋Š” '๋ฌด์„  ์› ์ปค๋„ฅํŠธ ๋ฐ•์Šค'๊ฐ€ ์ƒˆ๋กญ๊ฒŒ ์ ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. '๋ฌด์„  ์› ์ปค๋„ฅํŠธ ๋ฐ•์Šค'๋Š” ๋ฌด์ง€ํ–ฅ์„ฑ ๋ฌด์„  ๊ธฐ์ˆ ์„ ์ ์šฉํ•˜์—ฌ TV์—์„œ ๋ฉ€๋ฆฌ ๋–จ์–ด์ง„ ๊ณณ์—์„œ๋„ ์ฃผ๋ณ€๊ธฐ๊ธฐ๋ฅผ ์—ฐ๊ฒฐํ•˜์—ฌ ๋Š๊น€ ์—†์ด ์‹œ์ฒญ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํšŒ์‚ฌ๋Š” ์ตœ์‹  ๊ธฐ์ˆ ์„ ์ฑ„์šฉํ•œ ์‹ ์ œํ’ˆ๋“ค์„ ์ ๊ธฐ์— ์ถœ์‹œ, ๊ธ€๋กœ๋ฒŒ 1์œ„ ์œ„์น˜๋ฅผ ๊ตณ๊ฑดํžˆ ์ง€์ผœ ๋‚˜๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค.

+ +

2) ๋ชจ๋ฐ”์ผ ์‚ฐ์—…

+

๋‹น์‚ฌ๋Š” ์ฃผ๋ ฅ ์‚ฌ์—…์ธ ์Šค๋งˆํŠธํฐ ์‹œ์žฅ์—์„œ 2011๋…„ ์ดํ›„ ์ง€๋‚œ 15๋…„ ๋™์•ˆ ๊ธ‰๋ณ€ํ•˜๋Š” ์‹œ์žฅ ํ™˜๊ฒฝ ์†์—์„œ๋„ ๊ธ€๋กœ๋ฒŒ ์„ ๋„ ์ง€์œ„๋ฅผ ์œ ์ง€ ์ค‘์ž…๋‹ˆ๋‹ค. ์Šค๋งˆํŠธํฐ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ „์ฒด ๋ชจ๋ฐ”์ผ ์‹œ์žฅ์—์„œ์˜ ์‚ฌ์—… ์œ„์ƒ์„ ๊ฐ•ํ™”ํ•˜๊ธฐ ์œ„ํ•ด ํƒœ๋ธ”๋ฆฟ๊ณผ ์›จ์–ด๋Ÿฌ๋ธ”(์Šค๋งˆํŠธ์›Œ์น˜, ๋ฌด์„ ์ด์–ดํฐ ๋“ฑ), ์•ก์„ธ์„œ๋ฆฌ ๋“ฑ์˜ ์ œํ’ˆ๊ณผ ํ•จ๊ป˜ Digital Health, Digital Wallet ๋“ฑ ์ปจํ…์ธ ์™€ ์„œ๋น„์Šค ๋ถ€๋ฌธ์—์„œ๋„ ์‚ฌ์—… ๊ฒฝ์Ÿ๋ ฅ์„ ๊ฐ•ํ™”ํ•ด ๋‚˜๊ฐ€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

+

๋‹น์‚ฌ๋Š” ํ”„๋ฆฌ๋ฏธ์—„์—์„œ ๋ณด๊ธ‰ํ˜•๊นŒ์ง€ ๋‹ค์–‘ํ•˜๊ณ  ๊ฒฝ์Ÿ๋ ฅ ์žˆ๋Š” ์Šค๋งˆํŠธํฐ ๋ผ์ธ์—…์„ ํ™œ์šฉํ•˜์—ฌ ์ง€์—ญ๋ณ„ ์‹œ์žฅ ์ƒํ™ฉ๊ณผ ๊ฒฝ์Ÿ ํ™˜๊ฒฝ์— ์ตœ์ ํ™”๋œ ์ œํ’ˆ ํฌํŠธํด๋ฆฌ์˜ค๋ฅผ ์šด์˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ํ”„๋ฆฌ๋ฏธ์—„ ์Šค๋งˆํŠธํฐ์€ ์ƒ์„ฑํ˜• AI๋ฅผ ์ ์šฉํ•œ ๊ฒ€์ƒ‰ ใ†์‹ค์‹œ๊ฐ„ ํ†ต๋ฒˆ์—ญ ใ†์ž๋™ ๋‚ด์šฉ ์š”์•ฝ ใ†์‚ฌ์ง„ ํŽธ์ง‘, Dynamic AMOLED 2X(120Hz) ์ง€์›, UWB(Ultra Wideband)๋ฅผ ํ™œ์šฉํ•œ ๋””์ง€ํ„ธํ‚ค ๋ฐ ์ฝ˜ํ…์ธ  ๊ณต์œ , ์ดˆ์ŒํŒŒ ๋ฐฉ์‹์˜ ์˜จ์Šคํฌ๋ฆฐ ์ง€๋ฌธ ์ธ์‹, ์•ผ๊ฐ„ ์ดฌ์˜ ใ†8K ๋™์˜์ƒ ํŠนํ™” ์นด๋ฉ”๋ผ ๊ธฐ๋Šฅ ๋“ฑ ๊ณ ๊ฐ์ด ํ•„์š”๋กœ ํ•˜๋Š” ์ฐจ๋ณ„ํ™”๋œ ๊ธฐ๋Šฅ์„ ์ง€์†ํ•ด์„œ ์„ ๋ณด์ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

+

2019๋…„์—๋Š” ์„ธ๊ณ„ ์ตœ์ดˆ 5G ์Šค๋งˆํŠธํฐ ์ถœ์‹œ๋กœ 5G ์‹œ์žฅ ๋ฆฌ๋”์‹ญ์„ ์„ ์ ํ•˜๊ณ  ํด๋”๋ธ” ๋””์Šคํ”Œ๋ ˆ์ด๋ฅผ ํƒ‘์žฌํ•œ Galaxy Z ํด๋“œ๋กœ ์‹ ๊ทœ ์‹œ์žฅ์„ ๊ฐœ์ฒ™ํ•œ ๋ฐ ์ด์–ด, 2020๋…„์—๋Š” ์ƒํ•˜๋กœ ์ ‘ํžˆ๋Š” Galaxy Z ํ”Œ๋ฆฝ์„ ์ถœ์‹œํ•˜์˜€์Šต๋‹ˆ๋‹ค. Galaxy Z ํ”Œ๋ฆฝ ์‹œ๋ฆฌ์ฆˆ๋Š” ์•„์ด์ฝ”๋‹‰ํ•œ ๋””์ž์ธ๊ณผ ๋†’์€ ํœด๋Œ€์„ฑ์œผ๋กœ, ์—ฌ์„ฑ๊ณผ ์ Š์€ ์„ธ๋Œ€์˜ Galaxy ์„ ํ˜ธ๋„๋ฅผ ๋†’์ด๋Š”๋ฐ ๊ธฐ์—ฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ดํ›„ ๋‹น์‚ฌ๋งŒ์˜ ๊ธฐ์ˆ  ๋ฆฌ๋”์‹ญ๊ณผ ์†Œ๋น„์ž ๊ฐ€์น˜ ์ค‘์‹ฌ์˜ ์‚ฌ์šฉ์„ฑ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํด๋”๋ธ” ํŠนํ™” ๊ฒฝํ—˜์„ ๊ฐ•ํ™”ํ•œ ์‹ ๋ชจ๋ธ์„ ์ง€์† ์ถœ์‹œํ•˜๋ฉฐ ํด๋”๋ธ” ์‹œ์žฅ์„ ์ฃผ๋„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

+

2025๋…„ 7์›” ์ถœ์‹œ๋œ 'Galaxy Z ํด๋“œ7'์€ ์—ญ๋Œ€ Galaxy Z ํด๋“œ ์‹œ๋ฆฌ์ฆˆ ์ค‘ ๊ฐ€์žฅ ์Šฌ๋ฆผํ•˜๊ณ  ๊ฐ€๋ฒผ์šด ๋””์ž์ธ์œผ๋กœ ์ถœ์‹œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ๋” ๋„“์–ด์ง„ ๋””์Šคํ”Œ๋ ˆ์ด์™€ ๊ฐ•๋ ฅํ•œ 2์–ต ํ™”์†Œ ์นด๋ฉ”๋ผ, ํ–ฅ์ƒ๋œ AP ์„ฑ๋Šฅ์„ ํ†ตํ•ด ํ”„๋ฆฌ๋ฏธ์—„ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋™์‹œ์— ์ถœ์‹œ๋œ 'Galaxy Z ํ”Œ๋ฆฝ7'์€ ๋”์šฑ ์–‡๊ณ  ์ฝคํŒฉํŠธํ•œ ๋””์ž์ธ์œผ๋กœ ๋‹ค์–‘ํ•œ ๋ผ์ดํ”„์Šคํƒ€์ผ์—์„œ๋„ ๋›ฐ์–ด๋‚œ ์„ฑ๋Šฅ์„ ๋ฐœํœ˜ํ•ฉ๋‹ˆ๋‹ค.

+ +

ํ•œํŽธ, 2025๋…„ 2์›” ์ถœ์‹œ๋œ Galaxy S25 ์‹œ๋ฆฌ์ฆˆ๋Š” ํ•œ์ธต ๋” ๋ฐœ์ „ํ•œ Galaxy AI๋ฅผ ํ†ตํ•ด ์ž์—ฐ์Šค๋Ÿฝ๊ณ  ๊ฐœ์ธํ™”๋œ ๋ชจ๋ฐ”์ผ ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ†ตํ•ฉํ˜• AI ํ”Œ๋žซํผ์ธ 'One UI 7'์ด ํƒ‘์žฌ๋˜์–ด ์ž์—ฐ์Šค๋Ÿฝ๊ณ  ์ง๊ด€์ ์ธ ์‚ฌ์šฉ์„ฑ์„ ์ œ๊ณตํ•˜๊ณ , ํ…์ŠคํŠธ, ์Œ์„ฑ, ์ด๋ฏธ์ง€ ๋“ฑ ๋‹ค์–‘ํ•œ ์ ‘์ ์—์„œ ์‚ฌ์šฉ์ž์˜ ์ƒํ™ฉ์„ ์ดํ•ดํ•˜๊ณ  ์ทจํ–ฅ์„ ๋ถ„์„ํ•ด ๊ฐœ์ธํ™”๋œ AI ๊ฒฝํ—˜์„ ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์—ญ๋Œ€ ๊ฐ€์žฅ ๊ฐ•๋ ฅํ•œ ์„ฑ๋Šฅ๊ณผ ํ•จ๊ป˜ AI ๊ธฐ๋ฐ˜์˜ ์ฐจ์„ธ๋Œ€ 'ํ”„๋กœ๋น„์ฃผ์–ผ ์—”์ง„(ProVisual Engine)์„ ํƒ‘์žฌํ•ด ํ–ฅ์ƒ๋œ ์นด๋ฉ”๋ผ ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

+

์Šค๋งˆํŠธํฐ ์™ธ์—๋„ ๋Œ€ํ™”๋ฉด ๋””์Šคํ”Œ๋ ˆ์ด์™€ S Pen์œผ๋กœ ๋…ธํŠธ ํ•„๊ธฐ์™€ ์˜์ƒํšŒ์˜, ๋ฉ€ํ‹ฐํƒœ์Šคํ‚น ๋“ฑ ์ƒ์‚ฐ์ ์ธ ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๋Š” ํƒœ๋ธ”๋ฆฟ, ์ƒ์ฒด ์„ผ์„œ ๊ธฐ์ˆ ์„ ์ ์šฉํ•˜์—ฌ ๋”์šฑ ๊ณ ๋„ํ™”๋œ ํ”ผํŠธ๋‹ˆ์Šค ๋ฐ ์›ฐ๋‹ˆ์Šค ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ์Šค๋งˆํŠธ ์›Œ์น˜์™€ ํŽธ์•ˆํ•œ ์ฐฉ์šฉ๊ฐ์œผ๋กœ ๋งž์ถคํ˜• ๊ฑด๊ฐ•์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋Š” ์Šค๋งˆํŠธ๋ง, ํ’๋ถ€ํ•˜๊ณ  ๋›ฐ์–ด๋‚œ ์‚ฌ์šด๋“œ ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๋Š” ๋ฌด์„  ์ด์–ดํฐ ๋“ฑ ๋‹ค์–‘ํ•œ Galaxy Ecosystem ์ œํ’ˆ๊ตฐ์„ ํ†ตํ•ด ๋‹น์‚ฌ ์Šค๋งˆํŠธํฐ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ณ ๊ฐ์—๊ฒŒ ๋”์šฑ ํ’๋ถ€ํ•œ ๋ชจ๋ฐ”์ผ ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

+

๋‹น์‚ฌ๋Š” ์ œํ’ˆ๊ณผ ๋”๋ถˆ์–ด ๊ทธ๋™์•ˆ Samsung Wallet(่ˆŠ Samsung Pay), Samsung Health ๋“ฑ์˜ ์‹ค์šฉ์ ์ด๊ณ  ๊ฐ€์น˜ ์žˆ๋Š” ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•ด ์™”์Šต๋‹ˆ๋‹ค. Samsung Wallet์€ ๊ฒฐ์ œ ์™ธ์—๋„ ์‹ ๋ถ„์ฆ, ํ‹ฐ์ผ“ ๋“ฑ์˜ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์—ฌ ์‹ค๋ฌผ ์ง€๊ฐ‘์„ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ๋Š” ์„œ๋น„์Šค๋กœ ๋ฐœ์ „์‹œ์ผœ ๋‚˜๊ฐ€๊ณ  ์žˆ์œผ๋ฉฐ, Samsung Health๋Š” ์ƒˆ๋กœ์šด ํผํŒฉํ„ฐ์™€ AI ๊ธฐ๋Šฅ ์ ์šฉ์„ ํ†ตํ•ด ํ†ตํ•ฉ ๊ฑด๊ฐ•๊ด€๋ฆฌ ํ”Œ๋žซํผ์œผ๋กœ์„œ์˜ ์œ„์ƒ์„ ๋”์šฑ ๊ฐ•ํ™”ํ•ด ๋‚˜๊ฐ€๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

+

+

๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ œํ’ˆ์— ์žฌํ™œ์šฉ ์†Œ์žฌ ์ ์šฉ์„ ํ™•๋Œ€ํ•˜๋Š” ๋“ฑ, ์นœํ™˜๊ฒฝ ๊ธฐ์ˆ  ํ˜์‹ ๋„ ์ง€์†ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ, Galaxy S25 ์‹œ๋ฆฌ์ฆˆ์—๋Š” ๊ธˆ, ๊ตฌ๋ฆฌ ๋“ฑ 8์ข…์˜ ์žฌํ™œ์šฉ ์†Œ์žฌ๋ฅผ ์‚ฌ์šฉํ•˜์˜€๊ณ , ๋” ๋‚˜์•„๊ฐ€ ๊ตฌํ˜• Galaxy ๋‹จ๋ง์˜ ํ๋ฐฐํ„ฐ๋ฆฌ์—์„œ ์ง์ ‘ ์ฝ”๋ฐœํŠธ๋ฅผ ์ถ”์ถœํ•˜์—ฌ ์žฌ์‚ฌ์šฉํ•˜๋Š” ๋ฐฐํ„ฐ๋ฆฌ ์ˆœํ™˜ ์žฌํ™œ์šฉ ์ฒด๊ณ„๋ฅผ ์ ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

+

+

์ถ”๊ฐ€๋กœ, ๋‹น์‚ฌ๋Š” 2023๋…„ 5์›” ์ž๊ฐ€ ์ˆ˜๋ฆฌ ํ”„๋กœ๊ทธ๋žจ์„ ๋„์ž…ํ•˜๊ณ  ๋Œ€์ƒ ๋ชจ๋ธ๊ณผ ๊ตญ๊ฐ€๋ฅผ ํ™•๋Œ€ํ•ด ๋‚˜๊ฐ€๋ฉฐ e-waste๋ฅผ ์ตœ์†Œํ™”ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

+

๋‹น์‚ฌ๋Š” ์•ž์œผ๋กœ๋„ ์—…๊ณ„ ์ตœ๊ณ  ์ˆ˜์ค€์˜ R&D ์—ญ๋Ÿ‰์„ ๋ฐ”ํƒ•์œผ๋กœ ์ง€์†์ ์ธ ๊ธฐ์ˆ  ํ˜์‹  ๋ฐ ์ฒ ์ €ํ•œ ๋ฏธ๋ž˜ ์ค€๋น„๋ฅผ ํ†ตํ•ด ๊ณ ๊ฐ์—๊ฒŒ ์ƒˆ๋กœ์šด ๊ฐ€์น˜๋ฅผ ๋Š์ž„์—†์ด ์ œ๊ณตํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

+ +

[DS ๋ถ€๋ฌธ]

+ +

+(์‚ฐ์—…์˜ ํŠน์„ฑ ๋“ฑ) +๋ฐ˜๋„์ฒด๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ๊ธฐ์–ตํ•˜๋Š” ๋ฉ”๋ชจ๋ฆฌ ๋ฐ˜๋„์ฒด์™€ ์—ฐ์‚ฐ๊ณผ ์ถ”๋ก  ๋“ฑ ๋…ผ๋ฆฌ์ ์ธ ์ •๋ณด์ฒ˜๋ฆฌ ๊ธฐ๋Šฅ์„ ํ•˜๋Š” System LSI(๋น„๋ฉ”๋ชจ๋ฆฌ ๋ฐ˜๋„์ฒด)๋กœ ๊ตฌ๋ถ„๋ฉ๋‹ˆ๋‹ค. +

+

+

๋ฉ”๋ชจ๋ฆฌ ๋ฐ˜๋„์ฒด๋Š” ํฌ๊ฒŒ ์ฝ๊ณ  ์“ธ ์ˆ˜ ์žˆ๋Š” ๋žจ(RAM)์ œํ’ˆ๊ณผ ์ฝ๊ธฐ๋งŒ ํ•  ์ˆ˜ ์žˆ๋Š” ๋กฌ(ROM)์ œํ’ˆ์œผ๋กœ ๊ตฌ๋ถ„๋ฉ๋‹ˆ๋‹ค. ๋žจ(RAM)์€ ์ „์›์ด ๊บผ์ง€๋ฉด ๊ธฐ์–ต๋œ ๋‚ด์šฉ์€ ์ง€์›Œ์ ธ ํœ˜๋ฐœ์„ฑ ๋ฉ”๋ชจ๋ฆฌ(Volatile Memory)๋ผ๊ณ  ํ•˜๋ฉฐ, ์ปดํ“จํ„ฐ์˜ ์ฃผ๊ธฐ์–ต์žฅ์น˜, ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์˜ ์ผ์‹œ์  ๋กœ๋”ฉ(Loading), ๋ฐ์ดํ„ฐ์˜ ์ผ์‹œ์  ์ €์žฅ ๋“ฑ์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋กฌ(ROM, Read Only Memory)์€ ์ „์›์ด ๊บผ์ ธ๋„ Data๊ฐ€ ์ง€์›Œ์ง€์ง€ ์•Š๋Š” ๋น„ํœ˜๋ฐœ์„ฑ ๋ฉ”๋ชจ๋ฆฌ๋กœ ๋Œ€ํ‘œ์ ์œผ๋กœ ์ž…์ถœ๋ ฅ ์‹œ์Šคํ…œ์ด๋‚˜ IC์นด๋“œ ๋“ฑ์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

+

+

System LSI ์ œํ’ˆ์€ ์‘์šฉ์ฒ˜ ๋“ฑ์— ๋”ฐ๋ผ ์ข…๋ฅ˜๊ฐ€ ๋‹ค์–‘ํ•˜๋ฉฐ ๊ฐ€์žฅ ๊ทœ๋ชจ๊ฐ€ ํฐ ๊ฒƒ์ด PC ๋ฐ

+

๋ชจ๋ฐ”์ผ ๊ธฐ๊ธฐ, ์„œ๋ฒ„(Server) ๋“ฑ์˜ ์ค‘์•™์ฒ˜๋ฆฌ ์žฅ์น˜์ธ CPU(Central Processing Unit)์ด๊ณ  ๊ฐ€์ „, ๋„คํŠธ์›Œํฌ, ๊ฒŒ์ž„ ๋“ฑ ๋‹ค์–‘ํ•œ ๋ถ„์•ผ์— ์ ์šฉ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹น์‚ฌ๋Š” ์Šค๋งˆํŠธํฐ, ํƒœ๋ธ”๋ฆฟ ๋“ฑ ๋ชจ๋ฐ”์ผ์šฉ AP ์ œํ’ˆ๊ณผ ์ด๋ฏธ์ง€์„ผ์„œ ๋“ฑ์„ ๊ณต๊ธ‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

+

Foundry ์‚ฌ์—…์€ ํŒน๋ฆฌ์Šค(Fabless) ์—…์ฒด๊ฐ€ ์„ค๊ณ„ํ•œ ๋ฐ˜๋„์ฒด๋ฅผ ์œ„ํƒ ์ƒ์‚ฐํ•ด์„œ ๊ณต๊ธ‰ํ•ด์ฃผ๋Š”์‚ฌ์—…์œผ๋กœ, ์ผ๋ฐ˜ ์ œ์กฐ์—…์˜ OEM ๊ณต๊ธ‰๊ณผ ๋น„์Šทํ•œ ๊ฐœ๋…์˜ '์ˆ˜ํƒ ๋ฐ˜๋„์ฒด ์ œ์กฐ'์ž…๋‹ˆ๋‹ค. ๋ฐ˜๋„์ฒด ๊ณต์ • ๊ธฐ์ˆ ์˜ ๊ณ ๋„ํ™”์™€ ํˆฌ์ž ๋ถ€๋‹ด์˜ ์ฆ๊ฐ€๋กœ ๋งŽ์€ IDM์—…์ฒด๊ฐ€ ์ƒ์‚ฐ ์‹œ์„ค์„ ๋ณด์œ ํ•˜์ง€ ์•Š์€ ํŒน๋ฆฌ์Šค ์—…์ฒด๋กœ ์ „ํ™˜ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ด์— ๋”ฐ๋ผ ๋Œ€๊ทœ๋ชจ Capa๋ฅผ ๋ณด์œ  ์ค‘์ธ 5๊ฐœ ๋‚ด์™ธ์˜ ์†Œ์ˆ˜์—…์ฒด๊ฐ€ ์ „์ฒด Foundry ์‹œ์žฅ์˜ ๋Œ€๋ถ€๋ถ„์„ ์˜์œ„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+ +

(๊ตญ๋‚ด์™ธ ์‹œ์žฅ์—ฌ๊ฑด ๋“ฑ)

+

2025๋…„ 4๋ถ„๊ธฐ ๋ฉ”๋ชจ๋ฆฌ ์‹œ์žฅ์€ AI ์ธํ”„๋ผ ํ™•๋ณด ๊ฒฝ์Ÿ์— ๋”ฐ๋ฅธ ๋ฐ์ดํ„ฐ์„ผํ„ฐ ์—…์ฒด๋“ค์˜ CAPEX ํ™•๋Œ€ ์˜ํ–ฅ์œผ๋กœ ์„œ๋ฒ„ํ–ฅ ์ˆ˜์š”๊ฐ€ ์ง€์† ์ฆ๊ฐ€ํ•˜๋ฉด์„œ ์—…๊ณ„ ๊ณต๊ธ‰๋Ÿ‰์„ ํฌ๊ฒŒ ์ƒํšŒํ•˜์˜€์Šต๋‹ˆ๋‹ค. PC/๋ชจ๋ฐ”์ผ ์‹œ์žฅ์—์„œ๋„ ๋ฉ”๋ชจ๋ฆฌ ๊ณต๊ธ‰์‚ฌ๋“ค์˜ ์„œ๋ฒ„ ์ค‘์‹ฌ ๊ณต๊ธ‰ ๋Œ€์‘ ์˜ํ–ฅ์œผ๋กœ ํƒ€์ดํŠธํ•œ ์ˆ˜๊ธ‰ ์ƒํ™ฉ์ด ์ง€์† ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

+

+

2026๋…„์—๋„ AI ์ˆ˜์š” ๊ฐ•์„ธ๋Š” ์ง€์†๋  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค. ํŠนํžˆ, ์„œ๋ฒ„ ์‘์šฉ์—์„œ ๊ณ ์„ฑ๋Šฅ HBM4 ์‹œ์žฅ ๋ณธ๊ฒฉํ™”์™€ ๋”๋ถˆ์–ด, ์„œ๋ฒ„ํ–ฅ DRAM์˜ ๊ณ ์šฉ๋Ÿ‰ํ™”๊ฐ€ ๊ฐ€์†ํ™” ๋  ์ „๋ง์ด๋ฉฐ, NAND์˜ ๊ฒฝ์šฐ AI Inferenceํ–ฅ KV SSD์ธ PCIe Gen6 SSD ์ œํ’ˆ ๋„์ž…์œผ๋กœ ๊ณ ์„ฑ๋Šฅ TLC ์ˆ˜์š” ํ™•๋Œ€๊ฐ€ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค. ๋‹ค๋งŒ, PC/๋ชจ๋ฐ”์ผ ์‘์šฉ์˜ ๊ฒฝ์šฐ, ๋ฉ”๋ชจ๋ฆฌ ๊ฐ€๊ฒฉ ์ƒ์Šน์— ๋”ฐ๋ฅธ ๋ฉ”๋ชจ๋ฆฌ ์ฑ„์šฉ๋Ÿ‰ ์ถ•์†Œ ๋ฐ Set ๊ฐ€๊ฒฉ ์ธ์ƒ์ด ์˜ˆ์ƒ๋˜๋ฉฐ, ์ด์— ๋”ฐ๋ฅธ Set ์ถœํ•˜๋Ÿ‰ ์œ„์ถ• ๊ฐ€๋Šฅ์„ฑ์— ๋Œ€ํ•ด ์ง€์† ๋ชจ๋‹ˆํ„ฐ๋ง ํ•  ๊ณ„ํš์ž…๋‹ˆ๋‹ค.

+

+

2025๋…„ Foundry ์‹œ์žฅ์€ AI ๋ฐ HPCํ–ฅ ์„ ๋‹จ ๊ณต์ •์˜ ์ˆ˜์š” ํ˜ธ์กฐ๊ฐ€ ์ง€์†๋œ ๋ฐ˜๋ฉด Mature ๊ณต์ •์€ ์ค‘๊ตญ์„ ์ค‘์‹ฌ์œผ๋กœ ๊ฐ€๊ฒฉ ๊ฒฝ์Ÿ์ด ์‹ฌํ™”๋˜์–ด ์„ฑ์žฅ์ด ์ •์ฒด๋˜์—ˆ์Šต๋‹ˆ๋‹ค. 2026๋…„์€ 1๋ถ„๊ธฐ์— ๊ณ„์ ˆ์  ๋น„์ˆ˜๊ธฐ์— ๋”ฐ๋ฅธ ๊ณ ๊ฐ ์ˆ˜์š” ์ƒ์ €ํ•˜๊ณ  ์˜ํ–ฅ์œผ๋กœ ์—ญ์„ฑ์žฅ์ด ์˜ˆ์ƒ๋˜๋‚˜, ์„ ๋‹จ ๋…ธ๋“œ HPC ๋ฐ ๋ชจ๋ฐ”์ผ ๋ณธ๊ฒฉ ์–‘์‚ฐ์— ๋”ฐ๋ฅธ ๊ฐ•ํ•œ ์ˆ˜์š”๊ฐ€ ๋งค์ถœ ์„ฑ์žฅ์„ ์ฃผ๋„ํ•˜๋ฉฐ ์—ฐ ๋ˆ„์  ๊ธฐ์ค€ ๋‘ ์ž๋ฆฟ์ˆ˜ ์„ฑ์žฅ๋ฅ ์ด ์ „๋ง๋ฉ๋‹ˆ๋‹ค.

+

+

< DRAM ์‹œ์žฅ์ ์œ ์œจ ์ถ”์ด >

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+

(์˜์—…์˜ ๊ฐœํ™ฉ ๋“ฑ)

+

๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์—…์˜ 4๋ถ„๊ธฐ๋Š” ๋‚ฎ์€ ์žฌ๊ณ  ์ˆ˜์ค€ ๋ฐ ์ œํ•œ๋œ ๊ณต๊ธ‰ ๊ฐ€์šฉ๋Ÿ‰ ๋‚ด์—์„œ HBM ํŒ๋งค๋ฅผ ํ™•๋Œ€ํ•˜๋Š” ํ•œํŽธ, ์„œ๋ฒ„ํ–ฅ ๊ณ ๋ถ€๊ฐ€ ์ œํ’ˆ์ธ ๊ณ ์šฉ๋Ÿ‰ DDR5, LPDDR5x ๋ฐ ์„œ๋ฒ„ SSD ์ค‘์‹ฌ์œผ๋กœ ํŒ๋งคํ•˜๋ฉฐ ์ˆ˜์ต์„ฑ ๊ฐœ์„ ์— ์ง‘์ค‘ํ–ˆ์Šต๋‹ˆ๋‹ค.

+

+

2026๋…„์—๋„ ์ œํ’ˆ ๊ฒฝ์Ÿ๋ ฅ์„ ๊ธฐ๋ฐ˜์œผ๋กœ AI ์‹œ๋Œ€๋ฅผ ์„ ๋„ํ•ด ๋‚˜๊ฐˆ ๊ณ„ํš์ž…๋‹ˆ๋‹ค. DRAM์€ AI ์‹œ์žฅ ๋‚ด ์‹ ๊ทœ GPU ๋ฐ ASIC ์‹œ์žฅ์„ ํƒ€๊ฒŸ์œผ๋กœ ์„ฑ๋Šฅ ๊ฒฝ์Ÿ๋ ฅ์„ ๊ฐ–์ถ˜ HBM4๋ฅผ ์ ๊ธฐ์— ๊ณต๊ธ‰ ํ™•๋Œ€ํ•˜์—ฌ ๊ณ ๊ฐ ์ˆ˜์š”์— ์ ๊ทน ๋Œ€์‘ํ•  ๊ณ„ํš์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ, ๊ณ ์šฉ๋Ÿ‰ DDR5, SOCAMM2, GDDR7 ๋“ฑ AI ์—ฐ๊ณ„ ์ œํ’ˆ์˜ ๋น„์ค‘๋„ ์ง€์† ํ™•๋Œ€ํ•ด ๋‚˜๊ฐˆ ๊ณ„ํš์ž…๋‹ˆ๋‹ค. NAND์˜ ๊ฒฝ์šฐ, AIํ–ฅ KV SSD ์ˆ˜์š” ๊ฐ•์„ธ์™€ ์—ฐ๊ณ„ํ•ด TLC ๊ธฐ๋ฐ˜ ๊ณ ์šฉ๋Ÿ‰ Gen5 ์„œ๋ฒ„ SSD ์ค‘์‹ฌ ์ˆ˜์š” ํ™•๋Œ€์— ์ง‘์ค‘ ๋Œ€์‘ํ•  ๊ณ„ํš์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ, ํ•˜๋ฐ˜๊ธฐ ์‹ ๊ทœ GPU ํ”Œ๋žซํผ ๋„์ž…๊ณผ ํ•จ๊ป˜ PCIe Gen6 ์„œ๋ฒ„ ์‹œ์žฅ์˜ ๊ธ‰๊ฒฉํ•œ ํ™•์‚ฐ์ด ์ „๋ง๋˜๊ณ  ์žˆ์–ด, V9 ๊ณ ์„ฑ๋Šฅ ์ œํ’ˆ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ด๋‹น ์‹œ์žฅ์„ ์ดˆ๊ธฐ๋ถ€ํ„ฐ ์ฃผ๋„ํ•ด ๋‚˜๊ฐˆ ๊ณ„ํš์ž…๋‹ˆ๋‹ค.

+

+

System LSI๋Š” ๋ถˆ์•ˆ์ •ํ•œ ๋Œ€์™ธ ํ™˜๊ฒฝ ์†์—์„œ๋„ AI ์„ฑ์žฅ์— ๋”ฐ๋ฅธ ์ค‘์žฅ๊ธฐ ์ˆ˜์š” ํ™•๋Œ€๋ฅผ ๊ธฐํšŒ๋กœ ์‚ผ์•„, ๊ณ ๋ถ€๊ฐ€ ์ˆ˜์ฃผ ํ™•๋Œ€๋ฅผ ํ†ตํ•ด ์ˆ˜์ต ๊ตฌ์กฐ๋ฅผ ๊ฐœ์„ ํ•˜๊ณ  ์‘์šฉ์ฒ˜๋ฅผ ๋‹ค๋ณ€ํ™”ํ•˜๋Š” ์ „๋žต์„ ์ถ”์ง„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. SOC ์‚ฌ์—…์€ ์—…๊ณ„ ์ตœ์ดˆ ๋ชจ๋ฐ”์ผ 2๋‚˜๋…ธ ์ œํ’ˆ์„ ์ ๊ธฐ ์ถœ์‹œํ•˜์—ฌ Galaxy S26 ๋ชจ๋ธ์— ํƒ‘์žฌ ์˜ˆ์ •์ด๋ฉฐ, ๋ชจ๋ฐ”์ผ ์™ธ ์‹ ๊ทœ Custom SOC ์‚ฌ์—… ๊ธฐํšŒ๋ฅผ ๊ฒ€ํ† ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€ ์„ผ์„œ๋Š” ๋ชจ๋ฐ”์ผ 2์–ตํ™”์†Œ ์ œํ’ˆ ๋ผ์ธ์—…์„ ํ™•๋Œ€ํ•˜๋ฉฐ ์‹œ์žฅ๋‚ด ๊ธฐ์ˆ  ๋ฆฌ๋”์‹ญ์„ ํ™•๋ณด ์ค‘์ด๋ฉฐ, ์ฐจ๋Ÿ‰ ๋ฐ ๋…ผ๋ชจ๋ฐ”์ผ ์‹œ์žฅ์œผ๋กœ ํ™•๋Œ€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋ฐ”์ผ DDI๋Š” OLEDํ–ฅ ์„ ๋„์  ์œ„์น˜๋ฅผ ๊ฐ•ํ™”ํ•˜๊ณ  ํŒจ๋„ DDI๋Š” ๋žฉํƒ‘, ํƒœ๋ธ”๋ฆฟ ์‹œ์žฅ์—์„œ OLEDํ–ฅ ํ™•๋Œ€ ์ค‘์ด๋ฉฐ, Power๋Š” Serverํ–ฅ์œผ๋กœ ์‘์šฉ์ฒ˜ ๋‹ค๋ณ€ํ™” ์ค‘์ž…๋‹ˆ๋‹ค.

+

+

Foundry๋Š” ์‹œ์žฅ ๋ณ€ํ™”์— ๋งž์ถ”์–ด ๋…ธ๋“œ ๋ฐ ์‘์šฉ๋ณ„ ์ „๋žต์„ ์ˆ˜๋ฆฝํ•˜์—ฌ ๊ธฐ์ˆ /๊ฐœ๋ฐœ, ์ œ์กฐ, ๋น„์ฆˆ๋‹ˆ์Šค ์—ญ๋Ÿ‰ ๋ถ€๋ถ„์˜ ๊ฒฝ์Ÿ๋ ฅ ๊ฐ•ํ™”๋ฅผ ์ถ”์ง„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. 2๋‚˜๋…ธ๋Š” 1์„ธ๋Œ€ ์ œํ’ˆ ์–‘์‚ฐ ์ดํ›„ ๊ณต์ • ์•ˆ์ •ํ™”์— ์ง‘์ค‘ํ•˜๋ฉด์„œ 2026๋…„ ํ•˜๋ฐ˜๊ธฐ์— ์˜ˆ์ •๋œ ํ›„์† ๊ณต์ • ์–‘์‚ฐ์„ ์œ„ํ•œ ์ ๊ธฐ ๊ฐœ๋ฐœ ์ง„ํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค. 4๋‚˜๋…ธ๋Š” ์•ˆ์ •์ ์ธ ์ˆ˜์œจ์„ ๋ฐ”ํƒ•์œผ๋กœ HBM4 Base-Die ์ œํ’ˆ ์–‘์‚ฐ์„ ์‹œ์ž‘ํ•˜์˜€๊ณ , ์„ฑ๋Šฅ ์ „๋ ฅ ์ตœ์ ํ™” ๊ณต์ •์˜ ์–‘์‚ฐ์„ฑ์„ ํ™•๋ณดํ•˜์—ฌ ๊ฒฝ์Ÿ๋ ฅ์„ ๊ฐ•ํ™”ํ•  ๊ณ„ํš์ž…๋‹ˆ๋‹ค. Mature ๋…ธ๋“œ๋Š” ๊ณ ๋ถ€๊ฐ€๊ฐ€์น˜ ์ œํ’ˆ ํฌํŠธํด๋ฆฌ์˜ค ํ™•๋Œ€๋ฅผ ์œ„ํ•œ Specialty ๊ณต์ • ๊ธฐ์ˆ  ํ™•๋ณด ๋ฐ ๊ณ ๊ฐ ์ˆ˜์ฃผ ๋‹ค๋ณ€ํ™”๋ฅผ ์ถ”์ง„ ์ค‘์œผ๋กœ, ์ˆ˜์ต์„ฑ ๊ฐœ์„ ๊ณผ ๋…ธ๋“œ ์žฅ๊ธฐ ํ™œ์šฉ์ด ๋™์‹œ์— ์ด๋ค„์งˆ ์ˆ˜ ์žˆ๋Š” ์ „๋žต์„ ๋งˆ๋ จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ง€์†์ ์ธ ๊ณ ๊ฐ ํ˜‘๋ ฅ ๋ฐ ๊ฐœ๋ฐœ/์ˆ˜์ฃผ/์ƒ์‚ฐ/๊ณต๊ธ‰ ๋“ฑ ์ „๋ฐฉ์œ„ ๊ฒฝ์Ÿ๋ ฅ์„ ๊ฐ•ํ™”ํ•จ์œผ๋กœ์จ ๋ณ€ํ™”ํ•˜๋Š” ์‹œ์žฅ์— ๋งž์ถฐ ์‚ฌ์—… ๊ธฐํšŒ๋ฅผ ํ™•๋Œ€ํ•ด ๋‚˜๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค.

+ + +

+[SDC] + +(์‚ฐ์—…์˜ ํŠน์„ฑ ๋“ฑ) +

+

๋””์Šคํ”Œ๋ ˆ์ด(Display)๋Š” ๊ฐ์ข… ์ „์ž๊ธฐ๊ธฐ์— ์‚ฌ์šฉ๋˜๋Š” ํ™”๋ฉดํ‘œ์‹œ์žฅ์น˜๋ฅผ ์ง€์นญํ•ฉ๋‹ˆ๋‹ค. ํ‘œ์‹œ๋ฐฉ์‹ ์ธก๋ฉด์—์„œ ํ‘œ์‹œ ์†Œ์ž๊ฐ€ ๋Šฅ๋™์ ์œผ๋กœ ๊ตฌ๋™๋˜๋Š”(Active Matrix) ๋ฐฉ์‹์ด ์ฃผ๋ฅ˜์ด๋ฉฐ, OLED(Organic Light Emitting Diode)์™€ QD-OLED(Quantum Dot-Organic Light Emitting Diode), TFT-LCD(Thin Film Transistor Liquid Crystal Display) ๋“ฑ์ด ์ด์— ํ•ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

+

+

OLED๋Š” ์Šค์Šค๋กœ ๋น›์„ ๋‚ด๋Š” ์œ ๊ธฐ๋ฌผ์งˆ์„ ์ด์šฉํ•œ ํ™”๋ฉดํ‘œ์‹œ์žฅ์น˜๋กœ, ๋ช…์•”๋น„์™€ ์ƒ‰ ์ผ์น˜์œจ์ด ๋†’๊ณ  ์ƒ‰ ์žฌํ˜„ ๋ฒ”์œ„๊ฐ€ ๋„“์œผ๋ฉฐ ์‘๋‹ต ์†๋„๊ฐ€ ๋น ๋ฅด๋‹ค๋Š” ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์žฅ์ ์„ ๋ฐ”ํƒ•์œผ๋กœ OLED ๋””์Šคํ”Œ๋ ˆ์ด๋Š” ๋ฉ€ํ‹ฐ๋ฏธ๋””์–ด ์ฝ˜ํ…์ธ , ์ธํ„ฐ๋„ท ์‚ฌ์šฉ ๋“ฑ ๋””์Šคํ”Œ๋ ˆ์ด์˜ ์„ฑ๋Šฅ์ด ์ค‘์‹œ๋˜๋Š” ์Šค๋งˆํŠธํฐ ์‹œ์žฅ์—์„œ ์ฑ„์šฉ์ด ๋น ๋ฅด๊ฒŒ ์ฆ๊ฐ€ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๊ณผ๊ฑฐ์—๋Š” OLED๋กœ ๊ณ ํ•ด์ƒ๋„์˜ ์žฅ์ˆ˜๋ช… ํŒจ๋„์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•ด ์‹œ์žฅ์˜ ํฐ ์šฐ๋ ค๊ฐ€ ์žˆ์—ˆ์œผ๋‚˜, ๋‹น์‚ฌ๋Š” ๊ธฐ์ˆ ์  ํ•œ๊ณ„๋ฅผ ๊ทน๋ณตํ•˜์—ฌ ์‹œ์žฅ์„ ๋ฐœ์ „์‹œ์ผœ ์™”์Šต๋‹ˆ๋‹ค. OLED๋Š” LCD์˜ ๋‹จ์ ์„ ๊ทน๋ณตํ•  ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ์•ž์œผ๋กœ ํด๋”๋ธ”, IT(ํƒœ๋ธ”๋ฆฟ/๋…ธํŠธ๋ถ), Auto ๋“ฑ ๋‹ค์–‘ํ•œ ์‘์šฉ์ฒ˜๋กœ ํ™•๋Œ€ ์ ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋ฉฐ ์•ž์œผ๋กœ ์‹œ์žฅ์ด ๋”์šฑ ํ™•๋Œ€๋  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค. QD-OLED๋Š” ์ˆ˜๋‚˜๋…ธ ๋ฏธํ„ฐ์˜ ๋ฐ˜๋„์ฒด ๊ฒฐ์ •์ธ ํ€€ํ…€๋‹ท(Quantum Dot)์„ ํŒจ๋„์— ๋‚ด์žฌํ™”ํ•ด ์ƒ‰ ์žฌํ˜„๋ ฅ๊ณผ ์‹œ์•ผ๊ฐ์„ ๊ทน๋Œ€ํ™”ํ•œ ์ž๋ฐœ๊ด‘ ๋””์Šคํ”Œ๋ ˆ์ด๋กœ, ํ–ฅํ›„ ํ”„๋ฆฌ๋ฏธ์—„๊ธ‰ TV ๋ฐ ๋ชจ๋‹ˆํ„ฐ ์‹œ์žฅ์„ ์ฃผ๋„ํ•  ์ฐจ์„ธ๋Œ€ ๋””์Šคํ”Œ๋ ˆ์ด๋กœ ์ฃผ๋ชฉ๋ฐ›๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+ +

(๊ตญ๋‚ด์™ธ ์‹œ์žฅ์—ฌ๊ฑด ๋“ฑ)

+

+ +

+์Šค๋งˆํŠธํฐ์šฉ ๋””์Šคํ”Œ๋ ˆ์ด ์‹œ์žฅ์€ 2024๋…„ 15.5์–ต๋Œ€์—์„œ 2025๋…„ 16.1์–ต๋Œ€๋กœ ์ฆ๊ฐ€ํ•  ๊ฒƒ์œผ๋กœ ์ „๋ง๋ฉ๋‹ˆ๋‹ค. ์Šค๋งˆํŠธํฐ์šฉ OLED ๋””์Šคํ”Œ๋ ˆ์ด์˜ ๊ฒฝ์šฐ 2024๋…„ 7.8์–ต๋Œ€์—์„œ + +2025๋…„ 8.2์–ต๋Œ€๋กœ ์ฆ๊ฐ€ํ•  ์ „๋ง์ด๋ฉฐ, ์ „์ฒด ์Šค๋งˆํŠธํฐ์šฉ ๋””์Šคํ”Œ๋ ˆ์ด ์‹œ์žฅ์—์„œ OLED๊ฐ€ ์ฐจ์ง€ํ•˜๋Š” ๋น„์ค‘์€ 2024๋…„ 50.7%์—์„œ 2025๋…„ 51.0%๋กœ ์ฆ๊ฐ€ํ•  ๊ฒƒ์œผ๋กœ ์ „๋ง๋ฉ๋‹ˆ๋‹ค(์ถœ์ฒ˜: Omdia 2026.01). +

+

+

๋˜ํ•œ, ๋Œ€ํ˜• ๋””์Šคํ”Œ๋ ˆ์ด ์‹œ์žฅ์€ 2024๋…„ 8.7์–ต๋Œ€์—์„œ 2025๋…„ 9.1 ์–ต๋Œ€๋กœ ์„ฑ์žฅํ•  ๊ฒƒ์œผ๋กœ ์ „๋ง๋ฉ๋‹ˆ๋‹ค(์ถœ์ฒ˜: Omdia 2025.12).

+

+

< ์Šค๋งˆํŠธํฐ ํŒจ๋„ ์‹œ์žฅ์ ์œ ์œจ ์ถ”์ด >

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +

(์˜์—…์˜ ๊ฐœํ™ฉ ๋“ฑ)

+

๋‹น์‚ฌ๋Š” 2007๋…„ ์„ธ๊ณ„ ์ตœ์ดˆ๋กœ OLED ์ œํ’ˆ์˜ ์ƒ์šฉํ™”์— ์„ฑ๊ณตํ•œ ์ดํ›„ ํ˜„์žฌ๊นŒ์ง€ ์ค‘์†Œํ˜• OLED ์‹œ์žฅ์—์„œ ๋…๋ณด์ ์ธ ์ ์œ ์œจ์„ ์œ ์ง€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์Šค๋งˆํŠธํฐ ์™ธ์—๋„ ํด๋”๋ธ”, ํƒœ๋ธ”๋ฆฟ, ์›Œ์น˜, ๋…ธํŠธ๋ถ, Auto ๋“ฑ์œผ๋กœ ์ œํ’ˆ๊ตฐ์„ ๋‹ค๊ฐํ™”ํ•˜์—ฌ ๋ช…์‹ค์ƒ๋ถ€ํ•œ OLED ๋””์Šคํ”Œ๋ ˆ์ด์˜ ์„ ๋„ ๊ธฐ์—…์œผ๋กœ ์ž๋ฆฌ๋งค๊น€ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

+

+

๋‹น์‚ฌ๋Š” ํ”„๋ฆฌ๋ฏธ์—„๋ถ€ํ„ฐ ๋ณด๊ธ‰ํ˜• ์ œํ’ˆ๊นŒ์ง€ ์ตœ์ ์˜ ์ œํ’ˆ ํฌํŠธํด๋ฆฌ์˜ค๋ฅผ ๊ตฌ์ถ•ํ•˜์—ฌ ์†Œ๋น„์ž์˜ ๊ณ ๋„ํ™”๋˜๋Š” ์š”๊ตฌ์— ์ ๊ทน ๋Œ€์‘ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์ฐจ๋ณ„ํ™”๋œ ๊ธฐ๋Šฅ๊ณผ ๋””์ž์ธ์˜ ํด๋”๋ธ”ใ†IT(ํƒœ๋ธ”๋ฆฟ/๋…ธํŠธ๋ถ)์šฉ ๋””์Šคํ”Œ๋ ˆ์ด๋ฅผ ์ง€์†์ ์œผ๋กœ ์ถœ์‹œํ•˜์—ฌ ์‹œ์žฅ์œผ๋กœ๋ถ€ํ„ฐ ์—…๊ณ„ ์ตœ๊ณ ์˜ ๊ธฐ์ˆ ๋ ฅ์„ ์ธ์ •๋ฐ›๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

+

+

๋˜ํ•œ, ์‹ ๊ทœ 8.6G IT OLED๋ผ์ธ์„ ์–‘์‚ฐ ์ผ์ •์— ๋งž์ถฐ ์ฐจ์งˆ์—†์ด ์ค€๋น„ํ•˜์—ฌ ์„ฑ์žฅํ•˜๋Š” IT OLED ์‹œ์žฅ์„ ์„ ์ ํ•˜๊ณ  ํŒ๋งค๋ฅผ ํ™•๋Œ€ํ•ด ๋‚˜๊ฐˆ ๊ณ„ํš์ž…๋‹ˆ๋‹ค. IT์šฉ ๋””์Šคํ”Œ๋ ˆ์ด ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ Auto์šฉ, ์›Œ์น˜์šฉ ๋“ฑ์œผ๋กœ ๋งค์ถœ ๊ตฌ์กฐ๋ฅผ ๋‹ค๋ณ€ํ™”ํ•˜์—ฌ ์‚ฌ์—… ํฌํŠธํด๋ฆฌ์˜ค ์•ˆ์ •์„ฑ์„ ๊ฐ•ํ™”ํ•ด ๋‚˜๊ฐˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

+

+

๋Œ€ํ˜• ๋””์Šคํ”Œ๋ ˆ์ด ์‹œ์žฅ์˜ ๊ฒฝ์šฐ, TV์™€ ๋ชจ๋‹ˆํ„ฐ ํ”„๋ฆฌ๋ฏธ์—„ ์‹œ์žฅ์—์„œ ์ž๋ฐœ๊ด‘ ๋””์Šคํ”Œ๋ ˆ์ด ๋น„์ค‘์ด ์ง€์† ์ฆ๊ฐ€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹น์‚ฌ๋Š” ๊ณ ํ•ด์ƒ๋„, ์ดˆ๊ณ ์ฃผ์‚ฌ์œจ ๋“ฑ ์„ฑ๋Šฅ์„ ์ฐจ๋ณ„ํ™”ํ•˜์—ฌ ํ”„๋ฆฌ๋ฏธ์—„ ๋ชจ๋‹ˆํ„ฐ์šฉ ๋””์Šคํ”Œ๋ ˆ์ด ์‹œ์žฅ์—์„œ ์ž…์ง€๋ฅผ ํ™•๋Œ€ํ•˜์˜€์œผ๋ฉฐ, ์ œํ’ˆ ๋ผ์ธ์—…์„ ๋‹ค์–‘ํ™”ํ•˜์—ฌ B2C์™€ B2B ๋ชจ๋‹ˆํ„ฐ์šฉ ๋””์Šคํ”Œ๋ ˆ์ด ์‹œ์žฅ๊นŒ์ง€ ํŒ๋งค ์˜์—ญ์„ ํ™•๋Œ€ํ•ด ๋‚˜๊ฐˆ ๊ณ„ํš์ž…๋‹ˆ๋‹ค.

+ + +

+[Harman] +(์‚ฐ์—…์˜ ํŠน์„ฑ ๋“ฑ) +Harman์€ ์ „์žฅ๋ถ€ํ’ˆ(Automotive)๊ณผ ๋ผ์ดํ”„์Šคํƒ€์ผ ์˜ค๋””์˜ค(Lifestyle Audio) ์‚ฐ์—…์—์„œ ๊ฒฝ์Ÿํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์ค‘ ์ „์žฅ๋ถ€๋ฌธ์ด Harman ์‚ฌ์—…์˜ ๊ฐ€์žฅ ํฐ ๋น„์ค‘์„ ์ฐจ์ง€ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ ๋””์ง€ํ„ธ ์ฝ•ํ•, ์นด์˜ค๋””์˜ค, ํ…”๋ ˆ๋งคํ‹ฑ์Šค ๋“ฑ ๋ถ„์•ผ์— ์ง„์ถœํ•ด ์žˆ์Šต๋‹ˆ๋‹ค. +

+

์š”์ฆ˜ ์†Œ๋น„์ž๋“ค์€ ์ฐจ๋Ÿ‰์„ ์„ ํƒํ•  ๋•Œ ๋‹จ์ˆœ ์ด๋™์ˆ˜๋‹จ ๊ธฐ๋Šฅ๋ณด๋‹ค ์ด๋™์‹œ์— ์ฆ๊ธธ ์ˆ˜ ์žˆ๋Š” ์ฐจ๋Ÿ‰๋‚ด ๊ฒฝํ—˜(In-Cabin Experience)์„ ์ค‘์š”์‹œํ•˜๋Š” ์ถ”์„ธ์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์†Œ๋น„์ž ๋‹ˆ์ฆˆ์— ๋งž์ถ”์–ด ์ž๋™์ฐจ๋„ IT ์ œํ’ˆ์ฒ˜๋Ÿผ SW๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ ๊ฐ€๋Šฅํ•œ SDV(Software Defined Vehicle)๋กœ ๋ณ€ํ™”ํ•˜๊ณ  ์žˆ์–ด ์ž๋™์ฐจ ์ œ์กฐ์‚ฌ๋“ค์€ ์ค‘์•™์ง‘์ค‘ํ˜• ์ „์žฅ ์•„ํ‚คํ…์ฒ˜ ๋„์ž…์„ ์‹œ๋„ํ•˜๊ณ  SW ๊ธฐ๋Šฅ์„ ๊ฐ•ํ™”ํ•˜๋Š” ๋“ฑ ๋งŽ์€ ํ˜์‹ ์„ ์ถ”์ง„ ์ค‘์ž…๋‹ˆ๋‹ค. ์ด์— ๋”ฐ๋ผ, ์ „์žฅ ๋ถ€ํ’ˆ์—…์ฒด๋“ค์˜ ๊ณต๊ธ‰์ œํ’ˆ์—๋„ ๋น ๋ฅธ ๊ธฐ์ˆ ์  ๋ณ€ํ™”๊ฐ€ ์ง„ํ–‰๋˜๊ณ  ์žˆ์–ด, ์—…์ฒด๊ฐ„ ๊ฒฝ์Ÿ์ด ์น˜์—ดํ•  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค. ์ด์—, Harman์€ ์ž๋™์ฐจ์˜ SDV๋กœ ๋ณ€ํ™”์— ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ๋Š” SW ๊ธฐ์ˆ ์—ญ๋Ÿ‰์„ ๊ฐ•ํ™”ํ•˜๊ณ , In-Cabin Experience์˜ ํ•ต์‹ฌ ์ „์žฅ๋ถ€ํ’ˆ๊ณผ ๊ณ ์„ฑ์žฅ์ด ์˜ˆ์ƒ๋˜๋Š” AR HUD, ๋””์Šคํ”Œ๋ ˆ์ด, ์šด์ „์ž ๋ชจ๋‹ˆํ„ฐ๋ง ๋“ฑ ์‹ ์ œํ’ˆ์„ ์œก์„ฑํ•˜๋Š” ํ•œํŽธ ์ „์ž์˜ SW/์„œ๋น„์Šค ์—ญ๋Ÿ‰์„ ์ฐจ๋Ÿ‰์— ์ ‘๋ชฉํ•˜์—ฌ ๋ณด๋‹ค ํŽธ๋ฆฌํ•˜๊ณ  ์ฐจ๋ณ„ํ™”๋œ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๋Š”๋ฐ ์ฃผ๋ ฅํ•  ๊ณ„ํš์ž…๋‹ˆ๋‹ค.๋ผ์ดํ”„์Šคํƒ€์ผ ์˜ค๋””์˜ค ์‚ฐ์—…์€ ์ปจ์Šˆ๋จธ ์˜ค๋””์˜ค์™€ ํ”„๋กœํŽ˜์…”๋„ ์˜ค๋””์˜ค ์†”๋ฃจ์…˜์œผ๋กœ ๊ตฌ๋ถ„๋ฉ๋‹ˆ๋‹ค. ์ปจ์Šˆ๋จธ ์˜ค๋””์˜ค ์ œํ’ˆ(True Wireless Stereo, Portable Speaker, Headphone ๋“ฑ)์€ ๊ณผ๊ฑฐ ๋‹จ์ˆœ ์Œ์› ์žฌ์ƒ๊ธฐ๊ธฐ์˜€์œผ๋‚˜, ์ตœ๊ทผ ๋ฌด์„  ์—ฐ๊ฒฐ๊ธฐ์ˆ  ์ ์šฉ ๋ฐ AI ํƒ‘์žฌ๊ฐ€ ํ™•์‚ฐ๋˜์–ด ๊ธฐ์ˆ  ์ค‘์‹ฌ์˜ IT ๊ธฐ๊ธฐ๋กœ ์ง„ํ™”ํ•˜๋Š” ์ถ”์„ธ์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ธฐ์ˆ  ๋ณ€ํ™”์— ์˜ํ•ด, IT ์—…์ฒด๋“ค์ด ์ปจ์Šˆ๋จธ ์˜ค๋””์˜ค ์‹œ์žฅ์— ์ฐธ์—ฌํ•˜์—ฌ ์Œํ–ฅ ์„ค๊ณ„ ๊ธฐ์ˆ ์„ ๋ณด์œ ํ•œ ์ „ํ†ต ์˜ค๋””์˜ค ์—…์ฒด๋“ค๊ณผ ๊ฒฝ์Ÿ ์ค‘์ž…๋‹ˆ๋‹ค.

+

+ +

์ปจ์Šˆ๋จธ ์˜ค๋””์˜ค ์‹œ์žฅ์€ TWS ํ—ค๋“œํฐ๊ณผ Home ์˜ค๋””์˜ค ๋ฐ ๊ฒŒ์ด๋ฐ ํ—ค๋“œํฐ ๋ถ„์•ผ์—์„œ ๊ณ ์„ฑ์žฅ์ด ์˜ˆ์ƒ๋˜๊ณ , ์†Œ๋น„์ž๋“ค์€ ํ”„๋ฆฌ๋ฏธ์—„ ์˜ค๋””์˜ค๋ฅผ ๊ฒฝํ—˜(๊ณ ์Œ์งˆ/๋ฌด์†์‹ค ์Œ์› ๋“ฑ)ํ•˜๊ณ ์ž ํ•˜๋Š” ๊ฒฝํ–ฅ์ด ๋šœ๋ ทํ•ด์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์— Harman์€ ๊ณ ์„ฑ์žฅ ๋ถ„์•ผ์— ์‚ฌ์—…์—ญ๋Ÿ‰๊ณผ ๊ฒฝ์Ÿ๋ ฅ์„ ๊ฐ•ํ™”ํ•˜๊ณ  ์‹ ์ œํ’ˆ์„ ์ง€์† ์ถœ์‹œํ•˜์—ฌ ์‹œ์žฅ์— ๋Œ€์‘ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ํ”„๋ฆฌ๋ฏธ์—„ ์˜ค๋””์˜ค ์‹œ์žฅ์—์„œ์˜ ๋ฆฌ๋”์‹ญ ๊ฐ•ํ™”๋ฅผ ์œ„ํ•ด B&W, Denon ๋ฐ Marantz ๋“ฑ์˜ ๋ธŒ๋žœ๋“œ๋ฅผ ๋ณด์œ ํ•œ ๋ฏธ๊ตญ์—…์ฒด Sound United๋ฅผ 2025๋…„ 3๋ถ„๊ธฐ์— ์ธ์ˆ˜ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ํ–ฅํ›„ Harman์€ ๋ธŒ๋žœ๋“œ ๊ฒฝ์Ÿ๋ ฅ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์‚ฌ์—… ๊ฒฝ์Ÿ๋ ฅ์„ ์ง€์† ์ œ๊ณ ํ•˜์—ฌ ์ปจ์Šˆ๋จธ ์˜ค๋””์˜ค ์‹œ์žฅ์—์„œ ๊ธ€๋กœ๋ฒŒ 1์œ„ ์ž…์ง€๋ฅผ ๊ฐ•ํ™”ํ•ด ๋‚˜๊ฐˆ ๊ณ„ํš์ž…๋‹ˆ๋‹ค.

+ +

+ +(๊ตญ๋‚ด์™ธ ์‹œ์žฅ์—ฌ๊ฑด ๋“ฑ) +2025๋…„ ์ „ ์„ธ๊ณ„ ์ž๋™์ฐจ ์ƒ์‚ฐ๋Ÿ‰์€ ์ „๋…„ ๋Œ€๋น„ 3.7% ์ฆ๊ฐ€ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ค‘๊ตญ์˜ ๊ฒฌ์กฐํ•œ ์‹ค์ ์— ๊ธฐ์ธํ•˜์˜€์œผ๋‚˜, ๋ถ๋ฏธ์™€ ์œ ๋Ÿฝ์˜ ๊ฐ์†Œ์„ธ๊ฐ€ ์ผ๋ถ€ ์ƒ์‡„ํ•˜๋Š” ์š”์ธ์œผ๋กœ ์ž‘์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค. 2026๋…„ ์ „ ์„ธ๊ณ„ ์ž๋™์ฐจ ์ƒ์‚ฐ๋Ÿ‰์€ 2025๋…„ ๋Œ€๋น„ 0.4% ์†Œํญ ๊ฐ์†Œํ•  ๊ฒƒ์œผ๋กœ ์ „๋ง๋˜๋ฉฐ, ์ด๋Š” ๋ถ๋ฏธ ์ง€์—ญ์˜ ๊ด€์„ธ ์˜ํ–ฅ ๋ฐ ์ค‘๊ตญ์˜ ๋ณด์กฐ๊ธˆ ์ถ•์†Œ๋กœ ์ธํ•œ ์ˆ˜์š” ์•ฝ์„ธ๊ฐ€ ์ฃผ์š” ์›์ธ์œผ๋กœ ๋ถ„์„๋ฉ๋‹ˆ๋‹ค(์ถœ์ฒ˜: S&P Global Light Vehicle Production Forecast 2026.01).< ๋””์ง€ํ„ธ ์ฝ•ํ• ์‹œ์žฅ์ ์œ ์œจ ์ถ”์ด > +

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ + +

(์˜์—…์˜ ๊ฐœํ™ฉ ๋“ฑ) +Harman์€ ์ „์žฅ๋ถ€ํ’ˆ ์‹œ์žฅ์—์„œ ์ฐจ๋Ÿ‰๋‚ด ๊ฒฝํ—˜์˜ ์ค‘์‹ฌ์ด ๋˜๋Š” ๋””์ง€ํ„ธ ์ฝ•ํ•, ์นด ์˜ค๋””์˜ค ๋ถ„์•ผ์—์„œ ์„ ๋„์  ์‹œ์žฅ ์ž…์ง€๋ฅผ ์œ ์ง€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹น์‚ฌ๋Š” Harman์˜ ์„ ๋‘ ์œ„์ƒ์„ ๊ฐ•ํ™”ํ•˜๊ธฐ ์œ„ํ•ด Harman์˜ ์ „์žฅ์‚ฌ์—…์— ๋‹น์‚ฌ์˜ ๋ฌด์„ ํ†ต์‹ , ๋””์Šคํ”Œ๋ ˆ์ด ๋“ฑ IT ๊ธฐ์ˆ ์„ ์ง€์† ์ ‘๋ชฉ์‹œํ‚ค๊ณ , ๋””์ง€ํ„ธ ์ฝ•ํ•์— ADAS (Advanced Driver Assistance System, ์ฒจ๋‹จ ์šด์ „์ž ๋ณด์กฐ ์‹œ์Šคํ…œ) ๊ธฐ๋Šฅ์„ ํ†ตํ•ฉํ•œ ์ค‘์•™์ง‘์ค‘ํ˜• ์ปจํŠธ๋กค๋Ÿฌ ์ œํ’ˆ์— ์‚ฌ์—…์—ญ๋Ÿ‰์„ ์ง‘์ค‘ํ•˜์—ฌ ์ฐจ๋Ÿ‰์˜ SDV(Software Defined Vehicle)ํ™” ๋ณ€ํ™”๋ฅผ ์ฃผ๋„ํ•˜๋Š” ํ•ต์‹ฌ ์ „์žฅ์—…์ฒด๋กœ ์„ฑ์žฅํ•  ๊ณ„ํš์ž…๋‹ˆ๋‹ค.Harman์€ ๋…์ž ๋ณด์œ ํ•œ ์Œํ–ฅ ์„ค๊ณ„๋Šฅ๋ ฅ๊ณผ ๋‹ค์–‘ํ•œ ์†Œ๋น„์ž์ธต์„ ๊ฒจ๋ƒฅํ•œ ๋ฉ€ํ‹ฐ ๋ธŒ๋žœ๋“œ ์ „๋žต์„ ๊ตฌ์‚ฌํ•˜์—ฌ ์ผ๋ฐ˜ ์†Œ๋น„์ž์™€ ์Œ์•… ์• ํ˜ธ๊ฐ€๋“ค ์‚ฌ์ด์—์„œ ์˜ค๋””์˜ค ์ „๋ฌธ ์—…์ฒด๋กœ์„œ ๋ธŒ๋žœ๋“œ ์ธ์ง€๋„ ๋ฐ ์˜ํ–ฅ๋ ฅ์„ ํ™•๋Œ€ํ•ด ์™”์Šต๋‹ˆ๋‹ค. ํ–ฅํ›„ ๋‹น์‚ฌ์™€ ํ˜‘๋ ฅ์„ ๊ฐ•ํ™”ํ•˜๊ณ  ์ƒˆ๋กœ์šด ์ œํ’ˆ์„ ์ถœ์‹œํ•˜์—ฌ ์˜ค๋””์˜ค ์‚ฐ์—…๋‚ด์—์„œ์˜ ์œ„์ƒ๊ณผ ์ž…์ง€๋ฅผ ๋”์šฑ ๊ฐ•ํ™”ํ•ด ๋‚˜๊ฐˆ ์˜ˆ์ •์ž…๋‹ˆ๋‹ค. +

+ + +

+๋ผ. ์‚ฌ์—…๋ถ€๋ฌธ๋ณ„ ์š”์•ฝ ์žฌ๋ฌด ํ˜„ํ™ฉ +2025๋…„(์ œ57๊ธฐ) ๋งค์ถœ์€ DX ๋ถ€๋ฌธ์ด 187์กฐ 9,673์–ต์›(56.3%), DS ๋ถ€๋ฌธ์ด 130์กฐ 1,282์–ต์›(39%)์ด๋ฉฐ, SDC๊ฐ€ 29์กฐ 8,417์–ต์›(8.9%), Harman์€ 15์กฐ 7,833์–ต์›(4.7%)์ž…๋‹ˆ๋‹ค.2025๋…„(์ œ57๊ธฐ) ์˜์—…์ด์ต์€ DX ๋ถ€๋ฌธ์ด 12์กฐ 8,527์–ต์›, DS ๋ถ€๋ฌธ์ด 24์กฐ 8,581์–ต์›, SDC๊ฐ€ 4์กฐ 1,163์–ต์›, ๊ทธ๋ฆฌ๊ณ  Harman์€ 1์กฐ 5,311์–ต์›์ž…๋‹ˆ๋‹ค. +

+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +
[table omitted in fixture]
+ +

+(๊ณตํ†ต ํŒ๋งค๋น„์™€ ๊ด€๋ฆฌ๋น„ ๋ฐ ์ž์‚ฐ์˜ ํ•ฉ๋ฆฌ์  ๋ฐฐ๋ถ€ ๊ธฐ์ค€ ์ ์šฉ)ใ†๊ณตํ†ต ํŒ๋งค๋น„์™€ ๊ด€๋ฆฌ๋น„์˜ ๊ฒฝ์šฐ ๊ฐ ์ œํ’ˆใ†๋ชจ๋ธ๋ณ„ ๊ท€์†์ด ํ™•์‹คํ•œ ์ง์ ‘๋น„์šฉ(์œ„์ž„์„ฑ ๊ฒฝ๋น„)์€ ๊ฐ ์ œํ’ˆใ†๋ชจ๋ธ๋ถ€๋ฌธ์— ์ง์ ‘ ๊ท€์†์‹œํ‚ค๊ณ  ๊ท€์†์—ฌ๋ถ€๊ฐ€ ๋ถˆ๋ถ„๋ช…ํ•œ ๊ณตํ†ต๊ฒฝ๋น„๋Š” ๊ฐ ๋ฐฐ๋ถ€ ๊ธฐ์ค€(๋งค์ถœ์•ก๋น„, ์ธ์›์ˆ˜๋น„ ๋“ฑ)์— ์˜๊ฑฐ ์ ์ ˆํžˆ ๋ฐฐ๋ถ€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ใ†๊ณตํ†ต์ž์‚ฐ์˜ ๊ฒฝ์šฐ ์ง์ ‘๊ท€์†์ด ๊ฐ€๋Šฅํ•œ ์ž์‚ฐ(์žฌ๊ณ ์ž์‚ฐ, ๊ณ ์ •์ž์‚ฐ, ํˆฌ์ž์ž์‚ฐ ๋“ฑ)์€ ํ•ด๋‹น ๋ถ€์„œ์— ์ง์ ‘ ๊ท€์†๋˜๋‚˜, ์ „์‚ฌ ๊ณตํ†ต๊ด€๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ์ž์‚ฐ ๋ฐ ์ž์‚ฐ๊ท€์†์ด ๋ถˆ๋ถ„๋ช…ํ•œ ์ž์‚ฐ์€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ํ•ฉ๋ฆฌ์ ์ธ ๋ฐฐ๋ถ€ ๊ธฐ์ค€(๋งค์ถœ์•ก๋น„, ์„ธ์ „์ด์ต๋น„ ๋“ฑ)์— ์˜๊ฑฐ ๊ฐ ๋ถ€๋ฌธ์— ๋ฐฐ๋ถ€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +

+ +
+
+ + + + + +
diff --git a/src/tools/finance-kr/api.ts b/src/tools/finance-kr/api.ts index 3028a00cf..9d9474d1e 100644 --- a/src/tools/finance-kr/api.ts +++ b/src/tools/finance-kr/api.ts @@ -87,4 +87,59 @@ export const dartApi = { return { data, url: safeUrlString }; }, + + /** + * Fetch a binary DART payload (the `/document.xml` ZIP โ€” original filing body). + * `get()` is JSON-only; this returns the raw bytes. Like `corpCode.xml`, DART + * returns an XML/text status payload (not a ZIP) when the key is bad or the + * document is missing โ€” surface that as an error instead of a corrupt ZIP. + * Not cached: binary doesn't fit the JSON cache; callers cache the parsed text. + */ + async getBinary( + endpoint: string, + params: Record, + ): Promise<{ bytes: Uint8Array; url: string }> { + const label = describeRequest(endpoint, params); + const apiKey = getApiKey(); + if (!apiKey) { + throw new Error('[DART API] DART_API_KEY not set'); + } + + const url = new URL(`${BASE_URL}${endpoint}`); + url.searchParams.set('crtfc_key', apiKey); + for (const [key, value] of Object.entries(params)) { + if (value !== undefined && value !== null) { + url.searchParams.set(key, String(value)); + } + } + + let response: Response; + try { + response = await fetch(url.toString()); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`[DART API] network error: ${label} โ€” ${message}`); + throw new Error(`[DART API] request failed for ${label}: ${message}`); + } + + if (!response.ok) { + const detail = `${response.status} ${response.statusText}`; + logger.error(`[DART API] HTTP error: ${label} โ€” ${detail}`); + throw new Error(`[DART API] request failed: ${detail}`); + } + + const contentType = response.headers.get('content-type') ?? ''; + if (contentType.includes('xml') || contentType.includes('json') || contentType.includes('text')) { + const body = await response.text().catch(() => ''); + const detail = body.slice(0, 200); + logger.error(`[DART API] expected binary: ${label} โ€” got ${contentType}: ${detail}`); + throw new Error(`[DART API] ${label}: expected document ZIP, got ${contentType}: ${detail}`); + } + + const bytes = new Uint8Array(await response.arrayBuffer()); + + const safeUrl = new URL(url.toString()); + safeUrl.searchParams.delete('crtfc_key'); + return { bytes, url: safeUrl.toString() }; + }, }; diff --git a/src/tools/finance-kr/dart-document.test.ts b/src/tools/finance-kr/dart-document.test.ts new file mode 100644 index 000000000..7881e04d0 --- /dev/null +++ b/src/tools/finance-kr/dart-document.test.ts @@ -0,0 +1,159 @@ +import { describe, it, expect } from 'bun:test'; +import { readFileSync } from 'fs'; +import { zipSync, strToU8 } from 'fflate'; +import { + decodeDsd, + extractDsdBody, + dsdToPlainText, + splitSections, + selectSections, + sectionTitles, +} from './dart-document.js'; + +// Handwritten DSD fixture modeled on the confirmed real structure: +// tags delimit sections (ASCII Roman majors, Arabic subs), tables carry +// numbers (dropped), and full-width โ… /โ…ก appear only in prose cross-refs / table cells. +const SAMPLE = `<?xml version="1.0" encoding="utf-8"?> +<DOCUMENT><BODY> +<TITLE ATOC="N" ENG="Table of Contents">๋ชฉ ์ฐจ +

ํ‘œ์ง€

+I. ํšŒ์‚ฌ์˜ ๊ฐœ์š” +1. ํšŒ์‚ฌ์˜ ๊ฐœ์š” +

๋‹น์‚ฌ๋Š” ํ…Œ์ŠคํŠธ ์ฃผ์‹ํšŒ์‚ฌ์ด๋ฉฐ R&D ์ค‘์‹ฌ ๊ธฐ์—…์ž…๋‹ˆ๋‹ค.

+II. ์‚ฌ์—…์˜ ๋‚ด์šฉ +

โ˜ž ์ž์„ธํ•œ ์‚ฌํ•ญ์€ 'โ…ก. ์‚ฌ์—…์˜ ๋‚ด์šฉ'์„ ์ฐธ๊ณ ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

+1. ์‚ฌ์—…์˜ ๊ฐœ์š” +

๋‹น์‚ฌ๋Š” ๋ฐ˜๋„์ฒด์™€ ๋””์Šคํ”Œ๋ ˆ์ด ๋‘ ๋ถ€๋ฌธ์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

+2. ์ฃผ์š” ์ œํ’ˆ ๋ฐ ์„œ๋น„์Šค +

์ฃผ์š” ์ œํ’ˆ์€ DRAM๊ณผ NAND์ž…๋‹ˆ๋‹ค.

+
โ… . ๋ฐœํ–‰ํ•  ์ฃผ์‹์˜ ์ด์ˆ˜1,000,000
+5. ์œ„ํ—˜๊ด€๋ฆฌ ๋ฐ ํŒŒ์ƒ๊ฑฐ๋ž˜ +

๋‹น์‚ฌ๋Š” ํ™˜์œจ๋ณ€๋™์œ„ํ—˜๊ณผ ์‹ ์šฉ์œ„ํ—˜์— ๋…ธ์ถœ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

+III. ์žฌ๋ฌด์— ๊ด€ํ•œ ์‚ฌํ•ญ +1. ์š”์•ฝ์žฌ๋ฌด์ •๋ณด +
๋งค์ถœ์•ก100
+IV. ์ด์‚ฌ์˜ ๊ฒฝ์˜์ง„๋‹จ ๋ฐ ๋ถ„์„์˜๊ฒฌ +1. ์˜ˆ์ธก์ •๋ณด์— ๋Œ€ํ•œ ์ฃผ์˜์‚ฌํ•ญ +

๋ณธ ์ž๋ฃŒ๋Š” ์˜ˆ์ธก์ •๋ณด๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

+XI. ๊ทธ ๋ฐ–์— ํˆฌ์ž์ž ๋ณดํ˜ธ๋ฅผ ์œ„ํ•˜์—ฌ ํ•„์š”ํ•œ ์‚ฌํ•ญ +3. ์ œ์žฌํ˜„ํ™ฉ ๋“ฑ +

๋‹น์‚ฌ๋Š” ์ค‘๋Œ€ํ•œ ์ œ์žฌ๋ฅผ ๋ฐ›์€ ์‚ฌ์‹ค์ด ์—†์Šต๋‹ˆ๋‹ค.

+`; + +describe('dsdToPlainText', () => { + it('drops tables, strips tags, decodes entities', () => { + const out = dsdToPlainText('

R&D ์ค‘์‹ฌ

9,999
'); + expect(out).toContain('R&D ์ค‘์‹ฌ'); + expect(out).not.toContain('9,999'); + }); +}); + +describe('splitSections', () => { + const sections = splitSections(SAMPLE); + + it('identifies the five major Roman sections', () => { + const majors = sections.filter((s) => s.level === 'major').map((s) => s.numeral); + expect(majors).toEqual(['I', 'II', 'III', 'IV', 'XI']); + }); + + it('attaches Arabic sub-sections to their parent major', () => { + const risk = sections.find((s) => s.title.includes('์œ„ํ—˜๊ด€๋ฆฌ')); + expect(risk?.numeral).toBe('5'); + expect(risk?.level).toBe('sub'); + expect(risk?.parent).toBe('II'); + }); + + it('does not treat full-width โ…ก in prose/table cells as a section', () => { + // Only one section should carry the 'II' numeral (the real ), not the cross-ref. + expect(sections.filter((s) => s.numeral === 'II').length).toBe(1); + }); +}); + +describe('selectSections', () => { + const sections = splitSections(SAMPLE); + + it('business pulls all of II incl. sub-sections', () => { + const { business } = selectSections(sections, ['business']); + expect(business).toContain('๋ฐ˜๋„์ฒด์™€ ๋””์Šคํ”Œ๋ ˆ์ด'); + expect(business).toContain('DRAM๊ณผ NAND'); + expect(business).toContain('ํ™˜์œจ๋ณ€๋™์œ„ํ—˜'); // ์œ„ํ—˜๊ด€๋ฆฌ is part of II + }); + + it('products pulls only the product sub-section, not risk prose', () => { + const { products } = selectSections(sections, ['products']); + expect(products).toContain('DRAM๊ณผ NAND'); + expect(products).not.toContain('ํ™˜์œจ๋ณ€๋™์œ„ํ—˜'); + }); + + it('risks pulls the scattered II.5 ์œ„ํ—˜๊ด€๋ฆฌ + XI ํˆฌ์ž์ž ๋ณดํ˜ธ', () => { + const { risks } = selectSections(sections, ['risks']); + expect(risks).toContain('ํ™˜์œจ๋ณ€๋™์œ„ํ—˜'); // II.5 ์œ„ํ—˜๊ด€๋ฆฌ + expect(risks).toContain('์ œ์žฌ๋ฅผ ๋ฐ›์€ ์‚ฌ์‹ค'); // XI.3 ์ œ์žฌํ˜„ํ™ฉ + expect(risks).not.toContain('DRAM๊ณผ NAND'); // not products + }); + + it('mdna pulls IV management discussion', () => { + const { mdna } = selectSections(sections, ['mdna']); + expect(mdna).toContain('์˜ˆ์ธก์ •๋ณด๋ฅผ ํฌํ•จ'); + }); + + it('overview decodes entities in I. ํšŒ์‚ฌ์˜ ๊ฐœ์š”', () => { + const { overview } = selectSections(sections, ['overview']); + expect(overview).toContain('R&D ์ค‘์‹ฌ'); + }); + + it('drops table numbers from narrative output', () => { + const { business } = selectSections(sections, ['business']); + expect(business).not.toContain('1,000,000'); + expect(business).not.toContain('๋ฐœํ–‰ํ•  ์ฃผ์‹์˜ ์ด์ˆ˜'); + }); + + it('joins multiple blocks with a triple newline (the dedup contract)', () => { + const { business } = selectSections(sections, ['business']); + expect(business).toContain('\n\n\n'); // II major + its sub-sections + }); +}); + +describe('extractDsdBody', () => { + it('picks the main no-underscore .xml entry over attachments', () => { + const zip = zipSync({ + '20260310002820.xml': strToU8('<DOCUMENT>MAIN BODY ๋ณธ๋ฌธ</DOCUMENT>'), + '20260310002820_00760.xml': strToU8('<DOCUMENT>๊ฐ์‚ฌ๋ณด๊ณ ์„œ</DOCUMENT>'), + }); + expect(extractDsdBody(zip)).toContain('MAIN BODY'); + }); + + it('throws on an empty ZIP', () => { + expect(() => extractDsdBody(zipSync({}))).toThrow(); + }); +}); + +describe('decodeDsd', () => { + it('decodes UTF-8 bytes', () => { + expect(decodeDsd(strToU8('์‚ผ์„ฑ์ „์ž'))).toBe('์‚ผ์„ฑ์ „์ž'); + }); + + it('falls back to EUC-KR when UTF-8 yields replacement chars', () => { + // EUC-KR 0xB0A1 = '๊ฐ€'; invalid as UTF-8 โ†’ triggers the fallback branch. + const bytes = new Uint8Array(Array.from({ length: 30 }, () => [0xb0, 0xa1]).flat()); + expect(decodeDsd(bytes)).toContain('๊ฐ€๊ฐ€'); + }); +}); + +describe('real fixture (Samsung 005930 ์‚ฌ์—…๋ณด๊ณ ์„œ II. ์‚ฌ์—…์˜ ๋‚ด์šฉ)', () => { + const fixture = readFileSync(`${import.meta.dir}/__fixtures__/dsd-005930-business-section.xml`, 'utf-8'); + const sections = splitSections(fixture); + + it('finds the II. ์‚ฌ์—…์˜ ๋‚ด์šฉ major and its 7 sub-sections', () => { + const titles = sectionTitles(sections); + expect(titles.some((t) => t.includes('์‚ฌ์—…์˜ ๋‚ด์šฉ'))).toBe(true); + expect(titles.some((t) => t.includes('์œ„ํ—˜๊ด€๋ฆฌ'))).toBe(true); + }); + + it('extracts grounded business + products + risk prose', () => { + const sel = selectSections(sections, ['business', 'products', 'risks']); + expect(sel.business).toContain('๋ถ€๋ฌธ'); // segment narrative + expect(sel.products).toContain('DRAM'); + expect(sel.risks).toContain('์œ„ํ—˜'); // ์žฌ๋ฌด์œ„ํ—˜๊ด€๋ฆฌ์ •์ฑ… โ€ฆ + }); +}); diff --git a/src/tools/finance-kr/dart-document.ts b/src/tools/finance-kr/dart-document.ts new file mode 100644 index 000000000..982ecb11c --- /dev/null +++ b/src/tools/finance-kr/dart-document.ts @@ -0,0 +1,282 @@ +/** + * Parse the narrative body of a DART periodic report (์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ). + * + * The `/document.xml` endpoint returns a ZIP of DART "DSD" documents (quasi-XML). + * Ground truth (confirmed against 005930 ์‚ฌ์—…๋ณด๊ณ ์„œ): + * - The ZIP holds the main report (`<rcept_no>.xml`) plus `_NNNNN.xml` attachments + * (๊ฐ์‚ฌ๋ณด๊ณ ์„œ etc.). Narrative lives only in the main, no-underscore entry. + * - Encoding is UTF-8 (declared); legacy filings may be EUC-KR โ†’ fallback. + * - Sections are delimited by `<TITLE ATOC="Y" ENG="...">ํ•œ๊ธ€ ์ œ๋ชฉ`: + * majors use ASCII Roman (`II. ์‚ฌ์—…์˜ ๋‚ด์šฉ`), sub-sections use Arabic (`5. ์œ„ํ—˜๊ด€๋ฆฌโ€ฆ`). + * Full-width `โ…ก/โ…ข` only occur in prose cross-refs and table cells, so anchoring + * on `` tags (not free-text regex) avoids those false positives. + * - Risk is scattered: `II. 5. ์œ„ํ—˜๊ด€๋ฆฌ ๋ฐ ํŒŒ์ƒ๊ฑฐ๋ž˜` + `XI. ๊ทธ ๋ฐ–์— ํˆฌ์ž์ž ๋ณดํ˜ธโ€ฆ`. + * - Tables dominate the byte count and carry numbers (โ†’ get_financials_kr); we drop + * them and keep prose. + */ +import { unzipSync, strFromU8 } from 'fflate'; +import { dartApi } from './api.js'; + +/** Narrative section categories the planner can request. */ +export type SectionCategory = 'overview' | 'business' | 'products' | 'risks' | 'mdna'; + +export const SECTION_CATEGORIES: readonly SectionCategory[] = [ + 'overview', + 'business', + 'products', + 'risks', + 'mdna', +]; + +export interface DsdSection { + /** Normalized numeral: 'I'..'XV' (major) or '5', '2-1' (sub); '' when headerless. */ + numeral: string; + level: 'major' | 'sub'; + /** Korean title without the leading numeral, e.g. '์‚ฌ์—…์˜ ๋‚ด์šฉ'. */ + title: string; + /** Parent major numeral for sub-sections (e.g. 'II'); '' for majors. */ + parent: string; + /** Raw DSD fragment between this title and the next; clean via `sectionText`. */ + html: string; +} + +const FULLWIDTH_ROMAN: Record<string, string> = { + 'โ… ': 'I', 'โ…ก': 'II', 'โ…ข': 'III', 'โ…ฃ': 'IV', 'โ…ค': 'V', + 'โ…ฅ': 'VI', 'โ…ฆ': 'VII', 'โ…ง': 'VIII', 'โ…จ': 'IX', 'โ…ฉ': 'X', + 'โ…ช': 'XI', 'โ…ซ': 'XII', +}; + +const MAJOR_NUMERALS = new Set([ + 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII', 'XIII', 'XIV', 'XV', +]); + +// --------------------------------------------------------------------------- +// ZIP โ†’ main document string +// --------------------------------------------------------------------------- + +/** Decode DSD bytes; UTF-8 first, EUC-KR fallback for legacy filings. */ +export function decodeDsd(bytes: Uint8Array): string { + const utf8 = strFromU8(bytes); + const repl = utf8.match(/๏ฟฝ/g)?.length ?? 0; + if (repl > 20) { + try { + const euckr = new TextDecoder('euc-kr').decode(bytes); + const hangul = (s: string) => s.match(/[๊ฐ€-ํžฃ]/g)?.length ?? 0; + if (hangul(euckr) > hangul(utf8)) return euckr; + } catch { + /* keep utf8 */ + } + } + return utf8; +} + +/** + * Pick and decode the main narrative document from the document.xml ZIP. + * Prefers the `.xml` entry with no `_NNNNN` attachment suffix (the report body), + * choosing the largest such entry; falls back to the largest entry overall. + */ +export function extractDsdBody(zipBytes: Uint8Array): string { + const files = unzipSync(zipBytes); + const names = Object.keys(files); + if (names.length === 0) { + throw new Error('[DART document] ZIP contained no files'); + } + const xml = names.filter((n) => /\.xml$/i.test(n)); + const candidates = xml.length > 0 ? xml : names; + const noSuffix = candidates.filter((n) => /^[^_]+\.xml$/i.test(n)); + const pool = noSuffix.length > 0 ? noSuffix : candidates; + const main = pool.slice().sort((a, b) => files[b].length - files[a].length)[0]; + return decodeDsd(files[main]); +} + +// --------------------------------------------------------------------------- +// DSD markup โ†’ plain text +// --------------------------------------------------------------------------- + +function decodeEntities(s: string): string { + return s + .replace(/ /gi, ' ') + .replace(/&/gi, '&') + .replace(/</gi, '<') + .replace(/>/gi, '>') + .replace(/"/gi, '"') + .replace(/'/gi, "'") + .replace(/&#(\d+);/g, (_, d) => { + const code = Number(d); + return Number.isFinite(code) ? String.fromCodePoint(code) : ' '; + }) + .replace(/&#x([0-9a-f]+);/gi, (_, h) => { + const code = parseInt(h, 16); + return Number.isFinite(code) ? String.fromCodePoint(code) : ' '; + }); +} + +/** Strip nested tags inside a `<TITLE>โ€ฆ` to its visible text. */ +function titleText(inner: string): string { + return decodeEntities(inner.replace(/<[^>]+>/g, ' ')).replace(/\s+/g, ' ').trim(); +} + +/** + * Convert a DSD fragment to readable plaintext: drop tables/images (numbers belong + * to get_financials_kr), turn block tags into newlines, strip the rest, decode + * entities, and collapse whitespace. + */ +export function dsdToPlainText(fragment: string): string { + const stripped = fragment + .replace(//g, '') + .replace(//gi, '\n') + .replace(//gi, ' ') + .replace(/]*>/gi, ' ') + .replace(/<\/?(P|BR|TR|DIV|SPAN|TITLE|SECTION-\d+|PGBRK|LI|UL)\b[^>]*>/gi, '\n') + .replace(/<[^>]+>/g, ' '); + return decodeEntities(stripped) + .replace(/[ \t\u00a0]+/g, ' ') + .replace(/ *\n */g, '\n') + .replace(/\n{3,}/g, '\n\n') + .trim(); +} + +/** Clean text of a parsed section (lazy โ€” only call on sections you keep). */ +export function sectionText(section: DsdSection): string { + return dsdToPlainText(section.html); +} + +// --------------------------------------------------------------------------- +// Section splitting + selection +// --------------------------------------------------------------------------- + +const TITLE_RE = /]*>([\s\S]*?)<\/TITLE>/gi; + +function classifyTitle(title: string): { numeral: string; level: 'major' | 'sub'; name: string } { + // Major: ASCII Roman numeral + '.' (e.g. "II. ์‚ฌ์—…์˜ ๋‚ด์šฉ"). + const major = title.match(/^([IVXLC]+)\s*[.๏ผŽ]\s*(.+)$/); + if (major && MAJOR_NUMERALS.has(major[1])) { + return { numeral: major[1], level: 'major', name: major[2].trim() }; + } + // Full-width Roman fallback (rare in titles). + const fw = title.match(/^([โ… -โ…ซ])\s*[.๏ผŽ]\s*(.+)$/); + if (fw) { + return { numeral: FULLWIDTH_ROMAN[fw[1]] ?? fw[1], level: 'major', name: fw[2].trim() }; + } + // Sub: Arabic numeral, optionally "n-m" (e.g. "5. ์œ„ํ—˜๊ด€๋ฆฌโ€ฆ", "2-1. ์—ฐ๊ฒฐ ์žฌ๋ฌด์ƒํƒœํ‘œ"). + const sub = title.match(/^(\d+(?:-\d+)?)\s*[.๏ผŽ]\s*(.+)$/); + if (sub) { + return { numeral: sub[1], level: 'sub', name: sub[2].trim() }; + } + // Headerless (๋ชฉ์ฐจ, ใ€๋Œ€ํ‘œ์ด์‚ฌ ๋“ฑ์˜ ํ™•์ธใ€‘) โ€” keep as a no-numeral leaf. + return { numeral: '', level: 'sub', name: title }; +} + +/** Split a DSD document into an ordered, hierarchy-aware list of sections. */ +export function splitSections(body: string): DsdSection[] { + const anchors: { titleStart: number; bodyStart: number; title: string }[] = []; + for (const m of body.matchAll(TITLE_RE)) { + anchors.push({ + titleStart: m.index ?? 0, + bodyStart: (m.index ?? 0) + m[0].length, + title: titleText(m[1]), + }); + } + + const sections: DsdSection[] = []; + let currentMajor = ''; + for (let i = 0; i < anchors.length; i++) { + const a = anchors[i]; + const bodyEnd = i + 1 < anchors.length ? anchors[i + 1].titleStart : body.length; + const { numeral, level, name } = classifyTitle(a.title); + if (level === 'major') currentMajor = numeral; + sections.push({ + numeral, + level, + title: name, + parent: level === 'sub' ? currentMajor : '', + html: body.slice(a.bodyStart, bodyEnd), + }); + } + return sections; +} + +function sectionMatches(s: DsdSection, cat: SectionCategory): boolean { + switch (cat) { + case 'overview': + // I. ํšŒ์‚ฌ์˜ ๊ฐœ์š” + its sub-sections. + return (s.numeral === 'I' && s.level === 'major') || s.parent === 'I'; + case 'business': + // II. ์‚ฌ์—…์˜ ๋‚ด์šฉ + all its sub-sections (์‚ฌ์—…๊ฐœ์š”ยท์ œํ’ˆยท๋งค์ถœยท์œ„ํ—˜๊ด€๋ฆฌยทR&Dโ€ฆ). + return (s.numeral === 'II' && s.level === 'major') || s.parent === 'II'; + case 'products': + // Sub-sections under II about products / sales / materials. + return s.parent === 'II' && /์ œํ’ˆ|์„œ๋น„์Šค|๋งค์ถœ|์ˆ˜์ฃผ|์›์žฌ๋ฃŒ|์ƒ์‚ฐ์„ค๋น„/.test(s.title); + case 'risks': + // Scattered: II.5 ์œ„ํ—˜๊ด€๋ฆฌ ๋ฐ ํŒŒ์ƒ๊ฑฐ๋ž˜ + all of XI ํˆฌ์ž์ž ๋ณดํ˜ธ + any ์œ„ํ—˜์š”์†Œ. + return ( + (s.parent === 'II' && /์œ„ํ—˜\s*๊ด€๋ฆฌ|ํŒŒ์ƒ๊ฑฐ๋ž˜/.test(s.title)) || + (s.numeral === 'XI' && s.level === 'major') || + s.parent === 'XI' || + /์œ„ํ—˜\s*์š”์†Œ/.test(s.title) + ); + case 'mdna': + // IV. ์ด์‚ฌ์˜ ๊ฒฝ์˜์ง„๋‹จ ๋ฐ ๋ถ„์„์˜๊ฒฌ + sub-sections. + return ( + (s.numeral === 'IV' && s.level === 'major') || + s.parent === 'IV' || + /๊ฒฝ์˜\s*์ง„๋‹จ|๋ถ„์„์˜๊ฒฌ/.test(s.title) + ); + default: + return false; + } +} + +/** Select and clean the requested categories from a parsed section list. */ +export function selectSections( + sections: DsdSection[], + categories: SectionCategory[], +): Record { + const out: Record = {}; + for (const cat of categories) { + const blocks: string[] = []; + for (const s of sections) { + if (!sectionMatches(s, cat)) continue; + const text = sectionText(s); + if (text.trim().length === 0) continue; + const label = s.numeral ? `${s.numeral}. ${s.title}` : s.title; + blocks.push(`[${label}]\n${text}`); + } + // Triple-newline between blocks (dsdToPlainText caps internal runs at \n\n) so the + // summary builder can split blocks apart again for cross-category dedup. + if (blocks.length > 0) out[cat] = blocks.join('\n\n\n'); + } + return out; +} + +/** A flat list of section headings, for surfacing what was present on a miss. */ +export function sectionTitles(sections: DsdSection[]): string[] { + return sections + .filter((s) => s.numeral !== '') + .map((s) => `${s.numeral}. ${s.title}`); +} + +// --------------------------------------------------------------------------- +// Orchestration +// --------------------------------------------------------------------------- + +export interface ExtractedSections { + sections: Record; + url: string; + allTitles: string[]; +} + +/** Fetch document.xml, unzip, parse, and select the requested narrative categories. */ +export async function fetchAndExtractSections( + rceptNo: string, + categories: SectionCategory[], +): Promise { + const { bytes, url } = await dartApi.getBinary('/document.xml', { rcept_no: rceptNo }); + const body = extractDsdBody(bytes); + const all = splitSections(body); + return { + sections: selectSections(all, categories), + url, + allTitles: sectionTitles(all), + }; +} diff --git a/src/tools/finance-kr/read-filings-kr.test.ts b/src/tools/finance-kr/read-filings-kr.test.ts new file mode 100644 index 000000000..a1b23119f --- /dev/null +++ b/src/tools/finance-kr/read-filings-kr.test.ts @@ -0,0 +1,93 @@ +import { describe, it, expect, afterEach } from 'bun:test'; +import { pickRceptNo, buildSummaryInput, resolveInternalModel, type DartFiling } from './read-filings-kr.js'; + +// Representative /list.json rows (์ •๊ธฐ๊ณต์‹œ), date-desc as DART returns them. +const SAMSUNG: DartFiling[] = [ + { rcept_no: '20260515002181', report_nm: '๋ถ„๊ธฐ๋ณด๊ณ ์„œ (2026.03)', rcept_dt: '20260515' }, + { rcept_no: '20260310002820', report_nm: '์‚ฌ์—…๋ณด๊ณ ์„œ (2025.12)', rcept_dt: '20260310' }, + { rcept_no: '20251114002447', report_nm: '๋ถ„๊ธฐ๋ณด๊ณ ์„œ (2025.09)', rcept_dt: '20251114' }, + { rcept_no: '20250814003156', report_nm: '๋ฐ˜๊ธฐ๋ณด๊ณ ์„œ (2025.06)', rcept_dt: '20250814' }, + { rcept_no: '20250515001922', report_nm: '๋ถ„๊ธฐ๋ณด๊ณ ์„œ (2025.03)', rcept_dt: '20250515' }, + { rcept_no: '20250311001085', report_nm: '์‚ฌ์—…๋ณด๊ณ ์„œ (2024.12)', rcept_dt: '20250311' }, +]; + +describe('pickRceptNo', () => { + it('picks the most recent ์‚ฌ์—…๋ณด๊ณ ์„œ for annual', () => { + expect(pickRceptNo(SAMSUNG, 'annual')?.rcept_no).toBe('20260310002820'); + }); + + it('filters annual by fiscal year via the period month', () => { + expect(pickRceptNo(SAMSUNG, 'annual', 2024)?.rcept_no).toBe('20250311001085'); + }); + + it('picks ๋ฐ˜๊ธฐ๋ณด๊ณ ์„œ for semiannual', () => { + expect(pickRceptNo(SAMSUNG, 'semiannual')?.rcept_no).toBe('20250814003156'); + }); + + it('disambiguates 1๋ถ„๊ธฐ (.03) from 3๋ถ„๊ธฐ (.09)', () => { + expect(pickRceptNo(SAMSUNG, 'quarterly_1')?.rcept_no).toBe('20260515002181'); // 2026.03 + expect(pickRceptNo(SAMSUNG, 'quarterly_3')?.rcept_no).toBe('20251114002447'); // 2025.09 + }); + + it('prefers a [๊ธฐ์žฌ์ •์ •] amendment over the original', () => { + const withAmendment: DartFiling[] = [ + { rcept_no: 'AMEND', report_nm: '[๊ธฐ์žฌ์ •์ •]์‚ฌ์—…๋ณด๊ณ ์„œ (2025.12)', rcept_dt: '20260420' }, + { rcept_no: 'ORIG', report_nm: '์‚ฌ์—…๋ณด๊ณ ์„œ (2025.12)', rcept_dt: '20260310' }, + ]; + expect(pickRceptNo(withAmendment, 'annual')?.rcept_no).toBe('AMEND'); + }); + + it('returns null when no matching report type exists', () => { + const onlyHalf: DartFiling[] = [ + { rcept_no: 'H', report_nm: '๋ฐ˜๊ธฐ๋ณด๊ณ ์„œ (2025.06)', rcept_dt: '20250814' }, + ]; + expect(pickRceptNo(onlyHalf, 'annual')).toBeNull(); + }); +}); + +describe('buildSummaryInput', () => { + it('dedupes a block shared across categories (business โЇ risks ์œ„ํ—˜๊ด€๋ฆฌ)', () => { + // selectSections joins blocks with '\n\n\n'; ์œ„ํ—˜๊ด€๋ฆฌ appears under both categories. + const sections = { + business: '[1. ์‚ฌ์—…์˜ ๊ฐœ์š”]\n๋ฐ˜๋„์ฒด ์‚ฌ์—….\n\n\n[5. ์œ„ํ—˜๊ด€๋ฆฌ ๋ฐ ํŒŒ์ƒ๊ฑฐ๋ž˜]\nํ™˜์œจ ์œ„ํ—˜.', + risks: '[5. ์œ„ํ—˜๊ด€๋ฆฌ ๋ฐ ํŒŒ์ƒ๊ฑฐ๋ž˜]\nํ™˜์œจ ์œ„ํ—˜.\n\n\n[XI. ํˆฌ์ž์ž ๋ณดํ˜ธ]\n์ œ์žฌ ์—†์Œ.', + }; + const out = buildSummaryInput(sections); + // The ์œ„ํ—˜๊ด€๋ฆฌ block is emitted once total; risks' unique XI block survives. + expect(out.split('5. ์œ„ํ—˜๊ด€๋ฆฌ ๋ฐ ํŒŒ์ƒ๊ฑฐ๋ž˜').length - 1).toBe(1); + expect(out).toContain('๋ฐ˜๋„์ฒด ์‚ฌ์—…'); + expect(out).toContain('์ œ์žฌ ์—†์Œ'); + }); + + it('keeps non-overlapping categories intact', () => { + const out = buildSummaryInput({ business: '[1. ์‚ฌ์—…์˜ ๊ฐœ์š”]\nA.', mdna: '[1. ๋ถ„์„]\nB.' }); + expect(out).toContain('<>'); + expect(out).toContain('<>'); + expect(out).toContain('A.'); + expect(out).toContain('B.'); + }); +}); + +describe('resolveInternalModel', () => { + const original = process.env.READ_FILINGS_KR_MODEL; + afterEach(() => { + if (original === undefined) delete process.env.READ_FILINGS_KR_MODEL; + else process.env.READ_FILINGS_KR_MODEL = original; + }); + + it('defaults to the agent provider fast tier (cost)', () => { + delete process.env.READ_FILINGS_KR_MODEL; + expect(resolveInternalModel('gpt-5.5')).toBe('gpt-5.4-mini'); // OpenAI fastModel + expect(resolveInternalModel('claude-opus-4-8')).toBe('claude-haiku-4-5'); // Anthropic fastModel + }); + + it('honors READ_FILINGS_KR_MODEL override', () => { + process.env.READ_FILINGS_KR_MODEL = 'gpt-5.5'; + expect(resolveInternalModel('gpt-5.5')).toBe('gpt-5.5'); + }); + + it('ignores a `your-` placeholder override', () => { + process.env.READ_FILINGS_KR_MODEL = 'your-model'; + expect(resolveInternalModel('gpt-5.5')).toBe('gpt-5.4-mini'); + }); +}); diff --git a/src/tools/finance-kr/read-filings-kr.ts b/src/tools/finance-kr/read-filings-kr.ts new file mode 100644 index 000000000..353a4f319 --- /dev/null +++ b/src/tools/finance-kr/read-filings-kr.ts @@ -0,0 +1,509 @@ +import { DynamicStructuredTool } from '@langchain/core/tools'; +import type { RunnableConfig } from '@langchain/core/runnables'; +import { z } from 'zod'; +import { existsSync, mkdirSync, writeFileSync, readdirSync, statSync, unlinkSync } from 'fs'; +import { callLlm, getFastModel } from '../../model/llm.js'; +import { resolveProvider } from '../../providers.js'; +import { formatToolResult } from '../types.js'; +import { getCurrentDate } from '../../agent/prompts.js'; +import { resolveTicker } from '../../data/ticker-registry.js'; +import { dexterPath } from '../../utils/paths.js'; +import { readCache, writeCache } from '../../utils/cache.js'; +import { withTimeout, TTL_1H, TTL_24H } from '../finance/utils.js'; +import { isNoDataError } from './utils.js'; +import { dartApi } from './api.js'; +import { + fetchAndExtractSections, + SECTION_CATEGORIES, + type SectionCategory, + type ExtractedSections, +} from './dart-document.js'; + +export const READ_FILINGS_KR_DESCRIPTION = `Intelligent meta-tool for reading the QUALITATIVE narrative of a Korean (KOSPI/KOSDAQ) company's DART ์ •๊ธฐ๋ณด๊ณ ์„œ (์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ). Takes a natural-language query, finds the right report, extracts the relevant narrative sections from the original filing, and returns a grounded Korean analyst summary. + +## When to Use +- "ํšŒ์‚ฌ ์‚ฌ์—…์ด ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑ๋ผ ์žˆ์–ด" โ€” business structure / segments (II. ์‚ฌ์—…์˜ ๋‚ด์šฉ) +- "์ฃผ์š” ์ œํ’ˆ/๋งค์ถœ ๊ตฌ์„ฑ" โ€” main products & services (prose; for tables use get_financials_kr) +- "์ฃผ์š” ๋ฆฌ์Šคํฌ๊ฐ€ ๋ญ์•ผ" โ€” risk narrative (II.5 ์œ„ํ—˜๊ด€๋ฆฌ ๋ฐ ํŒŒ์ƒ๊ฑฐ๋ž˜ + XI ํˆฌ์ž์ž ๋ณดํ˜ธ) +- "๊ฒฝ์˜์ง„๋‹จ ์˜๊ฒฌ / MD&A" โ€” management discussion (IV. ์ด์‚ฌ์˜ ๊ฒฝ์˜์ง„๋‹จ ๋ฐ ๋ถ„์„์˜๊ฒฌ) +- Company overview (I. ํšŒ์‚ฌ์˜ ๊ฐœ์š”) + +## When NOT to Use +- Financial line items / numbers โ†’ get_financials_kr +- Disclosure metadata / filing history โ†’ get_filings_kr +- Shareholders, short interest, foreign ownership โ†’ the dedicated *_kr tools + +## Usage Notes +- Call ONCE with the full natural-language query. Accepts company names (์‚ผ์„ฑ์ „์ž โ†’ 005930). +- Returns a grounded summary in 'summary'; full extracted sections are persisted to rawSectionsFile for drill-down (read_file). +- 6-digit Korean tickers only. The narrative is qualitative โ€” numeric tables route to get_financials_kr.`; + +// --------------------------------------------------------------------------- +// Plan schema (LLM call #1) +// --------------------------------------------------------------------------- + +type ReportType = 'annual' | 'semiannual' | 'quarterly_1' | 'quarterly_3'; + +const PlanSchema = z.object({ + ticker: z + .string() + .describe('6-digit Korean stock ticker (e.g. 005930 for Samsung). Resolve company names to tickers yourself.'), + report_type: z + .enum(['annual', 'semiannual', 'quarterly_1', 'quarterly_3']) + .describe( + "Which periodic report to read: 'annual' (์‚ฌ์—…๋ณด๊ณ ์„œ, default), 'semiannual' (๋ฐ˜๊ธฐ๋ณด๊ณ ์„œ), " + + "'quarterly_1' (1๋ถ„๊ธฐ๋ณด๊ณ ์„œ), 'quarterly_3' (3๋ถ„๊ธฐ๋ณด๊ณ ์„œ). Use 'annual' unless the query names a quarter/half.", + ), + year: z + .number() + .int() + .min(2015) + .max(2100) + .nullable() + .describe('Business (fiscal) year of the report, e.g. 2025 for the FY2025 ์‚ฌ์—…๋ณด๊ณ ์„œ. null for the most recent.'), + sections: z + .array(z.enum(['overview', 'business', 'products', 'risks', 'mdna'])) + .min(1) + .describe( + 'Narrative section categories to read. Map intent: ์‚ฌ์—… ๊ตฌ์„ฑ/์„ธ๊ทธ๋จผํŠธโ†’["business"], ์ฃผ์š” ์ œํ’ˆ/๋งค์ถœ๊ตฌ์„ฑโ†’["products"], ' + + '์ฃผ์š” ๋ฆฌ์Šคํฌโ†’["risks"], ๊ฒฝ์˜์ง„๋‹จ/MD&Aโ†’["mdna"], ํšŒ์‚ฌ ๊ฐœ์š”โ†’["overview"]. Pick the minimum set.', + ), +}); + +type Plan = z.infer; + +function buildPlanPrompt(): string { + return `You are a Korean DART filing planning assistant. +Current date: ${getCurrentDate()} + +Given a user query about a Korean company's report narrative, return a structured plan: +- ticker (6-digit; resolve names: ์‚ผ์„ฑ์ „์žโ†’005930, SKํ•˜์ด๋‹‰์Šคโ†’000660, ๋„ค์ด๋ฒ„โ†’035420, LGํ™”ํ•™โ†’051910, ํ˜„๋Œ€์ฐจโ†’005380, ์นด์นด์˜คโ†’035720) +- report_type (annual unless the query names a quarter/half) +- year (fiscal year, or null for most recent) +- sections (which narrative categories answer the query) + +Section mapping: +- ์‚ฌ์—… ๊ตฌ์„ฑ / ์‚ฌ์—…๋ถ€๋ฌธ / ์„ธ๊ทธ๋จผํŠธ / "๋ฌด์Šจ ์‚ฌ์—…" โ†’ business +- ์ฃผ์š” ์ œํ’ˆ / ์ œํ’ˆ๊ตฐ / ๋งค์ถœ ๊ตฌ์„ฑ(์ •์„ฑ) โ†’ products +- ๋ฆฌ์Šคํฌ / ์œ„ํ—˜ / ์œ„ํ—˜์š”์ธ โ†’ risks +- ๊ฒฝ์˜์ง„๋‹จ / MD&A / ๊ฒฝ์˜์ง„ ๋ถ„์„ โ†’ mdna +- ํšŒ์‚ฌ ๊ฐœ์š” / ์—ฐํ˜ โ†’ overview +Choose the minimum set that answers the query.`; +} + +// --------------------------------------------------------------------------- +// rcept_no selection (deterministic, pure โ€” exported for tests) +// --------------------------------------------------------------------------- + +export interface DartFiling { + rcept_no?: string; + report_nm?: string; + rcept_dt?: string; +} + +const REPORT_NM_PATTERN: Record = { + annual: /์‚ฌ์—…๋ณด๊ณ ์„œ/, + semiannual: /๋ฐ˜๊ธฐ๋ณด๊ณ ์„œ/, + quarterly_1: /๋ถ„๊ธฐ๋ณด๊ณ ์„œ/, + quarterly_3: /๋ถ„๊ธฐ๋ณด๊ณ ์„œ/, +}; + +/** Period month/year encoded in `report_nm` like "์‚ฌ์—…๋ณด๊ณ ์„œ (2025.12)". */ +function periodMonth(f: DartFiling): string | null { + const m = String(f.report_nm ?? '').match(/\(\d{4}\.(\d{2})\)/); + return m ? m[1] : null; +} +function periodYear(f: DartFiling): number | null { + const m = String(f.report_nm ?? '').match(/\((\d{4})\.\d{2}\)/); + return m ? Number(m[1]) : null; +} + +/** + * Pick the single rcept_no for the requested report. Filters by report-name type, + * disambiguates 1๋ถ„๊ธฐ/3๋ถ„๊ธฐ by the period month (.03 vs .09, filing month as fallback), + * filters by fiscal year when given, and prefers a [๊ธฐ์žฌ์ •์ •] amendment (it supersedes + * the original) then the most recently received. + */ +export function pickRceptNo( + list: DartFiling[], + reportType: ReportType, + year?: number | null, +): DartFiling | null { + const pattern = REPORT_NM_PATTERN[reportType]; + let candidates = list.filter((f) => pattern.test(String(f.report_nm ?? ''))); + + if (reportType === 'quarterly_1' || reportType === 'quarterly_3') { + const wantMonth = reportType === 'quarterly_1' ? '03' : '09'; + const byPeriod = candidates.filter((f) => periodMonth(f) === wantMonth); + if (byPeriod.length > 0) { + candidates = byPeriod; + } else { + const wantFilingMonths = reportType === 'quarterly_1' ? ['04', '05', '06'] : ['10', '11', '12']; + const byFiling = candidates.filter((f) => wantFilingMonths.includes(String(f.rcept_dt ?? '').slice(4, 6))); + if (byFiling.length > 0) candidates = byFiling; + } + } + + if (year != null) { + const byYear = candidates.filter((f) => { + const py = periodYear(f); + return py != null ? py === year : String(f.rcept_dt ?? '').slice(0, 4) === String(year + 1); + }); + if (byYear.length > 0) candidates = byYear; + } + + if (candidates.length === 0) return null; + + const sorted = candidates + .slice() + .sort((a, b) => String(b.rcept_dt ?? '').localeCompare(String(a.rcept_dt ?? ''))); + const amended = sorted.find((f) => String(f.report_nm ?? '').includes('๊ธฐ์žฌ์ •์ •')); + return amended ?? sorted[0]; +} + +function listRange(year?: number | null): { bgn_de: string; end_de: string } { + if (year != null) { + // Report for FY `year` is filed in `year`+1 (annual ~Mar). Cover both years. + return { bgn_de: `${year}0101`, end_de: `${year + 1}0630` }; + } + const today = new Date(); + const past = new Date(today); + past.setUTCMonth(today.getUTCMonth() - 18); + const fmt = (d: Date) => d.toISOString().slice(0, 10).replace(/-/g, ''); + return { bgn_de: fmt(past), end_de: fmt(today) }; +} + +// --------------------------------------------------------------------------- +// Extraction (cached) + summarization input +// --------------------------------------------------------------------------- + +const CACHE_ENDPOINT = '/_dsd_sections'; // synthetic โ€” caches parsed text, never hits network +const DART_DOCUMENT_TIMEOUT_MS = 30_000; // the 5MB ZIP leg is slower than other sub-tools +const PER_SECTION_CHARS = 40_000; +const TOTAL_CHARS = 120_000; + +async function getOrExtractSections( + rceptNo: string, + categories: SectionCategory[], +): Promise { + // Cache every category keyed on rcept_no alone (filings are immutable) so a later + // request for any subset is served without re-downloading the ~MB document.xml ZIP. + const cacheParams = { rcept_no: rceptNo }; + let full: ExtractedSections; + const cached = readCache(CACHE_ENDPOINT, cacheParams, TTL_24H); + if (cached) { + full = { + sections: (cached.data.sections as Record) ?? {}, + allTitles: (cached.data.allTitles as string[]) ?? [], + url: cached.url, + }; + } else { + full = await withTimeout( + fetchAndExtractSections(rceptNo, [...SECTION_CATEGORIES]), + DART_DOCUMENT_TIMEOUT_MS, + 'read_filings_kr document.xml', + ); + writeCache(CACHE_ENDPOINT, cacheParams, { sections: full.sections, allTitles: full.allTitles }, full.url); + } + // Return only the requested categories from the full (cached) set. + const sections: Record = {}; + for (const cat of categories) { + if (full.sections[cat]) sections[cat] = full.sections[cat]; + } + return { sections, allTitles: full.allTitles, url: full.url }; +} + +/** + * Concatenate extracted sections under a per-section / total char budget, dropping + * section blocks already emitted under an earlier category โ€” business โЇ products and + * II.5 ์œ„ํ—˜๊ด€๋ฆฌ, so requesting business+risks would otherwise feed them to the LLM twice. + */ +export function buildSummaryInput(sections: Record): string { + const seenBlocks = new Set(); + const parts: string[] = []; + let total = 0; + for (const [cat, text] of Object.entries(sections)) { + if (total >= TOTAL_CHARS) break; + // selectSections joins blocks with '\n\n\n'; each block's first line is its [label]. + const blocks = text.split('\n\n\n').filter((b) => { + const label = b.split('\n', 1)[0]; + if (seenBlocks.has(label)) return false; + seenBlocks.add(label); + return true; + }); + if (blocks.length === 0) continue; + let t = blocks.join('\n\n'); + if (t.length > PER_SECTION_CHARS) t = `${t.slice(0, PER_SECTION_CHARS)}\nโ€ฆ(์ดํ•˜ ์ƒ๋žต)`; + if (total + t.length > TOTAL_CHARS) { + t = `${t.slice(0, Math.max(0, TOTAL_CHARS - total))}\nโ€ฆ(์ดํ•˜ ์ƒ๋žต)`; + } + total += t.length; + parts.push(`<<${cat}>>\n${t}`); + } + return parts.join('\n\n'); +} + +// Escape curly braces so LangChain's ChatPromptTemplate (the OpenAI/Gemini path in +// callLlm) doesn't treat filing text as template variables. The Anthropic path uses +// raw messages, so braces there are harmless โ€” escaping just mirrors US read_filings. +function escapeTemplateVars(str: string): string { + return str.replace(/\{/g, '{{').replace(/\}/g, '}}'); +} + +function buildSummaryPrompt( + corpName: string, + reportNm: string, + rceptNo: string, + body: string, +): string { + return `๋‹น์‹ ์€ ํ•œ๊ตญ ์ฃผ์‹ ๋ฆฌ์„œ์น˜ ์• ๋„๋ฆฌ์ŠคํŠธ์ž…๋‹ˆ๋‹ค. +์˜ค๋Š˜ ๋‚ ์งœ: ${getCurrentDate()} + +์•„๋ž˜๋Š” ${corpName}์˜ ${reportNm} (DART ์ ‘์ˆ˜๋ฒˆํ˜ธ ${rceptNo}) ๋ณธ๋ฌธ์—์„œ ์ถ”์ถœํ•œ ์ •์„ฑ์  ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค. +์‚ฌ์šฉ์ž ์งˆ๋ฌธ์— ๋Œ€ํ•ด ์•„๋ž˜ ๋ฐœ์ทŒ ๋‚ด์šฉ์—๋งŒ ๊ทผ๊ฑฐํ•˜์—ฌ ํ•œ๊ตญ์–ด๋กœ ๋‹ตํ•˜์„ธ์š”. + +์›์น™: +- ๋ฐœ์ทŒ์— ์žˆ๋Š” ๊ตฌ์ฒด์  ์‚ฌ์—…๋ถ€๋ฌธยท์ œํ’ˆยท๋ฆฌ์Šคํฌยท์ˆ˜์น˜๋งŒ ์ธ์šฉํ•˜์„ธ์š”. ๋ฐœ์ทŒ์— ์—†๋Š” ์‚ฌ์‹ค์„ ์ง€์–ด๋‚ด์ง€ ๋งˆ์„ธ์š”(ํ™˜๊ฐ ๊ธˆ์ง€). +- ๋ฐœ์ทŒ๊ฐ€ ์งˆ๋ฌธ์„ ๋‹ค๋ฃจ์ง€ ์•Š์œผ๋ฉด ๋ชจ๋ฅธ๋‹ค๊ณ  ๋ช…์‹œํ•˜์„ธ์š”. +- ๋‹จ์ˆœ ๋‚˜์—ด์ด ์•„๋‹ˆ๋ผ ํ•ต์‹ฌ์„ ์ข…ํ•ฉยท๋ถ„์„ํ•˜์„ธ์š”. +- ํ‘œ/์ˆ˜์น˜ ๋ฐ์ดํ„ฐ์˜ ์ •๋ฐ€ ์ง‘๊ณ„๋Š” get_financials_kr ์˜์—ญ์ด๋ฏ€๋กœ, ์—ฌ๊ธฐ์„œ๋Š” ์ •์„ฑ์  ํ•ด์„์— ์ง‘์ค‘ํ•˜์„ธ์š”. + +[๋ฐœ์ทŒ ๋ณธ๋ฌธ] +${body}`; +} + +// --------------------------------------------------------------------------- +// Raw-section persistence (mirrors get-business-report's prune idiom) +// --------------------------------------------------------------------------- + +const RAW_FILE_KEEP = 30; + +function pruneRawFilingFiles(dir: string, keepName: string): void { + try { + const entries = readdirSync(dir) + .filter((f) => f.startsWith('kr-filing-') && f.endsWith('.json') && f !== keepName) + .map((f) => { + try { + return { f, mtime: statSync(`${dir}/${f}`).mtimeMs }; + } catch { + return null; + } + }) + .filter((e): e is { f: string; mtime: number } => e !== null) + .sort((a, b) => b.mtime - a.mtime); + for (const { f } of entries.slice(RAW_FILE_KEEP)) { + try { + unlinkSync(`${dir}/${f}`); + } catch { + /* already removed */ + } + } + } catch { + /* best-effort */ + } +} + +function persistRawSections( + meta: { ticker: string; corp_code: string; corp_name: string; report_nm: string; rcept_no: string }, + sections: Record, +): string | undefined { + try { + const dir = dexterPath('tool-results'); + if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); + const safeCorp = meta.corp_code.replace(/[^a-zA-Z0-9_-]/g, '_'); + const safeRcept = meta.rcept_no.replace(/[^a-zA-Z0-9_-]/g, '_'); + const fileName = `kr-filing-${safeCorp}-${safeRcept}.json`; + const filePath = `${dir}/${fileName}`; + writeFileSync(filePath, JSON.stringify({ ...meta, sections }, null, 2), 'utf-8'); + pruneRawFilingFiles(dir, fileName); + return filePath; + } catch { + return undefined; // best-effort; the summary is the source of truth + } +} + +// --------------------------------------------------------------------------- +// Tool factory +// --------------------------------------------------------------------------- + +function errMsg(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} + +/** + * Model for this tool's internal plan + summary calls. These are extract/compress + * subtasks, so they default to the agent provider's fast tier (e.g. gpt-5.4-mini) + * to keep cost down while the agent itself stays on the flagship. Override with + * READ_FILINGS_KR_MODEL to force a specific model (e.g. the flagship for max quality). + */ +export function resolveInternalModel(agentModel: string): string { + const override = process.env.READ_FILINGS_KR_MODEL?.trim(); + if (override && !override.startsWith('your-')) return override; + return getFastModel(resolveProvider(agentModel).id, agentModel); +} + +/** + * Create a read_filings_kr tool configured with the given model. + * Workflow: (1) structured-output plan โ†’ (2) deterministic rcept_no lookup via + * /list.json โ†’ (3) document.xml fetch + DSD parse (cached) โ†’ (4) grounded Korean + * summary. Raw sections are persisted for drill-down. + */ +export function createReadFilingsKr(model: string): DynamicStructuredTool { + return new DynamicStructuredTool({ + name: 'read_filings_kr', + description: READ_FILINGS_KR_DESCRIPTION, + schema: z.object({ + query: z + .string() + .describe('Natural-language query about a Korean company report narrative (e.g. "์‚ผ์„ฑ์ „์ž ์‚ฌ์—… ๊ตฌ์„ฑ", "์ฃผ์š” ๋ฆฌ์Šคํฌ").'), + }), + func: async (input, _runManager, config?: RunnableConfig) => { + const onProgress = config?.metadata?.onProgress as ((msg: string) => void) | undefined; + // plan + summary run on the fast tier (cost); see resolveInternalModel. + const internalModel = resolveInternalModel(model); + + // 1. Plan ticker + report + sections. + onProgress?.('Planning DART filing read...'); + let plan: Plan; + try { + const { response } = await callLlm(input.query, { + model: internalModel, + systemPrompt: buildPlanPrompt(), + outputSchema: PlanSchema, + }); + plan = PlanSchema.parse(response); + } catch (error) { + return formatToolResult({ error: 'Failed to plan filing read', details: errMsg(error) }, []); + } + + const ticker = plan.ticker.trim(); + let resolved: { corp_code: string; corp_name: string } | null; + try { + resolved = await resolveTicker(ticker); + } catch (error) { + return formatToolResult({ error: 'Ticker resolution failed', ticker, details: errMsg(error) }, []); + } + if (!resolved) { + return formatToolResult({ error: `Ticker ${ticker} not found in DART corp registry` }, []); + } + const identity = { ticker, corp_code: resolved.corp_code, corp_name: resolved.corp_name }; + + // 2. Find the report's rcept_no via /list.json (์ •๊ธฐ๊ณต์‹œ). + onProgress?.(`Finding ${plan.report_type} report for ${resolved.corp_name}...`); + const range = listRange(plan.year); + let filings: DartFiling[] = []; + let listUrl = ''; + try { + const { data, url } = await dartApi.get( + '/list.json', + { + corp_code: resolved.corp_code, + bgn_de: range.bgn_de, + end_de: range.end_de, + pblntf_ty: 'A', + page_count: 100, + page_no: 1, + sort: 'date', + sort_mth: 'desc', + }, + { cacheable: true, ttlMs: TTL_1H }, + ); + listUrl = url; + filings = Array.isArray(data.list) ? (data.list as DartFiling[]) : []; + } catch (error) { + const message = errMsg(error); + if (!isNoDataError(message)) { + return formatToolResult({ ...identity, error: 'DART filing search failed', details: message }, []); + } + } + + const picked = pickRceptNo(filings, plan.report_type, plan.year); + if (!picked?.rcept_no) { + return formatToolResult( + { + ...identity, + error: `No ${plan.report_type} report found for ${ticker}${plan.year != null ? ` (${plan.year})` : ''}`, + availableReports: filings.map((f) => f.report_nm).filter(Boolean).slice(0, 20), + }, + listUrl ? [listUrl] : [], + ); + } + const rceptNo = String(picked.rcept_no); + const reportNm = String(picked.report_nm ?? ''); + + // 3. Fetch + parse the document body (cached). + onProgress?.(`Reading ${reportNm}...`); + let extracted: ExtractedSections; + try { + extracted = await getOrExtractSections(rceptNo, plan.sections as SectionCategory[]); + } catch (error) { + return formatToolResult( + { ...identity, report_nm: reportNm, rcept_no: rceptNo, error: 'Failed to read DART document', details: errMsg(error) }, + listUrl ? [listUrl] : [], + ); + } + + const sourceUrls = [extracted.url, listUrl].filter(Boolean); + const found = Object.keys(extracted.sections); + if (found.length === 0) { + return formatToolResult( + { + ...identity, + report_nm: reportNm, + rcept_no: rceptNo, + sections_found: [], + availableTitles: extracted.allTitles, + note: 'Requested narrative sections were not located in this report. See availableTitles and retry with a different focus, or use get_financials_kr for numeric data.', + }, + sourceUrls, + ); + } + + // 4. Persist raw sections for drill-down. + const rawSectionsFile = persistRawSections( + { ...identity, report_nm: reportNm, rcept_no: rceptNo }, + extracted.sections, + ); + + // 5. Summarize (grounded analyst answer). + onProgress?.('Summarizing report narrative...'); + let summary: string; + try { + const systemPrompt = escapeTemplateVars( + buildSummaryPrompt(resolved.corp_name, reportNm, rceptNo, buildSummaryInput(extracted.sections)), + ); + const { response } = await callLlm(input.query, { model: internalModel, systemPrompt }); + summary = typeof response === 'string' ? response : String(response); + } catch (error) { + return formatToolResult( + { + ...identity, + report_nm: reportNm, + rcept_no: rceptNo, + sections_found: found, + ...(rawSectionsFile ? { rawSectionsFile } : {}), + _error: `summarization failed: ${errMsg(error)}`, + }, + sourceUrls, + ); + } + + return formatToolResult( + { + ...identity, + report_nm: reportNm, + rcept_no: rceptNo, + sections_found: found, + summary, + ...(rawSectionsFile + ? { + rawSectionsFile, + note: 'summary is grounded in the report narrative. Use read_file on rawSectionsFile for the full extracted sections.', + } + : {}), + }, + sourceUrls, + ); + }, + }); +} diff --git a/src/tools/registry.test.ts b/src/tools/registry.test.ts new file mode 100644 index 000000000..82e37d7b0 --- /dev/null +++ b/src/tools/registry.test.ts @@ -0,0 +1,30 @@ +import { describe, it, expect, afterEach } from 'bun:test'; +import { getToolRegistry } from './registry.js'; + +const KEY = 'DART_API_KEY'; + +describe('read_filings_kr registration (DART gate)', () => { + const original = process.env[KEY]; + afterEach(() => { + if (original === undefined) delete process.env[KEY]; + else process.env[KEY] = original; + }); + + it('is registered when DART_API_KEY is set', () => { + process.env[KEY] = 'test-key'; + const names = getToolRegistry('gpt-5.5').map((t) => t.name); + expect(names).toContain('read_filings_kr'); + }); + + it('is absent when DART_API_KEY is missing', () => { + delete process.env[KEY]; + const names = getToolRegistry('gpt-5.5').map((t) => t.name); + expect(names).not.toContain('read_filings_kr'); + }); + + it('is absent for a `your-` placeholder key', () => { + process.env[KEY] = 'your-dart-key'; + const names = getToolRegistry('gpt-5.5').map((t) => t.name); + expect(names).not.toContain('read_filings_kr'); + }); +}); diff --git a/src/tools/registry.ts b/src/tools/registry.ts index be78e3572..2552defcc 100644 --- a/src/tools/registry.ts +++ b/src/tools/registry.ts @@ -15,6 +15,7 @@ import { createGetFinancialsKr, GET_FINANCIALS_KR_DESCRIPTION } from './finance- import { getFilingsKr, GET_FILINGS_KR_DESCRIPTION } from './finance-kr/get-filings-kr.js'; import { getLargeHoldersKr, GET_LARGE_HOLDERS_KR_DESCRIPTION } from './finance-kr/get-large-holders-kr.js'; import { getInsiderTradesKr, GET_INSIDER_TRADES_KR_DESCRIPTION } from './finance-kr/get-insider-trades-kr.js'; +import { createReadFilingsKr, READ_FILINGS_KR_DESCRIPTION } from './finance-kr/read-filings-kr.js'; import { getShortBalanceKr, GET_SHORT_BALANCE_KR_DESCRIPTION } from './finance-kr/get-short-balance-kr.js'; import { getForeignOwnershipKr, GET_FOREIGN_OWNERSHIP_KR_DESCRIPTION } from './finance-kr/get-foreign-ownership-kr.js'; import { getNpsHoldings_tool, GET_NPS_HOLDINGS_DESCRIPTION } from './finance-kr/get-nps-holdings.js'; @@ -216,6 +217,14 @@ export function getToolRegistry(model: string): RegisteredTool[] { compactDescription: 'Korean ์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ ์†Œ์œ ๋ณด๊ณ  (insider/executive ownership) for 6-digit tickers.', concurrencySafe: true, }); + tools.push({ + name: 'read_filings_kr', + tool: createReadFilingsKr(model), + description: READ_FILINGS_KR_DESCRIPTION, + compactDescription: + 'Korean DART ์‚ฌ์—…ยท๋ฐ˜๊ธฐยท๋ถ„๊ธฐ๋ณด๊ณ ์„œ narrative (์‚ฌ์—…์˜ ๋‚ด์šฉ, ์œ„ํ—˜๊ด€๋ฆฌ, ๊ฒฝ์˜์ง„๋‹จ MD&A) โ€” qualitative content for 6-digit tickers.', + concurrencySafe: true, + }); } // ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ comes from Naver's keyless JSON API โ€” always available. From 1478a498d2004b9919df42a854ea2c3083e9aa8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SH=20KWON=20=F0=9F=87=B0=F0=9F=87=B7?= Date: Wed, 3 Jun 2026 04:31:02 +0900 Subject: [PATCH 10/70] =?UTF-8?q?get=5Fmarket=5Fdata=5Fkr:=20=ED=98=84?= =?UTF-8?q?=EC=9E=AC=EA=B0=80=C2=B7=EC=8B=9C=EC=B4=9D=C2=B7PER/PBR=C2=B7?= =?UTF-8?q?=EB=AA=A9=ED=91=9C=EC=A3=BC=EA=B0=80=20=EC=BB=A8=EC=84=BC?= =?UTF-8?q?=EC=84=9C=EC=8A=A4=20(Naver=20keyless)=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * get_market_data_kr: ํ˜„์žฌ๊ฐ€ยท์‹œ์ดยทPER/PBRยท๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค (Naver keyless) The get_market_data equivalent for 6-digit KR tickers (get_market_data does not resolve them). A single keyless Naver /integration call returns: - price + daily change, 52-week range, session OHLC - market cap (์กฐ/์–ต ํŒŒ์‹ฑ) and derived shares outstanding (= ์‹œ์ด รท ํ˜„์žฌ๊ฐ€) - PER/PBR/EPS/BPS + forward ์ถ”์ •PER/EPS, dividend yield - analyst consensus: ๋ชฉํ‘œ์ฃผ๊ฐ€ + mean recommendation + implied upside - same-industry peers (ticker, price, market cap) Fills the biggest KR gap: DCF/valuation previously inferred current price and share count via web_search. The DCF skill now sources both from this tool. - parseNaverMetric: strips ๋ฐฐ/์›/% suffixes (parseKrxNumber broke on them) - parseKoreanMarketCapToKRW: ์กฐ/์–ต โ†’ KRW - registered unconditionally (keyless, like get_foreign_ownership_kr) - wired into prompt routing + KR research section, DCF skill, write-memo KR branch - refreshed kr-eval fixtures so synthesis questions (#4/#14) capture the new tool - unit tests + live-verified across KOSPI / KOSDAQ / holding names Co-Authored-By: Claude Opus 4.8 (1M context) * get_market_data_kr: review fixes โ€” not-found guard, 4xx message, parser cleanup - fetchNaverIntegration: an unknown 6-digit code returns 409 (not 404); surface it as a clear "check the 6-digit ticker" error instead of a raw HTTP status the model can't act on - hasNoMarketData guard: return _error instead of an all-null snapshot when a 200 payload carries no usable data (name+price+marketCap all null). Cannot false-positive on a valid ticker, whose 200 always includes stockName - parseNaverMetric: drop unused ์ฃผ from the strip class โ€” ์ฃผ only appears in Naver label keys (52์ฃผ, ์ฃผ๋‹น๋ฐฐ๋‹น๊ธˆ), never in parsed values โ€” + regression test - tests: hasNoMarketData false-positive/negative coverage, parseNaverMetric edges Verified the foreign-ownership sibling does NOT need the same 4xx branch: its /trend endpoint returns 200 + [] (not 409) for an unknown ticker, so an empty list is the correct response (indistinguishable from a valid no-data ticker). Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- .gitignore | 5 +- CLAUDE.md | 1 + .../default/business-overview-samsung.json | 4 +- .../default/cross-signal-alteogen.json | 47 ++-- .../fixtures/default/filings-hyundai.json | 6 +- .../fixtures/default/foreign-flow-hynix.json | 2 +- .../default/institutional-vs-foreign.json | 2 +- .../fixtures/default/key-risks-samsung.json | 4 +- .../default/large-holders-samsung.json | 4 +- .../default/samsung-fundamentals.json | 2 +- .../fixtures/default/samsung-synthesis.json | 40 ++-- .../default/short-balance-ecopro.json | 2 +- .../fixtures/default/spinoff-lgchem.json | 35 ++- src/agent/prompts.ts | 11 +- src/cli.ts | 4 + src/skills/dcf/SKILL.md | 4 +- src/skills/write-memo/SKILL.md | 7 + .../finance-kr/get-market-data-kr.test.ts | 154 +++++++++++++ src/tools/finance-kr/get-market-data-kr.ts | 202 ++++++++++++++++++ src/tools/finance-kr/naver-api.ts | 61 ++++++ src/tools/finance-kr/utils.ts | 19 ++ src/tools/registry.ts | 11 + 22 files changed, 541 insertions(+), 86 deletions(-) create mode 100644 src/tools/finance-kr/get-market-data-kr.test.ts create mode 100644 src/tools/finance-kr/get-market-data-kr.ts diff --git a/.gitignore b/.gitignore index 777a37399..8827ba008 100644 --- a/.gitignore +++ b/.gitignore @@ -60,4 +60,7 @@ logs/ coverage.json # Scratchpad files -scratchpads/* \ No newline at end of file +scratchpads/* + +# Claude Code runtime locks +.claude/scheduled_tasks.lock \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index d8f2f244b..6fb31a47a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -59,6 +59,7 @@ These bite without warning. Fix once you learn them. ### Phase 3 โ€” Korea-specific - [x] `get_foreign_ownership_kr` โ€” ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ. Source: **Naver mobile JSON** (`m.stock.naver.com/api/stock/{code}/trend`), keyless. Registered unconditionally. +- [x] `get_market_data_kr` โ€” ํ˜„์žฌ๊ฐ€ยท์ผ๋ณ€๋™ยท52์ฃผยท์‹œ๊ฐ€์ด์•กยท๋ฐœํ–‰์ฃผ์‹์ˆ˜(๋„์ถœ)ยทPER/PBR/EPS/BPSยท์ถ”์ •PER/EPSยท๋ฐฐ๋‹น์ˆ˜์ต๋ฅ ยท๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค(๋ชฉํ‘œ๊ฐ€+ํˆฌ์ž์˜๊ฒฌ)ยท๋™์ข… peer. Source: **Naver mobile JSON** (`m.stock.naver.com/api/stock/{code}/integration`), keyless. Registered unconditionally. The `get_market_data` equivalent for 6-digit tickers (get_market_data does NOT resolve them) โ€” unblocks DCF ํ˜„์žฌ๊ฐ€/๋ฐœํ–‰์ฃผ์‹์ˆ˜ (previously `web_search` inference). ์‹œ์ด์€ `2,075์กฐ 4,289์–ต` ๋ฌธ์ž์—ด์„ ์กฐ/์–ต ํŒŒ์‹ฑ; ๋ฐœํ–‰์ฃผ์‹์ˆ˜ = ์‹œ์ดรทํ˜„์žฌ๊ฐ€ ๋„์ถœ(์ •ํ™•๊ฐ’์€ `get_short_balance_kr.listedShares`); ์ง€ํ‘œ๊ฐ’์€ `๋ฐฐ`/`์›`/`%` ์ ‘๋ฏธ์‚ฌ๋ผ `parseNaverMetric`๋กœ ํŒŒ์‹ฑ. - [x] `get_short_balance_kr` โ€” ๊ณต๋งค๋„ ์ˆœ๋ณด์œ ์ž”๊ณ . Source: **KRX Data Marketplace login scrape** (bld `MDCSTAT30502`). Needs `KRX_ID`/`KRX_PW` (anonymous access returns `LOGOUT` since 2024โ€“25). Tickerโ†’ISIN via `MDCSTAT01901` (`src/data/krx-instrument-registry.ts`). Login flow ported from pykrx in `src/tools/finance-kr/krx-session.ts`. - [x] `get_nps_holdings` โ€” ๊ตญ๋ฏผ์—ฐ๊ธˆ ๋ณด์œ . Source: **data.go.kr odcloud** dataset 3070507 (year-end snapshot, not quarterly). Needs `DATA_GO_KR_SERVICE_KEY` (Decoded key). No ticker column โ†’ matches by Korean stock name. diff --git a/scripts/kr-eval/fixtures/default/business-overview-samsung.json b/scripts/kr-eval/fixtures/default/business-overview-samsung.json index 0ffb68d34..0cbc011f5 100644 --- a/scripts/kr-eval/fixtures/default/business-overview-samsung.json +++ b/scripts/kr-eval/fixtures/default/business-overview-samsung.json @@ -8,9 +8,9 @@ { "tool": "read_filings_kr", "args": { - "query": "์‚ผ์„ฑ์ „์ž 005930 ์ตœ๊ทผ ์‚ฌ์—…๋ณด๊ณ ์„œ ์‚ฌ์—…์˜ ๋‚ด์šฉ ์‚ฌ์—…๋ถ€๋ฌธ ๊ตฌ์„ฑ" + "query": "์‚ผ์„ฑ์ „์ž 005930 ์ตœ๊ทผ ์‚ฌ์—…๋ณด๊ณ ์„œ ์‚ฌ์—…์˜ ๋‚ด์šฉ: ์‚ฌ์—…๋ถ€๋ฌธ ๊ตฌ์„ฑ ๋ฐ ์ฃผ์š” ์ œํ’ˆ ์„œ๋น„์Šค ์š”์•ฝ" }, - "result": "{\"data\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_nm\":\"์‚ฌ์—…๋ณด๊ณ ์„œ (2025.12)\",\"rcept_no\":\"20260310002820\",\"sections_found\":[\"business\"],\"summary\":\"์‚ผ์„ฑ์ „์ž(005930)์˜ ์ตœ๊ทผ ์‚ฌ์—…๋ณด๊ณ ์„œ(2025.12, DART ์ ‘์ˆ˜๋ฒˆํ˜ธ 20260310002820)์ƒ ์‚ฌ์—…๋ถ€๋ฌธ์€ ํฌ๊ฒŒ **DX, DS, SDC, Harman**์œผ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค. ํšŒ์‚ฌ๋Š” ๋ณธ์‚ฌ๋ฅผ ๊ฑฐ์ ์œผ๋กœ ๊ตญ๋‚ด์™ธ ์ƒ์‚ฐยทํŒ๋งคยท์—ฐ๊ตฌ ๋ฒ•์ธ ๋“ฑ **308๊ฐœ ์ข…์†๊ธฐ์—…**์„ ํ†ตํ•ด ๊ธ€๋กœ๋ฒŒ ์ „์ž ์‚ฌ์—…์„ ์˜์œ„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n| ์‚ฌ์—…๋ถ€๋ฌธ | ์ฃผ์š” ์‚ฌ์—… ๋‚ด์šฉ | ์ฃผ์š” ์ œํ’ˆยท์„œ๋น„์Šค | 2025๋…„ ๋งค์ถœ |\\n|---|---|---|---:|\\n| **DX(Device eXperience)** | ์™„์ œํ’ˆ, ์ฆ‰ Set ์‚ฌ์—… ์ค‘์‹ฌ | TV, ๋ชจ๋‹ˆํ„ฐ, ๋ƒ‰์žฅ๊ณ , ์„ธํƒ๊ธฐ, ์—์–ด์ปจ, ์Šค๋งˆํŠธํฐ, ๋„คํŠธ์›Œํฌ์‹œ์Šคํ…œ, PC ๋“ฑ | **187์กฐ 9,673์–ต์›** / **56.3%** |\\n| **DS(Device Solutions)** | ๋ฐ˜๋„์ฒด ๋ถ€ํ’ˆ ์‚ฌ์—… | DRAM, NAND Flash, ๋ชจ๋ฐ”์ผAP, ์ด๋ฏธ์ง€์„ผ์„œ, Foundry ๋“ฑ | **130์กฐ 1,282์–ต์›** / **39.0%** |\\n| **SDC** | ๋””์Šคํ”Œ๋ ˆ์ด ํŒจ๋„ ์‚ฌ์—… | ์Šค๋งˆํŠธํฐ์šฉ OLED ํŒจ๋„, ํด๋”๋ธ”ยทํƒœ๋ธ”๋ฆฟยท๋…ธํŠธ๋ถยทAuto์šฉ OLED, QD-OLED ๋“ฑ | **29์กฐ 8,417์–ต์›** / **8.9%** |\\n| **Harman** | ์ „์žฅ ๋ฐ ์˜ค๋””์˜ค ์‚ฌ์—… | ๋””์ง€ํ„ธ ์ฝ•ํ•, ์นด์˜ค๋””์˜ค, ํ…”๋ ˆ๋งคํ‹ฑ์Šค, ํฌํ„ฐ๋ธ”ยท์‚ฌ์šด๋“œ๋ฐ” ์Šคํ”ผ์ปค, ์ปจ์Šˆ๋จธ์˜ค๋””์˜ค ๋“ฑ | **15์กฐ 7,833์–ต์›** / **4.7%** |\\n\\nํ•ต์‹ฌ์ ์œผ๋กœ ๋ณด๋ฉด, ์‚ผ์„ฑ์ „์ž๋Š” **DX ๋ถ€๋ฌธ์ด ๋งค์ถœ์˜ ์ ˆ๋ฐ˜ ์ด์ƒ์„ ์ฐจ์ง€ํ•˜๋Š” ์™„์ œํ’ˆ ์ค‘์‹ฌ ์ถ•**์ด๊ณ , **DS ๋ถ€๋ฌธ์ด ๋ฉ”๋ชจ๋ฆฌยท์‹œ์Šคํ…œ๋ฐ˜๋„์ฒดยทํŒŒ์šด๋“œ๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ํ•ต์‹ฌ ๋ถ€ํ’ˆ ์ถ•**์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์— **SDC๋Š” OLED ์ค‘์‹ฌ์˜ ๋””์Šคํ”Œ๋ ˆ์ด**, **Harman์€ ์ „์žฅ๋ถ€ํ’ˆ๊ณผ ์˜ค๋””์˜ค**๋ฅผ ๋‹ด๋‹นํ•˜๋ฉฐ ์‚ฌ์—… ํฌํŠธํด๋ฆฌ์˜ค๋ฅผ ๋ณด์™„ํ•ฉ๋‹ˆ๋‹ค.\\n\\n์‚ฌ์—… ์„ฑ๊ฒฉ๋ณ„๋กœ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n1. **Set ์‚ฌ์—…: DX ๋ถ€๋ฌธ** \\n TV, ์Šค๋งˆํŠธํฐ, ๊ฐ€์ „, PC, ๋„คํŠธ์›Œํฌ์‹œ์Šคํ…œ ๋“ฑ ์†Œ๋น„์žยท๊ธฐ์—…์šฉ ์™„์ œํ’ˆ์„ ์ƒ์‚ฐยทํŒ๋งคํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์—…๋ณด๊ณ ์„œ์ƒ DX ๋ถ€๋ฌธ์€ 2025๋…„ ๋งค์ถœ ๊ธฐ์ค€ ์ตœ๋Œ€ ๋ถ€๋ฌธ์ž…๋‹ˆ๋‹ค.\\n\\n2. **๋ถ€ํ’ˆ ์‚ฌ์—…: DS ๋ถ€๋ฌธ ๋ฐ SDC** \\n DS ๋ถ€๋ฌธ์€ DRAM, NAND Flash, ๋ชจ๋ฐ”์ผAP ๋“ฑ ๋ฐ˜๋„์ฒด๋ฅผ ์ƒ์‚ฐยทํŒ๋งคํ•˜๋ฉฐ, SDC๋Š” ์Šค๋งˆํŠธํฐ์šฉ OLED ํŒจ๋„ ๋“ฑ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ DS ๋ถ€๋ฌธ์€ AI ์ธํ”„๋ผ ํ™•๋Œ€์— ๋”ฐ๋ฅธ ์„œ๋ฒ„ํ–ฅ ๋ฉ”๋ชจ๋ฆฌ, HBM, ๊ณ ์šฉ๋Ÿ‰ DDR5, ์„œ๋ฒ„ SSD, Foundry ์„ ๋‹จ๊ณต์ • ๋“ฑ์„ ์ฃผ์š” ์„ฑ์žฅ์ถ•์œผ๋กœ ์ œ์‹œํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n3. **์ „์žฅยท์˜ค๋””์˜ค ์‚ฌ์—…: Harman** \\n Harman์€ ๋””์ง€ํ„ธ ์ฝ•ํ•, ์นด์˜ค๋””์˜ค ๋“ฑ ์ „์žฅ์ œํ’ˆ๊ณผ ํฌํ„ฐ๋ธ” ์Šคํ”ผ์ปค, ์‚ฌ์šด๋“œ๋ฐ” ๋“ฑ ์˜ค๋””์˜ค ์ œํ’ˆ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ์ „์žฅ๋ถ€ํ’ˆ์—์„œ๋Š” ์ฐจ๋Ÿ‰ ๋‚ด ๊ฒฝํ—˜, SDV ๋Œ€์‘, ADAS ํ†ตํ•ฉ ์ปจํŠธ๋กค๋Ÿฌ ๋“ฑ์ด ์ฃผ์š” ๋ฐฉํ–ฅ์œผ๋กœ ์–ธ๊ธ‰๋ฉ๋‹ˆ๋‹ค.\\n\\n์ง€์—ญ ์šด์˜ ๊ตฌ์กฐ๋„ ์‚ฌ์—…๋ถ€๋ฌธ๋ณ„๋กœ ๋‚˜๋‰ฉ๋‹ˆ๋‹ค. DX ๋ถ€๋ฌธ์€ ํ•ด์™ธ **9๊ฐœ ์ง€์—ญ์ด๊ด„**, DS ๋ถ€๋ฌธ์€ ํ•ด์™ธ **5๊ฐœ ์ง€์—ญ์ด๊ด„**์„ ๋‘๊ณ  ์žˆ์œผ๋ฉฐ, SDC์™€ Harman ์‚ฐํ•˜ ์ข…์†๊ธฐ์—…๊นŒ์ง€ ํฌํ•จํ•ด ๊ธ€๋กœ๋ฒŒ ์ƒ์‚ฐยทํŒ๋งคยท์—ฐ๊ตฌ ์ฒด๊ณ„๋ฅผ ์šด์˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\",\"rawSectionsFile\":\".dexter/tool-results/kr-filing-00126380-20260310002820.json\",\"note\":\"summary is grounded in the report narrative. Use read_file on rawSectionsFile for the full extracted sections.\"},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/document.xml?rcept_no=20260310002820\",\"https://opendart.fss.or.kr/api/list.json?corp_code=00126380&bgn_de=20241201&end_de=20260531&pblntf_ty=A&page_count=100&page_no=1&sort=date&sort_mth=desc\"]}" + "result": "{\"data\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_nm\":\"์‚ฌ์—…๋ณด๊ณ ์„œ (2025.12)\",\"rcept_no\":\"20260310002820\",\"sections_found\":[\"business\",\"products\"],\"summary\":\"์‚ผ์„ฑ์ „์ž์˜ ์ตœ๊ทผ ์‚ฌ์—…๋ณด๊ณ ์„œ ๊ธฐ์ค€ ์‚ฌ์—…๋ถ€๋ฌธ ๊ตฌ์„ฑ๊ณผ ์ฃผ์š” ์ œํ’ˆยท์„œ๋น„์Šค๋ฅผ ์š”์•ฝํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.\\n\\n## 1) ์‚ฌ์—…๋ถ€๋ฌธ ๊ตฌ์„ฑ\\n์‚ผ์„ฑ์ „์ž๋Š” ํฌ๊ฒŒ 4๊ฐœ ์ถ•์œผ๋กœ ์‚ฌ์—…์„ ์šด์˜ํ•ฉ๋‹ˆ๋‹ค.\\n\\n- **DX(Device eXperience) ๋ถ€๋ฌธ**\\n - ์†Œ๋น„์ž์šฉ ์™„์ œํ’ˆ ์ค‘์‹ฌ ์‚ฌ์—…\\n - TV, ๋ชจ๋‹ˆํ„ฐ, ๋ƒ‰์žฅ๊ณ , ์„ธํƒ๊ธฐ, ์—์–ด์ปจ, ์Šค๋งˆํŠธํฐ, ๋„คํŠธ์›Œํฌ์‹œ์Šคํ…œ, PC ๋“ฑ์„ ์ƒ์‚ฐยทํŒ๋งค\\n\\n- **DS(Device Solutions) ๋ถ€๋ฌธ**\\n - ๋ฐ˜๋„์ฒด ๋ถ€ํ’ˆ ์‚ฌ์—…\\n - DRAM, NAND Flash, ๋ชจ๋ฐ”์ผAP ๋“ฑ์„ ์ƒ์‚ฐยทํŒ๋งค\\n - ๋˜ํ•œ ์‹œ์Šคํ…œ ๋ฐ˜๋„์ฒด ๊ด€๋ จ ์‚ฌ์—…๋„ ํฌํ•จ\\n\\n- **SDC**\\n - ์Šค๋งˆํŠธํฐ์šฉ OLED ํŒจ๋„ ๋“ฑ์„ ์ƒ์‚ฐยทํŒ๋งค\\n\\n- **Harman**\\n - ์ „์žฅ ๋ฐ ์˜ค๋””์˜ค ์‚ฌ์—…\\n - ๋””์ง€ํ„ธ ์ฝ•ํ•, ์นด์˜ค๋””์˜ค, ํฌํ„ฐ๋ธ”/์‚ฌ์šด๋“œ๋ฐ” ์Šคํ”ผ์ปค ๋“ฑ ์ปจ์Šˆ๋จธ์˜ค๋””์˜ค ์ œํ’ˆ์„ ๊ฐœ๋ฐœยท์ƒ์‚ฐยทํŒ๋งค\\n\\n## 2) ์ฃผ์š” ์ œํ’ˆยท์„œ๋น„์Šค ์š”์•ฝ\\n### DX ๋ถ€๋ฌธ\\n- **์ฃผ์š” ์ œํ’ˆ**\\n - TV, ๋ชจ๋‹ˆํ„ฐ, ๋ƒ‰์žฅ๊ณ , ์„ธํƒ๊ธฐ, ์—์–ด์ปจ, ์Šค๋งˆํŠธํฐ, ๋„คํŠธ์›Œํฌ์‹œ์Šคํ…œ, PC\\n- **ํŠน์ง•**\\n - Set ์‚ฌ์—…์˜ ์ค‘์‹ฌ\\n - ํ”„๋ฆฌ๋ฏธ์—„ ์ œํ’ˆ, AI ๊ธฐ๋Šฅ, ์ดˆ๋Œ€ํ˜• TV, ํด๋”๋ธ” ์Šค๋งˆํŠธํฐ ๋“ฑ ์ฐจ๋ณ„ํ™” ์ œํ’ˆ์„ ํ†ตํ•ด ๊ฒฝ์Ÿ\\n\\n### DS ๋ถ€๋ฌธ\\n- **์ฃผ์š” ์ œํ’ˆ**\\n - DRAM, NAND Flash, ๋ชจ๋ฐ”์ผAP\\n - ๋ฉ”๋ชจ๋ฆฌ ๋ฐ ์‹œ์Šคํ…œ ๋ฐ˜๋„์ฒด ๊ด€๋ จ ์ œํ’ˆ\\n- **ํŠน์ง•**\\n - AI ์ˆ˜์š” ํ™•๋Œ€์— ๋”ฐ๋ผ HBM4, ๊ณ ์šฉ๋Ÿ‰ DDR5, LPDDR5x, ์„œ๋ฒ„ SSD, ๊ณ ์„ฑ๋Šฅ NAND ๊ด€๋ จ ์ œํ’ˆ ๋น„์ค‘ ํ™•๋Œ€๋ฅผ ์ถ”์ง„\\n\\n### SDC\\n- **์ฃผ์š” ์ œํ’ˆ**\\n - ์Šค๋งˆํŠธํฐ์šฉ OLED ํŒจ๋„\\n - ์ค‘์†Œํ˜• ๋ฐ ๋Œ€ํ˜• ๋””์Šคํ”Œ๋ ˆ์ด ๊ด€๋ จ ์ œํ’ˆ\\n- **ํŠน์ง•**\\n - OLED ์ค‘์‹ฌ\\n - ํด๋”๋ธ”, IT(ํƒœ๋ธ”๋ฆฟ/๋…ธํŠธ๋ถ), Auto, ์›Œ์น˜์šฉ์œผ๋กœ ์‘์šฉ์ฒ˜๋ฅผ ๋„“ํžˆ๋Š” ์ „๋žต\\n\\n### Harman\\n- **์ฃผ์š” ์ œํ’ˆ**\\n - ๋””์ง€ํ„ธ ์ฝ•ํ•, ์นด์˜ค๋””์˜ค\\n - ํฌํ„ฐ๋ธ”/์‚ฌ์šด๋“œ๋ฐ” ์Šคํ”ผ์ปค ๋“ฑ ์ปจ์Šˆ๋จธ์˜ค๋””์˜ค\\n- **ํŠน์ง•**\\n - ์ „์žฅ๋ถ€๋ฌธ ์ค‘์‹ฌ\\n - ์ฐจ๋Ÿ‰ ๋‚ด ๊ฒฝํ—˜(In-Cabin Experience) ๊ด€๋ จ ์ œํ’ˆ๊ณผ ์˜ค๋””์˜ค ์‚ฌ์—… ๊ฐ•ํ™”\\n\\n## 3) ํ•ต์‹ฌ ์ •๋ฆฌ\\n์‚ผ์„ฑ์ „์ž์˜ ์‚ฌ์—… ๊ตฌ์กฐ๋Š” \\n**์™„์ œํ’ˆ(DX) + ๋ฐ˜๋„์ฒด(DS) + ๋””์Šคํ”Œ๋ ˆ์ด(SDC) + ์ „์žฅยท์˜ค๋””์˜ค(Harman)** ๋กœ ์š”์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. \\n์ฆ‰, ์†Œ๋น„์ž ๊ฐ€์ „ยท๋ชจ๋ฐ”์ผ๋ถ€ํ„ฐ ๋ฐ˜๋„์ฒด, ๋””์Šคํ”Œ๋ ˆ์ด, ์ „์žฅ๊นŒ์ง€ ์•„์šฐ๋ฅด๋Š” **์ข…ํ•ฉ ์ „์ž๊ธฐ์—…** ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.\\n\\n์›ํ•˜์‹œ๋ฉด ๋‹ค์Œ ๋‹จ๊ณ„๋กœ \\n**โ€œ์‚ฌ์—…๋ถ€๋ฌธ๋ณ„ ๋งค์ถœ ๋น„์ค‘๊ณผ ์˜์—…์ด์ต๊นŒ์ง€ ํฌํ•จํ•œ ํ•œ ์žฅ ์š”์•ฝโ€** ํ˜•ํƒœ๋กœ๋„ ์ •๋ฆฌํ•ด๋“œ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\",\"rawSectionsFile\":\".dexter/tool-results/kr-filing-00126380-20260310002820.json\",\"note\":\"summary is grounded in the report narrative. Use read_file on rawSectionsFile for the full extracted sections.\"},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/document.xml?rcept_no=20260310002820\",\"https://opendart.fss.or.kr/api/list.json?corp_code=00126380&bgn_de=20241202&end_de=20260602&pblntf_ty=A&page_count=100&page_no=1&sort=date&sort_mth=desc\"]}" } ] } diff --git a/scripts/kr-eval/fixtures/default/cross-signal-alteogen.json b/scripts/kr-eval/fixtures/default/cross-signal-alteogen.json index 4bc47d200..6ed868bc5 100644 --- a/scripts/kr-eval/fixtures/default/cross-signal-alteogen.json +++ b/scripts/kr-eval/fixtures/default/cross-signal-alteogen.json @@ -8,7 +8,7 @@ { "tool": "memory_search", "args": { - "query": "์‚ฌ์šฉ์ž ํˆฌ์ž ๋ชฉํ‘œ ์œ„ํ—˜์„ฑํ–ฅ ํฌ์ง€์…˜ ์ œํ•œ ํ•œ๊ตญ์ฃผ์‹ ์•Œํ…Œ์˜ค์   196170 ๊ด€๋ จ ์„ ํ˜ธ" + "query": "์‚ฌ์šฉ์ž์˜ ํˆฌ์ž ๋ชฉํ‘œ ์œ„ํ—˜์„ฑํ–ฅ ํฌํŠธํด๋ฆฌ์˜ค ํฌ์ง€์…˜ ํ•œ๋„ ํ•œ๊ตญ์ฃผ์‹ ์•Œํ…Œ์˜ค์   ๊ด€๋ จ ์„ ํ˜ธ" }, "result": "{\"data\":{\"results\":[]}}" }, @@ -16,44 +16,50 @@ "tool": "get_nps_holdings", "args": { "ticker": "196170", - "limit": 10 + "limit": 5 }, "result": "{\"data\":{\"source\":\"NPS ๊ตญ๋‚ด์ฃผ์‹ ํˆฌ์ž์ •๋ณด (data.go.kr, year-end snapshot)\",\"query\":{\"name\":null,\"ticker\":\"196170\",\"resolvedName\":\"์•Œํ…Œ์˜ค์  \"},\"holdings\":[{\"name\":\"์•Œํ…Œ์˜ค์  \",\"evalAmount\":3615,\"weightPct\":0.26,\"shareRatioPct\":2.19}]}}" }, { - "tool": "get_large_holders_kr", + "tool": "get_foreign_ownership_kr", "args": { "ticker": "196170", "limit": 30 }, - "result": "{\"data\":{\"ticker\":\"196170\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"holders\":[{\"rcept_no\":\"20260407003590\",\"rcept_dt\":\"2026-04-07\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"๋ฐ•์ˆœ์žฌ\",\"stkqy\":\"10,931,550\",\"stkqy_irds\":\"-30,500\",\"stkrt\":\"20.40\",\"stkrt_irds\":\"-0.14\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"ํŠน๋ณ„๊ด€๊ณ„์ž ๋ณ€๋™ (์ž„์›์˜ ํ‡ด์ž„)\"},{\"rcept_no\":\"20250403003117\",\"rcept_dt\":\"2025-04-03\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"๋ฐ•์ˆœ์žฌ\",\"stkqy\":\"10,962,050\",\"stkqy_irds\":\"-95,430\",\"stkrt\":\"20.54\",\"stkrt_irds\":\"-0.26\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"ํŠน๋ณ„๊ด€๊ณ„์ž ๋ณ€๋™ (์ž„์›์˜ ์‹ ๊ทœ ์ž„ ๋ฐ ํ‡ด์ž„)\"},{\"rcept_no\":\"20241121000172\",\"rcept_dt\":\"2024-11-21\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"report_tp\":\"์•ฝ์‹\",\"repror\":\"ํ˜•์ธ์šฐ\",\"stkqy\":\"2,724,000\",\"stkqy_irds\":\"62,000\",\"stkrt\":\"5.11\",\"stkrt_irds\":\"0.12\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"์žฅ๋‚ด ๋งค์ˆ˜๋กœ ์ธํ•œ ์ง€๋ถ„์œจ ๋ณ€๋™ \"},{\"rcept_no\":\"20241030000276\",\"rcept_dt\":\"2024-10-30\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"report_tp\":\"์•ฝ์‹\",\"repror\":\"ํ˜•์ธ์šฐ\",\"stkqy\":\"2,662,000\",\"stkqy_irds\":\"962,000\",\"stkrt\":\"4.99\",\"stkrt_irds\":\"-1.06\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"๋ฌด์ƒ์ฆ์ž,์ง€๋ถ„๋ณ€๋™,์ด๋ฐœํ–‰์ฃผ์‹์ˆ˜ ์ฆ๊ฐ€๋กœ ์ธํ•œ ์ง€๋ถ„์œจ ๋ณ€๋™\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/majorstock.json?corp_code=00989619\"]}" + "result": "{\"data\":{\"ticker\":\"196170\",\"ownership\":[{\"date\":\"2026-06-01\",\"foreignHoldRatio\":14.53,\"foreignNetBuyQty\":-11369,\"orgNetBuyQty\":32463,\"individualNetBuyQty\":-16694,\"closePrice\":366000,\"tradingVolume\":637892},{\"date\":\"2026-05-29\",\"foreignHoldRatio\":14.51,\"foreignNetBuyQty\":-52380,\"orgNetBuyQty\":63376,\"individualNetBuyQty\":-11103,\"closePrice\":369000,\"tradingVolume\":524776},{\"date\":\"2026-05-28\",\"foreignHoldRatio\":14.78,\"foreignNetBuyQty\":-59350,\"orgNetBuyQty\":28932,\"individualNetBuyQty\":29399,\"closePrice\":369500,\"tradingVolume\":474757},{\"date\":\"2026-05-27\",\"foreignHoldRatio\":14.82,\"foreignNetBuyQty\":124100,\"orgNetBuyQty\":177641,\"individualNetBuyQty\":-304662,\"closePrice\":386500,\"tradingVolume\":948649},{\"date\":\"2026-05-26\",\"foreignHoldRatio\":14.29,\"foreignNetBuyQty\":-75626,\"orgNetBuyQty\":28015,\"individualNetBuyQty\":40719,\"closePrice\":365500,\"tradingVolume\":464199},{\"date\":\"2026-05-22\",\"foreignHoldRatio\":14.45,\"foreignNetBuyQty\":148954,\"orgNetBuyQty\":51563,\"individualNetBuyQty\":-195745,\"closePrice\":364500,\"tradingVolume\":748378},{\"date\":\"2026-05-21\",\"foreignHoldRatio\":14.07,\"foreignNetBuyQty\":-29380,\"orgNetBuyQty\":-23872,\"individualNetBuyQty\":49797,\"closePrice\":351500,\"tradingVolume\":444911},{\"date\":\"2026-05-20\",\"foreignHoldRatio\":14.15,\"foreignNetBuyQty\":-47664,\"orgNetBuyQty\":47417,\"individualNetBuyQty\":224,\"closePrice\":359500,\"tradingVolume\":436772},{\"date\":\"2026-05-19\",\"foreignHoldRatio\":14.24,\"foreignNetBuyQty\":-42540,\"orgNetBuyQty\":67674,\"individualNetBuyQty\":-29331,\"closePrice\":366500,\"tradingVolume\":754483},{\"date\":\"2026-05-18\",\"foreignHoldRatio\":14.34,\"foreignNetBuyQty\":-66312,\"orgNetBuyQty\":-13072,\"individualNetBuyQty\":82558,\"closePrice\":357500,\"tradingVolume\":490438}]},\"sourceUrls\":[\"https://m.stock.naver.com/api/stock/196170/trend\"]}" }, { - "tool": "get_foreign_ownership_kr", + "tool": "get_market_data_kr", + "args": { + "ticker": "196170" + }, + "result": "{\"data\":{\"ticker\":\"196170\",\"name\":\"์•Œํ…Œ์˜ค์  \",\"quote\":{\"date\":\"2026-06-01\",\"price\":366000,\"change\":-3000,\"changePct\":-0.81,\"direction\":\"ํ•˜๋ฝ\",\"open\":366000,\"high\":381000,\"low\":353500,\"high52w\":569000,\"low52w\":321500,\"volume\":637892},\"valuation\":{\"marketCap\":19183900000000,\"marketCapDisplay\":\"19์กฐ 1,839์–ต\",\"sharesOutstanding\":52415027,\"per\":148.24,\"pbr\":38.31,\"eps\":2415,\"bps\":9344,\"forwardPer\":69.6,\"forwardEps\":5144,\"dividendYieldPct\":0.1,\"dividendPerShare\":371},\"consensus\":{\"date\":\"2026-06-01\",\"targetPrice\":566667,\"recommendationMean\":4,\"upsidePct\":54.83},\"peers\":[{\"ticker\":\"950160\",\"name\":\"์ฝ”์˜ค๋กฑํ‹ฐ์Šˆ์ง„\",\"price\":107800,\"changePct\":15.05,\"marketCap\":9134546000000},{\"ticker\":\"087010\",\"name\":\"ํŽฉํŠธ๋ก \",\"price\":271000,\"changePct\":-2.69,\"marketCap\":6319001000000},{\"ticker\":\"298380\",\"name\":\"์—์ด๋น„์—˜๋ฐ”์ด์˜ค\",\"price\":107100,\"changePct\":-4.29,\"marketCap\":5996257000000},{\"ticker\":\"347850\",\"name\":\"๋””์•ค๋””ํŒŒ๋งˆํ…\",\"price\":84500,\"changePct\":1.68,\"marketCap\":3701450000000},{\"ticker\":\"145020\",\"name\":\"ํœด์ ค\",\"price\":243500,\"changePct\":-2.01,\"marketCap\":2996032000000},{\"ticker\":\"0009K0\",\"name\":\"์—์ž„๋“œ๋ฐ”์ด์˜ค\",\"price\":34000,\"changePct\":-0.58,\"marketCap\":2198819000000}]},\"sourceUrls\":[\"https://m.stock.naver.com/api/stock/196170/integration\"]}" + }, + { + "tool": "get_filings_kr", "args": { "ticker": "196170", - "limit": 30 + "limit": 30, + "start_date": "2025-06-02", + "end_date": "2026-06-02" }, - "result": "{\"data\":{\"ticker\":\"196170\",\"ownership\":[{\"date\":\"2026-05-29\",\"foreignHoldRatio\":14.69,\"foreignNetBuyQty\":-52380,\"orgNetBuyQty\":63376,\"individualNetBuyQty\":-11103,\"closePrice\":369000,\"tradingVolume\":524776},{\"date\":\"2026-05-28\",\"foreignHoldRatio\":14.78,\"foreignNetBuyQty\":-59350,\"orgNetBuyQty\":28932,\"individualNetBuyQty\":29399,\"closePrice\":369500,\"tradingVolume\":474757},{\"date\":\"2026-05-27\",\"foreignHoldRatio\":14.82,\"foreignNetBuyQty\":124100,\"orgNetBuyQty\":177641,\"individualNetBuyQty\":-304662,\"closePrice\":386500,\"tradingVolume\":948649},{\"date\":\"2026-05-26\",\"foreignHoldRatio\":14.29,\"foreignNetBuyQty\":-75626,\"orgNetBuyQty\":28015,\"individualNetBuyQty\":40719,\"closePrice\":365500,\"tradingVolume\":464199},{\"date\":\"2026-05-22\",\"foreignHoldRatio\":14.45,\"foreignNetBuyQty\":148954,\"orgNetBuyQty\":51563,\"individualNetBuyQty\":-195745,\"closePrice\":364500,\"tradingVolume\":748378},{\"date\":\"2026-05-21\",\"foreignHoldRatio\":14.07,\"foreignNetBuyQty\":-29380,\"orgNetBuyQty\":-23872,\"individualNetBuyQty\":49797,\"closePrice\":351500,\"tradingVolume\":444911},{\"date\":\"2026-05-20\",\"foreignHoldRatio\":14.15,\"foreignNetBuyQty\":-47664,\"orgNetBuyQty\":47417,\"individualNetBuyQty\":224,\"closePrice\":359500,\"tradingVolume\":436772},{\"date\":\"2026-05-19\",\"foreignHoldRatio\":14.24,\"foreignNetBuyQty\":-42540,\"orgNetBuyQty\":67674,\"individualNetBuyQty\":-29331,\"closePrice\":366500,\"tradingVolume\":754483},{\"date\":\"2026-05-18\",\"foreignHoldRatio\":14.34,\"foreignNetBuyQty\":-66312,\"orgNetBuyQty\":-13072,\"individualNetBuyQty\":82558,\"closePrice\":357500,\"tradingVolume\":490438},{\"date\":\"2026-05-15\",\"foreignHoldRatio\":14.57,\"foreignNetBuyQty\":-78600,\"orgNetBuyQty\":5850,\"individualNetBuyQty\":73006,\"closePrice\":369000,\"tradingVolume\":706370}]},\"sourceUrls\":[\"https://m.stock.naver.com/api/stock/196170/trend\"]}" + "result": "{\"data\":{\"ticker\":\"196170\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"filings\":[{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"๋ถ„๊ธฐ๋ณด๊ณ ์„œ (2026.03)\",\"rcept_no\":\"20260515001517\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20260515\",\"rm\":\"\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"ํˆฌ์žํŒ๋‹จ๊ด€๋ จ์ฃผ์š”๊ฒฝ์˜์‚ฌํ•ญ (์•„์ด์  ํ”ผ์ฃผ(ALT-L9, ์•„์ผ๋ฆฌ์•„ ๋ฐ”์ด์˜ค์‹œ๋ฐ€๋Ÿฌ) ์‹์•ฝ์ฒ˜ ํ’ˆ๋ชฉํ—ˆ๊ฐ€ ์Šน์ธ)\",\"rcept_no\":\"20260515900004\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20260515\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"์—ฐ๊ฒฐ์žฌ๋ฌด์ œํ‘œ๊ธฐ์ค€์˜์—…(์ž ์ •)์‹ค์ (๊ณต์ •๊ณต์‹œ) \",\"rcept_no\":\"20260508900289\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20260508\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"์ฃผ์‹๋“ฑ์˜๋Œ€๋Ÿ‰๋ณด์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ(์ผ๋ฐ˜)\",\"rcept_no\":\"20260407003590\",\"flr_nm\":\"๋ฐ•์ˆœ์žฌ\",\"rcept_dt\":\"20260407\",\"rm\":\"\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"๊ธฐ์—…์„ค๋ช…ํšŒ(IR)๊ฐœ์ตœ \",\"rcept_no\":\"20260407900868\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20260407\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"์‚ฌ์™ธ์ด์‚ฌ์˜์„ ์ž„ใ†ํ•ด์ž„๋˜๋Š”์ค‘๋„ํ‡ด์ž„์—๊ด€ํ•œ์‹ ๊ณ \",\"rcept_no\":\"20260401003222\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20260401\",\"rm\":\"\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"์ •๊ธฐ์ฃผ์ฃผ์ดํšŒ๊ฒฐ๊ณผ \",\"rcept_no\":\"20260331903683\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20260331\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"ํˆฌ์žํŒ๋‹จ๊ด€๋ จ์ฃผ์š”๊ฒฝ์˜์‚ฌํ•ญ (์žฌ์กฐํ•ฉ ์ธ๊ฐ„ ํžˆ์•Œ๋ฃจ๋กœ๋‹ˆ๋‹ค์ œ ์ œํ’ˆ ALT-B4 ๋…์ ์  ๋ผ์ด์„ ์Šค ๊ณ„์•ฝ ์ฒด๊ฒฐ)\",\"rcept_no\":\"20260325901047\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20260325\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"์‚ฌ์—…๋ณด๊ณ ์„œ (2025.12)\",\"rcept_no\":\"20260323001596\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20260323\",\"rm\":\"์—ฐ\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"๊ฐ์‚ฌ๋ณด๊ณ ์„œ์ œ์ถœ \",\"rcept_no\":\"20260323902120\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20260323\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"์˜๊ฒฐ๊ถŒ๋Œ€๋ฆฌํ–‰์‚ฌ๊ถŒ์œ ์ฐธ๊ณ ์„œ๋ฅ˜\",\"rcept_no\":\"20260316001308\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20260316\",\"rm\":\"\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"์ฃผ์ฃผ์ดํšŒ์†Œ์ง‘๊ณต๊ณ \",\"rcept_no\":\"20260316001218\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20260316\",\"rm\":\"\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"์ฃผ์ฃผ์ดํšŒ์†Œ์ง‘๊ฒฐ์˜ \",\"rcept_no\":\"20260316900686\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20260316\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"๋งค์ถœ์•ก๋˜๋Š”์†์ต๊ตฌ์กฐ30%(๋Œ€๊ทœ๋ชจ๋ฒ•์ธ์€15%)์ด์ƒ๋ณ€๋™ \",\"rcept_no\":\"20260227900641\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20260227\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"๊ธฐ์—…์„ค๋ช…ํšŒ(IR)๊ฐœ์ตœ \",\"rcept_no\":\"20260220900281\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20260220\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"ํ˜„๊ธˆใ†ํ˜„๋ฌผ๋ฐฐ๋‹น๊ฒฐ์ • \",\"rcept_no\":\"20260211900396\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20260211\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"์˜์—…(์ž ์ •)์‹ค์ (๊ณต์ •๊ณต์‹œ) \",\"rcept_no\":\"20260202900398\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20260202\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"ํˆฌ์žํŒ๋‹จ๊ด€๋ จ์ฃผ์š”๊ฒฝ์˜์‚ฌํ•ญ (์ธ๊ฐ„ ํžˆ์•Œ๋ฃจ๋กœ๋‹ˆ๋‹ค์ œ ์ œํ’ˆ(ALTB4) ๋…์ ์  ๋ผ์ด์„ ์Šค ๊ณ„์•ฝ ์ฒด๊ฒฐ)\",\"rcept_no\":\"20260120900306\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20260120\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"๊ธฐ์—…์„ค๋ช…ํšŒ(IR)๊ฐœ์ตœ \",\"rcept_no\":\"20260108900396\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20260108\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"๋Œ€ํ‘œ์ด์‚ฌ๋ณ€๊ฒฝ \",\"rcept_no\":\"20251226900529\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20251226\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"์ž„์‹œ์ฃผ์ฃผ์ดํšŒ๊ฒฐ๊ณผ \",\"rcept_no\":\"20251208900301\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20251208\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"ํˆฌ์žํŒ๋‹จ๊ด€๋ จ์ฃผ์š”๊ฒฝ์˜์‚ฌํ•ญ (ALTB4 ๋ผ์ด์„ ์Šค ๊ณ„์•ฝ ๊ด€๋ จ ๋งˆ์ผ์Šคํ†ค ๊ธฐ์ˆ ๋ฃŒ ์ˆ˜๋ น)\",\"rcept_no\":\"20251127900375\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20251127\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"์˜๊ฒฐ๊ถŒ๋Œ€๋ฆฌํ–‰์‚ฌ๊ถŒ์œ ์ฐธ๊ณ ์„œ๋ฅ˜\",\"rcept_no\":\"20251121000327\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20251121\",\"rm\":\"\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"์ฃผ์ฃผ์ดํšŒ์†Œ์ง‘๊ณต๊ณ \",\"rcept_no\":\"20251121000317\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20251121\",\"rm\":\"\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"๋ถ„๊ธฐ๋ณด๊ณ ์„œ (2025.09)\",\"rcept_no\":\"20251114000465\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20251114\",\"rm\":\"\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"์ƒ์žฅํ์ง€์Šน์ธ์„์œ„ํ•œ์˜์•ˆ์ƒ์ •๊ฒฐ์ • \",\"rcept_no\":\"20251106900183\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20251106\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"์ฃผ์ฃผ๋ช…๋ถ€ํ์‡„๊ธฐ๊ฐ„๋˜๋Š”๊ธฐ์ค€์ผ์„ค์ • \",\"rcept_no\":\"20251106900187\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20251106\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"์ฃผ์ฃผ์ดํšŒ์†Œ์ง‘๊ฒฐ์˜ (์ž„์‹œ์ฃผ์ฃผ์ดํšŒ)\",\"rcept_no\":\"20251106900175\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20251106\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"๊ธฐ์—…์„ค๋ช…ํšŒ(IR)๊ฐœ์ตœ \",\"rcept_no\":\"20251010900149\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20251010\",\"rm\":\"์ฝ”\"},{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"ํˆฌ์žํŒ๋‹จ๊ด€๋ จ์ฃผ์š”๊ฒฝ์˜์‚ฌํ•ญ (ALT-B4 ๊ด€๋ จ ๋งˆ์ผ์Šคํ†ค ๊ธฐ์ˆ ๋ฃŒ ์ˆ˜๋ น)\",\"rcept_no\":\"20251001900011\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20251001\",\"rm\":\"์ฝ”\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/list.json?corp_code=00989619&bgn_de=20250602&end_de=20260602&page_count=30&page_no=1&sort=date&sort_mth=desc\"]}" }, { "tool": "get_insider_trades_kr", "args": { "ticker": "196170", - "limit": 30 + "limit": 20 }, "result": "{\"data\":{\"ticker\":\"196170\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"trades\":[{\"rcept_no\":\"20250521000476\",\"rcept_dt\":\"2025-05-21\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"repror\":\"์ „ํƒœ์—ฐ\",\"isu_exctv_rgist_at\":\"๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"๋ถ€์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"7,200\",\"sp_stock_lmp_irds_cnt\":\"7,200\",\"sp_stock_lmp_rate\":\"0.01\",\"sp_stock_lmp_irds_rate\":\"0.01\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/elestock.json?corp_code=00989619\"]}" }, { - "tool": "get_filings_kr", + "tool": "get_large_holders_kr", "args": { "ticker": "196170", - "filing_type": "material", - "limit": 30, - "start_date": "2025-01-01", - "end_date": "2026-05-31" + "limit": 20 }, - "result": "{\"data\":{\"ticker\":\"196170\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"filings\":[{\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"stock_code\":\"196170\",\"corp_cls\":\"K\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์œ ์ƒ์ฆ์ž๊ฒฐ์ •)\",\"rcept_no\":\"20250204000409\",\"flr_nm\":\"์•Œํ…Œ์˜ค์  \",\"rcept_dt\":\"20250204\",\"rm\":\"\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/list.json?corp_code=00989619&bgn_de=20250101&end_de=20260531&pblntf_ty=B&page_count=30&page_no=1&sort=date&sort_mth=desc\"]}" + "result": "{\"data\":{\"ticker\":\"196170\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"holders\":[{\"rcept_no\":\"20260407003590\",\"rcept_dt\":\"2026-04-07\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"๋ฐ•์ˆœ์žฌ\",\"stkqy\":\"10,931,550\",\"stkqy_irds\":\"-30,500\",\"stkrt\":\"20.40\",\"stkrt_irds\":\"-0.14\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"ํŠน๋ณ„๊ด€๊ณ„์ž ๋ณ€๋™ (์ž„์›์˜ ํ‡ด์ž„)\"},{\"rcept_no\":\"20250403003117\",\"rcept_dt\":\"2025-04-03\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"๋ฐ•์ˆœ์žฌ\",\"stkqy\":\"10,962,050\",\"stkqy_irds\":\"-95,430\",\"stkrt\":\"20.54\",\"stkrt_irds\":\"-0.26\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"ํŠน๋ณ„๊ด€๊ณ„์ž ๋ณ€๋™ (์ž„์›์˜ ์‹ ๊ทœ ์ž„ ๋ฐ ํ‡ด์ž„)\"},{\"rcept_no\":\"20241121000172\",\"rcept_dt\":\"2024-11-21\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"report_tp\":\"์•ฝ์‹\",\"repror\":\"ํ˜•์ธ์šฐ\",\"stkqy\":\"2,724,000\",\"stkqy_irds\":\"62,000\",\"stkrt\":\"5.11\",\"stkrt_irds\":\"0.12\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"์žฅ๋‚ด ๋งค์ˆ˜๋กœ ์ธํ•œ ์ง€๋ถ„์œจ ๋ณ€๋™ \"},{\"rcept_no\":\"20241030000276\",\"rcept_dt\":\"2024-10-30\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"report_tp\":\"์•ฝ์‹\",\"repror\":\"ํ˜•์ธ์šฐ\",\"stkqy\":\"2,662,000\",\"stkqy_irds\":\"962,000\",\"stkrt\":\"4.99\",\"stkrt_irds\":\"-1.06\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"๋ฌด์ƒ์ฆ์ž,์ง€๋ถ„๋ณ€๋™,์ด๋ฐœํ–‰์ฃผ์‹์ˆ˜ ์ฆ๊ฐ€๋กœ ์ธํ•œ ์ง€๋ถ„์œจ ๋ณ€๋™\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/majorstock.json?corp_code=00989619\"]}" }, { "tool": "get_short_balance_kr", @@ -61,21 +67,14 @@ "ticker": "196170", "limit": 30 }, - "result": "{\"data\":{\"ticker\":\"196170\",\"isin\":\"KR7196170005\",\"name\":\"์•Œํ…Œ์˜ค์  \",\"short\":[{\"date\":\"2026-05-27\",\"balanceQty\":1205800,\"listedShares\":53586343,\"balanceAmount\":466041700000,\"marketCap\":20711121569500,\"balanceRatio\":2.25},{\"date\":\"2026-05-26\",\"balanceQty\":1286670,\"listedShares\":53586343,\"balanceAmount\":470277885000,\"marketCap\":19585808366500,\"balanceRatio\":2.4},{\"date\":\"2026-05-22\",\"balanceQty\":1243707,\"listedShares\":53586343,\"balanceAmount\":453331201500,\"marketCap\":19532222023500,\"balanceRatio\":2.32},{\"date\":\"2026-05-21\",\"balanceQty\":1333634,\"listedShares\":53586343,\"balanceAmount\":468772351000,\"marketCap\":18835599564500,\"balanceRatio\":2.49},{\"date\":\"2026-05-20\",\"balanceQty\":1321033,\"listedShares\":53586343,\"balanceAmount\":474911363500,\"marketCap\":19264290308500,\"balanceRatio\":2.47},{\"date\":\"2026-05-19\",\"balanceQty\":1305095,\"listedShares\":53586343,\"balanceAmount\":478317317500,\"marketCap\":19639394709500,\"balanceRatio\":2.44},{\"date\":\"2026-05-18\",\"balanceQty\":1329259,\"listedShares\":53586343,\"balanceAmount\":475210092500,\"marketCap\":19157117622500,\"balanceRatio\":2.48},{\"date\":\"2026-05-15\",\"balanceQty\":1305448,\"listedShares\":53533843,\"balanceAmount\":481710312000,\"marketCap\":19753988067000,\"balanceRatio\":2.44},{\"date\":\"2026-05-14\",\"balanceQty\":1283139,\"listedShares\":53533843,\"balanceAmount\":494008515000,\"marketCap\":20610529555000,\"balanceRatio\":2.4},{\"date\":\"2026-05-13\",\"balanceQty\":1444301,\"listedShares\":53533843,\"balanceAmount\":511282554000,\"marketCap\":18950980422000,\"balanceRatio\":2.7},{\"date\":\"2026-05-12\",\"balanceQty\":1312876,\"listedShares\":53533843,\"balanceAmount\":449003592000,\"marketCap\":18308574306000,\"balanceRatio\":2.45},{\"date\":\"2026-05-11\",\"balanceQty\":1392048,\"listedShares\":53533843,\"balanceAmount\":452415600000,\"marketCap\":17398498975000,\"balanceRatio\":2.6},{\"date\":\"2026-05-08\",\"balanceQty\":1386297,\"listedShares\":53533843,\"balanceAmount\":472034128500,\"marketCap\":18228273541500,\"balanceRatio\":2.59},{\"date\":\"2026-05-07\",\"balanceQty\":1347885,\"listedShares\":53533843,\"balanceAmount\":480521002500,\"marketCap\":19084815029500,\"balanceRatio\":2.52},{\"date\":\"2026-05-06\",\"balanceQty\":1327836,\"listedShares\":53533843,\"balanceAmount\":482668386000,\"marketCap\":19459551930500,\"balanceRatio\":2.48},{\"date\":\"2026-05-04\",\"balanceQty\":1301448,\"listedShares\":53533843,\"balanceAmount\":485440104000,\"marketCap\":19968123439000,\"balanceRatio\":2.43}]},\"sourceUrls\":[\"https://data.krx.co.kr/comm/bldAttendant/getJsonData.cmd?bld=dbms%2FMDC%2FSTAT%2Fsrt%2FMDCSTAT30502\"]}" - }, - { - "tool": "get_financials_kr", - "args": { - "query": "์•Œํ…Œ์˜ค์   196170 ์ตœ๊ทผ 3~5๋…„ ์—ฐ๊ฐ„ ๋ฐ ์ตœ๊ทผ ๋ถ„๊ธฐ K-IFRS ์š”์•ฝ: ๋งค์ถœ์•ก, ์˜์—…์ด์ต, ์ˆœ์ด์ต, ์˜์—…์ด์ต๋ฅ , ์ˆœ์ด์ต๋ฅ , ROE, ๋ถ€์ฑ„๋น„์œจ, ํ˜„๊ธˆํ๋ฆ„" - }, - "result": "{\"data\":{\"get_business_report_kr_196170\":{\"ticker\":\"196170\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"report_type\":\"quarterly_1\",\"fs_div\":\"CFS\",\"periods\":[{\"bsns_year\":2026,\"report_type\":\"quarterly_1\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"1๋ถ„๊ธฐ ๋ˆ„์ (thstrm=๋‹น๊ธฐ, frmtrm=์ „๋…„ ๋™๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„์€ YTD; BS๋Š” ๋ถ„๊ธฐ๋ง vs ์ „๋…„๋ง.\",\"incomeStatement\":{\"revenue\":{\"current\":71598550624,\"prior\":83696077912,\"label\":\"์ˆ˜์ต(๋งค์ถœ์•ก)\",\"display\":\"716์–ต\"},\"operatingProfit\":{\"current\":39300649498,\"prior\":61017565621,\"label\":\"์˜์—…์ด์ต(์†์‹ค)\",\"display\":\"393์–ต\"},\"netIncome\":{\"current\":71344275231,\"prior\":83037817151,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"713์–ต\"},\"controllingNetIncome\":{\"current\":71697430092,\"prior\":83176892415,\"label\":\"์ง€๋ฐฐ๊ธฐ์—…์˜ ์†Œ์œ ์ฃผ์—๊ฒŒ ๊ท€์†๋˜๋Š” ๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"717์–ต\"},\"eps\":{\"current\":1340,\"prior\":1561,\"label\":\"๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต(์†์‹ค)\",\"display\":\"1,340์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":729469655232,\"prior\":698742517170,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"7,295์–ต\"},\"totalLiabilities\":{\"current\":216611535645,\"prior\":244299577022,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"2,166์–ต\"},\"totalEquity\":{\"current\":512858119587,\"prior\":454442940148,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"5,129์–ต\"},\"cashAndEquivalents\":{\"current\":42276215274,\"prior\":66380706542,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"423์–ต\"}},\"cashFlow\":{\"operating\":{\"current\":20990009464,\"prior\":-4818704171,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"210์–ต\"},\"investing\":{\"current\":-45527020941,\"prior\":-122430965346,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-455์–ต\"},\"financing\":{\"current\":-97920386,\"prior\":153406458502,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-97,920,386\"},\"capex\":{\"current\":13857283233,\"prior\":875102242,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"139์–ต\"}},\"ratios\":{\"operatingMarginPct\":54.9,\"netMarginPct\":99.6,\"roePct\":null,\"debtToEquityPct\":42.2,\"revenueYoYPct\":-14.5,\"operatingProfitYoYPct\":-35.6,\"netIncomeYoYPct\":-14.1,\"freeCashFlow\":7132726231,\"freeCashFlowDisplay\":\"71์–ต\"}}],\"rawLineItemsFile\":\".dexter/tool-results/kr-financials-00989619-11013-CFS-2026_2026.json\",\"note\":\"periods[].summary holds normalized key metrics (KRW). Use read_file on rawLineItemsFile only for accounts not in the summary.\"}},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2025&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2024&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2023&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2022&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2021&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2026&reprt_code=11013&fs_div=CFS\"]}" + "result": "{\"data\":{\"ticker\":\"196170\",\"isin\":\"KR7196170005\",\"name\":\"์•Œํ…Œ์˜ค์  \",\"short\":[{\"date\":\"2026-05-28\",\"balanceQty\":1251992,\"listedShares\":53586343,\"balanceAmount\":462611044000,\"marketCap\":19800153738500,\"balanceRatio\":2.34},{\"date\":\"2026-05-27\",\"balanceQty\":1205800,\"listedShares\":53586343,\"balanceAmount\":466041700000,\"marketCap\":20711121569500,\"balanceRatio\":2.25},{\"date\":\"2026-05-26\",\"balanceQty\":1286670,\"listedShares\":53586343,\"balanceAmount\":470277885000,\"marketCap\":19585808366500,\"balanceRatio\":2.4},{\"date\":\"2026-05-22\",\"balanceQty\":1243707,\"listedShares\":53586343,\"balanceAmount\":453331201500,\"marketCap\":19532222023500,\"balanceRatio\":2.32},{\"date\":\"2026-05-21\",\"balanceQty\":1333634,\"listedShares\":53586343,\"balanceAmount\":468772351000,\"marketCap\":18835599564500,\"balanceRatio\":2.49},{\"date\":\"2026-05-20\",\"balanceQty\":1321033,\"listedShares\":53586343,\"balanceAmount\":474911363500,\"marketCap\":19264290308500,\"balanceRatio\":2.47},{\"date\":\"2026-05-19\",\"balanceQty\":1305095,\"listedShares\":53586343,\"balanceAmount\":478317317500,\"marketCap\":19639394709500,\"balanceRatio\":2.44},{\"date\":\"2026-05-18\",\"balanceQty\":1329259,\"listedShares\":53586343,\"balanceAmount\":475210092500,\"marketCap\":19157117622500,\"balanceRatio\":2.48},{\"date\":\"2026-05-15\",\"balanceQty\":1305448,\"listedShares\":53533843,\"balanceAmount\":481710312000,\"marketCap\":19753988067000,\"balanceRatio\":2.44},{\"date\":\"2026-05-14\",\"balanceQty\":1283139,\"listedShares\":53533843,\"balanceAmount\":494008515000,\"marketCap\":20610529555000,\"balanceRatio\":2.4},{\"date\":\"2026-05-13\",\"balanceQty\":1444301,\"listedShares\":53533843,\"balanceAmount\":511282554000,\"marketCap\":18950980422000,\"balanceRatio\":2.7},{\"date\":\"2026-05-12\",\"balanceQty\":1312876,\"listedShares\":53533843,\"balanceAmount\":449003592000,\"marketCap\":18308574306000,\"balanceRatio\":2.45},{\"date\":\"2026-05-11\",\"balanceQty\":1392048,\"listedShares\":53533843,\"balanceAmount\":452415600000,\"marketCap\":17398498975000,\"balanceRatio\":2.6},{\"date\":\"2026-05-08\",\"balanceQty\":1386297,\"listedShares\":53533843,\"balanceAmount\":472034128500,\"marketCap\":18228273541500,\"balanceRatio\":2.59},{\"date\":\"2026-05-07\",\"balanceQty\":1347885,\"listedShares\":53533843,\"balanceAmount\":480521002500,\"marketCap\":19084815029500,\"balanceRatio\":2.52},{\"date\":\"2026-05-06\",\"balanceQty\":1327836,\"listedShares\":53533843,\"balanceAmount\":482668386000,\"marketCap\":19459551930500,\"balanceRatio\":2.48},{\"date\":\"2026-05-04\",\"balanceQty\":1301448,\"listedShares\":53533843,\"balanceAmount\":485440104000,\"marketCap\":19968123439000,\"balanceRatio\":2.43}]},\"sourceUrls\":[\"https://data.krx.co.kr/comm/bldAttendant/getJsonData.cmd?bld=dbms%2FMDC%2FSTAT%2Fsrt%2FMDCSTAT30502\"]}" }, { "tool": "get_financials_kr", "args": { - "query": "์•Œํ…Œ์˜ค์   196170 2021 2022 2023 2024 2025 ์‚ฌ์—…๋ณด๊ณ ์„œ ์—ฐ๊ฒฐ K-IFRS ์—ฐ๊ฐ„ ์š”์•ฝ ๋งค์ถœ์•ก ์˜์—…์ด์ต ๋‹น๊ธฐ์ˆœ์ด์ต ์˜์—…์ด์ต๋ฅ  ์ˆœ์ด์ต๋ฅ  ROE ๋ถ€์ฑ„๋น„์œจ" + "query": "์•Œํ…Œ์˜ค์   196170 ์ตœ๊ทผ 5๋…„ ๋ฐ ์ตœ๊ทผ ๋ถ„๊ธฐ K-IFRS ์—ฐ๊ฒฐ/๋ณ„๋„ ๋งค์ถœ์•ก ์˜์—…์ด์ต ์ˆœ์ด์ต ์˜์—…์ด์ต๋ฅ  ์ˆœ์ด์ต๋ฅ  ROE ์š”์•ฝ, YoY ํฌํ•จ" }, - "result": "{\"data\":{\"get_business_report_kr_196170\":{\"ticker\":\"196170\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"periods\":[{\"bsns_year\":2025,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":215858918957,\"prior\":102854620800,\"label\":\"์ˆ˜์ต(๋งค์ถœ์•ก)\",\"display\":\"2,159์–ต\"},\"operatingProfit\":{\"current\":106900077097,\"prior\":25403990856,\"label\":\"์˜์—…์ด์ต(์†์‹ค)\",\"display\":\"1,069์–ต\"},\"netIncome\":{\"current\":145172981930,\"prior\":60690355931,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"1,452์–ต\"},\"controllingNetIncome\":{\"current\":141666024710,\"prior\":62252318998,\"label\":\"์ง€๋ฐฐ๊ธฐ์—…์˜ ์†Œ์œ ์ฃผ์—๊ฒŒ ๊ท€์†๋˜๋Š” ๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"1,417์–ต\"},\"eps\":{\"current\":2653,\"prior\":1172,\"label\":\"๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต(์†์‹ค)\",\"display\":\"2,653์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":698742517170,\"prior\":409011974213,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"6,987์–ต\"},\"totalLiabilities\":{\"current\":244299577022,\"prior\":134920913122,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"2,443์–ต\"},\"totalEquity\":{\"current\":454442940148,\"prior\":274091061091,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"4,544์–ต\"},\"cashAndEquivalents\":{\"current\":66380706542,\"prior\":18993906684,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"664์–ต\"}},\"cashFlow\":{\"operating\":{\"current\":124127150186,\"prior\":53044297258,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"1,241์–ต\"},\"investing\":{\"current\":-231120222423,\"prior\":-107771450399,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-2,311์–ต\"},\"financing\":{\"current\":155645065971,\"prior\":43258945323,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"1,556์–ต\"},\"capex\":{\"current\":23006600667,\"prior\":2718373772,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"230์–ต\"}},\"ratios\":{\"operatingMarginPct\":49.5,\"netMarginPct\":67.3,\"roePct\":31.9,\"debtToEquityPct\":53.8,\"revenueYoYPct\":109.9,\"operatingProfitYoYPct\":320.8,\"netIncomeYoYPct\":139.2,\"freeCashFlow\":101120549519,\"freeCashFlowDisplay\":\"1,011์–ต\"}},{\"bsns_year\":2024,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":102854620800,\"prior\":96522928887,\"label\":\"์ˆ˜์ต(๋งค์ถœ์•ก)\",\"display\":\"1,029์–ต\"},\"operatingProfit\":{\"current\":25403990856,\"prior\":-9736838487,\"label\":\"์˜์—…์ด์ต(์†์‹ค)\",\"display\":\"254์–ต\"},\"netIncome\":{\"current\":60690355931,\"prior\":-3582208686,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"607์–ต\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":1172,\"prior\":-65,\"label\":\"๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต(์†์‹ค)\",\"display\":\"1,172์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":409011974213,\"prior\":256099994583,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"4,090์–ต\"},\"totalLiabilities\":{\"current\":134920913122,\"prior\":108258260247,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"1,349์–ต\"},\"totalEquity\":{\"current\":274091061091,\"prior\":147841734336,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"2,741์–ต\"},\"cashAndEquivalents\":{\"current\":18993906684,\"prior\":30199528819,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"190์–ต\"}},\"cashFlow\":{\"operating\":{\"current\":53044297258,\"prior\":-7765575368,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"530์–ต\"},\"investing\":{\"current\":-107771450399,\"prior\":-9343034472,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-1,078์–ต\"},\"financing\":{\"current\":43258945323,\"prior\":1736653090,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"433์–ต\"},\"capex\":{\"current\":2718373772,\"prior\":1219312216,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"27์–ต\"}},\"ratios\":{\"operatingMarginPct\":24.7,\"netMarginPct\":59,\"roePct\":22.1,\"debtToEquityPct\":49.2,\"revenueYoYPct\":6.6,\"operatingProfitYoYPct\":360.9,\"netIncomeYoYPct\":1794.2,\"freeCashFlow\":50325923486,\"freeCashFlowDisplay\":\"503์–ต\"}},{\"bsns_year\":2023,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":96522928887,\"prior\":28806344498,\"label\":\"์ˆ˜์ต(๋งค์ถœ์•ก)\",\"display\":\"965์–ต\"},\"operatingProfit\":{\"current\":-9736838487,\"prior\":-29373680278,\"label\":\"์˜์—…์ด์ต(์†์‹ค)\",\"display\":\"-97์–ต\"},\"netIncome\":{\"current\":-3582208686,\"prior\":-10077904116,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"-36์–ต\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":-65,\"prior\":-159,\"label\":\"๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต(์†์‹ค)\",\"display\":\"-65์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":256099994583,\"prior\":245490136097,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"2,561์–ต\"},\"totalLiabilities\":{\"current\":108258260247,\"prior\":100108123094,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"1,083์–ต\"},\"totalEquity\":{\"current\":147841734336,\"prior\":145382013003,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"1,478์–ต\"},\"cashAndEquivalents\":{\"current\":30199528819,\"prior\":45828916763,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"302์–ต\"}},\"cashFlow\":{\"operating\":{\"current\":-7765575368,\"prior\":-17874625308,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-78์–ต\"},\"investing\":{\"current\":-9343034472,\"prior\":50213127538,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-93์–ต\"},\"financing\":{\"current\":1736653090,\"prior\":2918943647,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"17์–ต\"},\"capex\":{\"current\":1219312216,\"prior\":6423379588,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"12์–ต\"}},\"ratios\":{\"operatingMarginPct\":-10.1,\"netMarginPct\":-3.7,\"roePct\":-2.4,\"debtToEquityPct\":73.2,\"revenueYoYPct\":235.1,\"operatingProfitYoYPct\":66.9,\"netIncomeYoYPct\":64.5,\"freeCashFlow\":-8984887584,\"freeCashFlowDisplay\":\"-90์–ต\"}},{\"bsns_year\":2022,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":28806344498,\"prior\":38745944027,\"label\":\"์ˆ˜์ต(๋งค์ถœ์•ก)\",\"display\":\"288์–ต\"},\"operatingProfit\":{\"current\":-29373680278,\"prior\":-15192014489,\"label\":\"์˜์—…์ด์ต(์†์‹ค)\",\"display\":\"-294์–ต\"},\"netIncome\":{\"current\":-10077904116,\"prior\":-11587524002,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"-101์–ต\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":-159,\"prior\":-124,\"label\":\"๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต(์†์‹ค)\",\"display\":\"-159์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":245490136097,\"prior\":258318546360,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"2,455์–ต\"},\"totalLiabilities\":{\"current\":100108123094,\"prior\":126791463008,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"1,001์–ต\"},\"totalEquity\":{\"current\":145382013003,\"prior\":131527083352,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"1,454์–ต\"},\"cashAndEquivalents\":{\"current\":45828916763,\"prior\":10539625956,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"458์–ต\"}},\"cashFlow\":{\"operating\":{\"current\":-17874625308,\"prior\":-9460530126,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-179์–ต\"},\"investing\":{\"current\":50213127538,\"prior\":-125919778791,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"502์–ต\"},\"financing\":{\"current\":2918943647,\"prior\":107003154810,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"29์–ต\"},\"capex\":{\"current\":6423379588,\"prior\":13585166384,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"64์–ต\"}},\"ratios\":{\"operatingMarginPct\":-102,\"netMarginPct\":-35,\"roePct\":-6.9,\"debtToEquityPct\":68.9,\"revenueYoYPct\":-25.7,\"operatingProfitYoYPct\":-93.3,\"netIncomeYoYPct\":13,\"freeCashFlow\":-24298004896,\"freeCashFlowDisplay\":\"-243์–ต\"}},{\"bsns_year\":2021,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":41123115244,\"prior\":42425842457,\"label\":\"์ˆ˜์ต(๋งค์ถœ์•ก)\",\"display\":\"411์–ต\"},\"operatingProfit\":{\"current\":-12795830212,\"prior\":-58168688,\"label\":\"์˜์—…์ด์ต(์†์‹ค)\",\"display\":\"-128์–ต\"},\"netIncome\":{\"current\":-9191339725,\"prior\":-18274586780,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"-92์–ต\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":-92,\"prior\":-377,\"label\":\"๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต(์†์‹ค)\",\"display\":\"-92์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":260337559420,\"prior\":159206928125,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"2,603์–ต\"},\"totalLiabilities\":{\"current\":126414291791,\"prior\":98625173163,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"1,264์–ต\"},\"totalEquity\":{\"current\":133923267629,\"prior\":60581754962,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"1,339์–ต\"},\"cashAndEquivalents\":{\"current\":10539625956,\"prior\":38922455345,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"105์–ต\"}},\"cashFlow\":{\"operating\":{\"current\":-9419517066,\"prior\":1512244562,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-94์–ต\"},\"investing\":{\"current\":-125960791851,\"prior\":-42916978884,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-1,260์–ต\"},\"financing\":{\"current\":107003154810,\"prior\":76085158900,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"1,070์–ต\"},\"capex\":{\"current\":13585166384,\"prior\":5121455610,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"136์–ต\"}},\"ratios\":{\"operatingMarginPct\":-31.1,\"netMarginPct\":-22.4,\"roePct\":-6.9,\"debtToEquityPct\":94.4,\"revenueYoYPct\":-3.1,\"operatingProfitYoYPct\":-21897.8,\"netIncomeYoYPct\":49.7,\"freeCashFlow\":-23004683450,\"freeCashFlowDisplay\":\"-230์–ต\"}}],\"rawLineItemsFile\":\".dexter/tool-results/kr-financials-00989619-11011-CFS-2021_2025.json\",\"note\":\"periods[].summary holds normalized key metrics (KRW). Use read_file on rawLineItemsFile only for accounts not in the summary.\"}},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2025&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2024&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2023&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2022&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2021&reprt_code=11011&fs_div=CFS\"]}" + "result": "{\"data\":{\"get_business_report_kr_196170\":{\"ticker\":\"196170\",\"corp_code\":\"00989619\",\"corp_name\":\"์•Œํ…Œ์˜ค์  \",\"report_type\":\"quarterly_1\",\"fs_div\":\"OFS\",\"periods\":[{\"bsns_year\":2026,\"report_type\":\"quarterly_1\",\"fs_div\":\"OFS\",\"unit\":\"KRW\",\"basis\":\"1๋ถ„๊ธฐ ๋ˆ„์ (thstrm=๋‹น๊ธฐ, frmtrm=์ „๋…„ ๋™๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„์€ YTD; BS๋Š” ๋ถ„๊ธฐ๋ง vs ์ „๋…„๋ง.\",\"incomeStatement\":{\"revenue\":{\"current\":67939501903,\"prior\":80411676989,\"label\":\"์ˆ˜์ต(๋งค์ถœ์•ก)\",\"display\":\"679์–ต\"},\"operatingProfit\":{\"current\":41029532343,\"prior\":62445295259,\"label\":\"์˜์—…์ด์ต(์†์‹ค)\",\"display\":\"410์–ต\"},\"netIncome\":{\"current\":72973778919,\"prior\":84587820693,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"730์–ต\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":1364,\"prior\":1587,\"label\":\"๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต(์†์‹ค)\",\"display\":\"1,364์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":661886122806,\"prior\":630454581496,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"6,619์–ต\"},\"totalLiabilities\":{\"current\":154840689902,\"prior\":183272113966,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"1,548์–ต\"},\"totalEquity\":{\"current\":507045432904,\"prior\":447182467530,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"5,070์–ต\"},\"cashAndEquivalents\":{\"current\":37934372514,\"prior\":62663251064,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"379์–ต\"}},\"cashFlow\":{\"operating\":{\"current\":21281338222,\"prior\":-3356615582,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"213์–ต\"},\"investing\":{\"current\":-46453691365,\"prior\":-123596146281,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-465์–ต\"},\"financing\":{\"current\":-61211440,\"prior\":154828895064,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-61,211,440\"},\"capex\":{\"current\":13840032932,\"prior\":853502242,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"138์–ต\"}},\"ratios\":{\"operatingMarginPct\":60.4,\"netMarginPct\":107.4,\"roePct\":null,\"debtToEquityPct\":30.5,\"revenueYoYPct\":-15.5,\"operatingProfitYoYPct\":-34.3,\"netIncomeYoYPct\":-13.7,\"freeCashFlow\":7441305290,\"freeCashFlowDisplay\":\"74์–ต\"}}],\"rawLineItemsFile\":\".dexter/tool-results/kr-financials-00989619-11013-OFS-2026_2026.json\",\"note\":\"periods[].summary holds normalized key metrics (KRW). Use read_file on rawLineItemsFile only for accounts not in the summary.\"}},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2025&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2024&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2023&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2022&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2021&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2025&reprt_code=11011&fs_div=OFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2024&reprt_code=11011&fs_div=OFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2023&reprt_code=11011&fs_div=OFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2022&reprt_code=11011&fs_div=OFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2021&reprt_code=11011&fs_div=OFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2026&reprt_code=11013&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00989619&bsns_year=2026&reprt_code=11013&fs_div=OFS\"]}" } ] } diff --git a/scripts/kr-eval/fixtures/default/filings-hyundai.json b/scripts/kr-eval/fixtures/default/filings-hyundai.json index 8486cb9b5..4c8b807b7 100644 --- a/scripts/kr-eval/fixtures/default/filings-hyundai.json +++ b/scripts/kr-eval/fixtures/default/filings-hyundai.json @@ -9,11 +9,9 @@ "tool": "get_filings_kr", "args": { "ticker": "005380", - "limit": 20, - "start_date": "2025-05-31", - "end_date": "2026-05-31" + "limit": 20 }, - "result": "{\"data\":{\"ticker\":\"005380\",\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"filings\":[{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ ๊ณ„ํš(์ž์œจ๊ณต์‹œ) (2025๋…„ ์ดํ–‰ํ˜„ํ™ฉ)\",\"rcept_no\":\"20260529802007\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260529\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"ํŠน์ˆ˜๊ด€๊ณ„์ธ์œผ๋กœ๋ถ€ํ„ฐ๊ธฐํƒ€์œ ๊ฐ€์ฆ๊ถŒ๋งค์ˆ˜\",\"rcept_no\":\"20260529002065\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260529\",\"rm\":\"๊ณต\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๋Œ€๊ทœ๋ชจ๊ธฐ์—…์ง‘๋‹จํ˜„ํ™ฉ๊ณต์‹œ[์—ฐ1ํšŒ๊ณต์‹œ๋ฐ1/4๋ถ„๊ธฐ์šฉ(๋Œ€ํ‘œํšŒ์‚ฌ)]\",\"rcept_no\":\"20260529001880\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260529\",\"rm\":\"๊ณต\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๋Œ€๊ทœ๋ชจ๊ธฐ์—…์ง‘๋‹จํ˜„ํ™ฉ๊ณต์‹œ[์—ฐ1ํšŒ(๋™์ผ์ธ์šฉ)]\",\"rcept_no\":\"20260529001416\",\"flr_nm\":\"์ •์˜์„ \",\"rcept_dt\":\"20260529\",\"rm\":\"๊ณต\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"[๊ธฐ์žฌ์ •์ •]ํƒ€๋ฒ•์ธ์ฃผ์‹๋ฐ์ถœ์ž์ฆ๊ถŒ์ทจ๋“๊ฒฐ์ •(์ž์œจ๊ณต์‹œ) \",\"rcept_no\":\"20260521800573\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260521\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๊ธฐ์—…์„ค๋ช…ํšŒ(IR)๊ฐœ์ตœ(์•ˆ๋‚ด๊ณต์‹œ) \",\"rcept_no\":\"20260520800521\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260520\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๊ธฐ์—…์„ค๋ช…ํšŒ(IR)๊ฐœ์ตœ(์•ˆ๋‚ด๊ณต์‹œ) \",\"rcept_no\":\"20260518800599\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260518\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๋ถ„๊ธฐ๋ณด๊ณ ์„œ (2026.03)\",\"rcept_no\":\"20260515002418\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260515\",\"rm\":\"\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"ํŠน์ˆ˜๊ด€๊ณ„์ธ์œผ๋กœ๋ถ€ํ„ฐ๊ธฐํƒ€์œ ๊ฐ€์ฆ๊ถŒ๋งค์ˆ˜\",\"rcept_no\":\"20260511000643\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260511\",\"rm\":\"๊ณต\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260511000484\",\"flr_nm\":\"๊น€์˜ํ›ˆ\",\"rcept_dt\":\"20260511\",\"rm\":\"\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๊ธฐ์—…์„ค๋ช…ํšŒ(IR)๊ฐœ์ตœ(์•ˆ๋‚ด๊ณต์‹œ) \",\"rcept_no\":\"20260506800556\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260506\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"์˜์—…(์ž ์ •)์‹ค์ (๊ณต์ •๊ณต์‹œ) \",\"rcept_no\":\"20260504800532\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260504\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"ํ’๋ฌธ๋˜๋Š”๋ณด๋„์—๋Œ€ํ•œํ•ด๋ช…(๋ฏธํ™•์ •) \",\"rcept_no\":\"20260430801200\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260430\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260430000968\",\"flr_nm\":\"์ด์„ฑํ˜ธ\",\"rcept_dt\":\"20260430\",\"rm\":\"\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๊ธฐ์—…์„ค๋ช…ํšŒ(IR)๊ฐœ์ตœ(์•ˆ๋‚ด๊ณต์‹œ) \",\"rcept_no\":\"20260429800396\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260429\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"[๊ธฐ์žฌ์ •์ •]์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260428000498\",\"flr_nm\":\"๊น€์ค€์—ฝ\",\"rcept_dt\":\"20260428\",\"rm\":\"\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"[๊ธฐ์žฌ์ •์ •]์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260428000487\",\"flr_nm\":\"๊น€์ค€์—ฝ\",\"rcept_dt\":\"20260428\",\"rm\":\"\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"ํƒ€๋ฒ•์ธ์ฃผ์‹๋ฐ์ถœ์ž์ฆ๊ถŒ์ทจ๋“๊ฒฐ์ •(์ž์œจ๊ณต์‹œ) \",\"rcept_no\":\"20260424800636\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260424\",\"rm\":\"์œ ์ •\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๊ณต์ •๊ฑฐ๋ž˜์ž์œจ์ค€์ˆ˜ํ”„๋กœ๊ทธ๋žจ์šด์˜ํ˜„ํ™ฉ(์•ˆ๋‚ด๊ณต์‹œ) \",\"rcept_no\":\"20260423800640\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260423\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"์•ฝ๊ด€์—์˜ํ•œ๊ธˆ์œต๊ฑฐ๋ž˜์‹œ๊ณ„์—ด๊ธˆ์œตํšŒ์‚ฌ์˜๊ฑฐ๋ž˜์ƒ๋Œ€๋ฐฉ์˜๊ณต์‹œ\",\"rcept_no\":\"20260423000562\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260423\",\"rm\":\"๊ณต\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/list.json?corp_code=00164742&bgn_de=20250531&end_de=20260531&page_count=20&page_no=1&sort=date&sort_mth=desc\"]}" + "result": "{\"data\":{\"ticker\":\"005380\",\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"filings\":[{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"[๊ธฐ์žฌ์ •์ •]๋Œ€๊ทœ๋ชจ๊ธฐ์—…์ง‘๋‹จํ˜„ํ™ฉ๊ณต์‹œ[์—ฐ1ํšŒ๊ณต์‹œ๋ฐ1/4๋ถ„๊ธฐ์šฉ(๋Œ€ํ‘œํšŒ์‚ฌ)]\",\"rcept_no\":\"20260601001973\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260601\",\"rm\":\"๊ณต\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๊ธฐ์—…์ง€๋ฐฐ๊ตฌ์กฐ๋ณด๊ณ ์„œ๊ณต์‹œ \",\"rcept_no\":\"20260601800966\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260601\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"์˜์—…(์ž ์ •)์‹ค์ (๊ณต์ •๊ณต์‹œ) \",\"rcept_no\":\"20260601801279\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260601\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ ๊ณ„ํš(์ž์œจ๊ณต์‹œ) (2025๋…„ ์ดํ–‰ํ˜„ํ™ฉ)\",\"rcept_no\":\"20260529802007\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260529\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"ํŠน์ˆ˜๊ด€๊ณ„์ธ์œผ๋กœ๋ถ€ํ„ฐ๊ธฐํƒ€์œ ๊ฐ€์ฆ๊ถŒ๋งค์ˆ˜\",\"rcept_no\":\"20260529002065\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260529\",\"rm\":\"๊ณต\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๋Œ€๊ทœ๋ชจ๊ธฐ์—…์ง‘๋‹จํ˜„ํ™ฉ๊ณต์‹œ[์—ฐ1ํšŒ๊ณต์‹œ๋ฐ1/4๋ถ„๊ธฐ์šฉ(๋Œ€ํ‘œํšŒ์‚ฌ)]\",\"rcept_no\":\"20260529001880\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260529\",\"rm\":\"๊ณต์ •\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๋Œ€๊ทœ๋ชจ๊ธฐ์—…์ง‘๋‹จํ˜„ํ™ฉ๊ณต์‹œ[์—ฐ1ํšŒ(๋™์ผ์ธ์šฉ)]\",\"rcept_no\":\"20260529001416\",\"flr_nm\":\"์ •์˜์„ \",\"rcept_dt\":\"20260529\",\"rm\":\"๊ณต\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"[๊ธฐ์žฌ์ •์ •]ํƒ€๋ฒ•์ธ์ฃผ์‹๋ฐ์ถœ์ž์ฆ๊ถŒ์ทจ๋“๊ฒฐ์ •(์ž์œจ๊ณต์‹œ) \",\"rcept_no\":\"20260521800573\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260521\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๊ธฐ์—…์„ค๋ช…ํšŒ(IR)๊ฐœ์ตœ(์•ˆ๋‚ด๊ณต์‹œ) \",\"rcept_no\":\"20260520800521\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260520\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๊ธฐ์—…์„ค๋ช…ํšŒ(IR)๊ฐœ์ตœ(์•ˆ๋‚ด๊ณต์‹œ) \",\"rcept_no\":\"20260518800599\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260518\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๋ถ„๊ธฐ๋ณด๊ณ ์„œ (2026.03)\",\"rcept_no\":\"20260515002418\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260515\",\"rm\":\"\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"ํŠน์ˆ˜๊ด€๊ณ„์ธ์œผ๋กœ๋ถ€ํ„ฐ๊ธฐํƒ€์œ ๊ฐ€์ฆ๊ถŒ๋งค์ˆ˜\",\"rcept_no\":\"20260511000643\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260511\",\"rm\":\"๊ณต\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260511000484\",\"flr_nm\":\"๊น€์˜ํ›ˆ\",\"rcept_dt\":\"20260511\",\"rm\":\"\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๊ธฐ์—…์„ค๋ช…ํšŒ(IR)๊ฐœ์ตœ(์•ˆ๋‚ด๊ณต์‹œ) \",\"rcept_no\":\"20260506800556\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260506\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"์˜์—…(์ž ์ •)์‹ค์ (๊ณต์ •๊ณต์‹œ) \",\"rcept_no\":\"20260504800532\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260504\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"ํ’๋ฌธ๋˜๋Š”๋ณด๋„์—๋Œ€ํ•œํ•ด๋ช…(๋ฏธํ™•์ •) \",\"rcept_no\":\"20260430801200\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260430\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260430000968\",\"flr_nm\":\"์ด์„ฑํ˜ธ\",\"rcept_dt\":\"20260430\",\"rm\":\"\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"๊ธฐ์—…์„ค๋ช…ํšŒ(IR)๊ฐœ์ตœ(์•ˆ๋‚ด๊ณต์‹œ) \",\"rcept_no\":\"20260429800396\",\"flr_nm\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"rcept_dt\":\"20260429\",\"rm\":\"์œ \"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"[๊ธฐ์žฌ์ •์ •]์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260428000498\",\"flr_nm\":\"๊น€์ค€์—ฝ\",\"rcept_dt\":\"20260428\",\"rm\":\"\"},{\"corp_code\":\"00164742\",\"corp_name\":\"ํ˜„๋Œ€์ž๋™์ฐจ\",\"stock_code\":\"005380\",\"corp_cls\":\"Y\",\"report_nm\":\"[๊ธฐ์žฌ์ •์ •]์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260428000487\",\"flr_nm\":\"๊น€์ค€์—ฝ\",\"rcept_dt\":\"20260428\",\"rm\":\"\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/list.json?corp_code=00164742&bgn_de=20250602&end_de=20260602&page_count=20&page_no=1&sort=date&sort_mth=desc\"]}" } ] } diff --git a/scripts/kr-eval/fixtures/default/foreign-flow-hynix.json b/scripts/kr-eval/fixtures/default/foreign-flow-hynix.json index eb9bd56bd..66f944bf5 100644 --- a/scripts/kr-eval/fixtures/default/foreign-flow-hynix.json +++ b/scripts/kr-eval/fixtures/default/foreign-flow-hynix.json @@ -11,7 +11,7 @@ "ticker": "000660", "limit": 30 }, - "result": "{\"data\":{\"ticker\":\"000660\",\"ownership\":[{\"date\":\"2026-05-29\",\"foreignHoldRatio\":51.44,\"foreignNetBuyQty\":-437330,\"orgNetBuyQty\":-42513,\"individualNetBuyQty\":473879,\"closePrice\":2333000,\"tradingVolume\":7066332},{\"date\":\"2026-05-28\",\"foreignHoldRatio\":51.5,\"foreignNetBuyQty\":-784789,\"orgNetBuyQty\":22893,\"individualNetBuyQty\":725316,\"closePrice\":2289000,\"tradingVolume\":6229499},{\"date\":\"2026-05-27\",\"foreignHoldRatio\":51.65,\"foreignNetBuyQty\":-18203,\"orgNetBuyQty\":529897,\"individualNetBuyQty\":-481602,\"closePrice\":2243000,\"tradingVolume\":7545783},{\"date\":\"2026-05-26\",\"foreignHoldRatio\":51.62,\"foreignNetBuyQty\":-108495,\"orgNetBuyQty\":386967,\"individualNetBuyQty\":-271309,\"closePrice\":2052000,\"tradingVolume\":4903591},{\"date\":\"2026-05-22\",\"foreignHoldRatio\":51.61,\"foreignNetBuyQty\":-158042,\"orgNetBuyQty\":152990,\"individualNetBuyQty\":1150,\"closePrice\":1941000,\"tradingVolume\":3135190},{\"date\":\"2026-05-21\",\"foreignHoldRatio\":51.66,\"foreignNetBuyQty\":-419491,\"orgNetBuyQty\":738512,\"individualNetBuyQty\":-337068,\"closePrice\":1940000,\"tradingVolume\":5096690},{\"date\":\"2026-05-20\",\"foreignHoldRatio\":51.76,\"foreignNetBuyQty\":-704213,\"orgNetBuyQty\":240569,\"individualNetBuyQty\":447712,\"closePrice\":1745000,\"tradingVolume\":5535123},{\"date\":\"2026-05-19\",\"foreignHoldRatio\":51.87,\"foreignNetBuyQty\":-1128221,\"orgNetBuyQty\":-122347,\"individualNetBuyQty\":1224285,\"closePrice\":1745000,\"tradingVolume\":4575855},{\"date\":\"2026-05-18\",\"foreignHoldRatio\":52.06,\"foreignNetBuyQty\":-559756,\"orgNetBuyQty\":267189,\"individualNetBuyQty\":292885,\"closePrice\":1840000,\"tradingVolume\":6481608},{\"date\":\"2026-05-15\",\"foreignHoldRatio\":52.14,\"foreignNetBuyQty\":-1391973,\"orgNetBuyQty\":-497490,\"individualNetBuyQty\":1855635,\"closePrice\":1819000,\"tradingVolume\":7485233}]},\"sourceUrls\":[\"https://m.stock.naver.com/api/stock/000660/trend\"]}" + "result": "{\"data\":{\"ticker\":\"000660\",\"ownership\":[{\"date\":\"2026-06-01\",\"foreignHoldRatio\":51.34,\"foreignNetBuyQty\":-729059,\"orgNetBuyQty\":859988,\"individualNetBuyQty\":-125982,\"closePrice\":2363000,\"tradingVolume\":5602897},{\"date\":\"2026-05-29\",\"foreignHoldRatio\":51.44,\"foreignNetBuyQty\":-437330,\"orgNetBuyQty\":-42513,\"individualNetBuyQty\":473879,\"closePrice\":2333000,\"tradingVolume\":8518013},{\"date\":\"2026-05-28\",\"foreignHoldRatio\":51.5,\"foreignNetBuyQty\":-784789,\"orgNetBuyQty\":22893,\"individualNetBuyQty\":725316,\"closePrice\":2289000,\"tradingVolume\":6229499},{\"date\":\"2026-05-27\",\"foreignHoldRatio\":51.65,\"foreignNetBuyQty\":-18203,\"orgNetBuyQty\":529897,\"individualNetBuyQty\":-481602,\"closePrice\":2243000,\"tradingVolume\":7545783},{\"date\":\"2026-05-26\",\"foreignHoldRatio\":51.62,\"foreignNetBuyQty\":-108495,\"orgNetBuyQty\":386967,\"individualNetBuyQty\":-271309,\"closePrice\":2052000,\"tradingVolume\":4903591},{\"date\":\"2026-05-22\",\"foreignHoldRatio\":51.61,\"foreignNetBuyQty\":-158042,\"orgNetBuyQty\":152990,\"individualNetBuyQty\":1150,\"closePrice\":1941000,\"tradingVolume\":3135190},{\"date\":\"2026-05-21\",\"foreignHoldRatio\":51.66,\"foreignNetBuyQty\":-419491,\"orgNetBuyQty\":738512,\"individualNetBuyQty\":-337068,\"closePrice\":1940000,\"tradingVolume\":5096690},{\"date\":\"2026-05-20\",\"foreignHoldRatio\":51.76,\"foreignNetBuyQty\":-704213,\"orgNetBuyQty\":240569,\"individualNetBuyQty\":447712,\"closePrice\":1745000,\"tradingVolume\":5535123},{\"date\":\"2026-05-19\",\"foreignHoldRatio\":51.87,\"foreignNetBuyQty\":-1128221,\"orgNetBuyQty\":-122347,\"individualNetBuyQty\":1224285,\"closePrice\":1745000,\"tradingVolume\":4575855},{\"date\":\"2026-05-18\",\"foreignHoldRatio\":52.06,\"foreignNetBuyQty\":-559756,\"orgNetBuyQty\":267189,\"individualNetBuyQty\":292885,\"closePrice\":1840000,\"tradingVolume\":6481608}]},\"sourceUrls\":[\"https://m.stock.naver.com/api/stock/000660/trend\"]}" } ] } diff --git a/scripts/kr-eval/fixtures/default/institutional-vs-foreign.json b/scripts/kr-eval/fixtures/default/institutional-vs-foreign.json index a0bd3e68f..e2f22f53b 100644 --- a/scripts/kr-eval/fixtures/default/institutional-vs-foreign.json +++ b/scripts/kr-eval/fixtures/default/institutional-vs-foreign.json @@ -11,7 +11,7 @@ "ticker": "005930", "limit": 5 }, - "result": "{\"data\":{\"ticker\":\"005930\",\"ownership\":[{\"date\":\"2026-05-29\",\"foreignHoldRatio\":48.27,\"foreignNetBuyQty\":-1061741,\"orgNetBuyQty\":5314304,\"individualNetBuyQty\":-4237361,\"closePrice\":317000,\"tradingVolume\":32804208},{\"date\":\"2026-05-28\",\"foreignHoldRatio\":48.29,\"foreignNetBuyQty\":-5173125,\"orgNetBuyQty\":-1613562,\"individualNetBuyQty\":6500347,\"closePrice\":299500,\"tradingVolume\":30195334},{\"date\":\"2026-05-27\",\"foreignHoldRatio\":48.39,\"foreignNetBuyQty\":-274173,\"orgNetBuyQty\":1550904,\"individualNetBuyQty\":-1105685,\"closePrice\":307000,\"tradingVolume\":33916688},{\"date\":\"2026-05-26\",\"foreignHoldRatio\":48.37,\"foreignNetBuyQty\":1891734,\"orgNetBuyQty\":1513469,\"individualNetBuyQty\":-3263161,\"closePrice\":299000,\"tradingVolume\":23441371},{\"date\":\"2026-05-22\",\"foreignHoldRatio\":48.32,\"foreignNetBuyQty\":-3419695,\"orgNetBuyQty\":-307631,\"individualNetBuyQty\":3602972,\"closePrice\":292500,\"tradingVolume\":18395274}]},\"sourceUrls\":[\"https://m.stock.naver.com/api/stock/005930/trend\"]}" + "result": "{\"data\":{\"ticker\":\"005930\",\"ownership\":[{\"date\":\"2026-06-01\",\"foreignHoldRatio\":48.3,\"foreignNetBuyQty\":-180865,\"orgNetBuyQty\":3832695,\"individualNetBuyQty\":-3173072,\"closePrice\":349000,\"tradingVolume\":45052488},{\"date\":\"2026-05-29\",\"foreignHoldRatio\":48.29,\"foreignNetBuyQty\":-1061741,\"orgNetBuyQty\":5314304,\"individualNetBuyQty\":-4237361,\"closePrice\":317000,\"tradingVolume\":37241537},{\"date\":\"2026-05-28\",\"foreignHoldRatio\":48.29,\"foreignNetBuyQty\":-5173125,\"orgNetBuyQty\":-1613562,\"individualNetBuyQty\":6500347,\"closePrice\":299500,\"tradingVolume\":30195334},{\"date\":\"2026-05-27\",\"foreignHoldRatio\":48.39,\"foreignNetBuyQty\":-274173,\"orgNetBuyQty\":1550904,\"individualNetBuyQty\":-1105685,\"closePrice\":307000,\"tradingVolume\":33916688},{\"date\":\"2026-05-26\",\"foreignHoldRatio\":48.37,\"foreignNetBuyQty\":1891734,\"orgNetBuyQty\":1513469,\"individualNetBuyQty\":-3263161,\"closePrice\":299000,\"tradingVolume\":23441371}]},\"sourceUrls\":[\"https://m.stock.naver.com/api/stock/005930/trend\"]}" } ] } diff --git a/scripts/kr-eval/fixtures/default/key-risks-samsung.json b/scripts/kr-eval/fixtures/default/key-risks-samsung.json index c364ecbfd..483fc6764 100644 --- a/scripts/kr-eval/fixtures/default/key-risks-samsung.json +++ b/scripts/kr-eval/fixtures/default/key-risks-samsung.json @@ -8,9 +8,9 @@ { "tool": "read_filings_kr", "args": { - "query": "์‚ผ์„ฑ์ „์ž 005930 ์ตœ๊ทผ ์‚ฌ์—…๋ณด๊ณ ์„œ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ ์ฃผ์š” ๋ฆฌ์Šคํฌ: ์‚ฌ์—…์˜ ๋‚ด์šฉ ์œ„ํ—˜๊ด€๋ฆฌ ๋ฐ ํŒŒ์ƒ๊ฑฐ๋ž˜, ํˆฌ์ž์ž ๋ณดํ˜ธ ๊ด€๋ จ ์œ„ํ—˜ ์š”์•ฝ" + "query": "์‚ผ์„ฑ์ „์ž 005930 ์ตœ์‹  ์‚ฌ์—…๋ณด๊ณ ์„œ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ ์ฃผ์š” ๋ฆฌ์Šคํฌ ์œ„ํ—˜๊ด€๋ฆฌ ํˆฌ์ž์ž ๋ณดํ˜ธ ์š”์•ฝ" }, - "result": "{\"data\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_nm\":\"์‚ฌ์—…๋ณด๊ณ ์„œ (2025.12)\",\"rcept_no\":\"20260310002820\",\"sections_found\":[\"risks\"],\"summary\":\"์•„๋ž˜ ์š”์•ฝ์€ ์ œ๊ณต๋œ ์‚ผ์„ฑ์ „์ž 2025.12 ์‚ฌ์—…๋ณด๊ณ ์„œ ๋ฐœ์ทŒ ๋‚ด์šฉ์— ๊ทผ๊ฑฐํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ฐœ์ทŒ์—๋Š” โ€œ์‚ฌ์—…์˜ ๋‚ด์šฉโ€์ƒ ์ œํ’ˆยท์‚ฌ์—…๋ถ€๋ณ„ ์˜์—… ๋ฆฌ์Šคํฌ๋Š” ํฌํ•จ๋˜์–ด ์žˆ์ง€ ์•Š์•„, ํ•ด๋‹น ๋ถ€๋ถ„์€ ํ™•์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ โ€œ์œ„ํ—˜๊ด€๋ฆฌ ๋ฐ ํŒŒ์ƒ๊ฑฐ๋ž˜โ€, โ€œ์šฐ๋ฐœ๋ถ€์ฑ„ยท์ œ์žฌโ€, โ€œ๋…น์ƒ‰๊ฒฝ์˜โ€ ๋“ฑ ํˆฌ์ž์ž ๋ณดํ˜ธ ๊ด€์ ์˜ ์ฃผ์š” ๋ฆฌ์Šคํฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •๋ฆฌ๋ฉ๋‹ˆ๋‹ค.\\n\\n## 1) ์žฌ๋ฌด์œ„ํ—˜: ํ™˜์œจยท๊ธˆ๋ฆฌยท์ฃผ๊ฐ€ยท์‹ ์šฉยท์œ ๋™์„ฑ ๋ฆฌ์Šคํฌ\\n\\n์‚ผ์„ฑ์ „์ž๋Š” ๊ธ€๋กœ๋ฒŒ ์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•ด **์‹œ์žฅ์œ„ํ—˜, ์‹ ์šฉ์œ„ํ—˜, ์œ ๋™์„ฑ์œ„ํ—˜**์— ๋…ธ์ถœ๋˜์–ด ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ๊ธ€๋กœ๋ฒŒ ์žฌ๋ฌด๊ด€๋ฆฌ๊ธฐ์ค€, ๊ณ ๊ฐยท๊ฑฐ๋ž˜์„  ์žฌ๋ฌด์œ„ํ—˜ ์ธก์ •, ํ™˜ํ—ท์ง€, ์ž๊ธˆ์ˆ˜์ง€ ์ ๊ฒ€ ๋“ฑ์„ ํ†ตํ•ด ๊ด€๋ฆฌํ•œ๋‹ค๊ณ  ๊ณต์‹œํ–ˆ์Šต๋‹ˆ๋‹ค.\\n\\n### ํ™˜์œจ๋ณ€๋™์œ„ํ—˜\\n- ๊ธ€๋กœ๋ฒŒ ๊ฑฐ๋ž˜๋กœ ์ธํ•ด ๊ธฐ๋Šฅํ†ตํ™”์™€ ๋‹ค๋ฅธ ํ†ตํ™” ๊ฑฐ๋ž˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉฐ, ์ฃผ์š” ๋…ธ์ถœ ํ†ตํ™”๋Š” **USD, EUR ๋“ฑ**์ž…๋‹ˆ๋‹ค.\\n- ํšŒ์‚ฌ๋Š” ํ†ตํ™”๋ณ„ ์ž์‚ฐยท๋ถ€์ฑ„ ๊ทœ๋ชจ๋ฅผ ์ผ์น˜์‹œํ‚ค๊ณ , ์ˆ˜์ถœ์ž…ยท์˜ˆ๊ธˆยท์ฐจ์ž… ๊ฑฐ๋ž˜์—์„œ ์ž…๊ธˆยท์ง€์ถœ ํ†ตํ™”๋ฅผ ๋งž์ถฐ ํ™˜ํฌ์ง€์…˜ ๋ฐœ์ƒ์„ ์–ต์ œํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n- ์ผ๋ถ€ ๋ฐœ์ƒํ•œ ํ™˜ํฌ์ง€์…˜์€ **์ฑ„๊ถŒ๋งค๊ฐ, ์„ ๋ฌผํ™˜** ๋“ฑ์„ ํ™œ์šฉํ•ด ์ถ•์†Œํ•ฉ๋‹ˆ๋‹ค.\\n- ๋‹ค๋งŒ ํšŒ์‚ฌ๋„ โ€œํ™˜์œจ๋ณ€๋™์œ„ํ—˜์„ ๊ฐ์†Œ์‹œํ‚ค์ง€๋งŒ ์™„์ „ํžˆ ์ œ๊ฑฐํ•˜์ง€๋Š” ์•Š์„ ์ˆ˜ ์žˆ๋‹คโ€๊ณ  ๋ช…์‹œํ•˜๊ณ  ์žˆ์–ด, ํ™˜์œจ ๊ธ‰๋ณ€ ์‹œ ์†์ต ๋ณ€๋™ ๊ฐ€๋Šฅ์„ฑ์€ ๋‚จ์•„ ์žˆ์Šต๋‹ˆ๋‹ค.\\n- ํˆฌ๊ธฐ์  ์™ธํ™˜๊ฑฐ๋ž˜๋Š” ์—„๊ฒฉํžˆ ๊ธˆ์ง€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n### ์ด์ž์œจ๋ณ€๋™์œ„ํ—˜\\n- ์˜ˆ๊ธˆ ๋ฐ ๋ณ€๋™๊ธˆ๋ฆฌ๋ถ€ ์ฐจ์ž…๊ธˆ์—์„œ ์ด์ž์ˆ˜์ตยท๋น„์šฉ ๋ณ€๋™ ์œ„ํ—˜์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.\\n- ์‚ผ์„ฑ์ „์ž๋Š” ๋‚ด๋ถ€์ž๊ธˆ ๊ณต์œ ๋ฅผ ํ†ตํ•ด ์™ธ๋ถ€์ฐจ์ž…๊ธˆ์„ ์ตœ์†Œํ™”ํ•จ์œผ๋กœ์จ ๊ธˆ์œต๋น„์šฉ๊ณผ ๋ถˆํ™•์‹ค์„ฑ์„ ์ค„์ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n### ์ฃผ๊ฐ€๋ณ€๋™์œ„ํ—˜\\n- ์ „๋žต์  ๋ชฉ์  ๋“ฑ์œผ๋กœ ์ง€๋ถ„์ƒํ’ˆ์— ํˆฌ์žํ•˜๊ณ  ์žˆ์–ด ์ƒ์žฅ์ฃผ์‹ ๊ฐ€๊ฒฉ ๋ณ€๋™์— ๋…ธ์ถœ๋ฉ๋‹ˆ๋‹ค.\\n- 2025๋…„ ๋ง ๊ธฐ์ค€ ์ง€๋ถ„์ƒํ’ˆ ์ƒ์žฅ์ฃผ์‹ ๊ฐ€๊ฒฉ์ด 1% ๋ณ€๋™ํ•  ๊ฒฝ์šฐ:\\n - ๊ธฐํƒ€ํฌ๊ด„์†์ต ์˜ํ–ฅ: **132,067๋ฐฑ๋งŒ์›**\\n - ๋‹น๊ธฐ์†์ต ์˜ํ–ฅ: **1,460๋ฐฑ๋งŒ์›**\\n- ์ „๊ธฐ ๋Œ€๋น„ ์˜ํ–ฅ ๊ทœ๋ชจ๊ฐ€ ์ปค์ง„ ์ ์€, ๋ณด์œ  ์ง€๋ถ„์ƒํ’ˆ์˜ ๊ณต์ •๊ฐ€์น˜ ๋ณ€๋™์ด ์ž๋ณธ ๋ฐ ์†์ต์— ๋ฏธ์น˜๋Š” ๋ฏผ๊ฐ๋„๊ฐ€ ํ™•๋Œ€๋˜์—ˆ์Œ์„ ์‹œ์‚ฌํ•ฉ๋‹ˆ๋‹ค.\\n\\n### ์‹ ์šฉ์œ„ํ—˜\\n- ๊ณ ๊ฐ ๋˜๋Š” ๊ฑฐ๋ž˜์ƒ๋Œ€๋ฐฉ์ด ๊ณ„์•ฝ์ƒ ์˜๋ฌด๋ฅผ ์ดํ–‰ํ•˜์ง€ ๋ชปํ•  ์œ„ํ—˜์ž…๋‹ˆ๋‹ค.\\n- ์‚ผ์„ฑ์ „์ž๋Š” ๊ณ ๊ฐยท๊ฑฐ๋ž˜์ƒ๋Œ€๋ฐฉ์˜ ์žฌ๋ฌด์ƒํƒœ์™€ ๊ณผ๊ฑฐ ๊ฒฝํ—˜ ๋“ฑ์„ ๋ฐ˜์˜ํ•ด ์‹ ์šฉ๋„๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ํ‰๊ฐ€ํ•˜๊ณ , ์‹ ์šฉํ•œ๋„๋ฅผ ์„ค์ •ยท๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.\\n- ์œ„ํ—˜๊ตญ๊ฐ€ ์†Œ์žฌ ๊ฑฐ๋ž˜์ฒ˜์˜ ๋งค์ถœ์ฑ„๊ถŒ์€ ๋ณดํ—˜ํ•œ๋„ ๋‚ด์—์„œ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.\\n- ๊ธˆ์œต๊ธฐ๊ด€ ๊ฑฐ๋ž˜๋Š” ์›์น™์ ์œผ๋กœ **๊ตญ์ œ ์‹ ์šฉ๋“ฑ๊ธ‰ S&P A ์ด์ƒ ์€ํ–‰**๊ณผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\\n\\n### ์œ ๋™์„ฑ์œ„ํ—˜\\n- ๊ธˆ์œต๋ถ€์ฑ„ ๊ด€๋ จ ์˜๋ฌด ์ดํ–‰์— ์–ด๋ ค์›€์„ ๊ฒช์„ ์œ„ํ—˜์ž…๋‹ˆ๋‹ค.\\n- ์ฃผ์š” ์œ ๋™์„ฑ ์ˆ˜์š”๋Š” **์ƒ์‚ฐ, ์—ฐ๊ตฌ๊ฐœ๋ฐœ ํˆฌ์ž, ์šด์ „์ž๋ณธ, ๋ฐฐ๋‹น** ๋“ฑ์ž…๋‹ˆ๋‹ค.\\n- ๋Œ€๊ทœ๋ชจ ํˆฌ์ž๊ฐ€ ๋งŽ์€ ์‚ฌ์—… ํŠน์„ฑ์ƒ ์ ์ • ์œ ๋™์„ฑ ์œ ์ง€๊ฐ€ ์ค‘์š”ํ•˜๋‹ค๊ณ  ๊ณต์‹œํ–ˆ์Šต๋‹ˆ๋‹ค.\\n- ๊ถŒ์—ญ๋ณ„ **Cash Pooling**์„ ํ†ตํ•ด ์ž๊ธˆ ๋ถ€์กฑ ๋ฒ•์ธ๊ณผ ์ž‰์—ฌ ๋ฒ•์ธ ๊ฐ„ ์ž๊ธˆ์„ ๊ณต์œ ํ•˜๊ณ , ๊ธˆ์œต๋น„์šฉ ์ ˆ๊ฐ ๋ฐ ์œ ๋™์„ฑ์œ„ํ—˜ ์™„ํ™”๋ฅผ ์ถ”์ง„ํ•ฉ๋‹ˆ๋‹ค.\\n- 2025๋…„ ๋ง ํ˜„์žฌ ๊ตญ์ œ์‹ ์šฉํ‰๊ฐ€๊ธฐ๊ด€์œผ๋กœ๋ถ€ํ„ฐ **Moodyโ€™s Aa2, S&P AA-** ๋“ฑ ํˆฌ์ž์ ๊ฒฉ๋“ฑ๊ธ‰์„ ๋ถ€์—ฌ๋ฐ›์•„ ํ•„์š” ์‹œ ์ž๋ณธ์‹œ์žฅ์„ ํ†ตํ•œ ์ž๊ธˆ์กฐ๋‹ฌ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๊ณ  ์„ค๋ช…ํ–ˆ์Šต๋‹ˆ๋‹ค.\\n\\n## 2) ํŒŒ์ƒ์ƒํ’ˆ ๋ฐ ์˜ต์…˜ ๊ด€๋ จ ๋ฆฌ์Šคํฌ\\n\\n### ํ†ตํ™”์„ ๋„ ๊ฑฐ๋ž˜\\n- ์‚ผ์„ฑ์ „์ž๋Š” ํ™˜์œจ๋ณ€๋™ ์œ„ํ—˜๊ด€๋ฆฌ ๋ชฉ์ ์ƒ ์žฅ๋ถ€ํ†ตํ™”๊ฐ€ ์•„๋‹Œ ์™ธํ™” ํฌ์ง€์…˜์— ๋Œ€ํ•ด **ํ†ตํ™”์„ ๋„(Currency Forward)** ์ƒํ’ˆ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.\\n- 2025๋…„ ๋ง ํ˜„์žฌ **USD, EUR, JPY ๋“ฑ ์ด 33๊ฐœ ํ†ตํ™”**์— ๋Œ€ํ•ด **3,360๊ฑด**์˜ ํ†ตํ™”์„ ๋„ ๊ฑฐ๋ž˜๋ฅผ ์ฒด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.\\n- ์ด๋Š” ํ™˜์œ„ํ—˜ ๊ด€๋ฆฌ ๋ชฉ์ ์ด์ง€๋งŒ, ํŒŒ์ƒ์ƒํ’ˆ ํŠน์„ฑ์ƒ ํ™˜์œจ ๋ณ€๋™ ๋ฐ ๊ฑฐ๋ž˜์ƒ๋Œ€๋ฐฉ ๊ด€๋ จ ์œ„ํ—˜์ด ์™„์ „ํžˆ ์ œ๊ฑฐ๋˜๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค.\\n\\n### ๋ ˆ์ธ๋ณด์šฐ๋กœ๋ณดํ‹ฑ์Šค ์ฝœ์˜ต์…˜\\n- ์‚ผ์„ฑ์ „์ž๋Š” ๋ ˆ์ธ๋ณด์šฐ๋กœ๋ณดํ‹ฑ์Šคใˆœ ๋ฐ ์ตœ๋Œ€์ฃผ์ฃผ ๋“ฑ๊ณผ์˜ ์ฃผ์ฃผ๊ฐ„ ๊ณ„์•ฝ์— ๋”ฐ๋ผ ์ผ์ • ๊ธฐ๊ฐ„ ์ตœ๋Œ€์ฃผ์ฃผ ๋“ฑ์ด ๋ณด์œ ํ•œ ์ฃผ์‹์„ ๋งค๋„ํ•˜๋„๋ก ์ฒญ๊ตฌํ•  ๊ถŒ๋ฆฌ๋ฅผ ๋ณด์œ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n- 2024๋…„ 12์›” 31์ผ ์ด์‚ฌํšŒ์—์„œ ๋ ˆ์ธ๋ณด์šฐ๋กœ๋ณดํ‹ฑ์Šค ์ตœ๋Œ€์ฃผ์ฃผ ๋“ฑ 6์ธ์ด ๋ณด์œ ํ•œ **394๋งŒ์ฃผ** ์ธ์ˆ˜ ์ฝœ์˜ต์…˜ ํ–‰์‚ฌ๋ฅผ ์˜๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.\\n- ์ธ์ˆ˜๋Œ€๊ฐ€๋Š” **267,463๋ฐฑ๋งŒ์›**์ด๋ฉฐ, 2025๋…„ 3์›” 12์ผ ์ง€๋ถ„์ธ์ˆ˜ ์ ˆ์ฐจ๋ฅผ ์™„๋ฃŒํ–ˆ์Šต๋‹ˆ๋‹ค.\\n- 2025๋…„ ๋ง ํ˜„์žฌ ๋ณด์œ  ์ฝœ์˜ต์…˜์˜ ํ–‰์‚ฌ๊ฐ€์•ก์€ ์‹œ๊ฐ€๋กœ, ๊ณต์ •๊ฐ€์น˜๋ฅผ ๋ณ„๋„๋กœ ํ‰๊ฐ€ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.\\n\\n### ์‚ผ์„ฑ๋””์Šคํ”Œ๋ ˆ์ด์˜ ํ’‹์˜ต์…˜ ๋ฐ ์ฃผ์‹์„ ๋„๊ณ„์•ฝ\\n- ์‚ผ์„ฑ๋””์Šคํ”Œ๋ ˆ์ดใˆœ๋Š” Corning ์ง€๋ถ„์ฆ๊ถŒ ์ผ๋ถ€๋ฅผ Corning์— ๋งค๊ฐํ•  ์ˆ˜ ์žˆ๋Š” ํ’‹์˜ต์…˜์„ ๋ณด์œ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n- ๋˜ํ•œ CSOT๊ฐ€ ์ƒ์žฅ๊ธฐํ•œ ๋‚ด ๋น„์ƒ์žฅ ์‹œ, ๋ณด์œ  CSOT ์ง€๋ถ„์ฆ๊ถŒ ์ „๋ถ€ ๋˜๋Š” ์ผ๋ถ€๋ฅผ TCL์— ๋งค๊ฐํ•  ์ˆ˜ ์žˆ๋Š” ํ’‹์˜ต์…˜์„ ๋ณด์œ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n- ํ•ด๋‹น ํ’‹์˜ต์…˜๋“ค์˜ ๊ณต์ •๊ฐ€์น˜๋Š” 2025๋…„ ๋ง ํ˜„์žฌ ํ•œ์˜ํšŒ๊ณ„๋ฒ•์ธ์ด ํ‰๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.\\n- ์‚ผ์„ฑ๋””์Šคํ”Œ๋ ˆ์ด๋Š” 2025๋…„ 12์›” ๋ณต์ˆ˜์˜ ๊ธˆ์œต๊ธฐ๊ด€๊ณผ ์‚ผ์„ฑ์ „์ž ๋ณดํ†ต์ฃผ๋ฅผ ๊ธฐ์ดˆ์ž์‚ฐ์œผ๋กœ ํ•˜๋Š” **์ฐจ์•ก๊ฒฐ์ œํ˜• ์ฃผ์‹์„ ๋„๊ณ„์•ฝ**๋„ ์ฒด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.\\n\\n## 3) ์†Œ์†กยท๋ถ„์Ÿยท์กฐ์‚ฌ ๊ด€๋ จ ์šฐ๋ฐœ ๋ฆฌ์Šคํฌ\\n\\n- ์‚ผ์„ฑ์ „์ž๋Š” ์ •์ƒ์ ์ธ ์˜์—…๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•œ ๋‹ค์ˆ˜์˜ ์†Œ์†ก, ๋ถ„์Ÿ, ๊ทœ์ œ๊ธฐ๊ด€ ์กฐ์‚ฌ๋ฅผ ์ง„ํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค.\\n- ์ž์›์˜ ์œ ์ถœ๊ธˆ์•ก๊ณผ ์‹œ๊ธฐ๋Š” ๋ถˆํ™•์‹คํ•ฉ๋‹ˆ๋‹ค.\\n- ๊ฒฝ์˜์ง„์€ ์ด๋Ÿฌํ•œ ์†Œ์†ก ๋“ฑ์˜ ๊ฒฐ๊ณผ๊ฐ€ ํšŒ์‚ฌ ์žฌ๋ฌด์ƒํƒœ์— ์ค‘์š”ํ•œ ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š์„ ๊ฒƒ์œผ๋กœ ํŒ๋‹จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n- ๋‹ค๋งŒ ํˆฌ์ž์ž ๊ด€์ ์—์„œ๋Š” ๊ฒฐ๊ณผ์™€ ์‹œ๊ธฐ๊ฐ€ ๋ถˆํ™•์‹คํ•œ ์šฐ๋ฐœ ๋ฆฌ์Šคํฌ๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.\\n- ๋˜ํ•œ ์‚ผ์„ฑ์ „์žใˆœ, ์‚ผ์„ฑ๋””์Šคํ”Œ๋ ˆ์ดใˆœ ๋“ฑ์€ ๋ถ„ํ• ๋œ ์‚ผ์„ฑ๋””์Šคํ”Œ๋ ˆ์ดใˆœ ๋“ฑ์˜ ๋ถ„ํ•  ์ „ ์ฑ„๋ฌด์— ๋Œ€ํ•ด ์—ฐ๋Œ€ํ•˜์—ฌ ๋ณ€์ œํ•  ์ฑ…์ž„์ด ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n## 4) ์ œ์žฌยท์ค€๋ฒ• ๋ฆฌ์Šคํฌ\\n\\n์‚ผ์„ฑ์ „์ž ๋ฐ ์ข…์†ํšŒ์‚ฌ๋Š” ์‚ฐ์—…์•ˆ์ „, ๊ณต์ •๊ฑฐ๋ž˜, ๊ฐœ์ธ์ •๋ณด, ํ™˜๊ฒฝ, ์ˆ˜์ถœํ†ต์ œ, ์†Œ๋น„์ž๋ณดํ˜ธ, ์„ธ๋ฌด, ์ œ์žฌ ๋Œ€์ƒ๊ตญ ๊ฑฐ๋ž˜, ๊ด‘๊ณ , ๋ฐ˜๋…์  ๋“ฑ ๋‹ค์–‘ํ•œ ์˜์—ญ์—์„œ ์ œ์žฌ๋ฅผ ๋ฐ›์€ ๋‚ด์—ญ์ด ๊ณต์‹œ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐœ๋ณ„ ๊ธˆ์•ก์€ ๋Œ€๋ถ€๋ถ„ ํšŒ์‚ฌ ๊ทœ๋ชจ ๋Œ€๋น„ ์ œํ•œ์ ์œผ๋กœ ๋ณด์ด๋‚˜, ๋ฐ˜๋ณต ๋ฐœ์ƒ ์‹œ ๋‚ด๋ถ€ํ†ต์ œยทํ‰ํŒ ๋ฆฌ์Šคํฌ๋กœ ์—ฐ๊ฒฐ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n์ฃผ์š” ์‚ฌ๋ก€๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.\\n\\n### ์‚ฐ์—…์•ˆ์ „ยทํ™˜๊ฒฝยท์œ„ํ—˜๋ฌผ ๊ด€๋ จ\\n- ์‚ผ์„ฑ์ „์ž๋Š” ๊ตฌ ์‚ฐ์—…์•ˆ์ „๋ณด๊ฑด๋ฒ• ๋ฐ ์‚ฐ์—…์•ˆ์ „๋ณด๊ฑด๋ฒ• ์œ„๋ฐ˜์œผ๋กœ ๋ฒŒ๊ธˆ **15๋ฐฑ๋งŒ์›**์„ ํ™•์ •ยท๋‚ฉ๋ถ€ํ–ˆ์Šต๋‹ˆ๋‹ค.\\n- ๊ตฌ๋ฏธ์‚ฌ์—…์žฅ ์œ„ํ—˜๋ฌผ์•ˆ์ „๊ด€๋ฆฌ๋ฒ• ์œ„๋ฐ˜์œผ๋กœ ์กฐ์น˜๋ช…๋ น์„ ๋ฐ›๊ณ  ์œ„ํ—˜๋ฌผ์ทจ๊ธ‰์†Œ ์„ค์น˜ํ—ˆ๊ฐ€์‹ ์ฒญ ๋“ฑ ์กฐ์น˜๋ฅผ ์™„๋ฃŒํ–ˆ์Šต๋‹ˆ๋‹ค.\\n- ๊ด‘์ฃผ์‚ฌ์—…์žฅ์€ ๋Œ€๊ธฐํ™˜๊ฒฝ๋ณด์ „๋ฒ• ์œ„๋ฐ˜์œผ๋กœ ๊ฒฝ๊ณ  ๋ฐ ๊ณผํƒœ๋ฃŒ **2๋ฐฑ๋งŒ์›**์„ ๋ถ€๊ณผ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.\\n- ๊ธฐํฅยทํ™”์„ฑ์‚ฌ์—…์žฅ ๊ด€๋ จ ์‚ฐ์—…์•ˆ์ „๋ณด๊ฑด๋ฒ• ์œ„๋ฐ˜ ๊ณผํƒœ๋ฃŒ, ์›์ž๋ ฅ์•ˆ์ „๋ฒ• ์œ„๋ฐ˜ ๊ณผํƒœ๋ฃŒ ๋“ฑ๋„ ๊ณต์‹œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.\\n- ์‚ผ์„ฑ๋””์Šคํ”Œ๋ ˆ์ด๋Š” ํ™”ํ•™๋ฌผ์งˆ๊ด€๋ฆฌ๋ฒ• ์œ„๋ฐ˜ ์‹œ์ •์กฐ์น˜, ์‚ฐ์—…์•ˆ์ „๋ณด๊ฑด๋ฒ• ์œ„๋ฐ˜ ๋ถ€๋ถ„์ž‘์—…์ค‘์ง€ ๋ช…๋ น ๋“ฑ์„ ๋ฐ›์•˜๊ณ  ์‹œ์ •ํ–ˆ๋‹ค๊ณ  ๊ณต์‹œํ–ˆ์Šต๋‹ˆ๋‹ค.\\n\\n### ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ\\n- ์‚ผ์„ฑ์ „์ž๋Š” ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ๋ฒ• ์œ„๋ฐ˜์œผ๋กœ ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ์œ„์›ํšŒ๋กœ๋ถ€ํ„ฐ ์‹œ์ •์กฐ์น˜, ๊ณผ์ง•๊ธˆ **875.6๋ฐฑ๋งŒ์›**, ๊ณผํƒœ๋ฃŒ **2.4๋ฐฑ๋งŒ์›**์„ ๋ถ€๊ณผ๋ฐ›์•„ ๋‚ฉ๋ถ€ํ–ˆ์Šต๋‹ˆ๋‹ค.\\n- 2025๋…„ 12์›”์—๋„ ๊ฐœ์ธ์ •๋ณด ํŒŒ๊ธฐ ๋ฐ ์ •์ •ยท์‚ญ์ œ ๊ด€๋ จ ์œ„๋ฐ˜์œผ๋กœ ์‹œ์ •์กฐ์น˜์™€ **9.6๋ฐฑ๋งŒ์›**์„ ๋ถ€๊ณผ๋ฐ›์•„ ๋‚ฉ๋ถ€ํ–ˆ์Šต๋‹ˆ๋‹ค.\\n\\n### ๊ณต์ •๊ฑฐ๋ž˜ยทํ•˜๋„๊ธ‰ยท๊ด‘๊ณ ยท์†Œ๋น„์ž๋ณดํ˜ธ\\n- ์‚ผ์„ฑ์ „์ž๋Š” 2024๋…„ ๊ณต์ •๊ฑฐ๋ž˜์œ„์›ํšŒ๋กœ๋ถ€ํ„ฐ ๋Œ€๋ฆฌ์ ๊ฑฐ๋ž˜ ๊ณต์ •ํ™”์— ๊ด€ํ•œ ๋ฒ•๋ฅ  ์œ„๋ฐ˜ ํ˜์˜๋กœ ์‹œ์ •๋ช…๋ น์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.\\n- ์ผ๋ถ€ ์ข…์†ํšŒ์‚ฌ๋Š” ํ•˜๋„๊ธ‰๊ฑฐ๋ž˜ ๊ณต์‹œ ์˜๋ฌด ์œ„๋ฐ˜ ๊ณผํƒœ๋ฃŒ๋ฅผ ๋ถ€๊ณผ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.\\n- ํ•ด์™ธ ์ข…์†ํšŒ์‚ฌ์—์„œ๋Š” ํ—ˆ์œ„๊ด‘๊ณ , ๋ฒ•์  ๋ณด์ฆ๊ธฐ๊ฐ„ ๋‚ด ์ˆ˜๋ฆฌ ์˜๋ฌด ์œ„๋ฐ˜, ์†Œ๋น„์ž ์ œํ’ˆ์ˆ˜๋ฆฌ ๊ฑฐ๋ถ€ ๋“ฑ ์†Œ๋น„์ž๋ณดํ˜ธ ๊ด€๋ จ ์ œ์žฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.\\n- SETK๋Š” ํœด๋Œ€ํฐ ํŒ๋งค๋ชจ๋ธ ํŠน์ • ๊ธฐ๋Šฅ ํ—ˆ์œ„๊ด‘๊ณ ๋กœ ํŠ€๋ฅดํ‚ค์˜ˆ ๋‹น๊ตญ์˜ ๊ณผ์ง•๊ธˆ์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.\\n- SEMAG๋Š” ์žฌํŒ๋งค๊ฐ€๊ฒฉ ์œ ์ง€ ๋“ฑ ๊ณต์ •๊ฒฝ์Ÿ ์œ„๋ฐ˜์œผ๋กœ ํŠ€๋‹ˆ์ง€ ๋ฐ˜๋…์ ์œ„์›ํšŒ๋กœ๋ถ€ํ„ฐ ๊ณผ์ง•๊ธˆ์„ ๋ถ€๊ณผ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.\\n\\n### ์ˆ˜์ถœํ†ต์ œยท์ œ์žฌยท๊ด€์„ธ ๊ด€๋ จ\\n- ์‚ผ์„ฑ๋ฉ”๋””์Šจใˆœ๋Š” ์ƒํ™ฉํ—ˆ๊ฐ€ ๋Œ€์ƒ ์ผ๋ถ€ ํ’ˆ๋ชฉ์„ ์‚ฌ์ „ ํ—ˆ๊ฐ€ ์—†์ด ์ˆ˜์ถœํ•ด ์‚ฐ์—…ํ†ต์ƒ์ž์›๋ถ€ ๊ต์œก๋ช…๋ น์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.\\n- Harman์€ ๋ฐ˜๋คํ•‘ ๊ด€์„ธ๊ฐ€ ์ž˜๋ชป ์ง€๊ธ‰๋˜์—ˆ์Œ์„ ์ธ์ง€ํ–ˆ์œผ๋‚˜ ์ž๋ฐœ์  ์ž์ฒด ๋ณด๊ณ ๋ฅผ ํ•˜์ง€ ์•Š์•„ ๋ฏธ๊ตญ ๋ฒ•๋ฌด๋ถ€๋กœ๋ถ€ํ„ฐ ํ—ˆ์œ„์ฒญ๊ตฌ๋ฒ• ์œ„๋ฐ˜ ๋ฒŒ๊ธˆ **USD 11,800,000**์„ ๋ถ€๊ณผ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.\\n- Harman์€ ํŠน์ • ๊ฑฐ๋ž˜๊ฐ€ ๋ฏธ๊ตญ ์ œ์žฌ ๋Œ€์ƒ๊ตญ๊ฐ€์™€ ๊ด€๋ จ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Œ์„ ์ž๋ฐœ์ ์œผ๋กœ ๋ณด๊ณ ํ–ˆ์œผ๋‚˜, ํ•ด์™ธ์ž์‚ฐํ†ต์ œ๊ตญ์œผ๋กœ๋ถ€ํ„ฐ ๊ณผํƒœ๋ฃŒ **USD 1,454,145**๋ฅผ ๋ถ€๊ณผ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.\\n\\n## 5) ํ™˜๊ฒฝยทํƒ„์†Œ ๊ด€๋ จ ๋ฆฌ์Šคํฌ ๋ฐ ๊ด€๋ฆฌ\\n\\n- ์‚ผ์„ฑ์ „์ž๋Š” ์ •๋ถ€์˜ ์ €ํƒ„์†Œ ๋…น์ƒ‰์„ฑ์žฅ ์ •์ฑ…์— ๋”ฐ๋ผ ์˜จ์‹ค๊ฐ€์Šค ๋ฐฐ์ถœ๋Ÿ‰๊ณผ ์—๋„ˆ์ง€ ์‚ฌ์šฉ๋Ÿ‰์„ ์ •๋ถ€์— ์‹ ๊ณ ํ•˜๊ณ , ์ง€์†๊ฐ€๋Šฅ๋ณด๊ณ ์„œ ๋“ฑ์„ ํ†ตํ•ด ๊ณต๊ฐœํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n- 2025๋…„ ๋ง ๋ณ„๋„ ๊ธฐ์ค€์œผ๋กœ ์ˆ˜์›, ๊ตฌ๋ฏธ, ๊ธฐํฅยทํ™”์„ฑ, ํ‰ํƒ, ์˜จ์–‘, ์ฒœ์•ˆ์‚ฌ์—…์žฅ์ด ๋…น์ƒ‰๊ธฐ์—…์œผ๋กœ ์ง€์ •๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.\\n- 2025๋…„ 12์›” ๋ง ๊ธฐ์ค€ ์ด **6๊ฑด**์˜ ๋…น์ƒ‰๊ธฐ์ˆ  ์œ ํšจ ์ธ์ฆ๊ณผ, ํ•ด๋‹น ๊ธฐ์ˆ ์ด ์ ์šฉ๋œ **121๊ฐœ ๋ชจ๋ธ**์˜ ๋…น์ƒ‰๊ธฐ์ˆ ์ œํ’ˆ ํ™•์ธ ์ธ์ฆ์„ ๋ณด์œ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n- ์‚ผ์„ฑ์ „์ž๋Š” ์˜จ์‹ค๊ฐ€์Šค ์—๋„ˆ์ง€ ๋ชฉํ‘œ๊ด€๋ฆฌ์—…์ฒด์ด์ž, 2015๋…„๋ถ€ํ„ฐ ์˜จ์‹ค๊ฐ€์Šค ๋ฐฐ์ถœ๊ถŒ ํ• ๋‹น๋Œ€์ƒ์—…์ฒด์— ํ•ด๋‹นํ•ฉ๋‹ˆ๋‹ค.\\n- ๋”ฐ๋ผ์„œ ํƒ„์†Œ๋ฐฐ์ถœ ๊ทœ์ œ, ์—๋„ˆ์ง€ ์‚ฌ์šฉ๋Ÿ‰ ๊ด€๋ฆฌ, ๋ฐฐ์ถœ๊ถŒ ๊ด€๋ จ ์ œ๋„ ๋ณ€ํ™”๋Š” ์ง€์†์ ์ธ ๊ด€๋ฆฌ ๋Œ€์ƒ์ž…๋‹ˆ๋‹ค.\\n\\n## ์ข…ํ•ฉ ํŒ๋‹จ\\n\\n์ œ๊ณต ๋ฐœ์ทŒ ๊ธฐ์ค€์œผ๋กœ ๋ณผ ๋•Œ ์‚ผ์„ฑ์ „์ž์˜ ํ•ต์‹ฌ ๋ฆฌ์Šคํฌ๋Š” ํฌ๊ฒŒ ์„ธ ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค.\\n\\n1. **๊ธ€๋กœ๋ฒŒ ์˜์—… ๊ธฐ๋ฐ˜์˜ ๊ธˆ์œต์‹œ์žฅ ๋ฆฌ์Šคํฌ** \\n USD, EUR ๋“ฑ ํ™˜์œจ, ๊ธˆ๋ฆฌ, ์ง€๋ถ„์ƒํ’ˆ ์ฃผ๊ฐ€ ๋ณ€๋™์— ๋…ธ์ถœ๋˜์–ด ์žˆ์œผ๋ฉฐ, ํ†ตํ™”์„ ๋„์™€ ์ž๊ธˆ๊ด€๋ฆฌ ์ฒด๊ณ„๋กœ ์™„ํ™”ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n2. **๋Œ€๊ทœ๋ชจ ํˆฌ์ž๊ธฐ์—… ํŠน์„ฑ์ƒ ์œ ๋™์„ฑยท์ž๋ณธ๊ด€๋ฆฌ ์ค‘์š”์„ฑ** \\n ์ƒ์‚ฐยท์—ฐ๊ตฌ๊ฐœ๋ฐœยท์šด์ „์ž๋ณธยท๋ฐฐ๋‹น ์ˆ˜์š”๊ฐ€ ํฌ๊ธฐ ๋•Œ๋ฌธ์— ์ ์ • ์œ ๋™์„ฑ ๊ด€๋ฆฌ๊ฐ€ ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค. ๋‹ค๋งŒ Cash Pooling, ๋ณธ์‚ฌ ์ง€๊ธ‰๋ณด์ฆ ๊ธฐ๋ฐ˜ ํ•ด์™ธ ์ฐจ์ž…ํ•œ๋„, Aa2/AA- ์‹ ์šฉ๋“ฑ๊ธ‰์€ ์™„์ถฉ ์š”์ธ์ž…๋‹ˆ๋‹ค.\\n\\n3. **์ค€๋ฒ•ยท๊ทœ์ œยทํ‰ํŒ ๋ฆฌ์Šคํฌ์˜ ๋ฒ”์œ„ ํ™•๋Œ€** \\n ์‚ฐ์—…์•ˆ์ „, ๊ฐœ์ธ์ •๋ณด, ๊ณต์ •๊ฑฐ๋ž˜, ํ™˜๊ฒฝ, ์ˆ˜์ถœํ†ต์ œ, ํ•ด์™ธ ์ œ์žฌยท๊ด€์„ธ, ์†Œ๋น„์ž๋ณดํ˜ธ ๋“ฑ ๋‹ค์–‘ํ•œ ์˜์—ญ์—์„œ ์ œ์žฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์€ ๋ฒŒ๊ธˆยท๊ณผํƒœ๋ฃŒ ๋‚ฉ๋ถ€ ๋ฐ ์žฌ๋ฐœ๋ฐฉ์ง€์ฑ… ์‹œํ–‰์œผ๋กœ ๊ณต์‹œ๋˜์–ด ์žˆ์œผ๋‚˜, ํˆฌ์ž์ž ๋ณดํ˜ธ ๊ด€์ ์—์„œ๋Š” ๋‚ด๋ถ€ํ†ต์ œ์™€ ๊ธ€๋กœ๋ฒŒ ๊ทœ์ œ ๋Œ€์‘ ์—ญ๋Ÿ‰์„ ์ง€์† ์ ๊ฒ€ํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.\",\"rawSectionsFile\":\".dexter/tool-results/kr-filing-00126380-20260310002820.json\",\"note\":\"summary is grounded in the report narrative. Use read_file on rawSectionsFile for the full extracted sections.\"},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/document.xml?rcept_no=20260310002820\",\"https://opendart.fss.or.kr/api/list.json?corp_code=00126380&bgn_de=20241201&end_de=20260531&pblntf_ty=A&page_count=100&page_no=1&sort=date&sort_mth=desc\"]}" + "result": "{\"data\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_nm\":\"์‚ฌ์—…๋ณด๊ณ ์„œ (2025.12)\",\"rcept_no\":\"20260310002820\",\"sections_found\":[\"risks\"],\"summary\":\"์‚ผ์„ฑ์ „์ž ์ตœ์‹  ์‚ฌ์—…๋ณด๊ณ ์„œ ๋ฐœ์ทŒ ๊ธฐ์ค€์œผ๋กœ ๋ณด๋ฉด, **ํ•ต์‹ฌ ๋ฆฌ์Šคํฌ๋Š” ํ™˜์œจยท๊ธˆ๋ฆฌยท์ฃผ๊ฐ€ ๊ฐ™์€ ์‹œ์žฅ์œ„ํ—˜, ๊ฑฐ๋ž˜์ƒ๋Œ€๋ฐฉ ์‹ ์šฉ์œ„ํ—˜, ๋Œ€๊ทœ๋ชจ ํˆฌ์ž์— ๋”ฐ๋ฅธ ์œ ๋™์„ฑ์œ„ํ—˜, ๊ทธ๋ฆฌ๊ณ  ์ž๋ณธ๊ตฌ์กฐ ๊ด€๋ฆฌ**๋กœ ์ •๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ํˆฌ์ž์ž ๊ด€์ ์—์„œ๋Š” **ํ—ค์ง€์™€ ๋‚ด๋ถ€ํ†ต์ œ๋Š” ์ƒ๋‹นํžˆ ์ฒด๊ณ„์ ์ด์ง€๋งŒ, ๊ธ€๋กœ๋ฒŒ ์˜์—…ยท๋Œ€๊ทœ๋ชจ ํˆฌ์ž ํŠน์„ฑ์ƒ ์œ„ํ—˜์ด ์™„์ „ํžˆ ์ œ๊ฑฐ๋˜์ง€๋Š” ์•Š๋Š”๋‹ค**๋Š” ์ ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.\\n\\n## 1) ์‹œ์žฅ์œ„ํ—˜: ํ™˜์œจยท๊ธˆ๋ฆฌยท์ฃผ๊ฐ€ ๋ณ€๋™\\n- **ํ™˜์œจ๋ณ€๋™์œ„ํ—˜**์— ๊ฐ€์žฅ ์ ๊ทน์ ์œผ๋กœ ๋Œ€์‘ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. \\n - USD, EUR ๋“ฑ ์ฃผ์š” ํ†ตํ™”์— ๋…ธ์ถœ๋˜์–ด ์žˆ์œผ๋ฉฐ, \\n - ํ†ตํ™”๋ณ„ ์ž์‚ฐยท๋ถ€์ฑ„ ๊ทœ๋ชจ๋ฅผ ๋งž์ถ”๊ณ , \\n - ์ˆ˜์ถœ์ž…ยท์˜ˆ๊ธˆยท์ฐจ์ž… ์‹œ ๊ธฐ๋Šฅํ†ตํ™” ๊ธฐ์ค€ ๊ฑฐ๋ž˜ ๋˜๋Š” ์ž…์ถœ๊ธˆ ํ†ตํ™” ์ผ์น˜๋กœ ํ™˜ํฌ์ง€์…˜์„ ์ค„์ด๋ฉฐ, \\n - ๋‚จ๋Š” ํฌ์ง€์…˜์€ **์ฑ„๊ถŒ๋งค๊ฐ, ์„ ๋ฌผํ™˜**์œผ๋กœ ์ถ•์†Œํ•ฉ๋‹ˆ๋‹ค. \\n- ํ•ด์™ธ ์ฃผ์š” ๊ถŒ์—ญ๋ณ„ **์ง€์—ญ๊ธˆ์œต์„ผํ„ฐ(๋ฏธ๊ตญ, ์˜๊ตญ, ์‹ฑ๊ฐ€ํฌ๋ฅด, ์ค‘๊ตญ, ๋ธŒ๋ผ์งˆ, UAE)**๋ฅผ ํ†ตํ•ด ํ™˜์œจ ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ํ™˜๊ฑฐ๋ž˜ ๋Œ€ํ–‰์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\\n- ๋‹ค๋งŒ ํšŒ์‚ฌ ์Šค์Šค๋กœ๋„ **ํ™˜์œ„ํ—˜์„ ์ค„์ผ ๋ฟ ์™„์ „ํžˆ ์ œ๊ฑฐํ•˜์ง€๋Š” ๋ชปํ•œ๋‹ค**๊ณ  ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค.\\n\\n- **์ด์ž์œจ๋ณ€๋™์œ„ํ—˜**์€ ์˜ˆ๊ธˆ ๋ฐ ๋ณ€๋™๊ธˆ๋ฆฌ๋ถ€ ์ฐจ์ž…๊ธˆ์—์„œ ๋ฐœ์ƒํ•˜๋ฉฐ, \\n - **๋‚ด๋ถ€์ž๊ธˆ ๊ณต์œ ๋ฅผ ํ†ตํ•ด ์™ธ๋ถ€์ฐจ์ž…์„ ์ตœ์†Œํ™”**ํ•ด ๊ธˆ์œต๋น„์šฉ๊ณผ ๋ถˆํ™•์‹ค์„ฑ์„ ์ค„์ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n- **์ฃผ๊ฐ€๋ณ€๋™์œ„ํ—˜**์€ ์ „๋žต์  ๋ชฉ์ ์˜ ์ง€๋ถ„ํˆฌ์ž์—์„œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. \\n - 2025๋…„๋ง ๊ธฐ์ค€ ์ƒ์žฅ์ฃผ์‹์˜ ์ฃผ๊ฐ€ 1% ๋ณ€๋™ ์‹œ \\n - **๊ธฐํƒ€ํฌ๊ด„์†์ต ์˜ํ–ฅ: 132,067๋ฐฑ๋งŒ์›**, \\n - **๋‹น๊ธฐ์†์ต ์˜ํ–ฅ: 1,460๋ฐฑ๋งŒ์›**์œผ๋กœ ์ œ์‹œ๋ผ ์žˆ์Šต๋‹ˆ๋‹ค. \\n - ์ฆ‰, ์ง€๋ถ„ํˆฌ์ž ํฌํŠธํด๋ฆฌ์˜ค๊ฐ€ ์†์ต๊ณผ ์ž๋ณธ ๋ณ€๋™์„ฑ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n## 2) ์‹ ์šฉ์œ„ํ—˜: ๊ฑฐ๋ž˜์ฒ˜ยท๊ธˆ์œต๊ธฐ๊ด€ ๊ด€๋ฆฌ\\n- ๊ณ ๊ฐ๊ณผ ๊ฑฐ๋ž˜์ƒ๋Œ€๋ฐฉ์˜ ๊ณ„์•ฝ๋ถˆ์ดํ–‰ ๊ฐ€๋Šฅ์„ฑ์— ๋Œ€๋น„ํ•ด \\n - **์žฌ๋ฌด์ƒํƒœ, ๊ณผ๊ฑฐ ๊ฒฝํ—˜ ๋“ฑ์„ ๋ฐ”ํƒ•์œผ๋กœ ์ •๊ธฐ์ ์œผ๋กœ ์‹ ์šฉ๋„ ํ‰๊ฐ€**, \\n - **์‹ ์šฉํ•œ๋„ ์„ค์ •ยท๊ด€๋ฆฌ**๋ฅผ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n- **์œ„ํ—˜๊ตญ๊ฐ€ ๊ฑฐ๋ž˜์ฒ˜์˜ ๋งค์ถœ์ฑ„๊ถŒ์€ ๋ณดํ—˜ํ•œ๋„ ๋‚ด์—์„œ ๊ด€๋ฆฌ**ํ•ฉ๋‹ˆ๋‹ค.\\n- ๊ธˆ์œต๊ธฐ๊ด€ ๊ฑฐ๋ž˜๋Š” **S&P A ์ด์ƒ ์€ํ–‰**์„ ์›์น™์œผ๋กœ ํ•˜๊ณ , ์‹ ๊ทœ ๊ธˆ์œต๊ธฐ๊ด€์€ ๊ฑด์ „์„ฑ ํ‰๊ฐ€ ํ›„ ๊ฑฐ๋ž˜ํ•ฉ๋‹ˆ๋‹ค.\\n- ๊ธˆ์œต๊ณ„์•ฝ๋„ **๋ถ€์ฑ„๋น„์œจ ์ œํ•œ ์กฐํ•ญ, ๋‹ด๋ณด์ œ๊ณต, ์ฐจ์ž…๊ธˆ ํšŒ์ˆ˜ ๋“ฑ ์ œ์•ฝ์กฐ๊ฑด์ด ์—†๋Š” ๊ณ„์•ฝ ์œ„์ฃผ**๋กœ ์ฒด๊ฒฐํ•ด ๋ถˆํ•„์š”ํ•œ ๋ฆฌ์Šคํฌ๋ฅผ ์ค„์ด๋ ค๋Š” ๋ชจ์Šต์ž…๋‹ˆ๋‹ค.\\n\\n## 3) ์œ ๋™์„ฑ์œ„ํ—˜: ๋Œ€๊ทœ๋ชจ ํˆฌ์ž์™€ ์ž๊ธˆ์šด์šฉ\\n- ์‚ผ์„ฑ์ „์ž๋Š” **์ƒ์‚ฐยท์—ฐ๊ตฌ๊ฐœ๋ฐœ ํˆฌ์ž, ์šด์ „์ž๋ณธ, ๋ฐฐ๋‹น** ๋“ฑ ์ž๊ธˆ์ˆ˜์š”๊ฐ€ ํฌ๊ธฐ ๋•Œ๋ฌธ์— **์ ์ • ์œ ๋™์„ฑ ์œ ์ง€๊ฐ€ ๋งค์šฐ ์ค‘์š”**ํ•˜๋‹ค๊ณ  ์ ์‹œํ•ฉ๋‹ˆ๋‹ค.\\n- ๋Œ€์‘์ˆ˜๋‹จ์€ \\n - **์ฃผ๊ธฐ์  ์ž๊ธˆ์ˆ˜์ง€ ์˜ˆ์ธก**, \\n - ํ•„์š” ํ˜„๊ธˆ์ˆ˜์ค€ ์‚ฐ์ •, \\n - ์ˆ˜์ž…ยท์ง€์ถœ ๊ด€๋ฆฌ, \\n - ๊ถŒ์—ญ๋ณ„ **Cash Pooling**์ž…๋‹ˆ๋‹ค.\\n- Cash Pooling์€ ๊ถŒ์—ญ ๋‚ด ์ž๊ธˆ์ž‰์—ฌยท๋ถ€์กฑ ๋ฒ•์ธ ๊ฐ„ ์ž๊ธˆ์„ ๊ณต์œ ํ•ด **๊ฐœ๋ณ„ ๋ฒ•์ธ์˜ ์œ ๋™์„ฑ์œ„ํ—˜์„ ๋‚ฎ์ถ”๊ณ  ๊ธˆ์œต๋น„์šฉ์„ ์ค„์ด๋Š” ๊ตฌ์กฐ**์ž…๋‹ˆ๋‹ค.\\n- ๋˜ํ•œ ํ•ด์™ธ ์ข…์†๊ธฐ์—…์ด ์“ธ ์ˆ˜ ์žˆ๋„๋ก **๋ณธ์‚ฌ ์ง€๊ธ‰๋ณด์ฆ์„ ํ†ตํ•ด ์ฐจ์ž…ํ•œ๋„ ํ™•๋ณด**๋ฅผ ํ•˜๊ณ , \\n - 2025๋…„๋ง ๊ธฐ์ค€ **Moodyโ€™s Aa2, S&P AA- ํˆฌ์ž์ ๊ฒฉ๋“ฑ๊ธ‰**์„ ๋ณด์œ ํ•ด ํ•„์š” ์‹œ ์ž๋ณธ์‹œ์žฅ ์กฐ๋‹ฌ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๊ณ  ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.\\n\\n## 4) ์ž๋ณธ์œ„ํ—˜: ์‹ ์šฉ๋“ฑ๊ธ‰ยท๋ถ€์ฑ„๋น„์œจ ๊ด€๋ฆฌ\\n- ์ž๋ณธ๊ด€๋ฆฌ ๋ชฉ์ ์€ **๊ณ„์†๊ธฐ์—…์œผ๋กœ์„œ ์ด์ต์„ ์ง€์† ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ ๋ณดํ˜ธ**์™€ **๊ฑด์ „ํ•œ ์ž๋ณธ๊ตฌ์กฐ ์œ ์ง€**์ž…๋‹ˆ๋‹ค.\\n- ๊ด€๋ฆฌ์ง€ํ‘œ๋กœ **์‹ ์šฉ๋“ฑ๊ธ‰, ๋ถ€์ฑ„๋น„์œจ**์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.\\n- ํˆฌ์ž์ž ์ž…์žฅ์—์„œ๋Š” ๋‹จ์ˆœํžˆ ๋ถ€์ฑ„ ๊ทœ๋ชจ๊ฐ€ ์•„๋‹ˆ๋ผ, **๋Œ€๊ทœ๋ชจ ํˆฌ์ž ์‚ฌ์ดํด์„ ๋ฒ„ํ‹ธ ์ˆ˜ ์žˆ๋Š” ์ž๋ณธ ์•ˆ์ •์„ฑ**์ด ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค.\\n\\n## 5) ํŒŒ์ƒ์ƒํ’ˆยทํ’‹๋ฐฑ์˜ต์…˜ ๋“ฑ: ํ—ค์ง€์™€ ์˜ต์…˜์„ฑ ๋ฆฌ์Šคํฌ\\n- ํ™˜์œจ์œ„ํ—˜ ๊ด€๋ฆฌ ๋ชฉ์ ์˜ **ํ†ตํ™”์„ ๋„(Currency Forward)**๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.\\n - 2025๋…„๋ง ๊ธฐ์ค€ **USD, EUR, JPY ๋“ฑ 33๊ฐœ ํ†ตํ™”, 3,360๊ฑด**์˜ ํ†ตํ™”์„ ๋„ ๊ฑฐ๋ž˜๋ฅผ ์ฒด๊ฒฐ.\\n- ๋‹ค๋งŒ ํŒŒ์ƒ๊ฑฐ๋ž˜๋Š” **์œ„ํ—˜ํšŒํ”ผ ๋ชฉ์ **๊ณผ **๋งค๋งค๋ชฉ์ **์œผ๋กœ๋„ ์€ํ–‰์„ ํ†ตํ•ด ์šด์šฉ๋ฉ๋‹ˆ๋‹ค.\\n- ์˜ต์…˜ ๊ด€๋ จ์œผ๋กœ๋Š” \\n - **๋ ˆ์ธ๋ณด์šฐ๋กœ๋ณดํ‹ฑ์Šคใˆœ ์ฝœ์˜ต์…˜** ์ธ์ˆ˜ ์ ˆ์ฐจ๋ฅผ 2025๋…„ 3์›” 12์ผ ์™„๋ฃŒํ–ˆ๊ณ , \\n - ์ข…์†ํšŒ์‚ฌ ์‚ผ์„ฑ๋””์Šคํ”Œ๋ ˆ์ดใˆœ๋Š” **Corning ์ง€๋ถ„ ํ’‹์˜ต์…˜**, **TCL/CSOT ๊ด€๋ จ ํ’‹์˜ต์…˜**, ๊ทธ๋ฆฌ๊ณ  **์ฐจ์•ก๊ฒฐ์ œํ˜• ์ฃผ์‹์„ ๋„๊ณ„์•ฝ**์„ ๋ณด์œ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. \\n- ์ด๋Š” ๋‹จ๊ธฐ ์†์ต ๋ณ€๋™์„ฑ๋ฟ ์•„๋‹ˆ๋ผ ํ–ฅํ›„ ํ˜„๊ธˆ์œ ์ถœ ๊ฐ€๋Šฅ์„ฑ๋„ ๋‚ดํฌํ•  ์ˆ˜ ์žˆ์–ด, ํˆฌ์ž์ž๋Š” **์˜ต์…˜ ๊ตฌ์กฐ์— ๋”ฐ๋ฅธ ์ž ์žฌ ๋ถ€๋‹ด**์„ ์ฃผ๋ชฉํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n## 6) ํˆฌ์ž์ž ๋ณดํ˜ธ ๊ด€์ ์—์„œ ๋ณด๋ฉด\\n- ํšŒ์‚ฌ๋Š” \\n - ํˆฌ๊ธฐ์  ์™ธํ™˜๊ฑฐ๋ž˜๋ฅผ **์—„๊ฒฉํžˆ ๊ธˆ์ง€**, \\n - ์‹ ์šฉ๋„๊ฐ€ ๋†’์€ ์€ํ–‰๋งŒ ๊ฑฐ๋ž˜, \\n - ์ž๊ธˆ์€ ๊ถŒ์—ญ๋ณ„๋กœ ํ†ตํ•ฉ ์šด์šฉ, \\n - ๋‚ด๋ถ€ํ†ต์ œ์™€ ๊ต์œก์„ ์ง€์† ๊ฐ•ํ™”ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n- ํ•˜์ง€๋งŒ ๋ฐœ์ทŒ๋œ ์ œ์žฌ ๋‚ด์—ญ์„ ๋ณด๋ฉด, \\n - ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ, ์‚ฐ์—…์•ˆ์ „, ํ™”ํ•™๋ฌผ์งˆ, ๊ณต์ •๊ฑฐ๋ž˜, ํ—ˆ์œ„๊ด‘๊ณ , ์ˆ˜์ถœํ—ˆ๊ฐ€, ๋ฐ˜๋…์  ๋“ฑ์—์„œ **๋‹ค์ˆ˜์˜ ํ–‰์ •ยท์‚ฌ๋ฒ• ์ œ์žฌ๊ฐ€ ๋ฐ˜๋ณต์ ์œผ๋กœ ๋ฐœ์ƒ**ํ–ˆ์Šต๋‹ˆ๋‹ค.\\n- ๋”ฐ๋ผ์„œ ํˆฌ์ž์ž ๋ณดํ˜ธ์˜ ํ•ต์‹ฌ์€ โ€œ๋ฆฌ์Šคํฌ๊ด€๋ฆฌ ์ œ๋„๊ฐ€ ์—†๋А๋ƒโ€๊ฐ€ ์•„๋‹ˆ๋ผ, **๊ด‘๋ฒ”์œ„ํ•œ ์‚ฌ์—…ํฌํŠธํด๋ฆฌ์˜ค ๋•Œ๋ฌธ์— ์ปดํ”Œ๋ผ์ด์–ธ์Šค ๋ฆฌ์Šคํฌ๊ฐ€ ์ƒ์‹œ ์กด์žฌํ•œ๋‹ค**๋Š” ์ ์ž…๋‹ˆ๋‹ค. \\n - ํšŒ์‚ฌ๋Š” ๋Œ€๋ถ€๋ถ„์˜ ์‚ฌ๊ฑด์— ๋Œ€ํ•ด **๋ฒŒ๊ธˆ/๊ณผํƒœ๋ฃŒ ๋‚ฉ๋ถ€ ์™„๋ฃŒ**, **๊ต์œกยทํ”„๋กœ์„ธ์Šค ๊ฐœ์„ **์„ ์ง„ํ–‰ ์ค‘์ด๋ผ๊ณ  ์ ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\\n\\n## ํ•œ ์ค„ ์š”์•ฝ\\n์‚ผ์„ฑ์ „์ž๋Š” **ํ™˜ํ—ค์ง€ยทCash Poolingยท์‹ ์šฉํ•œ๋„ยทํˆฌ์ž์ ๊ฒฉ ์กฐ๋‹ฌ๋Šฅ๋ ฅ**์„ ํ†ตํ•ด ์žฌ๋ฌด์œ„ํ—˜์„ ๊ฝค ์ฒด๊ณ„์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์ง€๋งŒ, **๊ธ€๋กœ๋ฒŒ ์˜์—…ยท๋Œ€๊ทœ๋ชจ ํˆฌ์žยท๋ณต์žกํ•œ ํŒŒ์ƒ/์˜ต์…˜ ๊ตฌ์กฐ์™€ ๋”๋ถˆ์–ด ๋‹ค์–‘ํ•œ ๊ทœ์ œยท์ค€๋ฒ• ๋ฆฌ์Šคํฌ๊ฐ€ ์ƒ์กด**ํ•˜๋ฏ€๋กœ, ํˆฌ์ž์ž๋Š” โ€œ์•ˆ์ •์  ์ž๊ธˆ์กฐ๋‹ฌ ๋Šฅ๋ ฅโ€๊ณผ โ€œ๋ฐ˜๋ณต๋˜๋Š” ์ปดํ”Œ๋ผ์ด์–ธ์Šค ์ด์Šˆโ€๋ฅผ ํ•จ๊ป˜ ๋ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\\n\\n์›ํ•˜์‹œ๋ฉด ์ œ๊ฐ€ ์ด์–ด์„œ \\n1) **๋ฆฌ์Šคํฌ๋ฅผ ํˆฌ์ž์ž ๊ด€์ (๋งค์ˆ˜/๋ณด์œ  ์ฒดํฌํฌ์ธํŠธ)**์œผ๋กœ ์žฌ์ •๋ฆฌํ•˜๊ฑฐ๋‚˜, \\n2) **์‚ฌ์—…๋ณด๊ณ ์„œ ๊ธฐ์ค€ โ€˜๊ธ์ •์š”์ธ vs ๋ฆฌ์Šคํฌโ€™ 1ํŽ˜์ด์ง€ ์š”์•ฝ** ํ˜•ํƒœ๋กœ๋„ ์ •๋ฆฌํ•ด๋“œ๋ฆด๊ฒŒ์š”.\",\"rawSectionsFile\":\".dexter/tool-results/kr-filing-00126380-20260310002820.json\",\"note\":\"summary is grounded in the report narrative. Use read_file on rawSectionsFile for the full extracted sections.\"},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/document.xml?rcept_no=20260310002820\",\"https://opendart.fss.or.kr/api/list.json?corp_code=00126380&bgn_de=20241202&end_de=20260602&pblntf_ty=A&page_count=100&page_no=1&sort=date&sort_mth=desc\"]}" } ] } diff --git a/scripts/kr-eval/fixtures/default/large-holders-samsung.json b/scripts/kr-eval/fixtures/default/large-holders-samsung.json index 9fafb7587..d6bd369d4 100644 --- a/scripts/kr-eval/fixtures/default/large-holders-samsung.json +++ b/scripts/kr-eval/fixtures/default/large-holders-samsung.json @@ -9,9 +9,9 @@ "tool": "get_large_holders_kr", "args": { "ticker": "005930", - "limit": 30 + "limit": 50 }, - "result": "{\"data\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"holders\":[{\"rcept_no\":\"20260522000589\",\"rcept_dt\":\"2026-05-22\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,513,080\",\"stkqy_irds\":\"-48,004\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"49,359,000\",\"ctr_stkrt\":\"0.84\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260504000421\",\"rcept_dt\":\"2026-05-04\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,561,084\",\"stkqy_irds\":\"-10,417\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"63,584,000\",\"ctr_stkrt\":\"1.09\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260424000758\",\"rcept_dt\":\"2026-04-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,571,501\",\"stkqy_irds\":\"-8,476\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"67,703,000\",\"ctr_stkrt\":\"1.16\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260417000682\",\"rcept_dt\":\"2026-04-17\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,579,977\",\"stkqy_irds\":\"2,778\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"67,703,000\",\"ctr_stkrt\":\"1.16\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260413002934\",\"rcept_dt\":\"2026-04-13\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,577,199\",\"stkqy_irds\":\"-22,378,550\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.13\",\"ctr_stkqy\":\"80,237,000\",\"ctr_stkrt\":\"1.37\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\\n- ์‹ ํƒ์ฃผ์‹ ์ฒ˜๋ถ„\"},{\"rcept_no\":\"20260130000730\",\"rcept_dt\":\"2026-01-30\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,173,955,749\",\"stkqy_irds\":\"35,644\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"95,237,000\",\"ctr_stkrt\":\"1.61\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260123000544\",\"rcept_dt\":\"2026-01-23\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,173,920,105\",\"stkqy_irds\":\"-187,402\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"103,134,000\",\"ctr_stkrt\":\"1.74\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260116000451\",\"rcept_dt\":\"2026-01-16\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,174,107,507\",\"stkqy_irds\":\"-11,934\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"103,134,000\",\"ctr_stkrt\":\"1.74\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ์œ ๊ฐ€์ฆ๊ถŒ์‹ ํƒ(๋ณด์œ ํ˜•ํƒœ ๋ณ€๊ฒฝ ํฌํ•จ)\"},{\"rcept_no\":\"20260109000680\",\"rcept_dt\":\"2026-01-09\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,174,119,441\",\"stkqy_irds\":\"-386,875\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"-0.01\",\"ctr_stkqy\":\"88,134,000\",\"ctr_stkrt\":\"1.49\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20251107000558\",\"rcept_dt\":\"2025-11-07\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,174,506,316\",\"stkqy_irds\":\"-17,733,231\",\"stkrt\":\"19.84\",\"stkrt_irds\":\"-0.30\",\"ctr_stkqy\":\"86,267,000\",\"ctr_stkrt\":\"1.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\\n- ์‹ ํƒ์ฃผ์‹ ์ฒ˜๋ถ„\"},{\"rcept_no\":\"20251031000575\",\"rcept_dt\":\"2025-10-31\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,239,547\",\"stkqy_irds\":\"-52,021\",\"stkrt\":\"20.14\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"145,670,929\",\"ctr_stkrt\":\"2.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20251024000542\",\"rcept_dt\":\"2025-10-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,291,568\",\"stkqy_irds\":\"-60,260\",\"stkrt\":\"20.14\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"145,670,929\",\"ctr_stkrt\":\"2.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20251017000512\",\"rcept_dt\":\"2025-10-17\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,351,828\",\"stkqy_irds\":\"-168,900\",\"stkrt\":\"20.14\",\"stkrt_irds\":\"-0.01\",\"ctr_stkqy\":\"145,670,929\",\"ctr_stkrt\":\"2.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\\n- ์œ ๊ฐ€์ฆ๊ถŒ์‹ ํƒ(๋ณด์œ ํ˜•ํƒœ ๋ณ€๊ฒฝ ํฌํ•จ)\"},{\"rcept_no\":\"20250919000589\",\"rcept_dt\":\"2025-09-19\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,520,728\",\"stkqy_irds\":\"-45,660\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250905000524\",\"rcept_dt\":\"2025-09-05\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,566,388\",\"stkqy_irds\":\"-77,185\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250814004144\",\"rcept_dt\":\"2025-08-14\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,643,573\",\"stkqy_irds\":\"-92,241\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250801001081\",\"rcept_dt\":\"2025-08-01\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,735,814\",\"stkqy_irds\":\"9,551\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250725000756\",\"rcept_dt\":\"2025-07-25\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,726,263\",\"stkqy_irds\":\"4,560\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250718000569\",\"rcept_dt\":\"2025-07-18\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,721,703\",\"stkqy_irds\":\"-164,817\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250620000604\",\"rcept_dt\":\"2025-06-20\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,886,520\",\"stkqy_irds\":\"-191,832\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250523000612\",\"rcept_dt\":\"2025-05-23\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,078,352\",\"stkqy_irds\":\"-876\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250516000386\",\"rcept_dt\":\"2025-05-16\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,079,228\",\"stkqy_irds\":\"-29,835\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.01\",\"ctr_stkqy\":\"152,224,392\",\"ctr_stkrt\":\"2.57\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250509000826\",\"rcept_dt\":\"2025-05-09\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,109,063\",\"stkqy_irds\":\"-32,842\",\"stkrt\":\"20.16\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"152,224,392\",\"ctr_stkrt\":\"2.57\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250425000921\",\"rcept_dt\":\"2025-04-25\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,141,905\",\"stkqy_irds\":\"-9,066\",\"stkrt\":\"20.16\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250418000638\",\"rcept_dt\":\"2025-04-18\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,150,971\",\"stkqy_irds\":\"144,693\",\"stkrt\":\"20.16\",\"stkrt_irds\":\"0.01\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250321001642\",\"rcept_dt\":\"2025-03-21\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,006,278\",\"stkqy_irds\":\"-10,682\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250307001130\",\"rcept_dt\":\"2025-03-07\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,016,960\",\"stkqy_irds\":\"-77,470\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"0.16\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250214002629\",\"rcept_dt\":\"2025-02-14\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,094,430\",\"stkqy_irds\":\"-4,963,824\",\"stkrt\":\"19.99\",\"stkrt_irds\":\"-0.08\",\"ctr_stkqy\":\"95,423,993\",\"ctr_stkrt\":\"1.60\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250124000900\",\"rcept_dt\":\"2025-01-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,198,080,226\",\"stkqy_irds\":\"-38,791\",\"stkrt\":\"20.07\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"95,423,993\",\"ctr_stkrt\":\"1.60\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250124000908\",\"rcept_dt\":\"2025-01-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,198,058,254\",\"stkqy_irds\":\"-21,972\",\"stkrt\":\"20.07\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"95,423,993\",\"ctr_stkrt\":\"1.60\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/majorstock.json?corp_code=00126380\"]}" + "result": "{\"data\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"holders\":[{\"rcept_no\":\"20260522000589\",\"rcept_dt\":\"2026-05-22\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,513,080\",\"stkqy_irds\":\"-48,004\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"49,359,000\",\"ctr_stkrt\":\"0.84\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260504000421\",\"rcept_dt\":\"2026-05-04\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,561,084\",\"stkqy_irds\":\"-10,417\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"63,584,000\",\"ctr_stkrt\":\"1.09\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260424000758\",\"rcept_dt\":\"2026-04-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,571,501\",\"stkqy_irds\":\"-8,476\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"67,703,000\",\"ctr_stkrt\":\"1.16\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260417000682\",\"rcept_dt\":\"2026-04-17\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,579,977\",\"stkqy_irds\":\"2,778\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"67,703,000\",\"ctr_stkrt\":\"1.16\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260413002934\",\"rcept_dt\":\"2026-04-13\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,577,199\",\"stkqy_irds\":\"-22,378,550\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.13\",\"ctr_stkqy\":\"80,237,000\",\"ctr_stkrt\":\"1.37\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\\n- ์‹ ํƒ์ฃผ์‹ ์ฒ˜๋ถ„\"},{\"rcept_no\":\"20260130000730\",\"rcept_dt\":\"2026-01-30\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,173,955,749\",\"stkqy_irds\":\"35,644\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"95,237,000\",\"ctr_stkrt\":\"1.61\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260123000544\",\"rcept_dt\":\"2026-01-23\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,173,920,105\",\"stkqy_irds\":\"-187,402\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"103,134,000\",\"ctr_stkrt\":\"1.74\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260116000451\",\"rcept_dt\":\"2026-01-16\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,174,107,507\",\"stkqy_irds\":\"-11,934\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"103,134,000\",\"ctr_stkrt\":\"1.74\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ์œ ๊ฐ€์ฆ๊ถŒ์‹ ํƒ(๋ณด์œ ํ˜•ํƒœ ๋ณ€๊ฒฝ ํฌํ•จ)\"},{\"rcept_no\":\"20260109000680\",\"rcept_dt\":\"2026-01-09\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,174,119,441\",\"stkqy_irds\":\"-386,875\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"-0.01\",\"ctr_stkqy\":\"88,134,000\",\"ctr_stkrt\":\"1.49\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20251107000558\",\"rcept_dt\":\"2025-11-07\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,174,506,316\",\"stkqy_irds\":\"-17,733,231\",\"stkrt\":\"19.84\",\"stkrt_irds\":\"-0.30\",\"ctr_stkqy\":\"86,267,000\",\"ctr_stkrt\":\"1.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\\n- ์‹ ํƒ์ฃผ์‹ ์ฒ˜๋ถ„\"},{\"rcept_no\":\"20251031000575\",\"rcept_dt\":\"2025-10-31\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,239,547\",\"stkqy_irds\":\"-52,021\",\"stkrt\":\"20.14\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"145,670,929\",\"ctr_stkrt\":\"2.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20251024000542\",\"rcept_dt\":\"2025-10-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,291,568\",\"stkqy_irds\":\"-60,260\",\"stkrt\":\"20.14\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"145,670,929\",\"ctr_stkrt\":\"2.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20251017000512\",\"rcept_dt\":\"2025-10-17\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,351,828\",\"stkqy_irds\":\"-168,900\",\"stkrt\":\"20.14\",\"stkrt_irds\":\"-0.01\",\"ctr_stkqy\":\"145,670,929\",\"ctr_stkrt\":\"2.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\\n- ์œ ๊ฐ€์ฆ๊ถŒ์‹ ํƒ(๋ณด์œ ํ˜•ํƒœ ๋ณ€๊ฒฝ ํฌํ•จ)\"},{\"rcept_no\":\"20250919000589\",\"rcept_dt\":\"2025-09-19\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,520,728\",\"stkqy_irds\":\"-45,660\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250905000524\",\"rcept_dt\":\"2025-09-05\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,566,388\",\"stkqy_irds\":\"-77,185\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250814004144\",\"rcept_dt\":\"2025-08-14\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,643,573\",\"stkqy_irds\":\"-92,241\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250801001081\",\"rcept_dt\":\"2025-08-01\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,735,814\",\"stkqy_irds\":\"9,551\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250725000756\",\"rcept_dt\":\"2025-07-25\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,726,263\",\"stkqy_irds\":\"4,560\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250718000569\",\"rcept_dt\":\"2025-07-18\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,721,703\",\"stkqy_irds\":\"-164,817\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250620000604\",\"rcept_dt\":\"2025-06-20\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,886,520\",\"stkqy_irds\":\"-191,832\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250523000612\",\"rcept_dt\":\"2025-05-23\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,078,352\",\"stkqy_irds\":\"-876\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250516000386\",\"rcept_dt\":\"2025-05-16\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,079,228\",\"stkqy_irds\":\"-29,835\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.01\",\"ctr_stkqy\":\"152,224,392\",\"ctr_stkrt\":\"2.57\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250509000826\",\"rcept_dt\":\"2025-05-09\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,109,063\",\"stkqy_irds\":\"-32,842\",\"stkrt\":\"20.16\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"152,224,392\",\"ctr_stkrt\":\"2.57\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250425000921\",\"rcept_dt\":\"2025-04-25\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,141,905\",\"stkqy_irds\":\"-9,066\",\"stkrt\":\"20.16\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250418000638\",\"rcept_dt\":\"2025-04-18\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,150,971\",\"stkqy_irds\":\"144,693\",\"stkrt\":\"20.16\",\"stkrt_irds\":\"0.01\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250321001642\",\"rcept_dt\":\"2025-03-21\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,006,278\",\"stkqy_irds\":\"-10,682\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250307001130\",\"rcept_dt\":\"2025-03-07\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,016,960\",\"stkqy_irds\":\"-77,470\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"0.16\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250214002629\",\"rcept_dt\":\"2025-02-14\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,094,430\",\"stkqy_irds\":\"-4,963,824\",\"stkrt\":\"19.99\",\"stkrt_irds\":\"-0.08\",\"ctr_stkqy\":\"95,423,993\",\"ctr_stkrt\":\"1.60\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250124000900\",\"rcept_dt\":\"2025-01-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,198,080,226\",\"stkqy_irds\":\"-38,791\",\"stkrt\":\"20.07\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"95,423,993\",\"ctr_stkrt\":\"1.60\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250124000908\",\"rcept_dt\":\"2025-01-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,198,058,254\",\"stkqy_irds\":\"-21,972\",\"stkrt\":\"20.07\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"95,423,993\",\"ctr_stkrt\":\"1.60\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20241220000709\",\"rcept_dt\":\"2024-12-20\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,198,119,017\",\"stkqy_irds\":\"-91,303\",\"stkrt\":\"20.07\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"95,423,993\",\"ctr_stkrt\":\"1.60\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20241115000355\",\"rcept_dt\":\"2024-11-15\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,198,210,320\",\"stkqy_irds\":\"-259,744\",\"stkrt\":\"20.07\",\"stkrt_irds\":\"-0.01\",\"ctr_stkqy\":\"95,423,993\",\"ctr_stkrt\":\"1.60\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20241025000537\",\"rcept_dt\":\"2024-10-25\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,198,946,824\",\"stkqy_irds\":\"57,566\",\"stkrt\":\"20.08\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"97,526,980\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20241025000530\",\"rcept_dt\":\"2024-10-25\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,198,889,258\",\"stkqy_irds\":\"6,317\",\"stkrt\":\"20.08\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"97,526,980\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20241025000545\",\"rcept_dt\":\"2024-10-25\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,198,598,417\",\"stkqy_irds\":\"-348,407\",\"stkrt\":\"20.08\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"96,222,980\",\"ctr_stkrt\":\"1.61\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20241025000551\",\"rcept_dt\":\"2024-10-25\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,198,470,064\",\"stkqy_irds\":\"-11,113\",\"stkrt\":\"20.08\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"96,536,993\",\"ctr_stkrt\":\"1.62\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20241025000548\",\"rcept_dt\":\"2024-10-25\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,198,481,177\",\"stkqy_irds\":\"-117,240\",\"stkrt\":\"20.08\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"96,536,993\",\"ctr_stkrt\":\"1.62\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20240726000565\",\"rcept_dt\":\"2024-07-26\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,198,882,941\",\"stkqy_irds\":\"-55,084\",\"stkrt\":\"20.08\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"97,526,980\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20240719000464\",\"rcept_dt\":\"2024-07-19\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,198,938,025\",\"stkqy_irds\":\"-347,788\",\"stkrt\":\"20.08\",\"stkrt_irds\":\"-0.01\",\"ctr_stkqy\":\"97,526,980\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/majorstock.json?corp_code=00126380\"]}" } ] } diff --git a/scripts/kr-eval/fixtures/default/samsung-fundamentals.json b/scripts/kr-eval/fixtures/default/samsung-fundamentals.json index 48d983d54..dc50f1088 100644 --- a/scripts/kr-eval/fixtures/default/samsung-fundamentals.json +++ b/scripts/kr-eval/fixtures/default/samsung-fundamentals.json @@ -8,7 +8,7 @@ { "tool": "get_financials_kr", "args": { - "query": "์‚ผ์„ฑ์ „์ž 005930 ์ตœ๊ทผ ์‹ค์  ๋งค์ถœ์•ก ์˜์—…์ด์ต ์ „๋…„๋™๊ธฐ๋Œ€๋น„ YoY ์ตœ๊ทผ ๋ถ„๊ธฐ K-IFRS ์—ฐ๊ฒฐ ๊ธฐ์ค€" + "query": "์‚ผ์„ฑ์ „์ž 005930 ์ตœ๊ทผ ๋ถ„๊ธฐ ์‹ค์  ๋งค์ถœ์•ก ์˜์—…์ด์ต ์ „๋…„๋™๊ธฐ๋Œ€๋น„ YoY" }, "result": "{\"data\":{\"get_business_report_kr_005930\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_type\":\"quarterly_1\",\"fs_div\":\"CFS\",\"periods\":[{\"bsns_year\":2026,\"report_type\":\"quarterly_1\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"1๋ถ„๊ธฐ ๋ˆ„์ (thstrm=๋‹น๊ธฐ, frmtrm=์ „๋…„ ๋™๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„์€ YTD; BS๋Š” ๋ถ„๊ธฐ๋ง vs ์ „๋…„๋ง.\",\"incomeStatement\":{\"revenue\":{\"current\":133873444000000,\"prior\":79140503000000,\"label\":\"๋งค์ถœ์•ก\",\"display\":\"133.9์กฐ\"},\"operatingProfit\":{\"current\":57232797000000,\"prior\":6685272000000,\"label\":\"์˜์—…์ด์ต\",\"display\":\"57.2์กฐ\"},\"netIncome\":{\"current\":47225272000000,\"prior\":8222878000000,\"label\":\"๋ถ„๊ธฐ์ˆœ์ด์ต\",\"display\":\"47.2์กฐ\"},\"controllingNetIncome\":{\"current\":47101190000000,\"prior\":8028407000000,\"label\":\"์ง€๋ฐฐ๊ธฐ์—… ์†Œ์œ ์ฃผ์ง€๋ถ„\",\"display\":\"47.1์กฐ\"},\"eps\":{\"current\":7123,\"prior\":1192,\"label\":\"๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต(์†์‹ค)\",\"display\":\"7,123์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":633339604000000,\"prior\":566942110000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"633.3์กฐ\"},\"totalLiabilities\":{\"current\":146703628000000,\"prior\":130621773000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"146.7์กฐ\"},\"totalEquity\":{\"current\":486635976000000,\"prior\":436320337000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"486.6์กฐ\"},\"cashAndEquivalents\":{\"current\":73306751000000,\"prior\":57856378000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"73.3์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":40274106000000,\"prior\":16580866000000,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"40.3์กฐ\"},\"investing\":{\"current\":-21456000000000,\"prior\":-6431187000000,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-21.5์กฐ\"},\"financing\":{\"current\":-5455268000000,\"prior\":-11341720000000,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-5.5์กฐ\"},\"capex\":{\"current\":17127003000000,\"prior\":12127934000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"17.1์กฐ\"}},\"ratios\":{\"operatingMarginPct\":42.8,\"netMarginPct\":35.3,\"roePct\":null,\"debtToEquityPct\":30.1,\"revenueYoYPct\":69.2,\"operatingProfitYoYPct\":756.1,\"netIncomeYoYPct\":474.3,\"freeCashFlow\":23147103000000,\"freeCashFlowDisplay\":\"23.1์กฐ\"}}],\"rawLineItemsFile\":\".dexter/tool-results/kr-financials-00126380-11013-CFS-2026_2026.json\",\"note\":\"periods[].summary holds normalized key metrics (KRW). Use read_file on rawLineItemsFile only for accounts not in the summary.\"}},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00126380&bsns_year=2026&reprt_code=11013&fs_div=CFS\"]}" } diff --git a/scripts/kr-eval/fixtures/default/samsung-synthesis.json b/scripts/kr-eval/fixtures/default/samsung-synthesis.json index 8bf729fa4..8aaa44844 100644 --- a/scripts/kr-eval/fixtures/default/samsung-synthesis.json +++ b/scripts/kr-eval/fixtures/default/samsung-synthesis.json @@ -6,11 +6,11 @@ }, "toolCalls": [ { - "tool": "memory_search", + "tool": "get_market_data_kr", "args": { - "query": "์‚ฌ์šฉ์ž์˜ ํˆฌ์ž ๋ชฉํ‘œ ์œ„ํ—˜์„ฑํ–ฅ ํฌํŠธํด๋ฆฌ์˜ค ๋ณด์œ ์ข…๋ชฉ ์‚ผ์„ฑ์ „์ž ํˆฌ์ž ์„ ํ˜ธ ๋งค์ˆ˜ ๋งค๋„ ๊ธฐ์ค€" + "ticker": "005930" }, - "result": "{\"data\":{\"results\":[]}}" + "result": "{\"data\":{\"ticker\":\"005930\",\"name\":\"์‚ผ์„ฑ์ „์ž\",\"quote\":{\"date\":\"2026-06-01\",\"price\":349000,\"change\":32000,\"changePct\":10.09,\"direction\":\"์ƒ์Šน\",\"open\":356000,\"high\":377000,\"low\":342000,\"high52w\":377000,\"low52w\":56800,\"volume\":45052488},\"valuation\":{\"marketCap\":2076890500000000,\"marketCapDisplay\":\"2,076์กฐ 8,905์–ต\",\"sharesOutstanding\":5950975645,\"per\":28.71,\"pbr\":4.94,\"eps\":12372,\"bps\":71907,\"forwardPer\":8.24,\"forwardEps\":43098,\"dividendYieldPct\":0.47,\"dividendPerShare\":1668},\"consensus\":{\"date\":\"2026-06-01\",\"targetPrice\":401250,\"recommendationMean\":4.04,\"upsidePct\":14.97},\"peers\":[{\"ticker\":\"000660\",\"name\":\"SKํ•˜์ด๋‹‰์Šค\",\"price\":2309000,\"changePct\":-2.29,\"marketCap\":1645629760000000},{\"ticker\":\"042700\",\"name\":\"ํ•œ๋ฏธ๋ฐ˜๋„์ฒด\",\"price\":278000,\"changePct\":-5.12,\"marketCap\":26496791000000},{\"ticker\":\"036930\",\"name\":\"์ฃผ์„ฑ์—”์ง€๋‹ˆ์–ด๋ง\",\"price\":194600,\"changePct\":4.91,\"marketCap\":9045226000000},{\"ticker\":\"000990\",\"name\":\"DBํ•˜์ดํ…\",\"price\":170800,\"changePct\":-5.84,\"marketCap\":7430583000000},{\"ticker\":\"058470\",\"name\":\"๋ฆฌ๋…ธ๊ณต์—…\",\"price\":92600,\"changePct\":-4.83,\"marketCap\":7057217000000},{\"ticker\":\"039030\",\"name\":\"์ด์˜คํ…Œํฌ๋‹‰์Šค\",\"price\":437000,\"changePct\":-2.89,\"marketCap\":5383643000000}]},\"sourceUrls\":[\"https://m.stock.naver.com/api/stock/005930/integration\"]}" }, { "tool": "get_foreign_ownership_kr", @@ -18,26 +18,23 @@ "ticker": "005930", "limit": 30 }, - "result": "{\"data\":{\"ticker\":\"005930\",\"ownership\":[{\"date\":\"2026-05-29\",\"foreignHoldRatio\":48.27,\"foreignNetBuyQty\":-1061741,\"orgNetBuyQty\":5314304,\"individualNetBuyQty\":-4237361,\"closePrice\":317000,\"tradingVolume\":32804208},{\"date\":\"2026-05-28\",\"foreignHoldRatio\":48.29,\"foreignNetBuyQty\":-5173125,\"orgNetBuyQty\":-1613562,\"individualNetBuyQty\":6500347,\"closePrice\":299500,\"tradingVolume\":30195334},{\"date\":\"2026-05-27\",\"foreignHoldRatio\":48.39,\"foreignNetBuyQty\":-274173,\"orgNetBuyQty\":1550904,\"individualNetBuyQty\":-1105685,\"closePrice\":307000,\"tradingVolume\":33916688},{\"date\":\"2026-05-26\",\"foreignHoldRatio\":48.37,\"foreignNetBuyQty\":1891734,\"orgNetBuyQty\":1513469,\"individualNetBuyQty\":-3263161,\"closePrice\":299000,\"tradingVolume\":23441371},{\"date\":\"2026-05-22\",\"foreignHoldRatio\":48.32,\"foreignNetBuyQty\":-3419695,\"orgNetBuyQty\":-307631,\"individualNetBuyQty\":3602972,\"closePrice\":292500,\"tradingVolume\":18395274},{\"date\":\"2026-05-21\",\"foreignHoldRatio\":48.39,\"foreignNetBuyQty\":3672423,\"orgNetBuyQty\":2608486,\"individualNetBuyQty\":-5931378,\"closePrice\":299500,\"tradingVolume\":36168689},{\"date\":\"2026-05-20\",\"foreignHoldRatio\":48.33,\"foreignNetBuyQty\":-5733354,\"orgNetBuyQty\":2756787,\"individualNetBuyQty\":2765915,\"closePrice\":276000,\"tradingVolume\":35662077},{\"date\":\"2026-05-19\",\"foreignHoldRatio\":48.42,\"foreignNetBuyQty\":-9263047,\"orgNetBuyQty\":1418906,\"individualNetBuyQty\":7818601,\"closePrice\":275500,\"tradingVolume\":30767569},{\"date\":\"2026-05-18\",\"foreignHoldRatio\":48.61,\"foreignNetBuyQty\":-4486093,\"orgNetBuyQty\":2803397,\"individualNetBuyQty\":1689597,\"closePrice\":281000,\"tradingVolume\":33555214},{\"date\":\"2026-05-15\",\"foreignHoldRatio\":48.69,\"foreignNetBuyQty\":-8855741,\"orgNetBuyQty\":-1529944,\"individualNetBuyQty\":10257346,\"closePrice\":270500,\"tradingVolume\":38075487}]},\"sourceUrls\":[\"https://m.stock.naver.com/api/stock/005930/trend\"]}" + "result": "{\"data\":{\"ticker\":\"005930\",\"ownership\":[{\"date\":\"2026-06-01\",\"foreignHoldRatio\":48.3,\"foreignNetBuyQty\":-180865,\"orgNetBuyQty\":3832695,\"individualNetBuyQty\":-3173072,\"closePrice\":349000,\"tradingVolume\":45052488},{\"date\":\"2026-05-29\",\"foreignHoldRatio\":48.29,\"foreignNetBuyQty\":-1061741,\"orgNetBuyQty\":5314304,\"individualNetBuyQty\":-4237361,\"closePrice\":317000,\"tradingVolume\":37241537},{\"date\":\"2026-05-28\",\"foreignHoldRatio\":48.29,\"foreignNetBuyQty\":-5173125,\"orgNetBuyQty\":-1613562,\"individualNetBuyQty\":6500347,\"closePrice\":299500,\"tradingVolume\":30195334},{\"date\":\"2026-05-27\",\"foreignHoldRatio\":48.39,\"foreignNetBuyQty\":-274173,\"orgNetBuyQty\":1550904,\"individualNetBuyQty\":-1105685,\"closePrice\":307000,\"tradingVolume\":33916688},{\"date\":\"2026-05-26\",\"foreignHoldRatio\":48.37,\"foreignNetBuyQty\":1891734,\"orgNetBuyQty\":1513469,\"individualNetBuyQty\":-3263161,\"closePrice\":299000,\"tradingVolume\":23441371},{\"date\":\"2026-05-22\",\"foreignHoldRatio\":48.32,\"foreignNetBuyQty\":-3419695,\"orgNetBuyQty\":-307631,\"individualNetBuyQty\":3602972,\"closePrice\":292500,\"tradingVolume\":18395274},{\"date\":\"2026-05-21\",\"foreignHoldRatio\":48.39,\"foreignNetBuyQty\":3672423,\"orgNetBuyQty\":2608486,\"individualNetBuyQty\":-5931378,\"closePrice\":299500,\"tradingVolume\":36168689},{\"date\":\"2026-05-20\",\"foreignHoldRatio\":48.33,\"foreignNetBuyQty\":-5733354,\"orgNetBuyQty\":2756787,\"individualNetBuyQty\":2765915,\"closePrice\":276000,\"tradingVolume\":35662077},{\"date\":\"2026-05-19\",\"foreignHoldRatio\":48.42,\"foreignNetBuyQty\":-9263047,\"orgNetBuyQty\":1418906,\"individualNetBuyQty\":7818601,\"closePrice\":275500,\"tradingVolume\":30767569},{\"date\":\"2026-05-18\",\"foreignHoldRatio\":48.61,\"foreignNetBuyQty\":-4486093,\"orgNetBuyQty\":2803397,\"individualNetBuyQty\":1689597,\"closePrice\":281000,\"tradingVolume\":33555214}]},\"sourceUrls\":[\"https://m.stock.naver.com/api/stock/005930/trend\"]}" }, { "tool": "get_large_holders_kr", "args": { "ticker": "005930", - "limit": 30 + "limit": 20 }, - "result": "{\"data\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"holders\":[{\"rcept_no\":\"20260522000589\",\"rcept_dt\":\"2026-05-22\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,513,080\",\"stkqy_irds\":\"-48,004\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"49,359,000\",\"ctr_stkrt\":\"0.84\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260504000421\",\"rcept_dt\":\"2026-05-04\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,561,084\",\"stkqy_irds\":\"-10,417\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"63,584,000\",\"ctr_stkrt\":\"1.09\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260424000758\",\"rcept_dt\":\"2026-04-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,571,501\",\"stkqy_irds\":\"-8,476\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"67,703,000\",\"ctr_stkrt\":\"1.16\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260417000682\",\"rcept_dt\":\"2026-04-17\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,579,977\",\"stkqy_irds\":\"2,778\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"67,703,000\",\"ctr_stkrt\":\"1.16\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260413002934\",\"rcept_dt\":\"2026-04-13\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,577,199\",\"stkqy_irds\":\"-22,378,550\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.13\",\"ctr_stkqy\":\"80,237,000\",\"ctr_stkrt\":\"1.37\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\\n- ์‹ ํƒ์ฃผ์‹ ์ฒ˜๋ถ„\"},{\"rcept_no\":\"20260130000730\",\"rcept_dt\":\"2026-01-30\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,173,955,749\",\"stkqy_irds\":\"35,644\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"95,237,000\",\"ctr_stkrt\":\"1.61\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260123000544\",\"rcept_dt\":\"2026-01-23\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,173,920,105\",\"stkqy_irds\":\"-187,402\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"103,134,000\",\"ctr_stkrt\":\"1.74\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260116000451\",\"rcept_dt\":\"2026-01-16\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,174,107,507\",\"stkqy_irds\":\"-11,934\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"103,134,000\",\"ctr_stkrt\":\"1.74\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ์œ ๊ฐ€์ฆ๊ถŒ์‹ ํƒ(๋ณด์œ ํ˜•ํƒœ ๋ณ€๊ฒฝ ํฌํ•จ)\"},{\"rcept_no\":\"20260109000680\",\"rcept_dt\":\"2026-01-09\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,174,119,441\",\"stkqy_irds\":\"-386,875\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"-0.01\",\"ctr_stkqy\":\"88,134,000\",\"ctr_stkrt\":\"1.49\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20251107000558\",\"rcept_dt\":\"2025-11-07\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,174,506,316\",\"stkqy_irds\":\"-17,733,231\",\"stkrt\":\"19.84\",\"stkrt_irds\":\"-0.30\",\"ctr_stkqy\":\"86,267,000\",\"ctr_stkrt\":\"1.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\\n- ์‹ ํƒ์ฃผ์‹ ์ฒ˜๋ถ„\"},{\"rcept_no\":\"20251031000575\",\"rcept_dt\":\"2025-10-31\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,239,547\",\"stkqy_irds\":\"-52,021\",\"stkrt\":\"20.14\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"145,670,929\",\"ctr_stkrt\":\"2.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20251024000542\",\"rcept_dt\":\"2025-10-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,291,568\",\"stkqy_irds\":\"-60,260\",\"stkrt\":\"20.14\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"145,670,929\",\"ctr_stkrt\":\"2.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20251017000512\",\"rcept_dt\":\"2025-10-17\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,351,828\",\"stkqy_irds\":\"-168,900\",\"stkrt\":\"20.14\",\"stkrt_irds\":\"-0.01\",\"ctr_stkqy\":\"145,670,929\",\"ctr_stkrt\":\"2.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\\n- ์œ ๊ฐ€์ฆ๊ถŒ์‹ ํƒ(๋ณด์œ ํ˜•ํƒœ ๋ณ€๊ฒฝ ํฌํ•จ)\"},{\"rcept_no\":\"20250919000589\",\"rcept_dt\":\"2025-09-19\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,520,728\",\"stkqy_irds\":\"-45,660\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250905000524\",\"rcept_dt\":\"2025-09-05\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,566,388\",\"stkqy_irds\":\"-77,185\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250814004144\",\"rcept_dt\":\"2025-08-14\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,643,573\",\"stkqy_irds\":\"-92,241\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250801001081\",\"rcept_dt\":\"2025-08-01\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,735,814\",\"stkqy_irds\":\"9,551\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250725000756\",\"rcept_dt\":\"2025-07-25\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,726,263\",\"stkqy_irds\":\"4,560\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250718000569\",\"rcept_dt\":\"2025-07-18\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,721,703\",\"stkqy_irds\":\"-164,817\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250620000604\",\"rcept_dt\":\"2025-06-20\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,886,520\",\"stkqy_irds\":\"-191,832\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250523000612\",\"rcept_dt\":\"2025-05-23\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,078,352\",\"stkqy_irds\":\"-876\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250516000386\",\"rcept_dt\":\"2025-05-16\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,079,228\",\"stkqy_irds\":\"-29,835\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.01\",\"ctr_stkqy\":\"152,224,392\",\"ctr_stkrt\":\"2.57\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250509000826\",\"rcept_dt\":\"2025-05-09\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,109,063\",\"stkqy_irds\":\"-32,842\",\"stkrt\":\"20.16\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"152,224,392\",\"ctr_stkrt\":\"2.57\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250425000921\",\"rcept_dt\":\"2025-04-25\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,141,905\",\"stkqy_irds\":\"-9,066\",\"stkrt\":\"20.16\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250418000638\",\"rcept_dt\":\"2025-04-18\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,150,971\",\"stkqy_irds\":\"144,693\",\"stkrt\":\"20.16\",\"stkrt_irds\":\"0.01\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250321001642\",\"rcept_dt\":\"2025-03-21\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,006,278\",\"stkqy_irds\":\"-10,682\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250307001130\",\"rcept_dt\":\"2025-03-07\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,016,960\",\"stkqy_irds\":\"-77,470\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"0.16\",\"ctr_stkqy\":\"96,199,993\",\"ctr_stkrt\":\"1.63\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250214002629\",\"rcept_dt\":\"2025-02-14\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,193,094,430\",\"stkqy_irds\":\"-4,963,824\",\"stkrt\":\"19.99\",\"stkrt_irds\":\"-0.08\",\"ctr_stkqy\":\"95,423,993\",\"ctr_stkrt\":\"1.60\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250124000900\",\"rcept_dt\":\"2025-01-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,198,080,226\",\"stkqy_irds\":\"-38,791\",\"stkrt\":\"20.07\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"95,423,993\",\"ctr_stkrt\":\"1.60\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250124000908\",\"rcept_dt\":\"2025-01-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,198,058,254\",\"stkqy_irds\":\"-21,972\",\"stkrt\":\"20.07\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"95,423,993\",\"ctr_stkrt\":\"1.60\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/majorstock.json?corp_code=00126380\"]}" + "result": "{\"data\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"holders\":[{\"rcept_no\":\"20260522000589\",\"rcept_dt\":\"2026-05-22\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,513,080\",\"stkqy_irds\":\"-48,004\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"49,359,000\",\"ctr_stkrt\":\"0.84\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260504000421\",\"rcept_dt\":\"2026-05-04\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,561,084\",\"stkqy_irds\":\"-10,417\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"63,584,000\",\"ctr_stkrt\":\"1.09\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260424000758\",\"rcept_dt\":\"2026-04-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,571,501\",\"stkqy_irds\":\"-8,476\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"67,703,000\",\"ctr_stkrt\":\"1.16\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260417000682\",\"rcept_dt\":\"2026-04-17\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,579,977\",\"stkqy_irds\":\"2,778\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"67,703,000\",\"ctr_stkrt\":\"1.16\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260413002934\",\"rcept_dt\":\"2026-04-13\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,151,577,199\",\"stkqy_irds\":\"-22,378,550\",\"stkrt\":\"19.70\",\"stkrt_irds\":\"-0.13\",\"ctr_stkqy\":\"80,237,000\",\"ctr_stkrt\":\"1.37\",\"report_resn\":\"- ํŠน๋ณ„๊ด€๊ณ„์ž ๋ฐ ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\\n- ์‹ ํƒ์ฃผ์‹ ์ฒ˜๋ถ„\"},{\"rcept_no\":\"20260130000730\",\"rcept_dt\":\"2026-01-30\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,173,955,749\",\"stkqy_irds\":\"35,644\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"95,237,000\",\"ctr_stkrt\":\"1.61\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260123000544\",\"rcept_dt\":\"2026-01-23\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,173,920,105\",\"stkqy_irds\":\"-187,402\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"103,134,000\",\"ctr_stkrt\":\"1.74\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20260116000451\",\"rcept_dt\":\"2026-01-16\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,174,107,507\",\"stkqy_irds\":\"-11,934\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"103,134,000\",\"ctr_stkrt\":\"1.74\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ์œ ๊ฐ€์ฆ๊ถŒ์‹ ํƒ(๋ณด์œ ํ˜•ํƒœ ๋ณ€๊ฒฝ ํฌํ•จ)\"},{\"rcept_no\":\"20260109000680\",\"rcept_dt\":\"2026-01-09\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,174,119,441\",\"stkqy_irds\":\"-386,875\",\"stkrt\":\"19.83\",\"stkrt_irds\":\"-0.01\",\"ctr_stkqy\":\"88,134,000\",\"ctr_stkrt\":\"1.49\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20251107000558\",\"rcept_dt\":\"2025-11-07\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,174,506,316\",\"stkqy_irds\":\"-17,733,231\",\"stkrt\":\"19.84\",\"stkrt_irds\":\"-0.30\",\"ctr_stkqy\":\"86,267,000\",\"ctr_stkrt\":\"1.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\\n- ์‹ ํƒ์ฃผ์‹ ์ฒ˜๋ถ„\"},{\"rcept_no\":\"20251031000575\",\"rcept_dt\":\"2025-10-31\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,239,547\",\"stkqy_irds\":\"-52,021\",\"stkrt\":\"20.14\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"145,670,929\",\"ctr_stkrt\":\"2.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20251024000542\",\"rcept_dt\":\"2025-10-24\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,291,568\",\"stkqy_irds\":\"-60,260\",\"stkrt\":\"20.14\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"145,670,929\",\"ctr_stkrt\":\"2.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20251017000512\",\"rcept_dt\":\"2025-10-17\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,351,828\",\"stkqy_irds\":\"-168,900\",\"stkrt\":\"20.14\",\"stkrt_irds\":\"-0.01\",\"ctr_stkqy\":\"145,670,929\",\"ctr_stkrt\":\"2.46\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\\n- ์œ ๊ฐ€์ฆ๊ถŒ์‹ ํƒ(๋ณด์œ ํ˜•ํƒœ ๋ณ€๊ฒฝ ํฌํ•จ)\"},{\"rcept_no\":\"20250919000589\",\"rcept_dt\":\"2025-09-19\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,520,728\",\"stkqy_irds\":\"-45,660\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250905000524\",\"rcept_dt\":\"2025-09-05\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,566,388\",\"stkqy_irds\":\"-77,185\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250814004144\",\"rcept_dt\":\"2025-08-14\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,643,573\",\"stkqy_irds\":\"-92,241\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250801001081\",\"rcept_dt\":\"2025-08-01\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,735,814\",\"stkqy_irds\":\"9,551\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"127,096,929\",\"ctr_stkrt\":\"2.15\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250725000756\",\"rcept_dt\":\"2025-07-25\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,726,263\",\"stkqy_irds\":\"4,560\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250718000569\",\"rcept_dt\":\"2025-07-18\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,721,703\",\"stkqy_irds\":\"-164,817\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20250620000604\",\"rcept_dt\":\"2025-06-20\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"stkqy\":\"1,192,886,520\",\"stkqy_irds\":\"-191,832\",\"stkrt\":\"20.15\",\"stkrt_irds\":\"-0.00\",\"ctr_stkqy\":\"135,378,929\",\"ctr_stkrt\":\"2.29\",\"report_resn\":\"- ๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\\n- ๋ณด์œ ์ฃผ์‹๋“ฑ์— ๊ด€ํ•œ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/majorstock.json?corp_code=00126380\"]}" }, { - "tool": "get_filings_kr", + "tool": "get_short_balance_kr", "args": { "ticker": "005930", - "filing_type": "material", - "limit": 20, - "start_date": "2025-01-01", - "end_date": "2026-05-31" + "limit": 30 }, - "result": "{\"data\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"filings\":[{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ฒ˜๋ถ„๊ฒฐ์ •)\",\"rcept_no\":\"20260318001203\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20260318\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ทจ๋“๊ฒฐ์ •)\",\"rcept_no\":\"20260318001062\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20260318\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ฒ˜๋ถ„๊ฒฐ์ •)\",\"rcept_no\":\"20260129000004\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20260129\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ทจ๋“๊ฒฐ์ •)\",\"rcept_no\":\"20260129000003\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20260129\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ฒ˜๋ถ„๊ฒฐ์ •)\",\"rcept_no\":\"20260126000001\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20260126\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ทจ๋“๊ฒฐ์ •)\",\"rcept_no\":\"20260107000715\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20260107\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ฒ˜๋ถ„๊ฒฐ์ •)\",\"rcept_no\":\"20251030000502\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20251030\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ฒ˜๋ถ„๊ฒฐ์ •)\",\"rcept_no\":\"20250725000794\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20250725\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ทจ๋“๊ฒฐ์ •)\",\"rcept_no\":\"20250708000011\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20250708\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ฒ˜๋ถ„๊ฒฐ์ •)\",\"rcept_no\":\"20250523000601\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20250523\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"[๊ธฐ์žฌ์ •์ •]์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ทจ๋“๊ฒฐ์ •)\",\"rcept_no\":\"20250218001596\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20250218\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ทจ๋“๊ฒฐ์ •)\",\"rcept_no\":\"20250218000019\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20250218\",\"rm\":\"์ •\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(ํ•ด์™ธ์ฆ๊ถŒ์‹œ์žฅ์ฃผ๊ถŒ๋“ฑ์ƒ์žฅ)\",\"rcept_no\":\"20250213001817\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20250213\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(ํ•ด์™ธ์ฆ๊ถŒ์‹œ์žฅ์ฃผ๊ถŒ๋“ฑ์ƒ์žฅํ์ง€)\",\"rcept_no\":\"20250213001808\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20250213\",\"rm\":\"\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/list.json?corp_code=00126380&bgn_de=20250101&end_de=20260531&pblntf_ty=B&page_count=20&page_no=1&sort=date&sort_mth=desc\"]}" + "result": "{\"data\":{\"ticker\":\"005930\",\"isin\":\"KR7005930003\",\"name\":\"์‚ผ์„ฑ์ „์ž\",\"short\":[{\"date\":\"2026-05-28\",\"balanceQty\":0,\"listedShares\":5846278608,\"balanceAmount\":0,\"marketCap\":1750960443096000,\"balanceRatio\":0},{\"date\":\"2026-05-27\",\"balanceQty\":0,\"listedShares\":5846278608,\"balanceAmount\":0,\"marketCap\":1794807532656000,\"balanceRatio\":0},{\"date\":\"2026-05-26\",\"balanceQty\":0,\"listedShares\":5846278608,\"balanceAmount\":0,\"marketCap\":1748037303792000,\"balanceRatio\":0},{\"date\":\"2026-05-22\",\"balanceQty\":0,\"listedShares\":5846278608,\"balanceAmount\":0,\"marketCap\":1710036492840000,\"balanceRatio\":0},{\"date\":\"2026-05-21\",\"balanceQty\":0,\"listedShares\":5846278608,\"balanceAmount\":0,\"marketCap\":1750960443096000,\"balanceRatio\":0},{\"date\":\"2026-05-20\",\"balanceQty\":94881,\"listedShares\":5846278608,\"balanceAmount\":26187156000,\"marketCap\":1613572895808000,\"balanceRatio\":0},{\"date\":\"2026-05-19\",\"balanceQty\":179226,\"listedShares\":5846278608,\"balanceAmount\":49376763000,\"marketCap\":1610649756504000,\"balanceRatio\":0},{\"date\":\"2026-05-18\",\"balanceQty\":221813,\"listedShares\":5846278608,\"balanceAmount\":62329453000,\"marketCap\":1642804288848000,\"balanceRatio\":0},{\"date\":\"2026-05-15\",\"balanceQty\":243518,\"listedShares\":5846278608,\"balanceAmount\":65871619000,\"marketCap\":1581418363464000,\"balanceRatio\":0},{\"date\":\"2026-05-14\",\"balanceQty\":316122,\"listedShares\":5846278608,\"balanceAmount\":93572112000,\"marketCap\":1730498467968000,\"balanceRatio\":0.01},{\"date\":\"2026-05-13\",\"balanceQty\":296168,\"listedShares\":5846278608,\"balanceAmount\":84111712000,\"marketCap\":1660343124672000,\"balanceRatio\":0.01},{\"date\":\"2026-05-12\",\"balanceQty\":163909,\"listedShares\":5846278608,\"balanceAmount\":45730611000,\"marketCap\":1631111731632000,\"balanceRatio\":0},{\"date\":\"2026-05-11\",\"balanceQty\":240481,\"listedShares\":5846278608,\"balanceAmount\":68657325500,\"marketCap\":1669112542584000,\"balanceRatio\":0},{\"date\":\"2026-05-08\",\"balanceQty\":289206,\"listedShares\":5846278608,\"balanceAmount\":77651811000,\"marketCap\":1569725806248000,\"balanceRatio\":0},{\"date\":\"2026-05-07\",\"balanceQty\":198717,\"listedShares\":5846278608,\"balanceAmount\":53951665500,\"marketCap\":1587264642072000,\"balanceRatio\":0},{\"date\":\"2026-05-06\",\"balanceQty\":146558,\"listedShares\":5846278608,\"balanceAmount\":38984428000,\"marketCap\":1555110109728000,\"balanceRatio\":0},{\"date\":\"2026-05-04\",\"balanceQty\":26128,\"listedShares\":5846278608,\"balanceAmount\":6074760000,\"marketCap\":1359259776360000,\"balanceRatio\":0}]},\"sourceUrls\":[\"https://data.krx.co.kr/comm/bldAttendant/getJsonData.cmd?bld=dbms%2FMDC%2FSTAT%2Fsrt%2FMDCSTAT30502\"]}" }, { "tool": "get_nps_holdings", @@ -53,20 +50,29 @@ "ticker": "005930", "limit": 20 }, - "result": "{\"data\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"trades\":[{\"rcept_no\":\"20260529001627\",\"rcept_dt\":\"2026-05-29\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"๊น€๊ฒฝ์„\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"237\",\"sp_stock_lmp_irds_cnt\":\"1\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260526000244\",\"rcept_dt\":\"2026-05-26\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์กฐ๋ฏธ์„ \",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"3,409\",\"sp_stock_lmp_irds_cnt\":\"120\",\"sp_stock_lmp_rate\":\"0\",\"sp_stock_lmp_irds_rate\":\"0\"},{\"rcept_no\":\"20260526000141\",\"rcept_dt\":\"2026-05-26\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์‹ ์šฉ์šฐ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"836\",\"sp_stock_lmp_irds_cnt\":\"50\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260522000164\",\"rcept_dt\":\"2026-05-22\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์–€์†ํ—จ๋ฆญ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"1,437\",\"sp_stock_lmp_irds_cnt\":\"500\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260522000036\",\"rcept_dt\":\"2026-05-22\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์–€์†ํ—จ๋ฆญ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"937\",\"sp_stock_lmp_irds_cnt\":\"600\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.0\"},{\"rcept_no\":\"20260513000081\",\"rcept_dt\":\"2026-05-13\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์‹ ์Šน์ฒ \",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"๋ถ€์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"2,105\",\"sp_stock_lmp_irds_cnt\":\"-2,200\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260511000085\",\"rcept_dt\":\"2026-05-11\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์ด๊ทœ์ฒ \",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"243\",\"sp_stock_lmp_irds_cnt\":\"0\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260508000866\",\"rcept_dt\":\"2026-05-08\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์ด๋™๊ฑด\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"๋ถ€์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"45\",\"sp_stock_lmp_irds_cnt\":\"45\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260429000319\",\"rcept_dt\":\"2026-04-29\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"ํ•˜์ง€ํ›ˆ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"1,726\",\"sp_stock_lmp_irds_cnt\":\"52\",\"sp_stock_lmp_rate\":\"0\",\"sp_stock_lmp_irds_rate\":\"0\"},{\"rcept_no\":\"20260423000164\",\"rcept_dt\":\"2026-04-23\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์ž„์ง€์šด\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"2,164\",\"sp_stock_lmp_irds_cnt\":\"230\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260417000440\",\"rcept_dt\":\"2026-04-17\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"๊น€๋ฏผ์šฐ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"1,430\",\"sp_stock_lmp_irds_cnt\":\"-550\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260406003839\",\"rcept_dt\":\"2026-04-06\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์•„ํฌํ…Œ์ปค๋งˆ๋ ์œ \",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"1,555\",\"sp_stock_lmp_irds_cnt\":\"-631\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260403003368\",\"rcept_dt\":\"2026-04-03\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์—ฐ์ง€ํ˜„\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"2,678\",\"sp_stock_lmp_irds_cnt\":\"173\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260331000460\",\"rcept_dt\":\"2026-03-31\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์†ก๊ธฐ์žฌ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"3,415\",\"sp_stock_lmp_irds_cnt\":\"397\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260330000333\",\"rcept_dt\":\"2026-03-30\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"๊น€๋ณดํ˜„\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"๋ถ€์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"4,466\",\"sp_stock_lmp_irds_cnt\":\"164\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260330000506\",\"rcept_dt\":\"2026-03-30\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์กฐ์„ฑํฌ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"3,000\",\"sp_stock_lmp_irds_cnt\":\"2,910\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260319001393\",\"rcept_dt\":\"2026-03-20\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์ด์Šน๋ฏผ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"166\",\"sp_stock_lmp_irds_cnt\":\"166\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260317000158\",\"rcept_dt\":\"2026-03-17\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์กฐ์˜์ค€\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"๋ถ€์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"9,460\",\"sp_stock_lmp_irds_cnt\":\"2,885\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260313000315\",\"rcept_dt\":\"2026-03-13\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์˜ค๋ฌธ์šฑ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"๋ถ€์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"25,000\",\"sp_stock_lmp_irds_cnt\":\"500\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260313001248\",\"rcept_dt\":\"2026-03-13\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์†ก์žฌํ˜\",\"isu_exctv_rgist_at\":\"๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"17,100\",\"sp_stock_lmp_irds_cnt\":\"20\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/elestock.json?corp_code=00126380\"]}" + "result": "{\"data\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"trades\":[{\"rcept_no\":\"20260602000150\",\"rcept_dt\":\"2026-06-02\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์ตœ์ธ์ˆ˜\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"1,178\",\"sp_stock_lmp_irds_cnt\":\"1,178\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260601000001\",\"rcept_dt\":\"2026-06-01\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"ํ•˜์ง€ํ›ˆ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"1,736\",\"sp_stock_lmp_irds_cnt\":\"10\",\"sp_stock_lmp_rate\":\"0\",\"sp_stock_lmp_irds_rate\":\"0\"},{\"rcept_no\":\"20260529001627\",\"rcept_dt\":\"2026-05-29\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"๊น€๊ฒฝ์„\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"237\",\"sp_stock_lmp_irds_cnt\":\"1\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260526000244\",\"rcept_dt\":\"2026-05-26\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์กฐ๋ฏธ์„ \",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"3,409\",\"sp_stock_lmp_irds_cnt\":\"120\",\"sp_stock_lmp_rate\":\"0\",\"sp_stock_lmp_irds_rate\":\"0\"},{\"rcept_no\":\"20260526000141\",\"rcept_dt\":\"2026-05-26\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์‹ ์šฉ์šฐ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"836\",\"sp_stock_lmp_irds_cnt\":\"50\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260522000164\",\"rcept_dt\":\"2026-05-22\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์–€์†ํ—จ๋ฆญ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"1,437\",\"sp_stock_lmp_irds_cnt\":\"500\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260522000036\",\"rcept_dt\":\"2026-05-22\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์–€์†ํ—จ๋ฆญ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"937\",\"sp_stock_lmp_irds_cnt\":\"600\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.0\"},{\"rcept_no\":\"20260513000081\",\"rcept_dt\":\"2026-05-13\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์‹ ์Šน์ฒ \",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"๋ถ€์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"2,105\",\"sp_stock_lmp_irds_cnt\":\"-2,200\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260511000085\",\"rcept_dt\":\"2026-05-11\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์ด๊ทœ์ฒ \",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"243\",\"sp_stock_lmp_irds_cnt\":\"0\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260508000866\",\"rcept_dt\":\"2026-05-08\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์ด๋™๊ฑด\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"๋ถ€์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"45\",\"sp_stock_lmp_irds_cnt\":\"45\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260429000319\",\"rcept_dt\":\"2026-04-29\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"ํ•˜์ง€ํ›ˆ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"1,726\",\"sp_stock_lmp_irds_cnt\":\"52\",\"sp_stock_lmp_rate\":\"0\",\"sp_stock_lmp_irds_rate\":\"0\"},{\"rcept_no\":\"20260423000164\",\"rcept_dt\":\"2026-04-23\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์ž„์ง€์šด\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"2,164\",\"sp_stock_lmp_irds_cnt\":\"230\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260417000440\",\"rcept_dt\":\"2026-04-17\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"๊น€๋ฏผ์šฐ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"1,430\",\"sp_stock_lmp_irds_cnt\":\"-550\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260406003839\",\"rcept_dt\":\"2026-04-06\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์•„ํฌํ…Œ์ปค๋งˆ๋ ์œ \",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"1,555\",\"sp_stock_lmp_irds_cnt\":\"-631\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260403003368\",\"rcept_dt\":\"2026-04-03\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์—ฐ์ง€ํ˜„\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"2,678\",\"sp_stock_lmp_irds_cnt\":\"173\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260331000460\",\"rcept_dt\":\"2026-03-31\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์†ก๊ธฐ์žฌ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"3,415\",\"sp_stock_lmp_irds_cnt\":\"397\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260330000333\",\"rcept_dt\":\"2026-03-30\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"๊น€๋ณดํ˜„\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"๋ถ€์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"4,466\",\"sp_stock_lmp_irds_cnt\":\"164\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260330000506\",\"rcept_dt\":\"2026-03-30\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์กฐ์„ฑํฌ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"3,000\",\"sp_stock_lmp_irds_cnt\":\"2,910\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260319001393\",\"rcept_dt\":\"2026-03-20\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์ด์Šน๋ฏผ\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"์ƒ๋ฌด\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"166\",\"sp_stock_lmp_irds_cnt\":\"166\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"},{\"rcept_no\":\"20260317000158\",\"rcept_dt\":\"2026-03-17\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"repror\":\"์กฐ์˜์ค€\",\"isu_exctv_rgist_at\":\"๋น„๋“ฑ๊ธฐ์ž„์›\",\"isu_exctv_ofcps\":\"๋ถ€์‚ฌ์žฅ\",\"isu_main_shrholdr\":\"-\",\"sp_stock_lmp_cnt\":\"9,460\",\"sp_stock_lmp_irds_cnt\":\"2,885\",\"sp_stock_lmp_rate\":\"0.00\",\"sp_stock_lmp_irds_rate\":\"0.00\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/elestock.json?corp_code=00126380\"]}" }, { - "tool": "get_short_balance_kr", + "tool": "get_filings_kr", "args": { "ticker": "005930", - "limit": 30 + "limit": 20, + "start_date": "2025-01-01", + "end_date": "2026-06-02" + }, + "result": "{\"data\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"filings\":[{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260602000150\",\"flr_nm\":\"์ตœ์ธ์ˆ˜\",\"rcept_dt\":\"20260602\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"๋Œ€๊ทœ๋ชจ๊ธฐ์—…์ง‘๋‹จํ˜„ํ™ฉ๊ณต์‹œ[์—ฐ1ํšŒ(๋™์ผ์ธ์šฉ)]\",\"rcept_no\":\"20260601001179\",\"flr_nm\":\"์ด์žฌ์šฉ\",\"rcept_dt\":\"20260601\",\"rm\":\"๊ณต\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"๊ธฐ์—…์ง€๋ฐฐ๊ตฌ์กฐ๋ณด๊ณ ์„œ๊ณต์‹œ \",\"rcept_no\":\"20260601800268\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20260601\",\"rm\":\"์œ \"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"๋Œ€๊ทœ๋ชจ๊ธฐ์—…์ง‘๋‹จํ˜„ํ™ฉ๊ณต์‹œ[์—ฐ1ํšŒ๊ณต์‹œ๋ฐ1/4๋ถ„๊ธฐ์šฉ(๋Œ€ํ‘œํšŒ์‚ฌ)]\",\"rcept_no\":\"20260601000172\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20260601\",\"rm\":\"๊ณต\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260601000001\",\"flr_nm\":\"ํ•˜์ง€ํ›ˆ\",\"rcept_dt\":\"20260601\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260529001627\",\"flr_nm\":\"๊น€๊ฒฝ์„\",\"rcept_dt\":\"20260529\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"[๊ธฐ์žฌ์ •์ •]์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260526000244\",\"flr_nm\":\"์กฐ๋ฏธ์„ \",\"rcept_dt\":\"20260526\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260526000141\",\"flr_nm\":\"์‹ ์šฉ์šฐ\",\"rcept_dt\":\"20260526\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์‹๋“ฑ์˜๋Œ€๋Ÿ‰๋ณด์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ(์ผ๋ฐ˜)\",\"rcept_no\":\"20260522000589\",\"flr_nm\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"rcept_dt\":\"20260522\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260522000164\",\"flr_nm\":\"์–€์†ํ—จ๋ฆญ\",\"rcept_dt\":\"20260522\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"[๊ธฐ์žฌ์ •์ •]์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260522000036\",\"flr_nm\":\"์–€์†ํ—จ๋ฆญ\",\"rcept_dt\":\"20260522\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"๋™์ผ์ธ๋“ฑ์ถœ์ž๊ณ„์—ดํšŒ์‚ฌ์™€์˜์ƒํ’ˆใ†์šฉ์—ญ๊ฑฐ๋ž˜๋ณ€๊ฒฝ\",\"rcept_no\":\"20260515002812\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20260515\",\"rm\":\"๊ณต\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"๋™์ผ์ธ๋“ฑ์ถœ์ž๊ณ„์—ดํšŒ์‚ฌ์™€์˜์ƒํ’ˆใ†์šฉ์—ญ๊ฑฐ๋ž˜๋ณ€๊ฒฝ\",\"rcept_no\":\"20260515002790\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20260515\",\"rm\":\"๊ณต\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"๋™์ผ์ธ๋“ฑ์ถœ์ž๊ณ„์—ดํšŒ์‚ฌ์™€์˜์ƒํ’ˆใ†์šฉ์—ญ๊ฑฐ๋ž˜๋ณ€๊ฒฝ\",\"rcept_no\":\"20260515002770\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20260515\",\"rm\":\"๊ณต\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"๋ถ„๊ธฐ๋ณด๊ณ ์„œ (2026.03)\",\"rcept_no\":\"20260515002181\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20260515\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260513000081\",\"flr_nm\":\"์‹ ์Šน์ฒ \",\"rcept_dt\":\"20260513\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260511000085\",\"flr_nm\":\"์ด๊ทœ์ฒ \",\"rcept_dt\":\"20260511\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ตœ๋Œ€์ฃผ์ฃผ๋“ฑ์†Œ์œ ์ฃผ์‹๋ณ€๋™์‹ ๊ณ ์„œ \",\"rcept_no\":\"20260508801105\",\"flr_nm\":\"์‚ผ์„ฑ์ „์ž\",\"rcept_dt\":\"20260508\",\"rm\":\"์œ \"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ž„์›ใ†์ฃผ์š”์ฃผ์ฃผํŠน์ •์ฆ๊ถŒ๋“ฑ์†Œ์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20260508000866\",\"flr_nm\":\"์ด๋™๊ฑด\",\"rcept_dt\":\"20260508\",\"rm\":\"\"},{\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"stock_code\":\"005930\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์‹๋“ฑ์˜๋Œ€๋Ÿ‰๋ณด์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ(์ผ๋ฐ˜)\",\"rcept_no\":\"20260504000421\",\"flr_nm\":\"์‚ผ์„ฑ๋ฌผ์‚ฐ\",\"rcept_dt\":\"20260504\",\"rm\":\"\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/list.json?corp_code=00126380&bgn_de=20250101&end_de=20260602&page_count=20&page_no=1&sort=date&sort_mth=desc\"]}" + }, + { + "tool": "memory_search", + "args": { + "query": "์‚ฌ์šฉ์ž ํˆฌ์ž ๋ชฉํ‘œ ์œ„ํ—˜์„ฑํ–ฅ ํฌ์ง€์…˜ ์ œํ•œ ์‚ผ์„ฑ์ „์ž ํ•œ๊ตญ์ฃผ์‹ ์„ ํ˜ธ" }, - "result": "{\"data\":{\"ticker\":\"005930\",\"isin\":\"KR7005930003\",\"name\":\"์‚ผ์„ฑ์ „์ž\",\"short\":[{\"date\":\"2026-05-27\",\"balanceQty\":0,\"listedShares\":5846278608,\"balanceAmount\":0,\"marketCap\":1794807532656000,\"balanceRatio\":0},{\"date\":\"2026-05-26\",\"balanceQty\":0,\"listedShares\":5846278608,\"balanceAmount\":0,\"marketCap\":1748037303792000,\"balanceRatio\":0},{\"date\":\"2026-05-22\",\"balanceQty\":0,\"listedShares\":5846278608,\"balanceAmount\":0,\"marketCap\":1710036492840000,\"balanceRatio\":0},{\"date\":\"2026-05-21\",\"balanceQty\":0,\"listedShares\":5846278608,\"balanceAmount\":0,\"marketCap\":1750960443096000,\"balanceRatio\":0},{\"date\":\"2026-05-20\",\"balanceQty\":94881,\"listedShares\":5846278608,\"balanceAmount\":26187156000,\"marketCap\":1613572895808000,\"balanceRatio\":0},{\"date\":\"2026-05-19\",\"balanceQty\":179226,\"listedShares\":5846278608,\"balanceAmount\":49376763000,\"marketCap\":1610649756504000,\"balanceRatio\":0},{\"date\":\"2026-05-18\",\"balanceQty\":221813,\"listedShares\":5846278608,\"balanceAmount\":62329453000,\"marketCap\":1642804288848000,\"balanceRatio\":0},{\"date\":\"2026-05-15\",\"balanceQty\":243518,\"listedShares\":5846278608,\"balanceAmount\":65871619000,\"marketCap\":1581418363464000,\"balanceRatio\":0},{\"date\":\"2026-05-14\",\"balanceQty\":316122,\"listedShares\":5846278608,\"balanceAmount\":93572112000,\"marketCap\":1730498467968000,\"balanceRatio\":0.01},{\"date\":\"2026-05-13\",\"balanceQty\":296168,\"listedShares\":5846278608,\"balanceAmount\":84111712000,\"marketCap\":1660343124672000,\"balanceRatio\":0.01},{\"date\":\"2026-05-12\",\"balanceQty\":163909,\"listedShares\":5846278608,\"balanceAmount\":45730611000,\"marketCap\":1631111731632000,\"balanceRatio\":0},{\"date\":\"2026-05-11\",\"balanceQty\":240481,\"listedShares\":5846278608,\"balanceAmount\":68657325500,\"marketCap\":1669112542584000,\"balanceRatio\":0},{\"date\":\"2026-05-08\",\"balanceQty\":289206,\"listedShares\":5846278608,\"balanceAmount\":77651811000,\"marketCap\":1569725806248000,\"balanceRatio\":0},{\"date\":\"2026-05-07\",\"balanceQty\":198717,\"listedShares\":5846278608,\"balanceAmount\":53951665500,\"marketCap\":1587264642072000,\"balanceRatio\":0},{\"date\":\"2026-05-06\",\"balanceQty\":146558,\"listedShares\":5846278608,\"balanceAmount\":38984428000,\"marketCap\":1555110109728000,\"balanceRatio\":0},{\"date\":\"2026-05-04\",\"balanceQty\":26128,\"listedShares\":5846278608,\"balanceAmount\":6074760000,\"marketCap\":1359259776360000,\"balanceRatio\":0}]},\"sourceUrls\":[\"https://data.krx.co.kr/comm/bldAttendant/getJsonData.cmd?bld=dbms%2FMDC%2FSTAT%2Fsrt%2FMDCSTAT30502\"]}" + "result": "{\"data\":{\"results\":[]}}" }, { "tool": "get_financials_kr", "args": { - "query": "์‚ผ์„ฑ์ „์ž 005930 ์ตœ๊ทผ 5๋…„ ์—ฐ๊ฐ„ ๋ฐ ์ตœ๊ทผ ๋ถ„๊ธฐ K-IFRS ๋งค์ถœ ์˜์—…์ด์ต ์ˆœ์ด์ต ์˜์—…์ด์ต๋ฅ  ์ˆœ์ด์ต๋ฅ  ROE ๋ถ€์ฑ„๋น„์œจ ํ˜„๊ธˆํ๋ฆ„ ์š”์•ฝ YoY ํฌํ•จ" + "query": "005930 ์‚ผ์„ฑ์ „์ž ์ตœ๊ทผ 5๋…„ ์—ฐ๊ฐ„ ๋ฐ ์ตœ๊ทผ ๋ถ„๊ธฐ K-IFRS ๋งค์ถœ ์˜์—…์ด์ต ์ˆœ์ด์ต ์˜์—…์ด์ต๋ฅ  ์ˆœ์ด์ต๋ฅ  ROE ์š”์•ฝ YoY" }, "result": "{\"data\":{\"get_business_report_kr_005930\":{\"ticker\":\"005930\",\"corp_code\":\"00126380\",\"corp_name\":\"์‚ผ์„ฑ์ „์ž\",\"report_type\":\"quarterly_1\",\"fs_div\":\"CFS\",\"periods\":[{\"bsns_year\":2026,\"report_type\":\"quarterly_1\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"1๋ถ„๊ธฐ ๋ˆ„์ (thstrm=๋‹น๊ธฐ, frmtrm=์ „๋…„ ๋™๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„์€ YTD; BS๋Š” ๋ถ„๊ธฐ๋ง vs ์ „๋…„๋ง.\",\"incomeStatement\":{\"revenue\":{\"current\":133873444000000,\"prior\":79140503000000,\"label\":\"๋งค์ถœ์•ก\",\"display\":\"133.9์กฐ\"},\"operatingProfit\":{\"current\":57232797000000,\"prior\":6685272000000,\"label\":\"์˜์—…์ด์ต\",\"display\":\"57.2์กฐ\"},\"netIncome\":{\"current\":47225272000000,\"prior\":8222878000000,\"label\":\"๋ถ„๊ธฐ์ˆœ์ด์ต\",\"display\":\"47.2์กฐ\"},\"controllingNetIncome\":{\"current\":47101190000000,\"prior\":8028407000000,\"label\":\"์ง€๋ฐฐ๊ธฐ์—… ์†Œ์œ ์ฃผ์ง€๋ถ„\",\"display\":\"47.1์กฐ\"},\"eps\":{\"current\":7123,\"prior\":1192,\"label\":\"๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต(์†์‹ค)\",\"display\":\"7,123์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":633339604000000,\"prior\":566942110000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"633.3์กฐ\"},\"totalLiabilities\":{\"current\":146703628000000,\"prior\":130621773000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"146.7์กฐ\"},\"totalEquity\":{\"current\":486635976000000,\"prior\":436320337000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"486.6์กฐ\"},\"cashAndEquivalents\":{\"current\":73306751000000,\"prior\":57856378000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"73.3์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":40274106000000,\"prior\":16580866000000,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"40.3์กฐ\"},\"investing\":{\"current\":-21456000000000,\"prior\":-6431187000000,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-21.5์กฐ\"},\"financing\":{\"current\":-5455268000000,\"prior\":-11341720000000,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-5.5์กฐ\"},\"capex\":{\"current\":17127003000000,\"prior\":12127934000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"17.1์กฐ\"}},\"ratios\":{\"operatingMarginPct\":42.8,\"netMarginPct\":35.3,\"roePct\":null,\"debtToEquityPct\":30.1,\"revenueYoYPct\":69.2,\"operatingProfitYoYPct\":756.1,\"netIncomeYoYPct\":474.3,\"freeCashFlow\":23147103000000,\"freeCashFlowDisplay\":\"23.1์กฐ\"}}],\"rawLineItemsFile\":\".dexter/tool-results/kr-financials-00126380-11013-CFS-2026_2026.json\",\"note\":\"periods[].summary holds normalized key metrics (KRW). Use read_file on rawLineItemsFile only for accounts not in the summary.\"}},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00126380&bsns_year=2025&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00126380&bsns_year=2024&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00126380&bsns_year=2023&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00126380&bsns_year=2022&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00126380&bsns_year=2021&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00126380&bsns_year=2026&reprt_code=11013&fs_div=CFS\"]}" } diff --git a/scripts/kr-eval/fixtures/default/short-balance-ecopro.json b/scripts/kr-eval/fixtures/default/short-balance-ecopro.json index b976a2899..08b4b5f9d 100644 --- a/scripts/kr-eval/fixtures/default/short-balance-ecopro.json +++ b/scripts/kr-eval/fixtures/default/short-balance-ecopro.json @@ -11,7 +11,7 @@ "ticker": "247540", "limit": 20 }, - "result": "{\"data\":{\"ticker\":\"247540\",\"isin\":\"KR7247540008\",\"name\":\"์—์ฝ”ํ”„๋กœ๋น„์— \",\"short\":[{\"date\":\"2026-05-27\",\"balanceQty\":2384672,\"listedShares\":97830434,\"balanceAmount\":509127472000,\"marketCap\":20886797659000,\"balanceRatio\":2.44},{\"date\":\"2026-05-26\",\"balanceQty\":2479889,\"listedShares\":97830434,\"balanceAmount\":545575580000,\"marketCap\":21522695480000,\"balanceRatio\":2.53},{\"date\":\"2026-05-22\",\"balanceQty\":2606871,\"listedShares\":97830434,\"balanceAmount\":563084136000,\"marketCap\":21131373744000,\"balanceRatio\":2.66},{\"date\":\"2026-05-21\",\"balanceQty\":2734333,\"listedShares\":97830434,\"balanceAmount\":533194935000,\"marketCap\":19076934630000,\"balanceRatio\":2.79},{\"date\":\"2026-05-20\",\"balanceQty\":2763541,\"listedShares\":97830434,\"balanceAmount\":488317694700,\"marketCap\":17286637687800,\"balanceRatio\":2.82},{\"date\":\"2026-05-19\",\"balanceQty\":2799134,\"listedShares\":97830434,\"balanceAmount\":510562041600,\"marketCap\":17844271161600,\"balanceRatio\":2.86},{\"date\":\"2026-05-18\",\"balanceQty\":2727216,\"listedShares\":97830434,\"balanceAmount\":519261926400,\"marketCap\":18626914633600,\"balanceRatio\":2.79},{\"date\":\"2026-05-15\",\"balanceQty\":2694595,\"listedShares\":97830434,\"balanceAmount\":513320347500,\"marketCap\":18636697677000,\"balanceRatio\":2.75},{\"date\":\"2026-05-14\",\"balanceQty\":2663111,\"listedShares\":97830434,\"balanceAmount\":556590199000,\"marketCap\":20446560706000,\"balanceRatio\":2.72},{\"date\":\"2026-05-13\",\"balanceQty\":2617061,\"listedShares\":97830434,\"balanceAmount\":515822723100,\"marketCap\":19282378541400,\"balanceRatio\":2.68},{\"date\":\"2026-05-12\",\"balanceQty\":2527091,\"listedShares\":97830434,\"balanceAmount\":519317200500,\"marketCap\":20104154187000,\"balanceRatio\":2.58},{\"date\":\"2026-05-11\",\"balanceQty\":2467434,\"listedShares\":97830434,\"balanceAmount\":547770348000,\"marketCap\":21718356348000,\"balanceRatio\":2.52},{\"date\":\"2026-05-08\",\"balanceQty\":2381594,\"listedShares\":97830434,\"balanceAmount\":565628575000,\"marketCap\":23234728075000,\"balanceRatio\":2.43},{\"date\":\"2026-05-07\",\"balanceQty\":2468960,\"listedShares\":97830434,\"balanceAmount\":581440080000,\"marketCap\":23039067207000,\"balanceRatio\":2.52},{\"date\":\"2026-05-06\",\"balanceQty\":2590202,\"listedShares\":97830434,\"balanceAmount\":591861157000,\"marketCap\":22354254169000,\"balanceRatio\":2.65},{\"date\":\"2026-05-04\",\"balanceQty\":2669587,\"listedShares\":97830434,\"balanceAmount\":575295998500,\"marketCap\":21082458527000,\"balanceRatio\":2.73}]},\"sourceUrls\":[\"https://data.krx.co.kr/comm/bldAttendant/getJsonData.cmd?bld=dbms%2FMDC%2FSTAT%2Fsrt%2FMDCSTAT30502\"]}" + "result": "{\"data\":{\"ticker\":\"247540\",\"isin\":\"KR7247540008\",\"name\":\"์—์ฝ”ํ”„๋กœ๋น„์— \",\"short\":[{\"date\":\"2026-05-28\",\"balanceQty\":2292824,\"listedShares\":97830434,\"balanceAmount\":500982044000,\"marketCap\":21375949829000,\"balanceRatio\":2.34},{\"date\":\"2026-05-27\",\"balanceQty\":2384672,\"listedShares\":97830434,\"balanceAmount\":509127472000,\"marketCap\":20886797659000,\"balanceRatio\":2.44},{\"date\":\"2026-05-26\",\"balanceQty\":2479889,\"listedShares\":97830434,\"balanceAmount\":545575580000,\"marketCap\":21522695480000,\"balanceRatio\":2.53},{\"date\":\"2026-05-22\",\"balanceQty\":2606871,\"listedShares\":97830434,\"balanceAmount\":563084136000,\"marketCap\":21131373744000,\"balanceRatio\":2.66},{\"date\":\"2026-05-21\",\"balanceQty\":2734333,\"listedShares\":97830434,\"balanceAmount\":533194935000,\"marketCap\":19076934630000,\"balanceRatio\":2.79},{\"date\":\"2026-05-20\",\"balanceQty\":2763541,\"listedShares\":97830434,\"balanceAmount\":488317694700,\"marketCap\":17286637687800,\"balanceRatio\":2.82},{\"date\":\"2026-05-19\",\"balanceQty\":2799134,\"listedShares\":97830434,\"balanceAmount\":510562041600,\"marketCap\":17844271161600,\"balanceRatio\":2.86},{\"date\":\"2026-05-18\",\"balanceQty\":2727216,\"listedShares\":97830434,\"balanceAmount\":519261926400,\"marketCap\":18626914633600,\"balanceRatio\":2.79},{\"date\":\"2026-05-15\",\"balanceQty\":2694595,\"listedShares\":97830434,\"balanceAmount\":513320347500,\"marketCap\":18636697677000,\"balanceRatio\":2.75},{\"date\":\"2026-05-14\",\"balanceQty\":2663111,\"listedShares\":97830434,\"balanceAmount\":556590199000,\"marketCap\":20446560706000,\"balanceRatio\":2.72},{\"date\":\"2026-05-13\",\"balanceQty\":2617061,\"listedShares\":97830434,\"balanceAmount\":515822723100,\"marketCap\":19282378541400,\"balanceRatio\":2.68},{\"date\":\"2026-05-12\",\"balanceQty\":2527091,\"listedShares\":97830434,\"balanceAmount\":519317200500,\"marketCap\":20104154187000,\"balanceRatio\":2.58},{\"date\":\"2026-05-11\",\"balanceQty\":2467434,\"listedShares\":97830434,\"balanceAmount\":547770348000,\"marketCap\":21718356348000,\"balanceRatio\":2.52},{\"date\":\"2026-05-08\",\"balanceQty\":2381594,\"listedShares\":97830434,\"balanceAmount\":565628575000,\"marketCap\":23234728075000,\"balanceRatio\":2.43},{\"date\":\"2026-05-07\",\"balanceQty\":2468960,\"listedShares\":97830434,\"balanceAmount\":581440080000,\"marketCap\":23039067207000,\"balanceRatio\":2.52},{\"date\":\"2026-05-06\",\"balanceQty\":2590202,\"listedShares\":97830434,\"balanceAmount\":591861157000,\"marketCap\":22354254169000,\"balanceRatio\":2.65},{\"date\":\"2026-05-04\",\"balanceQty\":2669587,\"listedShares\":97830434,\"balanceAmount\":575295998500,\"marketCap\":21082458527000,\"balanceRatio\":2.73}]},\"sourceUrls\":[\"https://data.krx.co.kr/comm/bldAttendant/getJsonData.cmd?bld=dbms%2FMDC%2FSTAT%2Fsrt%2FMDCSTAT30502\"]}" } ] } diff --git a/scripts/kr-eval/fixtures/default/spinoff-lgchem.json b/scripts/kr-eval/fixtures/default/spinoff-lgchem.json index 89d509842..b51849c5f 100644 --- a/scripts/kr-eval/fixtures/default/spinoff-lgchem.json +++ b/scripts/kr-eval/fixtures/default/spinoff-lgchem.json @@ -16,42 +16,31 @@ { "tool": "get_filings_kr", "args": { - "ticker": "051910", - "filing_type": "material", - "start_date": "2020-01-01", + "ticker": "373220", + "filing_type": "issuance", + "start_date": "2021-01-01", "end_date": "2022-12-31", "limit": 50 }, - "result": "{\"data\":{\"ticker\":\"051910\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"filings\":[{\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"stock_code\":\"051910\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ฒ˜๋ถ„๊ฒฐ์ •)\",\"rcept_no\":\"20221123000333\",\"flr_nm\":\"LGํ™”ํ•™\",\"rcept_dt\":\"20221123\",\"rm\":\"\"},{\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"stock_code\":\"051910\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ฒ˜๋ถ„๊ฒฐ์ •)\",\"rcept_no\":\"20211022000367\",\"flr_nm\":\"LGํ™”ํ•™\",\"rcept_dt\":\"20211022\",\"rm\":\"\"},{\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"stock_code\":\"051910\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(ํšŒ์‚ฌ๋ถ„ํ• ๊ฒฐ์ •)\",\"rcept_no\":\"20200917000127\",\"flr_nm\":\"LGํ™”ํ•™\",\"rcept_dt\":\"20200917\",\"rm\":\"\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/list.json?corp_code=00356361&bgn_de=20200101&end_de=20221231&pblntf_ty=B&page_count=50&page_no=1&sort=date&sort_mth=desc\"]}" + "result": "{\"data\":{\"ticker\":\"373220\",\"corp_code\":\"01515323\",\"corp_name\":\"LG์—๋„ˆ์ง€์†”๋ฃจ์…˜\",\"filings\":[{\"corp_code\":\"01515323\",\"corp_name\":\"LG์—๋„ˆ์ง€์†”๋ฃจ์…˜\",\"stock_code\":\"373220\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฆ๊ถŒ๋ฐœํ–‰์‹ค์ ๋ณด๊ณ ์„œ\",\"rcept_no\":\"20220121000496\",\"flr_nm\":\"LG์—๋„ˆ์ง€์†”๋ฃจ์…˜\",\"rcept_dt\":\"20220121\",\"rm\":\"\"},{\"corp_code\":\"01515323\",\"corp_name\":\"LG์—๋„ˆ์ง€์†”๋ฃจ์…˜\",\"stock_code\":\"373220\",\"corp_cls\":\"Y\",\"report_nm\":\"[๊ธฐ์žฌ์ •์ •]ํˆฌ์ž์„ค๋ช…์„œ\",\"rcept_no\":\"20220114000408\",\"flr_nm\":\"LG์—๋„ˆ์ง€์†”๋ฃจ์…˜\",\"rcept_dt\":\"20220114\",\"rm\":\"\"},{\"corp_code\":\"01515323\",\"corp_name\":\"LG์—๋„ˆ์ง€์†”๋ฃจ์…˜\",\"stock_code\":\"373220\",\"corp_cls\":\"Y\",\"report_nm\":\"[๋ฐœํ–‰์กฐ๊ฑดํ™•์ •]์ฆ๊ถŒ์‹ ๊ณ ์„œ(์ง€๋ถ„์ฆ๊ถŒ)\",\"rcept_no\":\"20220114000401\",\"flr_nm\":\"LG์—๋„ˆ์ง€์†”๋ฃจ์…˜\",\"rcept_dt\":\"20220114\",\"rm\":\"\"},{\"corp_code\":\"01515323\",\"corp_name\":\"LG์—๋„ˆ์ง€์†”๋ฃจ์…˜\",\"stock_code\":\"373220\",\"corp_cls\":\"Y\",\"report_nm\":\"ํˆฌ์ž์„ค๋ช…์„œ\",\"rcept_no\":\"20211229000030\",\"flr_nm\":\"LG์—๋„ˆ์ง€์†”๋ฃจ์…˜\",\"rcept_dt\":\"20211229\",\"rm\":\"์ •\"},{\"corp_code\":\"01515323\",\"corp_name\":\"LG์—๋„ˆ์ง€์†”๋ฃจ์…˜\",\"stock_code\":\"373220\",\"corp_cls\":\"Y\",\"report_nm\":\"[๊ธฐ์žฌ์ •์ •]์ฆ๊ถŒ์‹ ๊ณ ์„œ(์ง€๋ถ„์ฆ๊ถŒ)\",\"rcept_no\":\"20211224000319\",\"flr_nm\":\"LG์—๋„ˆ์ง€์†”๋ฃจ์…˜\",\"rcept_dt\":\"20211224\",\"rm\":\"์ •\"},{\"corp_code\":\"01515323\",\"corp_name\":\"LG์—๋„ˆ์ง€์†”๋ฃจ์…˜\",\"stock_code\":\"373220\",\"corp_cls\":\"Y\",\"report_nm\":\"[๊ธฐ์žฌ์ •์ •]์ฆ๊ถŒ์‹ ๊ณ ์„œ(์ง€๋ถ„์ฆ๊ถŒ)\",\"rcept_no\":\"20211208000440\",\"flr_nm\":\"LG์—๋„ˆ์ง€์†”๋ฃจ์…˜\",\"rcept_dt\":\"20211208\",\"rm\":\"์ •\"},{\"corp_code\":\"01515323\",\"corp_name\":\"LG์—๋„ˆ์ง€์†”๋ฃจ์…˜\",\"stock_code\":\"373220\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฆ๊ถŒ์‹ ๊ณ ์„œ(์ง€๋ถ„์ฆ๊ถŒ)\",\"rcept_no\":\"20211207000476\",\"flr_nm\":\"LG์—๋„ˆ์ง€์†”๋ฃจ์…˜\",\"rcept_dt\":\"20211207\",\"rm\":\"์ •\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/list.json?corp_code=01515323&bgn_de=20210101&end_de=20221231&pblntf_ty=C&page_count=50&page_no=1&sort=date&sort_mth=desc\"]}" }, { - "tool": "get_large_holders_kr", + "tool": "get_filings_kr", "args": { "ticker": "051910", - "limit": 20 - }, - "result": "{\"data\":{\"ticker\":\"051910\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"holders\":[{\"rcept_no\":\"20260305001433\",\"rcept_dt\":\"2026-03-05\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"report_tp\":\"์•ฝ์‹\",\"repror\":\"BlackRockFundAdvisors\",\"stkqy\":\"3,539,585\",\"stkqy_irds\":\"57,870\",\"stkrt\":\"5.01\",\"stkrt_irds\":\"0.08\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"- ๋‹จ์ˆœํˆฌ์ž๋ชฉ์ ์œผ๋กœ ์žฅ๋‚ด์—์„œ ๋ฐœํ–‰ํšŒ์‚ฌ์˜ ์ฃผ์‹ ๋งค์ˆ˜\"},{\"rcept_no\":\"20250401002135\",\"rcept_dt\":\"2025-04-01\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"report_tp\":\"์•ฝ์‹\",\"repror\":\"๊ตญ๋ฏผ์—ฐ๊ธˆ๊ณต๋‹จ\",\"stkqy\":\"6,096,881\",\"stkqy_irds\":\"735,235\",\"stkrt\":\"8.64\",\"stkrt_irds\":\"1.04\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"๋‹จ์ˆœ์ถ”๊ฐ€์ทจ๋“/์ฒ˜๋ถ„\"},{\"rcept_no\":\"20250307000710\",\"rcept_dt\":\"2025-03-07\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"LG\",\"stkqy\":\"24,691,077\",\"stkqy_irds\":\"642,241\",\"stkrt\":\"34.98\",\"stkrt_irds\":\"0.91\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\"},{\"rcept_no\":\"20241129001948\",\"rcept_dt\":\"2024-11-29\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"report_tp\":\"์ผ๋ฐ˜\",\"repror\":\"LG\",\"stkqy\":\"24,048,836\",\"stkqy_irds\":\"493,879\",\"stkrt\":\"34.07\",\"stkrt_irds\":\"0.7\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"๋ณด์œ ์ฃผ์‹์ˆ˜ ๋ณ€๋™\"},{\"rcept_no\":\"20240819000186\",\"rcept_dt\":\"2024-08-19\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"report_tp\":\"์•ฝ์‹\",\"repror\":\"๊ตญ๋ฏผ์—ฐ๊ธˆ๊ณต๋‹จ\",\"stkqy\":\"5,361,646\",\"stkqy_irds\":\"16,737\",\"stkrt\":\"7.60\",\"stkrt_irds\":\"0.03\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"๋‹จ์ˆœํˆฌ์ž๋ชฉ์ ์—์„œ ์ผ๋ฐ˜ํˆฌ์ž๋ชฉ์ ์œผ๋กœ ๋ณด์œ ๋ชฉ์  ๋ณ€๊ฒฝ\"},{\"rcept_no\":\"20240805000259\",\"rcept_dt\":\"2024-08-05\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"report_tp\":\"์•ฝ์‹\",\"repror\":\"๊ตญ๋ฏผ์—ฐ๊ธˆ๊ณต๋‹จ\",\"stkqy\":\"5,344,909\",\"stkqy_irds\":\"149,763\",\"stkrt\":\"7.57\",\"stkrt_irds\":\"0.21\",\"ctr_stkqy\":\"-\",\"ctr_stkrt\":\"-\",\"report_resn\":\"์ผ๋ฐ˜ํˆฌ์ž๋ชฉ์ ์—์„œ ๋‹จ์ˆœํˆฌ์ž๋ชฉ์ ์œผ๋กœ ๋ณด์œ ๋ชฉ์  ๋ณ€๊ฒฝ\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/majorstock.json?corp_code=00356361\"]}" - }, - { - "tool": "get_financials_kr", - "args": { - "query": "LGํ™”ํ•™ 051910 2019 2020 2021 2022 2023 ์—ฐ๊ฒฐ ๋ฐ ๋ณ„๋„ ์žฌ๋ฌด์ œํ‘œ ๋งค์ถœ ์˜์—…์ด์ต ์ˆœ์ด์ต ์ž์‚ฐ ์ž๋ณธ ์š”์•ฝ" + "filing_type": "material", + "start_date": "2020-01-01", + "end_date": "2022-12-31", + "limit": 50 }, - "result": "{\"data\":{\"get_business_report_kr_051910\":{\"ticker\":\"051910\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"report_type\":\"annual\",\"fs_div\":\"OFS\",\"periods\":[{\"bsns_year\":2023,\"report_type\":\"annual\",\"fs_div\":\"OFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":19947377000000,\"prior\":22637157000000,\"label\":\"๋งค์ถœ\",\"display\":\"19.9์กฐ\"},\"operatingProfit\":{\"current\":-109149000000,\"prior\":1052276000000,\"label\":\"์˜์—…์ด์ต\",\"display\":\"-1,091์–ต\"},\"netIncome\":{\"current\":228126000000,\"prior\":3006969000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต\",\"display\":\"2,281์–ต\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":2910,\"prior\":38573,\"label\":\"๋ณดํ†ต์ฃผ ๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต\",\"display\":\"2,910์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":34093584000000,\"prior\":32872824000000,\"label\":\"์ž์‚ฐ ์ด๊ณ„\",\"display\":\"34.1์กฐ\"},\"totalLiabilities\":{\"current\":13688372000000,\"prior\":11799396000000,\"label\":\"๋ถ€์ฑ„ ์ด๊ณ„\",\"display\":\"13.7์กฐ\"},\"totalEquity\":{\"current\":20405212000000,\"prior\":21073428000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"20.4์กฐ\"},\"cashAndEquivalents\":{\"current\":2096074000000,\"prior\":1323209000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"2.1์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":2104000000000,\"prior\":1387508000000,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"2.1์กฐ\"},\"investing\":{\"current\":-2912170000000,\"prior\":61821000000,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-2.9์กฐ\"},\"financing\":{\"current\":1587984000000,\"prior\":-1185101000000,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"1.6์กฐ\"},\"capex\":{\"current\":2371302000000,\"prior\":1732944000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"2.4์กฐ\"}},\"ratios\":{\"operatingMarginPct\":-0.5,\"netMarginPct\":1.1,\"roePct\":1.1,\"debtToEquityPct\":67.1,\"revenueYoYPct\":-11.9,\"operatingProfitYoYPct\":-110.4,\"netIncomeYoYPct\":-92.4,\"freeCashFlow\":-267302000000,\"freeCashFlowDisplay\":\"-2,673์–ต\"}},{\"bsns_year\":2022,\"report_type\":\"annual\",\"fs_div\":\"OFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":23403601000000,\"prior\":20471044000000,\"label\":\"๋งค์ถœ\",\"display\":\"23.4์กฐ\"},\"operatingProfit\":{\"current\":1070050000000,\"prior\":3019160000000,\"label\":\"์˜์—…์ด์ต\",\"display\":\"1.1์กฐ\"},\"netIncome\":{\"current\":3006969000000,\"prior\":2514263000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"3.0์กฐ\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":32872824000000,\"prior\":30929152000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"32.9์กฐ\"},\"totalLiabilities\":{\"current\":11799396000000,\"prior\":12144275000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"11.8์กฐ\"},\"totalEquity\":{\"current\":21073428000000,\"prior\":18784877000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"21.1์กฐ\"},\"cashAndEquivalents\":{\"current\":1323209000000,\"prior\":1056665000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"1.3์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":1387508000000,\"prior\":4041768000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"1.4์กฐ\"},\"investing\":{\"current\":61821000000,\"prior\":-3055452000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"618์–ต\"},\"financing\":{\"current\":-1185101000000,\"prior\":-790329000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-1.2์กฐ\"},\"capex\":{\"current\":-1732944000000,\"prior\":-1990446000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-1.7์กฐ\"}},\"ratios\":{\"operatingMarginPct\":4.6,\"netMarginPct\":12.8,\"roePct\":14.3,\"debtToEquityPct\":56,\"revenueYoYPct\":14.3,\"operatingProfitYoYPct\":-64.6,\"netIncomeYoYPct\":19.6,\"freeCashFlow\":-345436000000,\"freeCashFlowDisplay\":\"-3,454์–ต\"}},{\"bsns_year\":2021,\"report_type\":\"annual\",\"fs_div\":\"OFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":20471044000000,\"prior\":20830009000000,\"label\":\"๋งค์ถœ\",\"display\":\"20.5์กฐ\"},\"operatingProfit\":{\"current\":3019160000000,\"prior\":1114437000000,\"label\":\"์˜์—…์ด์ต (์†์‹ค)\",\"display\":\"3.0์กฐ\"},\"netIncome\":{\"current\":2514263000000,\"prior\":-98223000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"2.5์กฐ\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":30929152000000,\"prior\":25850150000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"30.9์กฐ\"},\"totalLiabilities\":{\"current\":12144275000000,\"prior\":8886645000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"12.1์กฐ\"},\"totalEquity\":{\"current\":18784877000000,\"prior\":16963505000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"18.8์กฐ\"},\"cashAndEquivalents\":{\"current\":1056665000000,\"prior\":852668000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"1.1์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":4041768000000,\"prior\":5236679000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"4.0์กฐ\"},\"investing\":{\"current\":-3055452000000,\"prior\":-4391344000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-3.1์กฐ\"},\"financing\":{\"current\":-790329000000,\"prior\":-989022000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-7,903์–ต\"},\"capex\":{\"current\":-1990446000000,\"prior\":-2187196000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-2.0์กฐ\"}},\"ratios\":{\"operatingMarginPct\":14.7,\"netMarginPct\":12.3,\"roePct\":13.4,\"debtToEquityPct\":64.6,\"revenueYoYPct\":-1.7,\"operatingProfitYoYPct\":170.9,\"netIncomeYoYPct\":2659.7,\"freeCashFlow\":2051322000000,\"freeCashFlowDisplay\":\"2.1์กฐ\"}},{\"bsns_year\":2020,\"report_type\":\"annual\",\"fs_div\":\"OFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":20830009000000,\"prior\":21809592000000,\"label\":\"๋งค์ถœ\",\"display\":\"20.8์กฐ\"},\"operatingProfit\":{\"current\":1114437000000,\"prior\":624165000000,\"label\":\"์˜์—…์ด์ต (์†์‹ค)\",\"display\":\"1.1์กฐ\"},\"netIncome\":{\"current\":-98223000000,\"prior\":365924000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"-982์–ต\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":25850150000000,\"prior\":27027086000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"25.9์กฐ\"},\"totalLiabilities\":{\"current\":8886645000000,\"prior\":10566856000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"8.9์กฐ\"},\"totalEquity\":{\"current\":16963505000000,\"prior\":16460230000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"17.0์กฐ\"},\"cashAndEquivalents\":{\"current\":852668000000,\"prior\":1002263000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"8,527์–ต\"}},\"cashFlow\":{\"operating\":{\"current\":5236679000000,\"prior\":1943151000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"5.2์กฐ\"},\"investing\":{\"current\":-4391344000000,\"prior\":-4862288000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-4.4์กฐ\"},\"financing\":{\"current\":-989022000000,\"prior\":2240321000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-9,890์–ต\"},\"capex\":{\"current\":-2187196000000,\"prior\":-2800070000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-2.2์กฐ\"}},\"ratios\":{\"operatingMarginPct\":5.4,\"netMarginPct\":-0.5,\"roePct\":-0.6,\"debtToEquityPct\":52.4,\"revenueYoYPct\":-4.5,\"operatingProfitYoYPct\":78.5,\"netIncomeYoYPct\":-126.8,\"freeCashFlow\":3049483000000,\"freeCashFlowDisplay\":\"3.0์กฐ\"}},{\"bsns_year\":2019,\"report_type\":\"annual\",\"fs_div\":\"OFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":22312561000000,\"prior\":23315318000000,\"label\":\"๋งค์ถœ\",\"display\":\"22.3์กฐ\"},\"operatingProfit\":{\"current\":582425000000,\"prior\":1953618000000,\"label\":\"์˜์—…์ด์ต (์†์‹ค)\",\"display\":\"5,824์–ต\"},\"netIncome\":{\"current\":365924000000,\"prior\":1581064000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"3,659์–ต\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":27027086000000,\"prior\":23730578000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"27.0์กฐ\"},\"totalLiabilities\":{\"current\":10566856000000,\"prior\":7180672000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"10.6์กฐ\"},\"totalEquity\":{\"current\":16460230000000,\"prior\":16549906000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"16.5์กฐ\"},\"cashAndEquivalents\":{\"current\":1002263000000,\"prior\":1690862000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"1.0์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":1943151000000,\"prior\":2024847000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"1.9์กฐ\"},\"investing\":{\"current\":-4862288000000,\"prior\":-3017036000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-4.9์กฐ\"},\"financing\":{\"current\":2240321000000,\"prior\":1046184000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"2.2์กฐ\"},\"capex\":{\"current\":-2800070000000,\"prior\":-2515595000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-2.8์กฐ\"}},\"ratios\":{\"operatingMarginPct\":2.6,\"netMarginPct\":1.6,\"roePct\":2.2,\"debtToEquityPct\":64.2,\"revenueYoYPct\":-4.3,\"operatingProfitYoYPct\":-70.2,\"netIncomeYoYPct\":-76.9,\"freeCashFlow\":-856919000000,\"freeCashFlowDisplay\":\"-8,569์–ต\"}}],\"rawLineItemsFile\":\".dexter/tool-results/kr-financials-00356361-11011-OFS-2019_2023.json\",\"note\":\"periods[].summary holds normalized key metrics (KRW). Use read_file on rawLineItemsFile only for accounts not in the summary.\"}},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2023&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2022&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2021&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2020&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2019&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2023&reprt_code=11011&fs_div=OFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2022&reprt_code=11011&fs_div=OFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2021&reprt_code=11011&fs_div=OFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2020&reprt_code=11011&fs_div=OFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2019&reprt_code=11011&fs_div=OFS\"]}" + "result": "{\"data\":{\"ticker\":\"051910\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"filings\":[{\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"stock_code\":\"051910\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ฒ˜๋ถ„๊ฒฐ์ •)\",\"rcept_no\":\"20221123000333\",\"flr_nm\":\"LGํ™”ํ•™\",\"rcept_dt\":\"20221123\",\"rm\":\"\"},{\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"stock_code\":\"051910\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(์ž๊ธฐ์ฃผ์‹์ฒ˜๋ถ„๊ฒฐ์ •)\",\"rcept_no\":\"20211022000367\",\"flr_nm\":\"LGํ™”ํ•™\",\"rcept_dt\":\"20211022\",\"rm\":\"\"},{\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"stock_code\":\"051910\",\"corp_cls\":\"Y\",\"report_nm\":\"์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ์„œ(ํšŒ์‚ฌ๋ถ„ํ• ๊ฒฐ์ •)\",\"rcept_no\":\"20200917000127\",\"flr_nm\":\"LGํ™”ํ•™\",\"rcept_dt\":\"20200917\",\"rm\":\"\"}]},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/list.json?corp_code=00356361&bgn_de=20200101&end_de=20221231&pblntf_ty=B&page_count=50&page_no=1&sort=date&sort_mth=desc\"]}" }, { "tool": "get_financials_kr", "args": { - "query": "LGํ™”ํ•™ 051910 ์—ฐ๊ฒฐ์žฌ๋ฌด์ œํ‘œ CFS 2019 2020 2021 2022 2023 ๋งค์ถœ ์˜์—…์ด์ต ์ˆœ์ด์ต ์ž์‚ฐ ์ž๋ณธ ์š”์•ฝ" - }, - "result": "{\"data\":{\"get_business_report_kr_051910\":{\"ticker\":\"051910\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"periods\":[{\"bsns_year\":2023,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":55249785000000,\"prior\":50983251000000,\"label\":\"๋งค์ถœ\",\"display\":\"55.2์กฐ\"},\"operatingProfit\":{\"current\":2529196000000,\"prior\":2979354000000,\"label\":\"์˜์—…์ด์ต\",\"display\":\"2.5์กฐ\"},\"netIncome\":{\"current\":2053425000000,\"prior\":2195518000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต\",\"display\":\"2.1์กฐ\"},\"controllingNetIncome\":{\"current\":1337812000000,\"prior\":1845363000000,\"label\":\"์ง€๋ฐฐ์ฃผ์ฃผ์— ๊ท€์†๋˜๋Š” ์ˆœ์ด์ต\",\"display\":\"1.3์กฐ\"},\"eps\":{\"current\":17086,\"prior\":23670,\"label\":\"๋ณดํ†ต์ฃผ ๊ธฐ๋ณธ์ฃผ๋‹น์ด์ต\",\"display\":\"17,086์›\"}},\"balanceSheet\":{\"totalAssets\":{\"current\":77466694000000,\"prior\":67973823000000,\"label\":\"์ž์‚ฐ ์ด๊ณ„\",\"display\":\"77.5์กฐ\"},\"totalLiabilities\":{\"current\":36528509000000,\"prior\":30492696000000,\"label\":\"๋ถ€์ฑ„ ์ด๊ณ„\",\"display\":\"36.5์กฐ\"},\"totalEquity\":{\"current\":40938185000000,\"prior\":37481127000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"40.9์กฐ\"},\"cashAndEquivalents\":{\"current\":9084899000000,\"prior\":8497882000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"9.1์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":7536462000000,\"prior\":569880000000,\"label\":\"์˜์—…ํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"7.5์กฐ\"},\"investing\":{\"current\":-13169629000000,\"prior\":-9229217000000,\"label\":\"ํˆฌ์žํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-13.2์กฐ\"},\"financing\":{\"current\":6157504000000,\"prior\":13331856000000,\"label\":\"์žฌ๋ฌดํ™œ๋™ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"6.2์กฐ\"},\"capex\":{\"current\":-12959891000000,\"prior\":-8406288000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-13.0์กฐ\"}},\"ratios\":{\"operatingMarginPct\":4.6,\"netMarginPct\":3.7,\"roePct\":5,\"debtToEquityPct\":89.2,\"revenueYoYPct\":8.4,\"operatingProfitYoYPct\":-15.1,\"netIncomeYoYPct\":-6.5,\"freeCashFlow\":-5423429000000,\"freeCashFlowDisplay\":\"-5.4์กฐ\"}},{\"bsns_year\":2022,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":51864888000000,\"prior\":42599284000000,\"label\":\"๋งค์ถœ\",\"display\":\"51.9์กฐ\"},\"operatingProfit\":{\"current\":2995698000000,\"prior\":5026370000000,\"label\":\"์˜์—…์ด์ต\",\"display\":\"3.0์กฐ\"},\"netIncome\":{\"current\":2195518000000,\"prior\":3953904000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"2.2์กฐ\"},\"controllingNetIncome\":{\"current\":1845363000000,\"prior\":3669787000000,\"label\":\"์ง€๋ฐฐ๊ธฐ์—…์˜ ์†Œ์œ ์ฃผ์ง€๋ถ„\",\"display\":\"1.8์กฐ\"},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":67973823000000,\"prior\":51135302000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"68.0์กฐ\"},\"totalLiabilities\":{\"current\":30492696000000,\"prior\":27925314000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"30.5์กฐ\"},\"totalEquity\":{\"current\":37481127000000,\"prior\":23209988000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"37.5์กฐ\"},\"cashAndEquivalents\":{\"current\":8497882000000,\"prior\":3760834000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"8.5์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":569880000000,\"prior\":5509816000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"5,699์–ต\"},\"investing\":{\"current\":-9229217000000,\"prior\":-5349305000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-9.2์กฐ\"},\"financing\":{\"current\":13331856000000,\"prior\":123670000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"13.3์กฐ\"},\"capex\":{\"current\":-8406288000000,\"prior\":-5773593000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-8.4์กฐ\"}},\"ratios\":{\"operatingMarginPct\":5.8,\"netMarginPct\":4.2,\"roePct\":5.9,\"debtToEquityPct\":81.4,\"revenueYoYPct\":21.8,\"operatingProfitYoYPct\":-40.4,\"netIncomeYoYPct\":-44.5,\"freeCashFlow\":-7836408000000,\"freeCashFlowDisplay\":\"-7.8์กฐ\"}},{\"bsns_year\":2021,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":42654722000000,\"prior\":30058872000000,\"label\":\"๋งค์ถœ\",\"display\":\"42.7์กฐ\"},\"operatingProfit\":{\"current\":5025466000000,\"prior\":1805359000000,\"label\":\"์˜์—…์ด์ต (์†์‹ค)\",\"display\":\"5.0์กฐ\"},\"netIncome\":{\"current\":3953904000000,\"prior\":682408000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"4.0์กฐ\"},\"controllingNetIncome\":{\"current\":3669787000000,\"prior\":512634000000,\"label\":\"์ง€๋ฐฐ๊ธฐ์—…์˜ ์†Œ์œ ์ฃผ์ง€๋ถ„\",\"display\":\"3.7์กฐ\"},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":51135302000000,\"prior\":41388894000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"51.1์กฐ\"},\"totalLiabilities\":{\"current\":27925314000000,\"prior\":22598454000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"27.9์กฐ\"},\"totalEquity\":{\"current\":23209988000000,\"prior\":18790440000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"23.2์กฐ\"},\"cashAndEquivalents\":{\"current\":3760834000000,\"prior\":3274250000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"3.8์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":5509816000000,\"prior\":5840832000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"5.5์กฐ\"},\"investing\":{\"current\":-5349305000000,\"prior\":-5296375000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-5.3์กฐ\"},\"financing\":{\"current\":123670000000,\"prior\":938069000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"1,237์–ต\"},\"capex\":{\"current\":-5773593000000,\"prior\":-5535791000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-5.8์กฐ\"}},\"ratios\":{\"operatingMarginPct\":11.8,\"netMarginPct\":9.3,\"roePct\":17,\"debtToEquityPct\":120.3,\"revenueYoYPct\":41.9,\"operatingProfitYoYPct\":178.4,\"netIncomeYoYPct\":479.4,\"freeCashFlow\":-263777000000,\"freeCashFlowDisplay\":\"-2,638์–ต\"}},{\"bsns_year\":2020,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":30076510000000,\"prior\":27353078000000,\"label\":\"๋งค์ถœ\",\"display\":\"30.1์กฐ\"},\"operatingProfit\":{\"current\":1798159000000,\"prior\":825415000000,\"label\":\"์˜์—…์ด์ต (์†์‹ค)\",\"display\":\"1.8์กฐ\"},\"netIncome\":{\"current\":682408000000,\"prior\":376104000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"6,824์–ต\"},\"controllingNetIncome\":{\"current\":512634000000,\"prior\":313365000000,\"label\":\"์ง€๋ฐฐ๊ธฐ์—…์˜ ์†Œ์œ ์ฃผ์ง€๋ถ„\",\"display\":\"5,126์–ต\"},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":41388894000000,\"prior\":34024426000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"41.4์กฐ\"},\"totalLiabilities\":{\"current\":22598454000000,\"prior\":16640637000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"22.6์กฐ\"},\"totalEquity\":{\"current\":18790440000000,\"prior\":17383789000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"18.8์กฐ\"},\"cashAndEquivalents\":{\"current\":3274250000000,\"prior\":1888631000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"3.3์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":5840832000000,\"prior\":3121101000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"5.8์กฐ\"},\"investing\":{\"current\":-5296375000000,\"prior\":-6111441000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-5.3์กฐ\"},\"financing\":{\"current\":938069000000,\"prior\":2300645000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"9,381์–ต\"},\"capex\":{\"current\":-5535791000000,\"prior\":-6238203000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-5.5์กฐ\"}},\"ratios\":{\"operatingMarginPct\":6,\"netMarginPct\":2.3,\"roePct\":3.6,\"debtToEquityPct\":120.3,\"revenueYoYPct\":10,\"operatingProfitYoYPct\":117.8,\"netIncomeYoYPct\":81.4,\"freeCashFlow\":305041000000,\"freeCashFlowDisplay\":\"3,050์–ต\"}},{\"bsns_year\":2019,\"report_type\":\"annual\",\"fs_div\":\"CFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":28625042000000,\"prior\":28183013000000,\"label\":\"๋งค์ถœ\",\"display\":\"28.6์กฐ\"},\"operatingProfit\":{\"current\":895648000000,\"prior\":2246070000000,\"label\":\"์˜์—…์ด์ต (์†์‹ค)\",\"display\":\"8,956์–ต\"},\"netIncome\":{\"current\":376104000000,\"prior\":1519312000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"3,761์–ต\"},\"controllingNetIncome\":{\"current\":313365000000,\"prior\":1472608000000,\"label\":\"์ง€๋ฐฐ๊ธฐ์—…์˜ ์†Œ์œ ์ฃผ์ง€๋ถ„\",\"display\":\"3,134์–ต\"},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":34024426000000,\"prior\":28944137000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"34.0์กฐ\"},\"totalLiabilities\":{\"current\":16640637000000,\"prior\":11622009000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"16.6์กฐ\"},\"totalEquity\":{\"current\":17383789000000,\"prior\":17322128000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"17.4์กฐ\"},\"cashAndEquivalents\":{\"current\":1888631000000,\"prior\":2513702000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"1.9์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":3121101000000,\"prior\":2124969000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"3.1์กฐ\"},\"investing\":{\"current\":-6111441000000,\"prior\":-3639046000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-6.1์กฐ\"},\"financing\":{\"current\":2300645000000,\"prior\":1793848000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"2.3์กฐ\"},\"capex\":{\"current\":-6238203000000,\"prior\":-4219355000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-6.2์กฐ\"}},\"ratios\":{\"operatingMarginPct\":3.1,\"netMarginPct\":1.3,\"roePct\":2.2,\"debtToEquityPct\":95.7,\"revenueYoYPct\":1.6,\"operatingProfitYoYPct\":-60.1,\"netIncomeYoYPct\":-75.2,\"freeCashFlow\":-3117102000000,\"freeCashFlowDisplay\":\"-3.1์กฐ\"}}],\"rawLineItemsFile\":\".dexter/tool-results/kr-financials-00356361-11011-CFS-2019_2023.json\",\"note\":\"periods[].summary holds normalized key metrics (KRW). Use read_file on rawLineItemsFile only for accounts not in the summary.\"}},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2023&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2022&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2021&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2020&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2019&reprt_code=11011&fs_div=CFS\"]}" - }, - { - "tool": "get_market_data", - "args": { - "query": "LGํ™”ํ•™ 051910 stock price around 2020-09-17, 2021-01 peak, 2022-01-27 LG Energy Solution IPO, current or 2023 year-end performance" + "query": "LGํ™”ํ•™ 051910 ์—ฐ๊ฒฐ ๋ฐ ๋ณ„๋„ 2019 2020 2021 2022 ๋งค์ถœ ์˜์—…์ด์ต ์ˆœ์ด์ต ์š”์•ฝ" }, - "result": "{\"data\":{\"_errors\":[{\"tool\":\"get_stock_prices\",\"args\":{\"ticker\":\"051910\",\"start_date\":\"2020-09-14\",\"end_date\":\"2020-09-21\",\"interval\":\"day\"},\"error\":\"[Financial Datasets API] request failed: 400 Bad Request\"},{\"tool\":\"get_stock_prices\",\"args\":{\"ticker\":\"051910\",\"start_date\":\"2021-01-01\",\"end_date\":\"2021-01-31\",\"interval\":\"day\"},\"error\":\"[Financial Datasets API] request failed: 400 Bad Request\"},{\"tool\":\"get_stock_prices\",\"args\":{\"ticker\":\"051910\",\"start_date\":\"2022-01-24\",\"end_date\":\"2022-01-31\",\"interval\":\"day\"},\"error\":\"[Financial Datasets API] request failed: 400 Bad Request\"},{\"tool\":\"get_stock_prices\",\"args\":{\"ticker\":\"051910\",\"start_date\":\"2023-01-01\",\"end_date\":\"2023-12-31\",\"interval\":\"day\"},\"error\":\"[Financial Datasets API] request failed: 400 Bad Request\"},{\"tool\":\"get_stock_price\",\"args\":{\"ticker\":\"051910\"},\"error\":\"[Financial Datasets API] request failed: 400 Bad Request\"}]}}" + "result": "{\"data\":{\"get_business_report_kr_051910\":{\"ticker\":\"051910\",\"corp_code\":\"00356361\",\"corp_name\":\"LGํ™”ํ•™\",\"report_type\":\"annual\",\"fs_div\":\"OFS\",\"periods\":[{\"bsns_year\":2022,\"report_type\":\"annual\",\"fs_div\":\"OFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":23403601000000,\"prior\":20471044000000,\"label\":\"๋งค์ถœ\",\"display\":\"23.4์กฐ\"},\"operatingProfit\":{\"current\":1070050000000,\"prior\":3019160000000,\"label\":\"์˜์—…์ด์ต\",\"display\":\"1.1์กฐ\"},\"netIncome\":{\"current\":3006969000000,\"prior\":2514263000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"3.0์กฐ\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":32872824000000,\"prior\":30929152000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"32.9์กฐ\"},\"totalLiabilities\":{\"current\":11799396000000,\"prior\":12144275000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"11.8์กฐ\"},\"totalEquity\":{\"current\":21073428000000,\"prior\":18784877000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"21.1์กฐ\"},\"cashAndEquivalents\":{\"current\":1323209000000,\"prior\":1056665000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"1.3์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":1387508000000,\"prior\":4041768000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"1.4์กฐ\"},\"investing\":{\"current\":61821000000,\"prior\":-3055452000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"618์–ต\"},\"financing\":{\"current\":-1185101000000,\"prior\":-790329000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-1.2์กฐ\"},\"capex\":{\"current\":-1732944000000,\"prior\":-1990446000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-1.7์กฐ\"}},\"ratios\":{\"operatingMarginPct\":4.6,\"netMarginPct\":12.8,\"roePct\":14.3,\"debtToEquityPct\":56,\"revenueYoYPct\":14.3,\"operatingProfitYoYPct\":-64.6,\"netIncomeYoYPct\":19.6,\"freeCashFlow\":-345436000000,\"freeCashFlowDisplay\":\"-3,454์–ต\"}},{\"bsns_year\":2021,\"report_type\":\"annual\",\"fs_div\":\"OFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":20471044000000,\"prior\":20830009000000,\"label\":\"๋งค์ถœ\",\"display\":\"20.5์กฐ\"},\"operatingProfit\":{\"current\":3019160000000,\"prior\":1114437000000,\"label\":\"์˜์—…์ด์ต (์†์‹ค)\",\"display\":\"3.0์กฐ\"},\"netIncome\":{\"current\":2514263000000,\"prior\":-98223000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"2.5์กฐ\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":30929152000000,\"prior\":25850150000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"30.9์กฐ\"},\"totalLiabilities\":{\"current\":12144275000000,\"prior\":8886645000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"12.1์กฐ\"},\"totalEquity\":{\"current\":18784877000000,\"prior\":16963505000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"18.8์กฐ\"},\"cashAndEquivalents\":{\"current\":1056665000000,\"prior\":852668000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"1.1์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":4041768000000,\"prior\":5236679000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"4.0์กฐ\"},\"investing\":{\"current\":-3055452000000,\"prior\":-4391344000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-3.1์กฐ\"},\"financing\":{\"current\":-790329000000,\"prior\":-989022000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-7,903์–ต\"},\"capex\":{\"current\":-1990446000000,\"prior\":-2187196000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-2.0์กฐ\"}},\"ratios\":{\"operatingMarginPct\":14.7,\"netMarginPct\":12.3,\"roePct\":13.4,\"debtToEquityPct\":64.6,\"revenueYoYPct\":-1.7,\"operatingProfitYoYPct\":170.9,\"netIncomeYoYPct\":2659.7,\"freeCashFlow\":2051322000000,\"freeCashFlowDisplay\":\"2.1์กฐ\"}},{\"bsns_year\":2020,\"report_type\":\"annual\",\"fs_div\":\"OFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":20830009000000,\"prior\":21809592000000,\"label\":\"๋งค์ถœ\",\"display\":\"20.8์กฐ\"},\"operatingProfit\":{\"current\":1114437000000,\"prior\":624165000000,\"label\":\"์˜์—…์ด์ต (์†์‹ค)\",\"display\":\"1.1์กฐ\"},\"netIncome\":{\"current\":-98223000000,\"prior\":365924000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"-982์–ต\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":25850150000000,\"prior\":27027086000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"25.9์กฐ\"},\"totalLiabilities\":{\"current\":8886645000000,\"prior\":10566856000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"8.9์กฐ\"},\"totalEquity\":{\"current\":16963505000000,\"prior\":16460230000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"17.0์กฐ\"},\"cashAndEquivalents\":{\"current\":852668000000,\"prior\":1002263000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"8,527์–ต\"}},\"cashFlow\":{\"operating\":{\"current\":5236679000000,\"prior\":1943151000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"5.2์กฐ\"},\"investing\":{\"current\":-4391344000000,\"prior\":-4862288000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-4.4์กฐ\"},\"financing\":{\"current\":-989022000000,\"prior\":2240321000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-9,890์–ต\"},\"capex\":{\"current\":-2187196000000,\"prior\":-2800070000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-2.2์กฐ\"}},\"ratios\":{\"operatingMarginPct\":5.4,\"netMarginPct\":-0.5,\"roePct\":-0.6,\"debtToEquityPct\":52.4,\"revenueYoYPct\":-4.5,\"operatingProfitYoYPct\":78.5,\"netIncomeYoYPct\":-126.8,\"freeCashFlow\":3049483000000,\"freeCashFlowDisplay\":\"3.0์กฐ\"}},{\"bsns_year\":2019,\"report_type\":\"annual\",\"fs_div\":\"OFS\",\"unit\":\"KRW\",\"basis\":\"์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.\",\"incomeStatement\":{\"revenue\":{\"current\":22312561000000,\"prior\":23315318000000,\"label\":\"๋งค์ถœ\",\"display\":\"22.3์กฐ\"},\"operatingProfit\":{\"current\":582425000000,\"prior\":1953618000000,\"label\":\"์˜์—…์ด์ต (์†์‹ค)\",\"display\":\"5,824์–ต\"},\"netIncome\":{\"current\":365924000000,\"prior\":1581064000000,\"label\":\"๋‹น๊ธฐ์ˆœ์ด์ต(์†์‹ค)\",\"display\":\"3,659์–ต\"},\"controllingNetIncome\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null},\"eps\":{\"current\":null,\"prior\":null,\"label\":null,\"display\":null}},\"balanceSheet\":{\"totalAssets\":{\"current\":27027086000000,\"prior\":23730578000000,\"label\":\"์ž์‚ฐ์ด๊ณ„\",\"display\":\"27.0์กฐ\"},\"totalLiabilities\":{\"current\":10566856000000,\"prior\":7180672000000,\"label\":\"๋ถ€์ฑ„์ด๊ณ„\",\"display\":\"10.6์กฐ\"},\"totalEquity\":{\"current\":16460230000000,\"prior\":16549906000000,\"label\":\"์ž๋ณธ์ด๊ณ„\",\"display\":\"16.5์กฐ\"},\"cashAndEquivalents\":{\"current\":1002263000000,\"prior\":1690862000000,\"label\":\"ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ\",\"display\":\"1.0์กฐ\"}},\"cashFlow\":{\"operating\":{\"current\":1943151000000,\"prior\":2024847000000,\"label\":\"์˜์—…ํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"1.9์กฐ\"},\"investing\":{\"current\":-4862288000000,\"prior\":-3017036000000,\"label\":\"ํˆฌ์žํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"-4.9์กฐ\"},\"financing\":{\"current\":2240321000000,\"prior\":1046184000000,\"label\":\"์žฌ๋ฌดํ™œ๋™์œผ๋กœ ์ธํ•œ ํ˜„๊ธˆํ๋ฆ„\",\"display\":\"2.2์กฐ\"},\"capex\":{\"current\":-2800070000000,\"prior\":-2515595000000,\"label\":\"์œ ํ˜•์ž์‚ฐ์˜ ์ทจ๋“\",\"display\":\"-2.8์กฐ\"}},\"ratios\":{\"operatingMarginPct\":2.6,\"netMarginPct\":1.6,\"roePct\":2.2,\"debtToEquityPct\":64.2,\"revenueYoYPct\":-4.3,\"operatingProfitYoYPct\":-70.2,\"netIncomeYoYPct\":-76.9,\"freeCashFlow\":-856919000000,\"freeCashFlowDisplay\":\"-8,569์–ต\"}}],\"rawLineItemsFile\":\".dexter/tool-results/kr-financials-00356361-11011-OFS-2019_2022.json\",\"note\":\"periods[].summary holds normalized key metrics (KRW). Use read_file on rawLineItemsFile only for accounts not in the summary.\"}},\"sourceUrls\":[\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2022&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2021&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2020&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2019&reprt_code=11011&fs_div=CFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2022&reprt_code=11011&fs_div=OFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2021&reprt_code=11011&fs_div=OFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2020&reprt_code=11011&fs_div=OFS\",\"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?corp_code=00356361&bsns_year=2019&reprt_code=11011&fs_div=OFS\"]}" } ] } diff --git a/src/agent/prompts.ts b/src/agent/prompts.ts index b0f1afd8a..a869b9308 100644 --- a/src/agent/prompts.ts +++ b/src/agent/prompts.ts @@ -105,16 +105,17 @@ filings, insider reports. A generic "good company, watch the price" answer waste For any analyze / ์–ด๋•Œ / ํ‰๊ฐ€ / ๋งค์ˆ˜ยท๋งค๋„ ํŒ๋‹จ question on a Korean stock: - GATHER broadly ONLY for open-ended questions (analyze / ์–ด๋•Œ / ํ‰๊ฐ€ / ๋งค์ˆ˜ยท๋งค๋„): call - get_financials_kr (multi-year) AND get_foreign_ownership_kr, get_large_holders_kr, - get_filings_kr (recent material), plus get_short_balance_kr / get_nps_holdings when available โ€” - they run concurrently. For a NARROW ask (DCFยท๋‹จ์ผ ์ง€ํ‘œยทํŠน์ • ๊ณต์‹œ/์ด๋ฒคํŠธ) or when a skill is + get_financials_kr (multi-year) AND get_market_data_kr (ํ˜„์žฌ๊ฐ€ยท๋ฐธ๋ฅ˜์—์ด์…˜ยท๋ชฉํ‘œ์ฃผ๊ฐ€), get_foreign_ownership_kr, + get_large_holders_kr, get_filings_kr (recent material), plus get_short_balance_kr / get_nps_holdings when + available โ€” they run concurrently. For a NARROW ask (DCFยท๋‹จ์ผ ์ง€ํ‘œยทํŠน์ • ๊ณต์‹œ/์ด๋ฒคํŠธ) or when a skill is driving the query, gather ONLY what that task needs; do NOT run the full sweep. - GROUND the answer in concrete, dated, numeric signals only you can see โ€” not a textbook description. Always state, with numbers + YoY: actual revenue / operating profit / net income / margins / ROE from get_financials_kr's \`summary\` (never give an investment view without earnings); ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ and recent net-buy trend (foreign vs ๊ธฐ๊ด€ vs ๊ฐœ์ธ); ๊ณต๋งค๋„ ์ž”๊ณ ๋น„์ค‘ level + direction (โ‰ˆ0% = no bearish positioning / no squeeze fuel; rising = - building short pressure); NPS / 5%-rule / insider direction as smart-money signals. + building short pressure); NPS / 5%-rule / insider direction as smart-money signals; and current + ๋ฐธ๋ฅ˜์—์ด์…˜ from get_market_data_kr โ€” ํ˜„์žฌ๊ฐ€, PER/PBR vs ์—…์ข…ยท์ž๊ธฐ ๊ณผ๊ฑฐ ๋ฐด๋“œ, ๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค ๋Œ€๋น„ ์ƒ์Šน์—ฌ๋ ฅ. - SYNTHESIZE the signals into ONE integrated thesis, not separate bullets. Three strands: ์ˆ˜๊ธ‰(์™ธ๊ตญ์ธยท๊ณต๋งค๋„ยท๊ธฐ๊ด€ ๋ฐฉํ–ฅ) ยท ์‹ค์ (๋งค์ถœ/์ด์ต/๋งˆ์ง„/ROE) ยท ์ง€๋ฐฐ๊ตฌ์กฐ(๋Œ€๋Ÿ‰๋ณด์œ  ์ง‘์ค‘ยท๊ณ„์—ด ์ง€๋ถ„). State explicitly whether they agree or conflict and which dominates โ€” e.g. @@ -290,7 +291,7 @@ ${toolDescriptions} ## Tool Usage Policy - Call get_financials or get_market_data ONCE with the full natural language query โ€” they handle multi-company/multi-metric requests internally. Do NOT break up queries into multiple calls. -- 6-digit numeric tickers (e.g. 005930, 035420) are Korean stocks โ€” use get_financials_kr for fundamentals, get_filings_kr for DART disclosures, get_large_holders_kr for 5%-rule major shareholders, get_insider_trades_kr for executive/insider ownership, get_short_balance_kr for ๊ณต๋งค๋„ ์ž”๊ณ  (short interest), get_foreign_ownership_kr for ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ (foreign ownership), get_nps_holdings for ๊ตญ๋ฏผ์—ฐ๊ธˆ (National Pension Service) holdings, and read_filings_kr for the report narrative (์‚ฌ์—… ๊ตฌ์„ฑยท์ฃผ์š” ๋ฆฌ์Šคํฌยท๊ฒฝ์˜์ง„๋‹จ MD&A from DART ์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ). ASCII tickers (AAPL, MSFT) use the US tools. +- 6-digit numeric tickers (e.g. 005930, 035420) are Korean stocks โ€” use get_financials_kr for fundamentals, get_market_data_kr for current priceยท์‹œ๊ฐ€์ด์•กยท๋ฐœํ–‰์ฃผ์‹์ˆ˜ยทPER/PBR/EPSยท๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค (the get_market_data equivalent โ€” get_market_data does NOT resolve 6-digit tickers), get_filings_kr for DART disclosures, get_large_holders_kr for 5%-rule major shareholders, get_insider_trades_kr for executive/insider ownership, get_short_balance_kr for ๊ณต๋งค๋„ ์ž”๊ณ  (short interest), get_foreign_ownership_kr for ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ (foreign ownership), get_nps_holdings for ๊ตญ๋ฏผ์—ฐ๊ธˆ (National Pension Service) holdings, and read_filings_kr for the report narrative (์‚ฌ์—… ๊ตฌ์„ฑยท์ฃผ์š” ๋ฆฌ์Šคํฌยท๊ฒฝ์˜์ง„๋‹จ MD&A from DART ์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ). ASCII tickers (AAPL, MSFT) use the US tools. - Only use web_fetch when headlines are insufficient (need quotes, deal specifics, earnings details). - Tool results are automatically capped. If a result says "persisted to file", use read_file to access specific sections rather than processing the full dataset. - Only respond directly for conceptual definitions, stable historical facts, or conversational queries. diff --git a/src/cli.ts b/src/cli.ts index 588a77e05..3a1296624 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -95,6 +95,10 @@ function summarizeToolResult(tool: string, args: Record, result const n = Array.isArray(parsed.data.ownership) ? parsed.data.ownership.length : 0; return `Found ${n} days`; } + if (tool === 'get_market_data_kr') { + const price = parsed.data.quote?.price; + return typeof price === 'number' ? `Price โ‚ฉ${price.toLocaleString()}` : 'Received snapshot'; + } if (tool === 'get_nps_holdings') { const n = Array.isArray(parsed.data.holdings) ? parsed.data.holdings.length : 0; return `Found ${n} holdings`; diff --git a/src/skills/dcf/SKILL.md b/src/skills/dcf/SKILL.md index bd4461225..7f4f86156 100644 --- a/src/skills/dcf/SKILL.md +++ b/src/skills/dcf/SKILL.md @@ -34,14 +34,14 @@ DCF ๋ถ„์„ ์ง„ํ–‰: **US ๊ฒฝ๋กœ** โ€” `get_financials` ๋„๊ตฌ๋ฅผ ๋‹ค์Œ ์ฟผ๋ฆฌ๋กœ ํ˜ธ์ถœํ•œ๋‹ค: -> **๐Ÿ‡ฐ๐Ÿ‡ท KR override:** ๋Œ€์‹  `get_financials_kr`๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค(DART ์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ๋กœ ๋ผ์šฐํŒ…). ์ž์—ฐ์–ด ์ฟผ๋ฆฌ ํ•˜๋‚˜๋ฉด ์ถฉ๋ถ„ํ•˜๋‹ค. ์˜ˆ: `"005930 ์ตœ๊ทผ 5๋…„ ์—ฐ๊ฒฐ ์žฌ๋ฌด์ œํ‘œ ํ˜„๊ธˆํ๋ฆ„ยท์†์ตยท์žฌ๋ฌด์ƒํƒœํ‘œ"`. ๊ทธ๋‹ค์Œ ํ˜„์žฌ๊ฐ€๋Š” `get_market_data`๊ฐ€ ํ•œ๊ตญ ํ‹ฐ์ปค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๊ทธ๊ฑธ ์“ฐ๊ณ , ์•„๋‹ˆ๋ฉด `web_search`/`get_foreign_ownership_kr`๋กœ ์ตœ๊ทผ ์ข…๊ฐ€๋ฅผ ๋ณด์™„ํ•œ๋‹ค. +> **๐Ÿ‡ฐ๐Ÿ‡ท KR override:** ๋Œ€์‹  `get_financials_kr`๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค(DART ์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ๋กœ ๋ผ์šฐํŒ…). ์ž์—ฐ์–ด ์ฟผ๋ฆฌ ํ•˜๋‚˜๋ฉด ์ถฉ๋ถ„ํ•˜๋‹ค. ์˜ˆ: `"005930 ์ตœ๊ทผ 5๋…„ ์—ฐ๊ฒฐ ์žฌ๋ฌด์ œํ‘œ ํ˜„๊ธˆํ๋ฆ„ยท์†์ตยท์žฌ๋ฌด์ƒํƒœํ‘œ"`. ๊ทธ๋‹ค์Œ ํ˜„์žฌ๊ฐ€ยท์‹œ๊ฐ€์ด์•กยท๋ฐœํ–‰์ฃผ์‹์ˆ˜ยทPER/PBRยท๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค๋Š” `get_market_data_kr`๋กœ ๊ฐ€์ ธ์˜จ๋‹ค(`get_market_data`๋Š” 6์ž๋ฆฌ ํ‹ฐ์ปค๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์œผ๋‹ˆ `web_search` ์ถ”๋ก ์— ๊ธฐ๋Œ€์ง€ ๋งˆ๋ผ). > > **์ถœ๋ ฅ์€ `periods[].summary` ์— ์ •๊ทœํ™”๋ผ ์žˆ๋‹ค(KRW).** ๋ผ๋ฒจ์„ ์ง์ ‘ ํŒŒ์‹ฑํ•˜์ง€ ๋ง๊ณ  ์ด ํ•„๋“œ๋ฅผ ์ฝ์–ด๋ผ: > - ํ˜„๊ธˆํ๋ฆ„: `cashFlow.operating`(=์˜์—…ํ˜„๊ธˆํ๋ฆ„), `cashFlow.capex`, `ratios.freeCashFlow`(=์˜์—…CFโˆ’|capex|). `free_cash_flow`๊ฐ€ ์—†์œผ๋ฉด ์ด ๊ฐ’์„ ์“ด๋‹ค. > - ์†์ต: `incomeStatement.revenue / operatingProfit / netIncome`, `ratios.revenueYoYPct`. > - ์žฌ๋ฌด์ƒํƒœํ‘œ: `balanceSheet.totalLiabilities`(์ˆœ๋ถ€์ฑ„ ๊ทผ์‚ฌ; ๊ฐ€๋Šฅํ•˜๋ฉด ์ด์ž๋ถ€์ฑ„๋งŒ ๋ณ„๋„ ํ™•์ธ), `balanceSheet.cashAndEquivalents`, `balanceSheet.totalEquity`. > - ๊ฐ metric์€ `{ current, prior, label, display }` ๊ตฌ์กฐ์ด๋ฉฐ `current`(๋‹น๊ธฐ)๋ฅผ ์“ด๋‹ค. ๋ถ„๊ธฐยท๋ฐ˜๊ธฐ ์†์ต/ํ˜„๊ธˆํ๋ฆ„์€ ๋ˆ„์ (YTD)์ž„์„ `summary.basis`๊ฐ€ ์•Œ๋ ค์ค€๋‹ค. -> - ๋ฐœํ–‰์ฃผ์‹์ˆ˜(`outstanding_shares`)๋Š” summary์— ์—†๋‹ค โ€” `get_short_balance_kr`์˜ `listedShares` ๋˜๋Š” `web_search`๋กœ ๋ณด์™„ํ•œ๋‹ค. +> - ๋ฐœํ–‰์ฃผ์‹์ˆ˜(`outstanding_shares`)๋Š” summary์— ์—†๋‹ค โ€” `get_market_data_kr`์˜ `valuation.sharesOutstanding`(์‹œ์ดรทํ˜„์žฌ๊ฐ€ ๋„์ถœ)์„ ์“ฐ๊ณ , ์ •ํ™•ํ•œ ์ƒ์žฅ์ฃผ์‹์ˆ˜๊ฐ€ ํ•„์š”ํ•˜๋ฉด `get_short_balance_kr`์˜ `listedShares`๋กœ ๋ณด์™„ํ•œ๋‹ค. > > summary๊ฐ€ ๋น„์–ด ์žˆ์œผ๋ฉด(์€ํ–‰ยท์ง€์ฃผ์‚ฌ ๋“ฑ ๋น„ํ‘œ์ค€ ๋ผ๋ฒจ) `rawLineItemsFile` ์„ `read_file`๋กœ ์—ด์–ด ์ง์ ‘ ์ฐพ๋Š”๋‹ค. `account_nm`์€ ํšŒ์‚ฌยท์—ฐ๋„๋งˆ๋‹ค ๋‹ฌ๋ผ **์ •ํ™• ์ผ์น˜ ๊ธˆ์ง€** โ€” ๋ถ€๋ถ„ ๋ฌธ์ž์—ด/`account_id`๋กœ ๋งค์นญํ•œ๋‹ค. diff --git a/src/skills/write-memo/SKILL.md b/src/skills/write-memo/SKILL.md index e79d3e652..8b074c6be 100644 --- a/src/skills/write-memo/SKILL.md +++ b/src/skills/write-memo/SKILL.md @@ -36,6 +36,13 @@ Confirm or ask for the following before drafting: ## Step 2: Gather Data (Parallel) +> **๐Ÿ‡ฐ๐Ÿ‡ท KR override (6์ž๋ฆฌ ํ‹ฐ์ปค / ํ•œ๊ตญ ์ƒ์žฅ์‚ฌ):** US ๋„๊ตฌ(`get_financials`, `get_market_data`, `read_filings`)๋Š” 6์ž๋ฆฌ ํ‹ฐ์ปค๋ฅผ ํ•ด์„ํ•˜์ง€ ๋ชปํ•œ๋‹ค. ์•„๋ž˜ 2.1โ€“2.3์„ KR ๋„๊ตฌ๋กœ ๋Œ€์ฒดํ•œ๋‹ค(๋ณ‘๋ ฌ ํ˜ธ์ถœ): +> - **์žฌ๋ฌด**: `get_financials_kr` โ€” ์ž์—ฐ์–ด ์ฟผ๋ฆฌ ํ•˜๋‚˜๋กœ ๋ฉ€ํ‹ฐ์ด์–ด ์†์ตยทํ˜„๊ธˆํ๋ฆ„ยท์žฌ๋ฌด์ƒํƒœํ‘œ(K-IFRS, KRW). ์ •๋Ÿ‰ ์„ธ๊ทธ๋จผํŠธยท์ปจ์„ผ์„œ์Šค ์ถ”์ •์น˜ ๋„๊ตฌ๋Š” ์—†๋‹ค(์žˆ๋Š” ๊ฒƒ๋งŒ ์“ด๋‹ค). +> - **์‹œ์žฅยท๋ฐธ๋ฅ˜์—์ด์…˜**: `get_market_data_kr` โ€” ํ˜„์žฌ๊ฐ€ยท52์ฃผ ๋ ˆ์ธ์ง€ยท์‹œ๊ฐ€์ด์•กยท๋ฐœํ–‰์ฃผ์‹์ˆ˜ยทPER/PBR/EPS/BPSยท๋ฐฐ๋‹น์ˆ˜์ต๋ฅ ยท**๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค**ยท๋™์ข… peer. +> - **์ˆ˜๊ธ‰ยท์ง€๋ฐฐ๊ตฌ์กฐ**: `get_foreign_ownership_kr`(์™ธ๊ตญ์ธ ํ๋ฆ„), `get_large_holders_kr`(5%๋ฃฐ), `get_insider_trades_kr`(์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ), ๊ฐ€๋Šฅํ•˜๋ฉด `get_short_balance_kr`(๊ณต๋งค๋„)ยท`get_nps_holdings`(๊ตญ๋ฏผ์—ฐ๊ธˆ). +> - **๋‚ด๋Ÿฌํ‹ฐ๋ธŒ(์‚ฌ์—…ยท๋ฆฌ์ŠคํฌยทMD&A)**: `read_filings_kr` (DART ์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ) โ€” 10-K/10-Q ๋Œ€์‹ . `{{business_snapshot}}`ยท๋ฆฌ์Šคํฌ ๋ฌธ๊ตฌ๋ฅผ ์—ฌ๊ธฐ์„œ verbatim์œผ๋กœ ๋ฝ‘๋Š”๋‹ค. +> - ๋ฉ”๋ชจ ๋ณธ๋ฌธ์€ KRW๋กœ ์ž‘์„ฑํ•˜๊ณ , ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธยท๋ฌผ์ /์ธ์ ๋ถ„ํ• ยท์ง€์ฃผ์‚ฌ ํ• ์ธยท๊ฑฐ๋ž˜์„ธยท๋ฐฐ๋‹น์„ธ ๋“ฑ KR ๊ณ ์œ  ์š”์ธ์„ ๋ฐ˜์˜ํ•œ๋‹ค. + Issue these tool calls in parallel: ### 2.1 Financials diff --git a/src/tools/finance-kr/get-market-data-kr.test.ts b/src/tools/finance-kr/get-market-data-kr.test.ts new file mode 100644 index 000000000..d4e2bb642 --- /dev/null +++ b/src/tools/finance-kr/get-market-data-kr.test.ts @@ -0,0 +1,154 @@ +import { describe, it, expect } from 'bun:test'; +import { mapMarketData, parseKoreanMarketCapToKRW, hasNoMarketData } from './get-market-data-kr.js'; +import { parseNaverMetric } from './utils.js'; + +// Shape captured from m.stock.naver.com/api/stock/005930/integration. +const SAMSUNG_RAW = { + itemCode: '005930', + stockName: '์‚ผ์„ฑ์ „์ž', + totalInfos: [ + { code: 'lastClosePrice', key: '์ „์ผ', value: '349,000' }, + { code: 'openPrice', key: '์‹œ๊ฐ€', value: '356,000' }, + { code: 'highPrice', key: '๊ณ ๊ฐ€', value: '377,000' }, + { code: 'lowPrice', key: '์ €๊ฐ€', value: '342,000' }, + { code: 'accumulatedTradingVolume', key: '๊ฑฐ๋ž˜๋Ÿ‰', value: '55,130,555' }, + { code: 'marketValue', key: '์‹œ์ด', value: '2,075์กฐ 4,289์–ต' }, + { code: 'foreignRate', key: '์™ธ์ธ์†Œ์ง„์œจ', value: '48.30%' }, + { code: 'highPriceOf52Weeks', key: '52์ฃผ ์ตœ๊ณ ', value: '377,000' }, + { code: 'lowPriceOf52Weeks', key: '52์ฃผ ์ตœ์ €', value: '56,800' }, + { code: 'per', key: 'PER', value: '28.69๋ฐฐ' }, + { code: 'eps', key: 'EPS', value: '12,372์›' }, + { code: 'cnsPer', key: '์ถ”์ •PER', value: '8.24๋ฐฐ' }, + { code: 'cnsEps', key: '์ถ”์ •EPS', value: '43,098์›' }, + { code: 'pbr', key: 'PBR', value: '4.94๋ฐฐ' }, + { code: 'bps', key: 'BPS', value: '71,907์›' }, + { code: 'dividendYieldRatio', key: '๋ฐฐ๋‹น์ˆ˜์ต๋ฅ ', value: '0.47%' }, + { code: 'dividend', key: '์ฃผ๋‹น๋ฐฐ๋‹น๊ธˆ', value: '1,668์›' }, + ], + dealTrendInfos: [ + { + bizdate: '20260601', + closePrice: '349,000', + compareToPreviousClosePrice: '32,000', + compareToPreviousPrice: { code: '2', text: '์ƒ์Šน', name: 'RISING' }, + accumulatedTradingVolume: '45,052,488', + }, + ], + consensusInfo: { itemCode: '005930', createDate: '2026-06-01', recommMean: '4.04', priceTargetMean: '401,250' }, + industryCompareInfo: [ + { itemCode: '000660', stockName: 'SKํ•˜์ด๋‹‰์Šค', closePrice: '2,327,000', fluctuationsRatio: '-1.52', marketValue: '1,658,458,403' }, + ], +}; + +describe('parseNaverMetric', () => { + it('strips ๋ฐฐ/์›/% suffixes and comma grouping', () => { + expect(parseNaverMetric('28.69๋ฐฐ')).toBe(28.69); + expect(parseNaverMetric('12,372์›')).toBe(12372); + expect(parseNaverMetric('0.47%')).toBe(0.47); + expect(parseNaverMetric('401,250')).toBe(401250); + expect(parseNaverMetric('-1.52')).toBe(-1.52); + expect(parseNaverMetric('+5.5%')).toBe(5.5); + // ์ฃผ is a Naver label-key char (52์ฃผ, ์ฃผ๋‹น๋ฐฐ๋‹น๊ธˆ), never a value unit; a value + // carrying it is treated as non-numeric rather than silently stripped to a number. + expect(parseNaverMetric('1,668์ฃผ')).toBeNull(); + expect(parseNaverMetric('n/a')).toBeNull(); + expect(parseNaverMetric('N/A')).toBeNull(); + expect(parseNaverMetric('-')).toBeNull(); + expect(parseNaverMetric('')).toBeNull(); + expect(parseNaverMetric(null)).toBeNull(); + expect(parseNaverMetric(undefined)).toBeNull(); + }); +}); + +describe('parseKoreanMarketCapToKRW', () => { + it('parses ์กฐ/์–ต market cap to KRW', () => { + expect(parseKoreanMarketCapToKRW('2,075์กฐ 4,289์–ต')).toBe(2_075_428_900_000_000); + expect(parseKoreanMarketCapToKRW('4,289์–ต')).toBe(428_900_000_000); + expect(parseKoreanMarketCapToKRW('1์กฐ')).toBe(1_000_000_000_000); + }); + + it('returns null for a unit-less number (never misscales a bare value)', () => { + expect(parseKoreanMarketCapToKRW('1,658,458,403')).toBeNull(); + expect(parseKoreanMarketCapToKRW('')).toBeNull(); + expect(parseKoreanMarketCapToKRW(null)).toBeNull(); + }); +}); + +describe('mapMarketData', () => { + const m = mapMarketData('005930', SAMSUNG_RAW as Record); + + it('maps the latest quote with computed daily change %', () => { + expect(m.name).toBe('์‚ผ์„ฑ์ „์ž'); + expect(m.quote).toEqual({ + date: '2026-06-01', + price: 349000, + change: 32000, + changePct: 10.09, // 32000 / (349000 - 32000) + direction: '์ƒ์Šน', + open: 356000, + high: 377000, + low: 342000, + high52w: 377000, + low52w: 56800, + volume: 45052488, + }); + }); + + it('maps valuation and derives shares outstanding from market cap / price', () => { + expect(m.valuation.marketCap).toBe(2_075_428_900_000_000); + expect(m.valuation.marketCapDisplay).toBe('2,075์กฐ 4,289์–ต'); + expect(m.valuation.sharesOutstanding).toBe(5_946_787_679); + expect(m.valuation.per).toBe(28.69); + expect(m.valuation.pbr).toBe(4.94); + expect(m.valuation.eps).toBe(12372); + expect(m.valuation.bps).toBe(71907); + expect(m.valuation.forwardPer).toBe(8.24); + expect(m.valuation.forwardEps).toBe(43098); + expect(m.valuation.dividendYieldPct).toBe(0.47); + expect(m.valuation.dividendPerShare).toBe(1668); + }); + + it('maps consensus with implied upside to target', () => { + expect(m.consensus).toEqual({ + date: '2026-06-01', + targetPrice: 401250, + recommendationMean: 4.04, + upsidePct: 14.97, // (401250 - 349000) / 349000 + }); + }); + + it('maps peers with market cap converted from ๋ฐฑ๋งŒ to KRW', () => { + expect(m.peers).toEqual([ + { ticker: '000660', name: 'SKํ•˜์ด๋‹‰์Šค', price: 2327000, changePct: -1.52, marketCap: 1_658_458_403_000_000 }, + ]); + }); + + it('degrades gracefully on an empty payload', () => { + const empty = mapMarketData('005930', null); + expect(empty.name).toBeNull(); + expect(empty.quote.price).toBeNull(); + expect(empty.valuation.marketCap).toBeNull(); + expect(empty.valuation.sharesOutstanding).toBeNull(); + expect(empty.consensus.targetPrice).toBeNull(); + expect(empty.peers).toEqual([]); + }); +}); + +describe('hasNoMarketData', () => { + it('flags an all-null mapping (empty/garbage 200 payload) as not found', () => { + expect(hasNoMarketData(mapMarketData('999999', null))).toBe(true); + expect(hasNoMarketData(mapMarketData('999999', {} as Record))).toBe(true); + }); + + it('does not false-positive on a valid ticker (stockName alone is enough)', () => { + // A valid /integration response always carries stockName, so a real ticker + // never trips the guard even if price/marketCap are momentarily absent. + const sparse = mapMarketData('005930', { stockName: '์‚ผ์„ฑ์ „์ž' } as Record); + expect(sparse.name).toBe('์‚ผ์„ฑ์ „์ž'); + expect(hasNoMarketData(sparse)).toBe(false); + }); + + it('does not false-positive on the full Samsung payload', () => { + expect(hasNoMarketData(mapMarketData('005930', SAMSUNG_RAW as Record))).toBe(false); + }); +}); diff --git a/src/tools/finance-kr/get-market-data-kr.ts b/src/tools/finance-kr/get-market-data-kr.ts new file mode 100644 index 000000000..b0798a56f --- /dev/null +++ b/src/tools/finance-kr/get-market-data-kr.ts @@ -0,0 +1,202 @@ +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { z } from 'zod'; +import { fetchNaverIntegration } from './naver-api.js'; +import { parseNaverMetric, toIsoDate } from './utils.js'; +import { formatToolResult } from '../types.js'; +import { TTL_1H } from '../finance/utils.js'; + +export const GET_MARKET_DATA_KR_DESCRIPTION = `Retrieves a market-data + valuation snapshot for a Korean (KOSPI/KOSDAQ) listed company โ€” the Korean equivalent of get_market_data, which does NOT resolve 6-digit tickers. Returns: latest close price with daily change, 52-week range and session OHLC; market cap (์‹œ๊ฐ€์ด์•ก) and derived shares outstanding; valuation multiples PER/PBR/EPS/BPS plus forward ์ถ”์ •PER/์ถ”์ •EPS; dividend yield and dividend per share; analyst consensus (๋ชฉํ‘œ์ฃผ๊ฐ€ priceTargetMean + mean recommendation, higher = more bullish) with implied upside; and a short same-industry peer list (ticker, price, market cap) for quick comparables. + +Use this for current price, market cap, shares outstanding, PER/PBR/EV multiples, or ์ปจ์„ผ์„œ์Šค/๋ชฉํ‘œ์ฃผ๊ฐ€ on a Korean stock โ€” including as the price + share-count source for a DCF on a 6-digit ticker. Accepts a 6-digit ticker (e.g. 005930 for Samsung Electronics). All amounts in KRW. Source: Naver Finance (keyless). Note: shares outstanding is DERIVED as market cap รท price, so it is approximate (typically within ~1-2% of the true float, since Naver's market cap is not an exact sharesร—close product); when per-share precision matters (e.g. DCF), prefer get_short_balance_kr's listedShares for the exact ์ƒ์žฅ์ฃผ์‹์ˆ˜.`; + +const InputSchema = z.object({ + ticker: z + .string() + .regex(/^\d{6}$/, 'Korean ticker must be a 6-digit string (e.g. 005930 for Samsung).') + .describe('6-digit Korean stock ticker (e.g. 005930 for Samsung Electronics).'), +}); + +export interface MarketDataKr { + ticker: string; + name: string | null; + quote: { + date: string | null; + price: number | null; + change: number | null; + changePct: number | null; + direction: string | null; + open: number | null; + high: number | null; + low: number | null; + high52w: number | null; + low52w: number | null; + volume: number | null; + }; + valuation: { + marketCap: number | null; + marketCapDisplay: string | null; + sharesOutstanding: number | null; + per: number | null; + pbr: number | null; + eps: number | null; + bps: number | null; + forwardPer: number | null; + forwardEps: number | null; + dividendYieldPct: number | null; + dividendPerShare: number | null; + }; + consensus: { + date: string | null; + targetPrice: number | null; + recommendationMean: number | null; + upsidePct: number | null; + }; + peers: { ticker: string; name: string | null; price: number | null; changePct: number | null; marketCap: number | null }[]; +} + +/** + * Parse Naver's ์กฐ/์–ต-formatted market cap (e.g. "2,075์กฐ 4,289์–ต") to a KRW + * number. The subject company's marketValue is always rendered in ์กฐ/์–ต; peers + * use a plain ๋ฐฑ๋งŒ(million)-KRW number instead (handled separately). Returns + * null if no ์กฐ/์–ต/๋งŒ token is present, so a bare number is never misscaled. + */ +export function parseKoreanMarketCapToKRW(value: unknown): number | null { + const s = String(value ?? '').replace(/,/g, '').trim(); + if (!s) return null; + const jo = /(\d+(?:\.\d+)?)\s*์กฐ/.exec(s); + const eok = /(\d+(?:\.\d+)?)\s*์–ต/.exec(s); + const man = /(\d+(?:\.\d+)?)\s*๋งŒ/.exec(s); + if (!jo && !eok && !man) return null; + let krw = 0; + if (jo) krw += parseFloat(jo[1]) * 1e12; + if (eok) krw += parseFloat(eok[1]) * 1e8; + if (man) krw += parseFloat(man[1]) * 1e4; + return krw > 0 ? Math.round(krw) : null; +} + +function round2(n: number): number { + return Math.round(n * 100) / 100; +} + +/** + * A valid ticker's `/integration` always carries at least `stockName`; an empty + * or garbage 200 payload yields nulls across the board. Use this to surface a + * "not found" error instead of a snapshot of nulls. (Invalid codes return 409, + * which `fetchNaverIntegration` already throws on โ€” this is the 200 fallback.) + */ +export function hasNoMarketData(m: MarketDataKr): boolean { + return m.name === null && m.quote.price === null && m.valuation.marketCap === null; +} + +/** `totalInfos` is an array of `{ code, key, value }`; look up a value by code. */ +function totalInfo(totalInfos: unknown, code: string): unknown { + if (!Array.isArray(totalInfos)) return undefined; + const hit = totalInfos.find( + (it) => it && typeof it === 'object' && (it as Record).code === code, + ); + return hit ? (hit as Record).value : undefined; +} + +/** Map a raw Naver `/integration` payload to the friendly market-data shape. */ +export function mapMarketData(ticker: string, raw: Record | null): MarketDataKr { + const ti = raw?.totalInfos; + const deal = Array.isArray(raw?.dealTrendInfos) + ? (raw!.dealTrendInfos as Record[]) + : []; + const latest = deal[0] ?? {}; + const cons = (raw?.consensusInfo ?? null) as Record | null; + const compare = Array.isArray(raw?.industryCompareInfo) + ? (raw!.industryCompareInfo as Record[]) + : []; + + const price = parseNaverMetric(latest.closePrice) ?? parseNaverMetric(totalInfo(ti, 'lastClosePrice')); + const change = parseNaverMetric(latest.compareToPreviousClosePrice); + const prevClose = price !== null && change !== null ? price - change : null; + const changePct = + prevClose !== null && prevClose !== 0 && change !== null ? round2((change / prevClose) * 100) : null; + const dir = latest.compareToPreviousPrice; + const direction = + dir && typeof dir === 'object' ? ((dir as Record).text as string) ?? null : null; + + const marketCapRaw = totalInfo(ti, 'marketValue'); + const marketCap = parseKoreanMarketCapToKRW(marketCapRaw); + const sharesOutstanding = marketCap !== null && price ? Math.round(marketCap / price) : null; + + const targetPrice = parseNaverMetric(cons?.priceTargetMean); + const recommendationMean = parseNaverMetric(cons?.recommMean); + const upsidePct = + targetPrice !== null && price ? round2(((targetPrice - price) / price) * 100) : null; + + return { + ticker, + name: (raw?.stockName as string) ?? null, + quote: { + date: latest.bizdate ? toIsoDate(latest.bizdate) : null, + price, + change, + changePct, + direction, + open: parseNaverMetric(totalInfo(ti, 'openPrice')), + high: parseNaverMetric(totalInfo(ti, 'highPrice')), + low: parseNaverMetric(totalInfo(ti, 'lowPrice')), + high52w: parseNaverMetric(totalInfo(ti, 'highPriceOf52Weeks')), + low52w: parseNaverMetric(totalInfo(ti, 'lowPriceOf52Weeks')), + volume: + parseNaverMetric(latest.accumulatedTradingVolume) ?? + parseNaverMetric(totalInfo(ti, 'accumulatedTradingVolume')), + }, + valuation: { + marketCap, + marketCapDisplay: marketCapRaw === undefined || marketCapRaw === null ? null : String(marketCapRaw), + sharesOutstanding, + per: parseNaverMetric(totalInfo(ti, 'per')), + pbr: parseNaverMetric(totalInfo(ti, 'pbr')), + eps: parseNaverMetric(totalInfo(ti, 'eps')), + bps: parseNaverMetric(totalInfo(ti, 'bps')), + forwardPer: parseNaverMetric(totalInfo(ti, 'cnsPer')), + forwardEps: parseNaverMetric(totalInfo(ti, 'cnsEps')), + dividendYieldPct: parseNaverMetric(totalInfo(ti, 'dividendYieldRatio')), + dividendPerShare: parseNaverMetric(totalInfo(ti, 'dividend')), + }, + consensus: { + date: cons?.createDate ? toIsoDate(cons.createDate) : null, + targetPrice, + recommendationMean, + upsidePct, + }, + peers: compare.slice(0, 6).map((p) => { + // Peer marketValue is a plain ๋ฐฑ๋งŒ(million)-KRW number, not a ์กฐ/์–ต string. + const mv = parseNaverMetric(p.marketValue); + return { + ticker: String(p.itemCode ?? ''), + name: (p.stockName as string) ?? null, + price: parseNaverMetric(p.closePrice), + changePct: parseNaverMetric(p.fluctuationsRatio), + marketCap: mv !== null ? mv * 1e6 : null, + }; + }), + }; +} + +export const getMarketDataKr = new DynamicStructuredTool({ + name: 'get_market_data_kr', + description: GET_MARKET_DATA_KR_DESCRIPTION, + schema: InputSchema, + func: async (input) => { + const ticker = input.ticker.trim(); + try { + const { data, url } = await fetchNaverIntegration(ticker, { cacheable: true, ttlMs: TTL_1H }); + const mapped = data ? mapMarketData(ticker, data) : null; + if (!mapped || hasNoMarketData(mapped)) { + return formatToolResult( + { ticker, _error: `No market data found for ${ticker} โ€” check the 6-digit ticker` }, + [url], + ); + } + return formatToolResult(mapped, [url]); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return formatToolResult({ ticker, _error: message }, []); + } + }, +}); diff --git a/src/tools/finance-kr/naver-api.ts b/src/tools/finance-kr/naver-api.ts index 28fc9935c..6317e04e2 100644 --- a/src/tools/finance-kr/naver-api.ts +++ b/src/tools/finance-kr/naver-api.ts @@ -61,3 +61,64 @@ export async function fetchNaverTrend( } return { rows, url }; } + +export interface NaverIntegrationResult { + data: Record | null; + url: string; +} + +/** + * Fetch the `/integration` payload for a 6-digit ticker. Unlike `/trend` (a bare + * array), this returns a rich object: `totalInfos` (price, ์‹œ์ด, PER/PBR/EPS/BPS, + * ์ถ”์ • PER/EPS, ๋ฐฐ๋‹น), `dealTrendInfos` (latest daily close), `consensusInfo` + * (๋ชฉํ‘œ์ฃผ๊ฐ€ยทํˆฌ์ž์˜๊ฒฌ), and `industryCompareInfo` (peers). It is the closest + * keyless source for Korean market data + valuation multiples + consensus. + */ +export async function fetchNaverIntegration( + ticker: string, + options?: { cacheable?: boolean; ttlMs?: number }, +): Promise { + const endpoint = '/naver/integration'; + const params = { ticker }; + + if (options?.cacheable) { + const cached = readCache(endpoint, params, options.ttlMs); + if (cached) { + const payload = cached.data.payload; + return { + data: payload && typeof payload === 'object' ? (payload as Record) : null, + url: cached.url, + }; + } + } + + const url = `${BASE_URL}/${ticker}/integration`; + let response: Response; + try { + response = await fetch(url, { headers: { 'User-Agent': USER_AGENT, Accept: 'application/json' } }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`[Naver API] network error: ${ticker} โ€” ${message}`); + throw new Error(`[Naver API] request failed for ${ticker}: ${message}`); + } + + if (!response.ok) { + // An unknown/invalid 6-digit code returns 409 (not 404); surface that as a + // clear "no such ticker" rather than a raw HTTP status the model can't act on. + if (response.status === 409 || response.status === 404) { + throw new Error(`[Naver API] no data for ticker ${ticker} โ€” check the 6-digit ticker (status ${response.status})`); + } + throw new Error(`[Naver API] request failed: ${response.status} ${response.statusText}`); + } + + const json = (await response.json().catch(() => { + throw new Error(`[Naver API] request failed: invalid JSON for ${ticker}`); + })) as unknown; + const data = + json && typeof json === 'object' && !Array.isArray(json) ? (json as Record) : null; + + if (options?.cacheable) { + writeCache(endpoint, params, { payload: data }, url); + } + return { data, url }; +} diff --git a/src/tools/finance-kr/utils.ts b/src/tools/finance-kr/utils.ts index 6660c3120..b70a32616 100644 --- a/src/tools/finance-kr/utils.ts +++ b/src/tools/finance-kr/utils.ts @@ -58,6 +58,25 @@ export function parseKrxNumber(value: unknown): number | null { return Number.isFinite(n) ? n : null; } +/** + * Naver's `/integration` metric strings carry Korean unit suffixes that + * `parseKrxNumber` does not strip โ€” e.g. "28.69๋ฐฐ" (PER), "12,372์›" (EPS), + * "0.47%" (yield), "4.04" (recommendation), "401,250" (target price), "N/A". + * Strip the suffix/grouping and parse; null for blanks, "-", and "N/A". + */ +export function parseNaverMetric(value: unknown): number | null { + if (value === null || value === undefined) return null; + const s = String(value) + .trim() + .replace(/,/g, '') + .replace(/[%์›๋ฐฐ]/g, '') + .replace(/^\+/, '') + .trim(); + if (s === '' || s === '-' || s.toUpperCase() === 'N/A') return null; + const n = Number(s); + return Number.isFinite(n) ? n : null; +} + /** * KRX getJsonData responses wrap rows in an `OutBlock_1` array (some endpoints * use other keys). Missing/empty is a valid "no data" outcome, not an error. diff --git a/src/tools/registry.ts b/src/tools/registry.ts index 2552defcc..a7d3ba848 100644 --- a/src/tools/registry.ts +++ b/src/tools/registry.ts @@ -18,6 +18,7 @@ import { getInsiderTradesKr, GET_INSIDER_TRADES_KR_DESCRIPTION } from './finance import { createReadFilingsKr, READ_FILINGS_KR_DESCRIPTION } from './finance-kr/read-filings-kr.js'; import { getShortBalanceKr, GET_SHORT_BALANCE_KR_DESCRIPTION } from './finance-kr/get-short-balance-kr.js'; import { getForeignOwnershipKr, GET_FOREIGN_OWNERSHIP_KR_DESCRIPTION } from './finance-kr/get-foreign-ownership-kr.js'; +import { getMarketDataKr, GET_MARKET_DATA_KR_DESCRIPTION } from './finance-kr/get-market-data-kr.js'; import { getNpsHoldings_tool, GET_NPS_HOLDINGS_DESCRIPTION } from './finance-kr/get-nps-holdings.js'; import { GET_MARKET_DATA_DESCRIPTION } from './finance/get-market-data.js'; import { READ_FILINGS_DESCRIPTION } from './finance/read-filings.js'; @@ -236,6 +237,16 @@ export function getToolRegistry(model: string): RegisteredTool[] { concurrencySafe: true, }); + // Price + valuation + consensus snapshot, also from Naver's keyless API. + tools.push({ + name: 'get_market_data_kr', + tool: getMarketDataKr, + description: GET_MARKET_DATA_KR_DESCRIPTION, + compactDescription: + 'Korean price + market cap + PER/PBR + ๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค snapshot for 6-digit tickers (get_market_data equivalent).', + concurrencySafe: true, + }); + // ๊ณต๋งค๋„ ์ž”๊ณ  requires a KRX Data Marketplace login โ€” either a native ID/PW // or a pasted session cookie (KRX_COOKIE) for social-login accounts. const hasKrxIdPw = From 2038f6822bdf6edbfdcda460d2168f0d87a35760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SH=20KWON=20=F0=9F=87=B0=F0=9F=87=B7?= Date: Wed, 3 Jun 2026 17:57:00 +0900 Subject: [PATCH 11/70] =?UTF-8?q?DCF=20=EC=88=9C=EB=B6=80=EC=B1=84=20?= =?UTF-8?q?=EB=B6=80=ED=98=B8=20=EC=88=98=EC=A0=95:=20KR=20=EC=9D=B4?= =?UTF-8?q?=EC=9E=90=EB=B6=80=EC=B1=84(totalDebt)=C2=B7=EB=8B=A8=EA=B8=B0?= =?UTF-8?q?=EA=B8=88=EC=9C=B5=EC=83=81=ED=92=88=20=EC=A0=95=EA=B7=9C?= =?UTF-8?q?=ED=99=94=20+=20EV=E2=86=92Equity=20=EB=B6=80=ED=98=B8=20?= =?UTF-8?q?=EA=B7=9C=EC=B9=99=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * DCF ์ˆœ๋ถ€์ฑ„ ๋ถ€ํ˜ธ ์ˆ˜์ •: KR ์ด์ž๋ถ€์ฑ„(totalDebt)ยท๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ ์ •๊ทœํ™” + EVโ†’Equity ๋ถ€ํ˜ธ ๊ทœ์น™ DCF๊ฐ€ ์ฃผ์‹๊ฐ€์น˜ = EV โˆ’ ์ˆœ๋ถ€์ฑ„์—์„œ KR ๊ฒฝ๋กœ์˜ ์ˆœ๋ถ€์ฑ„ ํ•ญ์œผ๋กœ ๋ถ€์ฑ„์ด๊ณ„ (totalLiabilities)๋ฅผ "์ˆœ๋ถ€์ฑ„ ๊ทผ์‚ฌ"๋กœ ์ฐจ๊ฐํ–ˆ๋‹ค. ๋ถ€์ฑ„์ด๊ณ„๋Š” ๋งค์ž…์ฑ„๋ฌดยท์„ ์ˆ˜๊ธˆ ๋“ฑ ์˜์—…์„ฑ ๋ถ€์ฑ„๊นŒ์ง€ ํฌํ•จํ•˜๋ฏ€๋กœ ์ด์ž๋ถ€์ฑ„๊ฐ€ ์•„๋‹ˆ๋ฉฐ, ์‚ผ์„ฑ์ฒ˜๋Ÿผ ์ˆœํ˜„๊ธˆ ๊ธฐ์—…์—์„  ๋ถ€ํ˜ธ๊ฐ€ ๋’ค์ง‘ํ˜€ ์ ์ •๊ฐ€๊ฐ€ ๊ณผ์†Œํ‰๊ฐ€๋๋‹ค(์‚ผ์„ฑ ~112k). ์ด๋ฒˆ PR(#11)์ด DCF์— ์‹ค์ œ ๊ฐ€๊ฒฉ์„ ๊ณต๊ธ‰ํ•˜๋ฉด์„œ ๋” ๋„๋“œ๋ผ์ง„ ์ •ํ™•์„ฑ ๋ฒ„๊ทธ. ์ˆ˜์ •: - normalize-financials.ts: KR ์ •๊ทœํ™” ์š”์•ฝ์— balanceSheet.totalDebt(์ด์ž๋ถ€์ฑ„ ํ•ฉ๊ณ„)์™€ shortTermInvestments(๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ) ์ถ”๊ฐ€. ์ƒˆ sumMetrics ํ—ฌํผ + DEBT_SUM_SPEC๋กœ ์—ฌ๋Ÿฌ ์ฐจ์ž…๊ธˆ ๋ผ์ธ์„ ํ•ฉ์‚ฐ(์ฐจ์ž…๊ธˆ ๋ผ๋ฒจ์ด ์—†์œผ๋ฉด null โ†’ ๋ชจ๋ธ์ด raw ํŒŒ์ผ ํด๋ฐฑ). - skills/dcf/SKILL.md: line 42 KR override๋ฅผ totalDebt/shortTermInvestments ๊ธฐ๋ฐ˜์œผ๋กœ ์žฌ์ž‘์„ฑ, Step 5์— EVโ†’Equity ๋ธŒ๋ฆฌ์ง€์™€ ์ˆœํ˜„๊ธˆ ๋ถ€ํ˜ธ ๊ทœ์น™(Net Debt<0์ด๋ฉด EV์— ๊ฐ€์‚ฐ)์„ ์–‘ ๊ฒฝ๋กœ(US/KR) ๋ชจ๋‘ ๋ช…์‹œ, Step 7์— ์ˆœ๋ถ€์ฑ„ ๋ถ€ํ˜ธยท๊ทœ๋ชจ sanity check ์ถ”๊ฐ€. - US ๊ฒฝ๋กœ๋Š” ๋ฐ์ดํ„ฐ(total_debt/cash_and_equivalents/current_investments)๊ฐ€ ์ด๋ฏธ ๋ชจ๋ธ์— ์ „๋‹ฌ๋˜๋ฏ€๋กœ ๋งˆํฌ๋‹ค์šด๋งŒ ๋ช…ํ™•ํ™”. account_id ๊ฒ€์ฆ: ์‹ค์ œ DART CFS ํŽ˜์ด๋กœ๋“œ(์‚ผ์„ฑ์ „์ž 005930, LGํ™”ํ•™ 051910, ์•Œํ…Œ์˜ค์   196170)๋กœ ๋ผ๋ฒจ/id ๊ฒ€์ฆ. ํ›„๋ณด 5๊ฐœ ์ค‘ 4๊ฐœ๊ฐ€ ์‹ค๋ฐ์ดํ„ฐ์™€ ๋‹ฌ๋ผ ๊ต์ฒด(์˜ˆ: ์‚ผ์„ฑ ๋‹จ๊ธฐ์ฐจ์ž…๊ธˆ์€ account_id '-ํ‘œ์ค€๊ณ„์ •์ฝ”๋“œ ๋ฏธ์‚ฌ์šฉ-' โ†’ account_nm์œผ๋กœ ๋งค์นญ). LGํ™”ํ•™์ด ์ฐจ์ž…๊ธˆ์„ ๋™์ผ ๋ผ๋ฒจ ๋‘ ํ–‰์œผ๋กœ ๋ณด๊ณ ํ•ด, ์Šน์ธ๋œ ํ”Œ๋žœ์˜ "์ปดํฌ๋„ŒํŠธ๋‹น 1ํ–‰" ๋Œ€์‹  ๋งค์นญ๋˜๋Š” ๋ชจ๋“  ํ–‰์„ ํ•ฉ์‚ฐํ•˜๋Š” set ๊ธฐ๋ฐ˜ ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„(์•ˆ ๊ทธ๋Ÿฌ๋ฉด ~12์กฐ ๋ˆ„๋ฝ). ๊ฒ€์ฆ: bun run typecheck โœ… ยท bun test 187 pass โœ…. ์‚ผ์„ฑ ์ˆœํ˜„๊ธˆ 100.6์กฐ(=์ฐจ์ž…๊ธˆ 25.2์กฐ โˆ’ ํ˜„๊ธˆ 57.9์กฐ โˆ’ ๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ 68.0์กฐ) ํ™•์ธ, ์ ์ •๊ฐ€ ~112k โ†’ ~144k. Co-Authored-By: Claude Opus 4.8 (1M context) * ๋ฆฌ๋ทฐ ๋ฐ˜์˜: ์ˆœ๋ถ€์ฑ„ ๋ฌธ์„œ/์ฃผ์„ ์ •๋ฐ€ํ™” (๋™์ž‘ ๋ณ€๊ฒฝ ์—†์Œ) ์ฝ”๋“œ ๋ฆฌ๋ทฐ ํ›„์† ๋‹ค๋“ฌ๊ธฐ (4๊ฑด, ๋ชจ๋‘ doc/comment โ€” ๋™์ž‘ยทํ…Œ์ŠคํŠธ ์˜ํ–ฅ ์—†์Œ): - SKILL.md ํด๋ฐฑ ์ง€์นจ: totalDebt๊ฐ€ null์ผ ๋•Œ raw ํ•ฉ์‚ฐ์„ account_id๊ฐ€ ์•„๋‹ˆ๋ผ account_nm(๋ผ๋ฒจ) ์šฐ์„ ์œผ๋กœ ์•ˆ๋‚ด(์‚ผ์„ฑ ๋‹จ๊ธฐ์ฐจ์ž…๊ธˆ์ด '-ํ‘œ์ค€๊ณ„์ •์ฝ”๋“œ ๋ฏธ์‚ฌ์šฉ-'์ด๋ผ ๋ผ๋ฒจ์ด ๋” ์•ˆ์ •์ ์ด๋ผ๋Š” ์ด๋ฒˆ PR์˜ ๊ตํ›ˆ๊ณผ ์ผ์น˜). - SKILL.md Net Debt ์‹: shortTermInvestments/cashAndEquivalents๊ฐ€ null์ด๋ฉด 0์œผ๋กœ ๋ณธ๋‹ค๊ณ  ๋ช…์‹œ(US ๊ฒฝ๋กœ์˜ "์—†์œผ๋ฉด 0"๊ณผ ๋Œ€์นญ). - SKILL.md Step 5: LLM์ด ํ‰๋ฌธ์œผ๋กœ ์ฝ๋Š” ํ…์ŠคํŠธ๋ผ   HTML ์—”ํ‹ฐํ‹ฐ ์ œ๊ฑฐ. - DEBT_SUM_SPEC ์ฃผ์„: ์‹ค์ธก ๊ฒ€์ฆ ๋ผ๋ฒจ(์‚ผ์„ฑยทLGํ™”ํ•™ยท์•Œํ…Œ์˜ค์  )๊ณผ best-effort ์ถ”์ • ๋ผ๋ฒจ (์œ ๋™์„ฑ์‚ฌ์ฑ„/๋‹จ๊ธฐ์‚ฌ์ฑ„/์‹ ์ฃผ์ธ์ˆ˜๊ถŒ๋ถ€์‚ฌ์ฑ„ ๋“ฑ)์„ ๊ตฌ๋ถ„ํ•˜๊ณ , "์ฐจ์ž…๊ธˆ" ์†Œ๊ณ„+์„ธ๋ถ€ ๋™์‹œ๋ณด๊ณ  ์‹œ ์ด์ค‘๊ณ„์ƒ ๊ฐ€๋Šฅ์„ฑ caveat์„ ๋ช…์‹œ. Co-Authored-By: Claude Opus 4.8 (1M context) * README: ์—์ด์ „ํŠธ ์ง„ํ™” ๋ฐ˜์˜ + ์ฑ—๋ด‡ ์ฐจ๋ณ„์  ๊ฐ•ํ™” - get_market_data_kr๋ฅผ Korea-specific ํˆด ํ‘œ์— ์ถ”๊ฐ€(ํ‚ค ๋ถˆํ•„์š” Naver; ๊ทธ๊ฐ„ ๋ˆ„๋ฝ โ€” KR DCF ํ˜„์žฌ๊ฐ€/์ฃผ์‹์ˆ˜ ๊ณต๊ธ‰์›) - DCF ์ž๋™ ๋ถ„๊ธฐ ์„ค๋ช…์— "์ˆœ๋ถ€์ฑ„=์ด์ž๋ถ€์ฑ„(๋ถ€์ฑ„์ด๊ณ„ ์•„๋‹˜)โˆ’ํ˜„๊ธˆยท๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ, ์ˆœํ˜„๊ธˆ ๊ฐ€์‚ฐ" ๊ทœ์น™ ๋ฐ˜์˜ - "Dexter๊ฐ€ ๋งŒ๋“  ์ฐจ์ด"์— '์ •ํ™•ํ•œ ๋ฐธ๋ฅ˜์—์ด์…˜'(์ด์ž๋ถ€์ฑ„ ์ˆœ๋ถ€์ฑ„) ์ถ”๊ฐ€, '1์ฐจ ์ถœ์ฒ˜'์— ํ‚ค๋ฆฌ์Šค(์™ธ๊ตญ์ธยทํ˜„์žฌ๊ฐ€) ์šฐ์œ„ ๋ช…์‹œ, '๊ฐ์‚ฌยท์žฌํ˜„ ๊ฐ€๋Šฅ'์— KR record/replay ํ•˜๋„ค์Šค ๊ทผ๊ฑฐ ์ถ”๊ฐ€ - ๊ฐœ์š”์— '1์ฐจ ์ถœ์ฒ˜ + ๊ฐ์‚ฌ ๊ฐ€๋Šฅ' ์ฐจ๋ณ„์  ๋ถˆ๋ฆฟ(๋น„๊ต ์„น์…˜ ๋งํฌ) - ํ•œ๊ตญ ์ฃผ์‹ ํ‰๊ฐ€ ํ•˜๋„ค์Šค(kr-eval record/replay) ์„น์…˜ ์ถ”๊ฐ€ - env ๋ธ”๋ก์„ env.example๊ณผ ์ผ์น˜: LLM์— MOONSHOT/DEEPSEEK, ์›น๊ฒ€์ƒ‰์— Perplexity/LangSearch ํด๋ฐฑ ์ฒด์ธ - ๋น„๊ตํ‘œ ๋„ค์ด๋ฒ„ DCF ํ–‰์ด ์ˆœ๋ถ€์ฑ„ ๋ณด์ • ์ด์ „ ์ธก์ •๊ฐ’์ž„์„ ๊ฐ์ฃผ๋กœ ๋ช…์‹œ - get_market_data_kr ์‚ฌ์šฉ ์˜ˆ์‹œ(ํ˜„์žฌ๊ฐ€ยท๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค) ์ถ”๊ฐ€ Co-Authored-By: Claude Opus 4.8 (1M context) * docs: CLAUDE.mdยทAGENTS.md ๋“œ๋ฆฌํ”„ํŠธ ์ •์ • (์ฝ”๋“œ ๊ธฐ์ค€, ๋™์ž‘ ๋ณ€๊ฒฝ ์—†์Œ) ์ฝ”๋“œ์™€ ์–ด๊ธ‹๋‚œ ์„œ์ˆ ์„ ์‹ค์ œ ๊ตฌํ˜„์— ๋งž๊ฒŒ ์ •์ •: AGENTS.md (upstream ๊ธฐ์—ฌ์ž ๋ฌธ์„œ ๋“œ๋ฆฌํ”„ํŠธ): - Ink/Reactยท`cli.tsx`ยท`src/hooks` โ†’ pi-tuiยท`cli.ts`ยท`components`(.ts) (src/hooks ๋ฏธ์กด์žฌ) - ๊ฐ€๊ณต์˜ ํˆด๋ช… financial_search/financial_metrics โ†’ ์‹ค์ œ ํˆด(get_financials/get_market_data/ read_filings/stock_screener) + KR ํˆด 9์ข… ์ „๋ถ€ - web_search๋ฅผ Exaโ†’Perplexityโ†’Tavilyโ†’LangSearch ํด๋ฐฑ ์ฒด์ธ์œผ๋กœ - "์ตœ์ข…๋‹ต๋ณ€=๋ณ„๋„ LLM ์ฝœ(no tools bound)" โ†’ ๋‹จ์ผ ํŒจ์Šค(handleDirectResponse, ํˆด ํ•ญ์ƒ ๋ฐ”์ธ๋”ฉ; ๋ณ„๋„ ๋งˆ๋ฌด๋ฆฌ ํŒจ์Šค ์—†์Œ) - LLM ํ”„๋กœ๋ฐ”์ด๋”/env์— MoonshotยทDeepSeek + KR ๋ฐ์ดํ„ฐ ํ‚ค ์ถ”๊ฐ€, KR eval ํ•˜๋„ค์Šค ๋ช…์‹œ CLAUDE.md: - "FINANCIAL_DATASETS_API_KEY ์—†์œผ๋ฉด get_* ํˆด ๋ถ€์žฌ" ์ •์ • โ†’ US ํŒŒ์ด๋‚ธ์Šค ํˆด์€ ๋ฌด์กฐ๊ฑด ๋“ฑ๋ก๋˜๊ณ  ํ˜ธ์ถœ ์‹œ์ ์— 401(๋ถ€์žฌ ์•„๋‹˜). ์‹ค์ œ ๊ฒŒ์ดํŠธ(DART/๊ฒ€์ƒ‰/KRX/X) ๋ช…์‹œ - TUI ํŠธ๋žฉ์— components(.ts)ยทsrc/hooks ๋ฏธ์กด์žฌ ์ถ”๊ฐ€ - Stale docs ์„น์…˜ ๊ฐฑ์‹ (AGENTS ์ •์ • ์™„๋ฃŒ, README์—” Ink ์—†์Œ) Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- AGENTS.md | 42 +++--- CLAUDE.md | 8 +- README.md | 29 +++- src/skills/dcf/SKILL.md | 19 ++- .../finance-kr/normalize-financials.test.ts | 104 +++++++++++++ src/tools/finance-kr/normalize-financials.ts | 141 +++++++++++++++++- 6 files changed, 303 insertions(+), 40 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 0e06155b8..a827899a4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,24 +1,24 @@ # Repository Guidelines - Repo: https://github.com/virattt/dexter -- Dexter is a CLI-based AI agent for deep financial research, built with TypeScript, Ink (React for CLI), and LangChain. +- Dexter is a CLI-based AI agent for deep financial research, built with TypeScript, a terminal UI (`@mariozechner/pi-tui`), and LangChain. ## Project Structure - Source code: `src/` - Agent core: `src/agent/` (agent loop, prompts, scratchpad, token counting, types) - - CLI interface: `src/cli.tsx` (Ink/React), entry point: `src/index.tsx` - - Components: `src/components/` (Ink UI components) - - Hooks: `src/hooks/` (React hooks for agent runner, model selection, input history) + - CLI interface: `src/cli.ts` (pi-tui; no JSX), entry point: `src/index.tsx` (the `.tsx` extension is historical) + - Components: `src/components/` (pi-tui UI components, plain `.ts`) - Model/LLM: `src/model/llm.ts` (multi-provider LLM abstraction) - - Tools: `src/tools/` (financial search, web search, browser, skill tool) + - Tools: `src/tools/` (financial data, web search, browser, skill tool) - Tool descriptions: `src/tools/descriptions/` (rich descriptions injected into system prompt) - Finance tools: `src/tools/finance/` (prices, fundamentals, filings, insider trades, etc.) - - Search tools: `src/tools/search/` (Exa preferred, Tavily fallback) + - Korean tools: `src/tools/finance-kr/` (DART/KRX/Naver โ€” financials, filings, foreign ownership, short balance, NPS, market data) + - Search tools: `src/tools/search/` (Exa โ†’ Perplexity โ†’ Tavily โ†’ LangSearch fallback chain) - Browser: `src/tools/browser/` (Playwright-based web scraping) - Skills: `src/skills/` (SKILL.md-based extensible workflows, e.g. DCF valuation) - Utils: `src/utils/` (env, config, caching, token estimation, markdown tables) - - Evals: `src/evals/` (LangSmith evaluation runner with Ink UI) + - Evals: `src/evals/` (LangSmith evaluation runner). KR eval harness: `scripts/kr-eval/` (record/replay + LLM-judge) - Config: `.dexter/settings.json` (persisted model/provider selection) - Environment: `.env` (API keys; see `env.example`) - Scripts: `scripts/release.sh` @@ -27,16 +27,17 @@ - Runtime: Bun (primary). Use `bun` for all commands. - Install deps: `bun install` -- Run: `bun run start` or `bun run src/index.tsx` +- Run: `bun start` - Dev (watch mode): `bun run dev` - Type-check: `bun run typecheck` - Tests: `bun test` - Evals: `bun run src/evals/run.ts` (full) or `bun run src/evals/run.ts --sample 10` (sampled) +- KR evals: `bun run kr-eval` (live) / `kr-eval:record` / `kr-eval:replay` (deterministic, no keys) - CI runs `bun run typecheck` and `bun test` on push/PR. ## Coding Style & Conventions -- Language: TypeScript (ESM, strict mode). JSX via React (Ink for CLI rendering). +- Language: TypeScript (ESM, strict mode). The CLI renders via `@mariozechner/pi-tui` (no JSX in `src/cli.ts`). - Prefer strict typing; avoid `any`. - Keep files concise; extract helpers rather than duplicating code. - Add brief comments for tricky or non-obvious logic. @@ -45,7 +46,7 @@ ## LLM Providers -- Supported: OpenAI (default), Anthropic, Google, xAI (Grok), OpenRouter, Ollama (local). +- Supported: OpenAI (default), Anthropic, Google, xAI (Grok), OpenRouter, Moonshot, DeepSeek, Ollama (local). - Default model: `gpt-5.5`. Provider detection is prefix-based (`claude-` -> Anthropic, `gemini-` -> Google, etc.). - Fast models for lightweight tasks: see `FAST_MODELS` map in `src/model/llm.ts`. - Anthropic uses explicit `cache_control` on system prompt for prompt caching cost savings. @@ -53,18 +54,20 @@ ## Tools -- `financial_search`: primary tool for all financial data queries (prices, metrics, filings). Delegates to multiple sub-tools internally. -- `financial_metrics`: direct metric lookups (revenue, market cap, etc.). +- `get_financials`: US financial statements, metrics, segments (Financial Datasets). Routes multi-company/metric queries internally. +- `get_market_data`: US prices, company news, insider trades, 13F holdings. - `read_filings`: SEC filing reader for 10-K, 10-Q, 8-K documents. -- `web_search`: general web search (Exa if `EXASEARCH_API_KEY` set, else Tavily if `TAVILY_API_KEY` set). +- `stock_screener`: screen US stocks by financial criteria (P/E, growth, margins). +- Korean tools (`src/tools/finance-kr/`, all take 6-digit tickers): `get_financials_kr`, `get_filings_kr`, `get_large_holders_kr`, `get_insider_trades_kr`, `read_filings_kr` (DART, gated on `DART_API_KEY`); `get_market_data_kr`, `get_foreign_ownership_kr` (Naver, keyless); `get_short_balance_kr` (KRX login); `get_nps_holdings` (data.go.kr). +- `web_search`: general web search; provider fallback chain Exa โ†’ Perplexity โ†’ Tavily โ†’ LangSearch (registered if โ‰ฅ1 key set). - `browser`: Playwright-based web scraping for reading pages the agent discovers. - `skill`: invokes SKILL.md-defined workflows (e.g. DCF valuation). Each skill runs at most once per query. -- Tool registry: `src/tools/registry.ts`. Tools are conditionally included based on env vars. +- Tool registry: `src/tools/registry.ts`. US finance + screener tools register unconditionally (they 401 at call-time without `FINANCIAL_DATASETS_API_KEY`); KR, web_search, and X tools are gated by their env vars. ## Skills - Skills live as `SKILL.md` files with YAML frontmatter (`name`, `description`) and markdown body (instructions). -- Built-in skills: `src/skills/dcf/SKILL.md`. +- Built-in skills (`src/skills//SKILL.md`): `dcf` (DCF valuation; branches US/KR internally), `kr-spinoff` (๋ฌผ์ /์ธ์ ๋ถ„ํ•  analysis), `write-memo`, `x-research`. - Discovery: `src/skills/registry.ts` scans for SKILL.md files at startup. - Skills are exposed to the LLM as metadata in the system prompt; the LLM invokes them via the `skill` tool. @@ -73,15 +76,16 @@ - Agent loop: `src/agent/agent.ts`. Iterative tool-calling loop with configurable max iterations (default 10). - Scratchpad: `src/agent/scratchpad.ts`. Single source of truth for all tool results within a query. - Context management: Anthropic-style. Full tool results kept in context; oldest results cleared when token threshold exceeded. -- Final answer: generated in a separate LLM call with full scratchpad context (no tools bound). +- Final answer: the text of the turn where the model stops emitting tool calls (`handleDirectResponse`). Tools stay bound on every call โ€” there is no separate no-tools finalization pass, so answer quality is governed by the system prompt. - Events: agent yields typed events (`tool_start`, `tool_end`, `thinking`, `answer_start`, `done`, etc.) for real-time UI updates. ## Environment Variables -- LLM keys: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GOOGLE_API_KEY`, `XAI_API_KEY`, `OPENROUTER_API_KEY` +- LLM keys: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GOOGLE_API_KEY`, `XAI_API_KEY`, `OPENROUTER_API_KEY`, `MOONSHOT_API_KEY`, `DEEPSEEK_API_KEY` - Ollama: `OLLAMA_BASE_URL` (default `http://127.0.0.1:11434`) -- Finance: `FINANCIAL_DATASETS_API_KEY` -- Search: `EXASEARCH_API_KEY` (preferred), `TAVILY_API_KEY` (fallback) +- US finance: `FINANCIAL_DATASETS_API_KEY` +- Korean data: `DART_API_KEY` (์žฌ๋ฌดยท๊ณต์‹œ), `KRX_ID`+`KRX_PW` or `KRX_COOKIE` (๊ณต๋งค๋„), `DATA_GO_KR_SERVICE_KEY` (๊ตญ๋ฏผ์—ฐ๊ธˆ). Naver tools (์™ธ๊ตญ์ธยทํ˜„์žฌ๊ฐ€) need no key. +- Search: `EXASEARCH_API_KEY`, `PERPLEXITY_API_KEY`, `TAVILY_API_KEY`, `LANGSEARCH_API_KEY` (fallback chain in that order) - Tracing: `LANGSMITH_API_KEY`, `LANGSMITH_ENDPOINT`, `LANGSMITH_PROJECT`, `LANGSMITH_TRACING` - Never commit `.env` files or real API keys. diff --git a/CLAUDE.md b/CLAUDE.md index 6fb31a47a..42a223c17 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,8 +27,8 @@ CI runs `typecheck` + `test` on push/PR. These bite without warning. Fix once you learn them. -- **TUI is `@mariozechner/pi-tui`, not Ink.** `src/index.tsx` has the `.tsx` extension for historical reasons; there is no JSX in the active CLI (`src/cli.ts`). `AGENTS.md` and `README.md` still say Ink โ€” trust the code. -- **Tools are conditionally registered by env var** (`src/tools/registry.ts`). Missing `EXASEARCH_API_KEY` โ†’ exa just isn't a `web_search` candidate. Missing `FINANCIAL_DATASETS_API_KEY` โ†’ all `get_*` finance tools absent. +- **TUI is `@mariozechner/pi-tui`, not Ink.** `src/index.tsx` has the `.tsx` extension for historical reasons; there is no JSX in the active CLI (`src/cli.ts`). `src/components/` are plain-`.ts` pi-tui components (no React/JSX, no `src/hooks/`). Trust the code. +- **Registration is by env var โ€” but the US finance tools are NOT gated.** (`src/tools/registry.ts`) `get_financials`/`get_market_data`/`read_filings`/`stock_screener` and the keyless Naver KR tools (`get_market_data_kr`/`get_foreign_ownership_kr`) register **unconditionally**; without `FINANCIAL_DATASETS_API_KEY` the US ones stay bound to the LLM and 401 at call-time โ€” they are NOT absent. Actually gated: `DART_API_KEY` โ†’ the 5 KR DART tools; โ‰ฅ1 of `EXASEARCH_API_KEY`/`PERPLEXITY_API_KEY`/`TAVILY_API_KEY`/`LANGSEARCH_API_KEY` โ†’ `web_search` (missing one just drops that provider, not the tool); `KRX_*` / `DATA_GO_KR_SERVICE_KEY` / `X_BEARER_TOKEN` โ†’ their respective tools. - **`your-` is treated as a placeholder.** `checkApiKeyExists` (`src/utils/env.ts`) ignores values starting with `your-`. Copying `env.example` without replacing values = "key missing" behavior. - **Tool output shape feeds the UI.** `summarizeToolResult` in `src/cli.ts` switches on `tool` name and inspects `parsed.data` shape. New finance tools must return `{ data: ... }` and need a matching case here, or the UI shows stale status lines. - **`concurrencySafe` flag.** Each tool declares it; the executor parallelizes safe tools and serializes unsafe ones (browser, stateful APIs). Wrong value = subtle race conditions. @@ -87,6 +87,6 @@ These bite without warning. Fix once you learn them. ## Stale docs -- `AGENTS.md` is upstream's contributor doc with drift (says Ink/React, entry `cli.tsx`). -- `README.md` mentions Ink in places. The ๐Ÿ‡ฐ๐Ÿ‡ท section is current. +- `AGENTS.md` (upstream contributor doc): the TUI/tool drift has been corrected to match this fork (pi-tui not Ink, real tool names, KR tools, single-pass final answer). It still carries less KR detail than CLAUDE.md + README โ€” prefer those for Korean specifics. +- `README.md` is current (no Ink references; the ๐Ÿ‡ฐ๐Ÿ‡ท section is authoritative). - When in doubt, read the code. diff --git a/README.md b/README.md index 9fc680094..751b42a83 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Dexter๋Š” ๋ณต์žกํ•œ ๊ธˆ์œต ์งˆ๋ฌธ์„ ๋ฐ›์•„ ๋ช…ํ™•ํ•œ ๋‹จ๊ณ„๋ณ„ ๋ฆฌ์„œ์น˜ ๊ณ„ํš - **์ž๊ธฐ ๊ฒ€์ฆ**: ์ž์‹ ์˜ ์ž‘์—…์„ ์ ๊ฒ€ํ•˜๊ณ  ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋ฐ˜๋ณต - **์‹ค์‹œ๊ฐ„ ๊ธˆ์œต ๋ฐ์ดํ„ฐ**: ์†์ต๊ณ„์‚ฐ์„œยท์žฌ๋ฌด์ƒํƒœํ‘œยทํ˜„๊ธˆํ๋ฆ„ํ‘œ ์ ‘๊ทผ - **๋ฏธ๊ตญยทํ•œ๊ตญ ๋™์‹œ ์ง€์›**: ํ•˜๋‚˜์˜ ์—์ด์ „ํŠธ์—์„œ US/KR ์ข…๋ชฉ์„ ํ•จ๊ป˜ ๋‹ค๋ฃจ๊ณ  ๊ต์ฐจ ๋น„๊ต +- **1์ฐจ ์ถœ์ฒ˜ + ๊ฐ์‚ฌ ๊ฐ€๋Šฅ**: ๋‰ด์Šค(2์ฐจ)๊ฐ€ ์•„๋‹ˆ๋ผ DARTยทKRXยทNaver๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๊ณ , ๋ชจ๋“  ํˆด ํ˜ธ์ถœ์„ ์Šคํฌ๋ž˜์น˜ํŒจ๋“œ์— ๊ธฐ๋ก โ€” [์›น ์ฑ—๋ด‡๊ณผ ๋ฌด์—‡์ด ๋‹ค๋ฅธ์ง€](#-์›น-์ฑ—๋ด‡๊ณผ-๋น„๊ต) - **์•ˆ์ „์žฅ์น˜**: ๋ฌดํ•œ ๋ฃจํ”„ ๊ฐ์ง€์™€ ๋‹จ๊ณ„ ์ œํ•œ์„ ๋‚ด์žฅํ•ด ํญ์ฃผ ์‹คํ–‰ ๋ฐฉ์ง€ ### ์•„ํ‚คํ…์ฒ˜ @@ -56,6 +57,7 @@ DART์— ์—†๋Š” ํ•œ๊ตญ ํŠนํ™” ๋ฐ์ดํ„ฐ. ํˆด๋งˆ๋‹ค ์†Œ์Šคยทํ‚ค๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. | ํˆด | ๋Œ€์‘๋˜๋Š” ๋ฏธ๊ตญ ๊ฐœ๋… | ๋ฐ์ดํ„ฐ ์†Œ์Šค | ํ™œ์„ฑํ™” ์กฐ๊ฑด | |---|---|---|---| +| `get_market_data_kr` | `get_market_data` (ํ˜„์žฌ๊ฐ€ยท์‹œ์ด) | ํ˜„์žฌ๊ฐ€ยท์‹œ์ดยทPER/PBRยท๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šคยทpeer (Naver) | ํ•ญ์ƒ (ํ‚ค ๋ถˆํ•„์š”) | | `get_foreign_ownership_kr` | (๋ฏธ๊ตญ์—” ์—†์Œ) | ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ (Naver) | ํ•ญ์ƒ (ํ‚ค ๋ถˆํ•„์š”) | | `get_short_balance_kr` | Short interest | KRX ๊ณต๋งค๋„ ์ˆœ๋ณด์œ ์ž”๊ณ  | `KRX_ID`+`KRX_PW` ๋˜๋Š” `KRX_COOKIE` | | `get_nps_holdings` | (๋ฏธ๊ตญ์—” ์—†์Œ) | ๊ตญ๋ฏผ์—ฐ๊ธˆ ๊ตญ๋‚ด์ฃผ์‹ ํˆฌ์ž์ •๋ณด (data.go.kr) | `DATA_GO_KR_SERVICE_KEY` | @@ -73,7 +75,7 @@ LLM์ด "์‚ผ์„ฑ์ „์ž" ๊ฐ™์€ ์ž์—ฐ์–ด ์ž…๋ ฅ์„ ๋ฐ›์œผ๋ฉด, ํˆด ๋‚ด๋ถ€์—์„œ ** - **๋‹จ์œ„** โ€” ์›(KRW), ๋ฐฑ๋งŒ์›/์–ต/์กฐ ๋‹จ์œ„๋กœ ์ž๋™ ํฌ๋งทํŒ… - **๊ฑฐ๋ž˜์‹œ๊ฐ„** โ€” 09:00โ€“15:30 KST ๊ธฐ์ค€ - **์ƒํ•˜ํ•œ๊ฐ€** โ€” ยฑ30% ์ผ๊ฐ„ ๋ณ€๋™ ์ œํ•œ ๊ณ ๋ ค -- **DCF ์ž๋™ ๋ถ„๊ธฐ** โ€” 6์ž๋ฆฌ ํ‹ฐ์ปค๋ฉด DCF ์Šคํ‚ฌ์ด ํ•œ๊ตญ ๊ฒฝ๋กœ๋กœ ์ „ํ™˜(ํ•œ๊ตญ ๋ฒ•์ธ์„ธ์œจยท๊ตญ๊ณ ์ฑ„ ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌยทKRW). ๊ฑฐ๋ž˜์„ธยท๋ฐฐ๋‹น์„ธ๋Š” ํˆฌ์ž์ž ์„ธํ›„ ์‹คํ˜„์ˆ˜์ต๋ฅ  ์ฃผ์˜์‚ฌํ•ญ์œผ๋กœ ํ‘œ๊ธฐ +- **DCF ์ž๋™ ๋ถ„๊ธฐ** โ€” 6์ž๋ฆฌ ํ‹ฐ์ปค๋ฉด DCF ์Šคํ‚ฌ์ด ํ•œ๊ตญ ๊ฒฝ๋กœ๋กœ ์ „ํ™˜(ํ•œ๊ตญ ๋ฒ•์ธ์„ธ์œจ ~22%ยท๊ตญ๊ณ ์ฑ„ ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ ~3%ยทKRW). **์ˆœ๋ถ€์ฑ„๋ฅผ ๋ถ€์ฑ„์ด๊ณ„๊ฐ€ ์•„๋‹ˆ๋ผ ์ด์ž๋ถ€์ฑ„(์ฐจ์ž…๊ธˆยท์‚ฌ์ฑ„)์—์„œ ํ˜„๊ธˆยท๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ์„ ๋บ€ ๊ฐ’์œผ๋กœ ์‚ฐ์ •** โ€” ์‚ผ์„ฑ์ฒ˜๋Ÿผ ์ˆœํ˜„๊ธˆ ๊ธฐ์—…์€ ์ˆœํ˜„๊ธˆ์„ ๋”ํ•ด ์ฃผ์‹๊ฐ€์น˜๊ฐ€ EV๋ณด๋‹ค ์ปค์ง„๋‹ค(๋ถ€์ฑ„์ด๊ณ„๋กœ ์ž˜๋ชป ๋นผ๋ฉด ๋ถ€ํ˜ธ๊ฐ€ ๋’ค์ง‘ํ˜€ ์ €ํ‰๊ฐ€). ๊ฑฐ๋ž˜์„ธยท๋ฐฐ๋‹น์„ธ๋Š” ํˆฌ์ž์ž ์„ธํ›„ ์‹คํ˜„์ˆ˜์ต๋ฅ  ์ฃผ์˜์‚ฌํ•ญ์œผ๋กœ ํ‘œ๊ธฐ - **์žฌ๋ฒŒ ๊ทธ๋ฃน ๊ตฌ์กฐ** โ€” ์‚ผ์„ฑยทํ˜„๋Œ€์ฐจยทSKยทLG ๋“ฑ ๊ทธ๋ฃน ๋‚ด ์ง€๋ถ„ ๊ด€๊ณ„ ๋ถ„์„ ๊ฐ€๋Šฅ - **๋ฌผ์ ๋ถ„ํ•  ๋ถ„์„** โ€” LGํ™”ํ•™โ†’LG์—๋„ˆ์ง€์†”๋ฃจ์…˜ ๊ฐ™์€ ๋ถ„ํ•  ์ด๋ฒคํŠธ๋ฅผ ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ ๊ด€์ (ํฌ์„ยท์ง€์ฃผ์‚ฌ ๋””์Šค์นด์šดํŠธ)์—์„œ ํ‰๊ฐ€ํ•˜๋Š” ์ „์šฉ ์Šคํ‚ฌ - **๊ต์ฐจ์‹ ํ˜ธ ์ข…ํ•ฉ** โ€” ์žฌ๋ฌด(์ •๊ทœํ™” ์†์ต)ยท์™ธ๊ตญ์ธ ์ˆ˜๊ธ‰ยท๊ณต๋งค๋„ยท๋Œ€๋Ÿ‰๋ณด์œ ยท๊ณต์‹œ๋ฅผ ๋‹จ์ˆœ ๋‚˜์—ด์ด ์•„๋‹ˆ๋ผ **ํ•˜๋‚˜์˜ ํˆฌ์ž thesis๋กœ ์—ฎ์–ด** ์ œ์‹œ(์ˆ˜๊ธ‰ยท์‹ค์ ยท์ง€๋ฐฐ๊ตฌ์กฐ๊ฐ€ ๊ฐ™์€ ๋ฐฉํ–ฅ์ธ์ง€ ์ถฉ๋Œ์ธ์ง€ ํŒ๋‹จ, ๋Œ€๋Ÿ‰๋ณด์œ  ์ง‘์ค‘์€ ๋ฐธ๋ฅ˜์—์ด์…˜ ์š”์ธ์œผ๋กœ ์ฒ˜๋ฆฌ) @@ -119,8 +121,9 @@ LLM์ด "์‚ผ์„ฑ์ „์ž" ๊ฐ™์€ ์ž์—ฐ์–ด ์ž…๋ ฅ์„ ๋ฐ›์œผ๋ฉด, ํˆด ๋‚ด๋ถ€์—์„œ ** ### ํ•œ๊ตญ ํŠนํ™” ๋ฐ์ดํ„ฐ ``` +> ์‚ผ์„ฑ์ „์ž ํ˜„์žฌ๊ฐ€๋ž‘ ๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค ์•Œ๋ ค์ค˜ # ํ‚ค ๋ถˆํ•„์š” (Naver) > ์—์ฝ”ํ”„๋กœ๋น„์—  ๊ณต๋งค๋„ ์ˆœ๋ณด์œ ์ž”๊ณ  ์ถ”์ด ์–ด๋•Œ? # KRX ํ‚ค ํ•„์š” -> ์‚ผ์„ฑ์ „์ž ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ ์ตœ๊ทผ ์ถ”์„ธ ๋ณด์—ฌ์ค˜ +> ์‚ผ์„ฑ์ „์ž ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ ์ตœ๊ทผ ์ถ”์„ธ ๋ณด์—ฌ์ค˜ # ํ‚ค ๋ถˆํ•„์š” (Naver) > ๊ตญ๋ฏผ์—ฐ๊ธˆ์ด ๋ณด์œ ํ•œ ์‚ผ์„ฑ์ „์ž ์ง€๋ถ„์œจ ์•Œ๋ ค์ค˜ # data.go.kr ํ‚ค ํ•„์š” ``` @@ -151,7 +154,7 @@ LLM์ด "์‚ผ์„ฑ์ „์ž" ๊ฐ™์€ ์ž์—ฐ์–ด ์ž…๋ ฅ์„ ๋ฐ›์œผ๋ฉด, ํˆด ๋‚ด๋ถ€์—์„œ ** ## ๐Ÿ†š ์›น ์ฑ—๋ด‡๊ณผ ๋น„๊ต -2026๋…„ 5์›”, ๋™์ผํ•œ ์งˆ๋ฌธ 5๊ฐœ๋ฅผ **Dexter(LLM: GPT-5.5)** ์™€ **claude.ai(Opus 4.8, ์›น๊ฒ€์ƒ‰ ์‚ฌ์šฉ)** ์— ๋˜์ ธ ์‹ค์ธก ๋น„๊ตํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค ํ”„๋Ÿฐํ‹ฐ์–ด ๋ชจ๋ธ์ด๋ฏ€๋กœ ์ฐจ์ด๋ฅผ ๋งŒ๋“œ๋Š” ๊ฑด ๋ชจ๋ธ์ด ์•„๋‹ˆ๋ผ **๊ธˆ์œต ํ•˜๋„ค์Šค โ€” 1์ฐจ ์ถœ์ฒ˜ ๋„๊ตฌ(DARTยทKRXยทNaver) + ํ•œ๊ตญ ์‹œ์žฅ ์Šคํ‚ฌ** ์ž…๋‹ˆ๋‹ค. *(์•„๋ž˜ ์ˆ˜์น˜๋Š” ๊ฐ ๋„๊ตฌ๊ฐ€ ๋ฐ˜ํ™˜ํ•œ ๊ทธ๋Œ€๋กœ์ด๋ฉฐ, ๋ณ„๋„ ์ •๋ฐ€ ๊ฐ์‚ฌ๋Š” ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์‚ผ์„ฑ ์†์ต ํ–‰์€ ์žฌ๋ฌด ์ •๊ทœํ™” ๊ฐœ์„  ์ดํ›„ ์žฌ์ธก์ •.)* +2026๋…„ 5์›”, ๋™์ผํ•œ ์งˆ๋ฌธ 5๊ฐœ๋ฅผ **Dexter(LLM: GPT-5.5)** ์™€ **claude.ai(Opus 4.8, ์›น๊ฒ€์ƒ‰ ์‚ฌ์šฉ)** ์— ๋˜์ ธ ์‹ค์ธก ๋น„๊ตํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค ํ”„๋Ÿฐํ‹ฐ์–ด ๋ชจ๋ธ์ด๋ฏ€๋กœ ์ฐจ์ด๋ฅผ ๋งŒ๋“œ๋Š” ๊ฑด ๋ชจ๋ธ์ด ์•„๋‹ˆ๋ผ **๊ธˆ์œต ํ•˜๋„ค์Šค โ€” 1์ฐจ ์ถœ์ฒ˜ ๋„๊ตฌ(DARTยทKRXยทNaver) + ํ•œ๊ตญ ์‹œ์žฅ ์Šคํ‚ฌ** ์ž…๋‹ˆ๋‹ค. *(์•„๋ž˜ ์ˆ˜์น˜๋Š” ๊ฐ ๋„๊ตฌ๊ฐ€ ๋ฐ˜ํ™˜ํ•œ ๊ทธ๋Œ€๋กœ์ด๋ฉฐ, ๋ณ„๋„ ์ •๋ฐ€ ๊ฐ์‚ฌ๋Š” ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์‚ผ์„ฑ ์†์ต ํ–‰์€ ์žฌ๋ฌด ์ •๊ทœํ™” ๊ฐœ์„  ์ดํ›„ ์žฌ์ธก์ •. ๋„ค์ด๋ฒ„ DCF ํ–‰์€ ์ˆœ๋ถ€์ฑ„(์ˆœํ˜„๊ธˆ) ๋ณด์ • ์ด์ „ ์ธก์ •๊ฐ’์ด๋ผ ํ˜„ํ–‰ ์ฝ”๋“œ๋กœ๋Š” ๋” ๋†’๊ฒŒ ๋‚˜์˜ฌ ์ˆ˜ ์žˆ์Œ.)* | ์งˆ๋ฌธ | claude.ai (Opus 4.8 + ์›น๊ฒ€์ƒ‰) | Dexter (GPT-5.5 + ๊ธˆ์œต ํ•˜๋„ค์Šค) | |---|---|---| @@ -163,9 +166,10 @@ LLM์ด "์‚ผ์„ฑ์ „์ž" ๊ฐ™์€ ์ž์—ฐ์–ด ์ž…๋ ฅ์„ ๋ฐ›์œผ๋ฉด, ํˆด ๋‚ด๋ถ€์—์„œ ** **Dexter๊ฐ€ ๋งŒ๋“  ์ฐจ์ด** - **๊ตฌ์กฐํ™” ์žฌ๋ฌด ์š”์•ฝ** โ€” DART ์›์‹œ ๋ผ์ธ์•„์ดํ…œ์„ ๋งค์ถœยท์˜์—…์ด์ตยท์ˆœ์ด์ตยท๋งˆ์ง„ยทROEยทFCFยทYoY๋กœ ์ •๊ทœํ™”ํ•ด ์†์ต์„ ์ผ๊ด€๋˜๊ฒŒ ์ถ”์ถœ(๋Œ€์šฉ๋Ÿ‰ ์›๋ณธ์„ ๋ชป ์ฝ์–ด ๋ณด๋ฅ˜ํ•˜๋˜ ๋ฌธ์ œ ํ•ด๊ฒฐ). ์™ธ๊ตญ์ธยท๊ณต๋งค๋„ยท๋Œ€๋Ÿ‰๋ณด์œ ยท๊ตญ๋ฏผ์—ฐ๊ธˆ๊ณผ **๊ต์ฐจ ์ข…ํ•ฉ**ํ•ด ๋‹จ์ผ thesis๋กœ ์ œ์‹œ -- **1์ฐจ ์ถœ์ฒ˜ ์ง์ ‘** โ€” DART(์žฌ๋ฌดยท๊ณต์‹œ)ยทKRX(๊ณต๋งค๋„)ยทNaver(์™ธ๊ตญ์ธ)๋ฅผ API๋กœ ์ง์ ‘ ํ˜ธ์ถœ โ†’ ๋‰ด์Šค(2์ฐจ)๋ณด๋‹ค ์ •๋ฐ€ยท์ตœ์‹ ยท์ผ๋ณ„ +- **1์ฐจ ์ถœ์ฒ˜ ์ง์ ‘** โ€” DART(์žฌ๋ฌดยท๊ณต์‹œ)ยทKRX(๊ณต๋งค๋„)ยทNaver(์™ธ๊ตญ์ธ ์ง€๋ถ„์œจยทํ˜„์žฌ๊ฐ€ยท๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค)๋ฅผ API๋กœ ์ง์ ‘ ํ˜ธ์ถœ โ†’ ๋‰ด์Šค(2์ฐจ)๋ณด๋‹ค ์ •๋ฐ€ยท์ตœ์‹ ยท์ผ๋ณ„. ์™ธ๊ตญ์ธยทํ˜„์žฌ๊ฐ€๋Š” **API ํ‚ค ์—†์ด๋„** ๋™์ž‘(DART ํ‚ค ํ•˜๋‚˜๋ฉด ์ฑ—๋ด‡ ๋Œ€๋น„ ์šฐ์œ„) - **๊ตฌ์กฐํ™” ์‹œ๊ณ„์—ด** โ€” ๊ณต๋งค๋„ 18์ผ์น˜, ์™ธ๊ตญ์ธ ์ผ์ž๋ณ„์ฒ˜๋Ÿผ ๋‰ด์Šค์—” ์—†๋Š” ๋ฐ์ดํ„ฐ -- **๊ฐ์‚ฌ ๊ฐ€๋Šฅ** โ€” ๋ชจ๋“  ํˆด ํ˜ธ์ถœ์ด ์Šคํฌ๋ž˜์น˜ํŒจ๋“œ์— ๊ธฐ๋ก โ†’ ๊ฐ™์€ ์งˆ๋ฌธ์ด๋ฉด ๊ฐ™์€ ๋ฐ์ดํ„ฐ ๊ฒฝ๋กœ +- **์ •ํ™•ํ•œ ๋ฐธ๋ฅ˜์—์ด์…˜** โ€” DCF๊ฐ€ ์ˆœ๋ถ€์ฑ„๋ฅผ **์ด์ž๋ถ€์ฑ„ ๊ธฐ์ค€**(๋ถ€์ฑ„์ด๊ณ„ ์•„๋‹˜)์œผ๋กœ ์žก์•„ ์ˆœํ˜„๊ธˆ ๊ธฐ์—…(์‚ผ์„ฑ ๋“ฑ)์„ ์ €ํ‰๊ฐ€ํ•˜์ง€ ์•Š์Œ. ์ฑ—๋ด‡์ด ํ”ํžˆ ์“ฐ๋Š” "๋ถ€์ฑ„์ด๊ณ„=์ˆœ๋ถ€์ฑ„" ์–ด๋ฆผ์…ˆ์€ ์ˆœํ˜„๊ธˆ ๊ธฐ์—…์˜ ๋ถ€ํ˜ธ๋ฅผ ๋’ค์ง‘์–ด ์ ์ •๊ฐ€๋ฅผ ๊นŽ๋Š”๋‹ค +- **๊ฐ์‚ฌยท์žฌํ˜„ ๊ฐ€๋Šฅ** โ€” ๋ชจ๋“  ํˆด ํ˜ธ์ถœ์„ ์Šคํฌ๋ž˜์น˜ํŒจ๋“œ(JSONL)์— ๊ธฐ๋กํ•˜๊ณ , KR ํ‰๊ฐ€ ํ•˜๋„ค์Šค๊ฐ€ record/replay๋กœ ๊ฐ™์€ ์งˆ๋ฌธ์— ๊ฐ™์€ ๋ฐ์ดํ„ฐ ๊ฒฝ๋กœ๋ฅผ ๋ณด์žฅ(์ฑ—๋ด‡์˜ ๋น„๊ฒฐ์ •์  ๋‹ต๊ณผ ๋Œ€๋น„) - **KR ์ž๋™ ํŠœ๋‹** โ€” 6์ž๋ฆฌ ํ‹ฐ์ปค๋ฉด DCF๊ฐ€ K-IFRSยทKRWยท์„ธํ›„ ์‹คํ˜„์ˆ˜์ต๋ฅ  ๊ฒฝ๋กœ๋กœ ๋ถ„๊ธฐ - **ํ™˜๊ฐ๋ณด๋‹ค ์ •์ง** โ€” ๋ฐ์ดํ„ฐ๋ฅผ ๋ชป ์–ป์œผ๋ฉด ๋งŒ๋“ค์–ด๋‚ด์ง€ ์•Š๊ณ  "๋ชป ์–ป๋Š”๋‹ค"๊ณ  ํ•จ @@ -218,6 +222,7 @@ OPENAI_API_KEY=your-openai-api-key # ANTHROPIC_API_KEY=... (์„ ํƒ) # GOOGLE_API_KEY=... (์„ ํƒ) # XAI_API_KEY=... / OPENROUTER_API_KEY=... (์„ ํƒ) +# MOONSHOT_API_KEY=... / DEEPSEEK_API_KEY=...(์„ ํƒ) # OLLAMA_BASE_URL=http://127.0.0.1:11434 (๋กœ์ปฌ LLM) # --- ๋ฏธ๊ตญ ์‹œ์žฅ ๋ฐ์ดํ„ฐ --- @@ -231,9 +236,11 @@ DART_API_KEY=your-dart-api-key # ์žฌ๋ฌดยท๊ณต์‹œ (ํ•„์ˆ˜: KR ํŽ€๋” # DATA_GO_KR_SERVICE_KEY=... # ๊ตญ๋ฏผ์—ฐ๊ธˆ ๋ณด์œ  (data.go.kr, Decoded ํ‚ค) # READ_FILINGS_KR_MODEL=... # (์„ ํƒ) read_filings_kr ๋‚ด๋ถ€ ์š”์•ฝ ๋ชจ๋ธ ์˜ค๋ฒ„๋ผ์ด๋“œ. ๊ธฐ๋ณธ์€ provider์˜ fast ๋ชจ๋ธ(์ €๋น„์šฉ) -# --- ์›น ๊ฒ€์ƒ‰ (์„ ํƒ, Exa ์šฐ์„  โ†’ Tavily ํด๋ฐฑ) --- +# --- ์›น ๊ฒ€์ƒ‰ (์„ ํƒ, Exa โ†’ Perplexity โ†’ Tavily โ†’ LangSearch ํด๋ฐฑ) --- # EXASEARCH_API_KEY=your-exa-api-key +# PERPLEXITY_API_KEY=your-perplexity-api-key # TAVILY_API_KEY=your-tavily-api-key +# LANGSEARCH_API_KEY=your-langsearch-api-key # --- X/ํŠธ์œ„ํ„ฐ ์„ผํ‹ฐ๋จผํŠธ (์„ ํƒ) --- # X_BEARER_TOKEN=your-X-bearer-token @@ -253,12 +260,20 @@ bun dev # ๊ฐœ๋ฐœ์šฉ watch ๋ชจ๋“œ Dexter์—๋Š” ๊ธˆ์œต ์งˆ๋ฌธ ๋ฐ์ดํ„ฐ์…‹์œผ๋กœ ์—์ด์ „ํŠธ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋Š” ํ‰๊ฐ€(eval) ์Šค์œ„ํŠธ๊ฐ€ ํฌํ•จ๋ผ ์žˆ์Šต๋‹ˆ๋‹ค. ํ‰๊ฐ€๋Š” ์ถ”์ ์— LangSmith๋ฅผ, ์ •๋‹ต ์ฑ„์ ์— LLM-as-judge ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ```bash -bun run src/evals/run.ts # ์ „์ฒด ์งˆ๋ฌธ +bun run src/evals/run.ts # ์ „์ฒด ์งˆ๋ฌธ (๋ฏธ๊ตญ ์ค‘์‹ฌ) bun run src/evals/run.ts --sample 10 # ๋ฌด์ž‘์œ„ ์ƒ˜ํ”Œ 10๊ฐœ ``` ํ‰๊ฐ€ ๋Ÿฌ๋„ˆ๋Š” ์ง„ํ–‰ ์ƒํ™ฉ, ํ˜„์žฌ ์งˆ๋ฌธ, ์‹ค์‹œ๊ฐ„ ์ •ํ™•๋„ ํ†ต๊ณ„๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” UI๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ๋Š” ๋ถ„์„์„ ์œ„ํ•ด LangSmith์— ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค. +**ํ•œ๊ตญ ์ฃผ์‹ ํ‰๊ฐ€ ํ•˜๋„ค์Šค** โ€” ๊ณ ์ •๋œ ํ•œ๊ตญ ์งˆ๋ฌธ ๋ฑ…ํฌ + record/replay + LLM-judge ์ฑ„์ . `record`๋กœ ์‹ค์ œ DARTยทKRXยทNaver ์‘๋‹ต์„ ํ”ฝ์Šค์ฒ˜๋กœ ์ €์žฅํ•ด๋‘๋ฉด `replay`๋Š” ๋„คํŠธ์›Œํฌยทํ‚ค ์—†์ด ๊ฒฐ์ •์ ์œผ๋กœ ์žฌํ˜„ยทํšŒ๊ท€ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค("๊ฐ™์€ ์งˆ๋ฌธ โ†’ ๊ฐ™์€ ๋ฐ์ดํ„ฐ ๊ฒฝ๋กœ"๋ฅผ ๊ฒ€์ฆ โ€” ์ฑ—๋ด‡์˜ ๋น„๊ฒฐ์ •์  ๋‹ต๊ณผ ๋Œ€๋น„). + +```bash +bun run kr-eval # ๋ผ์ด๋ธŒ ์ฑ„์  +bun run kr-eval:record # ์‹ค์ œ ์‘๋‹ต์„ ํ”ฝ์Šค์ฒ˜๋กœ ๊ธฐ๋ก +bun run kr-eval:replay # ํ”ฝ์Šค์ฒ˜๋กœ ๊ฒฐ์ •์  ์žฌํ˜„ (ํ‚ค ๋ถˆํ•„์š”) +``` + ### ๐Ÿ› ๋””๋ฒ„๊น… Dexter๋Š” ๋””๋ฒ„๊น…๊ณผ ์ด๋ ฅ ์ถ”์ ์„ ์œ„ํ•ด ๋ชจ๋“  ํˆด ํ˜ธ์ถœ์„ ์Šคํฌ๋ž˜์น˜ํŒจ๋“œ(scratchpad) ํŒŒ์ผ์— ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ์ฟผ๋ฆฌ๋Š” `.dexter/scratchpad/`์— ์ƒˆ JSONL ํŒŒ์ผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. diff --git a/src/skills/dcf/SKILL.md b/src/skills/dcf/SKILL.md index 7f4f86156..73b9b140d 100644 --- a/src/skills/dcf/SKILL.md +++ b/src/skills/dcf/SKILL.md @@ -39,7 +39,11 @@ DCF ๋ถ„์„ ์ง„ํ–‰: > **์ถœ๋ ฅ์€ `periods[].summary` ์— ์ •๊ทœํ™”๋ผ ์žˆ๋‹ค(KRW).** ๋ผ๋ฒจ์„ ์ง์ ‘ ํŒŒ์‹ฑํ•˜์ง€ ๋ง๊ณ  ์ด ํ•„๋“œ๋ฅผ ์ฝ์–ด๋ผ: > - ํ˜„๊ธˆํ๋ฆ„: `cashFlow.operating`(=์˜์—…ํ˜„๊ธˆํ๋ฆ„), `cashFlow.capex`, `ratios.freeCashFlow`(=์˜์—…CFโˆ’|capex|). `free_cash_flow`๊ฐ€ ์—†์œผ๋ฉด ์ด ๊ฐ’์„ ์“ด๋‹ค. > - ์†์ต: `incomeStatement.revenue / operatingProfit / netIncome`, `ratios.revenueYoYPct`. -> - ์žฌ๋ฌด์ƒํƒœํ‘œ: `balanceSheet.totalLiabilities`(์ˆœ๋ถ€์ฑ„ ๊ทผ์‚ฌ; ๊ฐ€๋Šฅํ•˜๋ฉด ์ด์ž๋ถ€์ฑ„๋งŒ ๋ณ„๋„ ํ™•์ธ), `balanceSheet.cashAndEquivalents`, `balanceSheet.totalEquity`. +> - ์žฌ๋ฌด์ƒํƒœํ‘œ โ€” **์ˆœ๋ถ€์ฑ„(Net Debt)๋Š” ๋ถ€์ฑ„์ด๊ณ„๊ฐ€ ์•„๋‹ˆ๋‹ค. ์ด์ž๋ถ€์ฑ„๋งŒ ์“ด๋‹ค:** +> - `balanceSheet.totalDebt`(์ด์ž๋ถ€์ฑ„ ํ•ฉ๊ณ„ = ๋‹จ๊ธฐ์ฐจ์ž…๊ธˆ+์œ ๋™์„ฑ์žฅ๊ธฐ๋ถ€์ฑ„+์‚ฌ์ฑ„+์žฅ๊ธฐ์ฐจ์ž…๊ธˆ+์ „ํ™˜์‚ฌ์ฑ„), `balanceSheet.cashAndEquivalents`(ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ), `balanceSheet.shortTermInvestments`(๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ), `balanceSheet.totalEquity`. +> - **Net Debt = `totalDebt` โˆ’ (`cashAndEquivalents` + `shortTermInvestments`)** (`shortTermInvestments`ยท`cashAndEquivalents`๊ฐ€ null์ด๋ฉด 0์œผ๋กœ ๋ณธ๋‹ค). ์Œ์ˆ˜๋ฉด **์ˆœํ˜„๊ธˆ(net cash)** ๊ธฐ์—… โ€” ์ด๋•Œ ์ฃผ์‹๊ฐ€์น˜๋Š” EV๋ณด๋‹ค **์ปค์ง„๋‹ค**(์ˆœํ˜„๊ธˆ์„ ๋”ํ•œ๋‹ค). ์‚ผ์„ฑ์ „์ž์ฒ˜๋Ÿผ ์ฐจ์ž…๊ธˆ์ด ์ž‘๊ณ  ํ˜„๊ธˆ+๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ์ด ํฐ ์ข…๋ชฉ์ด ๋Œ€ํ‘œ์ (๋ถ€์ฑ„์ด๊ณ„๋ฅผ ์ˆœ๋ถ€์ฑ„๋กœ ์“ฐ๋ฉด ๋ถ€ํ˜ธ๊ฐ€ ๋’ค์ง‘ํ˜€ ์ €ํ‰๊ฐ€). +> - `totalDebt`๊ฐ€ `null`์ด๋ฉด(์€ํ–‰ยท์ง€์ฃผ์‚ฌ ๋“ฑ ์ด์ž๋ถ€์ฑ„ ๋ผ๋ฒจ ๋น„ํ‘œ์ค€) `rawLineItemsFile`์„ `read_file`๋กœ ์—ด์–ด ์ฐจ์ž…๊ธˆยท์‚ฌ์ฑ„ ๋ผ์ธ์„ **`account_nm`(๋ผ๋ฒจ) ์šฐ์„ , `account_id` ๋ณด์กฐ**๋กœ ์ง์ ‘ ํ•ฉ์‚ฐํ•˜๋ผ(์‚ผ์„ฑ์ฒ˜๋Ÿผ ์ฐจ์ž…๊ธˆ `account_id`๊ฐ€ `-ํ‘œ์ค€๊ณ„์ •์ฝ”๋“œ ๋ฏธ์‚ฌ์šฉ-`์ธ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„ ๋ผ๋ฒจ์ด ๋” ์•ˆ์ •์ ์ด๋‹ค). ๊ทธ๋ž˜๋„ ๋ถˆ๋ช…ํ™•ํ•˜๋ฉด ๋ณด๊ณ ๋œ `enterprise_value`ยท์‹œ์ด ๊ธฐ๋ฐ˜ ๋ธŒ๋ฆฌ์ง€๋กœ ํด๋ฐฑ. +> - `balanceSheet.totalLiabilities`(๋ถ€์ฑ„์ด๊ณ„)ยท`ratios.debtToEquityPct`๋Š” ๋ ˆ๋ฒ„๋ฆฌ์ง€ ์ ๊ฒ€์šฉ์ผ ๋ฟ **์ˆœ๋ถ€์ฑ„๊ฐ€ ์•„๋‹ˆ๋‹ค.** ๋ฆฌ์Šค๋ถ€์ฑ„๋Š” totalDebt์— ํฌํ•จํ•˜์ง€ ์•Š๋Š”๋‹ค(์˜์—…์„ฑ; FCF์— ์ด๋ฏธ ๋ฐ˜์˜). > - ๊ฐ metric์€ `{ current, prior, label, display }` ๊ตฌ์กฐ์ด๋ฉฐ `current`(๋‹น๊ธฐ)๋ฅผ ์“ด๋‹ค. ๋ถ„๊ธฐยท๋ฐ˜๊ธฐ ์†์ต/ํ˜„๊ธˆํ๋ฆ„์€ ๋ˆ„์ (YTD)์ž„์„ `summary.basis`๊ฐ€ ์•Œ๋ ค์ค€๋‹ค. > - ๋ฐœํ–‰์ฃผ์‹์ˆ˜(`outstanding_shares`)๋Š” summary์— ์—†๋‹ค โ€” `get_market_data_kr`์˜ `valuation.sharesOutstanding`(์‹œ์ดรทํ˜„์žฌ๊ฐ€ ๋„์ถœ)์„ ์“ฐ๊ณ , ์ •ํ™•ํ•œ ์ƒ์žฅ์ฃผ์‹์ˆ˜๊ฐ€ ํ•„์š”ํ•˜๋ฉด `get_short_balance_kr`์˜ `listedShares`๋กœ ๋ณด์™„ํ•œ๋‹ค. > @@ -123,7 +127,16 @@ DCF ๋ถ„์„ ์ง„ํ–‰: ## Step 5: ํ˜„์žฌ๊ฐ€์น˜ ๊ณ„์‚ฐ -๋ชจ๋“  FCF๋ฅผ ํ• ์ธ โ†’ ํ•ฉ์‚ฐํ•ด ๊ธฐ์—…๊ฐ€์น˜(Enterprise Value) โ†’ ์ˆœ๋ถ€์ฑ„(Net Debt) ์ฐจ๊ฐ โ†’ `outstanding_shares`๋กœ ๋‚˜๋ˆ  ์ฃผ๋‹น ์ ์ •๊ฐ€์น˜๋ฅผ ๊ตฌํ•œ๋‹ค. +๋ชจ๋“  FCF๋ฅผ ํ˜„์žฌ๊ฐ€์น˜๋กœ ํ• ์ธํ•˜๊ณ  ํ•ฉ์‚ฐํ•ด **๊ธฐ์—…๊ฐ€์น˜(Enterprise Value, EV)**๋ฅผ ๊ตฌํ•œ๋‹ค. ๊ทธ๋‹ค์Œ EV๋ฅผ **์ฃผ์‹๊ฐ€์น˜(Equity Value)**๋กœ ์ „ํ™˜ํ•œ๋‹ค: + +> **Equity Value = EV โˆ’ Net Debt**, ์—ฌ๊ธฐ์„œ **Net Debt = Total Debt โˆ’ Cash โˆ’ Short-term Investments** + +**๋ถ€ํ˜ธ ๊ทœ์น™(์ค‘์š”):** Net Debt๊ฐ€ ์Œ์ˆ˜์ด๋ฉด ํšŒ์‚ฌ๋Š” **์ˆœํ˜„๊ธˆ(net cash)** ์ƒํƒœ๋‹ค โ€” ํ˜„๊ธˆ์„ฑ์ž์‚ฐ์ด ์ฐจ์ž…๊ธˆ๋ณด๋‹ค ๋งŽ๋‹ค. ์ด๋•Œ๋Š” ์ˆœํ˜„๊ธˆ์„ **๋”ํ•˜๋ฏ€๋กœ Equity Value๊ฐ€ EV๋ณด๋‹ค ์ปค์ง„๋‹ค.** ์ˆœ๋ถ€์ฑ„๋ฅผ ๋ฌด์กฐ๊ฑด "์ฐจ๊ฐ"์œผ๋กœ๋งŒ ์ƒ๊ฐํ•ด ๋ถ€ํ˜ธ๋ฅผ ๋’ค์ง‘์ง€ ๋งˆ๋ผ. **์ด๋ถ€์ฑ„(๋ถ€์ฑ„์ด๊ณ„)๋ฅผ ์ˆœ๋ถ€์ฑ„๋กœ ํ˜ผ๋™ํ•˜๋ฉด ์ˆœํ˜„๊ธˆ ๊ธฐ์—…์„ ์ €ํ‰๊ฐ€ํ•œ๋‹ค** (์ด์ž๋ถ€์ฑ„๋งŒ Net Debt์— ๋“ค์–ด๊ฐ„๋‹ค). + +๋งˆ์ง€๋ง‰์œผ๋กœ `outstanding_shares`๋กœ ๋‚˜๋ˆ  ์ฃผ๋‹น ์ ์ •๊ฐ€์น˜๋ฅผ ๊ตฌํ•œ๋‹ค. + +- **US ๊ฒฝ๋กœ:** Total Debt=`total_debt`, Cash=`cash_and_equivalents`, Short-term Investments=`current_investments`(์—†์œผ๋ฉด 0) โ€” ยง1.3์—์„œ ์ถ”์ถœ. +- **๐Ÿ‡ฐ๐Ÿ‡ท KR ๊ฒฝ๋กœ:** Total Debt=`balanceSheet.totalDebt`, Cash=`balanceSheet.cashAndEquivalents`, Short-term Investments=`balanceSheet.shortTermInvestments`(์—†์œผ๋ฉด 0), ์ฃผ์‹์ˆ˜=`valuation.sharesOutstanding`. `totalDebt`๊ฐ€ `null`์ด๋ฉด ยง1 KR override์˜ ํด๋ฐฑ(rawLineItemsFile ์ง์ ‘ ํ•ฉ์‚ฐ ๋˜๋Š” ๋ณด๊ณ ๋œ EV ๋ธŒ๋ฆฌ์ง€)์„ ๋”ฐ๋ฅธ๋‹ค. ## Step 6: ๋ฏผ๊ฐ๋„ ๋ถ„์„ @@ -144,6 +157,8 @@ DCF ๋ถ„์„ ์ง„ํ–‰: 3. **์ฃผ๋‹น ๊ต์ฐจ ์ ๊ฒ€**: `free_cash_flow_per_share ร— 15~25`์™€ ๋น„๊ตํ•ด ๋Œ€๋žต์  ํ•ฉ๋ฆฌ์„ฑ ์ ๊ฒ€ +4. **์ˆœ๋ถ€์ฑ„ ๋ถ€ํ˜ธยท๊ทœ๋ชจ ์ ๊ฒ€**: Net Debt ๋ถ€ํ˜ธ๊ฐ€ ํšŒ์‚ฌ ์‹คํƒœ์™€ ๋งž๋Š”์ง€ ํ™•์ธํ•˜๋ผ. ์ฐจ์ž…๊ธˆ์ด ํ˜„๊ธˆ+๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ๋ณด๋‹ค ์ž‘์€๋ฐ ์–‘(+)์˜ ํฐ Net Debt๊ฐ€ ๋‚˜์˜ค๋ฉด ์ด๋ถ€์ฑ„๋ฅผ ์ž˜๋ชป ์“ด ๊ฒƒ์ด๋‹ค(์ˆœํ˜„๊ธˆ ๊ธฐ์—…์€ Net Debt<0, Equity Value>EV). ๋˜ |Net Debt|๊ฐ€ ์‹œ๊ฐ€์ด์•ก ๋Œ€๋น„ ๋น„ํ˜„์‹ค์ ์œผ๋กœ ํฌ๋ฉด(์˜ˆ: ์‹œ์ด์˜ ์ ˆ๋ฐ˜ ์ดˆ๊ณผ) ์ฐจ์ž…๊ธˆ ํ•ฉ์‚ฐ ๋ˆ„๋ฝ/๊ณผ๋‹ค๋ฅผ ์˜์‹ฌํ•˜๊ณ  `ratios.debtToEquityPct`ยท`rawLineItemsFile`๊ณผ ๊ต์ฐจ ํ™•์ธํ•˜๋ผ. + ๊ฒ€์ฆ์— ์‹คํŒจํ•˜๋ฉด ๊ฒฐ๊ณผ๋ฅผ ์ œ์‹œํ•˜๊ธฐ ์ „์— ๊ฐ€์ •์„ ์žฌ๊ณ ํ•˜๋ผ. ## Step 8: ์ถœ๋ ฅ ํ˜•์‹ diff --git a/src/tools/finance-kr/normalize-financials.test.ts b/src/tools/finance-kr/normalize-financials.test.ts index 334e9c575..b187086e5 100644 --- a/src/tools/finance-kr/normalize-financials.test.ts +++ b/src/tools/finance-kr/normalize-financials.test.ts @@ -3,8 +3,10 @@ import { parseAmount, formatKrw, findMetric, + sumMetrics, summarizePeriod, ACCOUNT_SPECS, + DEBT_SUM_SPEC, type DartRow, } from './normalize-financials.js'; @@ -145,3 +147,105 @@ describe('summarizePeriod', () => { expect(empty.ratios.freeCashFlow).toBeNull(); }); }); + +// Net-debt inputs (totalDebt = interest-bearing borrowings; shortTermInvestments = ๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ). +// Fixtures use REAL DART CFS line items so account_id/account_nm reflect actual filings: +// - ์‚ผ์„ฑ์ „์ž 005930 lists ๋‹จ๊ธฐ์ฐจ์ž…๊ธˆ with account_id '-ํ‘œ์ค€๊ณ„์ •์ฝ”๋“œ ๋ฏธ์‚ฌ์šฉ-' (no standard code) +// - LGํ™”ํ•™ 051910 aggregates all borrowings into two rows both labelled exactly "์ฐจ์ž…๊ธˆ" +// - ๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ uses ifrs-full_ShorttermDepositsNotClassifiedAsCashEquivalents +describe('balanceSheet.totalDebt โ€” interest-bearing debt sum', () => { + // ์‚ผ์„ฑ์ „์ž 2025 ์—ฐ๊ฒฐ BS, actual figures. + const samsungBS: DartRow[] = [ + { sj_div: 'BS', account_id: '-ํ‘œ์ค€๊ณ„์ •์ฝ”๋“œ ๋ฏธ์‚ฌ์šฉ-', account_nm: '๋‹จ๊ธฐ์ฐจ์ž…๊ธˆ', thstrm_amount: '17,574,980,000,000', frmtrm_amount: '17,000,000,000,000' }, + { sj_div: 'BS', account_id: 'ifrs-full_CurrentPortionOfLongtermBorrowings', account_nm: '์œ ๋™์„ฑ์žฅ๊ธฐ๋ถ€์ฑ„', thstrm_amount: '1,177,508,000,000', frmtrm_amount: '1,000,000,000,000' }, + { sj_div: 'BS', account_id: 'ifrs-full_NoncurrentPortionOfNoncurrentBondsIssued', account_nm: '์‚ฌ์ฑ„', thstrm_amount: '7,134,000,000', frmtrm_amount: '7,000,000,000' }, + { sj_div: 'BS', account_id: 'ifrs-full_NoncurrentPortionOfNoncurrentLoansReceived', account_nm: '์žฅ๊ธฐ์ฐจ์ž…๊ธˆ', thstrm_amount: '6,479,517,000,000', frmtrm_amount: '6,000,000,000,000' }, + { sj_div: 'BS', account_id: 'ifrs-full_CashAndCashEquivalents', account_nm: 'ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ', thstrm_amount: '57,856,378,000,000', frmtrm_amount: '49,680,710,000,000' }, + { sj_div: 'BS', account_id: 'ifrs-full_ShorttermDepositsNotClassifiedAsCashEquivalents', account_nm: '๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ', thstrm_amount: '67,965,021,000,000', frmtrm_amount: '65,102,886,000,000' }, + // noise that must NOT be summed into debt: + { sj_div: 'BS', account_id: 'ifrs-full_CurrentLeaseLiabilities', account_nm: '์œ ๋™ ๋ฆฌ์Šค๋ถ€์ฑ„', thstrm_amount: '1,500,000,000,000', frmtrm_amount: '1,400,000,000,000' }, + { sj_div: 'BS', account_id: 'ifrs-full_NoncurrentLeaseLiabilities', account_nm: '๋น„์œ ๋™ ๋ฆฌ์Šค๋ถ€์ฑ„', thstrm_amount: '2,000,000,000,000', frmtrm_amount: '1,900,000,000,000' }, + { sj_div: 'BS', account_id: '-ํ‘œ์ค€๊ณ„์ •์ฝ”๋“œ ๋ฏธ์‚ฌ์šฉ-', account_nm: '๋งค์ž…์ฑ„๋ฌด', thstrm_amount: '12,000,000,000,000', frmtrm_amount: '11,000,000,000,000' }, + { sj_div: 'BS', account_id: 'ifrs-full_CurrentProvisions', account_nm: '์œ ๋™์„ฑ์ถฉ๋‹น๋ถ€์ฑ„', thstrm_amount: '5,000,000,000,000', frmtrm_amount: '4,500,000,000,000' }, + ]; + + it('sums every borrowing line (๋‹จ๊ธฐ์ฐจ์ž…๊ธˆ matched by name โ€” its id is the no-code sentinel)', () => { + const s = summarizePeriod(samsungBS, { bsns_year: 2025, report_type: 'annual', fs_div: 'CFS' }); + expect(s.balanceSheet.totalDebt.current).toBe(25_239_139_000_000); + expect(s.balanceSheet.totalDebt.prior).toBe(24_007_000_000_000); + expect(s.balanceSheet.totalDebt.display).toBe('25.2์กฐ'); + expect(s.balanceSheet.totalDebt.label).toBe('๋‹จ๊ธฐ์ฐจ์ž…๊ธˆ + ์œ ๋™์„ฑ์žฅ๊ธฐ๋ถ€์ฑ„ + ์‚ฌ์ฑ„ + ์žฅ๊ธฐ์ฐจ์ž…๊ธˆ'); + }); + + it('excludes ๋ฆฌ์Šค๋ถ€์ฑ„ / ๋งค์ž…์ฑ„๋ฌด / ์ถฉ๋‹น๋ถ€์ฑ„ from debt', () => { + const debt = sumMetrics(samsungBS, DEBT_SUM_SPEC); + // If leases/payables/provisions leaked in, the sum would jump by 20.5์กฐ. + expect(debt.current).toBe(25_239_139_000_000); + expect(debt.label).not.toContain('๋ฆฌ์Šค'); + expect(debt.label).not.toContain('๋งค์ž…์ฑ„๋ฌด'); + }); + + it('resolves ๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ as shortTermInvestments โ†’ company is net cash', () => { + const s = summarizePeriod(samsungBS, { bsns_year: 2025, report_type: 'annual', fs_div: 'CFS' }); + expect(s.balanceSheet.shortTermInvestments.current).toBe(67_965_021_000_000); + expect(s.balanceSheet.shortTermInvestments.display).toBe('68.0์กฐ'); + // Net Debt = totalDebt โˆ’ (cash + shortTermInvestments) must be deeply negative (net cash). + const cash = s.balanceSheet.cashAndEquivalents.current!; + const sti = s.balanceSheet.shortTermInvestments.current!; + const debt = s.balanceSheet.totalDebt.current!; + expect(debt - (cash + sti)).toBe(-100_582_260_000_000); + expect(debt - (cash + sti)).toBeLessThan(0); + }); + + it('sums multiple rows sharing the same label (LGํ™”ํ•™ lists ์ฐจ์ž…๊ธˆ twice: current + non-current)', () => { + const lgChemBS: DartRow[] = [ + { sj_div: 'BS', account_id: '-ํ‘œ์ค€๊ณ„์ •์ฝ”๋“œ ๋ฏธ์‚ฌ์šฉ-', account_nm: '์ฐจ์ž…๊ธˆ', thstrm_amount: '3,804,367,000,000', frmtrm_amount: '3,000,000,000,000' }, + { sj_div: 'BS', account_id: '-ํ‘œ์ค€๊ณ„์ •์ฝ”๋“œ ๋ฏธ์‚ฌ์šฉ-', account_nm: '์ฐจ์ž…๊ธˆ', thstrm_amount: '12,160,152,000,000', frmtrm_amount: '11,000,000,000,000' }, + { sj_div: 'BS', account_id: 'ifrs-full_CashAndCashEquivalents', account_nm: 'ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ', thstrm_amount: '8,497,882,000,000' }, + ]; + const debt = sumMetrics(lgChemBS, DEBT_SUM_SPEC); + expect(debt.current).toBe(15_964_519_000_000); // both rows, not just the first + expect(debt.prior).toBe(14_000_000_000_000); + expect(debt.label).toBe('์ฐจ์ž…๊ธˆ'); // deduped label + }); + + it('includes ์ „ํ™˜์‚ฌ์ฑ„ (convertible bonds) and ์œ ๋™์„ฑ์žฅ๊ธฐ์ฐจ์ž…๊ธˆ variant', () => { + const altBS: DartRow[] = [ + { sj_div: 'BS', account_id: '-ํ‘œ์ค€๊ณ„์ •์ฝ”๋“œ ๋ฏธ์‚ฌ์šฉ-', account_nm: '์œ ๋™์„ฑ์žฅ๊ธฐ์ฐจ์ž…๊ธˆ', thstrm_amount: '972,248,000' }, + { sj_div: 'BS', account_id: 'dart_CurrentPortionOfConvertibleBonds', account_nm: '์œ ๋™์ „ํ™˜์‚ฌ์ฑ„', thstrm_amount: '1,600,360,970' }, + { sj_div: 'BS', account_id: 'ifrs-full_NoncurrentPortionOfNoncurrentLoansReceived', account_nm: '์žฅ๊ธฐ์ฐจ์ž…๊ธˆ', thstrm_amount: '4,027,752,000' }, + ]; + const debt = sumMetrics(altBS, DEBT_SUM_SPEC); + expect(debt.current).toBe(972_248_000 + 1_600_360_970 + 4_027_752_000); + expect(debt.label).toContain('์œ ๋™์ „ํ™˜์‚ฌ์ฑ„'); + }); + + it('counts a row matched by both id and name only once (no double count)', () => { + const dupMatch: DartRow[] = [ + // matches DEBT_SUM_SPEC by BOTH account_id and account_nm + { sj_div: 'BS', account_id: 'ifrs-full_CurrentPortionOfLongtermBorrowings', account_nm: '์œ ๋™์„ฑ์žฅ๊ธฐ๋ถ€์ฑ„', thstrm_amount: '1,000,000,000,000' }, + ]; + expect(sumMetrics(dupMatch, DEBT_SUM_SPEC).current).toBe(1_000_000_000_000); + }); + + it('totalDebt / shortTermInvestments are null for filers with no borrowing lines (banks/holdcos)', () => { + const bankBS: DartRow[] = [ + { sj_div: 'BS', account_id: 'ifrs-full_CashAndCashEquivalents', account_nm: 'ํ˜„๊ธˆ๋ฐํ˜„๊ธˆ์„ฑ์ž์‚ฐ', thstrm_amount: '20,000,000,000,000' }, + { sj_div: 'BS', account_id: '-ํ‘œ์ค€๊ณ„์ •์ฝ”๋“œ ๋ฏธ์‚ฌ์šฉ-', account_nm: '์˜ˆ์ˆ˜๋ถ€์ฑ„', thstrm_amount: '300,000,000,000,000' }, + { sj_div: 'BS', account_id: '-ํ‘œ์ค€๊ณ„์ •์ฝ”๋“œ ๋ฏธ์‚ฌ์šฉ-', account_nm: '๋ณดํ—˜๊ณ„์•ฝ๋ถ€์ฑ„', thstrm_amount: '150,000,000,000,000' }, + ]; + const s = summarizePeriod(bankBS, { bsns_year: 2025, report_type: 'annual', fs_div: 'CFS' }); + expect(s.balanceSheet.totalDebt.current).toBeNull(); + expect(s.balanceSheet.totalDebt.label).toBeNull(); + expect(s.balanceSheet.shortTermInvestments.current).toBeNull(); + }); + + it('does not substring-match (์˜ˆ์ˆ˜๋ถ€์ฑ„ / ๋งค์ž…์ฑ„๋ฌด are not ์ฐจ์ž…๊ธˆ)', () => { + const tricky: DartRow[] = [ + { sj_div: 'BS', account_id: '-ํ‘œ์ค€๊ณ„์ •์ฝ”๋“œ ๋ฏธ์‚ฌ์šฉ-', account_nm: '์˜ˆ์ˆ˜๋ถ€์ฑ„', thstrm_amount: '999,000,000,000' }, + { sj_div: 'BS', account_id: '-ํ‘œ์ค€๊ณ„์ •์ฝ”๋“œ ๋ฏธ์‚ฌ์šฉ-', account_nm: '๋‹จ๊ธฐ์ฐจ์ž…๊ธˆ๋ฐ์œ ๋™์„ฑ์žฅ๊ธฐ๋ถ€์ฑ„', thstrm_amount: '888,000,000,000' }, + ]; + // bare-label set is exact: neither row equals an entry, so debt is null. + expect(sumMetrics(tricky, DEBT_SUM_SPEC).current).toBeNull(); + }); +}); diff --git a/src/tools/finance-kr/normalize-financials.ts b/src/tools/finance-kr/normalize-financials.ts index c4472c955..fdb93d8f8 100644 --- a/src/tools/finance-kr/normalize-financials.ts +++ b/src/tools/finance-kr/normalize-financials.ts @@ -61,8 +61,16 @@ export interface FinancialSummary { balanceSheet: { totalAssets: MetricVal; totalLiabilities: MetricVal; + /** + * Interest-bearing debt only (๋‹จ๊ธฐ์ฐจ์ž…๊ธˆ + ์œ ๋™์„ฑ์žฅ๊ธฐ๋ถ€์ฑ„ + ์‚ฌ์ฑ„ + ์žฅ๊ธฐ์ฐจ์ž…๊ธˆ + ์ „ํ™˜์‚ฌ์ฑ„ ๋“ฑ), + * NOT ๋ถ€์ฑ„์ด๊ณ„. This is the net-debt numerator; `null` when no borrowing line matches + * (banks/holdcos/insurers use non-standard labels โ€” the model drills the raw file). + */ + totalDebt: MetricVal; totalEquity: MetricVal; cashAndEquivalents: MetricVal; + /** ๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ โ€” short-term financial instruments, cash-equivalent for the net-debt bridge. */ + shortTermInvestments: MetricVal; }; cashFlow: { operating: MetricVal; @@ -97,6 +105,21 @@ interface AccountSpec { statement: Statement; } +/** + * Spec for a metric that is the SUM of several line items (e.g. total interest-bearing + * debt). Unlike AccountSpec/findMetric (one row), sumMetrics adds EVERY row whose + * account_id โˆˆ accountIds OR account_nm โˆˆ accountNms โ€” a filer may report the same + * concept across multiple rows (LGํ™”ํ•™ lists current and non-current borrowings as two + * rows both labelled exactly "์ฐจ์ž…๊ธˆ"). Matching is by the exact-set membership only (no + * substring), so ๋ฆฌ์Šค๋ถ€์ฑ„/์ถฉ๋‹น๋ถ€์ฑ„/๋งค์ž…์ฑ„๋ฌด stay out. + */ +export interface SumSpec { + sjDiv: Statement; + accountIds: string[]; + accountNms: string[]; + kind: MetricKind; +} + /** account_id mapping verified against real DART payloads (Samsung 005930, CFS). */ export const ACCOUNT_SPECS: Record = { revenue: { @@ -164,6 +187,15 @@ export const ACCOUNT_SPECS: Record = { kind: 'amount', statement: 'BS', }, + // ๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ (short-term financial instruments) โ€” cash-equivalent for net debt. id/nm + // verified against real DART CFS payloads (์‚ผ์„ฑ์ „์ž 005930 and ์•Œํ…Œ์˜ค์   both use this id). + shortTermInvestments: { + sjDivs: ['BS'], + accountIds: ['ifrs-full_ShorttermDepositsNotClassifiedAsCashEquivalents'], + accountNms: ['๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ'], + kind: 'amount', + statement: 'BS', + }, cfo: { sjDivs: ['CF'], accountIds: ['ifrs-full_CashFlowsFromUsedInOperatingActivities'], @@ -194,6 +226,55 @@ export const ACCOUNT_SPECS: Record = { }, }; +/** + * Interest-bearing debt for the Net Debt bridge: Net Debt = totalDebt โˆ’ (cash + STI). + * Summed across every matching BS row (a filer may split borrowings over several lines, + * or โ€” like LGํ™”ํ•™ โ€” report two rows both labelled exactly "์ฐจ์ž…๊ธˆ"). + * + * VERIFIED against real DART CFS payloads: ์‚ผ์„ฑ์ „์ž 005930 (๋‹จ๊ธฐ์ฐจ์ž…๊ธˆ carries account_id + * `-ํ‘œ์ค€๊ณ„์ •์ฝ”๋“œ ๋ฏธ์‚ฌ์šฉ-` โ†’ the account_nm set, not the id, resolves it; plus ์œ ๋™์„ฑ์žฅ๊ธฐ๋ถ€์ฑ„/ + * ์‚ฌ์ฑ„/์žฅ๊ธฐ์ฐจ์ž…๊ธˆ), LGํ™”ํ•™ 051910 (bare "์ฐจ์ž…๊ธˆ" ร—2 โ€” current + non-current), ์•Œํ…Œ์˜ค์   196170 + * (์œ ๋™์ „ํ™˜์‚ฌ์ฑ„/์œ ๋™์„ฑ์žฅ๊ธฐ์ฐจ์ž…๊ธˆ). The id set is a preference layer; the exact-nm set is the + * reliable matcher. + * + * BEST-EFFORT (plausible but NOT present in those three samples): ์œ ๋™์„ฑ์‚ฌ์ฑ„, ๋‹จ๊ธฐ์‚ฌ์ฑ„, + * ์ „ํ™˜์‚ฌ์ฑ„, ๋น„์œ ๋™ ์ „ํ™˜์‚ฌ์ฑ„, ์‹ ์ฃผ์ธ์ˆ˜๊ถŒ๋ถ€์‚ฌ์ฑ„, ์œ ๋™์„ฑ์‹ ์ฃผ์ธ์ˆ˜๊ถŒ๋ถ€์‚ฌ์ฑ„. Exact-match means each + * either hits the real label or harmlessly misses โ€” never a false positive. + * + * ๋ฆฌ์Šค๋ถ€์ฑ„ (operating, already in FCF) is intentionally EXCLUDED. Caveat: a filer reporting + * BOTH itemized borrowings AND a bare "์ฐจ์ž…๊ธˆ" subtotal would double-count โ€” not observed + * (fnlttSinglAcntAll lists reported leaves, not computed subtotals), and Step 7 of the DCF + * skill cross-checks |Net Debt| vs market cap as a backstop. + */ +export const DEBT_SUM_SPEC: SumSpec = { + sjDiv: 'BS', + kind: 'amount', + accountIds: [ + 'ifrs-full_ShorttermBorrowings', + 'ifrs-full_CurrentPortionOfLongtermBorrowings', + 'ifrs-full_NoncurrentPortionOfNoncurrentLoansReceived', + 'ifrs-full_LongtermBorrowings', + 'ifrs-full_NoncurrentPortionOfNoncurrentBondsIssued', + 'ifrs-full_BondsIssued', + 'dart_CurrentPortionOfConvertibleBonds', + ], + accountNms: [ + '๋‹จ๊ธฐ์ฐจ์ž…๊ธˆ', + '์ฐจ์ž…๊ธˆ', + '์œ ๋™์„ฑ์žฅ๊ธฐ๋ถ€์ฑ„', + '์œ ๋™์„ฑ์žฅ๊ธฐ์ฐจ์ž…๊ธˆ', + '์žฅ๊ธฐ์ฐจ์ž…๊ธˆ', + '์œ ๋™์„ฑ์‚ฌ์ฑ„', + '์‚ฌ์ฑ„', + '๋‹จ๊ธฐ์‚ฌ์ฑ„', + '์œ ๋™์ „ํ™˜์‚ฌ์ฑ„', + '์ „ํ™˜์‚ฌ์ฑ„', + '๋น„์œ ๋™ ์ „ํ™˜์‚ฌ์ฑ„', + '์‹ ์ฃผ์ธ์ˆ˜๊ถŒ๋ถ€์‚ฌ์ฑ„', + '์œ ๋™์„ฑ์‹ ์ฃผ์ธ์ˆ˜๊ถŒ๋ถ€์‚ฌ์ฑ„', + ], +}; + const BASIS_NOTES: Record = { annual: '์—ฐ๊ฐ„(thstrm=๋‹น๊ธฐ, frmtrm=์ „๊ธฐ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„ YoY ๋น„๊ต ๊ฐ€๋Šฅ.', semiannual: '๋ฐ˜๊ธฐ ๋ˆ„์ (thstrm=๋‹น๋ฐ˜๊ธฐ ๋ˆ„์ , frmtrm=์ „๋…„ ๋™๊ธฐ ๋ˆ„์ ). ์†์ตยทํ˜„๊ธˆํ๋ฆ„์€ 6๊ฐœ์›” YTD.', @@ -263,22 +344,64 @@ export function findMetric(list: DartRow[], spec: AccountSpec): MetricVal { return { ...EMPTY_METRIC }; } +/** + * Prior-year same-period amount. On quarterly/semiannual flow statements DART leaves + * frmtrm_amount empty and carries ์ „๋…„ ๋™๊ธฐ in frmtrm_q_amount; on annual reports (and the + * quarterly balance sheet) there is no _q field, so fall back to frmtrm_amount (์ „๊ธฐ / + * ์ „๋…„๋ง). Without this, quarterly YoY is silently null. + */ +function priorAmount(row: DartRow): number | null { + const priorQ = parseAmount(row.frmtrm_q_amount); + return priorQ !== null ? priorQ : parseAmount(row.frmtrm_amount); +} + function toMetric(row: DartRow, kind: MetricKind): MetricVal { const current = parseAmount(row.thstrm_amount); - // Prior-year same period. On quarterly/semiannual flow statements DART leaves - // frmtrm_amount empty and carries ์ „๋…„ ๋™๊ธฐ in frmtrm_q_amount; on annual reports - // (and the quarterly balance sheet) there is no _q field, so fall back to - // frmtrm_amount (์ „๊ธฐ / ์ „๋…„๋ง). Without this, quarterly YoY is silently null. - const priorQ = parseAmount(row.frmtrm_q_amount); - const prior = priorQ !== null ? priorQ : parseAmount(row.frmtrm_amount); return { current, - prior, + prior: priorAmount(row), label: row.account_nm ?? null, display: formatDisplay(current, kind), }; } +/** + * Sum every BS row whose account_id โˆˆ spec.accountIds OR account_nm (whitespace- + * insensitive) โˆˆ spec.accountNms, counting each physical row once. Used for totalDebt, + * which spans several borrowing lines. Returns EMPTY_METRIC when nothing matches; a filer + * missing some borrowing lines still gets the sum of the ones it reports. + * + * NOTE: do NOT put the sentinel `-ํ‘œ์ค€๊ณ„์ •์ฝ”๋“œ ๋ฏธ์‚ฌ์šฉ-` in accountIds โ€” many unrelated rows + * share it; those lines are matched by their exact account_nm instead. + */ +export function sumMetrics(list: DartRow[], spec: SumSpec): MetricVal { + const idSet = new Set(spec.accountIds); + const nmSet = new Set(spec.accountNms.map(squash)); + const matched = list.filter( + (r) => + r.sj_div === spec.sjDiv && + ((r.account_id !== undefined && idSet.has(r.account_id)) || + (r.account_nm !== undefined && nmSet.has(squash(r.account_nm)))), + ); + if (matched.length === 0) return { ...EMPTY_METRIC }; + + let current: number | null = null; + let prior: number | null = null; + for (const row of matched) { + const c = parseAmount(row.thstrm_amount); + if (c !== null) current = (current ?? 0) + c; + const p = priorAmount(row); + if (p !== null) prior = (prior ?? 0) + p; + } + const labels = [...new Set(matched.map((r) => r.account_nm).filter((n): n is string => !!n))]; + return { + current, + prior, + label: labels.length > 0 ? labels.join(' + ') : null, + display: formatDisplay(current, spec.kind), + }; +} + function yoyPct(m: MetricVal): number | null { if (m.current === null || m.prior === null || m.prior === 0) return null; return round1(((m.current - m.prior) / Math.abs(m.prior)) * 100); @@ -310,8 +433,10 @@ export function summarizePeriod(list: DartRow[], opts: SummarizeOpts): Financial const eps = m('eps'); const totalAssets = m('totalAssets'); const totalLiabilities = m('totalLiabilities'); + const totalDebt = sumMetrics(list, DEBT_SUM_SPEC); const totalEquity = m('totalEquity'); const cashAndEquivalents = m('cashAndEquivalents'); + const shortTermInvestments = m('shortTermInvestments'); const cfo = m('cfo'); const cfi = m('cfi'); const cff = m('cff'); @@ -337,7 +462,7 @@ export function summarizePeriod(list: DartRow[], opts: SummarizeOpts): Financial unit: 'KRW', basis: BASIS_NOTES[opts.report_type], incomeStatement: { revenue, operatingProfit, netIncome, controllingNetIncome, eps }, - balanceSheet: { totalAssets, totalLiabilities, totalEquity, cashAndEquivalents }, + balanceSheet: { totalAssets, totalLiabilities, totalDebt, totalEquity, cashAndEquivalents, shortTermInvestments }, cashFlow: { operating: cfo, investing: cfi, financing: cff, capex }, ratios: { operatingMarginPct: ratioPct(operatingProfit.current, revenue.current), From 10fe1ba9ff4d0af84c6ce3473fefeaf9321da118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SH=20KWON=20=F0=9F=87=B0=F0=9F=87=B7?= Date: Fri, 5 Jun 2026 02:24:13 +0900 Subject: [PATCH 12/70] =?UTF-8?q?=ED=95=9C=EA=B5=AD=20=EC=A3=BC=EC=8B=9D?= =?UTF-8?q?=20=EB=A6=AC=EC=84=9C=EC=B9=98:=20=EA=B2=AC=EA=B3=A0=EC=84=B1?= =?UTF-8?q?=20=EB=A0=88=EC=9D=B4=EC=96=B4=20+=20governance=20=EB=B3=B8?= =?UTF-8?q?=EB=AC=B8=20(DART=20=EC=BF=BC=ED=84=B0=C2=B7=EC=84=9C=ED=82=B7?= =?UTF-8?q?=EB=B8=8C=EB=A0=88=EC=9D=B4=EC=BB=A4=20/=20keyless=20=ED=8B=B0?= =?UTF-8?q?=EC=96=B4=EB=A7=81=20/=20drift=20canary)=20(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * read_filings_kr: governance ์„น์…˜ ์นดํ…Œ๊ณ ๋ฆฌ ์ถ”๊ฐ€ (์ง€๋ฐฐ๊ตฌ์กฐยท์ตœ๋Œ€์ฃผ์ฃผยท๊ณ„์—ดํšŒ์‚ฌยท๋Œ€์ฃผ์ฃผ ๊ฑฐ๋ž˜) ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ 1์ฐจ ๋ณ€์ˆ˜(์†Œ์œ ๊ตฌ์กฐยทํŠน์ˆ˜๊ด€๊ณ„์ž)๋ฅผ inference๊ฐ€ ์•„๋‹Œ DART ์‚ฌ์—…๋ณด๊ณ ์„œ ๋ณธ๋ฌธ verbatim์œผ๋กœ ๋Œ์–ด์˜ค๋Š” 6๋ฒˆ์งธ narrative ์นดํ…Œ๊ณ ๋ฆฌ. - dart-document.ts: SectionCategory์— 'governance' + sectionMatches ์ผ€์ด์Šค. canonical ์ •๊ธฐ๋ณด๊ณ ์„œ ์„œ์‹ VI ์ด์‚ฌํšŒ ๋“ฑ ํšŒ์‚ฌ์˜ ๊ธฐ๊ด€ / VII ์ฃผ์ฃผ์— ๊ด€ํ•œ ์‚ฌํ•ญ / IX ๊ณ„์—ดํšŒ์‚ฌ ๋“ฑ / X ๋Œ€์ฃผ์ฃผ ๋“ฑ๊ณผ์˜ ๊ฑฐ๋ž˜๋‚ด์šฉ์„ ๋ฒˆํ˜ธ(VI/VII/IX/X) + title-regex ์ด์ค‘ ๋งค์นญ โ€” ๊ธฐ์กด risks ์ผ€์ด์Šค์™€ ๋™์ผ ์ „๋žต์ด๋ผ ๋ณด๊ณ ์„œ๋ณ„ ๋ฒˆํ˜ธ drift์— ๊ฒฌ๊ณ . selectSections๋Š” headerlessยทtitleless ์žก์Œ ์กฐ๊ฐ([] ๋ผ๋ฒจ)์„ ํ•„ํ„ฐ. - read-filings-kr.ts: zod enumยทํ”Œ๋ž˜๋„ˆ ํ”„๋กฌํ”„ํŠธยทํˆด ์„ค๋ช… ๋ฐฐ์„  + ์บ์‹œ ํ‚ค _dsd_sections โ†’ _dsd_sections_v2 ๋ฒ”ํ”„(๊ตฌ ์บ์‹œ๊ฐ€ ์ƒˆ ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์กฐ์šฉํžˆ ๋ˆ„๋ฝํ•˜์ง€ ์•Š๋„๋ก ๊ฐ•์ œ ์žฌ์ถ”์ถœ). - registry.ts: compactDescription์— ์ง€๋ฐฐ๊ตฌ์กฐยท์ตœ๋Œ€์ฃผ์ฃผยท๊ณ„์—ดํšŒ์‚ฌ ๋ช…์‹œ. ํŠน์ˆ˜๊ด€๊ณ„์ž์™€์˜ ๊ฑฐ๋ž˜ ์ฃผ์„์€ ๋ณธ๋ฌธ III. ์žฌ๋ฌด ์ฃผ์„์— ์กด์žฌํ•˜๊ณ  ๋งค์นญ๋„ ๋˜๋‚˜, ํ‘œ ์ค‘์‹ฌ์ด๋ผ dsdToPlainText๊ฐ€ ํ‘œ๋ฅผ ๋ฒ„๋ ค prose๊ฐ€ ๊ฑฐ์˜ ์—†์–ด ์ž๋™ ์ œ์™ธ๋จ โ€” ์ •๋ฐ€ ๊ฑฐ๋ž˜๋‚ด์—ญ์€ get_financials_kr ์˜์—ญ. ๋ณธ๋ฌธ narrative(X. ๋Œ€์ฃผ์ฃผ ๋“ฑ๊ณผ์˜ ๊ฑฐ๋ž˜๋‚ด์šฉ ๋“ฑ) ๊นŒ์ง€ ์ปค๋ฒ„. ์‹ค๊ฒ€์ฆ: 005930 FY2025 ์‚ฌ์—…๋ณด๊ณ ์„œ์—์„œ VI/VII/IX/X 7๋ธ”๋กยท์•ฝ 9.6KB ์‹ค์ œ prose ์ถ”์ถœ ํ™•์ธ. KR ์Šค์œ„ํŠธ green (governance ํ…Œ์ŠคํŠธ 5๊ฑด ์‹ ๊ทœ), typecheck ํด๋ฆฐ. Co-Authored-By: Claude Opus 4.8 (1M context) * KR DART ๊ฒฌ๊ณ ์„ฑ: ๋™์‹œ์„ฑ ์บก + 020 ์ฟผํ„ฐ ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค + ํ”Œ๋ ˆ์ด๋ถ keyless ํ‹ฐ์–ด๋ง (a) DART ํ˜ธ์ถœ ๊ฒฌ๊ณ ์„ฑ โ€” dart-throttle.ts ์‹ ๊ทœ: - ๊ณต์œ  ์นด์šดํŒ… ์„ธ๋งˆํฌ์–ด(๊ธฐ๋ณธ 4, DART_MAX_CONCURRENCY๋กœ ์กฐ์ •)๋กœ ๋ชจ๋“  DART ํ˜ธ์ถœ์˜ ๋™์‹œ์„ฑ ์บก. getBusinessReport(์—ฐ๋„ Promise.all) ร— get_financials_kr (sub-tool Promise.all) ร— executor(๋„๊ตฌ ๋ณ‘๋ ฌ)์˜ ๊ณฑ์…ˆ์‹ fan-out์ด ๋ฌด๋ฃŒ ํ‚ค 20,000/์ผ ์ฟผํ„ฐ๋ฅผ ํƒœ์šฐ๋Š” ๊ฒƒ์„ ์ฐจ๋‹จ. acquire๋Š” ๋‹จ์ผ ์‚ฌ์šฉ releaser๋ฅผ ๋ฐ˜ํ™˜ํ•ด ์ด์ค‘ ํ•ด์ œ๋กœ๋„ ์นด์šดํŠธ๊ฐ€ ์Œ์ˆ˜๊ฐ€ ๋˜๊ฑฐ๋‚˜ cap์„ ๋„˜๊ธฐ์ง€ ๋ชปํ•จ(๊ตฌ์กฐ์  ์•ˆ์ „). - 020 ์‚ฌ์šฉํ•œ๋„์ดˆ๊ณผ ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค: ์ฒซ 020์— latch โ†’ ๋‚˜๋จธ์ง€ fan-out ๋ฒ„์ŠคํŠธ๋Š” ๋„คํŠธ์›Œํฌ ์—†์ด ๋ช…ํ™•ํ•œ ํ•œ๊ตญ์–ด ๋ฉ”์‹œ์ง€๋กœ ์ฆ‰์‹œ ์‹คํŒจ, 5๋ถ„ ์ฟจ๋‹ค์šด ํ›„ half-open ์žฌ์‹œ๋„. ์ง€์ˆ˜ backoff์€ ์ผ์ผ ์ฟผํ„ฐ์—” ๋ฌด์˜๋ฏธํ•˜๋ฏ€๋กœ ์ฑ„ํƒํ•˜์ง€ ์•Š์Œ. - api.ts get/getBinary๊ฐ€ ์Šฌ๋กฏ+latch๋ฅผ ์‚ฌ์šฉ. assertDartOk๊ฐ€ 020์„ ๋ณ„๋„ ๋ถ„๋ฅ˜ํ•ด latch(013 ๋“ฑ ๋‹ค๋ฅธ ์ƒํƒœ๋Š” ๊ธฐ์กด ๋ฉ”์‹œ์ง€ยท๋™์ž‘ ๊ทธ๋Œ€๋กœ, latch ์•ˆ ํ•จ). getBinary๋Š” status XML/์‚ฌ์šฉํ•œ๋„ ๋ณธ๋ฌธ์œผ๋กœ 020 ๊ฐ์ง€(์ฝ˜ํ…์ธ ํƒ€์ž… ๊ฒŒ์ดํŠธ๊ฐ€ ๋จผ์ €๋ผ ์ •์ƒ ZIP์€ ์˜คํƒ ๋ถˆ๊ฐ€). (b) ํ”Œ๋ ˆ์ด๋ถ keyless ํ‹ฐ์–ด๋ง โ€” prompts.ts: - buildKoreanResearchSection์„ ์ˆœ์ˆ˜ ํ•จ์ˆ˜(hasDartKey: boolean)๋กœ ๋งŒ๋“ค๊ณ , DART ํ‚ค ์—†์„ ๋•Œ '' ๋Œ€์‹  keyless ํ‹ฐ์–ด ๋ฐ˜ํ™˜. ํ•ญ์ƒ ๋“ฑ๋ก๋˜๋Š” Naver get_market_data_krยทget_foreign_ownership_kr๋งŒ ์ฐธ์กฐ(unbound DART ๋„๊ตฌ๋Š” ์–ธ๊ธ‰ ์•ˆ ํ•จ)ํ•ด ์ˆ˜๊ธ‰ยท๋ฐธ๋ฅ˜์—์ด์…˜ ์ถ”๋ก ์ด keyless๋กœ๋„ ์‚ด์•„๋‚จ๊ฒŒ ํ•จ. - DART ๊ฒŒ์ดํŠธ๋ฅผ env.ts hasDartKey()๋กœ ๋‹จ์ผํ™” โ€” registry(๋„๊ตฌ ๋“ฑ๋ก)์™€ prompt(ํ”Œ๋ ˆ์ด๋ถ ํ‹ฐ์–ด)๊ฐ€ ๋™์ผ ํŒ์ •์„ ์จ์„œ "ํ”Œ๋ ˆ์ด๋ถ์ด ๋“ฑ๋ก ์•ˆ ๋œ DART ๋„๊ตฌ๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š”" ๋ถˆ์ผ์น˜๋ฅผ ์›์ฒœ ์ฐจ๋‹จ(registry๋Š” pure process.env ์˜๋ฏธ ์œ ์ง€ โ†’ registry.test ๊ทธ๋Œ€๋กœ ํ†ต๊ณผ). ํ…Œ์ŠคํŠธ: dart-throttle(์„ธ๋งˆํฌ์–ด capยทgate ์ฐจ๋‹จยท๋‹จ์ผ์‚ฌ์šฉ releaserยทthrow์‹œ ํ•ด์ œยท ์ฟจ๋‹ค์šด ๊ฒฝ๊ณ„ยทhalf-open ์žฌํŠธ๋ฆฝยทsibling-trip ์ฆ‰์‹œ์‹คํŒจ), api(assertDartOk 020/013/ 000 ๋ถ„๋ฅ˜), prompts(ํ‹ฐ์–ด๋ง) โ€” ์‹ ๊ทœ 16๊ฑด. KR ์Šค์œ„ํŠธ green, typecheck ํด๋ฆฐ. ๋ผ์ด๋ธŒ: 5๋…„์น˜ fan-out์ด cap ์ •ํ™•ํžˆ ๊ด€์ธกํ•˜๋ฉฐ ์ „๋Ÿ‰ ์ •์ƒ ๋ฐ˜ํ™˜, in-flight 0 ์ •๋ฆฌ. Co-Authored-By: Claude Opus 4.8 (1M context) * KR keyless ๋„๊ตฌ partial-drift canary (Naver schema ๋ณ€๊ฒฝ ์กฐ์šฉํ•œ null ํƒ์ง€) Naver ๋ชจ๋ฐ”์ผ JSON์€ ๊ณ„์•ฝ์ด ์—†์–ด ํ•„๋“œ ํ•˜๋‚˜๋งŒ rename๋˜๋ฉด ๊ทธ ๊ฐ’์ด ์กฐ์šฉํžˆ null์ด ๋˜๊ณ , ์ „๋ถ€ null์ผ ๋•Œ๋งŒ ๋ฐœ๋™ํ•˜๋Š” hasNoMarketData ๊ฐ€๋“œ๋ฅผ ํ†ต๊ณผํ•œ๋‹ค. "์ผ๋ถ€๋Š” ์žˆ๊ณ  ์ผ๋ถ€๋งŒ ๋ˆ„๋ฝ"(partial drift)์„ _dataQualityWarning์œผ๋กœ surfaceํ•ด ๋ชจ๋ธ์ด ๊ทธ ๊ฐ’์„ ์‚ฌ์‹ค๋กœ ๋ณด๊ณ ํ•˜์ง€ ์•Š๊ฒŒ ํ•œ๋‹ค. (๋‘˜์งธ keyless ์†Œ์Šค๊ฐ€ ์—†์–ด fallback์ด ์•„๋‹Œ detection; ์บ์‹œ staleness ์‹ ํ˜ธ๋Š” ๋ฒ”์œ„ ๋ฐ–.) - utils.ts: nullFields(์Šค๋ƒ…์ƒท) + deadColumns(์‹œ๊ณ„์—ด, ์ „ row null) ํ—ฌํผ. - get-market-data-kr.ts marketDataQualityWarning: ยท critical(ํ˜„์žฌ๊ฐ€ยท์‹œ์ด) ๋ˆ„๋ฝ โ†’ ๊ฒฝ๊ณ  ยท EQUITY(PER ๋˜๋Š” EPS ์žˆ์Œ)์ธ๋ฐ PBRยทBPS ๋‘˜ ๋‹ค ์—†์Œ โ†’ ๊ฒฝ๊ณ . ํŽ€๋“œ(ETN/ETF/REIT)ยท ์ƒ์žฅ๋‹น์ผ IPO๋Š” ์‹ค์ ยท์žฅ๋ถ€๊ฐ€๊ฐ€ ๋ณธ๋ž˜ ์—†์–ด PER/EPS ๊ฒŒ์ดํŠธ๋กœ ์กฐ์šฉํžˆ ํ†ต๊ณผ(์˜คํƒ ๋ฐฉ์ง€). ยท ETF/ETN ์Šคํ‚ค๋งˆ(nav/fundPay/etfBaseIdx)๋Š” ์‹œ์ดยท๋ฉ€ํ‹ฐํ”Œ์ด ๋ณธ๋ž˜ ์—†์œผ๋ฏ€๋กœ fund๋กœ ๊ฐ์ง€ํ•ด equity canary ์ž์ฒด๋ฅผ ๊ฑด๋„ˆ๋œ€ โ€” get_market_data๋Š” equity ๋„๊ตฌ. ยท dealTrendInfos.closePrice๊ฐ€ rename๋˜๋ฉด ์ „์ผ lastClosePrice fallback์ด stale ๊ฐ’์œผ๋กœ ๊ฐ€๋ ค canary๊ฐ€ ๋ชป ๋ณด๋Š” ์ตœ์•… ์ผ€์ด์Šค๋ฅผ ๋ณ„๋„ ๊ฐ์ง€(ํ˜„์žฌ๊ฐ€ ๋ฏธ๊ฐฑ์‹  ๊ฒฝ๊ณ ). ยท PER/EPS/๋ฐฐ๋‹น/์ถ”์ •์น˜๋Š” loss-makerยท๋ฌด๋ฐฐ๋‹นยท๋ฏธ์ปค๋ฒ„์—์„œ ์ •์ƒ null์ด๋ผ drift ์‹ ํ˜ธ ์—์„œ ์ œ์™ธ. - get-foreign-ownership-kr.ts: 6๊ฐœ ์ˆ˜๊ธ‰ ์ปฌ๋Ÿผ(์™ธ๊ตญ์ธยท๊ธฐ๊ด€ยท๊ฐœ์ธ ์ˆœ๋งค์ˆ˜ยท์ง€๋ถ„์œจยท์ข…๊ฐ€ยท ๊ฑฐ๋ž˜๋Ÿ‰)์ด ์ „ ๊ตฌ๊ฐ„ null์ด๋ฉด ๊ฒฝ๊ณ . ์ •์ƒ 0์€ 0์œผ๋กœ ํŒŒ์‹ฑ๋˜๋ฏ€๋กœ ์˜คํƒ ์—†์Œ. 1~2ํ–‰ ์ฐฝ(์‹ ๊ทœ์ƒ์žฅ)์—์„  "์ „ row null"์ด ์ž๋ช…ํžˆ ์ฐธ์ด๋ผ โ‰ฅ3ํ–‰์—์„œ๋งŒ ํŒ์ •. - prompts.ts: _dataQualityWarning ์ฒ˜๋ฆฌ ์ง€์นจ โ€” KR ๋Œ€์ฒด ์†Œ์Šค๊ฐ€ ์—†์œผ๋ฏ€๋กœ omit ์šฐ์„ , ๊ต์ฐจํ™•์ธ์€ ๋‹ค๋ฅธ ๋„๊ตฌ๊ฐ€ ๊ฐ™์€ ๊ฐ’์„ ์ค„ ๋•Œ๋งŒ. ๊ฒ€์ฆ: ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์‹ ๊ทœ(ํ—ฌํผยทcanaryยทETFยทbook-driftยทstale-closeยทforeign drift), KR ์Šค์œ„ํŠธ 126/0, typecheck ํด๋ฆฐ. ๋ผ์ด๋ธŒ ์˜คํƒ 0 โ€” ๋Œ€ํ˜•์ฃผยทKOSDAQยท์šฐ์„ ์ฃผยท์ง€์ฃผ์‚ฌยท ์€ํ–‰ยทSPACยทETFยทREIT ์ „๋ถ€ clean. ์ ๋Œ€์  ๋ฆฌ๋ทฐ 2๋ Œ์ฆˆ ๋ฐ˜์˜(closePrice ์˜คํƒ๋ˆ„๋ฝยทETF ์˜คํƒยท์ปฌ๋Ÿผ ์ปค๋ฒ„๋ฆฌ์ง€ยท๋‹จ์ผํ–‰ยทํ”„๋กฌํ”„ํŠธ ๋ฌธ๊ตฌ). Co-Authored-By: Claude Opus 4.8 (1M context) * README ์ „๋ฉด ๊ฐฑ์‹ : KR ๊ฒฌ๊ณ ์„ฑ ๋ ˆ์ด์–ด + governance ๋ณธ๋ฌธ + ์‚ฌ์‹ค ์ •์ • ์ด๋ฒˆ ๋ธŒ๋žœ์น˜์˜ ๋ณ€๊ฒฝ(governance ์„น์…˜ยทDART ๋™์‹œ์„ฑ/์„œํ‚ท๋ธŒ๋ ˆ์ด์ปคยทkeyless ํ‹ฐ์–ด๋งยท drift canary)์„ README์— ๋ฐ˜์˜ํ•˜๊ณ  ๋‚ก์€ ์‚ฌ์‹ค์„ ๋ฐ”๋กœ์žก์Œ. - ๐Ÿ‡ฐ๐Ÿ‡ท ์„น์…˜ ์žฌ๊ตฌ์„ฑ: ๋„๊ตฌ / ๊ฒฌ๊ณ ์„ฑยท์‹ ๋ขฐ์„ฑ(์‹ ๊ทœ) / ์ข…๋ชฉ์ฝ”๋“œ ํ•ด๊ฒฐ / ๊ณ ์œ ์ฒ˜๋ฆฌ. read_filings_kr์— ์ง€๋ฐฐ๊ตฌ์กฐยท์ตœ๋Œ€์ฃผ์ฃผยทํŠน์ˆ˜๊ด€๊ณ„์žยท๊ณ„์—ด์‚ฌยท๋Œ€์ฃผ์ฃผ๊ฑฐ๋ž˜ ์ถ”๊ฐ€. - ์‹ ๊ทœ "๊ฒฌ๊ณ ์„ฑยท์‹ ๋ขฐ์„ฑ": DART ์ฟผํ„ฐ ๋ณดํ˜ธ(๋™์‹œ์„ฑ ์บก + 020 ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค), keyless ํ”Œ๋ ˆ์ด๋ถ ํ‹ฐ์–ด๋ง, partial-drift canary(_dataQualityWarning), record/replay ๊ฒฐ์ •์  ์žฌํ˜„. - ์‚ฌ์‹ค ์ •์ •: DART ๋ฌด๋ฃŒ ํ•œ๋„ 10,000 โ†’ 20,000๊ฑด/์ผ, clone URL์„ ํฌํฌ (dexter-with-korea)๋กœ, env์— DART_MAX_CONCURRENCY ์ถ”๊ฐ€. - ๋น„๊ต/ํ•œ๊ณ„ ์„น์…˜์— ๊ฒฌ๊ณ ์„ฑยท์ปจ์„ผ์„œ์Šค ๊นŠ์ด ํ•œ๊ณ„ ๋ฐ˜์˜. Co-Authored-By: Claude Opus 4.8 (1M context) * README ์žฌ์ž‘์„ฑ: ์ƒ์† ๊ตฌ์กฐ ํƒˆํ”ผ, problemโ†’toolsโ†’durability ์•„ํฌ๋กœ ์—…์ŠคํŠธ๋ฆผ dexter์˜ ๊ตฌ์„ฑ(๊ฐœ์š”โ†’์‚ฌ์šฉ์˜ˆ์‹œโ†’๋น„๊ตโ†’๋น ๋ฅธ์‹œ์ž‘)์„ ๋”ฐ๋ฅด์ง€ ์•Š๊ณ  ์ด ํฌํฌ์˜ ์ •์ฒด์„ฑ(ํ•œ๊ตญ 1์ฐจ ์ถœ์ฒ˜ + ๊ฒฌ๊ณ ์„ฑ ๋ ˆ์ด์–ด)์— ๋งž์ถฐ ์žฌ๊ตฌ์„ฑ: - ํ•œ๊ตญ 1์ฐจ ์ถœ์ฒ˜ ์—์ด์ „ํŠธ๋กœ ์ •์ฒด์„ฑ ์žฌ์ •์˜ + ๋‹จ์ผ ์˜ˆ์‹œ๋กœ "๋ฌด์—‡์„ ํ•˜๋‚˜" ์ฆ‰์‹œ ์ œ์‹œ - "์™œ ํ•œ๊ตญ ์ฃผ์‹์€ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด์•ผ ํ–ˆ๋‚˜" โ€” ์†Œ์Šค๋ณ„ ํ˜„์‹ค(DART ์ฟผํ„ฐยทKRX ๋กœ๊ทธ์ธยท Naver ๋ฌด๊ณ„์•ฝยทNPS ์—ฐ๋ง์Šค๋ƒ…์ƒท) ํ‘œ๋กœ ๊ฒฌ๊ณ ์„ฑ ๋ ˆ์ด์–ด์˜ ํ•„์š”๋ฅผ ๋™๊ธฐํ™” - ๊ฒฌ๊ณ ์„ฑ์„ ๋…๋ฆฝ ์„น์…˜์œผ๋กœ ์ „๋ฉดํ™”(์ฟผํ„ฐ ๋ณดํ˜ธยทdrift canaryยทkeyless ํ‹ฐ์–ด๋งยท record/replay) - ํ•œ๊ตญ ์‹œ์žฅ ํŠœ๋‹ / ๋น ๋ฅธ์‹œ์ž‘ / ์˜ˆ์‹œ / ์ฑ—๋ด‡ ๋Œ€๋น„ / ์ •์งํ•œ ํ•œ๊ณ„ / ๊ฐœ๋ฐœยทํ‰๊ฐ€๋กœ ์ •๋ฆฌ ๋‚ด์šฉ ์‚ฌ์‹ค์€ ์ง์ „ ๊ฐฑ์‹ ๊ณผ ๋™์ผํ•˜๊ฒŒ ์ฝ”๋“œ ๊ทผ๊ฑฐ ์œ ์ง€. Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- README.md | 375 +++++++----------- src/agent/prompts.test.ts | 22 + src/agent/prompts.ts | 39 +- src/tools/finance-kr/api.test.ts | 32 ++ src/tools/finance-kr/api.ts | 106 +++-- src/tools/finance-kr/dart-document.test.ts | 76 ++++ src/tools/finance-kr/dart-document.ts | 26 +- src/tools/finance-kr/dart-throttle.test.ts | 145 +++++++ src/tools/finance-kr/dart-throttle.ts | 104 +++++ .../get-foreign-ownership-kr.test.ts | 32 ++ .../finance-kr/get-foreign-ownership-kr.ts | 29 +- .../finance-kr/get-market-data-kr.test.ts | 112 +++++- src/tools/finance-kr/get-market-data-kr.ts | 57 ++- src/tools/finance-kr/read-filings-kr.ts | 14 +- src/tools/finance-kr/utils.test.ts | 34 ++ src/tools/finance-kr/utils.ts | 26 ++ src/tools/registry.ts | 6 +- src/utils/env.ts | 11 + 18 files changed, 942 insertions(+), 304 deletions(-) create mode 100644 src/agent/prompts.test.ts create mode 100644 src/tools/finance-kr/api.test.ts create mode 100644 src/tools/finance-kr/dart-throttle.test.ts create mode 100644 src/tools/finance-kr/dart-throttle.ts diff --git a/README.md b/README.md index 751b42a83..9dcbc0841 100644 --- a/README.md +++ b/README.md @@ -1,315 +1,206 @@ -# Dexter ๐Ÿค– +# Dexter ๐Ÿ‡ฐ๐Ÿ‡ท -Dexter๋Š” ์ž‘์—…ํ•˜๋ฉด์„œ ์Šค์Šค๋กœ ์‚ฌ๊ณ ํ•˜๊ณ , ๊ณ„ํšํ•˜๊ณ , ํ•™์Šตํ•˜๋Š” ์ž์œจํ˜• ๊ธˆ์œต ๋ฆฌ์„œ์น˜ ์—์ด์ „ํŠธ์ž…๋‹ˆ๋‹ค. -์ž‘์—… ๊ณ„ํš(task planning), ์ž๊ธฐ ์ ๊ฒ€(self-reflection), ์‹ค์‹œ๊ฐ„ ์‹œ์žฅ ๋ฐ์ดํ„ฐ๋ฅผ ํ™œ์šฉํ•ด ๋ถ„์„์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. Claude Code๋ฅผ ๋– ์˜ฌ๋ฆฌ๋˜, ๊ธˆ์œต ๋ฆฌ์„œ์น˜์— ํŠนํ™”๋˜๋„๋ก ๋งŒ๋“ค์–ด์ง„ ๋„๊ตฌ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. +**ํ•œ๊ตญ ์ฃผ์‹์„ 1์ฐจ ์ถœ์ฒ˜๋กœ ์ง์ ‘ ๋ฆฌ์„œ์น˜ํ•˜๋Š” CLI ์—์ด์ „ํŠธ.** ๋‰ด์Šค ์š”์•ฝ์ด ์•„๋‹ˆ๋ผ DART(์ „์ž๊ณต์‹œ)ยทKRX(๊ณต๋งค๋„)ยทNaver(์‹œ์„ธยท์™ธ๊ตญ์ธ)ยท๊ตญ๋ฏผ์—ฐ๊ธˆ์„ API๋กœ ์ง์ ‘ ํ˜ธ์ถœํ•˜๊ณ , ์ˆ˜์ง‘ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ํ•˜๋‚˜์˜ ํˆฌ์ž thesis๋กœ ์—ฎ์–ด ์ธ์šฉ๊ณผ ํ•จ๊ป˜ ๋‹ตํ•ฉ๋‹ˆ๋‹ค. ๋ฏธ๊ตญ ์ข…๋ชฉ๋„ ๊ฐ™์€ ๋ฃจํ”„์—์„œ ๋‹ค๋ฃจ๊ณ  ๊ต์ฐจ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. -[![Twitter Follow](https://img.shields.io/twitter/follow/virattt?style=social)](https://twitter.com/virattt) [![Discord](https://img.shields.io/badge/Discord-Join%20Server-5865F2?style=social&logo=discord)](https://discord.gg/jpGHv2XB6T) - -Dexter ์•„ํ‚คํ…์ฒ˜ +[virattt/dexter](https://github.com/virattt/dexter)(๋ฏธ๊ตญ ์‹œ์žฅ ์ค‘์‹ฌ์˜ ์ž์œจ ๊ธˆ์œต ๋ฆฌ์„œ์น˜ ์—์ด์ „ํŠธ)๋ฅผ ํฌํฌํ•ด, ๊ทธ ์œ„์— **ํ•œ๊ตญ ์ฃผ์‹ ๋ฆฌ์„œ์น˜ ๋ ˆ์ด์–ด**์™€ ๊ทธ ๋ฐ์ดํ„ฐ๊ฐ€ ์กฐ์šฉํžˆ ๊นจ์ง€์ง€ ์•Š๊ฒŒ ๋ฐ›์ณ์ฃผ๋Š” **๊ฒฌ๊ณ ์„ฑ ๋ ˆ์ด์–ด**๋ฅผ ์–น์€ ๊ฒƒ์ด ์ด ์ €์žฅ์†Œ์˜ ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค. -## ๋ชฉ์ฐจ +[![Twitter Follow](https://img.shields.io/twitter/follow/virattt?style=social)](https://twitter.com/virattt) [![Discord](https://img.shields.io/badge/Discord-Join%20Server-5865F2?style=social&logo=discord)](https://discord.gg/jpGHv2XB6T) -- [๐Ÿ‘‹ ๊ฐœ์š”](#-๊ฐœ์š”) -- [๐Ÿ‡ฐ๐Ÿ‡ท ํ•œ๊ตญ ์ฃผ์‹ ๋ฆฌ์„œ์น˜](#-ํ•œ๊ตญ-์ฃผ์‹-๋ฆฌ์„œ์น˜) -- [๐Ÿ’ก ์‚ฌ์šฉ ์˜ˆ์‹œ](#-์‚ฌ์šฉ-์˜ˆ์‹œ) -- [๐Ÿ†š ์›น ์ฑ—๋ด‡๊ณผ ๋น„๊ต](#-์›น-์ฑ—๋ด‡๊ณผ-๋น„๊ต) -- [๐Ÿš€ ๋น ๋ฅธ ์‹œ์ž‘](#-๋น ๋ฅธ-์‹œ์ž‘) -- [๐Ÿ”ง ๋” ์•Œ์•„๋ณด๊ธฐ](#-๋”-์•Œ์•„๋ณด๊ธฐ) -- [๐Ÿ“„ ๋ผ์ด์„ ์Šค](#-๋ผ์ด์„ ์Šค) -- [โš ๏ธ ๋ฉด์ฑ… ์กฐํ•ญ](#๏ธ-๋ฉด์ฑ…-์กฐํ•ญ) +Dexter TUI -## ๐Ÿ‘‹ ๊ฐœ์š” +--- -Dexter๋Š” ๋ณต์žกํ•œ ๊ธˆ์œต ์งˆ๋ฌธ์„ ๋ฐ›์•„ ๋ช…ํ™•ํ•œ ๋‹จ๊ณ„๋ณ„ ๋ฆฌ์„œ์น˜ ๊ณ„ํš์œผ๋กœ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค. ๊ทธ ์ž‘์—…์„ ์‹ค์‹œ๊ฐ„ ์‹œ์žฅ ๋ฐ์ดํ„ฐ๋กœ ์‹คํ–‰ํ•˜๊ณ , ์ž์‹ ์˜ ์ž‘์—…์„ ์ ๊ฒ€ํ•˜๋ฉฐ, ๋ฐ์ดํ„ฐ๋กœ ๋’ท๋ฐ›์นจ๋˜๋Š” ํ™•์‹  ์žˆ๋Š” ๋‹ต์— ์ด๋ฅผ ๋•Œ๊นŒ์ง€ ๊ฒฐ๊ณผ๋ฅผ ๋‹ค๋“ฌ์Šต๋‹ˆ๋‹ค. +**๋ฐ”๋กœ๊ฐ€๊ธฐ** ยท [๋ฌด์—‡์„ ํ•˜๋‚˜](#๋ฌด์—‡์„-ํ•˜๋‚˜) ยท [์™œ ๋”ฐ๋กœ ๋งŒ๋“ค์—ˆ๋‚˜](#์™œ-ํ•œ๊ตญ-์ฃผ์‹์€-๋”ฐ๋กœ-๋งŒ๋“ค์–ด์•ผ-ํ–ˆ๋‚˜) ยท [๋„๊ตฌ](#๋„๊ตฌ) ยท [๊ฒฌ๊ณ ์„ฑ](#๊ฒฌ๊ณ ์„ฑ--์กฐ์šฉํžˆ-๊นจ์ง€์ง€-์•Š๊ธฐ) ยท [ํ•œ๊ตญ ์‹œ์žฅ ํŠœ๋‹](#ํ•œ๊ตญ-์‹œ์žฅ-ํŠœ๋‹) ยท [๋น ๋ฅธ ์‹œ์ž‘](#๋น ๋ฅธ-์‹œ์ž‘) ยท [์˜ˆ์‹œ](#์˜ˆ์‹œ) ยท [์›น ์ฑ—๋ด‡ ๋Œ€๋น„](#์›น-์ฑ—๋ด‡-๋Œ€๋น„) ยท [ํ•œ๊ณ„](#์ •์งํ•œ-ํ•œ๊ณ„) ยท [๊ฐœ๋ฐœยทํ‰๊ฐ€](#๊ฐœ๋ฐœ--ํ‰๊ฐ€) -**ํ•ต์‹ฌ ์—ญ๋Ÿ‰:** -- **์ง€๋Šฅํ˜• ์ž‘์—… ๊ณ„ํš**: ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๋ฅผ ๊ตฌ์กฐํ™”๋œ ๋ฆฌ์„œ์น˜ ๋‹จ๊ณ„๋กœ ์ž๋™ ๋ถ„ํ•ด -- **์ž์œจ ์‹คํ–‰**: ๊ธˆ์œต ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•  ์•Œ๋งž์€ ํˆด์„ ์„ ํƒํ•ด ์‹คํ–‰ -- **์ž๊ธฐ ๊ฒ€์ฆ**: ์ž์‹ ์˜ ์ž‘์—…์„ ์ ๊ฒ€ํ•˜๊ณ  ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋ฐ˜๋ณต -- **์‹ค์‹œ๊ฐ„ ๊ธˆ์œต ๋ฐ์ดํ„ฐ**: ์†์ต๊ณ„์‚ฐ์„œยท์žฌ๋ฌด์ƒํƒœํ‘œยทํ˜„๊ธˆํ๋ฆ„ํ‘œ ์ ‘๊ทผ -- **๋ฏธ๊ตญยทํ•œ๊ตญ ๋™์‹œ ์ง€์›**: ํ•˜๋‚˜์˜ ์—์ด์ „ํŠธ์—์„œ US/KR ์ข…๋ชฉ์„ ํ•จ๊ป˜ ๋‹ค๋ฃจ๊ณ  ๊ต์ฐจ ๋น„๊ต -- **1์ฐจ ์ถœ์ฒ˜ + ๊ฐ์‚ฌ ๊ฐ€๋Šฅ**: ๋‰ด์Šค(2์ฐจ)๊ฐ€ ์•„๋‹ˆ๋ผ DARTยทKRXยทNaver๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๊ณ , ๋ชจ๋“  ํˆด ํ˜ธ์ถœ์„ ์Šคํฌ๋ž˜์น˜ํŒจ๋“œ์— ๊ธฐ๋ก โ€” [์›น ์ฑ—๋ด‡๊ณผ ๋ฌด์—‡์ด ๋‹ค๋ฅธ์ง€](#-์›น-์ฑ—๋ด‡๊ณผ-๋น„๊ต) -- **์•ˆ์ „์žฅ์น˜**: ๋ฌดํ•œ ๋ฃจํ”„ ๊ฐ์ง€์™€ ๋‹จ๊ณ„ ์ œํ•œ์„ ๋‚ด์žฅํ•ด ํญ์ฃผ ์‹คํ–‰ ๋ฐฉ์ง€ +## ๋ฌด์—‡์„ ํ•˜๋‚˜ -### ์•„ํ‚คํ…์ฒ˜ +์ž์—ฐ์–ด๋กœ ์งˆ๋ฌธ์„ ๋˜์ง€๋ฉด โ€” ์‹œ์žฅ์„ ์ง€์ •ํ•  ํ•„์š” ์—†์ด โ€” ์—์ด์ „ํŠธ๊ฐ€ ํ‹ฐ์ปค ํ˜•ํƒœ(6์ž๋ฆฌ ์ˆซ์ž = ํ•œ๊ตญ)์™€ ์–ธ์–ด๋ฅผ ๋ณด๊ณ  ์•Œ๋งž์€ ๋„๊ตฌ๋กœ ๋ผ์šฐํŒ…ํ•˜๊ณ , 1์ฐจ ์ถœ์ฒ˜๋ฅผ ํ˜ธ์ถœํ•ด, ๊ฒฐ๊ณผ๋ฅผ ๊ต์ฐจ ์ข…ํ•ฉํ•œ ๋’ค ์ธ์šฉ๋œ ๋‹ต์„ ๋ƒ…๋‹ˆ๋‹ค. -Dexter TUI +``` +> ์‚ผ์„ฑ์ „์ž ์ง€๊ธˆ ํˆฌ์ž ๊ด€์ ์—์„œ ์–ด๋•Œ? +``` -## ๐Ÿ‡ฐ๐Ÿ‡ท ํ•œ๊ตญ ์ฃผ์‹ ๋ฆฌ์„œ์น˜ +์ด ํ•œ ์ค„์ด ๋‚ด๋ถ€์—์„œ๋Š”: **์ •๊ทœํ™” ์‹ค์ **(๋งค์ถœยท์˜์—…์ด์ตยท์ˆœ์ด์ตยท๋งˆ์ง„ยทROEยทFCFยทYoY) + **์™ธ๊ตญ์ธ/๊ธฐ๊ด€/๊ฐœ์ธ ์ผ๋ณ„ ์ˆ˜๊ธ‰** + **๊ณต๋งค๋„ ์ž”๊ณ ** + **5%๋ฃฐ ๋Œ€๋Ÿ‰๋ณด์œ ** + **๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค**๋ฅผ ๊ฐ๊ฐ 1์ฐจ ์ถœ์ฒ˜์—์„œ ๋Œ์–ด์™€, "์ˆ˜๊ธ‰ยท์‹ค์ ยท์ง€๋ฐฐ๊ตฌ์กฐ๊ฐ€ ๊ฐ™์€ ๋ฐฉํ–ฅ์ธ์ง€ ์ถฉ๋Œ์ธ์ง€"๋ฅผ ํŒ๋‹จํ•˜๋Š” ํ•˜๋‚˜์˜ thesis๋กœ ๋ฌถ์ž…๋‹ˆ๋‹ค. ๋‹จ์ˆœ ์ง€ํ‘œ ๋‚˜์—ด์ด ์•„๋‹ˆ๋ผ ์ข…ํ•ฉ์ด ๊ฒฐ๊ณผ๋ฌผ์ž…๋‹ˆ๋‹ค. -Dexter๋Š” ๋ฏธ๊ตญ ์ฃผ์‹๊ณผ ํ•œ๊ตญ ์ฃผ์‹์„ **ํ•˜๋‚˜์˜ ์—์ด์ „ํŠธ์—์„œ ๋™์‹œ์—** ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์‹œ์žฅ์„ ๋ช…์‹œํ•  ํ•„์š” ์—†์ด, LLM์ด ํ‹ฐ์ปค ํ˜•ํƒœ์™€ ํšŒ์‚ฌ๋ช… ์–ธ์–ด๋ฅผ ๋ณด๊ณ  ์•Œ๋งž์€ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ๊ณจ๋ผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. (ํ•œ๊ตญ ๋ฐ์ดํ„ฐ ํ™œ์„ฑํ™”๋Š” [๋น ๋ฅธ ์‹œ์ž‘](#-๋น ๋ฅธ-์‹œ์ž‘)์˜ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์ฐธ๊ณ .) +๋ชจ๋“  ๋„๊ตฌ ํ˜ธ์ถœ์€ ์Šคํฌ๋ž˜์น˜ํŒจ๋“œ(JSONL)์— ๊ธฐ๋ก๋˜์–ด **๋ฌด์—‡์„ ์–ด๋””์„œ ๊ฐ€์ ธ์™”๋Š”์ง€ ๊ฐ์‚ฌยท์žฌํ˜„**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -### ์‹ ๊ทœ ํˆด (DART ๊ธฐ๋ฐ˜) +## ์™œ ํ•œ๊ตญ ์ฃผ์‹์€ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด์•ผ ํ–ˆ๋‚˜ -`DART_API_KEY`๊ฐ€ ์„ค์ •๋˜๋ฉด ์ž๋™ ๋“ฑ๋ก๋ฉ๋‹ˆ๋‹ค. +๋ฏธ๊ตญ์šฉ ๋„๊ตฌ๋ฅผ 6์ž๋ฆฌ ํ•œ๊ตญ ํ‹ฐ์ปค์— ๊ทธ๋Œ€๋กœ ์“ธ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค(์• ์ดˆ์— ํ‹ฐ์ปค๋ฅผ ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•จ). ๊ทธ๋ฆฌ๊ณ  ํ•œ๊ตญ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋Š” ๋ฏธ๊ตญ์˜ ๊น”๋”ํ•œ ํ‚ค ๊ธฐ๋ฐ˜ API์™€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค: -| ํˆด | ๋Œ€์‘๋˜๋Š” ๋ฏธ๊ตญ ๊ฐœ๋… | ๋ฐ์ดํ„ฐ ์†Œ์Šค | +| ์†Œ์Šค | ๋ฌด์—‡ | ํ˜„์‹ค | |---|---|---| -| `get_financials_kr` | `get_financials` (10-K/10-Q) | DART ์‚ฌ์—…ยท๋ฐ˜๊ธฐยท๋ถ„๊ธฐ๋ณด๊ณ ์„œ | -| `get_filings_kr` | SEC EDGAR | DART ๊ณต์‹œ ๊ฒ€์ƒ‰ | -| `read_filings_kr` | `read_filings` (10-K ๋ณธ๋ฌธ) | DART ์‚ฌ์—…๋ณด๊ณ ์„œ ๋ณธ๋ฌธ (์‚ฌ์—…์˜ ๋‚ด์šฉยท์œ„ํ—˜๊ด€๋ฆฌยทMD&A) | -| `get_large_holders_kr` | 13F (5% ์ด์ƒ ๋ณด์œ ) | DART ๋Œ€๋Ÿ‰๋ณด์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ | -| `get_insider_trades_kr` | Form 4 (๋‚ด๋ถ€์ž ๊ฑฐ๋ž˜) | DART ์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ ๋ณด๊ณ  | +| **DART** (๊ธˆ๊ฐ์› ์ „์ž๊ณต์‹œ) | ์žฌ๋ฌดยท๊ณต์‹œยท์ง€๋ฐฐ๊ตฌ์กฐ | ๋ฌด๋ฃŒ ๊ณต์‹ API์ง€๋งŒ **์ผ 20,000๊ฑด ํ•œ๋„** | +| **KRX** (ํ•œ๊ตญ๊ฑฐ๋ž˜์†Œ) | ๊ณต๋งค๋„ ์ž”๊ณ  | 2024โ€“25๋ถ€ํ„ฐ **ํšŒ์› ๋กœ๊ทธ์ธ ํ•„์ˆ˜**(์ต๋ช… ์ฐจ๋‹จ) | +| **Naver** ๋ชจ๋ฐ”์ผ | ํ˜„์žฌ๊ฐ€ยท์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ | **๊ณต์‹ ๊ณ„์•ฝ ์—†๋Š”** JSON ์—”๋“œํฌ์ธํŠธ | +| **๊ตญ๋ฏผ์—ฐ๊ธˆ** (data.go.kr) | ์—ฐ๊ธฐ๊ธˆ ๋ณด์œ  | ๋ถ„๊ธฐ ์•„๋‹Œ **์—ฐ๋ง ์Šค๋ƒ…์ƒท**, ์ข…๋ชฉ๋ช… ๋งค์นญ | -### ์‹ ๊ทœ ํˆด (Korea-specific, ์†Œ์Šค๋ณ„ ํ‚ค) +๊ทธ๋ž˜์„œ ๋‘ ๊ฐ€์ง€๊ฐ€ ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค โ€” **(1)** ๊ฐ ์†Œ์Šค๋ฅผ ์ œ๋Œ€๋กœ ํ˜ธ์ถœํ•˜๋Š” ๋„๊ตฌ, **(2)** ๊ทธ๊ฒŒ ํ•œ๋„ ์ดˆ๊ณผยท๋กœ๊ทธ์ธ ๋งŒ๋ฃŒยท์‘๋‹ต ๊ตฌ์กฐ ๋ณ€๊ฒฝ์— **์กฐ์šฉํžˆ ๊นจ์ง€์ง€ ์•Š๊ฒŒ** ๋ฐ›์ณ์ฃผ๋Š” ๊ฒฌ๊ณ ์„ฑ ๋ ˆ์ด์–ด. ์•„๋ž˜ [๊ฒฌ๊ณ ์„ฑ](#๊ฒฌ๊ณ ์„ฑ--์กฐ์šฉํžˆ-๊นจ์ง€์ง€-์•Š๊ธฐ) ์„น์…˜์ด ์ด ํฌํฌ์˜ ์‹ค์ œ ์ฐจ๋ณ„์ ์ž…๋‹ˆ๋‹ค. -DART์— ์—†๋Š” ํ•œ๊ตญ ํŠนํ™” ๋ฐ์ดํ„ฐ. ํˆด๋งˆ๋‹ค ์†Œ์Šคยทํ‚ค๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. +## ๋„๊ตฌ -| ํˆด | ๋Œ€์‘๋˜๋Š” ๋ฏธ๊ตญ ๊ฐœ๋… | ๋ฐ์ดํ„ฐ ์†Œ์Šค | ํ™œ์„ฑํ™” ์กฐ๊ฑด | -|---|---|---|---| -| `get_market_data_kr` | `get_market_data` (ํ˜„์žฌ๊ฐ€ยท์‹œ์ด) | ํ˜„์žฌ๊ฐ€ยท์‹œ์ดยทPER/PBRยท๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šคยทpeer (Naver) | ํ•ญ์ƒ (ํ‚ค ๋ถˆํ•„์š”) | -| `get_foreign_ownership_kr` | (๋ฏธ๊ตญ์—” ์—†์Œ) | ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ (Naver) | ํ•ญ์ƒ (ํ‚ค ๋ถˆํ•„์š”) | -| `get_short_balance_kr` | Short interest | KRX ๊ณต๋งค๋„ ์ˆœ๋ณด์œ ์ž”๊ณ  | `KRX_ID`+`KRX_PW` ๋˜๋Š” `KRX_COOKIE` | -| `get_nps_holdings` | (๋ฏธ๊ตญ์—” ์—†์Œ) | ๊ตญ๋ฏผ์—ฐ๊ธˆ ๊ตญ๋‚ด์ฃผ์‹ ํˆฌ์ž์ •๋ณด (data.go.kr) | `DATA_GO_KR_SERVICE_KEY` | +**DART ๊ธฐ๋ฐ˜** โ€” `DART_API_KEY`๊ฐ€ ์„ค์ •๋˜๋ฉด ์ž๋™ ๋“ฑ๋ก. -### ์ข…๋ชฉ ์ฝ”๋“œ ํ•ด๊ฒฐ ํ๋ฆ„ +| ๋„๊ตฌ | ๋Œ€์‘ ๋ฏธ๊ตญ ๊ฐœ๋… | ๋‚ด์šฉ | +|---|---|---| +| `get_financials_kr` | `get_financials` (10-K/10-Q) | ์‚ฌ์—…ยท๋ฐ˜๊ธฐยท๋ถ„๊ธฐ๋ณด๊ณ ์„œ, K-IFRS ์ •๊ทœํ™” ์š”์•ฝ(์—ฐ๊ฒฐ/๋ณ„๋„) | +| `get_filings_kr` | SEC EDGAR | DART ๊ณต์‹œ ๊ฒ€์ƒ‰ | +| `read_filings_kr` | `read_filings` (10-K ๋ณธ๋ฌธ) | ๋ณด๊ณ ์„œ ๋ณธ๋ฌธ โ€” ์‚ฌ์—…์˜ ๋‚ด์šฉยท์ฃผ์š”์ œํ’ˆยท์œ„ํ—˜๊ด€๋ฆฌยทMD&A **+ ์ง€๋ฐฐ๊ตฌ์กฐยท์ตœ๋Œ€์ฃผ์ฃผยทํŠน์ˆ˜๊ด€๊ณ„์žยท๊ณ„์—ดํšŒ์‚ฌยท๋Œ€์ฃผ์ฃผ ๊ฑฐ๋ž˜** | +| `get_large_holders_kr` | 13F (5%+ ๋ณด์œ ) | ๋Œ€๋Ÿ‰๋ณด์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ | +| `get_insider_trades_kr` | Form 4 (๋‚ด๋ถ€์ž) | ์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ ๋ณด๊ณ  | -LLM์ด "์‚ผ์„ฑ์ „์ž" ๊ฐ™์€ ์ž์—ฐ์–ด ์ž…๋ ฅ์„ ๋ฐ›์œผ๋ฉด, ํˆด ๋‚ด๋ถ€์—์„œ **ํ‹ฐ์ปค โ†’ corp_code** ๋ณ€ํ™˜์„ ๊ฑฐ์ณ DART API๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. +**Korea-specific** โ€” DART์— ์—†๋Š” ๋ฐ์ดํ„ฐ. ์†Œ์Šคยทํ‚ค๊ฐ€ ๋„๊ตฌ๋งˆ๋‹ค ๋‹ค๋ฆ„. -`TickerRegistry`๋Š” DART์˜ ๋งˆ์Šคํ„ฐ ํŒŒ์ผ(`corpCode.xml`)์„ **์ฒซ ์‹คํ–‰ ์‹œ ํ•œ ๋ฒˆ ๋‹ค์šด๋กœ๋“œ**ํ•ด `.dexter/cache/`์— ์ €์žฅํ•˜๊ณ , 7์ผ๋งˆ๋‹ค ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค. ์‹ ๊ทœ ์ƒ์žฅยท์‚ฌ๋ช… ๋ณ€๊ฒฝยท๋ฌผ์ ๋ถ„ํ• ์ด ์ž๋™ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. +| ๋„๊ตฌ | ๋‚ด์šฉ (์†Œ์Šค) | ํ™œ์„ฑํ™” | +|---|---|---| +| `get_market_data_kr` | ํ˜„์žฌ๊ฐ€ยท์‹œ์ดยทPER/PBR/EPS/BPSยท์ถ”์ •PERยท๋ฐฐ๋‹นยท๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šคยทpeer (Naver) | **ํ‚ค ๋ถˆํ•„์š”** | +| `get_foreign_ownership_kr` | ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ + ์™ธ๊ตญ์ธ/๊ธฐ๊ด€/๊ฐœ์ธ ์ˆœ๋งค์ˆ˜ (Naver) | **ํ‚ค ๋ถˆํ•„์š”** | +| `get_short_balance_kr` | ๊ณต๋งค๋„ ์ˆœ๋ณด์œ ์ž”๊ณ  (KRX) | `KRX_ID`+`KRX_PW` ๋˜๋Š” `KRX_COOKIE` | +| `get_nps_holdings` | ๊ตญ๋ฏผ์—ฐ๊ธˆ ๊ตญ๋‚ด์ฃผ์‹ ๋ณด์œ  (data.go.kr) | `DATA_GO_KR_SERVICE_KEY` | -### ํ•œ๊ตญ ์‹œ์žฅ ๊ณ ์œ  ์ฒ˜๋ฆฌ +> ํ˜„์žฌ๊ฐ€ยท์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ์€ **ํ‚ค ํ•˜๋‚˜ ์—†์ด** ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. DART ํ‚ค๊ฐ€ ์—†์–ด๋„ ํ•œ๊ตญ ์ข…๋ชฉ์˜ ์‹œ์„ธยท๋ฐธ๋ฅ˜์—์ด์…˜ยท์™ธ๊ตญ์ธ ์ˆ˜๊ธ‰์€ ๋ฐ”๋กœ ์กฐํšŒ๋ฉ๋‹ˆ๋‹ค. -- **K-IFRS ์žฌ๋ฌด์ œํ‘œ** โ€” ์—ฐ๊ฒฐ/๋ณ„๋„ ๋‘˜ ๋‹ค ์กฐํšŒ, **๋งค์ถœยท์˜์—…์ด์ตยท์ˆœ์ด์ตยท๋งˆ์ง„ยทROEยทFCFยทYoY๋กœ ์ •๊ทœํ™”ํ•œ ์š”์•ฝ**์„ ๋ฐ˜ํ™˜(์›์‹œ ๋ผ์ธ์•„์ดํ…œ์€ ํŒŒ์ผ๋กœ ๋ณด์กดํ•ด ํ•„์š” ์‹œ ๋“œ๋ฆด๋‹ค์šด). ์˜์—…์ด์ต ์ •์˜๊ฐ€ ๋ฏธ๊ตญ GAAP์™€ ๋ฏธ๋ฌ˜ํ•˜๊ฒŒ ๋‹ค๋ฆ„ -- **๋ณด๊ณ ์„œ ๋ณธ๋ฌธ ์ •์„ฑ ๋ถ„์„** โ€” ์‚ฌ์—…๋ณด๊ณ ์„œ์˜ ใ€ŒII. ์‚ฌ์—…์˜ ๋‚ด์šฉใ€ยท์œ„ํ—˜๊ด€๋ฆฌยท๊ฒฝ์˜์ง„๋‹จ(MD&A) ๋ณธ๋ฌธ์„ ์ถ”์ถœยท์š”์•ฝํ•ด "์‚ฌ์—…์ด ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑ๋ผ ์žˆ๋‚˜", "์ฃผ์š” ๋ฆฌ์Šคํฌ๊ฐ€ ๋ญ์•ผ" ๊ฐ™์€ ์ •์„ฑ ์งˆ๋ฌธ์— ๋ณธ๋ฌธ ๊ทผ๊ฑฐ๋กœ ๋‹ต๋ณ€(์ˆซ์ž ํ‘œ๋Š” `get_financials_kr`๋กœ ๋ผ์šฐํŒ…). ํ•œ๊ตญ ๋ณด๊ณ ์„œ์—” SEC 10-K์˜ ์œ„ํ—˜์š”์†Œ(Item 1A) ๊ฐ™์€ ๋‹จ์ผ ์„น์…˜์ด ์—†์–ด, ํฉ์–ด์ง„ ์œ„ํ—˜๊ด€๋ฆฌยทํˆฌ์ž์ž ๋ณดํ˜ธ ํ•ญ๋ชฉ์„ ๋ชจ์•„ ์ข…ํ•ฉ -- **๋‹จ์œ„** โ€” ์›(KRW), ๋ฐฑ๋งŒ์›/์–ต/์กฐ ๋‹จ์œ„๋กœ ์ž๋™ ํฌ๋งทํŒ… -- **๊ฑฐ๋ž˜์‹œ๊ฐ„** โ€” 09:00โ€“15:30 KST ๊ธฐ์ค€ -- **์ƒํ•˜ํ•œ๊ฐ€** โ€” ยฑ30% ์ผ๊ฐ„ ๋ณ€๋™ ์ œํ•œ ๊ณ ๋ ค -- **DCF ์ž๋™ ๋ถ„๊ธฐ** โ€” 6์ž๋ฆฌ ํ‹ฐ์ปค๋ฉด DCF ์Šคํ‚ฌ์ด ํ•œ๊ตญ ๊ฒฝ๋กœ๋กœ ์ „ํ™˜(ํ•œ๊ตญ ๋ฒ•์ธ์„ธ์œจ ~22%ยท๊ตญ๊ณ ์ฑ„ ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ ~3%ยทKRW). **์ˆœ๋ถ€์ฑ„๋ฅผ ๋ถ€์ฑ„์ด๊ณ„๊ฐ€ ์•„๋‹ˆ๋ผ ์ด์ž๋ถ€์ฑ„(์ฐจ์ž…๊ธˆยท์‚ฌ์ฑ„)์—์„œ ํ˜„๊ธˆยท๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ์„ ๋บ€ ๊ฐ’์œผ๋กœ ์‚ฐ์ •** โ€” ์‚ผ์„ฑ์ฒ˜๋Ÿผ ์ˆœํ˜„๊ธˆ ๊ธฐ์—…์€ ์ˆœํ˜„๊ธˆ์„ ๋”ํ•ด ์ฃผ์‹๊ฐ€์น˜๊ฐ€ EV๋ณด๋‹ค ์ปค์ง„๋‹ค(๋ถ€์ฑ„์ด๊ณ„๋กœ ์ž˜๋ชป ๋นผ๋ฉด ๋ถ€ํ˜ธ๊ฐ€ ๋’ค์ง‘ํ˜€ ์ €ํ‰๊ฐ€). ๊ฑฐ๋ž˜์„ธยท๋ฐฐ๋‹น์„ธ๋Š” ํˆฌ์ž์ž ์„ธํ›„ ์‹คํ˜„์ˆ˜์ต๋ฅ  ์ฃผ์˜์‚ฌํ•ญ์œผ๋กœ ํ‘œ๊ธฐ -- **์žฌ๋ฒŒ ๊ทธ๋ฃน ๊ตฌ์กฐ** โ€” ์‚ผ์„ฑยทํ˜„๋Œ€์ฐจยทSKยทLG ๋“ฑ ๊ทธ๋ฃน ๋‚ด ์ง€๋ถ„ ๊ด€๊ณ„ ๋ถ„์„ ๊ฐ€๋Šฅ -- **๋ฌผ์ ๋ถ„ํ•  ๋ถ„์„** โ€” LGํ™”ํ•™โ†’LG์—๋„ˆ์ง€์†”๋ฃจ์…˜ ๊ฐ™์€ ๋ถ„ํ•  ์ด๋ฒคํŠธ๋ฅผ ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ ๊ด€์ (ํฌ์„ยท์ง€์ฃผ์‚ฌ ๋””์Šค์นด์šดํŠธ)์—์„œ ํ‰๊ฐ€ํ•˜๋Š” ์ „์šฉ ์Šคํ‚ฌ -- **๊ต์ฐจ์‹ ํ˜ธ ์ข…ํ•ฉ** โ€” ์žฌ๋ฌด(์ •๊ทœํ™” ์†์ต)ยท์™ธ๊ตญ์ธ ์ˆ˜๊ธ‰ยท๊ณต๋งค๋„ยท๋Œ€๋Ÿ‰๋ณด์œ ยท๊ณต์‹œ๋ฅผ ๋‹จ์ˆœ ๋‚˜์—ด์ด ์•„๋‹ˆ๋ผ **ํ•˜๋‚˜์˜ ํˆฌ์ž thesis๋กœ ์—ฎ์–ด** ์ œ์‹œ(์ˆ˜๊ธ‰ยท์‹ค์ ยท์ง€๋ฐฐ๊ตฌ์กฐ๊ฐ€ ๊ฐ™์€ ๋ฐฉํ–ฅ์ธ์ง€ ์ถฉ๋Œ์ธ์ง€ ํŒ๋‹จ, ๋Œ€๋Ÿ‰๋ณด์œ  ์ง‘์ค‘์€ ๋ฐธ๋ฅ˜์—์ด์…˜ ์š”์ธ์œผ๋กœ ์ฒ˜๋ฆฌ) +## ๊ฒฌ๊ณ ์„ฑ โ€” ์กฐ์šฉํžˆ ๊นจ์ง€์ง€ ์•Š๊ธฐ -## ๐Ÿ’ก ์‚ฌ์šฉ ์˜ˆ์‹œ +ํ•œ๊ตญ ์†Œ์Šค์˜ ๊ฑฐ์นœ ํ˜„์‹ค ์œ„์—์„œ ์ž˜๋ชป๋œ ์ˆซ์ž๋ฅผ ์‚ฌ์‹ค์ฒ˜๋Ÿผ ๋‹ตํ•˜์ง€ ์•Š๋„๋ก ๋‹ค์Œ์„ ๋‚ด์žฅํ–ˆ์Šต๋‹ˆ๋‹ค. -์•„๋ž˜๋Š” Dexter์— ๋ฐ”๋กœ ๋˜์งˆ ์ˆ˜ ์žˆ๋Š” ๊ตฌ์ฒด์ ์ธ ์ฟผ๋ฆฌ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค. ์‹œ์žฅ(๋ฏธ๊ตญ/ํ•œ๊ตญ)์„ ๋”ฐ๋กœ ์ง€์ •ํ•  ํ•„์š” ์—†์ด, ํ‹ฐ์ปค ํ˜•ํƒœ์™€ ์–ธ์–ด๋ฅผ ๋ณด๊ณ  ์—์ด์ „ํŠธ๊ฐ€ ์•Œ๋งž์€ ํˆดยท์Šคํ‚ฌ๋กœ ๋ผ์šฐํŒ…ํ•ฉ๋‹ˆ๋‹ค. +- **DART ์ฟผํ„ฐ ๋ณดํ˜ธ** โ€” `get_financials_kr`(์—ฐ๋„๋ณ„) ร— ๋ผ์šฐํŒ…(์ข…๋ชฉ๋ณ„) ร— ์—์ด์ „ํŠธ(๋„๊ตฌ ๋ณ‘๋ ฌ)๊ฐ€ ๊ณฑํ•ด์ง€๋ฉด "์ด ์ข…๋ชฉ ๋ถ„์„" ํ•œ ๋ฒˆ์ด ์ˆ˜์‹ญ ๊ฐœ์˜ DART ํ˜ธ์ถœ๋กœ ๋ฒˆ์ง‘๋‹ˆ๋‹ค. **๊ณต์œ  ๋™์‹œ์„ฑ ์บก**(๊ธฐ๋ณธ 4, `DART_MAX_CONCURRENCY`๋กœ ์กฐ์ •)์ด ์ผ 20,000๊ฑด ํ•œ๋„๋ฅผ ํƒœ์šฐ์ง€ ์•Š๊ฒŒ ๋ง‰๊ณ , ํ•œ๋„ ์ดˆ๊ณผ(`020`)๊ฐ€ ๊ฐ์ง€๋˜๋ฉด **์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค**๊ฐ€ ๋‚˜๋จธ์ง€ ํ˜ธ์ถœ์„ ๋„คํŠธ์›Œํฌ ๋‚ญ๋น„ ์—†์ด ์ฆ‰์‹œ ์ค‘๋‹จํ•˜๊ณ  ๋ช…ํ™•ํ•œ ํ•œ๊ตญ์–ด ๋ฉ”์‹œ์ง€๋กœ ์•Œ๋ฆฐ ๋’ค ์ฟจ๋‹ค์šด ํ›„ ์ž๋™ ์žฌ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค. (์ผ์ผ ํ•œ๋„์—” ์žฌ์‹œ๋„ backoff๊ฐ€ ๋ฌด์˜๋ฏธํ•˜๋ฏ€๋กœ throttle + ๋ช…์‹œ์  ์•Œ๋ฆผ์œผ๋กœ ๋Œ€์‘.) +- **๋ฐ์ดํ„ฐ ๋“œ๋ฆฌํ”„ํŠธ ๊ฐ์ง€(canary)** โ€” Naver JSON์€ ๋น„๊ณต์‹์ด๋ผ ์–ด๋А ๋‚  ํ•„๋“œ๋ช…์ด ๋ฐ”๋€Œ๋ฉด ๊ฐ’์ด **์กฐ์šฉํžˆ null**์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ต์‹ฌ ํ•„๋“œ(ํ˜„์žฌ๊ฐ€ยท์‹œ์ดยท์ผ๋ณ„ ์ˆ˜๊ธ‰)๊ฐ€ ๋น„๋ฉด ๊ฒฐ๊ณผ์— `_dataQualityWarning`์„ ๋ถ™์—ฌ ๋ชจ๋ธ์ด ๊นจ์ง„ ๊ฐ’์„ ๋ณด๊ณ ํ•˜์ง€ ์•Š๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ณ„ ์ข…๊ฐ€๊ฐ€ rename๋˜์–ด **์ „์ผ ์ข…๊ฐ€๋กœ ๊ฐ€๋ ค์ง€๋Š”** ์ตœ์•… ์ผ€์ด์Šค๊นŒ์ง€ ๊ฐ์ง€ํ•˜๊ณ , ETF/ETNยทํŽ€๋“œยท๋ฌด์ปค๋ฒ„ ์ข…๋ชฉ์ฒ˜๋Ÿผ ๋ณธ๋ž˜ ์ผ๋ถ€ ์ง€ํ‘œ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ๋Š” ์˜คํƒ์„ ๋‚ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +- **keyless ํ”Œ๋ ˆ์ด๋ถ ํ‹ฐ์–ด๋ง** โ€” DART ํ‚ค๊ฐ€ ์—†์–ด๋„ keyless ๋„๊ตฌ๋งŒ์œผ๋กœ **์ˆ˜๊ธ‰ยท๋ฐธ๋ฅ˜์—์ด์…˜ ๋ฆฌ์„œ์น˜ ๊ฐ€์ด๋“œ๊ฐ€ ์‚ด์•„ ์žˆ์Šต๋‹ˆ๋‹ค.** ํ‚ค๊ฐ€ ์žˆ์œผ๋ฉด DART 1์ฐจ ์ž๋ฃŒ๋ฅผ ํฌํ•จํ•œ ์ „์ฒด ๊ต์ฐจ์‹ ํ˜ธ ํ”Œ๋ ˆ์ด๋ถ์œผ๋กœ ํ™•์žฅ๋ฉ๋‹ˆ๋‹ค(๋ฏธ๋“ฑ๋ก ๋„๊ตฌ๋ฅผ ๊ฐ€๋ฆฌํ‚ค์ง€ ์•Š๋„๋ก ๋“ฑ๋ก ๊ฒŒ์ดํŠธ์™€ ๋‹จ์ผ ์ง„์‹ค์›์œผ๋กœ ์ผ์น˜). +- **๊ฒฐ์ •์  ์žฌํ˜„(record/replay)** โ€” ํ‰๊ฐ€ ํ•˜๋„ค์Šค๊ฐ€ ์‹ค์ œ DARTยทKRXยทNaver ์‘๋‹ต์„ ํ”ฝ์Šค์ฒ˜๋กœ ๊ธฐ๋กํ•ด๋‘๋ฉด, ๊ฐ™์€ ์งˆ๋ฌธ์„ **ํ‚คยท๋„คํŠธ์›Œํฌ ์—†์ด ๊ฒฐ์ •์ ์œผ๋กœ ์žฌํ˜„ยทํšŒ๊ท€ ํ…Œ์ŠคํŠธ**ํ•ฉ๋‹ˆ๋‹ค. [๊ฐœ๋ฐœยทํ‰๊ฐ€](#๊ฐœ๋ฐœ--ํ‰๊ฐ€) ์ฐธ๊ณ . -### ๊ธฐ๋ณธ ์žฌ๋ฌด ๋ถ„์„ +## ํ•œ๊ตญ ์‹œ์žฅ ํŠœ๋‹ -``` -> ์‚ผ์„ฑ์ „์ž ์ตœ๊ทผ 5๋…„ ๋งค์ถœ๊ณผ ์˜์—…์ด์ต ์ถ”์„ธ ์ •๋ฆฌํ•ด์ค˜ -> ํ˜„๋Œ€์ฐจ(005380) ์—ฐ๊ฒฐ ๊ธฐ์ค€ ์ž‰์—ฌํ˜„๊ธˆํ๋ฆ„ 5๋…„์น˜ ๋ณด์—ฌ์ค˜ -> AAPL ์ตœ๊ทผ 4๊ฐœ ๋ถ„๊ธฐ ์˜์—…์ด์ต๋ฅ  ์ถ”์ด ์•Œ๋ ค์ค˜ -``` +- **DCF ์ž๋™ ๋ถ„๊ธฐ** โ€” 6์ž๋ฆฌ ํ‹ฐ์ปค๋ฉด DCF ์Šคํ‚ฌ์ด ํ•œ๊ตญ ๊ฒฝ๋กœ๋กœ ์ „ํ™˜(๋ฒ•์ธ์„ธ์œจ ~22%ยท๊ตญ๊ณ ์ฑ„ ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ ~3%ยทKRW). **์ˆœ๋ถ€์ฑ„๋ฅผ ๋ถ€์ฑ„์ด๊ณ„๊ฐ€ ์•„๋‹ˆ๋ผ ์ด์ž๋ถ€์ฑ„(์ฐจ์ž…๊ธˆยท์‚ฌ์ฑ„)์—์„œ ํ˜„๊ธˆยท๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ์„ ๋บ€ ๊ฐ’์œผ๋กœ** ์‚ฐ์ • โ€” ์‚ผ์„ฑ์ฒ˜๋Ÿผ ์ˆœํ˜„๊ธˆ ๊ธฐ์—…์€ ์ˆœํ˜„๊ธˆ์„ ๋”ํ•ด ์ฃผ์‹๊ฐ€์น˜๊ฐ€ EV๋ณด๋‹ค ์ปค์ง‘๋‹ˆ๋‹ค(๋ถ€์ฑ„์ด๊ณ„๋กœ ์ž˜๋ชป ๋นผ๋ฉด ๋ถ€ํ˜ธ๊ฐ€ ๋’ค์ง‘ํ˜€ ์ €ํ‰๊ฐ€). ๊ฑฐ๋ž˜์„ธยท๋ฐฐ๋‹น์„ธ๋Š” ํˆฌ์ž์ž ์„ธํ›„ ์‹คํ˜„์ˆ˜์ต๋ฅ  ์ฃผ์˜์‚ฌํ•ญ์œผ๋กœ ํ‘œ๊ธฐ. +- **์ง€๋ฐฐ๊ตฌ์กฐยท์†Œ์œ ๊ตฌ์กฐ ๋ณธ๋ฌธ** โ€” ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ์˜ 1์ฐจ ๋ณ€์ˆ˜(์ตœ๋Œ€์ฃผ์ฃผยทํŠน์ˆ˜๊ด€๊ณ„์ธยท๊ณ„์—ดํšŒ์‚ฌยท์ˆœํ™˜์ถœ์žยท๋Œ€์ฃผ์ฃผ ๊ฑฐ๋ž˜)๋ฅผ DART ์‚ฌ์—…๋ณด๊ณ ์„œ ๋ณธ๋ฌธ(VI ํšŒ์‚ฌ์˜ ๊ธฐ๊ด€ยทVII ์ฃผ์ฃผ์— ๊ด€ํ•œ ์‚ฌํ•ญยทIX ๊ณ„์—ดํšŒ์‚ฌยทX ๋Œ€์ฃผ์ฃผ ๋“ฑ๊ณผ์˜ ๊ฑฐ๋ž˜๋‚ด์šฉ)์—์„œ **์ถ”์ •์ด ์•„๋‹ˆ๋ผ 1์ฐจ ๊ทผ๊ฑฐ๋กœ** ์ถ”์ถœ. +- **๋ฌผ์ ๋ถ„ํ•  ๋ถ„์„** โ€” LGํ™”ํ•™โ†’LG์—๋„ˆ์ง€์†”๋ฃจ์…˜ ๊ฐ™์€ ๋ถ„ํ•  ์ด๋ฒคํŠธ๋ฅผ ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ ๊ด€์ (ํฌ์„ยท์ง€์ฃผ์‚ฌ ๋””์Šค์นด์šดํŠธ)์—์„œ ํ‰๊ฐ€ํ•˜๋Š” ์ „์šฉ ์Šคํ‚ฌ. +- **๊ต์ฐจ์‹ ํ˜ธ ์ข…ํ•ฉ** โ€” ์žฌ๋ฌดยท์™ธ๊ตญ์ธ ์ˆ˜๊ธ‰ยท๊ณต๋งค๋„ยท๋Œ€๋Ÿ‰๋ณด์œ ยท๊ณต์‹œ๋ฅผ ๋‹จ์ˆœ ๋‚˜์—ด์ด ์•„๋‹ˆ๋ผ ํ•˜๋‚˜์˜ thesis๋กœ ์—ฎ์Œ(๋Œ€๋Ÿ‰๋ณด์œ  ์ง‘์ค‘์€ ๋ฐธ๋ฅ˜์—์ด์…˜ ์š”์ธ์œผ๋กœ ์ฒ˜๋ฆฌ). +- **K-IFRS ์ •๊ทœํ™”** โ€” ์—ฐ๊ฒฐ/๋ณ„๋„ ๋‘˜ ๋‹ค, ๋งค์ถœยท์˜์—…์ด์ตยท์ˆœ์ด์ตยท๋งˆ์ง„ยทROEยทFCFยทYoY๋กœ ์š”์•ฝ(์›์‹œ ๋ผ์ธ์•„์ดํ…œ์€ ํŒŒ์ผ๋กœ ๋ณด์กดํ•ด ๋“œ๋ฆด๋‹ค์šด). ์˜์—…์ด์ต ์ •์˜๊ฐ€ ๋ฏธ๊ตญ GAAP์™€ ๋ฏธ๋ฌ˜ํ•˜๊ฒŒ ๋‹ค๋ฅธ ์  ๋ฐ˜์˜. +- **๋‹จ์œ„ยท์‹œ์žฅ ๊ทœ์น™** โ€” ์›(KRW)ยท์–ตยท์กฐ ์ž๋™ ํฌ๋งท, 09:00โ€“15:30 KST ๊ฑฐ๋ž˜์‹œ๊ฐ„, ยฑ30% ์ƒํ•˜ํ•œ๊ฐ€. +- **์ข…๋ชฉ ์ฝ”๋“œ ํ•ด๊ฒฐ** โ€” "์‚ผ์„ฑ์ „์ž" โ†’ ํ‹ฐ์ปค โ†’ `corp_code`. DART ๋งˆ์Šคํ„ฐ(`corpCode.xml`)๋ฅผ ์ฒซ ์‹คํ–‰ ์‹œ ๋ฐ›์•„ `.dexter/cache/`์— ์ €์žฅํ•˜๊ณ  7์ผ๋งˆ๋‹ค ๊ฐฑ์‹ (์‹ ๊ทœ ์ƒ์žฅยท์‚ฌ๋ช… ๋ณ€๊ฒฝยท๋ฌผ์ ๋ถ„ํ•  ์ž๋™ ๋ฐ˜์˜). KRX์šฉ ํ‹ฐ์ปคโ†’ISIN ๋งคํ•‘๋„ ๋ณ„๋„ ๊ด€๋ฆฌ. -### ๋ฐธ๋ฅ˜์—์ด์…˜ (DCF) +## ๋น ๋ฅธ ์‹œ์ž‘ -``` -> ์‚ผ์„ฑ์ „์ž DCF๋กœ ์ ์ •์ฃผ๊ฐ€ ๊ณ„์‚ฐํ•˜๊ณ  ํ˜„์žฌ๊ฐ€๋ž‘ ๋น„๊ตํ•ด์ค˜ -> ๋„ค์ด๋ฒ„(035420) ๋‚ด์žฌ๊ฐ€์น˜ ๊ตฌํ•˜๊ณ  WACCยทํ„ฐ๋ฏธ๋„ ์„ฑ์žฅ๋ฅ  ๋ฏผ๊ฐ๋„ํ‘œ๊นŒ์ง€ ๋ณด์—ฌ์ค˜ -> NVDA ์ ์ •๊ฐ€์น˜ ๋ถ„์„ํ•ด์ค˜ -``` +**ํ•„์š”ํ•œ ๊ฒƒ:** [Bun](https://bun.com) v1.0+ ยท LLM ํ‚ค ํ•˜๋‚˜(OpenAI/Anthropic/Google/xAI/OpenRouter/Ollama ๋“ฑ) ยท (์„ ํƒ) ์‹œ์žฅ ๋ฐ์ดํ„ฐ ํ‚ค. +ํ‚ค๊ฐ€ ํ•˜๋‚˜๋„ ์—†์–ด๋„ ํ•œ๊ตญ ์ข…๋ชฉ ํ˜„์žฌ๊ฐ€ยท์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ(Naver, ํ‚ค ๋ถˆํ•„์š”)์€ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. -> 6์ž๋ฆฌ ํ•œ๊ตญ ํ‹ฐ์ปค๋ฉด DCF ์Šคํ‚ฌ์ด ์ž๋™์œผ๋กœ ํ•œ๊ตญ ๊ฒฝ๋กœ(K-IFRSยท๋ฒ•์ธ์„ธ์œจยท๊ตญ๊ณ ์ฑ„ ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌยทKRW)๋กœ ๋ถ„๊ธฐํ•ฉ๋‹ˆ๋‹ค. +```bash +# Bun ์„ค์น˜ (์—†๋‹ค๋ฉด) +curl -fsSL https://bun.com/install | bash # macOS/Linux +# powershell -c "irm bun.sh/install.ps1|iex" # Windows -### ๊ณต์‹œ ยท ์†Œ์œ ๊ตฌ์กฐ (DART) +git clone https://github.com/SangHyeonKwon/dexter-with-korea.git +cd dexter-with-korea +bun install -``` -> ์นด์นด์˜ค ์ตœ๊ทผ 1๋…„ ์ฃผ์š” ๊ณต์‹œ ์ •๋ฆฌํ•ด์ค˜ -> ์‚ผ์„ฑ์ „์ž 5% ์ด์ƒ ๋Œ€๋Ÿ‰๋ณด์œ  ์ฃผ์ฃผ ๋ชฉ๋ก ๋ณด์—ฌ์ค˜ -> ์—์ฝ”ํ”„๋กœ ์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ ์ตœ๊ทผ ์ง€๋ถ„ ๋ณ€๋™ ์•Œ๋ ค์ค˜ +cp env.example .env # ํ‚ค ์ฑ„์šฐ๊ธฐ (์•„๋ž˜) +bun start # ๋Œ€ํ™”ํ˜• ์‹คํ–‰ (bun dev = watch ๋ชจ๋“œ) ``` -### ๋ณด๊ณ ์„œ ๋ณธ๋ฌธ ๋ถ„์„ (DART ์‚ฌ์—…๋ณด๊ณ ์„œ) +**ํ‚ค** (`.env`, `your-`๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฐ’์€ ๋ฏธ์„ค์ •์œผ๋กœ ๊ฐ„์ฃผ): -``` -> ์‚ผ์„ฑ์ „์ž ์‚ฌ์—…์ด ์–ด๋–ค ์‚ฌ์—…๋ถ€๋ฌธ์œผ๋กœ ๊ตฌ์„ฑ๋ผ ์žˆ์–ด? -> ์‚ผ์„ฑ์ „์ž ์ฃผ์š” ๋ฆฌ์Šคํฌ๊ฐ€ ๋ญ์•ผ? -> ๋„ค์ด๋ฒ„(035420) ๊ฒฝ์˜์ง„๋‹จ(MD&A)์—์„œ ๊ฒฝ์˜์ง„์ด ๊ฐ•์กฐํ•œ ํฌ์ธํŠธ ์ •๋ฆฌํ•ด์ค˜ -``` +```bash +# LLM โ€” ์ตœ์†Œ ํ•˜๋‚˜ ํ•„์ˆ˜ +OPENAI_API_KEY=your-openai-api-key +# ANTHROPIC_API_KEY / GOOGLE_API_KEY / XAI_API_KEY / OPENROUTER_API_KEY / OLLAMA_BASE_URL โ€ฆ -### ํ•œ๊ตญ ํŠนํ™” ๋ฐ์ดํ„ฐ +# ๋ฏธ๊ตญ ์‹œ์žฅ ๋ฐ์ดํ„ฐ +FINANCIAL_DATASETS_API_KEY=your-financial-datasets-api-key -``` -> ์‚ผ์„ฑ์ „์ž ํ˜„์žฌ๊ฐ€๋ž‘ ๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค ์•Œ๋ ค์ค˜ # ํ‚ค ๋ถˆํ•„์š” (Naver) -> ์—์ฝ”ํ”„๋กœ๋น„์—  ๊ณต๋งค๋„ ์ˆœ๋ณด์œ ์ž”๊ณ  ์ถ”์ด ์–ด๋•Œ? # KRX ํ‚ค ํ•„์š” -> ์‚ผ์„ฑ์ „์ž ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ ์ตœ๊ทผ ์ถ”์„ธ ๋ณด์—ฌ์ค˜ # ํ‚ค ๋ถˆํ•„์š” (Naver) -> ๊ตญ๋ฏผ์—ฐ๊ธˆ์ด ๋ณด์œ ํ•œ ์‚ผ์„ฑ์ „์ž ์ง€๋ถ„์œจ ์•Œ๋ ค์ค˜ # data.go.kr ํ‚ค ํ•„์š” +# ํ•œ๊ตญ ์‹œ์žฅ +DART_API_KEY=your-dart-api-key # ์žฌ๋ฌดยท๊ณต์‹œยท์ง€๋ฐฐ๊ตฌ์กฐ (๋ฌด๋ฃŒ, ์ผ 20,000๊ฑด โ€” opendart.fss.or.kr) +# KRX_ID / KRX_PW # ๊ณต๋งค๋„ ์ž”๊ณ  (data.krx.co.kr ๋กœ๊ทธ์ธ) +# KRX_COOKIE=JSESSIONID=... # ์†Œ์…œ ๋กœ๊ทธ์ธ ๊ณ„์ •์€ ๋ธŒ๋ผ์šฐ์ € ์ฟ ํ‚ค ๋ถ™์—ฌ๋„ฃ๊ธฐ +# DATA_GO_KR_SERVICE_KEY=... # ๊ตญ๋ฏผ์—ฐ๊ธˆ (data.go.kr, Decoded ํ‚ค) +# DART_MAX_CONCURRENCY=4 # (์„ ํƒ) DART ๋™์‹œ ํ˜ธ์ถœ ์ƒํ•œ +# READ_FILINGS_KR_MODEL=... # (์„ ํƒ) read_filings_kr ๋‚ด๋ถ€ ์š”์•ฝ ๋ชจ๋ธ + +# ์›น ๊ฒ€์ƒ‰ (์„ ํƒ, Exa โ†’ Perplexity โ†’ Tavily โ†’ LangSearch ํด๋ฐฑ) / X ์„ผํ‹ฐ๋จผํŠธ (์„ ํƒ) +# EXASEARCH_API_KEY / PERPLEXITY_API_KEY / TAVILY_API_KEY / LANGSEARCH_API_KEY / X_BEARER_TOKEN ``` -### ๋ฌผ์ ๋ถ„ํ•  ยท ์ด๋ฒคํŠธ ๋ถ„์„ +**ํ™œ์„ฑํ™” ๊ทœ์น™:** `DART_API_KEY` โ†’ 5๊ฐœ DART ๋„๊ตฌ ยท `KRX_ID`+`KRX_PW`(๋˜๋Š” `KRX_COOKIE`) โ†’ ๊ณต๋งค๋„ ยท `DATA_GO_KR_SERVICE_KEY` โ†’ ๊ตญ๋ฏผ์—ฐ๊ธˆ. ํ˜„์žฌ๊ฐ€ยท์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ์€ ํ‚ค ์—†์ด ํ•ญ์ƒ ๋“ฑ๋ก. -``` -> LGํ™”ํ•™ ๋ฌผ์ ๋ถ„ํ• ์ด ๊ธฐ์กด ์ฃผ์ฃผ๊ฐ€์น˜์— ์–ด๋–ค ์˜ํ–ฅ์ด์—ˆ๋Š”์ง€ ๋ถ„์„ํ•ด์ค˜ -> ์นด์นด์˜ค ์žํšŒ์‚ฌ ์ชผ๊ฐœ๊ธฐ ์ƒ์žฅ์ด ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ์— ๋ฏธ์นœ ์˜ํ–ฅ ํ‰๊ฐ€ํ•ด์ค˜ -``` +## ์˜ˆ์‹œ -### ํฌ๋กœ์Šค๋งˆ์ผ“ ๋น„๊ต +์‹œ์žฅ์„ ์ง€์ •ํ•  ํ•„์š” ์—†์ด ํ‹ฐ์ปค ํ˜•ํƒœ์™€ ์–ธ์–ด๋กœ ๋ผ์šฐํŒ…๋ฉ๋‹ˆ๋‹ค. ``` -> TSMC์™€ ์‚ผ์„ฑ์ „์ž ํŒŒ์šด๋“œ๋ฆฌ ์‚ฌ์—… ์žฌ๋ฌด๋กœ ๋น„๊ตํ•ด์ค˜ -> ์—”๋น„๋””์•„์™€ SKํ•˜์ด๋‹‰์Šค HBM ๊ด€๋ จ ์‹ค์  ๋น„๊ต ๋ถ„์„ํ•ด์ค˜ -``` +# ์žฌ๋ฌด ยท ๋ฐธ๋ฅ˜์—์ด์…˜ +> ์‚ผ์„ฑ์ „์ž ์ตœ๊ทผ 5๋…„ ๋งค์ถœ๊ณผ ์˜์—…์ด์ต ์ถ”์„ธ ์ •๋ฆฌํ•ด์ค˜ +> ์‚ผ์„ฑ์ „์ž DCF๋กœ ์ ์ •์ฃผ๊ฐ€ ๊ณ„์‚ฐํ•˜๊ณ  ํ˜„์žฌ๊ฐ€๋ž‘ ๋น„๊ตํ•ด์ค˜ # 6์ž๋ฆฌ๋ฉด KR ๊ฒฝ๋กœ ์ž๋™ ๋ถ„๊ธฐ +> ์—”๋น„๋””์•„์™€ SKํ•˜์ด๋‹‰์Šค HBM ๊ด€๋ จ ์‹ค์  ๋น„๊ต ๋ถ„์„ํ•ด์ค˜ # ํฌ๋กœ์Šค๋งˆ์ผ“ -> ํฌ๋กœ์Šค๋งˆ์ผ“ ์ฟผ๋ฆฌ๋Š” `get_financials`(US)์™€ `get_financials_kr`(KR)๋ฅผ ๋ชจ๋‘ ํ˜ธ์ถœํ•˜๊ณ , ๋‹จ์œ„ยทํšŒ๊ณ„๊ธฐ์ค€ ์ฐจ์ด๋ฅผ ๋ณด์ •ํ•ด ๋น„๊ต ํ‘œ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. +# ๊ณต์‹œ ยท ์ง€๋ฐฐ๊ตฌ์กฐ (DART) +> ์นด์นด์˜ค ์ตœ๊ทผ 1๋…„ ์ฃผ์š” ๊ณต์‹œ ์ •๋ฆฌํ•ด์ค˜ +> ์‚ผ์„ฑ์ „์ž ์ตœ๋Œ€์ฃผ์ฃผยทํŠน์ˆ˜๊ด€๊ณ„์ธ ์ง€๋ถ„์ด๋ž‘ ๊ณ„์—ดํšŒ์‚ฌ ๊ด€๊ณ„ ์‚ฌ์—…๋ณด๊ณ ์„œ ๊ธฐ์ค€์œผ๋กœ ์ •๋ฆฌํ•ด์ค˜ +> ์‚ผ์„ฑ์ „์ž ์ฃผ์š” ๋ฆฌ์Šคํฌ๊ฐ€ ๋ญ์•ผ? -### ํˆฌ์ž ๋ฉ”๋ชจ ยท ์„ผํ‹ฐ๋จผํŠธ +# ํ•œ๊ตญ ํŠนํ™” ๋ฐ์ดํ„ฐ +> ์‚ผ์„ฑ์ „์ž ํ˜„์žฌ๊ฐ€๋ž‘ ๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค ์•Œ๋ ค์ค˜ # ํ‚ค ๋ถˆํ•„์š” (Naver) +> ์‚ผ์„ฑ์ „์ž ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ ์ตœ๊ทผ ์ถ”์„ธ ๋ณด์—ฌ์ค˜ # ํ‚ค ๋ถˆํ•„์š” (Naver) +> ์—์ฝ”ํ”„๋กœ๋น„์—  ๊ณต๋งค๋„ ์ˆœ๋ณด์œ ์ž”๊ณ  ์ถ”์ด ์–ด๋•Œ? # KRX ํ‚ค ํ•„์š” +> ๊ตญ๋ฏผ์—ฐ๊ธˆ์ด ๋ณด์œ ํ•œ ์‚ผ์„ฑ์ „์ž ์ง€๋ถ„์œจ ์•Œ๋ ค์ค˜ # data.go.kr ํ‚ค ํ•„์š” -``` +# ์ด๋ฒคํŠธ ยท ๋ฉ”๋ชจ ยท ์ข…ํ•ฉ +> LGํ™”ํ•™ ๋ฌผ์ ๋ถ„ํ• ์ด ๊ธฐ์กด ์ฃผ์ฃผ๊ฐ€์น˜์— ์–ด๋–ค ์˜ํ–ฅ์ด์—ˆ๋Š”์ง€ ๋ถ„์„ํ•ด์ค˜ +> ์‚ผ์„ฑ์ „์ž ์ง€๊ธˆ ํˆฌ์ž ๊ด€์ ์—์„œ ์–ด๋•Œ? # ์ˆ˜๊ธ‰ยท์‹ค์ ยท์ง€๋ฐฐ๊ตฌ์กฐ ๊ต์ฐจ ์ข…ํ•ฉ > ์‚ผ์„ฑ์ „์ž ๋งค์ˆ˜ ๋…ผ๊ฑฐ๋ฅผ ํˆฌ์ž ๋ฉ”๋ชจ๋กœ ์ž‘์„ฑํ•ด์ค˜ -> ํ…Œ์Šฌ๋ผ์— ๋Œ€ํ•œ X(ํŠธ์œ„ํ„ฐ) ์—ฌ๋ก  ๋ถ„์„ํ•ด์ค˜ ``` -> ์ผ๋ถ€ ํ•œ๊ตญ ํŠนํ™” ํˆด(๊ณต๋งค๋„ยท๊ตญ๋ฏผ์—ฐ๊ธˆ)๊ณผ ์›น/X ๊ฒ€์ƒ‰์€ ํ•ด๋‹น API ํ‚ค๊ฐ€ ์„ค์ •๋ผ ์žˆ์–ด์•ผ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. [๋น ๋ฅธ ์‹œ์ž‘](#-๋น ๋ฅธ-์‹œ์ž‘) ์ฐธ๊ณ . - -## ๐Ÿ†š ์›น ์ฑ—๋ด‡๊ณผ ๋น„๊ต +## ์›น ์ฑ—๋ด‡ ๋Œ€๋น„ -2026๋…„ 5์›”, ๋™์ผํ•œ ์งˆ๋ฌธ 5๊ฐœ๋ฅผ **Dexter(LLM: GPT-5.5)** ์™€ **claude.ai(Opus 4.8, ์›น๊ฒ€์ƒ‰ ์‚ฌ์šฉ)** ์— ๋˜์ ธ ์‹ค์ธก ๋น„๊ตํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค ํ”„๋Ÿฐํ‹ฐ์–ด ๋ชจ๋ธ์ด๋ฏ€๋กœ ์ฐจ์ด๋ฅผ ๋งŒ๋“œ๋Š” ๊ฑด ๋ชจ๋ธ์ด ์•„๋‹ˆ๋ผ **๊ธˆ์œต ํ•˜๋„ค์Šค โ€” 1์ฐจ ์ถœ์ฒ˜ ๋„๊ตฌ(DARTยทKRXยทNaver) + ํ•œ๊ตญ ์‹œ์žฅ ์Šคํ‚ฌ** ์ž…๋‹ˆ๋‹ค. *(์•„๋ž˜ ์ˆ˜์น˜๋Š” ๊ฐ ๋„๊ตฌ๊ฐ€ ๋ฐ˜ํ™˜ํ•œ ๊ทธ๋Œ€๋กœ์ด๋ฉฐ, ๋ณ„๋„ ์ •๋ฐ€ ๊ฐ์‚ฌ๋Š” ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์‚ผ์„ฑ ์†์ต ํ–‰์€ ์žฌ๋ฌด ์ •๊ทœํ™” ๊ฐœ์„  ์ดํ›„ ์žฌ์ธก์ •. ๋„ค์ด๋ฒ„ DCF ํ–‰์€ ์ˆœ๋ถ€์ฑ„(์ˆœํ˜„๊ธˆ) ๋ณด์ • ์ด์ „ ์ธก์ •๊ฐ’์ด๋ผ ํ˜„ํ–‰ ์ฝ”๋“œ๋กœ๋Š” ๋” ๋†’๊ฒŒ ๋‚˜์˜ฌ ์ˆ˜ ์žˆ์Œ.)* +2026๋…„ 5์›”, ๋™์ผ ์งˆ๋ฌธ 5๊ฐœ๋ฅผ **Dexter(GPT-5.5)** ์™€ **claude.ai(Opus 4.8 + ์›น๊ฒ€์ƒ‰)** ์— ๋˜์ ธ ์‹ค์ธกํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค ํ”„๋Ÿฐํ‹ฐ์–ด ๋ชจ๋ธ์ด๋ฏ€๋กœ ์ฐจ์ด๋Š” ๋ชจ๋ธ์ด ์•„๋‹ˆ๋ผ **๊ธˆ์œต ํ•˜๋„ค์Šค โ€” 1์ฐจ ์ถœ์ฒ˜ ๋„๊ตฌ + ํ•œ๊ตญ ์‹œ์žฅ ์Šคํ‚ฌ** ์—์„œ ์˜ต๋‹ˆ๋‹ค. *(๊ฐ ๋„๊ตฌ๊ฐ€ ๋ฐ˜ํ™˜ํ•œ ๊ฐ’ ๊ทธ๋Œ€๋กœ์ด๋ฉฐ ๋ณ„๋„ ์ •๋ฐ€ ๊ฐ์‚ฌ๋Š” ์•ˆ ํ•จ. ์‚ผ์„ฑ ์†์ต์€ ์ •๊ทœํ™” ๊ฐœ์„  ํ›„ ์žฌ์ธก์ •, ๋„ค์ด๋ฒ„ DCF๋Š” ์ˆœ๋ถ€์ฑ„ ๋ณด์ • ์ด์ „ ๊ฐ’์ด๋ผ ํ˜„ํ–‰ ์ฝ”๋“œ๋ก  ๋” ๋†’๊ฒŒ ๋‚˜์˜ฌ ์ˆ˜ ์žˆ์Œ.)* -| ์งˆ๋ฌธ | claude.ai (Opus 4.8 + ์›น๊ฒ€์ƒ‰) | Dexter (GPT-5.5 + ๊ธˆ์œต ํ•˜๋„ค์Šค) | +| ์งˆ๋ฌธ | claude.ai (Opus 4.8 + ์›น๊ฒ€์ƒ‰) | Dexter (GPT-5.5 + ํ•˜๋„ค์Šค) | |---|---|---| -| ์‚ผ์„ฑ์ „์ž ๋งค์ถœ/์˜์—…์ด์ต | 333.6์กฐ / 43.6์กฐ, ๋‰ด์Šค ์ถœ์ฒ˜ โœ“ (๋‹จ "1Q 57์กฐ" ํ™˜๊ฐ ํ˜ผ์ž…) | **333.6์กฐ / 43.6์กฐ (+10.9% / +33.2% YoY)** + ๋งˆ์ง„ยทROEยทFCF, DART ๋ณด๊ณ ์„œ๋ฅผ ์ •๊ทœํ™” ์š”์•ฝ์œผ๋กœ ์ง์ ‘ ์ถ”์ถœ | -| ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ | 48.42% (05-20), ๋‰ด์Šค ๊ธฐ๋ฐ˜ | **48.27% (05-29)** ์ผ์ž๋ณ„ + ์ˆœ๋งค๋„๋Ÿ‰, Naver ์ง์ ‘ โ€” ๋” ์ตœ์‹  | -| ๋„ค์ด๋ฒ„ DCF | 161,700์›, ์ถฉ์‹ค(SOTPยท์ปจ์„ผ์„œ์Šค), ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ 4.15% | ~200,000์›, **KR ์ž๋™ ๋ถ„๊ธฐ**(ํ„ฐ๋ฏธ๋„ 2.0%ยท์„ธํ›„ ์‹คํ˜„์ˆ˜์ต๋ฅ  caption), ์‹ค์‹œ์„ธ ๊ธฐ๋ฐ˜ | -| LGํ™”ํ•™ ๋ฌผ์ ๋ถ„ํ•  | ๋‹ค์ถœ์ฒ˜ยทํ’๋ถ€ํ•œ ์„œ์ˆ  | DART ๊ณต์‹œ 9๊ฑด ์ง์ ‘ ์ธ์šฉ + ๊ตฌ์กฐ ๋ถ„์„ | -| ์—์ฝ”ํ”„๋กœ๋น„์—  ๊ณต๋งค๋„ | 1๊ฐœ ์‹œ์ (5/07)๋งŒ + "KRX์„œ ์กฐํšŒ" ์•ˆ๋‚ด | **18์ผ ์ผ๋ณ„ ์ž”๊ณ ์œจยท๊ธˆ์•กยท์ˆ˜๋Ÿ‰**(KRX ์ง์ ‘) | - -**Dexter๊ฐ€ ๋งŒ๋“  ์ฐจ์ด** -- **๊ตฌ์กฐํ™” ์žฌ๋ฌด ์š”์•ฝ** โ€” DART ์›์‹œ ๋ผ์ธ์•„์ดํ…œ์„ ๋งค์ถœยท์˜์—…์ด์ตยท์ˆœ์ด์ตยท๋งˆ์ง„ยทROEยทFCFยทYoY๋กœ ์ •๊ทœํ™”ํ•ด ์†์ต์„ ์ผ๊ด€๋˜๊ฒŒ ์ถ”์ถœ(๋Œ€์šฉ๋Ÿ‰ ์›๋ณธ์„ ๋ชป ์ฝ์–ด ๋ณด๋ฅ˜ํ•˜๋˜ ๋ฌธ์ œ ํ•ด๊ฒฐ). ์™ธ๊ตญ์ธยท๊ณต๋งค๋„ยท๋Œ€๋Ÿ‰๋ณด์œ ยท๊ตญ๋ฏผ์—ฐ๊ธˆ๊ณผ **๊ต์ฐจ ์ข…ํ•ฉ**ํ•ด ๋‹จ์ผ thesis๋กœ ์ œ์‹œ -- **1์ฐจ ์ถœ์ฒ˜ ์ง์ ‘** โ€” DART(์žฌ๋ฌดยท๊ณต์‹œ)ยทKRX(๊ณต๋งค๋„)ยทNaver(์™ธ๊ตญ์ธ ์ง€๋ถ„์œจยทํ˜„์žฌ๊ฐ€ยท๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค)๋ฅผ API๋กœ ์ง์ ‘ ํ˜ธ์ถœ โ†’ ๋‰ด์Šค(2์ฐจ)๋ณด๋‹ค ์ •๋ฐ€ยท์ตœ์‹ ยท์ผ๋ณ„. ์™ธ๊ตญ์ธยทํ˜„์žฌ๊ฐ€๋Š” **API ํ‚ค ์—†์ด๋„** ๋™์ž‘(DART ํ‚ค ํ•˜๋‚˜๋ฉด ์ฑ—๋ด‡ ๋Œ€๋น„ ์šฐ์œ„) -- **๊ตฌ์กฐํ™” ์‹œ๊ณ„์—ด** โ€” ๊ณต๋งค๋„ 18์ผ์น˜, ์™ธ๊ตญ์ธ ์ผ์ž๋ณ„์ฒ˜๋Ÿผ ๋‰ด์Šค์—” ์—†๋Š” ๋ฐ์ดํ„ฐ -- **์ •ํ™•ํ•œ ๋ฐธ๋ฅ˜์—์ด์…˜** โ€” DCF๊ฐ€ ์ˆœ๋ถ€์ฑ„๋ฅผ **์ด์ž๋ถ€์ฑ„ ๊ธฐ์ค€**(๋ถ€์ฑ„์ด๊ณ„ ์•„๋‹˜)์œผ๋กœ ์žก์•„ ์ˆœํ˜„๊ธˆ ๊ธฐ์—…(์‚ผ์„ฑ ๋“ฑ)์„ ์ €ํ‰๊ฐ€ํ•˜์ง€ ์•Š์Œ. ์ฑ—๋ด‡์ด ํ”ํžˆ ์“ฐ๋Š” "๋ถ€์ฑ„์ด๊ณ„=์ˆœ๋ถ€์ฑ„" ์–ด๋ฆผ์…ˆ์€ ์ˆœํ˜„๊ธˆ ๊ธฐ์—…์˜ ๋ถ€ํ˜ธ๋ฅผ ๋’ค์ง‘์–ด ์ ์ •๊ฐ€๋ฅผ ๊นŽ๋Š”๋‹ค -- **๊ฐ์‚ฌยท์žฌํ˜„ ๊ฐ€๋Šฅ** โ€” ๋ชจ๋“  ํˆด ํ˜ธ์ถœ์„ ์Šคํฌ๋ž˜์น˜ํŒจ๋“œ(JSONL)์— ๊ธฐ๋กํ•˜๊ณ , KR ํ‰๊ฐ€ ํ•˜๋„ค์Šค๊ฐ€ record/replay๋กœ ๊ฐ™์€ ์งˆ๋ฌธ์— ๊ฐ™์€ ๋ฐ์ดํ„ฐ ๊ฒฝ๋กœ๋ฅผ ๋ณด์žฅ(์ฑ—๋ด‡์˜ ๋น„๊ฒฐ์ •์  ๋‹ต๊ณผ ๋Œ€๋น„) -- **KR ์ž๋™ ํŠœ๋‹** โ€” 6์ž๋ฆฌ ํ‹ฐ์ปค๋ฉด DCF๊ฐ€ K-IFRSยทKRWยท์„ธํ›„ ์‹คํ˜„์ˆ˜์ต๋ฅ  ๊ฒฝ๋กœ๋กœ ๋ถ„๊ธฐ -- **ํ™˜๊ฐ๋ณด๋‹ค ์ •์ง** โ€” ๋ฐ์ดํ„ฐ๋ฅผ ๋ชป ์–ป์œผ๋ฉด ๋งŒ๋“ค์–ด๋‚ด์ง€ ์•Š๊ณ  "๋ชป ์–ป๋Š”๋‹ค"๊ณ  ํ•จ - -**์ •์งํ•œ ํ•œ๊ณ„** โ€” Dexter๊ฐ€ ํ•ญ์ƒ ์ด๊ธฐ๋Š” ๊ฑด ์•„๋‹™๋‹ˆ๋‹ค: -- ํ‘œ์ค€ ์ œ์กฐ์—…์€ ์†์ต์„ ์ •๊ทœํ™” ์š”์•ฝ์œผ๋กœ ๋งค๋„๋Ÿฝ๊ฒŒ ์ถ”์ถœํ•˜์ง€๋งŒ, ์€ํ–‰ยท์ง€์ฃผ์‚ฌ์ฒ˜๋Ÿผ ๋น„ํ‘œ์ค€ K-IFRS ๋ผ๋ฒจ(์˜์—…์ˆ˜์ต ๋“ฑ)์„ ์“ฐ๋Š” ๊ธฐ์—…์€ ์ผ๋ถ€ ์ง€ํ‘œ๊ฐ€ ๋น„์–ด raw ๋ผ์ธ์•„์ดํ…œ ๋“œ๋ฆด๋‹ค์šด์œผ๋กœ ๋ณด์™„ โ€” ์ปค๋ฒ„๋ฆฌ์ง€๊ฐ€ ๊ท ์ผํ•˜์ง„ ์•Š์Œ -- DCF ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ๋ฅผ Dexter ์Šคํ‚ฌ์€ 3.0%๋กœ ๊ณ ์ •, claude.ai๋Š” ๊ฒ€์ƒ‰์œผ๋กœ 4.15% ๋ฐ˜์˜ โ†’ ํ•˜๋“œ์ฝ”๋”ฉ ๊ฐ€์ •์ด ๋‚ก์„ ์ˆ˜ ์žˆ์Œ(๊ฐœ์„  ์˜ˆ์ •) -- ์›น๊ฒ€์ƒ‰ ํ”„๋Ÿฐํ‹ฐ์–ด ๋ชจ๋ธ์€ ๊ฐ•๋ ฅํ•ฉ๋‹ˆ๋‹ค. Dexter์˜ ์šฐ์œ„๋Š” "๋” ๋˜‘๋˜‘ํ•จ"์ด ์•„๋‹ˆ๋ผ **1์ฐจ ์ถœ์ฒ˜ยท๊ตฌ์กฐํ™”ยท๊ฐ์‚ฌ๊ฐ€๋Šฅยทํ•œ๊ตญ ํŠœ๋‹**์— ์žˆ์Šต๋‹ˆ๋‹ค - -## ๐Ÿš€ ๋น ๋ฅธ ์‹œ์ž‘ - -### ์‚ฌ์ „ ์ค€๋น„ - -- [Bun](https://bun.com) ๋Ÿฐํƒ€์ž„ (v1.0 ์ด์ƒ) -- **OpenAI API ํ‚ค** ([๋ฐœ๊ธ‰](https://platform.openai.com/api-keys)) โ€” ๋˜๋Š” ๋‹ค๋ฅธ LLM ์ œ๊ณต์ž -- **Financial Datasets API ํ‚ค** ([๋ฐœ๊ธ‰](https://financialdatasets.ai)) โ€” ๋ฏธ๊ตญ ์‹œ์žฅ ๋ฐ์ดํ„ฐ -- **DART API ํ‚ค** ([๋ฐœ๊ธ‰](https://opendart.fss.or.kr), ๋ฌด๋ฃŒ, ์ผ 10,000๊ฑด) โ€” ํ•œ๊ตญ ์‹œ์žฅ ๋ฐ์ดํ„ฐ -- (์„ ํƒ) Exa(์›น ๊ฒ€์ƒ‰), KRXยทdata.go.kr(ํ•œ๊ตญ ํŠนํ™” ๋ฐ์ดํ„ฐ), X(์„ผํ‹ฐ๋จผํŠธ) ํ‚ค - -Bun์ด ์—†๋‹ค๋ฉด: +| ์‚ผ์„ฑ ๋งค์ถœ/์˜์—…์ด์ต | 333.6์กฐ / 43.6์กฐ, ๋‰ด์Šค ์ถœ์ฒ˜ โœ“ (๋‹จ "1Q 57์กฐ" ํ™˜๊ฐ) | **333.6์กฐ / 43.6์กฐ (+10.9%/+33.2% YoY)** + ๋งˆ์ง„ยทROEยทFCF, DART ์ •๊ทœํ™” ์ง์ ‘ ์ถ”์ถœ | +| ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ | 48.42% (05-20), ๋‰ด์Šค | **48.27% (05-29)** ์ผ๋ณ„ + ์ˆœ๋งค๋„๋Ÿ‰, Naver ์ง์ ‘ | +| ๋„ค์ด๋ฒ„ DCF | 161,700์›, ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ 4.15% | ~200,000์›, **KR ์ž๋™ ๋ถ„๊ธฐ**, ์‹ค์‹œ์„ธ ๊ธฐ๋ฐ˜ | +| LGํ™”ํ•™ ๋ฌผ์ ๋ถ„ํ•  | ๋‹ค์ถœ์ฒ˜ ์„œ์ˆ  | DART ๊ณต์‹œ 9๊ฑด ์ง์ ‘ ์ธ์šฉ + ๊ตฌ์กฐ ๋ถ„์„ | +| ์—์ฝ”ํ”„๋กœ๋น„์—  ๊ณต๋งค๋„ | 1๊ฐœ ์‹œ์ (5/07) + "KRX์„œ ์กฐํšŒ" ์•ˆ๋‚ด | **18์ผ ์ผ๋ณ„ ์ž”๊ณ ์œจยท๊ธˆ์•กยท์ˆ˜๋Ÿ‰** (KRX ์ง์ ‘) | -```bash -# macOS/Linux -curl -fsSL https://bun.com/install | bash +ํ•ต์‹ฌ ์ฐจ์ด๋Š” **1์ฐจ ์ถœ์ฒ˜ ์ง์ ‘ ํ˜ธ์ถœ**(๋‰ด์Šค 2์ฐจ๊ฐ€ ์•„๋‹˜) ยท **๊ตฌ์กฐํ™” ์‹œ๊ณ„์—ด**(๊ณต๋งค๋„ 18์ผยท์™ธ๊ตญ์ธ ์ผ๋ณ„) ยท **์ •ํ™•ํ•œ ๋ฐธ๋ฅ˜์—์ด์…˜**(์ˆœํ˜„๊ธˆ ๊ธฐ์—…์„ ์ €ํ‰๊ฐ€ํ•˜์ง€ ์•Š๋Š” ์ˆœ๋ถ€์ฑ„ ๊ณ„์‚ฐ) ยท **๊นจ์ ธ๋„ ์กฐ์šฉํ•˜์ง€ ์•Š์Œ**(์ฟผํ„ฐ/๋“œ๋ฆฌํ”„ํŠธ ๋ช…์‹œ) ยท **๊ฐ์‚ฌยท์žฌํ˜„ ๊ฐ€๋Šฅ**(์Šคํฌ๋ž˜์น˜ํŒจ๋“œ + record/replay)์ž…๋‹ˆ๋‹ค. -# Windows -powershell -c "irm bun.sh/install.ps1|iex" -``` +## ์ •์งํ•œ ํ•œ๊ณ„ -์„ค์น˜ ํ›„ ํ„ฐ๋ฏธ๋„์„ ์žฌ์‹œ์ž‘ํ•˜๊ณ  ํ™•์ธ: `bun --version` +ํ•ญ์ƒ ์ด๊ธฐ๋Š” ๋„๊ตฌ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค: -### ์„ค์น˜ - -```bash -git clone https://github.com/virattt/dexter.git -cd dexter -bun install -``` +- ํ‘œ์ค€ ์ œ์กฐ์—… ์†์ต์€ ๋งค๋„๋Ÿฝ๊ฒŒ ์ •๊ทœํ™”๋˜์ง€๋งŒ, ์€ํ–‰ยท์ง€์ฃผ์‚ฌ์˜ ๋น„ํ‘œ์ค€ K-IFRS ๋ผ๋ฒจ(์˜์—…์ˆ˜์ต ๋“ฑ)์€ ์ผ๋ถ€ ์ง€ํ‘œ๊ฐ€ ๋น„์–ด raw ๋“œ๋ฆด๋‹ค์šด์œผ๋กœ ๋ณด์™„ โ€” ์ปค๋ฒ„๋ฆฌ์ง€๊ฐ€ ๊ท ์ผํ•˜์ง€ ์•Š์Œ. +- ์ •๋ฐ€ ์ปจ์„ผ์„œ์Šค(๋ธŒ๋กœ์ปค๋ณ„ ๋ชฉํ‘œ์ฃผ๊ฐ€ยท์ถ”์ •์น˜ ๋ถ„์‚ฐ)ยท์„ธ๊ทธ๋จผํŠธ ์žฌ๋ฌด๋Š” FnGuide ๋“ฑ ์œ ๋ฃŒ ๋‹จ๋ง์— ๋ชป ๋ฏธ์นจ(keyless ์†Œ์Šค๋Š” ๋‹จ์ผ ํ‰๊ท  ๋ชฉํ‘œ์ฃผ๊ฐ€๋ฟ). +- DCF ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌยทWACC๋Š” ๋ฐด๋“œ/๊ทผ์‚ฌ๊ฐ’ ๊ณ ์ •์ด๋ผ ๊ฐ€์ •์ด ๋‚ก์„ ์ˆ˜ ์žˆ์Œ. +- KRX ๊ณต๋งค๋„๋Š” ๋กœ๊ทธ์ธ ์˜์กด, Naver๋Š” ๋น„๊ณต์‹ ์—”๋“œํฌ์ธํŠธ โ€” ๊ฒฌ๊ณ ์„ฑ ๋ ˆ์ด์–ด๊ฐ€ ์ถฉ๊ฒฉ์„ ์ค„์ด์ง€๋งŒ ์ƒ๋ฅ˜ ๋ณ€๊ฒฝ ์ž์ฒด๋ฅผ ๋ง‰์ง„ ๋ชปํ•จ. +- ์ „์ฒด ํŠน์ˆ˜๊ด€๊ณ„์ž ๊ฑฐ๋ž˜ *์ฃผ์„*์€ ํ‘œ ์ค‘์‹ฌ์ด๋ผ ๋ณธ๋ฌธ narrative(๋Œ€์ฃผ์ฃผ ๊ฑฐ๋ž˜)๊นŒ์ง€๋งŒ ์ปค๋ฒ„. -### ํ™˜๊ฒฝ ๋ณ€์ˆ˜ +Dexter์˜ ์šฐ์œ„๋Š” "๋” ๋˜‘๋˜‘ํ•จ"์ด ์•„๋‹ˆ๋ผ **1์ฐจ ์ถœ์ฒ˜ยท๊ตฌ์กฐํ™”ยท๊ฐ์‚ฌ๊ฐ€๋Šฅยทํ•œ๊ตญ ํŠœ๋‹**์— ์žˆ์Šต๋‹ˆ๋‹ค. -`env.example`์„ ๋ณต์‚ฌํ•ด `.env`๋ฅผ ๋งŒ๋“ค๊ณ  ํ•„์š”ํ•œ ํ‚ค๋ฅผ ์ฑ„์›๋‹ˆ๋‹ค. (`your-`๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฐ’์€ ๋ฏธ์„ค์ •์œผ๋กœ ๊ฐ„์ฃผ๋ฉ๋‹ˆ๋‹ค.) +## ๊ฐœ๋ฐœ ยท ํ‰๊ฐ€ -```bash -cp env.example .env -``` +**๋Ÿฐํƒ€์ž„์€ Bun.** ์ž์ฃผ ์“ฐ๋Š” ๋ช…๋ น: ```bash -# --- LLM (์ตœ์†Œ ํ•˜๋‚˜ ํ•„์ˆ˜) --- -OPENAI_API_KEY=your-openai-api-key -# ANTHROPIC_API_KEY=... (์„ ํƒ) -# GOOGLE_API_KEY=... (์„ ํƒ) -# XAI_API_KEY=... / OPENROUTER_API_KEY=... (์„ ํƒ) -# MOONSHOT_API_KEY=... / DEEPSEEK_API_KEY=...(์„ ํƒ) -# OLLAMA_BASE_URL=http://127.0.0.1:11434 (๋กœ์ปฌ LLM) - -# --- ๋ฏธ๊ตญ ์‹œ์žฅ ๋ฐ์ดํ„ฐ --- -FINANCIAL_DATASETS_API_KEY=your-financial-datasets-api-key - -# --- ํ•œ๊ตญ ์‹œ์žฅ --- -DART_API_KEY=your-dart-api-key # ์žฌ๋ฌดยท๊ณต์‹œ (ํ•„์ˆ˜: KR ํŽ€๋”๋ฉ˜ํ„ธ/๊ณต์‹œ) -# KRX_ID=your-krx-id # ๊ณต๋งค๋„ ์ž”๊ณ  (data.krx.co.kr ๋กœ๊ทธ์ธ) -# KRX_PW=your-krx-password -# KRX_COOKIE=JSESSIONID=...; # ์†Œ์…œ ๋กœ๊ทธ์ธ ๊ณ„์ •์€ ๋ธŒ๋ผ์šฐ์ € ์ฟ ํ‚ค ๋ถ™์—ฌ๋„ฃ๊ธฐ -# DATA_GO_KR_SERVICE_KEY=... # ๊ตญ๋ฏผ์—ฐ๊ธˆ ๋ณด์œ  (data.go.kr, Decoded ํ‚ค) -# READ_FILINGS_KR_MODEL=... # (์„ ํƒ) read_filings_kr ๋‚ด๋ถ€ ์š”์•ฝ ๋ชจ๋ธ ์˜ค๋ฒ„๋ผ์ด๋“œ. ๊ธฐ๋ณธ์€ provider์˜ fast ๋ชจ๋ธ(์ €๋น„์šฉ) - -# --- ์›น ๊ฒ€์ƒ‰ (์„ ํƒ, Exa โ†’ Perplexity โ†’ Tavily โ†’ LangSearch ํด๋ฐฑ) --- -# EXASEARCH_API_KEY=your-exa-api-key -# PERPLEXITY_API_KEY=your-perplexity-api-key -# TAVILY_API_KEY=your-tavily-api-key -# LANGSEARCH_API_KEY=your-langsearch-api-key - -# --- X/ํŠธ์œ„ํ„ฐ ์„ผํ‹ฐ๋จผํŠธ (์„ ํƒ) --- -# X_BEARER_TOKEN=your-X-bearer-token +bun run typecheck # tsc --noEmit (ํ‘ธ์‹œ ์ „ ๊ถŒ์žฅ) +bun test # ์ „์ฒด ํ…Œ์ŠคํŠธ (Bun ๋Ÿฌ๋„ˆ) +bun test path.test.ts ``` -### ์‹คํ–‰ +**ํ‰๊ฐ€(eval)** โ€” LLM-as-judge ์ฑ„์ : ```bash -bun start # ๋Œ€ํ™”ํ˜• ๋ชจ๋“œ -bun dev # ๊ฐœ๋ฐœ์šฉ watch ๋ชจ๋“œ +bun run src/evals/run.ts --sample 10 # ๋ฏธ๊ตญ ์ค‘์‹ฌ ํ‰๊ฐ€ +bun run kr-eval # ํ•œ๊ตญ ์งˆ๋ฌธ ๋ฑ…ํฌ, ๋ผ์ด๋ธŒ ์ฑ„์  +bun run kr-eval:record # ์‹ค์ œ DARTยทKRXยทNaver ์‘๋‹ต์„ ํ”ฝ์Šค์ฒ˜๋กœ ๊ธฐ๋ก +bun run kr-eval:replay # ํ”ฝ์Šค์ฒ˜๋กœ ๊ฒฐ์ •์  ์žฌํ˜„ (ํ‚คยท๋„คํŠธ์›Œํฌ ๋ถˆํ•„์š”) ``` -## ๐Ÿ”ง ๋” ์•Œ์•„๋ณด๊ธฐ - -### ๐Ÿ“Š ํ‰๊ฐ€ +KR ์ฑ„์  ์ฐจ์›: ์‹ค์  YoY ยท ๊ต์ฐจ์‹ ํ˜ธ ยท ์ง€๋ฐฐ๊ตฌ์กฐ ยท grounding(ํ™˜๊ฐ ์—ฌ๋ถ€). "๊ฐ™์€ ์งˆ๋ฌธ โ†’ ๊ฐ™์€ ๋ฐ์ดํ„ฐ ๊ฒฝ๋กœ"๋ฅผ record/replay๋กœ ๊ฒ€์ฆํ•ด ์ฑ—๋ด‡์˜ ๋น„๊ฒฐ์ •์  ๋‹ต๊ณผ ๋Œ€๋น„ํ•ฉ๋‹ˆ๋‹ค. -Dexter์—๋Š” ๊ธˆ์œต ์งˆ๋ฌธ ๋ฐ์ดํ„ฐ์…‹์œผ๋กœ ์—์ด์ „ํŠธ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋Š” ํ‰๊ฐ€(eval) ์Šค์œ„ํŠธ๊ฐ€ ํฌํ•จ๋ผ ์žˆ์Šต๋‹ˆ๋‹ค. ํ‰๊ฐ€๋Š” ์ถ”์ ์— LangSmith๋ฅผ, ์ •๋‹ต ์ฑ„์ ์— LLM-as-judge ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +**๋””๋ฒ„๊น…** โ€” ๋ชจ๋“  ์ฟผ๋ฆฌ๊ฐ€ `.dexter/scratchpad/`์— JSONL๋กœ ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค(์›๋ณธ ์ฟผ๋ฆฌยท๊ฐ ํˆด ํ˜ธ์ถœ์˜ ์ธ์ž/์›์‹œ๊ฒฐ๊ณผ/LLM ์š”์•ฝยท์ถ”๋ก  ๋‹จ๊ณ„). ์—์ด์ „ํŠธ๊ฐ€ ๋ฌด์—‡์„ ์–ด๋””์„œ ๊ฐ€์ ธ์™€ ์–ด๋–ป๊ฒŒ ํ•ด์„ํ–ˆ๋Š”์ง€ ํ•œ ์ค„์”ฉ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -```bash -bun run src/evals/run.ts # ์ „์ฒด ์งˆ๋ฌธ (๋ฏธ๊ตญ ์ค‘์‹ฌ) -bun run src/evals/run.ts --sample 10 # ๋ฌด์ž‘์œ„ ์ƒ˜ํ”Œ 10๊ฐœ -``` +## ๋ผ์ด์„ ์Šค -ํ‰๊ฐ€ ๋Ÿฌ๋„ˆ๋Š” ์ง„ํ–‰ ์ƒํ™ฉ, ํ˜„์žฌ ์งˆ๋ฌธ, ์‹ค์‹œ๊ฐ„ ์ •ํ™•๋„ ํ†ต๊ณ„๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” UI๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ๋Š” ๋ถ„์„์„ ์œ„ํ•ด LangSmith์— ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค. - -**ํ•œ๊ตญ ์ฃผ์‹ ํ‰๊ฐ€ ํ•˜๋„ค์Šค** โ€” ๊ณ ์ •๋œ ํ•œ๊ตญ ์งˆ๋ฌธ ๋ฑ…ํฌ + record/replay + LLM-judge ์ฑ„์ . `record`๋กœ ์‹ค์ œ DARTยทKRXยทNaver ์‘๋‹ต์„ ํ”ฝ์Šค์ฒ˜๋กœ ์ €์žฅํ•ด๋‘๋ฉด `replay`๋Š” ๋„คํŠธ์›Œํฌยทํ‚ค ์—†์ด ๊ฒฐ์ •์ ์œผ๋กœ ์žฌํ˜„ยทํšŒ๊ท€ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค("๊ฐ™์€ ์งˆ๋ฌธ โ†’ ๊ฐ™์€ ๋ฐ์ดํ„ฐ ๊ฒฝ๋กœ"๋ฅผ ๊ฒ€์ฆ โ€” ์ฑ—๋ด‡์˜ ๋น„๊ฒฐ์ •์  ๋‹ต๊ณผ ๋Œ€๋น„). - -```bash -bun run kr-eval # ๋ผ์ด๋ธŒ ์ฑ„์  -bun run kr-eval:record # ์‹ค์ œ ์‘๋‹ต์„ ํ”ฝ์Šค์ฒ˜๋กœ ๊ธฐ๋ก -bun run kr-eval:replay # ํ”ฝ์Šค์ฒ˜๋กœ ๊ฒฐ์ •์  ์žฌํ˜„ (ํ‚ค ๋ถˆํ•„์š”) -``` - -### ๐Ÿ› ๋””๋ฒ„๊น… - -Dexter๋Š” ๋””๋ฒ„๊น…๊ณผ ์ด๋ ฅ ์ถ”์ ์„ ์œ„ํ•ด ๋ชจ๋“  ํˆด ํ˜ธ์ถœ์„ ์Šคํฌ๋ž˜์น˜ํŒจ๋“œ(scratchpad) ํŒŒ์ผ์— ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ์ฟผ๋ฆฌ๋Š” `.dexter/scratchpad/`์— ์ƒˆ JSONL ํŒŒ์ผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. - -``` -.dexter/scratchpad/ -โ”œโ”€โ”€ 2026-01-30-111400_9a8f10723f79.jsonl -โ”œโ”€โ”€ 2026-01-30-143022_a1b2c3d4e5f6.jsonl -โ””โ”€โ”€ ... -``` - -๊ฐ ํŒŒ์ผ์€ ๋‹ค์Œ์„ ์ถ”์ ํ•˜๋Š” ์ค„๋ฐ”๊ฟˆ ๊ตฌ๋ถ„(JSONL) ํ•ญ๋ชฉ์„ ๋‹ด์Šต๋‹ˆ๋‹ค: -- **init**: ์›๋ณธ ์ฟผ๋ฆฌ -- **tool_result**: ์ธ์žยท์›์‹œ ๊ฒฐ๊ณผยทLLM ์š”์•ฝ์„ ํฌํ•จํ•œ ๊ฐ ํˆด ํ˜ธ์ถœ -- **thinking**: ์—์ด์ „ํŠธ์˜ ์ถ”๋ก  ๋‹จ๊ณ„ - -**์Šคํฌ๋ž˜์น˜ํŒจ๋“œ ํ•ญ๋ชฉ ์˜ˆ์‹œ:** -```json -{"type":"tool_result","timestamp":"2026-01-30T11:14:05.123Z","toolName":"get_income_statements","args":{"ticker":"AAPL","period":"annual","limit":5},"result":{...},"llmSummary":"Retrieved 5 years of Apple annual income statements showing revenue growth from $274B to $394B"} -``` - -๋•๋ถ„์— ์—์ด์ „ํŠธ๊ฐ€ ์ •ํ™•ํžˆ ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ–ˆ๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ด์„ํ–ˆ๋Š”์ง€ ์†์‰ฝ๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - -## ๐Ÿ“„ ๋ผ์ด์„ ์Šค - -์ด ํ”„๋กœ์ ํŠธ๋Š” MIT ๋ผ์ด์„ ์Šค๋กœ ๋ฐฐํฌ๋ฉ๋‹ˆ๋‹ค. +MIT. ์—…์ŠคํŠธ๋ฆผ: [virattt/dexter](https://github.com/virattt/dexter). ## โš ๏ธ ๋ฉด์ฑ… ์กฐํ•ญ -์ด ํ”„๋กœ์ ํŠธ๋Š” **๊ต์œกยท์˜ค๋ฝยท์ •๋ณด ์ œ๊ณต ๋ชฉ์ ์œผ๋กœ๋งŒ** ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. ์‹ค์ œ ๊ฑฐ๋ž˜๋‚˜ ํˆฌ์ž๋ฅผ ์œ„ํ•œ ๊ฒƒ์ด ์•„๋‹™๋‹ˆ๋‹ค. - -- ๊ธˆ์œตยทํˆฌ์žยท์„ธ๋ฌดยท๋ฒ•๋ฅ  ์ž๋ฌธ์ด ์•„๋‹™๋‹ˆ๋‹ค -- ์ •ํ™•์„ฑยท์™„์ „์„ฑยทํŠน์ • ๋ชฉ์  ์ ํ•ฉ์„ฑ์„ ๋ณด์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค -- ์ถœ๋ ฅ ๊ฒฐ๊ณผ๊ฐ€ ๋ถ€์ •ํ™•ํ•˜๊ฑฐ๋‚˜ ๋ถˆ์™„์ „ํ•˜๊ฑฐ๋‚˜ ์‹œ์ ์ด ์ง€๋‚œ ๊ฒƒ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค -- ์ œ์ž‘์ž์™€ ๊ธฐ์—ฌ์ž๋Š” ์–ด๋– ํ•œ ๊ธˆ์ „์  ์†์‹ค์ด๋‚˜ ์†ํ•ด์— ๋Œ€ํ•ด์„œ๋„ ์ฑ…์ž„์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค -- ํˆฌ์ž ๊ฒฐ์ •์„ ๋‚ด๋ฆฌ๊ธฐ ์ „์— ๋ฉดํ—ˆ๋ฅผ ๋ณด์œ ํ•œ ๊ธˆ์œต ์ „๋ฌธ๊ฐ€์™€ ์ƒ๋‹ดํ•˜์„ธ์š” -- ๊ณผ๊ฑฐ ์„ฑ๊ณผ๊ฐ€ ๋ฏธ๋ž˜ ์ˆ˜์ต์„ ๋ณด์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค - -์ด ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ, ๊ท€ํ•˜๋Š” ํ•™์Šต ๋ฐ ์ •๋ณด ์ œ๊ณต ๋ชฉ์ ์œผ๋กœ๋งŒ ์‚ฌ์šฉํ•  ๊ฒƒ์— ๋™์˜ํ•˜๋ฉฐ ์‚ฌ์šฉ์— ๋”ฐ๋ฅธ ๋ชจ๋“  ์œ„ํ—˜์„ ๊ฐ์ˆ˜ํ•ฉ๋‹ˆ๋‹ค. +**๊ต์œกยท์ •๋ณด ์ œ๊ณต ๋ชฉ์  ์ „์šฉ.** ์‹ค๊ฑฐ๋ž˜/ํˆฌ์ž๋ฅผ ์œ„ํ•œ ๊ฒƒ์ด ์•„๋‹ˆ๋ฉฐ, ๊ธˆ์œตยทํˆฌ์žยท์„ธ๋ฌดยท๋ฒ•๋ฅ  ์ž๋ฌธ์ด ์•„๋‹™๋‹ˆ๋‹ค. ์ •ํ™•์„ฑยท์™„์ „์„ฑ์„ ๋ณด์žฅํ•˜์ง€ ์•Š๊ณ , ์ถœ๋ ฅ์ด ๋ถ€์ •ํ™•ํ•˜๊ฑฐ๋‚˜ ์‹œ์ ์ด ์ง€๋‚œ ๊ฒƒ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ œ์ž‘์žยท๊ธฐ์—ฌ์ž๋Š” ์–ด๋– ํ•œ ์†์‹ค์—๋„ ์ฑ…์ž„์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํˆฌ์ž ๊ฒฐ์ • ์ „ ๋ฉดํ—ˆ ๋ณด์œ  ์ „๋ฌธ๊ฐ€์™€ ์ƒ๋‹ดํ•˜์„ธ์š”. ์ด ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ๋ชจ๋“  ์œ„ํ—˜์„ ๋ณธ์ธ์ด ๊ฐ์ˆ˜ํ•˜๋Š” ๋ฐ ๋™์˜ํ•ฉ๋‹ˆ๋‹ค. diff --git a/src/agent/prompts.test.ts b/src/agent/prompts.test.ts new file mode 100644 index 000000000..4a4cdd5f7 --- /dev/null +++ b/src/agent/prompts.test.ts @@ -0,0 +1,22 @@ +import { describe, it, expect } from 'bun:test'; +import { buildKoreanResearchSection } from './prompts.js'; + +describe('buildKoreanResearchSection tiering', () => { + it('returns the full DART-backed playbook when a DART key is present', () => { + const s = buildKoreanResearchSection(true); + expect(s).toContain('your edge over generic assistants'); + expect(s).toContain('get_financials_kr'); // full tier drives the DART tools + expect(s).toContain('get_large_holders_kr'); + }); + + it('returns the keyless tier โ€” pointing at NO unbound DART tools โ€” without a DART key', () => { + const s = buildKoreanResearchSection(false); + expect(s).toContain('keyless market-data edge'); + expect(s).toContain('get_market_data_kr'); // always-registered Naver tools + expect(s).toContain('get_foreign_ownership_kr'); + expect(s).not.toContain('get_financials_kr'); // DART tools are not bound here + expect(s).not.toContain('get_large_holders_kr'); + expect(s).not.toContain('read_filings_kr'); + expect(s.length).toBeGreaterThan(0); // never empty โ€” the keyless tools always exist + }); +}); diff --git a/src/agent/prompts.ts b/src/agent/prompts.ts index a869b9308..7bdeb7bac 100644 --- a/src/agent/prompts.ts +++ b/src/agent/prompts.ts @@ -1,5 +1,5 @@ import { buildCompactToolDescriptions } from '../tools/registry.js'; -import { checkApiKeyExists } from '../utils/env.js'; +import { hasDartKey } from '../utils/env.js'; import { buildSkillMetadataSection, discoverSkills } from '../skills/index.js'; import { readFile } from 'node:fs/promises'; import { dirname, join } from 'node:path'; @@ -87,14 +87,34 @@ ${skillList} } /** - * Korea-specific research playbook. Only included when DART-backed KR tools are - * active (DART_API_KEY present). These first-party data sources are the agent's - * edge over generic assistants; without explicit synthesis guidance the model - * defaults to a generic per-metric summary indistinguishable from a chatbot. + * Korea-specific research playbook, in two tiers. These first-party data sources + * are the agent's edge over generic assistants; without explicit synthesis + * guidance the model defaults to a generic per-metric summary. + * - Full tier (DART_API_KEY present): the complete DART-backed sweep + synthesis. + * - Keyless tier (no DART key): get_market_data_kr and get_foreign_ownership_kr + * are registered UNCONDITIONALLY (Naver, no key), so the ์ˆ˜๊ธ‰ยท๋ฐธ๋ฅ˜์—์ด์…˜ guidance + * must survive without DART โ€” otherwise the model loses the playbook for tools + * it still has. Referencing only the always-registered keyless tools avoids + * pointing the model at unbound DART tools. + * Pure (the DART-key check lives at the call site) so both tiers are unit-testable. */ -function buildKoreanResearchSection(): string { - if (!checkApiKeyExists('DART_API_KEY')) { - return ''; +export function buildKoreanResearchSection(hasDartKey: boolean): string { + if (!hasDartKey) { + return `## Korean Stock Research (6-digit tickers โ€” keyless market-data edge) + +Even without a DART key you have keyless first-party Korean data a general chatbot does +NOT have: get_market_data_kr (ํ˜„์žฌ๊ฐ€ยท์ผ๋ณ€๋™ยท52์ฃผยท์‹œ๊ฐ€์ด์•กยทPER/PBR/EPS/BPSยท์ถ”์ •PERยท๋ฐฐ๋‹น์ˆ˜์ต๋ฅ ยท +๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šคยท๋™์ข… peer) and get_foreign_ownership_kr (์ผ๋ณ„ ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ + ์™ธ๊ตญ์ธ/๊ธฐ๊ด€/๊ฐœ์ธ +์ˆœ๋งค์ˆ˜ ํ๋ฆ„). For any Korean-stock ๊ฐ€๊ฒฉ / ๋ฐธ๋ฅ˜์—์ด์…˜ / ์ˆ˜๊ธ‰ question: + +- GROUND every claim in those tool numbers, dated โ€” current ํ˜„์žฌ๊ฐ€, PER/PBR vs ์—…์ข…ยท์ž๊ธฐ ๊ณผ๊ฑฐ + ๋ฐด๋“œ, ๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค ๋Œ€๋น„ ์ƒ์Šน์—ฌ๋ ฅ, ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ ์ˆ˜์ค€ + ์ตœ๊ทผ ์ˆœ๋งค์ˆ˜ ๋ฐฉํ–ฅ(์™ธ๊ตญ์ธ vs ๊ธฐ๊ด€ vs ๊ฐœ์ธ). + Never quote a price or multiple from memory; call the tool. +- SYNTHESIZE ์ˆ˜๊ธ‰(์™ธ๊ตญ์ธยท๊ธฐ๊ด€ ๋ฐฉํ–ฅ)๊ณผ ๋ฐธ๋ฅ˜์—์ด์…˜์„ ํ•˜๋‚˜์˜ ๊ด€์ ์œผ๋กœ ๋ฌถ์œผ์„ธ์š” โ€” ๋‹จ์ˆœ ๋‚˜์—ด ๊ธˆ์ง€. + ๊ณต๋งค๋„ ์ž”๊ณ (get_short_balance_kr)ยท๊ตญ๋ฏผ์—ฐ๊ธˆ(get_nps_holdings)์ด ๋“ฑ๋ก๋ผ ์žˆ์œผ๋ฉด smart-money ์‹ ํ˜ธ๋กœ ํ•จ๊ป˜ ์—ฎ์œผ์„ธ์š”. +- STATE LIMITS honestly: DART ํ‚ค๊ฐ€ ์—†์œผ๋ฉด ์ •ํ™•ํ•œ K-IFRS ์‹ค์ ยท๊ณต์‹œยท5%๋ฃฐยท์ž„์›๊ฑฐ๋ž˜๋Š” ์กฐํšŒ ๋ถˆ๊ฐ€์ž…๋‹ˆ๋‹ค. + ์‹ค์  ๊ธฐ๋ฐ˜ ํŒ๋‹จ์ด ํ•„์š”ํ•˜๋ฉด ๊ทธ ํ•œ๊ณ„๋ฅผ ๋ช…์‹œํ•˜๊ณ (์ˆซ์ž๋ฅผ ์ง€์–ด๋‚ด์ง€ ๋ง ๊ฒƒ) DART_API_KEY ์ถ”๊ฐ€๋ฅผ ์ œ์•ˆํ•˜์„ธ์š” โ€” + ๊ฐ€๊ฒฉยท์ˆ˜๊ธ‰ ๋ฒ”์œ„ ์•ˆ์—์„œ๋งŒ ํ™•์ •์ ์œผ๋กœ ๋‹ตํ•˜์„ธ์š”.`; } return `## Korean Stock Research (6-digit tickers โ€” your edge over generic assistants) @@ -294,9 +314,10 @@ ${toolDescriptions} - 6-digit numeric tickers (e.g. 005930, 035420) are Korean stocks โ€” use get_financials_kr for fundamentals, get_market_data_kr for current priceยท์‹œ๊ฐ€์ด์•กยท๋ฐœํ–‰์ฃผ์‹์ˆ˜ยทPER/PBR/EPSยท๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค (the get_market_data equivalent โ€” get_market_data does NOT resolve 6-digit tickers), get_filings_kr for DART disclosures, get_large_holders_kr for 5%-rule major shareholders, get_insider_trades_kr for executive/insider ownership, get_short_balance_kr for ๊ณต๋งค๋„ ์ž”๊ณ  (short interest), get_foreign_ownership_kr for ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ (foreign ownership), get_nps_holdings for ๊ตญ๋ฏผ์—ฐ๊ธˆ (National Pension Service) holdings, and read_filings_kr for the report narrative (์‚ฌ์—… ๊ตฌ์„ฑยท์ฃผ์š” ๋ฆฌ์Šคํฌยท๊ฒฝ์˜์ง„๋‹จ MD&A from DART ์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ). ASCII tickers (AAPL, MSFT) use the US tools. - Only use web_fetch when headlines are insufficient (need quotes, deal specifics, earnings details). - Tool results are automatically capped. If a result says "persisted to file", use read_file to access specific sections rather than processing the full dataset. +- If a KR market/flow tool result carries a \`_dataQualityWarning\` (possible upstream Naver schema drift), treat the flagged fields as unreliable โ€” omit them (or, only if another tool independently provides the same value, cross-check) and tell the user about the data-quality issue rather than reporting those numbers as fact. - Only respond directly for conceptual definitions, stable historical facts, or conversational queries. -${buildKoreanResearchSection()} +${buildKoreanResearchSection(hasDartKey())} ${buildSkillsSection()} diff --git a/src/tools/finance-kr/api.test.ts b/src/tools/finance-kr/api.test.ts new file mode 100644 index 000000000..cdd06089a --- /dev/null +++ b/src/tools/finance-kr/api.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect, afterEach } from 'bun:test'; +import { assertDartOk } from './api.js'; +import { isQuotaLatched, resetDartThrottle } from './dart-throttle.js'; + +afterEach(() => resetDartThrottle()); + +describe('assertDartOk', () => { + it('passes on status 000 without latching', () => { + resetDartThrottle(); + expect(() => assertDartOk('ok', { status: '000', message: '์ •์ƒ' })).not.toThrow(); + expect(isQuotaLatched()).toBe(false); + }); + + it('passes when there is no status field (data payload)', () => { + resetDartThrottle(); + expect(() => assertDartOk('list', { list: [{ a: 1 }] })).not.toThrow(); + }); + + it('throws on 013 (no data) but does NOT latch the quota breaker', () => { + resetDartThrottle(); + expect(() => assertDartOk('nodata', { status: '013', message: '์กฐํšŒ๋œ ๋ฐ์ดํƒ€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.' })).toThrow(/013/); + expect(isQuotaLatched()).toBe(false); + }); + + it('throws a quota-specific error AND latches on 020 ์‚ฌ์šฉํ•œ๋„์ดˆ๊ณผ', () => { + resetDartThrottle(); + expect(() => + assertDartOk('quota', { status: '020', message: '์‚ฌ์šฉํ•œ๋„๋ฅผ ์ดˆ๊ณผํ•˜์˜€์Šต๋‹ˆ๋‹ค.' }), + ).toThrow(/์‚ฌ์šฉํ•œ๋„ ์ดˆ๊ณผ/); + expect(isQuotaLatched()).toBe(true); + }); +}); diff --git a/src/tools/finance-kr/api.ts b/src/tools/finance-kr/api.ts index 9d9474d1e..07b5244a8 100644 --- a/src/tools/finance-kr/api.ts +++ b/src/tools/finance-kr/api.ts @@ -1,5 +1,11 @@ import { readCache, writeCache, describeRequest } from '../../utils/cache.js'; import { logger } from '../../utils/logger.js'; +import { + runWithDartSlot, + assertNotQuotaLatched, + tripQuotaLatch, + QUOTA_EXHAUSTED_MESSAGE, +} from './dart-throttle.js'; const BASE_URL = 'https://opendart.fss.or.kr/api'; @@ -19,10 +25,16 @@ function getApiKey(): string { * Reference: https://opendart.fss.or.kr/guide/main.do?apiGrpCd= * 000=์ •์ƒ, 013=์กฐํšŒ๋œ ๋ฐ์ดํƒ€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค, 020=์‚ฌ์šฉํ•œ๋„์ดˆ๊ณผ, etc. */ -function assertDartOk(label: string, data: Record): void { +export function assertDartOk(label: string, data: Record): void { const status = typeof data.status === 'string' ? data.status : undefined; if (status && status !== '000') { const message = typeof data.message === 'string' ? data.message : 'unknown'; + // 020 = ์‚ฌ์šฉํ•œ๋„์ดˆ๊ณผ (daily quota). Latch the breaker so the rest of a fan-out + // burst fails fast instead of each call also burning a doomed round-trip. + if (status === '020') { + tripQuotaLatch(); + throw new Error(`[DART API] ${label} โ€” ${QUOTA_EXHAUSTED_MESSAGE} (${message})`); + } throw new Error(`[DART API] ${label} โ€” status=${status} (${message})`); } } @@ -44,6 +56,7 @@ export const dartApi = { if (!apiKey) { throw new Error('[DART API] DART_API_KEY not set'); } + assertNotQuotaLatched(label); // fast path: already over quota โ†’ no slot, no network const url = new URL(`${BASE_URL}${endpoint}`); url.searchParams.set('crtfc_key', apiKey); @@ -53,26 +66,29 @@ export const dartApi = { } } - let response: Response; - try { - response = await fetch(url.toString()); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - logger.error(`[DART API] network error: ${label} โ€” ${message}`); - throw new Error(`[DART API] request failed for ${label}: ${message}`); - } + const data = await runWithDartSlot(async () => { + assertNotQuotaLatched(label); // a sibling may have 020'd while we waited for a slot + let response: Response; + try { + response = await fetch(url.toString()); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`[DART API] network error: ${label} โ€” ${message}`); + throw new Error(`[DART API] request failed for ${label}: ${message}`); + } - if (!response.ok) { - const detail = `${response.status} ${response.statusText}`; - logger.error(`[DART API] HTTP error: ${label} โ€” ${detail}`); - throw new Error(`[DART API] request failed: ${detail}`); - } + if (!response.ok) { + const detail = `${response.status} ${response.statusText}`; + logger.error(`[DART API] HTTP error: ${label} โ€” ${detail}`); + throw new Error(`[DART API] request failed: ${detail}`); + } - const data = (await response.json().catch(() => { - const detail = `invalid JSON (${response.status} ${response.statusText})`; - logger.error(`[DART API] parse error: ${label} โ€” ${detail}`); - throw new Error(`[DART API] request failed: ${detail}`); - })) as Record; + return (await response.json().catch(() => { + const detail = `invalid JSON (${response.status} ${response.statusText})`; + logger.error(`[DART API] parse error: ${label} โ€” ${detail}`); + throw new Error(`[DART API] request failed: ${detail}`); + })) as Record; + }); assertDartOk(label, data); @@ -104,6 +120,7 @@ export const dartApi = { if (!apiKey) { throw new Error('[DART API] DART_API_KEY not set'); } + assertNotQuotaLatched(label); // fast path: already over quota โ†’ no slot, no network const url = new URL(`${BASE_URL}${endpoint}`); url.searchParams.set('crtfc_key', apiKey); @@ -113,30 +130,39 @@ export const dartApi = { } } - let response: Response; - try { - response = await fetch(url.toString()); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - logger.error(`[DART API] network error: ${label} โ€” ${message}`); - throw new Error(`[DART API] request failed for ${label}: ${message}`); - } + const bytes = await runWithDartSlot(async () => { + assertNotQuotaLatched(label); // a sibling may have 020'd while we waited for a slot + let response: Response; + try { + response = await fetch(url.toString()); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`[DART API] network error: ${label} โ€” ${message}`); + throw new Error(`[DART API] request failed for ${label}: ${message}`); + } - if (!response.ok) { - const detail = `${response.status} ${response.statusText}`; - logger.error(`[DART API] HTTP error: ${label} โ€” ${detail}`); - throw new Error(`[DART API] request failed: ${detail}`); - } + if (!response.ok) { + const detail = `${response.status} ${response.statusText}`; + logger.error(`[DART API] HTTP error: ${label} โ€” ${detail}`); + throw new Error(`[DART API] request failed: ${detail}`); + } - const contentType = response.headers.get('content-type') ?? ''; - if (contentType.includes('xml') || contentType.includes('json') || contentType.includes('text')) { - const body = await response.text().catch(() => ''); - const detail = body.slice(0, 200); - logger.error(`[DART API] expected binary: ${label} โ€” got ${contentType}: ${detail}`); - throw new Error(`[DART API] ${label}: expected document ZIP, got ${contentType}: ${detail}`); - } + const contentType = response.headers.get('content-type') ?? ''; + if (contentType.includes('xml') || contentType.includes('json') || contentType.includes('text')) { + const body = await response.text().catch(() => ''); + // The binary endpoint returns an XML/JSON status payload (not a ZIP) on error; + // a 020 ์‚ฌ์šฉํ•œ๋„์ดˆ๊ณผ here must latch the breaker too. + if (/\s*020\s*<\/status>/.test(body) || body.includes('์‚ฌ์šฉํ•œ๋„')) { + tripQuotaLatch(); + throw new Error(`[DART API] ${label} โ€” ${QUOTA_EXHAUSTED_MESSAGE}`); + } + const detail = body.slice(0, 200); + logger.error(`[DART API] expected binary: ${label} โ€” got ${contentType}: ${detail}`); + throw new Error(`[DART API] ${label}: expected document ZIP, got ${contentType}: ${detail}`); + } - const bytes = new Uint8Array(await response.arrayBuffer()); + return new Uint8Array(await response.arrayBuffer()); + }); const safeUrl = new URL(url.toString()); safeUrl.searchParams.delete('crtfc_key'); diff --git a/src/tools/finance-kr/dart-document.test.ts b/src/tools/finance-kr/dart-document.test.ts index 7881e04d0..2d6fbc781 100644 --- a/src/tools/finance-kr/dart-document.test.ts +++ b/src/tools/finance-kr/dart-document.test.ts @@ -157,3 +157,79 @@ describe('real fixture (Samsung 005930 ์‚ฌ์—…๋ณด๊ณ ์„œ II. ์‚ฌ์—…์˜ ๋‚ด์šฉ)', ( expect(sel.risks).toContain('์œ„ํ—˜'); // ์žฌ๋ฌด์œ„ํ—˜๊ด€๋ฆฌ์ •์ฑ… โ€ฆ }); }); + +describe('governance category', () => { + // VI ์ด์‚ฌํšŒ ๋“ฑ ํšŒ์‚ฌ์˜ ๊ธฐ๊ด€ / VII ์ฃผ์ฃผ์— ๊ด€ํ•œ ์‚ฌํ•ญ / IX ๊ณ„์—ดํšŒ์‚ฌ ๋“ฑ / X ๋Œ€์ฃผ์ฃผ ๋“ฑ๊ณผ์˜ ๊ฑฐ๋ž˜๋‚ด์šฉ, + // interleaved with II (business) and XI (other) which governance must NOT pull. + const GOV_SAMPLE = ` + +II. ์‚ฌ์—…์˜ ๋‚ด์šฉ +

๋‹น์‚ฌ๋Š” ๋ฐ˜๋„์ฒด ์‚ฌ์—…์„ ์˜์œ„ํ•ฉ๋‹ˆ๋‹ค.

+VI. ์ด์‚ฌํšŒ ๋“ฑ ํšŒ์‚ฌ์˜ ๊ธฐ๊ด€์— ๊ด€ํ•œ ์‚ฌํ•ญ +1. ์ด์‚ฌํšŒ์— ๊ด€ํ•œ ์‚ฌํ•ญ +

์ด์‚ฌํšŒ๋Š” ์‚ฌ๋‚ด์ด์‚ฌ 5์ธ๊ณผ ์‚ฌ์™ธ์ด์‚ฌ 6์ธ์œผ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.

+VII. ์ฃผ์ฃผ์— ๊ด€ํ•œ ์‚ฌํ•ญ +1. ์ตœ๋Œ€์ฃผ์ฃผ ๋ฐ ๊ทธ ํŠน์ˆ˜๊ด€๊ณ„์ธ์˜ ์ฃผ์‹์†Œ์œ  ํ˜„ํ™ฉ +

์ตœ๋Œ€์ฃผ์ฃผ๋Š” ์™ธ ํŠน์ˆ˜๊ด€๊ณ„์ธ์ด๋ฉฐ ์ง€๋ถ„ ํ•ฉ๊ณ„๋Š” 20.1%์ž…๋‹ˆ๋‹ค.

+2. ์ฃผ์‹์˜ ๋ถ„ํฌ +

์†Œ์•ก์ฃผ์ฃผ๋Š” ์ „์ฒด์˜ 78%๋ฅผ ๋ณด์œ ํ•ฉ๋‹ˆ๋‹ค.

+IX. ๊ณ„์—ดํšŒ์‚ฌ ๋“ฑ์— ๊ด€ํ•œ ์‚ฌํ•ญ +

์‚ผ์„ฑ๋ฌผ์‚ฐ ๋“ฑ 59๊ฐœ ๊ณ„์—ดํšŒ์‚ฌ๋ฅผ ๋ณด์œ ํ•˜๋ฉฐ ์ˆœํ™˜์ถœ์ž ๊ตฌ์กฐ๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

+X. ๋Œ€์ฃผ์ฃผ ๋“ฑ๊ณผ์˜ ๊ฑฐ๋ž˜๋‚ด์šฉ +

๋Œ€์ฃผ์ฃผ์™€์˜ ์ž๊ธˆ๊ฑฐ๋ž˜ ๋ฐ ๋‹ด๋ณด์ œ๊ณต ๋‚ด์—ญ.

+XI. ๊ทธ ๋ฐ–์— ํˆฌ์ž์ž ๋ณดํ˜ธ๋ฅผ ์œ„ํ•˜์—ฌ ํ•„์š”ํ•œ ์‚ฌํ•ญ +

์ œ์žฌํ˜„ํ™ฉ ์—†์Œ.

+
`; + + const sections = splitSections(GOV_SAMPLE); + + it('pulls VI/VII/IX/X ownership & governance prose', () => { + const { governance } = selectSections(sections, ['governance']); + expect(governance).toContain('์‚ฌ์™ธ์ด์‚ฌ 6์ธ'); // VI ์ด์‚ฌํšŒ + expect(governance).toContain('์ตœ๋Œ€์ฃผ์ฃผ'); // VII ์ฃผ์ฃผ + expect(governance).toContain('์†Œ์•ก์ฃผ์ฃผ๋Š” ์ „์ฒด์˜ 78%'); // VII.2 ์ฃผ์‹์˜ ๋ถ„ํฌ (generic-titled sub, by parent) + expect(governance).toContain('์ˆœํ™˜์ถœ์ž'); // IX ๊ณ„์—ดํšŒ์‚ฌ + expect(governance).toContain('๋Œ€์ฃผ์ฃผ์™€์˜ ์ž๊ธˆ๊ฑฐ๋ž˜'); // X ๋Œ€์ฃผ์ฃผ ๋“ฑ๊ณผ์˜ ๊ฑฐ๋ž˜ + }); + + it('does not bleed into II business or XI other-matters', () => { + const { governance } = selectSections(sections, ['governance']); + expect(governance).not.toContain('๋ฐ˜๋„์ฒด ์‚ฌ์—…'); // II + expect(governance).not.toContain('์ œ์žฌํ˜„ํ™ฉ ์—†์Œ'); // XI + }); + + it('business does not pull governance sections', () => { + const { business } = selectSections(sections, ['business']); + expect(business).toContain('๋ฐ˜๋„์ฒด ์‚ฌ์—…'); + expect(business ?? '').not.toContain('์ตœ๋Œ€์ฃผ์ฃผ'); + }); + + it('drops headerless, title-less fragments (no empty-label block)', () => { + // A whitespace-only under a governance major โ†’ numeral='' & title=''. It matches + // by parent but must be filtered out as noise, not emitted as an empty "[]" block. + const noise = splitSections( + `<DOCUMENT><BODY> +<TITLE ATOC="Y" ENG="VII. Shareholders">VII. ์ฃผ์ฃผ์— ๊ด€ํ•œ ์‚ฌํ•ญ +

์ตœ๋Œ€์ฃผ์ฃผ ์ง€๋ถ„ 20%.

+ +

ํ—ค๋” ์—†๋Š” ์žก์Œ ์กฐ๊ฐ.

+`, + ); + const { governance } = selectSections(noise, ['governance']); + expect(governance).toContain('์ตœ๋Œ€์ฃผ์ฃผ ์ง€๋ถ„ 20%'); // VII major kept + expect(governance).not.toContain('[]'); // empty-label block filtered + expect(governance).not.toContain('ํ—ค๋” ์—†๋Š” ์žก์Œ'); // the title-less fragment dropped + }); + + it('catches a shareholder section even when its numeral drifts (title fallback)', () => { + // VIII (โˆ‰ canonical GOV_MAJORS) โ€” must still match via the ์ฃผ์ฃผ์— ๊ด€ํ•œ/์ตœ๋Œ€์ฃผ์ฃผ title regex. + const drift = splitSections( + ` +VIII. ์ฃผ์ฃผ์— ๊ด€ํ•œ ์‚ฌํ•ญ +

์ตœ๋Œ€์ฃผ์ฃผ ์ง€๋ถ„ 30%.

+
`, + ); + const { governance } = selectSections(drift, ['governance']); + expect(governance).toContain('์ตœ๋Œ€์ฃผ์ฃผ ์ง€๋ถ„ 30%'); + }); +}); diff --git a/src/tools/finance-kr/dart-document.ts b/src/tools/finance-kr/dart-document.ts index 982ecb11c..f89d7f7bf 100644 --- a/src/tools/finance-kr/dart-document.ts +++ b/src/tools/finance-kr/dart-document.ts @@ -18,7 +18,7 @@ import { unzipSync, strFromU8 } from 'fflate'; import { dartApi } from './api.js'; /** Narrative section categories the planner can request. */ -export type SectionCategory = 'overview' | 'business' | 'products' | 'risks' | 'mdna'; +export type SectionCategory = 'overview' | 'business' | 'products' | 'risks' | 'mdna' | 'governance'; export const SECTION_CATEGORIES: readonly SectionCategory[] = [ 'overview', @@ -26,6 +26,7 @@ export const SECTION_CATEGORIES: readonly SectionCategory[] = [ 'products', 'risks', 'mdna', + 'governance', ]; export interface DsdSection { @@ -222,6 +223,26 @@ function sectionMatches(s: DsdSection, cat: SectionCategory): boolean { s.parent === 'IV' || /๊ฒฝ์˜\s*์ง„๋‹จ|๋ถ„์„์˜๊ฒฌ/.test(s.title) ); + case 'governance': { + // ์ง€๋ฐฐ๊ตฌ์กฐยท์†Œ์œ ๊ตฌ์กฐยทํŠน์ˆ˜๊ด€๊ณ„์ž โ€” the Korea-discount drivers. Scattered across the + // canonical ์ •๊ธฐ๋ณด๊ณ ์„œ ์„œ์‹: VI ์ด์‚ฌํšŒ ๋“ฑ ํšŒ์‚ฌ์˜ ๊ธฐ๊ด€ / VII ์ฃผ์ฃผ์— ๊ด€ํ•œ ์‚ฌํ•ญ / + // IX ๊ณ„์—ดํšŒ์‚ฌ ๋“ฑ์— ๊ด€ํ•œ ์‚ฌํ•ญ / X ๋Œ€์ฃผ์ฃผ ๋“ฑ๊ณผ์˜ ๊ฑฐ๋ž˜๋‚ด์šฉ(์ดํ•ด๊ด€๊ณ„์ž์™€์˜ ๊ฑฐ๋ž˜), all in + // the main body. The ํŠน์ˆ˜๊ด€๊ณ„์ž์™€์˜ ๊ฑฐ๋ž˜ footnote (III ์žฌ๋ฌด ์ฃผ์„) is in the body too and + // matches here, but it is table-heavy so dsdToPlainText strips it to ~empty and it drops + // out โ€” precise related-party amounts stay get_financials_kr's job, not this narrative. + // (Verified live against 005930 FY2025: VI/VII/IX/X yield ~9.6KB of real prose.) + // Numerals drift across reports (esp. ๋ฐ˜๊ธฐ/๋ถ„๊ธฐ), so match the canonical majors AND + // their subs by numeral, then fall back to title keywords โ€” the title fallback catches + // a major (and its descriptively-titled subs) even when its numeral shifts. + const GOV_MAJORS = new Set(['VI', 'VII', 'IX', 'X']); + return ( + (s.level === 'major' && GOV_MAJORS.has(s.numeral)) || + (s.parent !== '' && GOV_MAJORS.has(s.parent)) || + /์ฃผ์ฃผ์—\s*๊ด€ํ•œ|์ตœ๋Œ€์ฃผ์ฃผ|ํŠน์ˆ˜๊ด€๊ณ„|์ฃผ์‹์˜\s*๋ถ„ํฌ|๊ณ„์—ดํšŒ์‚ฌ|ํƒ€๋ฒ•์ธ.?์ถœ์ž|๋Œ€์ฃผ์ฃผ.*๊ฑฐ๋ž˜|์ดํ•ด๊ด€๊ณ„์ž.*๊ฑฐ๋ž˜|์ด์‚ฌํšŒ|์‚ฌ์™ธ์ด์‚ฌ|ํšŒ์‚ฌ์˜\s*๊ธฐ๊ด€|์ง€๋ฐฐ๊ตฌ์กฐ/.test( + s.title, + ) + ); + } default: return false; } @@ -240,6 +261,9 @@ export function selectSections( const text = sectionText(s); if (text.trim().length === 0) continue; const label = s.numeral ? `${s.numeral}. ${s.title}` : s.title; + // Skip headerless, title-less fragments (numeral='' AND title='') โ€” they carry no + // citable heading and only add noise (observed under governance majors in real filings). + if (label.trim() === '') continue; blocks.push(`[${label}]\n${text}`); } // Triple-newline between blocks (dsdToPlainText caps internal runs at \n\n) so the diff --git a/src/tools/finance-kr/dart-throttle.test.ts b/src/tools/finance-kr/dart-throttle.test.ts new file mode 100644 index 000000000..b8f8f8c65 --- /dev/null +++ b/src/tools/finance-kr/dart-throttle.test.ts @@ -0,0 +1,145 @@ +import { describe, it, expect, afterEach } from 'bun:test'; +import { + runWithDartSlot, + acquireDartSlot, + activeDartRequests, + resetDartThrottle, + isQuotaLatched, + tripQuotaLatch, + assertNotQuotaLatched, + QUOTA_COOLDOWN_MS, +} from './dart-throttle.js'; + +const flush = () => new Promise((r) => setTimeout(r, 0)); + +afterEach(() => { + delete process.env.DART_MAX_CONCURRENCY; + resetDartThrottle(); +}); + +describe('runWithDartSlot concurrency cap', () => { + it('reaches but never exceeds the cap, and runs every task', async () => { + process.env.DART_MAX_CONCURRENCY = '3'; + resetDartThrottle(); + let inFlight = 0; + let peak = 0; + let done = 0; + const task = () => + runWithDartSlot(async () => { + inFlight++; + peak = Math.max(peak, inFlight); + await new Promise((r) => setTimeout(r, 5)); + inFlight--; + done++; + }); + await Promise.all(Array.from({ length: 20 }, task)); + expect(peak).toBe(3); // parallel up to the capโ€ฆ + expect(done).toBe(20); // โ€ฆand nothing dropped + expect(activeDartRequests()).toBe(0); // all slots released + }); + + it('blocks callers past the cap; one release admits exactly one waiter (no timer magic)', async () => { + process.env.DART_MAX_CONCURRENCY = '2'; + resetDartThrottle(); + let started = 0; + const resolvers: Array<() => void> = []; + const block = () => new Promise((res) => { started++; resolvers.push(res); }); + const tasks = Array.from({ length: 4 }, () => runWithDartSlot(block)); + + await flush(); + expect(started).toBe(2); // only the cap is admittedโ€ฆ + expect(activeDartRequests()).toBe(2); // โ€ฆthe other two are genuinely gated + + resolvers.shift()!(); // free exactly one slot + await flush(); + expect(started).toBe(3); // exactly one queued task admitted (one-for-one handoff) + expect(activeDartRequests()).toBe(2); + + while (resolvers.length || activeDartRequests() > 0) { + resolvers.shift()?.(); + await flush(); + } + await Promise.all(tasks); + expect(activeDartRequests()).toBe(0); + }); + + it('releases the slot even when the task throws', async () => { + process.env.DART_MAX_CONCURRENCY = '1'; + resetDartThrottle(); + await expect( + runWithDartSlot(async () => { + throw new Error('boom'); + }), + ).rejects.toThrow('boom'); + expect(activeDartRequests()).toBe(0); + let ran = false; + await runWithDartSlot(async () => { + ran = true; + }); + expect(ran).toBe(true); // the released slot is reusable + }); + + it('a releaser is single-use โ€” a double call cannot drive the count negative or over-admit', async () => { + process.env.DART_MAX_CONCURRENCY = '1'; + resetDartThrottle(); + const release = await acquireDartSlot(); + expect(activeDartRequests()).toBe(1); + release(); + expect(activeDartRequests()).toBe(0); + release(); // extra call โ€” must be a no-op + expect(activeDartRequests()).toBe(0); // not -1 + + let inFlight = 0; + let peak = 0; + await Promise.all( + Array.from({ length: 4 }, () => + runWithDartSlot(async () => { + inFlight++; + peak = Math.max(peak, inFlight); + await flush(); + inFlight--; + }), + ), + ); + expect(peak).toBe(1); // would exceed 1 if the double-release had corrupted the count + }); +}); + +describe('quota circuit-breaker', () => { + it('lifts exactly at the cooldown boundary (exclusive) and re-trips half-open', () => { + resetDartThrottle(); + expect(isQuotaLatched(1000)).toBe(false); + tripQuotaLatch(1000); + expect(isQuotaLatched(1000 + QUOTA_COOLDOWN_MS - 1)).toBe(true); + expect(isQuotaLatched(1000 + QUOTA_COOLDOWN_MS)).toBe(false); // boundary: lifted + tripQuotaLatch(1000 + QUOTA_COOLDOWN_MS); // a probe 020s again โ†’ re-latch + expect(isQuotaLatched(1000 + QUOTA_COOLDOWN_MS + 1)).toBe(true); // window re-extended + }); + + it('assertNotQuotaLatched throws a quota-specific error only while latched', () => { + resetDartThrottle(); + expect(() => assertNotQuotaLatched('x')).not.toThrow(); + tripQuotaLatch(); + expect(() => assertNotQuotaLatched('list.json')).toThrow(/์‚ฌ์šฉํ•œ๋„ ์ดˆ๊ณผ/); + resetDartThrottle(); + expect(() => assertNotQuotaLatched('x')).not.toThrow(); + }); + + it('a caller queued behind a sibling fails fast once the sibling trips the 020 latch', async () => { + process.env.DART_MAX_CONCURRENCY = '1'; + resetDartThrottle(); + let releaseT1!: () => void; + const t1 = runWithDartSlot(() => new Promise((res) => { releaseT1 = res; })); + // t2 queues behind t1 and re-checks the latch after it finally gets a slot (as api.ts does). + const t2 = runWithDartSlot(async () => { + assertNotQuotaLatched('queued'); + }); + await flush(); + expect(activeDartRequests()).toBe(1); // t1 holds the only slot, t2 is queued + + tripQuotaLatch(); // a sibling 020s while t2 is still waiting + releaseT1(); // free the slot โ†’ t2 acquires, re-checks, and fails fast + await t1; + await expect(t2).rejects.toThrow(/์‚ฌ์šฉํ•œ๋„/); + }); +}); diff --git a/src/tools/finance-kr/dart-throttle.ts b/src/tools/finance-kr/dart-throttle.ts new file mode 100644 index 000000000..6bb01986a --- /dev/null +++ b/src/tools/finance-kr/dart-throttle.ts @@ -0,0 +1,104 @@ +/** + * Shared throttle + daily-quota circuit-breaker for every OpenDART call. + * + * Why this exists: get_business_report_kr fans out over years (Promise.all), + * get_financials_kr fans out over routed sub-tool calls (Promise.all again), and + * the agent runs the concurrency-safe KR DART tools in parallel. Multiplied + * together, a single "์ด ์ข…๋ชฉ ๋ถ„์„ํ•ด์ค˜" sweep can fire dozens of DART requests at + * once โ€” wasteful against the free key's 20,000/day quota and prone to transient + * 020 ์‚ฌ์šฉํ•œ๋„์ดˆ๊ณผ. So we: + * 1. Bound the number of in-flight DART requests (a counting semaphore), and + * 2. Latch a circuit-breaker on the first 020 so the rest of a burst fails fast + * with a clear message instead of each doing a doomed round-trip. + * + * Retrying with backoff would NOT help: 020 is a daily cap, not a per-second rate + * limit, so the right response is throttle + a quota-aware error, not retry. + */ + +const DEFAULT_MAX_CONCURRENCY = 4; +/** How long to keep the breaker latched after a 020 before probing again. */ +export const QUOTA_COOLDOWN_MS = 5 * 60_000; + +function readCap(): number { + const raw = Number(process.env.DART_MAX_CONCURRENCY); + return Number.isFinite(raw) && raw >= 1 ? Math.floor(raw) : DEFAULT_MAX_CONCURRENCY; +} + +let maxConcurrency = readCap(); +let active = 0; +const queue: Array<() => void> = []; + +/** + * Free a held slot: hand it straight to the next waiter (count unchanged so the + * cap is conserved), or drop the count when none waits. The `active > 0` floor is + * defensive โ€” with single-use releasers it can't be hit, but it guarantees the + * count can never go negative (and thus never over-admit) even under misuse. + */ +function releaseSlot(): void { + const next = queue.shift(); + if (next) next(); + else if (active > 0) active--; +} + +/** Acquire a slot, resolving to its SINGLE-USE releaser (extra calls are no-ops). */ +export function acquireDartSlot(): Promise<() => void> { + const makeReleaser = (): (() => void) => { + let released = false; + return () => { + if (released) return; // idempotent โ€” double-release can't corrupt the count + released = true; + releaseSlot(); + }; + }; + if (active < maxConcurrency) { + active++; + return Promise.resolve(makeReleaser()); + } + return new Promise<() => void>((resolve) => { + queue.push(() => resolve(makeReleaser())); + }); +} + +/** Run `fn` holding one of the bounded DART request slots. */ +export async function runWithDartSlot(fn: () => Promise): Promise { + const release = await acquireDartSlot(); + try { + return await fn(); + } finally { + release(); + } +} + +let quotaExhaustedUntil = 0; + +export function isQuotaLatched(now: number = Date.now()): boolean { + return quotaExhaustedUntil > now; +} + +/** Latch the breaker after a 020 ์‚ฌ์šฉํ•œ๋„์ดˆ๊ณผ; it lifts after QUOTA_COOLDOWN_MS. */ +export function tripQuotaLatch(now: number = Date.now()): void { + quotaExhaustedUntil = now + QUOTA_COOLDOWN_MS; +} + +export const QUOTA_EXHAUSTED_MESSAGE = + 'DART ์ผ์ผ ์‚ฌ์šฉํ•œ๋„ ์ดˆ๊ณผ(020). ๋ฌด๋ฃŒ ํ‚ค๋Š” ์ผ 20,000๊ฑด ํ•œ๋„์ด๋ฉฐ ์ž์ •(KST) ์ดํ›„ ๋ฆฌ์…‹๋ฉ๋‹ˆ๋‹ค โ€” ์ž ์‹œ ํ›„ ์žฌ์‹œ๋„ํ•˜์„ธ์š”.'; + +/** Throw a clear quota error (no network call) while the breaker is latched. */ +export function assertNotQuotaLatched(label: string): void { + if (isQuotaLatched()) { + throw new Error(`[DART API] ${label} โ€” ${QUOTA_EXHAUSTED_MESSAGE}`); + } +} + +/** Test seam: reset throttle + latch and re-read the cap from env. */ +export function resetDartThrottle(): void { + maxConcurrency = readCap(); + active = 0; + queue.length = 0; + quotaExhaustedUntil = 0; +} + +/** Test seam: current in-flight DART request count. */ +export function activeDartRequests(): number { + return active; +} diff --git a/src/tools/finance-kr/get-foreign-ownership-kr.test.ts b/src/tools/finance-kr/get-foreign-ownership-kr.test.ts index abd9d09b3..0eb2f2cea 100644 --- a/src/tools/finance-kr/get-foreign-ownership-kr.test.ts +++ b/src/tools/finance-kr/get-foreign-ownership-kr.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect } from 'bun:test'; import { mapForeignRow } from './get-foreign-ownership-kr.js'; +import { deadColumns } from './utils.js'; describe('mapForeignRow', () => { it('maps and numeric-parses a Naver trend row', () => { @@ -23,3 +24,34 @@ describe('mapForeignRow', () => { }); }); }); + +describe('foreign-ownership partial-drift canary', () => { + // Mirrors the caller's structural column set (get-foreign-ownership-kr.ts). + const COLS: Array> = [ + 'foreignHoldRatio', + 'foreignNetBuyQty', + 'orgNetBuyQty', + 'individualNetBuyQty', + 'closePrice', + 'tradingVolume', + ]; + + it('flags foreignHoldRatio dead when Naver renames foreignerHoldRatio (rows still non-empty)', () => { + // Simulate the rename: foreignerHoldRatio is gone; every other field still maps. + const drifted = [ + { bizdate: '20260529', foreignerPureBuyQuant: '-1,000', organPureBuyQuant: '+2,000', individualPureBuyQuant: '-1,000', closePrice: '317,000', accumulatedTradingVolume: '100' }, + { bizdate: '20260528', foreignerPureBuyQuant: '+500', organPureBuyQuant: '-500', individualPureBuyQuant: '0', closePrice: '315,000', accumulatedTradingVolume: '200' }, + { bizdate: '20260527', foreignerPureBuyQuant: '0', organPureBuyQuant: '0', individualPureBuyQuant: '0', closePrice: '316,000', accumulatedTradingVolume: '150' }, + ].map(mapForeignRow); + expect(drifted.length).toBeGreaterThanOrEqual(3); // not a "no data" / single-row case + expect(deadColumns(drifted, COLS)).toEqual(['foreignHoldRatio']); + }); + + it('flags nothing for healthy rows (zeros parse to 0, not null)', () => { + const healthy = [ + { bizdate: '20260529', foreignerHoldRatio: '48.27%', foreignerPureBuyQuant: '-1,061,741', organPureBuyQuant: '+5,314,304', individualPureBuyQuant: '-4,237,361', closePrice: '317,000', accumulatedTradingVolume: '32,804,208' }, + { bizdate: '20260528', foreignerHoldRatio: '48.30%', foreignerPureBuyQuant: '0', organPureBuyQuant: '0', individualPureBuyQuant: '0', closePrice: '316,000', accumulatedTradingVolume: '0' }, + ].map(mapForeignRow); + expect(deadColumns(healthy, COLS)).toEqual([]); + }); +}); diff --git a/src/tools/finance-kr/get-foreign-ownership-kr.ts b/src/tools/finance-kr/get-foreign-ownership-kr.ts index 6c4284176..51e675ef3 100644 --- a/src/tools/finance-kr/get-foreign-ownership-kr.ts +++ b/src/tools/finance-kr/get-foreign-ownership-kr.ts @@ -1,7 +1,7 @@ import { DynamicStructuredTool } from '@langchain/core/tools'; import { z } from 'zod'; import { fetchNaverTrend } from './naver-api.js'; -import { parseKrxNumber, toIsoDate } from './utils.js'; +import { parseKrxNumber, toIsoDate, deadColumns } from './utils.js'; import { formatToolResult } from '../types.js'; import { TTL_6H } from '../finance/utils.js'; @@ -59,7 +59,32 @@ export const getForeignOwnershipKr = new DynamicStructuredTool({ .map(mapForeignRow) .sort((a, b) => b.date.localeCompare(a.date)) .slice(0, input.limit); - return formatToolResult({ ...base, ownership }, [url]); + // Partial-drift canary: a Naver field rename makes a whole column null across + // every row while the array stays non-empty (no error). Flag the dead columns + // so the model treats the gap as suspect upstream drift, not a real zero. + // Genuine emptiness parses to 0 (not null โ€” e.g. a flat/zero-foreign day), so a + // null column means the source field is gone, not that nothing happened. Require + // โ‰ฅ3 rows first: on a 1-2 row window (brand-new listing) "null in every row" is + // trivially satisfiable and would false-positive. + const dead = + ownership.length >= 3 + ? deadColumns(ownership, [ + 'foreignHoldRatio', + 'foreignNetBuyQty', + 'orgNetBuyQty', + 'individualNetBuyQty', + 'closePrice', + 'tradingVolume', + ]) + : []; + const warning = + dead.length > 0 + ? `์ˆ˜๊ธ‰ ์ปฌ๋Ÿผ์ด ์ „ ๊ตฌ๊ฐ„ null: ${dead.join(', ')}. Naver trend ์‘๋‹ต ๊ตฌ์กฐ ๋ณ€๊ฒฝ(ํ•„๋“œ rename) ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์–ด, ๊ฐ’์„ ์‹ ๋ขฐํ•˜๊ธฐ ์ „ ํ™•์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.` + : null; + return formatToolResult( + { ...base, ownership, ...(warning ? { _dataQualityWarning: warning } : {}) }, + [url], + ); } catch (error) { const message = error instanceof Error ? error.message : String(error); return formatToolResult({ ...base, _error: message }, []); diff --git a/src/tools/finance-kr/get-market-data-kr.test.ts b/src/tools/finance-kr/get-market-data-kr.test.ts index d4e2bb642..fdabebeb2 100644 --- a/src/tools/finance-kr/get-market-data-kr.test.ts +++ b/src/tools/finance-kr/get-market-data-kr.test.ts @@ -1,5 +1,10 @@ import { describe, it, expect } from 'bun:test'; -import { mapMarketData, parseKoreanMarketCapToKRW, hasNoMarketData } from './get-market-data-kr.js'; +import { + mapMarketData, + parseKoreanMarketCapToKRW, + hasNoMarketData, + marketDataQualityWarning, +} from './get-market-data-kr.js'; import { parseNaverMetric } from './utils.js'; // Shape captured from m.stock.naver.com/api/stock/005930/integration. @@ -152,3 +157,108 @@ describe('hasNoMarketData', () => { expect(hasNoMarketData(mapMarketData('005930', SAMSUNG_RAW as Record))).toBe(false); }); }); + +describe('marketDataQualityWarning (partial-drift canary)', () => { + it('is null for the full, healthy Samsung payload', () => { + expect(marketDataQualityWarning(mapMarketData('005930', SAMSUNG_RAW as Record))).toBeNull(); + }); + + it('is null for a truly-empty payload (that case is hasNoMarketDataโ€™s job)', () => { + expect(marketDataQualityWarning(mapMarketData('999999', null))).toBeNull(); + }); + + it('WARNS on the partial-drift gap hasNoMarketData misses (name present, structurals null)', () => { + // The exact payload hasNoMarketData treats as a valid ticker (stockName only). + const sparse = mapMarketData('005930', { stockName: '์‚ผ์„ฑ์ „์ž' } as Record); + expect(hasNoMarketData(sparse)).toBe(false); // slips past the no-data guardโ€ฆ + const warning = marketDataQualityWarning(sparse); // โ€ฆbut the canary catches it + expect(warning).toContain('price'); + expect(warning).toContain('marketCap'); + expect(warning).toContain('ํ•„๋“œ ๋ˆ„๋ฝ'); + }); + + it('warns when ์‹œ์ด is renamed away (critical field) even if everything else maps', () => { + const noMarketValue = { + ...SAMSUNG_RAW, + totalInfos: SAMSUNG_RAW.totalInfos.filter((t) => t.code !== 'marketValue'), + }; + const m = mapMarketData('005930', noMarketValue as Record); + expect(hasNoMarketData(m)).toBe(false); // price+name still present โ†’ not "no data" + expect(marketDataQualityWarning(m)).toContain('marketCap'); + }); + + it('does NOT warn when only legitimately-optional fields are absent (no false positive)', () => { + // A loss-maker / non-payer: drop PER, EPS, dividend, forward estimates โ€” all legit null. + const optionalOnly = { + ...SAMSUNG_RAW, + totalInfos: SAMSUNG_RAW.totalInfos.filter( + (t) => !['per', 'eps', 'cnsPer', 'cnsEps', 'dividendYieldRatio', 'dividend'].includes(t.code), + ), + }; + expect(marketDataQualityWarning(mapMarketData('005930', optionalOnly as Record))).toBeNull(); + }); + + it('does NOT warn on a no-coverage equity (has ์‹œ์ด + price, but no earnings/book) โ€” equity-conditional', () => { + // No PER/EPS โ†’ not earnings-reporting, so the missing PBR/BPS pair is NOT counted. + const noCoverage = { + ...SAMSUNG_RAW, + totalInfos: SAMSUNG_RAW.totalInfos.filter( + (t) => !['per', 'eps', 'cnsPer', 'cnsEps', 'pbr', 'bps'].includes(t.code), + ), + }; + const m = mapMarketData('123456', noCoverage as Record); + expect(hasNoMarketData(m)).toBe(false); // price + ์‹œ์ด present + expect(marketDataQualityWarning(m)).toBeNull(); + }); + + it('does NOT warn on a real ETF schema (nav/etfBaseIdx, no marketValue/multiples) โ€” fund detection', () => { + // Naver serves funds a different totalInfos shape; ์‹œ์ดยทPERยทPBR absent by construction, + // not drift. Without the fund check this would warn on the missing ์‹œ์ด (critical field). + const etf = { + itemCode: '069500', + stockName: 'KODEX 200', + totalInfos: [ + { code: 'lastClosePrice', key: '์ „์ผ', value: '138,730' }, + { code: 'nav', key: 'NAV', value: '138,900' }, + { code: 'fundPay', key: '๋ถ„๋ฐฐ๊ธˆ', value: '0' }, + { code: 'etfBaseIdx', key: '๊ธฐ์ดˆ์ง€์ˆ˜', value: '์ฝ”์Šคํ”ผ 200' }, + { code: 'issueName', key: '๋ฐœํ–‰์‚ฌ', value: '์‚ผ์„ฑ์ž์‚ฐ์šด์šฉ' }, + ], + dealTrendInfos: [ + { bizdate: '20260601', closePrice: '138,730', compareToPreviousClosePrice: '500', compareToPreviousPrice: { code: '2', text: '์ƒ์Šน', name: 'RISING' }, accumulatedTradingVolume: '1,000,000' }, + ], + }; + const m = mapMarketData('069500', etf as Record); + expect(m.quote.price).toBe(138730); + expect(m.valuation.marketCap).toBeNull(); // funds have no marketValue fieldโ€ฆ + expect(marketDataQualityWarning(m, etf as Record)).toBeNull(); // โ€ฆbut that's not drift + }); + + it('WARNS when an EQUITY loses both PBR and BPS (book-value drift, earnings still present)', () => { + const bookDrift = { + ...SAMSUNG_RAW, + totalInfos: SAMSUNG_RAW.totalInfos.filter((t) => !['pbr', 'bps'].includes(t.code)), + }; + const w = marketDataQualityWarning(mapMarketData('005930', bookDrift as Record)); + expect(w).toContain('pbr'); + expect(w).toContain('bps'); + }); + + it('WARNS when the daily close is renamed away and a stale ์ „์ผ close masks it', () => { + // dealTrendInfos[0] without closePrice โ†’ price falls back to lastClosePrice (349,000). + const staleClose = { + ...SAMSUNG_RAW, + dealTrendInfos: [ + { + bizdate: '20260601', + compareToPreviousClosePrice: '32,000', + compareToPreviousPrice: { code: '2', text: '์ƒ์Šน', name: 'RISING' }, + accumulatedTradingVolume: '45,052,488', + }, + ], + }; + const m = mapMarketData('005930', staleClose as Record); + expect(m.quote.price).toBe(349000); // stale prior-close rescued the valueโ€ฆ + expect(marketDataQualityWarning(m, staleClose as Record)).toContain('closePrice'); // โ€ฆcanary catches it + }); +}); diff --git a/src/tools/finance-kr/get-market-data-kr.ts b/src/tools/finance-kr/get-market-data-kr.ts index b0798a56f..f047bdeba 100644 --- a/src/tools/finance-kr/get-market-data-kr.ts +++ b/src/tools/finance-kr/get-market-data-kr.ts @@ -1,7 +1,7 @@ import { DynamicStructuredTool } from '@langchain/core/tools'; import { z } from 'zod'; import { fetchNaverIntegration } from './naver-api.js'; -import { parseNaverMetric, toIsoDate } from './utils.js'; +import { parseNaverMetric, toIsoDate, nullFields } from './utils.js'; import { formatToolResult } from '../types.js'; import { TTL_1H } from '../finance/utils.js'; @@ -88,6 +88,58 @@ export function hasNoMarketData(m: MarketDataKr): boolean { return m.name === null && m.quote.price === null && m.valuation.marketCap === null; } +/** + * Partial-drift canary. Naver has no API contract: a renamed `totalInfos` code maps + * to null while sibling fields still populate, slipping past hasNoMarketData (which + * only fires when name+price+marketCap are ALL null). We flag three drift shapes on + * a valid payload (name present): + * - a CRITICAL field (ํ˜„์žฌ๊ฐ€ or ์‹œ์ด) null โ€” every tradable listing, equity OR fund, + * must carry these; + * - PBR and BPS BOTH null on an EQUITY (PER or EPS present) โ€” book-value multiples + * are universal for equities, so both vanishing is drift. Funds/notes (ETN/ETF/ + * REIT) and day-1 IPOs legitimately lack book value AND report no earnings, so the + * PER/EPS gate keeps them quiet (no false positive); + * - the daily close (dealTrendInfos[0].closePrice) renamed away while the stale ์ „์ผ + * lastClosePrice fallback masks it โ€” the worst case, since price stays non-null and + * the model would otherwise treat yesterday's close as today's with no signal. + * PER/EPS/๋ฐฐ๋‹น/์ถ”์ •์น˜ are excluded as drift signals: legitimately null for loss-makers, + * non-payers, and uncovered small caps, so they'd false-positive. + */ +export function marketDataQualityWarning( + m: MarketDataKr, + raw?: Record | null, +): string | null { + if (m.name === null) return null; // truly-empty payload is hasNoMarketData's job + + // ETF/ETN/fund schema check FIRST. Naver serves funds a different totalInfos shape + // (nav/fundPay/etfBaseIdx, no marketValue/PER/PBR), so ์‹œ์ดยทmultiples are absent by + // construction, not drift. get_market_data_kr is an equity tool; a fund legitimately + // maps to price-only, so skip the equity canary rather than cry "schema drift". + const ti = Array.isArray(raw?.totalInfos) ? (raw!.totalInfos as Record[]) : []; + const isFund = ti.some((x) => { + const code = x && typeof x === 'object' ? (x as Record).code : undefined; + return code === 'nav' || code === 'etfBaseIdx' || code === 'fundPay'; + }); + if (isFund) return null; + + const v = m.valuation; + const missing = nullFields({ price: m.quote.price, marketCap: v.marketCap }); + + // Book-value pair is a drift signal ONLY for an earnings-reporting equity โ€” funds + // (ETN/ETF/REIT) and day-1 IPOs legitimately have neither earnings nor book value. + const reportsEarnings = v.per !== null || v.eps !== null; + if (reportsEarnings && v.pbr === null && v.bps === null) missing.push('pbr', 'bps'); + + // Daily-close rename masked by the stale ์ „์ผ ์ข…๊ฐ€ fallback (price stays non-null). + const deal = Array.isArray(raw?.dealTrendInfos) ? (raw!.dealTrendInfos as Record[]) : []; + if (deal.length > 0 && parseNaverMetric(deal[0].closePrice) === null && m.quote.price !== null) { + missing.push('ํ˜„์žฌ๊ฐ€(closePrice ๋ฏธ๊ฐฑ์‹  โ€” ์ „์ผ ์ข…๊ฐ€๋กœ ๋Œ€์ฒด)'); + } + + if (missing.length === 0) return null; + return `ํ•ต์‹ฌ ์‹œ์žฅ๋ฐ์ดํ„ฐ ํ•„๋“œ ๋ˆ„๋ฝ/์ด์ƒ: ${missing.join(', ')}. Naver ์‘๋‹ต ๊ตฌ์กฐ ๋ณ€๊ฒฝ(ํ•„๋“œ rename) ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์–ด, ์ด ์ข…๋ชฉ ์ˆ˜์น˜๋ฅผ ์‹ ๋ขฐํ•˜๊ธฐ ์ „ ํ™•์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.`; +} + /** `totalInfos` is an array of `{ code, key, value }`; look up a value by code. */ function totalInfo(totalInfos: unknown, code: string): unknown { if (!Array.isArray(totalInfos)) return undefined; @@ -193,7 +245,8 @@ export const getMarketDataKr = new DynamicStructuredTool({ [url], ); } - return formatToolResult(mapped, [url]); + const warning = marketDataQualityWarning(mapped, data); + return formatToolResult(warning ? { ...mapped, _dataQualityWarning: warning } : mapped, [url]); } catch (error) { const message = error instanceof Error ? error.message : String(error); return formatToolResult({ ticker, _error: message }, []); diff --git a/src/tools/finance-kr/read-filings-kr.ts b/src/tools/finance-kr/read-filings-kr.ts index 353a4f319..65002f3c2 100644 --- a/src/tools/finance-kr/read-filings-kr.ts +++ b/src/tools/finance-kr/read-filings-kr.ts @@ -26,12 +26,13 @@ export const READ_FILINGS_KR_DESCRIPTION = `Intelligent meta-tool for reading th - "์ฃผ์š” ์ œํ’ˆ/๋งค์ถœ ๊ตฌ์„ฑ" โ€” main products & services (prose; for tables use get_financials_kr) - "์ฃผ์š” ๋ฆฌ์Šคํฌ๊ฐ€ ๋ญ์•ผ" โ€” risk narrative (II.5 ์œ„ํ—˜๊ด€๋ฆฌ ๋ฐ ํŒŒ์ƒ๊ฑฐ๋ž˜ + XI ํˆฌ์ž์ž ๋ณดํ˜ธ) - "๊ฒฝ์˜์ง„๋‹จ ์˜๊ฒฌ / MD&A" โ€” management discussion (IV. ์ด์‚ฌ์˜ ๊ฒฝ์˜์ง„๋‹จ ๋ฐ ๋ถ„์„์˜๊ฒฌ) +- "์ง€๋ฐฐ๊ตฌ์กฐ / ์ตœ๋Œ€์ฃผ์ฃผ / ํŠน์ˆ˜๊ด€๊ณ„์ž / ๊ณ„์—ดํšŒ์‚ฌ / ์†Œ์œ ๊ตฌ์กฐ / ๋Œ€์ฃผ์ฃผ ๊ฑฐ๋ž˜" โ€” ownership & governance narrative (VI ์ด์‚ฌํšŒ ๋“ฑ ํšŒ์‚ฌ์˜ ๊ธฐ๊ด€ ยท VII ์ฃผ์ฃผ์— ๊ด€ํ•œ ์‚ฌํ•ญ ยท IX ๊ณ„์—ดํšŒ์‚ฌ ๋“ฑ ยท X ๋Œ€์ฃผ์ฃผ ๋“ฑ๊ณผ์˜ ๊ฑฐ๋ž˜๋‚ด์šฉ) - Company overview (I. ํšŒ์‚ฌ์˜ ๊ฐœ์š”) ## When NOT to Use - Financial line items / numbers โ†’ get_financials_kr - Disclosure metadata / filing history โ†’ get_filings_kr -- Shareholders, short interest, foreign ownership โ†’ the dedicated *_kr tools +- Exact shareholder stakes, 5%-rule holder lists, short interest, foreign-ownership numbers โ†’ the dedicated *_kr tools (get_large_holders_kr / get_short_balance_kr / get_foreign_ownership_kr). The "governance" category here is the QUALITATIVE ownership/affiliate/related-party narrative, not the structured holdings table. ## Usage Notes - Call ONCE with the full natural-language query. Accepts company names (์‚ผ์„ฑ์ „์ž โ†’ 005930). @@ -62,11 +63,12 @@ const PlanSchema = z.object({ .nullable() .describe('Business (fiscal) year of the report, e.g. 2025 for the FY2025 ์‚ฌ์—…๋ณด๊ณ ์„œ. null for the most recent.'), sections: z - .array(z.enum(['overview', 'business', 'products', 'risks', 'mdna'])) + .array(z.enum(['overview', 'business', 'products', 'risks', 'mdna', 'governance'])) .min(1) .describe( 'Narrative section categories to read. Map intent: ์‚ฌ์—… ๊ตฌ์„ฑ/์„ธ๊ทธ๋จผํŠธโ†’["business"], ์ฃผ์š” ์ œํ’ˆ/๋งค์ถœ๊ตฌ์„ฑโ†’["products"], ' + - '์ฃผ์š” ๋ฆฌ์Šคํฌโ†’["risks"], ๊ฒฝ์˜์ง„๋‹จ/MD&Aโ†’["mdna"], ํšŒ์‚ฌ ๊ฐœ์š”โ†’["overview"]. Pick the minimum set.', + '์ฃผ์š” ๋ฆฌ์Šคํฌโ†’["risks"], ๊ฒฝ์˜์ง„๋‹จ/MD&Aโ†’["mdna"], ํšŒ์‚ฌ ๊ฐœ์š”โ†’["overview"], ' + + '์ง€๋ฐฐ๊ตฌ์กฐ/์ตœ๋Œ€์ฃผ์ฃผ/ํŠน์ˆ˜๊ด€๊ณ„์ž/๊ณ„์—ดํšŒ์‚ฌ/์†Œ์œ ๊ตฌ์กฐ/๋Œ€์ฃผ์ฃผ ๊ฑฐ๋ž˜โ†’["governance"]. Pick the minimum set.', ), }); @@ -88,6 +90,7 @@ Section mapping: - ๋ฆฌ์Šคํฌ / ์œ„ํ—˜ / ์œ„ํ—˜์š”์ธ โ†’ risks - ๊ฒฝ์˜์ง„๋‹จ / MD&A / ๊ฒฝ์˜์ง„ ๋ถ„์„ โ†’ mdna - ํšŒ์‚ฌ ๊ฐœ์š” / ์—ฐํ˜ โ†’ overview +- ์ง€๋ฐฐ๊ตฌ์กฐ / ์ตœ๋Œ€์ฃผ์ฃผ / ํŠน์ˆ˜๊ด€๊ณ„์ž / ๊ณ„์—ดํšŒ์‚ฌ / ์†Œ์œ ๊ตฌ์กฐ / ๋Œ€์ฃผ์ฃผ(์ดํ•ด๊ด€๊ณ„์ž) ๊ฑฐ๋ž˜ / ์ˆœํ™˜์ถœ์ž โ†’ governance Choose the minimum set that answers the query.`; } @@ -177,7 +180,10 @@ function listRange(year?: number | null): { bgn_de: string; end_de: string } { // Extraction (cached) + summarization input // --------------------------------------------------------------------------- -const CACHE_ENDPOINT = '/_dsd_sections'; // synthetic โ€” caches parsed text, never hits network +// synthetic โ€” caches parsed text, never hits network. Versioned: bump the suffix when +// SECTION_CATEGORIES changes so pre-existing cache entries (which lack the new categories) +// miss and re-extract, instead of silently omitting the added category until the TTL lapses. +const CACHE_ENDPOINT = '/_dsd_sections_v2'; const DART_DOCUMENT_TIMEOUT_MS = 30_000; // the 5MB ZIP leg is slower than other sub-tools const PER_SECTION_CHARS = 40_000; const TOTAL_CHARS = 120_000; diff --git a/src/tools/finance-kr/utils.test.ts b/src/tools/finance-kr/utils.test.ts index e97c0cee4..123e24726 100644 --- a/src/tools/finance-kr/utils.test.ts +++ b/src/tools/finance-kr/utils.test.ts @@ -6,6 +6,8 @@ import { isNoDataError, parseKrxNumber, extractOutBlock, + nullFields, + deadColumns, } from './utils.js'; describe('toDartDate', () => { @@ -95,3 +97,35 @@ describe('extractOutBlock', () => { expect(extractOutBlock({ OutBlock_1: 'nope' })).toEqual([]); }); }); + +describe('nullFields', () => { + it('lists only the null/undefined fields, preserving order', () => { + expect(nullFields({ a: 1, b: null, c: 'x', d: undefined })).toEqual(['b', 'd']); + }); + + it('returns [] when every field is present (including 0 and empty string)', () => { + expect(nullFields({ a: 0, b: '', c: false })).toEqual([]); + }); +}); + +describe('deadColumns', () => { + it('flags a column that is null in EVERY row', () => { + const rows = [ + { ratio: 1, qty: null }, + { ratio: 2, qty: null }, + ]; + expect(deadColumns(rows, ['ratio', 'qty'])).toEqual(['qty']); + }); + + it('does not flag a column with even one present value (sporadic gap โ‰  drift)', () => { + const rows = [ + { ratio: 1, qty: null }, + { ratio: 2, qty: 5 }, + ]; + expect(deadColumns(rows, ['ratio', 'qty'])).toEqual([]); + }); + + it('returns [] for an empty row set (no data โ‰  drift)', () => { + expect(deadColumns([] as Array<{ ratio: number | null }>, ['ratio'])).toEqual([]); + }); +}); diff --git a/src/tools/finance-kr/utils.ts b/src/tools/finance-kr/utils.ts index b70a32616..ceee8362d 100644 --- a/src/tools/finance-kr/utils.ts +++ b/src/tools/finance-kr/utils.ts @@ -77,6 +77,32 @@ export function parseNaverMetric(value: unknown): number | null { return Number.isFinite(n) ? n : null; } +/** + * Partial-drift canary for the contract-less Naver mobile JSON. A renamed field + * silently maps to null while sibling fields still populate, so an all-null "no + * data" guard misses it. Given the structural fields a valid payload must carry, + * return the names that came back null/undefined โ€” callers warn when SOME (not + * all) are missing, which is the signature of an upstream schema change. + */ +export function nullFields(fields: Record): string[] { + return Object.entries(fields) + .filter(([, v]) => v === null || v === undefined) + .map(([k]) => k); +} + +/** + * Row-data variant of {@link nullFields}: of `columns`, return those that are + * null/undefined in EVERY row (rows must be non-empty). A whole structural column + * going null across the series is the signature of a Naver field rename, not a + * sporadic genuine gap. + */ +export function deadColumns(rows: T[], columns: (keyof T)[]): string[] { + if (rows.length === 0) return []; + return columns + .filter((c) => rows.every((r) => r[c] === null || r[c] === undefined)) + .map((c) => String(c)); +} + /** * KRX getJsonData responses wrap rows in an `OutBlock_1` array (some endpoints * use other keys). Missing/empty is a valid "no data" outcome, not an error. diff --git a/src/tools/registry.ts b/src/tools/registry.ts index a7d3ba848..6121260ff 100644 --- a/src/tools/registry.ts +++ b/src/tools/registry.ts @@ -3,7 +3,7 @@ import { createGetFinancials, createGetMarketData, createReadFilings, createScre import { exaSearch, perplexitySearch, tavilySearch, langSearch, WEB_SEARCH_DESCRIPTION, xSearchTool, X_SEARCH_DESCRIPTION } from './search/index.js'; import { createWebSearchTool, type WebSearchProvider } from './search/web-search.js'; import { getSetting } from '../utils/config.js'; -import { checkApiKeyExists, type SearchProviderId } from '../utils/env.js'; +import { checkApiKeyExists, hasDartKey, type SearchProviderId } from '../utils/env.js'; import { skillTool, SKILL_TOOL_DESCRIPTION } from './skill.js'; import { webFetchTool, WEB_FETCH_DESCRIPTION } from './fetch/web-fetch.js'; import { browserTool, BROWSER_DESCRIPTION } from './browser/browser.js'; @@ -189,7 +189,7 @@ export function getToolRegistry(model: string): RegisteredTool[] { }); } - if (process.env.DART_API_KEY && !process.env.DART_API_KEY.startsWith('your-')) { + if (hasDartKey()) { tools.push({ name: 'get_financials_kr', tool: createGetFinancialsKr(model), @@ -223,7 +223,7 @@ export function getToolRegistry(model: string): RegisteredTool[] { tool: createReadFilingsKr(model), description: READ_FILINGS_KR_DESCRIPTION, compactDescription: - 'Korean DART ์‚ฌ์—…ยท๋ฐ˜๊ธฐยท๋ถ„๊ธฐ๋ณด๊ณ ์„œ narrative (์‚ฌ์—…์˜ ๋‚ด์šฉ, ์œ„ํ—˜๊ด€๋ฆฌ, ๊ฒฝ์˜์ง„๋‹จ MD&A) โ€” qualitative content for 6-digit tickers.', + 'Korean DART ์‚ฌ์—…ยท๋ฐ˜๊ธฐยท๋ถ„๊ธฐ๋ณด๊ณ ์„œ narrative (์‚ฌ์—…์˜ ๋‚ด์šฉ, ์œ„ํ—˜๊ด€๋ฆฌ, ๊ฒฝ์˜์ง„๋‹จ MD&A, ์ง€๋ฐฐ๊ตฌ์กฐยท์ตœ๋Œ€์ฃผ์ฃผยท๊ณ„์—ดํšŒ์‚ฌ) โ€” qualitative content for 6-digit tickers.', concurrencySafe: true, }); } diff --git a/src/utils/env.ts b/src/utils/env.ts index 54aceed76..38429dd2a 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -46,6 +46,17 @@ export function checkApiKeyExists(apiKeyName: string): boolean { return false; } +/** + * Pure DART-key gate (process.env only, no .env-file fallback) โ€” the single source + * of truth for whether the DART-backed KR tools are bound. Used by BOTH the tool + * registry (to register them) and the system prompt (to pick the research-playbook + * tier), so the playbook can never advertise DART tools that aren't registered. + */ +export function hasDartKey(): boolean { + const value = process.env.DART_API_KEY; + return !!value && !value.startsWith('your-'); +} + export function saveApiKeyToEnv(apiKeyName: string, apiKeyValue: string): boolean { try { let lines: string[] = []; From 89d6150e38221a340547cdc97da2f7fffa3371a7 Mon Sep 17 00:00:00 2001 From: 0xSHKWON Date: Sun, 21 Jun 2026 04:44:26 +0900 Subject: [PATCH 13/70] =?UTF-8?q?=ED=95=9C=EA=B5=AD=20=EC=A3=BC=EC=8B=9D?= =?UTF-8?q?=20=EB=A6=AC=EC=84=9C=EC=B9=98=20=ED=92=88=EC=A7=88=EA=B0=9C?= =?UTF-8?q?=EC=84=A0:=20=EC=A2=85=EB=AA=A9=20=ED=95=B4=EC=84=9D=C2=B7?= =?UTF-8?q?=EC=82=AC=EC=97=85=EB=B6=80=EB=AC=B8=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=C2=B7=EB=B0=B8=EB=A5=98=EC=97=90=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=8A=A4=ED=82=AC=C2=B7eval=20=EC=A0=95=ED=99=95=EB=8F=84=20?= =?UTF-8?q?=EA=B2=8C=EC=9D=B4=ED=8A=B8=20(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ํ•œ๊ตญ ์ฃผ์‹ ๋ฆฌ์„œ์น˜ ํ’ˆ์งˆ๊ฐœ์„ : ์ข…๋ชฉ ํ•ด์„ยท์‚ฌ์—…๋ถ€๋ฌธ ๋ฐ์ดํ„ฐยท๋ฐธ๋ฅ˜์—์ด์…˜ ์Šคํ‚ฌยทeval ์ •ํ™•๋„ ๊ฒŒ์ดํŠธ ๊ฐ์‚ฌ(46๊ฐœ ๊ฒ€์ฆ ๊ฐญ) ๊ธฐ๋ฐ˜ 3-๋งˆ์ผ์Šคํ†ค ํ†ตํ•ฉ ์—…๊ทธ๋ ˆ์ด๋“œ. M1 โ€” ๋‹ต๋ณ€ ํ’ˆ์งˆ ๋ ˆ์ด์–ด(์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ=์œ ์ผํ•œ ์ตœ์ข…๋‹ต ๊ฒฐ์ •์ž): - ์ƒ๋Œ€๊ฐ€์น˜(๊ธฐ์กด peers ํ™œ์šฉ)ยทQoQ+forward ๋ชจ๋ฉ˜ํ…€ยท์‹ค์ ์˜ ์งˆยท๋ฐธ๋ฅ˜์—…(PBR<1 re-rating)ยทํ™˜์œจ strandยท๊ณต๋งค๋„ regime ์กฐ๊ฑดํ™”ยท์ข…๋ชฉ ์‹๋ณ„ ๊ฐ€๋“œ ์ถ”๊ฐ€(prompts.ts ์–‘ tier) - DCF: WACC๋ฅผ bottom-up CAPM(์‹ค์‹œ๊ฐ„ ๊ตญ๊ณ ์ฑ„ Rf+ฮฒ)๋กœ, ์ปจ์„ผ์„œ์Šค ๋ชฉํ‘œ์ฃผ๊ฐ€ ๋Œ€์กฐ, sector-wacc-kr๋Š” sanity bandํ™” M2 โ€” ์‹ ๋ขฐ๊ฐ€๋Šฅํ•œ ๋ผ์šฐํŒ… + ๋ถ„์„ ๊นŠ์ด: - resolve-kr.ts: 6์ž๋ฆฌ OR ํšŒ์‚ฌ๋ช… ํ†ตํ•ฉ ํ•ด์„(DART ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ โ†’ keyless Naver ์ž๋™์™„์„ฑ fallback). 8๊ฐœ ๋„๊ตฌ์— ๋ฐฐ์„ ํ•ด ๋ชจ๋ธ์˜ ์ฝ”๋“œ ์ถ”์ธก์— ์˜ํ•œ '์‚ฌ์ผ๋ŸฐํŠธ ๋‹ค๋ฅธ ํšŒ์‚ฌ' ์˜ค๋ฅ˜ ์ œ๊ฑฐ - get_segments_kr ์‹ ๊ทœ: DART ๋ฌธ์„œ์˜ ์‚ฌ์—…๋ถ€๋ฌธ๋ณ„ ์š”์•ฝ ์žฌ๋ฌดํ˜„ํ™ฉ ํ‘œ ๋ณต๊ตฌ(parseDsdTables). ์‚ผ์„ฑ/ํ˜„๋Œ€์ฐจ ๋ผ์ด๋ธŒ ๊ฒ€์ฆ - ์‹ ๊ทœ ์Šคํ‚ฌ 4์ข…(auto-discovered): kr-relative-valuationยทkr-sotp-holdcoยทkr-earnings-qualityยทkr-shareholder-return. DCF Step0๊ฐ€ ๊ฒฝ๊ธฐ๋ฏผ๊ฐ์ฃผ/์ง€์ฃผ์‚ฌ๋ฅผ ๋ผ์šฐํŒ… M3 โ€” eval ์•ˆ์ „๋ง: - ๊ฒฐ์ •๋ก ์  ์ •ํ™•๋„ ๊ฒŒ์ดํŠธ: checkRequiredPhrases + checkNumericAnchors(์กฐ/์–ตยท% ํŒŒ์‹ฑ, ํ—ˆ์šฉ์˜ค์ฐจ). buildQuestionResult hard-fail. samsung-fundamentals๋ฅผ fixture ์ˆ˜์น˜(133.9์กฐ/57.2์กฐ)์— ์•ต์ปค โ†’ replay PASS ๊ฒ€์ฆ - ์งˆ๋ฌธ๋ฑ…ํฌ 14โ†’22(์„ธ๊ทธ๋จผํŠธยท์ด๋ฆ„ํ•ด์„ยท์ง€์ฃผ์‚ฌยท๋ฐธ๋ฅ˜์—…ยท์ƒ๋Œ€๊ฐ€์น˜ยท์€ํ–‰ยท๋ชจ๋ฉ˜ํ…€ยท์šฐ์„ ์ฃผ), judge ์ฐจ์› 3์ข… ์ถ”๊ฐ€ - ci.yml์— kr-eval-replay ์žก(run-eval ๋ผ๋ฒจยท์‹œํฌ๋ฆฟ ๊ฒŒ์ดํŠธ, ํ‚ค ์—†์œผ๋ฉด graceful skip) ๊ฒ€์ฆ: typecheck ํ†ต๊ณผ, 241 ํ…Œ์ŠคํŠธ ํ†ต๊ณผ(2ํšŒ ์•ˆ์ •), ์ด๋ฆ„ํ•ด์„ยท์„ธ๊ทธ๋จผํŠธยทnumeric ๊ฒŒ์ดํŠธ ๋ผ์ด๋ธŒ ๊ฒ€์ฆ. Co-Authored-By: Claude Opus 4.8 (1M context) * get_segments_kr: summarizeToolResult ์ผ€์ด์Šค ์ถ”๊ฐ€ (TUI ์ƒํƒœ์ค„) ์‹ ๊ทœ KR ๋„๊ตฌ๊ฐ€ cli.ts์˜ summarizeToolResult์— ์ผ€์ด์Šค๊ฐ€ ์—†์–ด generic 'Received N fields'๋กœ ๋น ์ง€๋˜ ๊ฒƒ์„ 'Found N segment tables'๋กœ ์ˆ˜์ • (CLAUDE.md ์ปจ๋ฒค์…˜). Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 28 +++ scripts/kr-eval/judge.ts | 16 ++ scripts/kr-eval/questions.ts | 118 ++++++++++- scripts/kr-eval/report.ts | 6 +- scripts/kr-eval/runner.ts | 5 +- scripts/kr-eval/scorer.test.ts | 75 +++++++ scripts/kr-eval/scorer.ts | 72 ++++++- src/agent/prompts.ts | 71 ++++--- src/cli.ts | 4 + src/skills/dcf/SKILL.md | 19 +- src/skills/dcf/sector-wacc-kr.md | 8 +- src/skills/kr-earnings-quality/SKILL.md | 50 +++++ src/skills/kr-relative-valuation/SKILL.md | 56 ++++++ src/skills/kr-shareholder-return/SKILL.md | 49 +++++ src/skills/kr-sotp-holdco/SKILL.md | 51 +++++ src/skills/registry.test.ts | 11 +- src/tools/finance-kr/dart-document.ts | 25 +++ src/tools/finance-kr/get-filings-kr.ts | 18 +- src/tools/finance-kr/get-financials-kr.ts | 15 +- .../finance-kr/get-foreign-ownership-kr.ts | 16 +- src/tools/finance-kr/get-insider-trades-kr.ts | 18 +- src/tools/finance-kr/get-large-holders-kr.ts | 18 +- src/tools/finance-kr/get-market-data-kr.ts | 14 +- src/tools/finance-kr/get-segments-kr.test.ts | 69 +++++++ src/tools/finance-kr/get-segments-kr.ts | 188 ++++++++++++++++++ src/tools/finance-kr/get-short-balance-kr.ts | 14 +- src/tools/finance-kr/naver-api.ts | 61 ++++++ src/tools/finance-kr/read-filings-kr.ts | 19 +- src/tools/finance-kr/resolve-kr.test.ts | 46 +++++ src/tools/finance-kr/resolve-kr.ts | 91 +++++++++ .../sub-tools/get-business-report.ts | 16 +- src/tools/registry.ts | 11 +- 32 files changed, 1178 insertions(+), 100 deletions(-) create mode 100644 src/skills/kr-earnings-quality/SKILL.md create mode 100644 src/skills/kr-relative-valuation/SKILL.md create mode 100644 src/skills/kr-shareholder-return/SKILL.md create mode 100644 src/skills/kr-sotp-holdco/SKILL.md create mode 100644 src/tools/finance-kr/get-segments-kr.test.ts create mode 100644 src/tools/finance-kr/get-segments-kr.ts create mode 100644 src/tools/finance-kr/resolve-kr.test.ts create mode 100644 src/tools/finance-kr/resolve-kr.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbe0c9356..827255485 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,3 +30,31 @@ jobs: - name: Run ${{ matrix.task }} run: ${{ matrix.command }} + + # On-demand KR-eval regression run (replay mode โ€” recorded tool fixtures, no DART/KRX/Naver + # network). Still needs LLM keys for the agent + judge, so it's gated behind the `run-eval` + # label and pulls keys from repo secrets. Without keys the runner skips every question and + # exits 0, so a misconfigured repo never produces a false failure. + kr-eval-replay: + if: github.event.action == 'labeled' && github.event.label.name == 'run-eval' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + run: bun install --frozen-lockfile --ignore-scripts + + - name: KR eval (replay) + run: bun run kr-eval:replay + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} + # Optional repo variables to pin models to whatever key is configured + # (defaults: agent + judge = gpt-5.5, which needs OPENAI_API_KEY). + KR_RUNNER_MODEL: ${{ vars.KR_RUNNER_MODEL }} + KR_EVAL_JUDGE_MODEL: ${{ vars.KR_EVAL_JUDGE_MODEL }} diff --git a/scripts/kr-eval/judge.ts b/scripts/kr-eval/judge.ts index 6ba551266..15d09818f 100644 --- a/scripts/kr-eval/judge.ts +++ b/scripts/kr-eval/judge.ts @@ -27,12 +27,28 @@ const DIMENSION_RUBRICS: Record = { '์˜์—…์ด์ต, ์ˆœ์ด์ต, margins, or ROE with concrete numbers AND a year-over-year (YoY / ์ „๋…„๋Œ€๋น„ / ' + '์ „๋…„๋™๊ธฐ) comparison, grounded in the financial figures the tools returned. Score 1.0 for dated, ' + 'numeric YoY figures; ~0.5 if numbers but no YoY (or vice-versa); 0 if vague or missing.', + earnings_momentum: + 'DIMENSION = ์‹ค์  ๋ชจ๋ฉ˜ํ…€. Reward the answer ONLY if, beyond YoY, it addresses the DIRECTION of momentum ' + + 'โ€” the latest quarter-over-quarter (QoQ / ์ „๋ถ„๊ธฐ ๋Œ€๋น„) inflection AND/OR whether results beat or missed ' + + 'forward expectations (์ถ”์ •EPS/์ถ”์ •PER, ์ปจ์„ผ์„œ์Šค ๋Œ€๋น„ ์„œํ”„๋ผ์ด์ฆˆ) โ€” grounded in the tool figures. For a ' + + 'cyclical (๋ฐ˜๋„์ฒดยท2์ฐจ์ „์ง€ยทํ™”ํ•™ยท์ฒ ๊ฐ•) the QoQ turn matters more than YoY. Score ~0.5 for YoY-only with no ' + + 'momentum read; 0 if it ignores direction/consensus.', cross_signal: 'DIMENSION = ๊ต์ฐจ์‹ ํ˜ธ ์ข…ํ•ฉ (cross-signal synthesis). Reward the answer ONLY if it weaves ์ˆ˜๊ธ‰' + '(์™ธ๊ตญ์ธยท๊ณต๋งค๋„ยท๊ธฐ๊ด€ ๋ฐฉํ–ฅ) ยท ์‹ค์ (๋งค์ถœ/์ด์ต/๋งˆ์ง„/ROE) ยท ์ง€๋ฐฐ๊ตฌ์กฐ(๋Œ€๋Ÿ‰๋ณด์œ ยท๊ณ„์—ด ์ง€๋ถ„) into ONE integrated ' + 'investment thesis where the signals are explicitly cross-referenced (do they agree or conflict?) ' + 'and it ends with an evidence-anchored verdict and specific triggers. Score low for three ' + 'disconnected bullet lists with no synthesis, even if each bullet is individually correct.', + relative_value: + 'DIMENSION = ์ƒ๋Œ€๊ฐ€์น˜ (relative valuation). Reward the answer ONLY if it compares the target\'s multiples ' + + '(PER/PBR/EV โ€” and PBR/ROE for a bank) against PEERS or the sector (not just absolute "cheap"), and ' + + 'explains the premium/discount via fundamentals (์„ฑ์žฅยท๋งˆ์ง„ยทROEยท์ง€๋ฐฐ๊ตฌ์กฐ). For a cyclical it should lean on ' + + 'PBR/through-cycle rather than trough/peak PER. Score 0 for a bare PER without any peer/sector comparison.', + value_up: + 'DIMENSION = ๋ฐธ๋ฅ˜์—…/์ฃผ์ฃผํ™˜์› (Value-up & shareholder return). Reward the answer ONLY if it goes beyond ' + + '"low PBR = cheap" to assess RE-RATING potential: ๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ ๊ณ„ํšยท์ž์‚ฌ์ฃผ ์†Œ๊ฐ(๋งค์ž… ์•„๋‹Œ ์†Œ๊ฐ)ยท๋ฐฐ๋‹น์ •์ฑ…, and ' + + 'treats the ABSENCE of shareholder-return commitment as a reason the ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ persists. Score 0 if ' + + 'it just calls a sub-1 PBR cheap with no return/Value-up analysis.', governance: 'DIMENSION = ์ง€๋ฐฐ๊ตฌ์กฐ (governance) as a valuation factor. Reward the answer if it treats ๋Œ€๋Ÿ‰๋ณด์œ /' + '๊ณ„์—ด ์ง€๋ถ„/๋ฌผ์ ๋ถ„ํ• ยท์ธ์ ๋ถ„ํ•  as valuation-relevant (์ˆœํ™˜์ถœ์ž, ์Šน๊ณ„, ์ง€์ฃผ์‚ฌ ํ• ์ธ, ์†Œ์•ก์ฃผ์ฃผ ์˜ํ–ฅ, ๋”๋ธ”์นด์šดํŒ…) ' + diff --git a/scripts/kr-eval/questions.ts b/scripts/kr-eval/questions.ts index 16f905fbd..f7c40d077 100644 --- a/scripts/kr-eval/questions.ts +++ b/scripts/kr-eval/questions.ts @@ -4,7 +4,29 @@ // all that's needed to extend the harness. /** Answer-quality dimensions scored by the LLM judge (judge.ts). */ -export type DimensionId = 'earnings_yoy' | 'cross_signal' | 'governance' | 'grounding'; +export type DimensionId = + | 'earnings_yoy' + | 'earnings_momentum' + | 'cross_signal' + | 'governance' + | 'relative_value' + | 'value_up' + | 'grounding'; + +/** + * A deterministic numeric-accuracy gate: a value the answer MUST state within + * tolerance (anti-hallucination โ€” catches a plausible-but-wrong number the LLM judge + * waves through). Anchor `value`s to a committed fixture's figures so they stay stable. + * - unit 'krw' โ†’ matched against ์กฐ/์–ต amounts parsed from the answer; tolerancePct is RELATIVE % (default 5). + * - unit 'pct' โ†’ matched against `N%` figures; tolerancePct is ABSOLUTE percentage-points (default 1). + * - unit 'count' โ†’ matched against comma-grouped integers; tolerancePct is RELATIVE % (default 5). + */ +export interface NumericAnchor { + label: string; + value: number; + unit: 'krw' | 'pct' | 'count'; + tolerancePct?: number; +} export interface KrEvalQuestion { /** Stable id โ€” also the fixture filename (scripts/kr-eval/fixtures//.json). */ @@ -21,6 +43,10 @@ export interface KrEvalQuestion { dimensions: DimensionId[]; /** Per-dimension pass threshold override (default DEFAULT_THRESHOLD in scorer.ts). */ thresholds?: Partial>; + /** Hard gate: these substrings MUST appear in the answer (whitespace/case-insensitive) โ€” e.g. the resolved name or a segment label. */ + requiredPhrases?: string[]; + /** Hard gate: each value MUST appear in the answer within tolerance (anti-hallucination). */ + numericAnchors?: NumericAnchor[]; notes?: string; } @@ -32,7 +58,13 @@ export const KR_EVAL_QUESTIONS: KrEvalQuestion[] = [ expectedTools: ['get_financials_kr'], requiredTools: ['get_financials_kr'], dimensions: ['earnings_yoy', 'grounding'], - notes: '๋‹จ์ผ ์ข…๋ชฉ ํŽ€๋”๋ฉ˜ํ„ธ โ€” ์†์ต + ์ „๋…„๋Œ€๋น„ ์ˆซ์ž๊ฐ€ ํ•ต์‹ฌ. grounding์œผ๋กœ YoY๊ฐ€ ํˆด ๋ฐ์ดํ„ฐ์— ์‹ค์ œ ๊ทผ๊ฑฐํ•˜๋Š”์ง€ ๊ต์ฐจ๊ฒ€์ฆ.', + // Anchored to fixtures/default/samsung-fundamentals.json (revenue 133.9์กฐ, OP 57.2์กฐ): + // a deterministic accuracy gate that fails a plausible-but-wrong figure the judge misses. + numericAnchors: [ + { label: '๋งค์ถœ', value: 133.9e12, unit: 'krw', tolerancePct: 3 }, + { label: '์˜์—…์ด์ต', value: 57.2e12, unit: 'krw', tolerancePct: 5 }, + ], + notes: '๋‹จ์ผ ์ข…๋ชฉ ํŽ€๋”๋ฉ˜ํ„ธ โ€” ์†์ต + ์ „๋…„๋Œ€๋น„ ์ˆซ์ž๊ฐ€ ํ•ต์‹ฌ. grounding + numericAnchors๋กœ YoY/์ˆ˜์น˜๊ฐ€ ํˆด ๋ฐ์ดํ„ฐ์— ์‹ค์ œ ๊ทผ๊ฑฐํ•˜๋Š”์ง€ ๊ต์ฐจ๊ฒ€์ฆ.', }, { id: 'business-overview-samsung', @@ -162,4 +194,86 @@ export const KR_EVAL_QUESTIONS: KrEvalQuestion[] = [ thresholds: { governance: 0.65 }, notes: 'KOSDAQ ๊ต์ฐจ์‹ ํ˜ธ ์ข…ํ•ฉ.', }, + + // โ”€โ”€ Coverage expansion (M3): de-bias the ์‚ผ์„ฑ-heavy bank and exercise the new + // M2 tools/skills. These have no recorded fixture yet, so they run in live/record + // and SKIP under replay until `bun run kr-eval:record` captures them. + { + id: 'segments-samsung', + query: '์‚ผ์„ฑ์ „์ž(005930) ์‚ฌ์—…๋ถ€๋ฌธ๋ณ„ ๋งค์ถœ์ด๋ž‘ ์˜์—…์ด์ต ๋น„์ค‘ ์•Œ๋ ค์ค˜.', + ticker: '005930', + expectedTools: ['get_segments_kr'], + requiredTools: ['get_segments_kr'], + dimensions: ['grounding'], + // DX/DS are Samsung's reported segment labels โ€” a correct answer must name them. + requiredPhrases: ['DX', 'DS'], + notes: '์‹ ๊ทœ get_segments_kr โ€” ์‚ฌ์—…๋ถ€๋ฌธ๋ณ„ ์š”์•ฝ ์žฌ๋ฌดํ˜„ํ™ฉ ์ถ”์ถœ์ด ๋‹ต์— ๋ฐ˜์˜๋˜๋Š”์ง€(๋ถ€๋ฌธ๋ช… + grounding).', + }, + { + id: 'name-resolution-koreazinc', + query: '๊ณ ๋ ค์•„์—ฐ ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ์ด๋ž‘ ํ˜„์žฌ๊ฐ€ ์–ด๋•Œ?', + expectedTools: ['get_market_data_kr', 'get_foreign_ownership_kr'], + requiredTools: ['get_market_data_kr'], + dimensions: ['grounding'], + // Name-only (no ticker): the resolver must land on ๊ณ ๋ ค์•„์—ฐ(010130), not a wrong code. + // The resolved name appearing is the anti-"silent wrong company" check. + requiredPhrases: ['๊ณ ๋ ค์•„์—ฐ'], + notes: '์ด๋ฆ„ ํ•ด์„ ๊ฒฝ๋กœ(ํ‹ฐ์ปค ๋ฏธ์ œ๊ณต) โ€” resolveKrSecurity๊ฐ€ ์ •ํ™•ํ•œ ์ข…๋ชฉ์œผ๋กœ ๋ผ์šฐํŒ…๋˜๋Š”์ง€.', + }, + { + id: 'holdco-sotp-lg', + query: 'LG(003550) ์ง€์ฃผ์‚ฌ์ธ๋ฐ NAV(์ˆœ์ž์‚ฐ๊ฐ€์น˜) ๋Œ€๋น„ ์–ผ๋งˆ๋‚˜ ํ• ์ธ๋ผ ๊ฑฐ๋ž˜๋˜๋Š”์ง€ ๋ด์ค˜.', + ticker: '003550', + expectedTools: ['get_market_data_kr', 'get_large_holders_kr', 'read_filings_kr'], + requiredTools: ['get_market_data_kr'], + dimensions: ['governance', 'grounding'], + thresholds: { governance: 0.6 }, + notes: '์ง€์ฃผ์‚ฌ SOTP/NAV โ€” kr-sotp-holdco ์Šคํ‚ฌ ๊ฒฝ๋กœ. ์ง€์ฃผ์‚ฌ ํ• ์ธ์„ ๊ฐ€์น˜์š”์ธ์œผ๋กœ ๋‹ค๋ฃจ๋Š”์ง€(governance).', + }, + { + id: 'value-up-hyundai', + query: 'ํ˜„๋Œ€์ฐจ(005380) PBR๋„ ๋‚ฎ์€๋ฐ ๋ฐธ๋ฅ˜์—…ยท์ฃผ์ฃผํ™˜์›(์ž์‚ฌ์ฃผ ์†Œ๊ฐยท๋ฐฐ๋‹น) ๊ธฐ๋Œ€ํ•  ๋งŒํ•ด?', + ticker: '005380', + expectedTools: ['get_market_data_kr', 'get_filings_kr'], + requiredTools: ['get_market_data_kr'], + dimensions: ['value_up', 'grounding'], + notes: '๋ฐธ๋ฅ˜์—…/์ฃผ์ฃผํ™˜์› โ€” kr-shareholder-return ์Šคํ‚ฌ. PBR<1 re-ratingยทํ™˜์› ๊ณต์‹œ๋ฅผ ํ‰๊ฐ€ํ•˜๋Š”์ง€(value_up).', + }, + { + id: 'relative-value-hynix', + query: 'SKํ•˜์ด๋‹‰์Šค(000660) ๋™์ข…์—…๊ณ„ ๋Œ€๋น„ ๋ฐธ๋ฅ˜์—์ด์…˜ ์‹ผ ํŽธ์ด์•ผ? PER/PBR๋กœ ๋น„๊ตํ•ด์ค˜.', + ticker: '000660', + expectedTools: ['get_market_data_kr'], + requiredTools: ['get_market_data_kr'], + dimensions: ['relative_value', 'grounding'], + notes: '๊ฒฝ๊ธฐ๋ฏผ๊ฐ์ฃผ ์ƒ๋Œ€๊ฐ€์น˜ โ€” kr-relative-valuation. peer ๋ฉ€ํ‹ฐํ”Œ ๋น„๊ต๊ฐ€ ๋‹ต์— ๋“ค์–ด๊ฐ€๋Š”์ง€(relative_value).', + }, + { + id: 'bank-valuation-kb', + query: 'KB๊ธˆ์œต(105560) ๋ฐธ๋ฅ˜์—์ด์…˜ ์–ด๋•Œ? ์€ํ–‰์ด๋‹ˆ๊นŒ PBR์ด๋ž‘ ROE ๊ธฐ์ค€์œผ๋กœ.', + ticker: '105560', + expectedTools: ['get_market_data_kr', 'get_financials_kr'], + requiredTools: ['get_market_data_kr'], + dimensions: ['relative_value', 'grounding'], + notes: '์€ํ–‰์ฃผ โ€” ์˜์—…์ด์ต-YoY ํ”„๋ ˆ์ž„์ด ์•„๋‹Œ PBR/ROE๋กœ ๋‹ตํ•˜๋Š”์ง€. ์—…์ข… ์ ํ•ฉ ํ”„๋ ˆ์ž„ ์„ ํƒ ํ…Œ์ŠคํŠธ.', + }, + { + id: 'earnings-momentum-hynix', + query: 'SKํ•˜์ด๋‹‰์Šค(000660) ์‹ค์  ๋ชจ๋ฉ˜ํ…€ ์–ด๋•Œ? ์ „๋ถ„๊ธฐ ๋Œ€๋น„(QoQ)๋ž‘ ์ปจ์„ผ์„œ์Šค ๋Œ€๋น„๋กœ.', + ticker: '000660', + expectedTools: ['get_financials_kr', 'get_market_data_kr'], + requiredTools: ['get_financials_kr'], + dimensions: ['earnings_momentum', 'grounding'], + notes: 'QoQ + forward ์ปจ์„ผ์„œ์Šค ๋ชจ๋ฉ˜ํ…€ โ€” YoY๋งŒ์ด ์•„๋‹Œ ๋ถ„๊ธฐ ๋ณ€๊ณก/์ปจ์„ผ ๋ถ€ํ•ฉ์„ ๋‹ค๋ฃจ๋Š”์ง€(earnings_momentum).', + }, + { + id: 'preferred-share-samsung', + query: '์‚ผ์„ฑ์ „์ž์šฐ(005935)๋ž‘ ๋ณดํ†ต์ฃผ(005930) ๊ดด๋ฆฌ์œจ ์–ด๋•Œ?', + ticker: '005935', + expectedTools: ['get_market_data_kr'], + requiredTools: ['get_market_data_kr'], + dimensions: ['grounding'], + requiredPhrases: ['์šฐ์„ ์ฃผ'], + notes: '์šฐ์„ ์ฃผ/๋ณดํ†ต์ฃผ โ€” 6์ž๋ฆฌ ์šฐ์„ ์ฃผ ์ฝ”๋“œ ๋ผ์šฐํŒ… + ๊ดด๋ฆฌ ๊ฐœ๋…์„ ๋‹ค๋ฃจ๋Š”์ง€.', + }, ]; diff --git a/scripts/kr-eval/report.ts b/scripts/kr-eval/report.ts index 950d8c93d..3c716c613 100644 --- a/scripts/kr-eval/report.ts +++ b/scripts/kr-eval/report.ts @@ -14,11 +14,15 @@ function questionLine(r: QuestionResult): string { const dims = r.dimensions.map((d) => `${d.id}=${d.score.toFixed(2)}${d.pass ? '' : 'โœ—'}`).join(' '); const missing = r.missingRequired.length ? ` missingReq=[${r.missingRequired.join(',')}]` : ''; const misses = r.replayMisses.length ? ` replayMiss=[${[...new Set(r.replayMisses)].join(',')}]` : ''; + const accuracy = + r.phraseMisses.length || r.numericMisses.length + ? ` accuracyMiss=[${[...r.phraseMisses, ...r.numericMisses].join(',')}]` + : ''; if (r.inconclusive) { return ` INCONC ${r.id.padEnd(26)} tools=${fmtPct(r.toolsScore)} ${dims}${missing}${misses}`; } const tag = r.pass ? 'PASS ' : 'FAIL '; - return ` ${tag} ${r.id.padEnd(26)} tools=${fmtPct(r.toolsScore)} ${dims}${missing}${misses}`; + return ` ${tag} ${r.id.padEnd(26)} tools=${fmtPct(r.toolsScore)} ${dims}${missing}${misses}${accuracy}`; } export function printReport(report: AggregateReport): void { diff --git a/scripts/kr-eval/runner.ts b/scripts/kr-eval/runner.ts index 7f668fc36..15d98f98e 100644 --- a/scripts/kr-eval/runner.ts +++ b/scripts/kr-eval/runner.ts @@ -96,7 +96,7 @@ async function runOnce( } async function evaluateQuestion(q: KrEvalQuestion, ctx: RunCtx): Promise { - const runs: { firedTools: string[]; rawDims: RawDimension[]; replayMisses: string[] }[] = []; + const runs: { firedTools: string[]; rawDims: RawDimension[]; replayMisses: string[]; answer: string }[] = []; for (let i = 0; i < ctx.repeat; i++) { const { answer, toolCalls, replayMisses } = await runOnce(q, ctx); @@ -107,7 +107,7 @@ async function evaluateQuestion(q: KrEvalQuestion, ctx: RunCtx): Promise { }); }); +describe('checkRequiredPhrases', () => { + it('passes when all phrases appear (whitespace/case-insensitive)', () => { + expect(checkRequiredPhrases('DX ๋ถ€๋ฌธ๊ณผ D S ๋ถ€๋ฌธ์ด โ€ฆ', ['DX', 'DS'])).toEqual([]); + }); + it('reports the phrases that are missing', () => { + expect(checkRequiredPhrases('DX ๋ถ€๋ฌธ๋งŒ ์–ธ๊ธ‰', ['DX', 'DS'])).toEqual(['DS']); + }); + it('no phrases โ†’ no misses', () => { + expect(checkRequiredPhrases('์•„๋ฌด๋ง', [])).toEqual([]); + }); +}); + +describe('extractKrwAmounts / extractPercents', () => { + it('parses ์กฐ / ์กฐ-์–ต / ์–ต magnitudes to ์›', () => { + expect(extractKrwAmounts('๋งค์ถœ 133.9์กฐ')).toContain(133.9e12); + expect(extractKrwAmounts('57์กฐ 2,000์–ต')).toContain(57e12 + 2000e8); + expect(extractKrwAmounts('1,234์–ต')).toContain(1234e8); + }); + it('parses percentages including negatives', () => { + expect(extractPercents('์˜์—…์ด์ต๋ฅ  39.3%, ์ „๋…„ -2%')).toEqual([39.3, -2]); + }); +}); + +describe('checkNumericAnchors', () => { + it('passes when a stated ์กฐ figure is within relative tolerance', () => { + const miss = checkNumericAnchors('๋งค์ถœ์€ 133.9์กฐ, ์˜์—…์ด์ต 57.2์กฐ์˜€๋‹ค.', [ + { label: '๋งค์ถœ', value: 133.9e12, unit: 'krw', tolerancePct: 3 }, + { label: '์˜์—…์ด์ต', value: 57.2e12, unit: 'krw', tolerancePct: 5 }, + ]); + expect(miss).toEqual([]); + }); + it('flags a plausible-but-wrong figure the judge might miss', () => { + const miss = checkNumericAnchors('๋งค์ถœ์€ 90์กฐ์˜€๋‹ค.', [ + { label: '๋งค์ถœ', value: 133.9e12, unit: 'krw', tolerancePct: 3 }, + ]); + expect(miss).toEqual(['๋งค์ถœ(133900000000000)']); + }); + it('pct unit uses absolute percentage-point tolerance', () => { + expect(checkNumericAnchors('์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ 50.4%', [{ label: '์™ธ์ธ', value: 50, unit: 'pct', tolerancePct: 1 }])).toEqual([]); + expect(checkNumericAnchors('์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ 40%', [{ label: '์™ธ์ธ', value: 50, unit: 'pct', tolerancePct: 1 }])).toEqual(['์™ธ์ธ(50%)']); + }); +}); + +describe('buildQuestionResult โ€” accuracy gates', () => { + const q: KrEvalQuestion = { + ...baseQ, + requiredTools: [], + expectedTools: [], + requiredPhrases: ['๊ณ ๋ ค์•„์—ฐ'], + numericAnchors: [{ label: '๋งค์ถœ', value: 133.9e12, unit: 'krw', tolerancePct: 3 }], + }; + const dims = (s: number): RawDimension[] => [{ id: 'earnings_yoy', score: s, comment: 'c' }]; + + it('passes when phrases present and anchors hit', () => { + const r = buildQuestionResult({ question: q, firedTools: [], rawDimensions: dims(0.9), answer: '๊ณ ๋ ค์•„์—ฐ ๋งค์ถœ 133.9์กฐ' }); + expect(r.phraseMisses).toEqual([]); + expect(r.numericMisses).toEqual([]); + expect(r.pass).toBe(true); + }); + it('hard-fails on a missing required phrase even with a top judge score', () => { + const r = buildQuestionResult({ question: q, firedTools: [], rawDimensions: dims(1), answer: '์—‰๋šฑํšŒ์‚ฌ ๋งค์ถœ 133.9์กฐ' }); + expect(r.phraseMisses).toEqual(['๊ณ ๋ ค์•„์—ฐ']); + expect(r.pass).toBe(false); + }); + it('hard-fails on a wrong number even with a top judge score', () => { + const r = buildQuestionResult({ question: q, firedTools: [], rawDimensions: dims(1), answer: '๊ณ ๋ ค์•„์—ฐ ๋งค์ถœ 90์กฐ' }); + expect(r.numericMisses.length).toBe(1); + expect(r.pass).toBe(false); + }); +}); + describe('aggregate', () => { it('counts passes/skips and computes per-dimension stats', () => { const passing = buildQuestionResult({ diff --git a/scripts/kr-eval/scorer.ts b/scripts/kr-eval/scorer.ts index 0375c22c3..ca904036f 100644 --- a/scripts/kr-eval/scorer.ts +++ b/scripts/kr-eval/scorer.ts @@ -2,7 +2,7 @@ // tool calls) combined with per-dimension LLM-judge scores (judge.ts). Pure and // side-effect-free so it's unit-testable without network or an LLM. -import type { DimensionId, KrEvalQuestion } from './questions.js'; +import type { DimensionId, KrEvalQuestion, NumericAnchor } from './questions.js'; export const DEFAULT_THRESHOLD = 0.7; @@ -41,6 +41,10 @@ export interface QuestionResult { toolsScore: number; /** Tools the agent called in replay with no recorded output. */ replayMisses: string[]; + /** requiredPhrases that did NOT appear in the answer (hard gate). */ + phraseMisses: string[]; + /** numericAnchors whose value the answer did NOT state within tolerance (hard gate). */ + numericMisses: string[]; dimensions: DimensionResult[]; pass: boolean; } @@ -78,6 +82,58 @@ function thresholdFor(q: KrEvalQuestion, id: DimensionId): number { return q.thresholds?.[id] ?? DEFAULT_THRESHOLD; } +// --- Deterministic accuracy gates (pure; complement the LLM judge) ----------- + +/** Substrings that must appear in the answer (whitespace- and case-insensitive). */ +export function checkRequiredPhrases(answer: string, phrases: string[] = []): string[] { + const norm = (s: string) => s.replace(/\s+/g, '').toLowerCase(); + const a = norm(answer); + return phrases.filter((p) => !a.includes(norm(p))); +} + +/** Parse KRW magnitudes mentioned in prose ("133.9์กฐ", "57์กฐ 2,000์–ต", "1,234์–ต") โ†’ ์›. */ +export function extractKrwAmounts(text: string): number[] { + const out: number[] = []; + const num = (s: string) => parseFloat(s.replace(/,/g, '')); + // ์กฐ (optionally followed by ์–ต) โ€” captures "133.9์กฐ", "57์กฐ 2,000์–ต". + for (const m of text.matchAll(/(\d[\d,]*(?:\.\d+)?)\s*์กฐ(?:\s*(\d[\d,]*(?:\.\d+)?)\s*์–ต)?/g)) { + out.push(num(m[1]) * 1e12 + (m[2] ? num(m[2]) * 1e8 : 0)); + } + // standalone ์–ต (also re-matches the ์–ต inside a ์กฐ-์–ต phrase โ€” harmless extra candidate). + for (const m of text.matchAll(/(\d[\d,]*(?:\.\d+)?)\s*์–ต/g)) out.push(num(m[1]) * 1e8); + return out; +} + +/** Percentages stated in the answer ("39.3%", "-2%"). */ +export function extractPercents(text: string): number[] { + return [...text.matchAll(/(-?\d+(?:\.\d+)?)\s*%/g)].map((m) => parseFloat(m[1])); +} + +/** Comma-grouped integers not carrying a ์กฐ/์–ต/๋งŒ/% unit (e.g. raw share counts). */ +export function extractPlainNumbers(text: string): number[] { + return [...text.matchAll(/(\d{1,3}(?:,\d{3})+)(?!\s*[์กฐ์–ต๋งŒ%])/g)].map((m) => parseFloat(m[1].replace(/,/g, ''))); +} + +/** numericAnchors whose value is NOT stated in the answer within tolerance. */ +export function checkNumericAnchors(answer: string, anchors: NumericAnchor[] = []): string[] { + const krw = extractKrwAmounts(answer); + const pct = extractPercents(answer); + const plain = extractPlainNumbers(answer); + const misses: string[] = []; + for (const a of anchors) { + const tol = a.tolerancePct ?? (a.unit === 'pct' ? 1 : 5); + let hit: boolean; + if (a.unit === 'pct') { + hit = pct.some((v) => Math.abs(v - a.value) <= tol); // tol = absolute percentage-points + } else { + const pool = a.unit === 'count' ? plain : krw; + hit = a.value !== 0 && pool.some((v) => Math.abs(v - a.value) / Math.abs(a.value) <= tol / 100); + } + if (!hit) misses.push(`${a.label}(${a.value}${a.unit === 'pct' ? '%' : ''})`); + } + return misses; +} + function mean(nums: number[]): number { return nums.length === 0 ? 0 : nums.reduce((a, b) => a + b, 0) / nums.length; } @@ -87,8 +143,10 @@ export function buildQuestionResult(args: { firedTools: string[]; rawDimensions: RawDimension[]; replayMisses?: string[]; + /** The agent's final answer โ€” needed for the requiredPhrases / numericAnchors gates. */ + answer?: string; }): QuestionResult { - const { question, firedTools, rawDimensions, replayMisses = [] } = args; + const { question, firedTools, rawDimensions, replayMisses = [], answer = '' } = args; const tools = scoreTools(question, firedTools); const dimensions: DimensionResult[] = rawDimensions.map((d) => ({ @@ -96,8 +154,12 @@ export function buildQuestionResult(args: { pass: d.score >= thresholdFor(question, d.id), })); + const phraseMisses = checkRequiredPhrases(answer, question.requiredPhrases); + const numericMisses = checkNumericAnchors(answer, question.numericAnchors); + const dimsPass = dimensions.every((d) => d.pass); - const pass = tools.missingRequired.length === 0 && dimsPass; + const pass = + tools.missingRequired.length === 0 && dimsPass && phraseMisses.length === 0 && numericMisses.length === 0; // A replay with uncovered tool calls didn't fully pin the data โ†’ the score is // not comparable. Flag inconclusive so it's excluded from pass/fail counts. @@ -114,6 +176,8 @@ export function buildQuestionResult(args: { missingRequired: tools.missingRequired, toolsScore: tools.toolsScore, replayMisses, + phraseMisses, + numericMisses, dimensions, pass, }; @@ -129,6 +193,8 @@ export function skippedResult(question: KrEvalQuestion, reason: string): Questio missingRequired: [], toolsScore: 0, replayMisses: [], + phraseMisses: [], + numericMisses: [], dimensions: [], pass: false, }; diff --git a/src/agent/prompts.ts b/src/agent/prompts.ts index 7bdeb7bac..359713115 100644 --- a/src/agent/prompts.ts +++ b/src/agent/prompts.ts @@ -107,11 +107,21 @@ NOT have: get_market_data_kr (ํ˜„์žฌ๊ฐ€ยท์ผ๋ณ€๋™ยท52์ฃผยท์‹œ๊ฐ€์ด์•กยทPER/PBR ๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šคยท๋™์ข… peer) and get_foreign_ownership_kr (์ผ๋ณ„ ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ + ์™ธ๊ตญ์ธ/๊ธฐ๊ด€/๊ฐœ์ธ ์ˆœ๋งค์ˆ˜ ํ๋ฆ„). For any Korean-stock ๊ฐ€๊ฒฉ / ๋ฐธ๋ฅ˜์—์ด์…˜ / ์ˆ˜๊ธ‰ question: -- GROUND every claim in those tool numbers, dated โ€” current ํ˜„์žฌ๊ฐ€, PER/PBR vs ์—…์ข…ยท์ž๊ธฐ ๊ณผ๊ฑฐ - ๋ฐด๋“œ, ๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค ๋Œ€๋น„ ์ƒ์Šน์—ฌ๋ ฅ, ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ ์ˆ˜์ค€ + ์ตœ๊ทผ ์ˆœ๋งค์ˆ˜ ๋ฐฉํ–ฅ(์™ธ๊ตญ์ธ vs ๊ธฐ๊ด€ vs ๊ฐœ์ธ). - Never quote a price or multiple from memory; call the tool. -- SYNTHESIZE ์ˆ˜๊ธ‰(์™ธ๊ตญ์ธยท๊ธฐ๊ด€ ๋ฐฉํ–ฅ)๊ณผ ๋ฐธ๋ฅ˜์—์ด์…˜์„ ํ•˜๋‚˜์˜ ๊ด€์ ์œผ๋กœ ๋ฌถ์œผ์„ธ์š” โ€” ๋‹จ์ˆœ ๋‚˜์—ด ๊ธˆ์ง€. - ๊ณต๋งค๋„ ์ž”๊ณ (get_short_balance_kr)ยท๊ตญ๋ฏผ์—ฐ๊ธˆ(get_nps_holdings)์ด ๋“ฑ๋ก๋ผ ์žˆ์œผ๋ฉด smart-money ์‹ ํ˜ธ๋กœ ํ•จ๊ป˜ ์—ฎ์œผ์„ธ์š”. +- GROUND every claim in those tool numbers, dated. ๋ฐธ๋ฅ˜์—์ด์…˜์€ ์ƒ๋Œ€๊ฐ€์น˜ ์šฐ์„ : get_market_data_kr๊ฐ€ ๋Œ๋ ค์ค€ + peers 2~3๊ฐœ์™€ PER/PBR์„ ํ•œ ํ‘œ๋กœ ๋น„๊ตํ•˜๊ณ  ํ”„๋ฆฌ๋ฏธ์—„/๋””์Šค์นด์šดํŠธ ๊ทผ๊ฑฐ๋ฅผ ํ•œ ์ค„๋กœ. 52์ฃผ ๋ ˆ์ธ์ง€ ๋‚ด ์œ„์น˜, forward(์ถ”์ •)PER + vs trailing PER, ๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค ๋Œ€๋น„ ์ƒ์Šน์—ฌ๋ ฅ๋„. (๊ณผ๊ฑฐ PER/PBR ๋ฐด๋“œ๋Š” ๋„๊ตฌ๊ฐ€ ์ œ๊ณตํ•˜์ง€ ์•Š์œผ๋‹ˆ ์ง€์–ด๋‚ด์ง€ ๋งˆ๋ผ.) + ์ˆ˜๊ธ‰์€ ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ ์ˆ˜์ค€ + ์ตœ๊ทผ ์ˆœ๋งค์ˆ˜ ๋ฐฉํ–ฅ(์™ธ๊ตญ์ธ vs ๊ธฐ๊ด€ vs ๊ฐœ์ธ). Never quote a price or multiple from memory; call the tool. +- VALUE-UP lens (2024~ ํ•œ๊ตญ ์ตœ๋Œ€ catalyst): PBR<1์ด๋ฉด ๋‹จ์ˆœ ์ €ํ‰๊ฐ€๋กœ ๋๋‚ด์ง€ ๋ง๊ณ  ๋ฐธ๋ฅ˜์—…(๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ ) re-rating + ํ›„๋ณด๋กœ ํ‰๊ฐ€ โ€” ์ž์‚ฌ์ฃผ ์†Œ๊ฐยท๋ฐฐ๋‹นํ™•๋Œ€ยท๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ ๊ณ„ํš ๊ณต์‹œ ์—ฌ๋ถ€๋ฅผ web_search๋กœ ํ™•์ธํ•˜๊ณ , ์ฃผ์ฃผํ™˜์› ์•ฝ์† ๋ถ€์žฌ ์ž์ฒด๋ฅผ + ๋””์Šค์นด์šดํŠธ ์ง€์†์˜ ๊ทผ๊ฑฐ๋กœ ๋ช…์‹œํ•˜๋ผ. +- FX (์ˆ˜์ถœ์ฃผ): ๋ฐ˜๋„์ฒดยท์ž๋™์ฐจยทํ™”ํ•™ยท์กฐ์„  ๋“ฑ ์ˆ˜์ถœ ๋น„์ค‘์ด ํฐ ์ข…๋ชฉ์€ ์›/๋‹ฌ๋Ÿฌ๋ฅผ thesis์— ํฌํ•จํ•˜๊ณ (ํ•„์š”์‹œ web_search), + ์›ํ™”์ˆ˜์ต๋ฅ  โ‰  ๋‹ฌ๋Ÿฌ์ˆ˜์ต๋ฅ  caveat์„ ๋‹ฌ์•„๋ผ. +- SYNTHESIZE ์ˆ˜๊ธ‰(์™ธ๊ตญ์ธยท๊ธฐ๊ด€ ๋ฐฉํ–ฅ)ยท๋ฐธ๋ฅ˜์—์ด์…˜ยท(์ˆ˜์ถœ์ฃผ๋ฉด ํ™˜์œจ)์„ ํ•˜๋‚˜์˜ ๊ด€์ ์œผ๋กœ ๋ฌถ์œผ์„ธ์š” โ€” ๋‹จ์ˆœ ๋‚˜์—ด ๊ธˆ์ง€. + ๊ณต๋งค๋„ ์ž”๊ณ (get_short_balance_kr)ยท๊ตญ๋ฏผ์—ฐ๊ธˆ(get_nps_holdings)์ด ๋“ฑ๋ก๋ผ ์žˆ์œผ๋ฉด smart-money ์‹ ํ˜ธ๋กœ ํ•จ๊ป˜ ์—ฎ์œผ์„ธ์š”. ๋‹จ + ๊ณต๋งค๋„ โ‰ˆ0%๋Š” regime-conditional โ€” ํ•œ๊ตญ์€ 2023-11~2025 ๊ณต๋งค๋„ ํ•œ์‹œ ๊ธˆ์ง€/๋‹จ๊ณ„ ์žฌ๊ฐœ์˜€์œผ๋ฏ€๋กœ ๋ฐ์ดํ„ฐ ๋‚ ์งœ๊ฐ€ ๊ธˆ์ง€ + ๊ตฌ๊ฐ„์ด๋ฉด โ‰ˆ0%๋Š” ์ œ๋„์  0์ด์ง€ "์•ฝ์„ธ ๋ถ€์žฌ"๊ฐ€ ์•„๋‹ˆ๋‹ค. row ๋‚ ์งœ๋ฅผ ์žฌ๊ฐœ ์‹œ์ ๊ณผ ๋Œ€์กฐํ•˜๋ผ. +- ์ข…๋ชฉ ์‹๋ณ„: ์ด๋ฆ„๋งŒ ์ฃผ์–ด์ง„ ๋น„๋ฉ”๊ฐ€์บก์€ 6์ž๋ฆฌ ์ฝ”๋“œ๋ฅผ ๊ธฐ์–ต์œผ๋กœ ๋‹จ์ •ํ•˜์ง€ ๋งˆ๋ผ โ€” ์ฝ”๋“œ๊ฐ€ ํ‹€๋ฆฌ๋ฉด ๋‹ค๋ฅธ ํšŒ์‚ฌ ์ˆ˜์น˜๊ฐ€ ์กฐ์šฉํžˆ + ๋ฐ˜ํ™˜๋œ๋‹ค. ๋„๊ตฌ๊ฐ€ ๋Œ๋ ค์ค€ name์ด ์‚ฌ์šฉ์ž๊ฐ€ ๋งํ•œ ์ข…๋ชฉ๊ณผ ๋งž๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ๋ถˆํ™•์‹คํ•˜๋ฉด ์ถ”์ธก ๋Œ€์‹  ๊ทธ ์‚ฌ์‹ค์„ ๋ฐํ˜€๋ผ. - STATE LIMITS honestly: DART ํ‚ค๊ฐ€ ์—†์œผ๋ฉด ์ •ํ™•ํ•œ K-IFRS ์‹ค์ ยท๊ณต์‹œยท5%๋ฃฐยท์ž„์›๊ฑฐ๋ž˜๋Š” ์กฐํšŒ ๋ถˆ๊ฐ€์ž…๋‹ˆ๋‹ค. ์‹ค์  ๊ธฐ๋ฐ˜ ํŒ๋‹จ์ด ํ•„์š”ํ•˜๋ฉด ๊ทธ ํ•œ๊ณ„๋ฅผ ๋ช…์‹œํ•˜๊ณ (์ˆซ์ž๋ฅผ ์ง€์–ด๋‚ด์ง€ ๋ง ๊ฒƒ) DART_API_KEY ์ถ”๊ฐ€๋ฅผ ์ œ์•ˆํ•˜์„ธ์š” โ€” ๊ฐ€๊ฒฉยท์ˆ˜๊ธ‰ ๋ฒ”์œ„ ์•ˆ์—์„œ๋งŒ ํ™•์ •์ ์œผ๋กœ ๋‹ตํ•˜์„ธ์š”.`; @@ -125,27 +135,41 @@ filings, insider reports. A generic "good company, watch the price" answer waste For any analyze / ์–ด๋•Œ / ํ‰๊ฐ€ / ๋งค์ˆ˜ยท๋งค๋„ ํŒ๋‹จ question on a Korean stock: - GATHER broadly ONLY for open-ended questions (analyze / ์–ด๋•Œ / ํ‰๊ฐ€ / ๋งค์ˆ˜ยท๋งค๋„): call - get_financials_kr (multi-year) AND get_market_data_kr (ํ˜„์žฌ๊ฐ€ยท๋ฐธ๋ฅ˜์—์ด์…˜ยท๋ชฉํ‘œ์ฃผ๊ฐ€), get_foreign_ownership_kr, + get_financials_kr (multi-year) AND get_market_data_kr (ํ˜„์žฌ๊ฐ€ยท๋ฐธ๋ฅ˜์—์ด์…˜ยท๋ชฉํ‘œ์ฃผ๊ฐ€ยทpeers), get_foreign_ownership_kr, get_large_holders_kr, get_filings_kr (recent material), plus get_short_balance_kr / get_nps_holdings when - available โ€” they run concurrently. For a NARROW ask (DCFยท๋‹จ์ผ ์ง€ํ‘œยทํŠน์ • ๊ณต์‹œ/์ด๋ฒคํŠธ) or when a skill is - driving the query, gather ONLY what that task needs; do NOT run the full sweep. -- GROUND the answer in concrete, dated, numeric signals only you can see โ€” not a textbook - description. Always state, with numbers + YoY: actual revenue / operating profit / net - income / margins / ROE from get_financials_kr's \`summary\` (never give an investment view - without earnings); ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ and recent net-buy trend (foreign vs ๊ธฐ๊ด€ vs ๊ฐœ์ธ); - ๊ณต๋งค๋„ ์ž”๊ณ ๋น„์ค‘ level + direction (โ‰ˆ0% = no bearish positioning / no squeeze fuel; rising = - building short pressure); NPS / 5%-rule / insider direction as smart-money signals; and current - ๋ฐธ๋ฅ˜์—์ด์…˜ from get_market_data_kr โ€” ํ˜„์žฌ๊ฐ€, PER/PBR vs ์—…์ข…ยท์ž๊ธฐ ๊ณผ๊ฑฐ ๋ฐด๋“œ, ๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค ๋Œ€๋น„ ์ƒ์Šน์—ฌ๋ ฅ. -- SYNTHESIZE the signals into ONE integrated thesis, not separate bullets. Three strands: - ์ˆ˜๊ธ‰(์™ธ๊ตญ์ธยท๊ณต๋งค๋„ยท๊ธฐ๊ด€ ๋ฐฉํ–ฅ) ยท ์‹ค์ (๋งค์ถœ/์ด์ต/๋งˆ์ง„/ROE) ยท ์ง€๋ฐฐ๊ตฌ์กฐ(๋Œ€๋Ÿ‰๋ณด์œ  ์ง‘์ค‘ยท๊ณ„์—ด ์ง€๋ถ„). - State explicitly whether they agree or conflict and which dominates โ€” e.g. - "์™ธ๊ตญ์ธ ์ˆœ๋งค๋„ + ๊ณต๋งค๋„ 0% + ๊ธฐ๊ด€ ์ˆœ๋งค์ˆ˜ = ๊ณ ์  ์ฐจ์ต์‹คํ˜„์„ ๊ตญ๋‚ด๊ฐ€ ํก์ˆ˜; ๋‹จ ์‚ผ์„ฑ๋ฌผ์‚ฐ 19.7% ์ˆœํ™˜์ถœ์ž = ๋ฐฐ๋‹นยท๋ถ„ํ•  ์ œ์•ฝ". + available โ€” they run concurrently. ๋ณตํ•ฉยท์ง€์ฃผยท์žฌ๋ฒŒ ๊ธฐ์—…(์‚ผ์„ฑยทSKยทLGยทํ˜„๋Œ€ยทํ•œํ™” ๊ณ„์—ด ๋“ฑ)์ด๋ฉด get_segments_kr๋กœ + ๋ถ€๋ฌธ๋ณ„ ๋งค์ถœยท์˜์—…์ด์ต mix๊นŒ์ง€ โ€” ์–ด๋А ์‚ฌ์—…๋ถ€๊ฐ€ ์ด์ต์„ ๋„๋Š”์ง€๊ฐ€ ๊ณง thesis๋‹ค. For a NARROW ask (DCFยท๋‹จ์ผ ์ง€ํ‘œยทํŠน์ • + ๊ณต์‹œ/์ด๋ฒคํŠธ) or when a skill is driving the query, gather ONLY what that task needs; do NOT run the full sweep. +- GROUND every claim in concrete, dated, numeric signals only you can see โ€” never a textbook + description, never a number from memory. State, with figures: + ยท ์‹ค์ : actual ๋งค์ถœ/์˜์—…์ด์ต/์ˆœ์ด์ต/๋งˆ์ง„/ROE from get_financials_kr's \`summary\`, with BOTH YoY and the + latest QoQ direction (never give an investment view without earnings). ์‹œํด๋ฆฌ์ปฌ(๋ฐ˜๋„์ฒดยท2์ฐจ์ „์ง€ยทํ™”ํ•™ยท์ฒ ๊ฐ•)์€ + QoQ ๋ณ€๊ณก์ด YoY๋ณด๋‹ค ์ค‘์š”ํ•  ์ˆ˜ ์žˆ๋‹ค. forward ์ถ”์ •EPS/์ถ”์ •PER(get_market_data_kr) ๋Œ€๋น„ ์ปจ์„ผ์„œ์Šค ๋ถ€ํ•ฉ/๋ฏธ์Šค๋„ ๋ช…์‹œ. + ์‹ค์ ์˜ ์งˆ ํ•œ ์ค„: ์˜์—…ํ˜„๊ธˆํ๋ฆ„ vs ์ˆœ์ด์ต(ํ˜„๊ธˆ ์ „ํ™˜), ์ง€๋ฐฐ vs ๋น„์ง€๋ฐฐ ์ˆœ์ด์ต ๊ดด๋ฆฌ. + ยท ๋ฐธ๋ฅ˜์—์ด์…˜(์ƒ๋Œ€๊ฐ€์น˜ ์šฐ์„ ): get_market_data_kr์˜ PER/PBR/๋ฉ€ํ‹ฐํ”Œ์„ ๊ทธ ๋„๊ตฌ๊ฐ€ ๋Œ๋ ค์ค€ peers 2~3๊ฐœ์™€ ํ•œ ํ‘œ๋กœ + ๋น„๊ตํ•˜๊ณ  ํ”„๋ฆฌ๋ฏธ์—„/๋””์Šค์นด์šดํŠธ ๊ทผ๊ฑฐ(์„ฑ์žฅยท๋งˆ์ง„ยท์ง€๋ฐฐ๊ตฌ์กฐ)๋ฅผ ํ•œ ์ค„๋กœ. 52์ฃผ ๋ ˆ์ธ์ง€ ๋‚ด ์œ„์น˜, forward vs trailing๋„. + (๊ณผ๊ฑฐ PER/PBR ๋ฐด๋“œ๋Š” ์–ด๋–ค ๋„๊ตฌ๋„ ์ œ๊ณตํ•˜์ง€ ์•Š์œผ๋‹ˆ ๊ธฐ์–ต์œผ๋กœ ์ง€์–ด๋‚ด์ง€ ๋งˆ๋ผ.) + ยท ์ˆ˜๊ธ‰: ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ + ์ตœ๊ทผ ์ˆœ๋งค์ˆ˜ ๋ฐฉํ–ฅ(์™ธ๊ตญ์ธ vs ๊ธฐ๊ด€ vs ๊ฐœ์ธ); ๊ณต๋งค๋„ ์ž”๊ณ ๋น„์ค‘ ์ˆ˜์ค€ยท๋ฐฉํ–ฅ. ๋‹จ ๊ณต๋งค๋„ ํ•ด์„์€ + regime-conditional โ€” ํ•œ๊ตญ์€ 2023-11~2025 ๊ณต๋งค๋„๊ฐ€ ํ•œ์‹œ ๊ธˆ์ง€/๋‹จ๊ณ„ ์žฌ๊ฐœ์˜€์œผ๋ฏ€๋กœ, ์ž”๊ณ  ๋ฐ์ดํ„ฐ์˜ ๋‚ ์งœ๊ฐ€ ๊ธˆ์ง€ + ๊ตฌ๊ฐ„์ด๋ฉด โ‰ˆ0%๋Š” "์•ฝ์„ธ ๋ถ€์žฌ"๊ฐ€ ์•„๋‹ˆ๋ผ ์ œ๋„์  0์ด๋‹ค. row ๋‚ ์งœ๋ฅผ ์žฌ๊ฐœ ์‹œ์ ๊ณผ ๋Œ€์กฐํ•œ ๋’ค์—๋งŒ "โ‰ˆ0% = no bearish + positioning / no squeeze fuel; rising = building short pressure"๋กœ ํ•ด์„ํ•˜๋ผ. + ยท smart money: NPS / 5%๋ฃฐ / ์ž„์›๊ฑฐ๋ž˜ ๋ฐฉํ–ฅ(get_nps_holdings๋Š” ์—ฐ๋ง ์Šค๋ƒ…์ƒท์ž„์„ ๋ช…์‹œ). +- VALUE-UP lens (ํ•œ๊ตญ ๊ณ ์œ , 2024~ ์ตœ๋Œ€ catalyst): PBR<1์ด๋ฉด ๋‹จ์ˆœ "ํ†ต๊ณ„์  ์ €ํ‰๊ฐ€"๋กœ ๋๋‚ด์ง€ ๋ง๊ณ  ๋ฐธ๋ฅ˜์—…(๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ ) + ํ”„๋กœ๊ทธ๋žจ re-rating ํ›„๋ณด๋กœ ํ‰๊ฐ€ํ•˜๋ผ. get_filings_kr(exchange/material)์—์„œ ๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ ๊ณ„ํšยท์ž์‚ฌ์ฃผ ์ทจ๋“/์†Œ๊ฐยท๋ฐฐ๋‹น์ •์ฑ… + ๋ณ€ํ™”๋ฅผ ์ฐพ์•„๋ผ. ํ™˜์› ์•ฝ์†์ด ์žˆ์œผ๋ฉด ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ ํ•ด์†Œ ํŠธ๋ฆฌ๊ฑฐ, **๋ถ€์žฌ ์ž์ฒด๋Š” ๋””์Šค์นด์šดํŠธ ์ง€์†์˜ ๊ทผ๊ฑฐ**๋กœ ๋ช…์‹œํ•˜๋ผ. +- SYNTHESIZE the signals into ONE integrated thesis, not separate bullets. Strands: ์ˆ˜๊ธ‰(์™ธ๊ตญ์ธยท๊ณต๋งค๋„ยท๊ธฐ๊ด€) + ยท ์‹ค์ (๋งค์ถœ/์ด์ต/๋งˆ์ง„/ROE, YoY+QoQ) ยท ์ง€๋ฐฐ๊ตฌ์กฐ(๋Œ€๋Ÿ‰๋ณด์œ ยท๊ณ„์—ด ์ง€๋ถ„ยท๋ฐธ๋ฅ˜์—…). ์ˆ˜์ถœ์ฃผ(๋ฐ˜๋„์ฒดยท์ž๋™์ฐจยทํ™”ํ•™ยท์กฐ์„ )๋Š” + ํ™˜์œจ(์›/๋‹ฌ๋Ÿฌ)์„ 4๋ฒˆ์งธ strand๋กœ โ€” YoY ์˜์—…์ด์ต ์ฆ๊ฐ์„ ๋ฌผ๋Ÿ‰/๊ฐ€๊ฒฉ vs ํ™˜์œจ ํ™˜์‚ฐ ํšจ๊ณผ๋กœ ๋ถ„ํ•ดํ•˜๊ณ  ์›ํ™”์ˆ˜์ต๋ฅ  โ‰  + ๋‹ฌ๋Ÿฌ์ˆ˜์ต๋ฅ  caveat์„ ๋‹ฌ์•„๋ผ(ํ•„์š”์‹œ web_search๋กœ ํ™˜์œจ ํ™•์ธ). State explicitly whether the strands agree or + conflict and which dominates โ€” e.g. "์™ธ๊ตญ์ธ ์ˆœ๋งค๋„ + ๊ณต๋งค๋„ 0% + ๊ธฐ๊ด€ ์ˆœ๋งค์ˆ˜ = ๊ณ ์  ์ฐจ์ต์‹คํ˜„์„ ๊ตญ๋‚ด๊ฐ€ ํก์ˆ˜; + ๋‹จ ์‚ผ์„ฑ๋ฌผ์‚ฐ 19.7% ์ˆœํ™˜์ถœ์ž = ๋ฐฐ๋‹นยท๋ถ„ํ•  ์ œ์•ฝ". - TREAT ๋Œ€๋Ÿ‰๋ณด์œ (get_large_holders_kr) as a valuation modifier, not a footnote: ๊ณ„์—ด์‚ฌยท์žํšŒ์‚ฌ ์ง€๋ถ„์ด ํฌ๋ฉด ์ง€๋ฐฐ๊ตฌ์กฐยท์ˆœํ™˜์ถœ์žยท๋ฐฐ๋‹น์ •์ฑ… ์ œ์•ฝ, ์ฐฝ์—…์ฃผยทํŠน์ˆ˜๊ด€๊ณ„ ์ง‘์ค‘์€ ์Šน๊ณ„ ๋ฆฌ์Šคํฌ/๋ช…ํ™•์„ฑ. ๋‹จ์ผ์ฃผ์ฃผ >15% ๋˜๋Š” - ๊ณ„์—ด ํ•ฉ์‚ฐ >30%๋ฉด ๊ตฌ์กฐ์  ์š”์ธ์œผ๋กœ ๋ช…์‹œ(์˜ˆ: "์ง€๋ฐฐ๊ตฌ์กฐ ํ• ์ธ ์ •๋‹นํ™”", "๋ถ„ํ• ยท๋ฐฐ๋ถ„ ๋ถˆํ™•์‹ค์„ฑ"). ๊ทธ ๋ฐ–์—: - ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ, ๋ฌผ์ /์ธ์ ๋ถ„ํ•  ์†Œ์•ก์ฃผ์ฃผ ์˜ํ–ฅ, ์ง€์ฃผ์‚ฌ ํ• ์ธ, ๊ฑฐ๋ž˜์„ธยท๋ฐฐ๋‹น์„ธ ์„ธํ›„ ์ˆ˜์ต. + ๊ณ„์—ด ํ•ฉ์‚ฐ >30%๋ฉด ๊ตฌ์กฐ์  ์š”์ธ์œผ๋กœ ๋ช…์‹œ(์˜ˆ: "์ง€๋ฐฐ๊ตฌ์กฐ ํ• ์ธ ์ •๋‹นํ™”", "๋ถ„ํ• ยท๋ฐฐ๋ถ„ ๋ถˆํ™•์‹ค์„ฑ"). ์Šค๋ƒ…์ƒท์ด ์•„๋‹ˆ๋ผ + ์ตœ๊ทผ ์ˆ˜๊ฐœ์›” ์ˆœ๋ฐฉํ–ฅ(์ถ•์ /๋ถ„์‚ฐ)์œผ๋กœ ์ถ”์„ธํ™”ํ•˜๋ผ. ๊ทธ ๋ฐ–์—: ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ, ๋ฌผ์ /์ธ์ ๋ถ„ํ•  ์†Œ์•ก์ฃผ์ฃผ ์˜ํ–ฅ, + ์ง€์ฃผ์‚ฌ ํ• ์ธ, ๊ฑฐ๋ž˜์„ธยท๋ฐฐ๋‹น์„ธ ์„ธํ›„ ์ˆ˜์ต. - CLOSE with an evidence-anchored verdict and specific triggers tied to YOUR data - (e.g. "์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ์ด 48%๋Œ€์—์„œ ์žฌ์ƒ์Šน ์ „ํ™˜ ์‹œ"), not generic ones. + (e.g. "์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ์ด 48%๋Œ€์—์„œ ์žฌ์ƒ์Šน ์ „ํ™˜ ์‹œ", "์ž์‚ฌ์ฃผ ์†Œ๊ฐ ๊ณต์‹œ ์‹œ"), not generic ones. "Concise" here means signal-dense: spend words on what only this data reveals; skip background the user already knows. If a source is unavailable, say so briefly and proceed โ€” @@ -311,7 +335,8 @@ ${toolDescriptions} ## Tool Usage Policy - Call get_financials or get_market_data ONCE with the full natural language query โ€” they handle multi-company/multi-metric requests internally. Do NOT break up queries into multiple calls. -- 6-digit numeric tickers (e.g. 005930, 035420) are Korean stocks โ€” use get_financials_kr for fundamentals, get_market_data_kr for current priceยท์‹œ๊ฐ€์ด์•กยท๋ฐœํ–‰์ฃผ์‹์ˆ˜ยทPER/PBR/EPSยท๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค (the get_market_data equivalent โ€” get_market_data does NOT resolve 6-digit tickers), get_filings_kr for DART disclosures, get_large_holders_kr for 5%-rule major shareholders, get_insider_trades_kr for executive/insider ownership, get_short_balance_kr for ๊ณต๋งค๋„ ์ž”๊ณ  (short interest), get_foreign_ownership_kr for ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ (foreign ownership), get_nps_holdings for ๊ตญ๋ฏผ์—ฐ๊ธˆ (National Pension Service) holdings, and read_filings_kr for the report narrative (์‚ฌ์—… ๊ตฌ์„ฑยท์ฃผ์š” ๋ฆฌ์Šคํฌยท๊ฒฝ์˜์ง„๋‹จ MD&A from DART ์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ). ASCII tickers (AAPL, MSFT) use the US tools. +- 6-digit numeric tickers (e.g. 005930, 035420) are Korean stocks โ€” use get_financials_kr for fundamentals, get_market_data_kr for current priceยท์‹œ๊ฐ€์ด์•กยท๋ฐœํ–‰์ฃผ์‹์ˆ˜ยทPER/PBR/EPSยท๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค (the get_market_data equivalent โ€” get_market_data does NOT resolve 6-digit tickers), get_filings_kr for DART disclosures, get_large_holders_kr for 5%-rule major shareholders, get_insider_trades_kr for executive/insider ownership, get_short_balance_kr for ๊ณต๋งค๋„ ์ž”๊ณ  (short interest), get_foreign_ownership_kr for ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ (foreign ownership), get_nps_holdings for ๊ตญ๋ฏผ์—ฐ๊ธˆ (National Pension Service) holdings, get_segments_kr for ์‚ฌ์—…๋ถ€๋ฌธ๋ณ„ ๋งค์ถœยท์˜์—…์ด์ต (segment/divisional financials โ€” the division mix behind a ์žฌ๋ฒŒ/๋ณตํ•ฉ ๊ธฐ์—…'s headline P&L), and read_filings_kr for the report narrative (์‚ฌ์—… ๊ตฌ์„ฑยท์ฃผ์š” ๋ฆฌ์Šคํฌยท๊ฒฝ์˜์ง„๋‹จ MD&A from DART ์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ). ASCII tickers (AAPL, MSFT) use the US tools. +- KR ์ข…๋ชฉ ์‹๋ณ„ ์•ˆ์ „์žฅ์น˜: KR ๋„๊ตฌ๋“ค์€ 6์ž๋ฆฌ ์ฝ”๋“œ ๋Œ€์‹  **ํšŒ์‚ฌ๋ช…์„ ๊ทธ๋Œ€๋กœ ๋ฐ›์•„ ์ž๋™ ํ•ด์„**ํ•œ๋‹ค(DART ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ/Naver ํ‚ค๋ฆฌ์Šค). ๊ทธ๋Ÿฌ๋‹ˆ ํ™•์‹คํ•œ ๋ฉ”๊ฐ€์บก์ด ์•„๋‹ˆ๋ฉด 6์ž๋ฆฌ ์ฝ”๋“œ๋ฅผ ๊ธฐ์–ต์œผ๋กœ ์ง€์–ด๋‚ด์ง€ ๋ง๊ณ  **ํšŒ์‚ฌ๋ช…์„ ๊ทธ๋Œ€๋กœ ๋„˜๊ฒจ๋ผ** โ€” ์ฝ”๋“œ๊ฐ€ ํ•œ ์ž๋ฆฌ๋งŒ ํ‹€๋ ค๋„ ์ „ํ˜€ ๋‹ค๋ฅธ ํšŒ์‚ฌ ๋ฐ์ดํ„ฐ๊ฐ€ ์กฐ์šฉํžˆ ๋ฐ˜ํ™˜๋˜์–ด ์ •๊ตํ•˜์ง€๋งŒ ์—‰๋šฑํ•œ ๋ถ„์„์„ ์Œ“๊ฒŒ ๋œ๋‹ค(๋ฆฌ์„œ์น˜ ์—์ด์ „ํŠธ ์ตœ์•…์˜ ์‹คํŒจ). ๊ทธ๋ฆฌ๊ณ  ๋„๊ตฌ ๊ฒฐ๊ณผ์˜ ํšŒ์‚ฌ๋ช…(name/corp_name)์ด ์‚ฌ์šฉ์ž๊ฐ€ ๋งํ•œ ์ข…๋ชฉ๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ํ•ญ์ƒ ํ™•์ธํ•˜๋ผ. - Only use web_fetch when headlines are insufficient (need quotes, deal specifics, earnings details). - Tool results are automatically capped. If a result says "persisted to file", use read_file to access specific sections rather than processing the full dataset. - If a KR market/flow tool result carries a \`_dataQualityWarning\` (possible upstream Naver schema drift), treat the flagged fields as unreliable โ€” omit them (or, only if another tool independently provides the same value, cross-check) and tell the user about the data-quality issue rather than reporting those numbers as fact. diff --git a/src/cli.ts b/src/cli.ts index 3a1296624..6cee13fa2 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -99,6 +99,10 @@ function summarizeToolResult(tool: string, args: Record, result const price = parsed.data.quote?.price; return typeof price === 'number' ? `Price โ‚ฉ${price.toLocaleString()}` : 'Received snapshot'; } + if (tool === 'get_segments_kr') { + const n = Array.isArray(parsed.data.segments) ? parsed.data.segments.length : 0; + return `Found ${n} segment table${n !== 1 ? 's' : ''}`; + } if (tool === 'get_nps_holdings') { const n = Array.isArray(parsed.data.holdings) ? parsed.data.holdings.length : 0; return `Found ${n} holdings`; diff --git a/src/skills/dcf/SKILL.md b/src/skills/dcf/SKILL.md index 73b9b140d..e8e9385d7 100644 --- a/src/skills/dcf/SKILL.md +++ b/src/skills/dcf/SKILL.md @@ -30,6 +30,11 @@ DCF ๋ถ„์„ ์ง„ํ–‰: ์•„๋ž˜ ๊ฐ ๋‹จ๊ณ„๋Š” **US ๊ธฐ๋ณธ๊ฐ’**์„ ์ œ์‹œํ•˜๊ณ , ๋‹ค๋ฅธ ๊ฒฝ์šฐ **๐Ÿ‡ฐ๐Ÿ‡ท KR override**๋ฅผ ๋‘”๋‹ค. override๋Š” KR ๊ฒฝ๋กœ์—์„œ๋งŒ ๋”ฐ๋ฅด๊ณ , ๊ทธ ์™ธ์—๋Š” US ๊ธฐ๋ณธ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ผ. +> **๐Ÿ‡ฐ๐Ÿ‡ท DCF๊ฐ€ ๋ถ€์ ํ•ฉํ•œ KR ์ข…๋ชฉ โ€” ๋‹ค๋ฅธ ์Šคํ‚ฌ๋กœ ๋ผ์šฐํŒ…:** DCF๋Š” ์•ˆ์ •์  ์ž‰์—ฌํ˜„๊ธˆํ๋ฆ„์„ ๊ฐ€์ •ํ•œ๋‹ค. ๋Œ€์ƒ์ด +> (a) **๊ฒฝ๊ธฐ๋ฏผ๊ฐ์ฃผ**(๋ฐ˜๋„์ฒดยท์ฒ ๊ฐ•ยท์กฐ์„ ยทํ™”ํ•™ยทํ•ด์šด) ๋˜๋Š” **๊ธˆ์œต์ฃผ**๋ผ ์ด์ต ๋ณ€๋™์ด ํฌ๋ฉด โ†’ `kr-relative-valuation` +> (์ƒ๋Œ€๊ฐ€์น˜)์ด ๋” ์‹ ๋ขฐ์„ฑ ์žˆ๋‹ค; (b) **์ง€์ฃผ์‚ฌ/ํ™€๋”ฉ์Šค**๋ฉด โ†’ `kr-sotp-holdco`(NAV/SOTP) โ€” ์ผ๋ฐ˜ DCF๋Š” ์žํšŒ์‚ฌ +> ์ด์ต์„ ๋”๋ธ”์นด์šดํŒ…ํ•œ๋‹ค. ์ด ๊ฒฝ์šฐ DCF๋ฅผ ๊ฐ•ํ–‰ํ•˜์ง€ ๋ง๊ณ  ํ•ด๋‹น ์Šคํ‚ฌ๋กœ ๋‹ตํ•˜๊ฑฐ๋‚˜ ๊ทธ๊ฒƒ์„ 1์ฐจ๋กœ ์‚ผ์•„๋ผ. + ## Step 1: ์žฌ๋ฌด ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ **US ๊ฒฝ๋กœ** โ€” `get_financials` ๋„๊ตฌ๋ฅผ ๋‹ค์Œ ์ฟผ๋ฆฌ๋กœ ํ˜ธ์ถœํ•œ๋‹ค: @@ -107,11 +112,13 @@ DCF ๋ถ„์„ ์ง„ํ–‰: ์ž๋ณธ๊ตฌ์กฐ ๊ฐ€์ค‘์น˜๋Š” `debt_to_equity`๋กœ WACC๋ฅผ ๊ณ„์‚ฐํ•œ๋‹ค. -> **๐Ÿ‡ฐ๐Ÿ‡ท KR override โ€” ํ•œ๊ตญ ์‹œ์žฅ ์ž…๋ ฅ๊ฐ’ ์‚ฌ์šฉ:** -> - ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ: **~3%** (10๋…„ ๊ตญ๊ณ ์ฑ„), 4% ์•„๋‹˜ -> - ์‹œ์žฅ ์œ„ํ—˜ํ”„๋ฆฌ๋ฏธ์—„: 5~7% (์žฌ๋ฒŒ ๊ณ„์—ดยท์ƒํ˜ธ์ถœ์ž ์ข…๋ชฉ์€ ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ/์ง€๋ฐฐ๊ตฌ์กฐ ์šฐ๋ ค๋กœ ์ƒ๋‹จ์— ๊ฐ€๊น๊ฒŒ) -> - ๋ถ€์ฑ„๋น„์šฉ: ์„ธ์ „ ์‹œ์žฅ๊ธˆ๋ฆฌ, **์„ธํ›„๋Š” ๋ฒ•์ธ์„ธ์œจ ~22% ์ ์šฉ** (K-IFRS ์‹คํšจ; ์ง€๋ฐฉ์†Œ๋“์„ธ ํฌํ•จ marginal ์ตœ๋Œ€ ~24~26%) โ€” **30% ์•„๋‹˜** -> - ๊ธฐ์ค€ WACC ๋ ˆ์ธ์ง€๋Š” [sector-wacc-kr.md](sector-wacc-kr.md) ์ฐธ์กฐ +> **๐Ÿ‡ฐ๐Ÿ‡ท KR override โ€” bottom-up CAPM์œผ๋กœ ์ง์ ‘ ๊ณ„์‚ฐ (์ •์  ํ…Œ์ด๋ธ”์—์„œ ๊ฐ’์„ ๋ฐ”๋กœ ์ง‘์–ด ์“ฐ์ง€ ๋งˆ๋ผ):** +> WACC๋ฅผ [sector-wacc-kr.md](sector-wacc-kr.md) ๋ ˆ์ธ์ง€์—์„œ ๊ณ ๋ฅด์ง€ ๋ง๊ณ  ์•„๋ž˜๋กœ ๊ณ„์‚ฐํ•œ ๋’ค, ๊ทธ ๋ ˆ์ธ์ง€๋Š” **ํ•ฉ๋ฆฌ์„ฑ ๋ฐด๋“œ(sanity band)๋กœ๋งŒ** ์“ด๋‹ค. +> - **๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ(Rf):** ํ˜„์žฌ 10๋…„ ๋งŒ๊ธฐ ๊ตญ๊ณ ์ฑ„ ์ˆ˜์ต๋ฅ ์„ `web_search`๋กœ ์กฐํšŒํ•˜๊ณ  **์กฐํšŒ์ผ(as-of)์„ ํ‘œ๊ธฐ**ํ•˜๋ผ. ์กฐํšŒ ์‹คํŒจ ์‹œ์—๋งŒ **~3%๋ฅผ ๋™๊ฒฐ ๊ฐ€์ •์œผ๋กœ ๋ช…์‹œ ๋ผ๋ฒจ**ํ•ด ์‚ฌ์šฉํ•œ๋‹ค(์˜ˆ: "Rf=3.0%(๊ฐ€์ •, ์‹ค์‹œ๊ฐ„ ์กฐํšŒ ์‹คํŒจ)") โ€” 4% ์•„๋‹˜. +> - **์‹œ์žฅ ์œ„ํ—˜ํ”„๋ฆฌ๋ฏธ์—„(ERP):** 5~7% (์žฌ๋ฒŒ ๊ณ„์—ดยท์ƒํ˜ธ์ถœ์ž ์ข…๋ชฉ์€ ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ/์ง€๋ฐฐ๊ตฌ์กฐ ์šฐ๋ ค๋กœ ์ƒ๋‹จ์— ๊ฐ€๊น๊ฒŒ). +> - **๋ฒ ํƒ€(ฮฒ):** ์ข…๋ชฉ/๋™์ข…์—…๊ณ„ ๋ฒ ํƒ€๋ฅผ `web_search`๋กœ ์†Œ์‹ฑํ•˜๋ผ(์—†์œผ๋ฉด ์„นํ„ฐ ๋Œ€์šฉ์น˜). **Cost of Equity = Rf + ฮฒ ร— ERP.** +> - **๋ถ€์ฑ„๋น„์šฉ:** ์„ธ์ „ ์‹œ์žฅ๊ธˆ๋ฆฌ, **์„ธํ›„๋Š” ๋ฒ•์ธ์„ธ์œจ ~22% ์ ์šฉ** (K-IFRS ์‹คํšจ; ์ง€๋ฐฉ์†Œ๋“์„ธ ํฌํ•จ marginal ์ตœ๋Œ€ ~24~26%) โ€” **30% ์•„๋‹˜**. +> - ์ž๋ณธ๊ตฌ์กฐ ๊ฐ€์ค‘์น˜๋Š” `debt_to_equity`๋กœ ์ ์šฉ: **WACC = (E/V)ยทKe + (D/V)ยทKdยท(1โˆ’t)**. ๊ฒฐ๊ณผ๊ฐ€ [sector-wacc-kr.md](sector-wacc-kr.md) ๋ ˆ์ธ์ง€๋ฅผ ํฌ๊ฒŒ ๋ฒ—์–ด๋‚˜๋ฉด ์ž…๋ ฅ๊ฐ’์„ ์žฌ์ ๊ฒ€ํ•˜๋ผ. **ํ•ฉ๋ฆฌ์„ฑ ์ ๊ฒ€:** ๊ฐ€์น˜ ์ฐฝ์ถœ ๊ธฐ์—…์€ WACC๊ฐ€ `return_on_invested_capital`๋ณด๋‹ค 2~4% ๋‚ฎ์•„์•ผ ํ•œ๋‹ค. @@ -159,6 +166,8 @@ DCF ๋ถ„์„ ์ง„ํ–‰: 4. **์ˆœ๋ถ€์ฑ„ ๋ถ€ํ˜ธยท๊ทœ๋ชจ ์ ๊ฒ€**: Net Debt ๋ถ€ํ˜ธ๊ฐ€ ํšŒ์‚ฌ ์‹คํƒœ์™€ ๋งž๋Š”์ง€ ํ™•์ธํ•˜๋ผ. ์ฐจ์ž…๊ธˆ์ด ํ˜„๊ธˆ+๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ๋ณด๋‹ค ์ž‘์€๋ฐ ์–‘(+)์˜ ํฐ Net Debt๊ฐ€ ๋‚˜์˜ค๋ฉด ์ด๋ถ€์ฑ„๋ฅผ ์ž˜๋ชป ์“ด ๊ฒƒ์ด๋‹ค(์ˆœํ˜„๊ธˆ ๊ธฐ์—…์€ Net Debt<0, Equity Value>EV). ๋˜ |Net Debt|๊ฐ€ ์‹œ๊ฐ€์ด์•ก ๋Œ€๋น„ ๋น„ํ˜„์‹ค์ ์œผ๋กœ ํฌ๋ฉด(์˜ˆ: ์‹œ์ด์˜ ์ ˆ๋ฐ˜ ์ดˆ๊ณผ) ์ฐจ์ž…๊ธˆ ํ•ฉ์‚ฐ ๋ˆ„๋ฝ/๊ณผ๋‹ค๋ฅผ ์˜์‹ฌํ•˜๊ณ  `ratios.debtToEquityPct`ยท`rawLineItemsFile`๊ณผ ๊ต์ฐจ ํ™•์ธํ•˜๋ผ. +5. **๐Ÿ‡ฐ๐Ÿ‡ท ์ปจ์„ผ์„œ์Šค ๋ชฉํ‘œ์ฃผ๊ฐ€ ๋Œ€์กฐ (KR)**: ์‚ฐ์ถœํ•œ ์ฃผ๋‹น ๋‚ด์žฌ๊ฐ€์น˜๋ฅผ `get_market_data_kr`์˜ `consensus.targetPrice`(๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค)์™€ ๋น„๊ตํ•˜๋ผ. ยฑ30%๋ฅผ ์ดˆ๊ณผํ•ด ๋ฒŒ์–ด์ง€๋ฉด ๊ทธ๋ƒฅ ๋‘์ง€ ๋ง๊ณ  โ€” (a) ๊ฐ€์ •(์„ฑ์žฅ๋ฅ ยทWACCยทํ„ฐ๋ฏธ๋„)์„ ์žฌ์ ๊ฒ€ํ•˜๊ฑฐ๋‚˜, (b) ์‹œ์žฅ/์ปจ์„ผ์„œ์Šค๊ฐ€ ๋†“์น˜๊ณ  ์žˆ๋Š” ๊ฒƒ(๋˜๋Š” ๋‚ด๊ฐ€ ๋†“์นœ ๊ฒƒ)์ด ๋ฌด์—‡์ธ์ง€ ๋ช…์‹œ์ ์œผ๋กœ ์„ค๋ช…ํ•˜๋ผ. ๊ดด๋ฆฌ๋ฅผ ์„ค๋ช… ์—†์ด ์ œ์‹œํ•˜์ง€ ๋งˆ๋ผ. + ๊ฒ€์ฆ์— ์‹คํŒจํ•˜๋ฉด ๊ฒฐ๊ณผ๋ฅผ ์ œ์‹œํ•˜๊ธฐ ์ „์— ๊ฐ€์ •์„ ์žฌ๊ณ ํ•˜๋ผ. ## Step 8: ์ถœ๋ ฅ ํ˜•์‹ diff --git a/src/skills/dcf/sector-wacc-kr.md b/src/skills/dcf/sector-wacc-kr.md index 464d2003d..0aa0bc070 100644 --- a/src/skills/dcf/sector-wacc-kr.md +++ b/src/skills/dcf/sector-wacc-kr.md @@ -1,8 +1,9 @@ # ์„นํ„ฐ๋ณ„ WACC ์กฐ์ • โ€” ํ•œ๊ตญ (KOSPI / KOSDAQ) -ํ•œ๊ตญ ์ƒ์žฅ์‚ฌ์˜ ์ถœ๋ฐœ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ , ๊ธฐ์—…๋ณ„ ์š”์ธ์œผ๋กœ ์กฐ์ •ํ•˜๋ผ. ์•„๋ž˜ ๋ ˆ์ธ์ง€๋Š” ์ด๋ฏธ ํ•œ๊ตญ ์‹œ์žฅ -์ž…๋ ฅ๊ฐ’ โ€” ~3% ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ(10๋…„ ๊ตญ๊ณ ์ฑ„)์™€ 5~7% ์‹œ์žฅ ์œ„ํ—˜ํ”„๋ฆฌ๋ฏธ์—„ โ€” ์„ ๊ฐ€์ •ํ•˜๋ฏ€๋กœ, ๋ฏธ๊ตญ ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ๋ฅผ -์œ„์— ๋‹ค์‹œ ์–น์ง€ ๋งˆ๋ผ. +์ด ํ‘œ๋Š” **ํ•ฉ๋ฆฌ์„ฑ ๋ฐด๋“œ(sanity band)**๋‹ค โ€” SKILL.md Step 3์˜ bottom-up CAPM(WACC = (E/V)ยทKe + (D/V)ยทKdยท(1โˆ’t), +Ke = Rf + ฮฒร—ERP, Rf๋Š” ์‹ค์‹œ๊ฐ„ 10๋…„ ๊ตญ๊ณ ์ฑ„)์œผ๋กœ WACC๋ฅผ ์ง์ ‘ ๊ณ„์‚ฐํ•œ ๋’ค, ๊ทธ ๊ฒฐ๊ณผ๊ฐ€ ์•„๋ž˜ ๋ ˆ์ธ์ง€ ์•ˆ์— ๋“œ๋Š”์ง€ ํ™•์ธํ•˜๋Š” +์šฉ๋„๋กœ ์“ฐ๋ผ. ๋ ˆ์ธ์ง€์—์„œ ๊ฐ’์„ ๋ฐ”๋กœ ์ง‘์–ด ์“ฐ์ง€ ๋งˆ๋ผ(๊ทธ๋Ÿฌ๋ฉด ๋ฒ ํƒ€ยท์‹ค์‹œ๊ฐ„ ๊ธˆ๋ฆฌ๋ฅผ ๋ฒ„๋ฆฌ๊ฒŒ ๋œ๋‹ค). ๋ ˆ์ธ์ง€ ์ž์ฒด๋Š” ~3% +๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌยท5~7% ERP๋ฅผ ๊ฐ€์ •ํ•˜๋ฏ€๋กœ, ๋ฏธ๊ตญ ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ๋ฅผ ์œ„์— ๋‹ค์‹œ ์–น์ง€ ๋งˆ๋ผ. ## ์„นํ„ฐ ํŒ๋ณ„ @@ -42,6 +43,7 @@ KRX ์—…์ข…๋ถ„๋ฅ˜)์œผ๋กœ ์„นํ„ฐ๋ฅผ ์ถ”๋ก ํ•ด ์•„๋ž˜ ํ‘œ์— ๋งค์นญํ•˜๋ผ. - **๋ฐ˜๋ณตยท๊ตฌ๋… ๋งค์ถœ ๋ชจ๋ธ**: -0.5~1% - **ํˆฌ์ž๋“ฑ๊ธ‰ ์‹ ์šฉ๋“ฑ๊ธ‰ (KR ๊ธฐ์ค€ AA- ์ด์ƒ)**: -0.5% - **๋ณ€๋™์„ฑ ๋‚ฎ์€ ๊พธ์ค€ํ•œ ๋ฐฐ๋‹น์ฃผ**: -0.5% +- **๋ฐธ๋ฅ˜์—…(๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ ) ์ ๊ทน ์ฐธ์—ฌ** (๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ ๊ณ„ํš ๊ณต์‹œ + ์ž์‚ฌ์ฃผ ์†Œ๊ฐ/๋ฐฐ๋‹นํ™•๋Œ€ ์‹คํ–‰): -0.5~1% (์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ ๊ฐ€์‚ฐ์„ ๋ถ€๋ถ„ ์ƒ์‡„) ## ํ•ฉ๋ฆฌ์„ฑ ์ ๊ฒ€ diff --git a/src/skills/kr-earnings-quality/SKILL.md b/src/skills/kr-earnings-quality/SKILL.md new file mode 100644 index 000000000..384fd2864 --- /dev/null +++ b/src/skills/kr-earnings-quality/SKILL.md @@ -0,0 +1,50 @@ +--- +name: kr-earnings-quality +description: ํ•œ๊ตญ ์ƒ์žฅ์‚ฌ ์ด์ต์˜ ์งˆ(earnings quality) ์ ๊ฒ€ โ€” ๋ณด๊ณ ๋œ ์ˆœ์ด์ต์ด ์‹ค์ œ ํ˜„๊ธˆ๊ณผ ์ง€๋ฐฐ์ฃผ์ฃผ ๋ชซ์œผ๋กœ ๋’ท๋ฐ›์นจ๋˜๋Š”์ง€ ๊ฒ€์ฆํ•œ๋‹ค. ์˜์—…ํ˜„๊ธˆํ๋ฆ„ ๋Œ€๋น„ ์ˆœ์ด์ต(ํ˜„๊ธˆ ์ „ํ™˜), ์ง€๋ฐฐ vs ๋น„์ง€๋ฐฐ ์ˆœ์ด์ต, accrual, ์—ฐ๊ฒฐ vs ๋ณ„๋„๋ฅผ ๋ณธ๋‹ค. ์ด์ต์˜ ์งˆยท์–ด๋‹ํ€„๋ฆฌํ‹ฐยท์‹ค์ ์˜ ์งˆยทํ˜„๊ธˆํ๋ฆ„ vs ์ˆœ์ด์ตยทaccrualยท"์ด์ต์ด ์ง„์งœ๋ƒ"์™€ ํ•จ๊ป˜ ํŠธ๋ฆฌ๊ฑฐ. (Triggers on earnings quality, cash conversion, accruals.) +--- + +# ํ•œ๊ตญ ์ฃผ์‹ ์ด์ต์˜ ์งˆ(Earnings Quality) ์Šคํ‚ฌ + +๋ชฉํ‘œ: ๋ณด๊ณ ๋œ ์ˆœ์ด์ต์ด **ํ˜„๊ธˆ**์œผ๋กœ ๋’ท๋ฐ›์นจ๋˜๊ณ  **์ง€๋ฐฐ์ฃผ์ฃผ(์†Œ์•ก์ฃผ์ฃผ ํฌํ•จ)**์—๊ฒŒ ๊ท€์†๋˜๋Š”์ง€ ๊ฒ€์ฆํ•œ๋‹ค. ๊ฒฐ๋ก (์ด์ต์˜ ์งˆ ้ซ˜/ไธญ/ไฝŽ + ํ•ต์‹ฌ ๊ทผ๊ฑฐ)์„ ๋จผ์ €. ๊ฐ™์€ PER๋ผ๋„ ํ˜„๊ธˆ์œผ๋กœ ๋ฐ›์ณ์ง„ ์ด์ต๊ณผ ํšŒ๊ณ„์  ์ด์ต์€ ๊ฐ€์น˜๊ฐ€ ๋‹ค๋ฅด๋‹ค โ€” ์ด ์Šคํ‚ฌ์€ ๊ทธ ์ฐจ์ด๋ฅผ ๋“œ๋Ÿฌ๋‚ธ๋‹ค. + +## ์›Œํฌํ”Œ๋กœ ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +``` +KR ์ด์ต์˜ ์งˆ ์ ๊ฒ€ ์ง„ํ–‰: +- [ ] Step 1: ๋‹ค๋…„ ์žฌ๋ฌด ์ˆ˜์ง‘(์—ฐ๊ฒฐ) +- [ ] Step 2: ํ˜„๊ธˆ ์ „ํ™˜(OCF/NI) + FCF ์ ๊ฒ€ +- [ ] Step 3: ์ง€๋ฐฐ vs ๋น„์ง€๋ฐฐ ์ˆœ์ด์ต ๊ดด๋ฆฌ +- [ ] Step 4: ์—ฐ๊ฒฐ vs ๋ณ„๋„(๋ฐฐ๋‹น์žฌ์›) ๋น„๊ต +- [ ] Step 5: accrual ์ ๊ฒ€ + ์ข…ํ•ฉ ํ‰๊ฐ€ +``` + +## Step 1: ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ + +๋Œ€์ƒ ํ‹ฐ์ปค(๋˜๋Š” ํšŒ์‚ฌ๋ช…)๋กœ `get_financials_kr`๋ฅผ ํ˜ธ์ถœํ•ด ๋‹ค๋…„ ์—ฐ๊ฒฐ(CFS) `periods[].summary`๋ฅผ ๋ฐ›๋Š”๋‹ค. ํ•ต์‹ฌ ํ•„๋“œ(๊ฐ ๊ฐ’์€ `{ current, prior }`): +`incomeStatement.{netIncome, controllingNetIncome, operatingProfit}`, `cashFlow.operating`, `ratios.{freeCashFlow, roePct, netMarginPct}`, `balanceSheet.totalAssets`. + +## Step 2: ํ˜„๊ธˆ ์ „ํ™˜ (๊ฐ€์žฅ ์ค‘์š”) + +- **ํ˜„๊ธˆ ์ „ํ™˜๋น„์œจ = cashFlow.operating รท netIncome.** ์—ฌ๋Ÿฌ ํ•ด์— ๊ฑธ์ณ 1.0์„ **์ง€์†์ ์œผ๋กœ ๋ฐ‘๋Œ๋ฉด** ์ ์‹ ํ˜ธ โ€” ์ด์ต์ด ํ˜„๊ธˆ์ด ์•„๋‹ˆ๋ผ ๋งค์ถœ์ฑ„๊ถŒยท์žฌ๊ณ ๋กœ ๋ฌถ์—ฌ ์žˆ๋‹ค(๋ฐ€์–ด๋‚ด๊ธฐยท๊ฐ€๊ณต ๋งค์ถœ ์˜์‹ฌ). 1.0 ๋‚ด์™ธ~์ƒํšŒ๋ฉด ์–‘ํ˜ธ. +- **FCF ๋ถ€ํ˜ธ**(`ratios.freeCashFlow`): ํ‘์ž์ธ๋ฐ FCF๊ฐ€ ๋งŒ์„ฑ ์ ์ž๋ฉด ์ž๋ณธ์ ์ง€์ถœ/์šด์ „์ž๋ณธ์ด ์ด์ต์„ ์ž ์‹ โ€” ์งˆ ๋‚ฎ์Œ. + +## Step 3: ์ง€๋ฐฐ vs ๋น„์ง€๋ฐฐ ์ˆœ์ด์ต + +`controllingNetIncome` vs `netIncome`์„ ๋น„๊ตํ•œ๋‹ค. ๋น„์ง€๋ฐฐ์ง€๋ถ„(์†Œ์ˆ˜์ฃผ์ฃผ) ๋ชซ์ด ํฌ๋ฉด, ์—ฐ๊ฒฐ ์ˆœ์ด์ต์ด ์ปค ๋ณด์—ฌ๋„ **์‹ค์ œ ์ฃผ์ฃผ ๊ท€์† ์ด์ต์€ ์ž‘๋‹ค.** ์ง€๋ฐฐ์ง€๋ถ„ ๋น„์œจ = controllingNetIncome รท netIncome. ์žํšŒ์‚ฌ ์ง€๋ถ„์ด ๋‚ฎ์€ ์ง€๋ฐฐ๊ตฌ์กฐ์—์„œ ํ”ํ•˜๋‹ค โ€” PER์„ controllingNetIncome ๊ธฐ์ค€์œผ๋กœ ๋‹ค์‹œ ๋ณด๋ผ. + +## Step 4: ์—ฐ๊ฒฐ vs ๋ณ„๋„ (๋ฐฐ๋‹น์žฌ์›) + +`fs_div: "OFS"`๋กœ ํ•œ ๋ฒˆ ๋” ํ˜ธ์ถœํ•ด **๋ณ„๋„** ์ˆœ์ด์ต์„ ๋ณธ๋‹ค. ๋ฐฐ๋‹น์€ ๋ณดํ†ต ๋ณ„๋„ ์ด์ต์—์„œ ๋‚˜์˜ค๋ฏ€๋กœ, ์—ฐ๊ฒฐ ์ด์ต์ด ํฌ๋”๋ผ๋„ ๋ณ„๋„๊ฐ€ ์•ฝํ•˜๋ฉด ๋ฐฐ๋‹น ์—ฌ๋ ฅ์ด ์ œํ•œ๋œ๋‹ค. ์—ฐ๊ฒฐโ‰ซ๋ณ„๋„ ๊ดด๋ฆฌ๋Š” ์ง€๋ถ„๋ฒ•์ด์ต(๋ฏธ์‹คํ˜„ ํ˜„๊ธˆ) ๋น„์ค‘์ด ํฌ๋‹ค๋Š” ๋œป. + +## Step 5: accrual ์ ๊ฒ€ + ์ข…ํ•ฉ + +- **Accrual ๊ทผ์‚ฌ = (netIncome โˆ’ cashFlow.operating) รท totalAssets.** ๋†’๊ณ  ์–‘(+)์ผ์ˆ˜๋ก ์ด์ต์ด ๋ฐœ์ƒ์ฃผ์˜(ํšŒ๊ณ„์ )์— ์˜์กด โ€” ๋ฏธ๋ž˜ ์ด์ต ๋ฐ˜์ „ ์œ„ํ—˜. ์Œ(โˆ’)์ด๋ฉด ํ˜„๊ธˆ์ด ์ด์ต์„ ์ดˆ๊ณผ(๋ณด์ˆ˜์ ). +- ์ข…ํ•ฉ: ์œ„ ์‹ ํ˜ธ(ํ˜„๊ธˆ์ „ํ™˜ยท์ง€๋ฐฐ์ง€๋ถ„ยท๋ณ„๋„ยทaccrual)๋ฅผ ํ•˜๋‚˜์˜ ํŒ๋‹จ์œผ๋กœ ๋ฌถ์–ด ์ด์ต์˜ ์งˆ์„ ๋“ฑ๊ธ‰ํ™”ํ•œ๋‹ค. + +## ์ถœ๋ ฅ ํ˜•์‹ + +1. **ํ‰๊ฐ€**(1~2๋ฌธ์žฅ): ์ด์ต์˜ ์งˆ ้ซ˜/ไธญ/ไฝŽ + ๊ฐ€์žฅ ํฐ ๊ทผ๊ฑฐ. +2. **ํ˜„๊ธˆ ์ „ํ™˜ ํ‘œ**: ์—ฐ๋„๋ณ„ NI / OCF / ์ „ํ™˜๋น„์œจ / FCF. +3. **๊ท€์†ยท์žฌ์›**: ์ง€๋ฐฐ vs ๋น„์ง€๋ฐฐ, ์—ฐ๊ฒฐ vs ๋ณ„๋„ ๊ดด๋ฆฌ. +4. **accrual ์‹ ํ˜ธ**: ์ˆ˜์น˜ + ํ•ด์„. +5. **์ฃผ์˜์‚ฌํ•ญ**: ๋‹จ๋…„ ๋ณ€๋™์€ ์šด์ „์ž๋ณธ ํƒ€์ด๋ฐ์ผ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ถ”์„ธ๋กœ ๋ณด๋ผ. ์€ํ–‰ยท์ง€์ฃผ์‚ฌ๋Š” OCF ๊ฐœ๋…์ด ๋‹ฌ๋ผ ์ด ํ”„๋ ˆ์ž„์ด ๋ถ€๋ถ„์ ์œผ๋กœ๋งŒ ์ ์šฉ๋œ๋‹ค. diff --git a/src/skills/kr-relative-valuation/SKILL.md b/src/skills/kr-relative-valuation/SKILL.md new file mode 100644 index 000000000..5fcb4fccc --- /dev/null +++ b/src/skills/kr-relative-valuation/SKILL.md @@ -0,0 +1,56 @@ +--- +name: kr-relative-valuation +description: ํ•œ๊ตญ ์ƒ์žฅ์‚ฌ๋ฅผ ๋™์ข…์—…๊ณ„ ๋ฉ€ํ‹ฐํ”Œ๊ณผ ๋น„๊ตํ•˜๋Š” ์ƒ๋Œ€๊ฐ€์น˜ ๋ฐธ๋ฅ˜์—์ด์…˜. ๊ฒฝ๊ธฐ๋ฏผ๊ฐ์ฃผ(๋ฐ˜๋„์ฒดยท์ฒ ๊ฐ•ยท์กฐ์„ ยทํ™”ํ•™ยทํ•ด์šด)๋‚˜ ์ด์ต ๋ณ€๋™์ด ์ปค DCF๊ฐ€ ๋ถˆ์•ˆ์ •ํ•œ ์ข…๋ชฉ์˜ "์ ์ •์ฃผ๊ฐ€/์‹ธ๋ƒ ๋น„์‹ธ๋ƒ"๋ฅผ, PERยทPBRยทEV ๋ฉ€ํ‹ฐํ”Œ์„ peerยท์ž๊ธฐ ๊ณผ๊ฑฐ์™€ ๋น„๊ตํ•ด ๋‹ตํ•œ๋‹ค. ์ƒ๋Œ€๊ฐ€์น˜ยทpeer/๋™์ข…์—…๊ณ„ ๋น„๊ตยท๋ฉ€ํ‹ฐํ”ŒยทPER/PBR ๋ฐด๋“œยท๊ฒฝ๊ธฐ๋ฏผ๊ฐ์ฃผ ์ ์ •๊ฐ€์น˜ยท"ํ”ผ์–ด ๋Œ€๋น„ ์‹ธ๋ƒ"์™€ ํ•จ๊ป˜ ํŠธ๋ฆฌ๊ฑฐ. (Triggers on relative valuation, peer comps, multiples, cyclical fair value.) +--- + +# ํ•œ๊ตญ ์ฃผ์‹ ์ƒ๋Œ€๊ฐ€์น˜(Comps) ๋ฐธ๋ฅ˜์—์ด์…˜ ์Šคํ‚ฌ + +๋ชฉํ‘œ: ๋Œ€์ƒ ์ข…๋ชฉ์ด **๋™์ข…์—…๊ณ„ ๋Œ€๋น„** ์‹ผ์ง€ ๋น„์‹ผ์ง€, ๊ทธ๋ฆฌ๊ณ  ๊ทธ ํ”„๋ฆฌ๋ฏธ์—„/๋””์Šค์นด์šดํŠธ๊ฐ€ **์ •๋‹นํ•œ์ง€**๋ฅผ ๋ฉ€ํ‹ฐํ”Œ๋กœ ๋‹ตํ•œ๋‹ค. ๊ฒฐ๋ก (์ €ํ‰๊ฐ€/์ ์ •/๊ณ ํ‰๊ฐ€ + ๊ทผ๊ฑฐ)์„ ๋จผ์ € ์ œ์‹œํ•˜๋ผ. DCF๊ฐ€ ๋‹จ์ผ ์‹œ๋‚˜๋ฆฌ์˜ค์˜ ๋‚ด์žฌ๊ฐ€์น˜๋ผ๋ฉด, ์ด ์Šคํ‚ฌ์€ ์‹œ์žฅ์ด ์œ ์‚ฌ ๊ธฐ์—…์— ๋งค๊ธฐ๋Š” ๊ฐ’๊ณผ์˜ ๋น„๊ต๋‹ค. + +## ์–ธ์ œ ์ด ์Šคํ‚ฌ์ด DCF๋ณด๋‹ค ๋‚˜์€๊ฐ€ (Step 0 ๋ผ์šฐํŒ…) + +- **๊ฒฝ๊ธฐ๋ฏผ๊ฐ์ฃผ**(๋ฐ˜๋„์ฒดยท์ฒ ๊ฐ•ยท์กฐ์„ ยทํ™”ํ•™ยทํ•ด์šดยท๊ฑด์„ค): ์ด์ต์ด ์‚ฌ์ดํด์„ ํฌ๊ฒŒ ํƒ€ ๋‹จ์ผ-์‹œ์  DCF๊ฐ€ ์™œ๊ณก๋œ๋‹ค โ†’ ์ƒ๋Œ€๊ฐ€์น˜(ํŠนํžˆ **PBRยทEV/EBITDA**)์™€ through-cycle ์ •์ƒ์ด์ต์ด ๋” ์‹ ๋ขฐ์„ฑ ์žˆ๋‹ค. +- **๊ธˆ์œต์ฃผ**(์€ํ–‰ยท์ฆ๊ถŒยท๋ณดํ—˜): FCF ๊ฐœ๋…์ด ์•ฝํ•˜๋‹ค โ†’ **PBRยทROE** ๊ธฐ๋ฐ˜ ๋น„๊ต๊ฐ€ ํ‘œ์ค€. +- peer ๊ทธ๋ฃน์ด ๋šœ๋ ทํ•˜๊ณ  ์‚ฌ์šฉ์ž๊ฐ€ "๋™์ข… ๋Œ€๋น„", "์‹ธ๋ƒ"๋ฅผ ๋ฌผ์„ ๋•Œ. +์ˆœํ˜„๊ธˆํ๋ฆ„์ด ์•ˆ์ •์ ์ธ ์„ฑ์žฅยท์†Œ๋น„์žฌ๋ผ๋ฉด DCF(`dcf-valuation`)๋ฅผ ์šฐ์„ ํ•˜๊ณ  ์ด ์Šคํ‚ฌ์€ ๊ต์ฐจ๊ฒ€์ฆ์œผ๋กœ ์“ด๋‹ค. + +## ์›Œํฌํ”Œ๋กœ ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +``` +KR ์ƒ๋Œ€๊ฐ€์น˜ ๋ถ„์„ ์ง„ํ–‰: +- [ ] Step 1: ๋Œ€์ƒ ๋ฉ€ํ‹ฐํ”Œ + peer ํ›„๋ณด ์ˆ˜์ง‘ +- [ ] Step 2: peer๋ณ„ ๋ฉ€ํ‹ฐํ”Œ๋กœ comp ํ‘œ ๊ตฌ์„ฑ +- [ ] Step 3: ๋ฉ€ํ‹ฐํ”Œ ๊ฒฉ์ฐจ๋ฅผ ํŽ€๋”๋ฉ˜ํ„ธ๋กœ ์ •๊ทœํ™”(์„ฑ์žฅยท๋งˆ์ง„ยทROEยท์ง€๋ฐฐ๊ตฌ์กฐ) +- [ ] Step 4: ์ ์ • ๋ฉ€ํ‹ฐํ”Œ ร— ๋Œ€์ƒ ์ง€ํ‘œ โ†’ ์ ์ •๊ฐ€์น˜ ๋ฐด๋“œ +- [ ] Step 5: ํ‰๊ฐ€ + ์ฃผ์˜์‚ฌํ•ญ +``` + +## Step 1: ๋Œ€์ƒ + peer ์ˆ˜์ง‘ + +๋Œ€์ƒ ํ‹ฐ์ปค(๋˜๋Š” ํšŒ์‚ฌ๋ช…)๋กœ `get_market_data_kr`๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. ๋ฐ˜ํ™˜์—์„œ `valuation.{per, pbr, eps, bps, forwardPer, forwardEps}`, `quote.{price, high52w, low52w}`, ๊ทธ๋ฆฌ๊ณ  `peers[]`(๋™์ข… tickerยท์‹œ์ด)๋ฅผ ์ฝ๋Š”๋‹ค. `peers`๊ฐ€ ๋น„์—ˆ๊ฑฐ๋‚˜ ๋ถ€์ ์ ˆํ•˜๋ฉด `web_search`๋กœ ๋™์ข… 2~4๊ฐœ๋ฅผ ๋ณด์™„ํ•œ๋‹ค(๊ฐ™์€ KRX ์—…์ข…ยท์‚ฌ์—…๋ชจ๋ธ). + +## Step 2: comp ํ‘œ ๊ตฌ์„ฑ + +๊ฐ peer ํ‹ฐ์ปค๋กœ `get_market_data_kr`๋ฅผ **๋‹ค์‹œ** ํ˜ธ์ถœํ•ด ๋ฉ€ํ‹ฐํ”Œ์„ ์ฑ„์šด๋‹ค(peers[] ์ž์ฒด์—๋Š” priceยท์‹œ์ด๋งŒ ์žˆ๊ณ  PER/PBR์€ ์—†๋‹ค โ€” peer๋‹น 1์ฝœ). ํ•œ ํ‘œ๋กœ ์ •๋ฆฌ: ์ข…๋ชฉ | PER | forwardPER | PBR | ๋ฐฐ๋‹น์ˆ˜์ต๋ฅ  | ์‹œ์ด. ๋™์ข… **์ค‘์•™๊ฐ’**์„ ๊ฐ™์ด ์ ๋Š”๋‹ค(ํ‰๊ท ์€ ์ด์ƒ์น˜์— ๋Œ๋ฆฐ๋‹ค). + +## Step 3: ๊ฒฉ์ฐจ๋ฅผ ํŽ€๋”๋ฉ˜ํ„ธ๋กœ ์ •๊ทœํ™” + +๋ฉ€ํ‹ฐํ”Œ ์ฐจ์ด๋ฅผ ๊ทธ๋ƒฅ ๋‘์ง€ ๋ง๊ณ  ์„ค๋ช…ํ•˜๋ผ. `get_financials_kr`(multi-year)๋กœ ๋Œ€์ƒยท์ฃผ์š” peer์˜ `ratios.{revenueYoYPct, operatingProfitYoYPct, operatingMarginPct, netMarginPct, roePct}`๋ฅผ ๋น„๊ต: +- ๋” ๋†’์€ ์„ฑ์žฅ/๋งˆ์ง„/ROE โ†’ ํ”„๋ฆฌ๋ฏธ์—„ ๋ฉ€ํ‹ฐํ”Œ **์ •๋‹น**. ๋‚ฎ์€๋ฐ ํ”„๋ฆฌ๋ฏธ์—„์ด๋ฉด ๊ณ ํ‰๊ฐ€ ์‹ ํ˜ธ. +- ์ง€๋ฐฐ๊ตฌ์กฐ(๊ณ„์—ดยท์ˆœํ™˜์ถœ์ž: `get_large_holders_kr`) ์—ด์œ„ โ†’ ๋””์Šค์นด์šดํŠธ **์ •๋‹น**. +- ๊ฒฝ๊ธฐ๋ฏผ๊ฐ์ฃผ๋Š” **PER ํ•จ์ •** ์ฃผ์˜: ์ด์ต ๊ณ ์ ์—์„œ PER์ด ๋‚ฎ์•„ ๋ณด์ด๊ณ (์ €ํ‰๊ฐ€ ์ฐฉ์‹œ), ์ €์ ์—์„œ ๋†’์•„ ๋ณด์ธ๋‹ค โ†’ PBR๊ณผ ์ •์ƒ์ด์ต(through-cycle ํ‰๊ท  ์˜์—…์ด์ต) ๊ธฐ์ค€ PER๋กœ ๊ต์ฐจ ์ ๊ฒ€. + +## Step 4: ์ ์ •๊ฐ€์น˜ ๋ฐด๋“œ + +์ ์ • ๋ฉ€ํ‹ฐํ”Œ(๋™์ข… ์ค‘์•™๊ฐ’์„ ํŽ€๋”๋ฉ˜ํ„ธ ๊ฒฉ์ฐจ๋กœ ์กฐ์ •)์„ ๋Œ€์ƒ ์ง€ํ‘œ์— ๊ณฑํ•œ๋‹ค: +- ์ ์ •์ฃผ๊ฐ€(PER ๊ธฐ์ค€) = ์ ์ • PER ร— ๋Œ€์ƒ EPS. PBR ๊ธฐ์ค€ = ์ ์ • PBR ร— BPS. +- ๋ณด์ˆ˜/๊ธฐ๋ณธ/๋‚™๊ด€ 3๊ฐœ ๋ฉ€ํ‹ฐํ”Œ๋กœ **๋ฐด๋“œ**๋ฅผ ์ œ์‹œํ•˜๊ณ  ํ˜„์žฌ๊ฐ€ ๋Œ€๋น„ ์ƒ์Šน/ํ•˜๋ฝ ์—ฌ๋ ฅ(%)์„ ๊ณ„์‚ฐํ•œ๋‹ค. +- ๊ฐ€๋Šฅํ•˜๋ฉด `get_market_data_kr`์˜ `consensus.targetPrice`์™€ ๋Œ€์กฐํ•ด ๋‚ด ๋ฐด๋“œ๊ฐ€ ์ปจ์„ผ์„œ์Šค์™€ ์–ผ๋งˆ๋‚˜ ๋‹ค๋ฅธ์ง€ ๋ฐํžŒ๋‹ค. + +## Step 5: ์ถœ๋ ฅ ํ˜•์‹ + +1. **ํ‰๊ฐ€**(1~2๋ฌธ์žฅ): ๋™์ข… ๋Œ€๋น„ ์ €ํ‰๊ฐ€/์ ์ •/๊ณ ํ‰๊ฐ€ + ๊ฐ€์žฅ ํฐ ์ด์œ . +2. **comp ํ‘œ**: ๋Œ€์ƒ + peers + ์ค‘์•™๊ฐ’(PER/PBR/๋ฐฐ๋‹น/์„ฑ์žฅยทROE). +3. **์ •๊ทœํ™” ๋…ผ๋ฆฌ**: ํ”„๋ฆฌ๋ฏธ์—„/๋””์Šค์นด์šดํŠธ๊ฐ€ ์ •๋‹นํ•œ์ง€. +4. **์ ์ •๊ฐ€์น˜ ๋ฐด๋“œ**: ๋ฉ€ํ‹ฐํ”Œ ๊ธฐ์ค€๋ณ„ ์ ์ •์ฃผ๊ฐ€ + ์ƒ์Šน์—ฌ๋ ฅ. +5. **์ฃผ์˜์‚ฌํ•ญ**: ๊ฒฝ๊ธฐ ์œ„์น˜(์‚ฌ์ดํด ๊ณ /์ €์ ), ๋ฉ€ํ‹ฐํ”Œ ๊ธฐ์ค€์˜ ํ•œ๊ณ„, ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ๋Š” ์ผ๋ถ€ ๊ตฌ์กฐ์ . ๊ฑฐ๋ž˜์„ธยท๋ฐฐ๋‹น์„ธ ์„ธํ›„ ์ˆ˜์ต์€ ๋ณ„๋„. diff --git a/src/skills/kr-shareholder-return/SKILL.md b/src/skills/kr-shareholder-return/SKILL.md new file mode 100644 index 000000000..394e8ee95 --- /dev/null +++ b/src/skills/kr-shareholder-return/SKILL.md @@ -0,0 +1,49 @@ +--- +name: kr-shareholder-return +description: ํ•œ๊ตญ ์ƒ์žฅ์‚ฌ์˜ ์ฃผ์ฃผํ™˜์›ยท๋ฐธ๋ฅ˜์—…(๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ ) ๋ถ„์„ โ€” 2024๋…„ ์ดํ›„ ํ•œ๊ตญ ์ฆ์‹œ ์ตœ๋Œ€ catalyst. PBR<1 ์ข…๋ชฉ์˜ ๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ ๊ณ„ํšยท์ž์‚ฌ์ฃผ ์ทจ๋“/์†Œ๊ฐยท๋ฐฐ๋‹น์ •์ฑ…์„ ํ‰๊ฐ€ํ•ด ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ re-rating ๊ฐ€๋Šฅ์„ฑ์„ ํŒ๋‹จํ•œ๋‹ค. ๋ฐธ๋ฅ˜์—…ยท๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ ยท์ฃผ์ฃผํ™˜์›ยท์ž์‚ฌ์ฃผ ์†Œ๊ฐ/๋งค์ž…ยท๋ฐฐ๋‹น์ •์ฑ…/๋ฐฐ๋‹น์„ฑํ–ฅ/๋ฐฐ๋‹น์ˆ˜์ต๋ฅ ยทPBR 1๋ฐฐยท"์ €PBR ๋ฆฌ๋ ˆ์ดํŒ…"๊ณผ ํ•จ๊ป˜ ํŠธ๋ฆฌ๊ฑฐ. (Triggers on Value-up program, shareholder return, buyback, dividend policy, PBR<1 re-rating.) +--- + +# ํ•œ๊ตญ ์ฃผ์ฃผํ™˜์› / ๋ฐธ๋ฅ˜์—… ๋ถ„์„ ์Šคํ‚ฌ + +๋ชฉํ‘œ: ๋Œ€์ƒ์ด **๋ฐธ๋ฅ˜์—…(๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ ) re-rating ํ›„๋ณด**์ธ์ง€, ์ฆ‰ ์ฃผ์ฃผํ™˜์› ๊ฐ•ํ™”๋กœ ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ(์ €PBR)๊ฐ€ ์ขํ˜€์งˆ ์—ฌ์ง€๊ฐ€ ์žˆ๋Š”์ง€ ํŒ๋‹จํ•œ๋‹ค. ๊ฒฐ๋ก (re-rating ํ›„๋ณด ์—ฌ๋ถ€ + ํ•ต์‹ฌ ๊ทผ๊ฑฐ)์„ ๋จผ์ €. **PBR<1์„ ๋‹จ์ˆœ "ํ†ต๊ณ„์  ์ €ํ‰๊ฐ€"๋กœ ๋๋‚ด์ง€ ๋งˆ๋ผ โ€” ํ™˜์› ์˜์ง€/์‹คํ–‰์ด ์žˆ์–ด์•ผ ๋””์Šค์นด์šดํŠธ๊ฐ€ ํ’€๋ฆฐ๋‹ค. ํ™˜์› ์•ฝ์†์˜ ๋ถ€์žฌ ์ž์ฒด๊ฐ€ ๋””์Šค์นด์šดํŠธ ์ง€์†์˜ ๊ทผ๊ฑฐ๋‹ค.** + +## ๋ฐฐ๊ฒฝ (2024~) + +์ •๋ถ€ '๊ธฐ์—… ๋ฐธ๋ฅ˜์—… ํ”„๋กœ๊ทธ๋žจ'์œผ๋กœ ์ €PBRยท๊ณ ์ž‰์—ฌํ˜„๊ธˆ ๊ธฐ์—…์˜ **๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ ๊ณ„ํš** ์ž์œจ๊ณต์‹œ, ์ž์‚ฌ์ฃผ **์†Œ๊ฐ**(๋งค์ž…๋งŒ์œผ๋กœ๋Š” ๋ถ€์กฑ โ€” ์†Œ๊ฐํ•ด์•ผ ์ฃผ์‹์ˆ˜ ๊ฐ์†Œ), ๋ฐฐ๋‹น ํ™•๋Œ€๊ฐ€ ํ•ต์‹ฌ ํ…Œ๋งˆ๊ฐ€ ๋๋‹ค. ์ž์‚ฌ์ฃผ๋Š” **๋งค์ž… ํ›„ ์†Œ๊ฐ**ํ•ด์•ผ EPSยท์ฃผ์ฃผ๊ฐ€์น˜๊ฐ€ ์‹ค์ œ๋กœ ์˜ค๋ฅธ๋‹ค(๋งค์ž… ํ›„ ๋ณด์œ ๋Š” ์ถ”ํ›„ ์žฌ๋งค๊ฐ ๋ฆฌ์Šคํฌ). + +## ์›Œํฌํ”Œ๋กœ ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +``` +KR ์ฃผ์ฃผํ™˜์›/๋ฐธ๋ฅ˜์—… ๋ถ„์„ ์ง„ํ–‰: +- [ ] Step 1: ๋ฐธ๋ฅ˜์—์ด์…˜ยทํ™˜์› ํ˜„ํ™ฉ ์Šค๋ƒ…์ƒท +- [ ] Step 2: ๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ ๊ณ„ํšยท์ž์‚ฌ์ฃผยท๋ฐฐ๋‹น์ •์ฑ… ๊ณต์‹œ ํƒ์ƒ‰ +- [ ] Step 3: ํ™˜์› ์—ฌ๋ ฅ(ํ˜„๊ธˆยท๋ณ„๋„์ด์ต) ์ ๊ฒ€ +- [ ] Step 4: re-rating ํŒ๋‹จ + ํŠธ๋ฆฌ๊ฑฐ +``` + +## Step 1: ์Šค๋ƒ…์ƒท + +๋Œ€์ƒ ํ‹ฐ์ปค(๋˜๋Š” ํšŒ์‚ฌ๋ช…)๋กœ `get_market_data_kr` โ†’ `valuation.{pbr, dividendYieldPct, dividendPerShare, marketCap}`. **PBR<1์ด๋ฉด ๋ฐธ๋ฅ˜์—… 1์ฐจ ํ›„๋ณด.** ๋ฐฐ๋‹น์ˆ˜์ต๋ฅ  ์ˆ˜์ค€๊ณผ 52์ฃผ ์œ„์น˜๋„ ๊ธฐ๋ก. PBR์ด 1์„ ํฌ๊ฒŒ ๋„˜์œผ๋ฉด ๋ฐธ๋ฅ˜์—… ์•ต๊ธ€์€ ์•ฝํ•˜๋ฏ€๋กœ ์ผ๋ฐ˜ ๋ฐธ๋ฅ˜์—์ด์…˜์œผ๋กœ ์ „ํ™˜. + +## Step 2: ํ™˜์› ๊ณต์‹œ ํƒ์ƒ‰ (ํ•ต์‹ฌ) + +`get_filings_kr`๋กœ ํ™˜์› ๊ด€๋ จ ๊ณต์‹œ๋ฅผ ์ฐพ๋Š”๋‹ค(๊ธฐ๋ณธ ๋ฒ”์œ„ ~1๋…„, ํ•„์š”์‹œ `start_date` ํ™•๋Œ€): +- `filing_type: "exchange"`(๊ฑฐ๋ž˜์†Œ๊ณต์‹œ) โ†’ **๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ ๊ณ„ํš** ์ž์œจ๊ณต์‹œ. +- `filing_type: "material"`(์ฃผ์š”์‚ฌํ•ญ๋ณด๊ณ ) โ†’ **์ž๊ธฐ์ฃผ์‹ ์ทจ๋“/์†Œ๊ฐ**, ์ž๊ธฐ์ฃผ์‹์ทจ๋“ ์‹ ํƒ๊ณ„์•ฝ. +`report_nm`์—์„œ `๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ `, `์ž๊ธฐ์ฃผ์‹`, `์ž์‚ฌ์ฃผ`, `์†Œ๊ฐ`, `๋ฐฐ๋‹น` ํ‚ค์›Œ๋“œ ์Šค์บ”. ๋ฐฐ๋‹น์ •์ฑ… ๋ณธ๋ฌธ์€ `read_filings_kr`(sections `["overview"]` ๋˜๋Š” `["governance"]`)๋กœ ๋ณด์™„. ๊ณต์‹œ๊ฐ€ ๋ถ€์กฑํ•˜๋ฉด `web_search`๋กœ ์ตœ๊ทผ IR/๋ฐธ๋ฅ˜์—… ๋ฐœํ‘œ ํ™•์ธ. + +## Step 3: ํ™˜์› ์—ฌ๋ ฅ + +`get_financials_kr`๋กœ ํ™˜์›์„ ๊ฐ๋‹นํ•  ์ฒด๋ ฅ์„ ๋ณธ๋‹ค: +- **์ˆœํ˜„๊ธˆ**(`balanceSheet.cashAndEquivalents + shortTermInvestments โˆ’ totalDebt`)์ด ๋‘๋‘‘ํ•˜๋ฉด ์ž์‚ฌ์ฃผ ์†Œ๊ฐยทํŠน๋ณ„๋ฐฐ๋‹น ์—ฌ๋ ฅ. +- **FCF**(`ratios.freeCashFlow`)๊ฐ€ ํ‘์ž ์ง€์†์ด๋ฉด ์ง€์†๊ฐ€๋Šฅ ํ™˜์›. +- **๋ฐฐ๋‹น์„ฑํ–ฅ** = ๋ฐฐ๋‹น์ด์•ก รท `incomeStatement.netIncome`(๋˜๋Š” DPSร—์ฃผ์‹์ˆ˜ รท NI). ๋ฐฐ๋‹น์žฌ์›์€ **๋ณ„๋„(OFS)** ์ด์ต์ด๋ฏ€๋กœ ๋ณ„๋„ ์ด์ต๋„ ํ™•์ธ. +๋‚ฎ์€ ๋ฐฐ๋‹น์„ฑํ–ฅ + ๋‘๋‘‘ํ•œ ์ˆœํ˜„๊ธˆ = ํ™˜์› ์—ฌ๋ ฅ์€ ํฐ๋ฐ ์•ˆ ํ•˜๋Š” ์ƒํƒœ โ†’ ๋ฐธ๋ฅ˜์—… ์••๋ฐ•์˜ ํ‘œ์ ์ด์ž ์ž ์žฌ upside. + +## Step 4: re-rating ํŒ๋‹จ + ์ถœ๋ ฅ + +1. **ํ‰๊ฐ€**(1~2๋ฌธ์žฅ): ๋ฐธ๋ฅ˜์—… re-rating ํ›„๋ณด ์—ฌ๋ถ€ + ๊ฐ€์žฅ ํฐ ๊ทผ๊ฑฐ(๊ณต์‹œ ์‹คํ–‰ / ์—ฌ๋ ฅ / ๋ถ€์žฌ). +2. **ํ˜„ํ™ฉ**: PBR, ๋ฐฐ๋‹น์ˆ˜์ต๋ฅ ยท์„ฑํ–ฅ, ์ˆœํ˜„๊ธˆ/FCF. +3. **ํ™˜์› ํŠธ๋ž™๋ ˆ์ฝ”๋“œ**: ๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ ๊ณ„ํšยท์ž์‚ฌ์ฃผ ์†Œ๊ฐยท๋ฐฐ๋‹น ๊ณต์‹œ ์œ ๋ฌด์™€ ๋‚ด์šฉ(๋งค์ž… vs **์†Œ๊ฐ** ๊ตฌ๋ถ„). +4. **ํŒ๋‹จ**: ํ™˜์› ์˜์ง€+์—ฌ๋ ฅ+์‹คํ–‰์ด๋ฉด ๋””์Šค์นด์šดํŠธ ์ถ•์†Œ ํŠธ๋ฆฌ๊ฑฐ; ์—ฌ๋ ฅ์€ ์žˆ์œผ๋‚˜ ๋ฌดํ–‰๋™์ด๋ฉด ๋””์Šค์นด์šดํŠธ ์ง€์† ๊ทผ๊ฑฐ๋กœ ๋ช…์‹œ. +5. **ํŠธ๋ฆฌ๊ฑฐ/์ฃผ์˜**: "์ž์‚ฌ์ฃผ ์†Œ๊ฐ ๊ณต์‹œ", "๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ ๊ณ„ํš ์ƒํ–ฅ", "๋ฐฐ๋‹น์„ฑํ–ฅ ๋ชฉํ‘œ ์ œ์‹œ" ๋“ฑ ๊ตฌ์ฒด ํŠธ๋ฆฌ๊ฑฐ. ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ๋Š” ์ผ๋ถ€ ๊ตฌ์กฐ์ (์ง€๋ฐฐ๊ตฌ์กฐยท์„ธ์ œ)์ด๋ผ ํ™˜์›๋งŒ์œผ๋กœ ์™„์ „ ํ•ด์†Œ๋˜์ง„ ์•Š์Œ. ๋ฐฐ๋‹น์„ธ(๊ฑฐ์ฃผ์ž 15.4%, ์™ธ๊ตญ์ธ ~22% ๋˜๋Š” ์กฐ์„ธ์กฐ์•ฝ) ์„ธํ›„ ๋ณ„๋„. (์ง€์ฃผ์‚ฌ๋ฉด `kr-sotp-holdco`์™€ ํ•จ๊ป˜ ๋ณด๋ผ.) diff --git a/src/skills/kr-sotp-holdco/SKILL.md b/src/skills/kr-sotp-holdco/SKILL.md new file mode 100644 index 000000000..457333a99 --- /dev/null +++ b/src/skills/kr-sotp-holdco/SKILL.md @@ -0,0 +1,51 @@ +--- +name: kr-sotp-holdco +description: ํ•œ๊ตญ ์ง€์ฃผ์‚ฌ/ํ™€๋”ฉ์Šค์˜ SOTP(๋ถ€๋ถ„ํ•ฉ)ยทNAV ๋””์Šค์นด์šดํŠธ ๋ถ„์„. ์ƒ์žฅ ์žํšŒ์‚ฌ ์ง€๋ถ„์„ ์‹œ๊ฐ€๋กœ ํ‰๊ฐ€ํ•ด ์ˆœ์ž์‚ฐ๊ฐ€์น˜(NAV)๋ฅผ ๊ตฌํ•˜๊ณ  ์ง€์ฃผ์‚ฌ ์‹œ์ด์ด ๊ทธ ๋Œ€๋น„ ์–ผ๋งˆ๋‚˜ ํ• ์ธ ๊ฑฐ๋ž˜๋˜๋Š”์ง€ ์ •๋Ÿ‰ํ™”ํ•œ๋‹ค. ์ง€์ฃผ์‚ฌ DCF๋Š” ๋”๋ธ”์นด์šดํŒ…์ด๋ผ ๋ถ€์ ํ•ฉ. ์ง€์ฃผ์‚ฌยทํ™€๋”ฉ์ŠคยทSOTPยทsum-of-the-partsยทNAVยท์ˆœ์ž์‚ฐ๊ฐ€์น˜ยท์ง€์ฃผํšŒ์‚ฌ ํ• ์ธยท"์ง€์ฃผ์‚ฌ ์ ์ •๊ฐ€์น˜"์™€ ํ•จ๊ป˜ ํŠธ๋ฆฌ๊ฑฐ. (Triggers on holding company, holdco, SOTP, NAV discount.) +--- + +# ํ•œ๊ตญ ์ง€์ฃผ์‚ฌ SOTP / NAV ๋””์Šค์นด์šดํŠธ ์Šคํ‚ฌ + +๋ชฉํ‘œ: ์ง€์ฃผ์‚ฌ์˜ **์ˆœ์ž์‚ฐ๊ฐ€์น˜(NAV)**๋ฅผ ๋ถ€๋ถ„ํ•ฉ์œผ๋กœ ๊ณ„์‚ฐํ•˜๊ณ , ํ˜„์žฌ ์‹œ์ด์ด NAV ๋Œ€๋น„ ๋ช‡ % ํ• ์ธ์ธ์ง€, ๊ทธ ํ• ์ธ์ด ์ขํ˜€์งˆ ํŠธ๋ฆฌ๊ฑฐ๊ฐ€ ์žˆ๋Š”์ง€ ๋‹ตํ•œ๋‹ค. ๊ฒฐ๋ก (ํ• ์ธ์œจ + ์ขํ˜€์งˆ ๊ฐ€๋Šฅ์„ฑ)์„ ๋จผ์ €. **์ง€์ฃผ์‚ฌ์— ์ผ๋ฐ˜ DCF/์—ฐ๊ฒฐ PER๋ฅผ ์“ฐ์ง€ ๋งˆ๋ผ โ€” ์žํšŒ์‚ฌ ์ด์ต์ด ๋‘ ๋ฒˆ ๊ณ„์‚ฐ(๋”๋ธ”์นด์šดํŒ…)๋œ๋‹ค.** + +## Step 0: ์ง€์ฃผ์‚ฌ์ธ์ง€ ํ™•์ธ + +ํšŒ์‚ฌ๋ช…์ด "โ€ฆํ™€๋”ฉ์Šค/์ง€์ฃผ", ๋˜๋Š” ์‚ฌ์—…์ด ์žํšŒ์‚ฌ ์ง€๋ถ„ ๋ณด์œ ยท๋ฐฐ๋‹นยท์ƒํ‘œ๊ถŒ ์ˆ˜์ทจ ์ค‘์‹ฌ์ด๋ฉด ์ง€์ฃผ์‚ฌ๋‹ค. ์• ๋งคํ•˜๋ฉด `get_financials_kr` ๋ณ„๋„(OFS)์—์„œ ๋งค์ถœ์ด ์ž‘๊ณ  ์ง€๋ถ„๋ฒ•์ด์ต/๋ฐฐ๋‹น์ˆ˜์ต ๋น„์ค‘์ด ํฌ๋ฉด ์ง€์ฃผ์‚ฌ. ์ง€์ฃผ์‚ฌ๊ฐ€ ์•„๋‹ˆ๋ฉด ์ด ์Šคํ‚ฌ ๋Œ€์‹  `dcf-valuation` ๋˜๋Š” `kr-relative-valuation`์„ ์“ด๋‹ค. + +## ์›Œํฌํ”Œ๋กœ ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +``` +์ง€์ฃผ์‚ฌ SOTP ๋ถ„์„ ์ง„ํ–‰: +- [ ] Step 1: ๋ณด์œ  ์žํšŒ์‚ฌยท์ง€๋ถ„์œจ ๋งคํ•‘ +- [ ] Step 2: ์ƒ์žฅ ์žํšŒ์‚ฌ ์ง€๋ถ„๊ฐ€์น˜(์‹œ๊ฐ€) ํ‰๊ฐ€ +- [ ] Step 3: ๋น„์ƒ์žฅ ์žํšŒ์‚ฌยท์ž์ฒด์‚ฌ์—… ๊ฐ€์น˜ ํ‰๊ฐ€ +- [ ] Step 4: NAV ์ง‘๊ณ„ โ†’ ์ง€์ฃผ์‚ฌ ๋””์Šค์นด์šดํŠธ ๊ณ„์‚ฐ +- [ ] Step 5: ํ‰๊ฐ€ + ํ• ์ธ ์ถ•์†Œ ํŠธ๋ฆฌ๊ฑฐ +``` + +## Step 1: ์žํšŒ์‚ฌยท์ง€๋ถ„์œจ ๋งคํ•‘ + +์ง€์ฃผ์‚ฌ ํ‹ฐ์ปค๋กœ `read_filings_kr`(sections: `["governance"]`)๋ฅผ ํ˜ธ์ถœํ•ด ๊ณ„์—ดํšŒ์‚ฌยท์ถœ์ž ํ˜„ํ™ฉยท์ตœ๋Œ€์ฃผ์ฃผ ์ฒด์ธ์„ ์ฝ๊ณ , `get_large_holders_kr`๋กœ ์ง€์ฃผ์‚ฌ๊ฐ€ ๋ฐ›๋Š” 5%๋ฃฐ ๋ณด๊ณ (๋ฐ˜๋Œ€๋กœ ์ง€์ฃผ์‚ฌ๊ฐ€ ์žํšŒ์‚ฌ ๋Œ€์ฃผ์ฃผ)๋ฅผ ๊ต์ฐจํ•œ๋‹ค. ์ฃผ์š” ์ƒ์žฅ ์žํšŒ์‚ฌยท์ง€๋ถ„์œจ ๋ชฉ๋ก์„ ๋งŒ๋“ ๋‹ค. ๊ทธ๋ฃน ๊ตฌ์กฐ๊ฐ€ ๋ณต์žกํ•˜๋ฉด `web_search`๋กœ ๋ณด์™„(ํ•„์š”ํ•œ ๋งŒํผ๋งŒ). + +## Step 2: ์ƒ์žฅ ์žํšŒ์‚ฌ ์ง€๋ถ„๊ฐ€์น˜ (ํ•ต์‹ฌ) + +๊ฐ ์ƒ์žฅ ์žํšŒ์‚ฌ ํ‹ฐ์ปค๋กœ `get_market_data_kr`๋ฅผ ํ˜ธ์ถœ โ†’ `valuation.marketCap`. **์ง€๋ถ„๊ฐ€์น˜ = ์žํšŒ์‚ฌ ์‹œ์ด ร— ์ง€์ฃผ์‚ฌ ๋ณด์œ  ์ง€๋ถ„์œจ.** ํ‘œ๋กœ ์ •๋ฆฌ(์žํšŒ์‚ฌ | ์ง€๋ถ„์œจ | ์žํšŒ์‚ฌ ์‹œ์ด | ๊ท€์† ์ง€๋ถ„๊ฐ€์น˜). ์ƒ์žฅ ์žํšŒ์‚ฌ ๊ฐ€์น˜๋Š” ์‹œ๊ฐ€๋ผ ๊ฐ€์žฅ ๊ฐ๊ด€์ ์ธ ๋ถ€๋ถ„์ด๋‹ค. + +## Step 3: ๋น„์ƒ์žฅ ์žํšŒ์‚ฌ + ์ž์ฒด์‚ฌ์—… + +๋น„์ƒ์žฅ ์žํšŒ์‚ฌ๋Š” (a) `get_financials_kr` ๋ณ„๋„/์—ฐ๊ฒฐ๋กœ ์ˆœ์ž์‚ฐ(์žฅ๋ถ€๊ฐ€) ๋˜๋Š” (b) ๋™์ข… ์ƒ์žฅ์‚ฌ ๋ฉ€ํ‹ฐํ”Œ(PER/PBR) ๊ฐ„์ด ์ ์šฉ์œผ๋กœ ์ถ”์ •ํ•œ๋‹ค โ€” ์ถ”์ •์ž„์„ ๋ช…์‹œ. ์ง€์ฃผ์‚ฌ ์ž์ฒด์‚ฌ์—…(์ƒํ‘œ๊ถŒ ์ˆ˜์ตยท์ž„๋Œ€ ๋“ฑ)์€ ๋ณ„๋„ ์ด์ต์— ๋ฉ€ํ‹ฐํ”Œ์„ ์–น์–ด ๋”ํ•œ๋‹ค. ๋ถˆํ™•์‹คํ•˜๋ฉด ๋ณด์ˆ˜์ ์œผ๋กœ. + +## Step 4: NAV ์ง‘๊ณ„ โ†’ ๋””์Šค์นด์šดํŠธ + +``` +NAV = ฮฃ(์ƒ์žฅ ์žํšŒ์‚ฌ ๊ท€์† ์ง€๋ถ„๊ฐ€์น˜) + ฮฃ(๋น„์ƒ์žฅยท์ž์ฒด์‚ฌ์—… ๊ฐ€์น˜) + ์ˆœํ˜„๊ธˆ โˆ’ ์ˆœ๋ถ€์ฑ„(์ง€์ฃผ์‚ฌ ๋ณ„๋„) +์ง€์ฃผ์‚ฌ ๋””์Šค์นด์šดํŠธ = 1 โˆ’ (์ง€์ฃผ์‚ฌ ์‹œ์ด รท NAV) +``` +์ง€์ฃผ์‚ฌ ์‹œ์ด์€ `get_market_data_kr`์˜ `valuation.marketCap`. ์ˆœ๋ถ€์ฑ„๋Š” ๋ณ„๋„(OFS) ๊ธฐ์ค€. + +## Step 5: ์ถœ๋ ฅ ํ˜•์‹ + +1. **ํ‰๊ฐ€**(1~2๋ฌธ์žฅ): NAV ๋Œ€๋น„ ํ• ์ธ์œจ + ์ขํ˜€์งˆ ๊ฐ€๋Šฅ์„ฑ ํŒ๋‹จ. +2. **SOTP ํ‘œ**: ์žํšŒ์‚ฌ๋ณ„ ๊ท€์†๊ฐ€์น˜ + NAV ํ•ฉ๊ณ„. +3. **๋””์Šค์นด์šดํŠธ**: ํ˜„์žฌ ์‹œ์ด vs NAV, % ํ• ์ธ. ํ•œ๊ตญ ์ง€์ฃผ์‚ฌ๋Š” ๋งŒ์„ฑ์ ์œผ๋กœ 40~60% ํ• ์ธ์ด ํ”ํ•˜๋‹ค๋Š” ๋งฅ๋ฝ. +4. **ํ• ์ธ ์ถ•์†Œ ํŠธ๋ฆฌ๊ฑฐ**: ์ž์‚ฌ์ฃผ ์†Œ๊ฐยท๋ฐฐ๋‹น ํ™•๋Œ€ยท๊ธฐ์—…๊ฐ€์น˜์ œ๊ณ (๋ฐธ๋ฅ˜์—…)๊ณ„ํšยท์žํšŒ์‚ฌ IPO ๋ถ€์žฌ(์ชผ๊ฐœ๊ธฐ ์ข…๋ฃŒ)ยท์ง€๋ฐฐ๊ตฌ์กฐ ๋‹จ์ˆœํ™”. (๋ฐธ๋ฅ˜์—… ์ƒ์„ธ๋Š” `kr-shareholder-return` ์ฐธ์กฐ.) +5. **์ฃผ์˜์‚ฌํ•ญ**: ๋”๋ธ”์นด์šดํŒ…(์—ฐ๊ฒฐ PER๋กœ ๋ณด์ง€ ๋ง ๊ฒƒ), ์ƒ์žฅ ์žํšŒ์‚ฌ ์ฃผ๊ฐ€ ๋ณ€๋™์— NAV๊ฐ€ ์ถœ๋ ์ž„, ๋น„์ƒ์žฅ ๊ฐ€์น˜๋Š” ์ถ”์ •. ๊ฑฐ๋ž˜์„ธยท๋ฐฐ๋‹น์„ธ ์„ธํ›„ ๋ณ„๋„. diff --git a/src/skills/registry.test.ts b/src/skills/registry.test.ts index a8a90e1a4..2ee85d07c 100644 --- a/src/skills/registry.test.ts +++ b/src/skills/registry.test.ts @@ -8,7 +8,16 @@ afterEach(() => { describe('discoverSkills', () => { it('discovers all builtin skills with valid frontmatter', () => { const names = discoverSkills().map((s) => s.name); - for (const expected of ['dcf-valuation', 'write-memo', 'x-research', 'kr-spinoff-analysis']) { + for (const expected of [ + 'dcf-valuation', + 'write-memo', + 'x-research', + 'kr-spinoff-analysis', + 'kr-relative-valuation', + 'kr-sotp-holdco', + 'kr-earnings-quality', + 'kr-shareholder-return', + ]) { expect(names).toContain(expected); } }); diff --git a/src/tools/finance-kr/dart-document.ts b/src/tools/finance-kr/dart-document.ts index f89d7f7bf..048bff9c5 100644 --- a/src/tools/finance-kr/dart-document.ts +++ b/src/tools/finance-kr/dart-document.ts @@ -142,6 +142,31 @@ export function sectionText(section: DsdSection): string { return dsdToPlainText(section.html); } +/** + * Extract every `` in a DSD fragment as an array of rows (each row = the + * ordered cell texts). DART tables are plain `` / `` with `

`-wrapped + * cell text; we flatten inner tags, decode entities, and collapse whitespace. + * colspan/rowspan are NOT expanded โ€” cells are taken in document order, which still + * yields a readable row for merged headers. `dsdToPlainText` deliberately DROPS tables + * (numbers belong to get_financials_kr), so this is the only path that recovers the + * per-๋ถ€๋ฌธ (segment) summary tables for get_segments_kr. + */ +export function parseDsdTables(fragment: string): string[][][] { + const tables: string[][][] = []; + for (const tbl of fragment.match(//gi) ?? []) { + const rows: string[][] = []; + for (const tr of tbl.match(//gi) ?? []) { + const cells: string[] = []; + for (const m of tr.matchAll(/]*>([\s\S]*?)<\/T[DH]>/gi)) { + cells.push(decodeEntities(m[1].replace(/<[^>]+>/g, ' ')).replace(/\s+/g, ' ').trim()); + } + if (cells.length > 0) rows.push(cells); + } + if (rows.length > 0) tables.push(rows); + } + return tables; +} + // --------------------------------------------------------------------------- // Section splitting + selection // --------------------------------------------------------------------------- diff --git a/src/tools/finance-kr/get-filings-kr.ts b/src/tools/finance-kr/get-filings-kr.ts index d856a844d..e9f0730c5 100644 --- a/src/tools/finance-kr/get-filings-kr.ts +++ b/src/tools/finance-kr/get-filings-kr.ts @@ -2,7 +2,7 @@ import { DynamicStructuredTool } from '@langchain/core/tools'; import { z } from 'zod'; import { dartApi } from './api.js'; import { toDartDate, isNoDataError } from './utils.js'; -import { resolveTicker } from '../../data/ticker-registry.js'; +import { resolveKrSecurity } from './resolve-kr.js'; import { formatToolResult } from '../types.js'; import { TTL_1H } from '../finance/utils.js'; @@ -23,8 +23,8 @@ const PBLNTF_TY: Record = { const InputSchema = z.object({ ticker: z .string() - .regex(/^\d{6}$/, 'Korean ticker must be a 6-digit string (e.g. 005930 for Samsung).') - .describe('6-digit Korean stock ticker (e.g. 005930 for Samsung Electronics).'), + .min(1) + .describe('6-digit Korean stock ticker (e.g. 005930) OR the company name (e.g. ์‚ผ์„ฑ์ „์ž) โ€” a name is resolved to its DART listing automatically.'), filing_type: z .enum(['periodic', 'material', 'issuance', 'ownership', 'audit', 'exchange']) .optional() @@ -65,11 +65,15 @@ export const getFilingsKr = new DynamicStructuredTool({ description: GET_FILINGS_KR_DESCRIPTION, schema: InputSchema, func: async (input) => { - const ticker = input.ticker.trim(); - const resolved = await resolveTicker(ticker); - if (!resolved) { - return formatToolResult({ error: `Ticker ${ticker} not found in DART corp registry` }, []); + const sec = await resolveKrSecurity(input.ticker); + if (!sec?.corpCode) { + return formatToolResult( + { ticker: input.ticker, _error: `Could not resolve "${input.ticker}" to a DART corp_code โ€” pass a 6-digit ticker or an exact company name` }, + [], + ); } + const ticker = sec.stockCode; + const resolved = { corp_code: sec.corpCode, corp_name: sec.name ?? '' }; const range = defaultRange(); const params = { diff --git a/src/tools/finance-kr/get-financials-kr.ts b/src/tools/finance-kr/get-financials-kr.ts index a1d433e47..d07af4e08 100644 --- a/src/tools/finance-kr/get-financials-kr.ts +++ b/src/tools/finance-kr/get-financials-kr.ts @@ -48,18 +48,9 @@ Given a user's natural language query about a Korean (KOSPI/KOSDAQ) listed compa ## Guidelines -1. **Ticker Resolution** โ€” Convert Korean company names to 6-digit ticker codes: - - Samsung Electronics / ์‚ผ์„ฑ์ „์ž โ†’ 005930 - - SK Hynix / SKํ•˜์ด๋‹‰์Šค โ†’ 000660 - - Hyundai Motor / ํ˜„๋Œ€์ฐจ โ†’ 005380 - - LG Energy Solution / LG์—๋„ˆ์ง€์†”๋ฃจ์…˜ โ†’ 373220 - - Naver / ๋„ค์ด๋ฒ„ โ†’ 035420 - - Kakao / ์นด์นด์˜ค โ†’ 035720 - - Samsung Biologics / ์‚ผ์„ฑ๋ฐ”์ด์˜ค๋กœ์ง์Šค โ†’ 207940 - - Celltrion / ์…€ํŠธ๋ฆฌ์˜จ โ†’ 068270 - - POSCO Holdings / ํฌ์Šค์ฝ”ํ™€๋”ฉ์Šค โ†’ 005490 - - Hyundai Mobis / ํ˜„๋Œ€๋ชจ๋น„์Šค โ†’ 012330 - Strip any suffix the user provides (".KS", ".KQ") โ€” pass only the 6-digit code. +1. **Ticker / name** โ€” get_business_report_kr's \`ticker\` field accepts EITHER a 6-digit code OR a Korean company name; it resolves names to codes deterministically (DART registry / Naver). So: + - If the user gave a 6-digit code, pass it (strip any ".KS"/".KQ" suffix โ†’ just the 6 digits). + - Otherwise pass the company NAME verbatim (e.g. "์‚ผ์„ฑ์ „์ž", "๊ณ ๋ ค์•„์—ฐ"). Do NOT guess a code from memory for less-common names โ€” a wrong guess silently returns a different company. 2. **Report Type Selection** (report_type): - "์‚ฌ์—…๋ณด๊ณ ์„œ" / "annual" / "์—ฐ๊ฐ„" โ†’ annual (reprt_code 11011, typically filed in March) diff --git a/src/tools/finance-kr/get-foreign-ownership-kr.ts b/src/tools/finance-kr/get-foreign-ownership-kr.ts index 51e675ef3..86dec2866 100644 --- a/src/tools/finance-kr/get-foreign-ownership-kr.ts +++ b/src/tools/finance-kr/get-foreign-ownership-kr.ts @@ -1,6 +1,7 @@ import { DynamicStructuredTool } from '@langchain/core/tools'; import { z } from 'zod'; import { fetchNaverTrend } from './naver-api.js'; +import { resolveKrSecurity } from './resolve-kr.js'; import { parseKrxNumber, toIsoDate, deadColumns } from './utils.js'; import { formatToolResult } from '../types.js'; import { TTL_6H } from '../finance/utils.js'; @@ -12,8 +13,8 @@ Each daily row includes the foreign holding ratio as a % of shares (foreignHoldR const InputSchema = z.object({ ticker: z .string() - .regex(/^\d{6}$/, 'Korean ticker must be a 6-digit string (e.g. 005930 for Samsung).') - .describe('6-digit Korean stock ticker (e.g. 005930 for Samsung Electronics).'), + .min(1) + .describe('6-digit Korean stock ticker (e.g. 005930) OR the company name (e.g. ์‚ผ์„ฑ์ „์ž) โ€” a name is resolved to its listing automatically.'), limit: z .number() .int() @@ -51,8 +52,15 @@ export const getForeignOwnershipKr = new DynamicStructuredTool({ description: GET_FOREIGN_OWNERSHIP_KR_DESCRIPTION, schema: InputSchema, func: async (input) => { - const ticker = input.ticker.trim(); - const base = { ticker, ownership: [] as ForeignOwnershipRow[] }; + const resolved = await resolveKrSecurity(input.ticker); + if (!resolved) { + return formatToolResult( + { ticker: input.ticker, ownership: [] as ForeignOwnershipRow[], _error: `Could not resolve "${input.ticker}" to a Korean listing โ€” pass a 6-digit ticker or an exact company name` }, + [], + ); + } + const ticker = resolved.stockCode; + const base = { ticker, name: resolved.name, ownership: [] as ForeignOwnershipRow[] }; try { const { rows, url } = await fetchNaverTrend(ticker, { cacheable: true, ttlMs: TTL_6H }); const ownership = rows diff --git a/src/tools/finance-kr/get-insider-trades-kr.ts b/src/tools/finance-kr/get-insider-trades-kr.ts index 0c1ed8472..4c54f3475 100644 --- a/src/tools/finance-kr/get-insider-trades-kr.ts +++ b/src/tools/finance-kr/get-insider-trades-kr.ts @@ -2,7 +2,7 @@ import { DynamicStructuredTool } from '@langchain/core/tools'; import { z } from 'zod'; import { dartApi } from './api.js'; import { sortByRceptDtDesc, isNoDataError } from './utils.js'; -import { resolveTicker } from '../../data/ticker-registry.js'; +import { resolveKrSecurity } from './resolve-kr.js'; import { formatToolResult } from '../types.js'; import { TTL_24H } from '../finance/utils.js'; @@ -13,8 +13,8 @@ Each report includes the reporter (repror), relationship to the company (isu_exc const InputSchema = z.object({ ticker: z .string() - .regex(/^\d{6}$/, 'Korean ticker must be a 6-digit string (e.g. 005930 for Samsung).') - .describe('6-digit Korean stock ticker (e.g. 005930 for Samsung Electronics).'), + .min(1) + .describe('6-digit Korean stock ticker (e.g. 005930) OR the company name (e.g. ์‚ผ์„ฑ์ „์ž) โ€” a name is resolved to its DART listing automatically.'), limit: z .number() .int() @@ -29,11 +29,15 @@ export const getInsiderTradesKr = new DynamicStructuredTool({ description: GET_INSIDER_TRADES_KR_DESCRIPTION, schema: InputSchema, func: async (input) => { - const ticker = input.ticker.trim(); - const resolved = await resolveTicker(ticker); - if (!resolved) { - return formatToolResult({ error: `Ticker ${ticker} not found in DART corp registry` }, []); + const sec = await resolveKrSecurity(input.ticker); + if (!sec?.corpCode) { + return formatToolResult( + { ticker: input.ticker, _error: `Could not resolve "${input.ticker}" to a DART corp_code โ€” pass a 6-digit ticker or an exact company name` }, + [], + ); } + const ticker = sec.stockCode; + const resolved = { corp_code: sec.corpCode, corp_name: sec.name ?? '' }; try { // elestock.json has no date filter โ€” returns full history; sort + cap client-side. diff --git a/src/tools/finance-kr/get-large-holders-kr.ts b/src/tools/finance-kr/get-large-holders-kr.ts index 734fa34ac..62358f9c3 100644 --- a/src/tools/finance-kr/get-large-holders-kr.ts +++ b/src/tools/finance-kr/get-large-holders-kr.ts @@ -2,7 +2,7 @@ import { DynamicStructuredTool } from '@langchain/core/tools'; import { z } from 'zod'; import { dartApi } from './api.js'; import { sortByRceptDtDesc, isNoDataError } from './utils.js'; -import { resolveTicker } from '../../data/ticker-registry.js'; +import { resolveKrSecurity } from './resolve-kr.js'; import { formatToolResult } from '../types.js'; import { TTL_24H } from '../finance/utils.js'; @@ -13,8 +13,8 @@ Each report includes the filer (repror), shares held (stkqy), ownership ratio (s const InputSchema = z.object({ ticker: z .string() - .regex(/^\d{6}$/, 'Korean ticker must be a 6-digit string (e.g. 005930 for Samsung).') - .describe('6-digit Korean stock ticker (e.g. 005930 for Samsung Electronics).'), + .min(1) + .describe('6-digit Korean stock ticker (e.g. 005930) OR the company name (e.g. ์‚ผ์„ฑ์ „์ž) โ€” a name is resolved to its DART listing automatically.'), limit: z .number() .int() @@ -29,11 +29,15 @@ export const getLargeHoldersKr = new DynamicStructuredTool({ description: GET_LARGE_HOLDERS_KR_DESCRIPTION, schema: InputSchema, func: async (input) => { - const ticker = input.ticker.trim(); - const resolved = await resolveTicker(ticker); - if (!resolved) { - return formatToolResult({ error: `Ticker ${ticker} not found in DART corp registry` }, []); + const sec = await resolveKrSecurity(input.ticker); + if (!sec?.corpCode) { + return formatToolResult( + { ticker: input.ticker, _error: `Could not resolve "${input.ticker}" to a DART corp_code โ€” pass a 6-digit ticker or an exact company name` }, + [], + ); } + const ticker = sec.stockCode; + const resolved = { corp_code: sec.corpCode, corp_name: sec.name ?? '' }; try { // majorstock.json has no date filter โ€” returns full history; sort + cap client-side. diff --git a/src/tools/finance-kr/get-market-data-kr.ts b/src/tools/finance-kr/get-market-data-kr.ts index f047bdeba..8e4789b90 100644 --- a/src/tools/finance-kr/get-market-data-kr.ts +++ b/src/tools/finance-kr/get-market-data-kr.ts @@ -1,6 +1,7 @@ import { DynamicStructuredTool } from '@langchain/core/tools'; import { z } from 'zod'; import { fetchNaverIntegration } from './naver-api.js'; +import { resolveKrSecurity } from './resolve-kr.js'; import { parseNaverMetric, toIsoDate, nullFields } from './utils.js'; import { formatToolResult } from '../types.js'; import { TTL_1H } from '../finance/utils.js'; @@ -12,8 +13,8 @@ Use this for current price, market cap, shares outstanding, PER/PBR/EV multiples const InputSchema = z.object({ ticker: z .string() - .regex(/^\d{6}$/, 'Korean ticker must be a 6-digit string (e.g. 005930 for Samsung).') - .describe('6-digit Korean stock ticker (e.g. 005930 for Samsung Electronics).'), + .min(1) + .describe('6-digit Korean stock ticker (e.g. 005930) OR the company name (e.g. ์‚ผ์„ฑ์ „์ž) โ€” a name is resolved to its listing automatically.'), }); export interface MarketDataKr { @@ -235,7 +236,14 @@ export const getMarketDataKr = new DynamicStructuredTool({ description: GET_MARKET_DATA_KR_DESCRIPTION, schema: InputSchema, func: async (input) => { - const ticker = input.ticker.trim(); + const resolved = await resolveKrSecurity(input.ticker); + if (!resolved) { + return formatToolResult( + { ticker: input.ticker, _error: `Could not resolve "${input.ticker}" to a Korean listing โ€” pass a 6-digit ticker or an exact company name` }, + [], + ); + } + const ticker = resolved.stockCode; try { const { data, url } = await fetchNaverIntegration(ticker, { cacheable: true, ttlMs: TTL_1H }); const mapped = data ? mapMarketData(ticker, data) : null; diff --git a/src/tools/finance-kr/get-segments-kr.test.ts b/src/tools/finance-kr/get-segments-kr.test.ts new file mode 100644 index 000000000..c6911e829 --- /dev/null +++ b/src/tools/finance-kr/get-segments-kr.test.ts @@ -0,0 +1,69 @@ +import { describe, it, expect } from 'bun:test'; +import { isNumericCell, selectSegmentTables } from './get-segments-kr.js'; +import { parseDsdTables } from './dart-document.js'; + +describe('isNumericCell', () => { + it('treats formatted Korean financial figures as numeric', () => { + for (const s of ['526,547', '39.3%', '(1,234)', 'โ–ณ123', '1,879,673']) { + expect(isNumericCell(s)).toBe(true); + } + }); + it('rejects label cells', () => { + for (const s of ['DX ๋ถ€๋ฌธ', '์ œ58๊ธฐ 1๋ถ„๊ธฐ', '๋งค์ถœ์•ก', '']) { + expect(isNumericCell(s)).toBe(false); + } + }); +}); + +describe('selectSegmentTables', () => { + // Mirrors the real 005930 ๋ถ„๊ธฐ๋ณด๊ณ ์„œ shapes: the ๋ถ€๋ฌธ๋ณ„ P&L summary, a product-type + // table (no ์˜์—…์ด์ต), a sprawling R&D narrative, and an unrelated table. + const pnl = [ + ['๋ถ€๋ฌธ', '๊ตฌ๋ถ„', '์ œ58๊ธฐ 1๋ถ„๊ธฐ', '์ œ57๊ธฐ', '์ œ56๊ธฐ'], + ['DX ๋ถ€๋ฌธ', '๋งค์ถœ์•ก', '526,547', '1,879,673', '1,748,877'], + ['', '์˜์—…์ด์ต', '29,677', '128,527', '124,399'], + ['DS ๋ถ€๋ฌธ', '๋งค์ถœ์•ก', '817,156', '1,301,282', '1,110,660'], + ['', '์˜์—…์ด์ต', '69,000', '150,000', '230,000'], + ]; + const productType = [ + ['๋ถ€๋ฌธ', '๋งค์ถœ์œ ํ˜•', 'ํ’ˆ๋ชฉ', '์ œ58๊ธฐ 1๋ถ„๊ธฐ'], + ['DX ๋ถ€๋ฌธ', '์ œยท์ƒํ’ˆ', 'TV, ๋ชจ๋‹ˆํ„ฐ ๋“ฑ', '526,547'], + ['DS ๋ถ€๋ฌธ', '์ œยท์ƒํ’ˆ', 'DRAM, NAND ๋“ฑ', '817,156'], + ['SDC', '์ œยท์ƒํ’ˆ', 'OLED ํŒจ๋„ ๋“ฑ', '66,935'], + ]; + const rndNarrative = Array.from({ length: 90 }, (_, i) => ['DX ๋ถ€๋ฌธ', `์—ฐ๊ตฌ๊ณผ์ œ ${i}`, '๊ธฐ๋Œ€ํšจ๊ณผ ํ…์ŠคํŠธ, ๋งค์ถœ ๊ธฐ์—ฌ โ€ฆ']); + const unrelated = [ + ['๊ตฌ๋ถ„', '๊ธˆ์•ก'], + ['ํ˜„๊ธˆ', '1,000'], + ['์˜ˆ๊ธˆ', '2,000'], + ]; + + it('keeps segment financial tables and ranks the P&L (์˜์—…์ด์ต) summary first', () => { + const out = selectSegmentTables([rndNarrative, productType, pnl, unrelated], 3); + expect(out.length).toBeGreaterThanOrEqual(2); + expect(out[0].hasOperatingProfit).toBe(true); + expect(out[0].rows[1]).toContain('๋งค์ถœ์•ก'); + // unrelated table (no ๋ถ€๋ฌธ) is excluded + expect(out.every((t) => t.rows.flat().join(' ').includes('๋ถ€๋ฌธ'))).toBe(true); + }); + + it('respects the limit', () => { + expect(selectSegmentTables([rndNarrative, productType, pnl], 1).length).toBe(1); + expect(selectSegmentTables([rndNarrative, productType, pnl], 1)[0].hasOperatingProfit).toBe(true); + }); + + it('parses DSD

markup into rows and feeds selection end-to-end', () => { + const markup = + '
' + + '' + + '' + + '' + + '

๋ถ€๋ฌธ

๊ตฌ๋ถ„

์ œ58๊ธฐ

์ œ57๊ธฐ

์ œ56๊ธฐ

DX ๋ถ€๋ฌธ

๋งค์ถœ์•ก

526,547

1,879,673

1,748,877

ใ€€

์˜์—…์ด์ต

29,677

128,527

124,399

'; + const tables = parseDsdTables(markup); + expect(tables.length).toBe(1); + expect(tables[0][1]).toEqual(['DX ๋ถ€๋ฌธ', '๋งค์ถœ์•ก', '526,547', '1,879,673', '1,748,877']); + const out = selectSegmentTables(tables, 3); + expect(out.length).toBe(1); + expect(out[0].hasOperatingProfit).toBe(true); + }); +}); diff --git a/src/tools/finance-kr/get-segments-kr.ts b/src/tools/finance-kr/get-segments-kr.ts new file mode 100644 index 000000000..08f311cfa --- /dev/null +++ b/src/tools/finance-kr/get-segments-kr.ts @@ -0,0 +1,188 @@ +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { z } from 'zod'; +import { dartApi } from './api.js'; +import { resolveKrSecurity } from './resolve-kr.js'; +import { extractDsdBody, parseDsdTables } from './dart-document.js'; +import { isNoDataError } from './utils.js'; +import { formatToolResult } from '../types.js'; +import { TTL_1H } from '../finance/utils.js'; + +export const GET_SEGMENTS_KR_DESCRIPTION = `Retrieves ์‚ฌ์—…๋ถ€๋ฌธ๋ณ„ ์š”์•ฝ ์žฌ๋ฌดํ˜„ํ™ฉ (segment / divisional financials) for a Korean (KOSPI/KOSDAQ) listed company from its latest DART periodic report (์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ). For a ์žฌ๋ฒŒ/๋ณตํ•ฉ ๊ธฐ์—… the segment mix IS the thesis โ€” e.g. Samsung's DS(๋ฐ˜๋„์ฒด) vs DX(๊ฐ€์ „ยท๋ชจ๋ฐ”์ผ) vs SDC, or Hyundai's ๊ธˆ์œต vs ์ž๋™์ฐจ โ€” yet the headline consolidated P&L hides it. + +Returns the segment summary tables as structured rows (๋ถ€๋ฌธ ร— ๋งค์ถœ์•ก/์˜์—…์ด์ต/๋น„์ค‘, usually across 2โ€“3 periods), recovered from the report's narrative tables that the other tools strip. Accepts a 6-digit ticker (e.g. 005930) or a Korean company name (e.g. ์‚ผ์„ฑ์ „์ž). Use this for "์‚ฌ์—…๋ถ€๋ฌธ๋ณ„ ์‹ค์ ", "๋ถ€๋ฌธ๋ณ„ ๋งค์ถœ/์˜์—…์ด์ต", "์„ธ๊ทธ๋จผํŠธ ๋น„์ค‘", or to see which division drives earnings. For consolidated K-IFRS statements use get_financials_kr; for the qualitative business description use read_filings_kr.`; + +const InputSchema = z.object({ + ticker: z + .string() + .min(1) + .describe('6-digit Korean stock ticker (e.g. 005930) OR the company name (e.g. ์‚ผ์„ฑ์ „์ž) โ€” a name is resolved to its DART listing automatically.'), + limit: z + .number() + .int() + .min(1) + .max(6) + .default(3) + .describe('Maximum number of segment tables to return (default 3, ranked best-first).'), +}); + +const PERIODIC_RE = /์‚ฌ์—…๋ณด๊ณ ์„œ|๋ฐ˜๊ธฐ๋ณด๊ณ ์„œ|๋ถ„๊ธฐ๋ณด๊ณ ์„œ/; +const SEGMENT_KW = /๋ถ€๋ฌธ|์„ธ๊ทธ๋จผํŠธ/; +const REVENUE_KW = /๋งค์ถœ|์ˆ˜์ต/; +const OPINC_KW = /์˜์—…์ด์ต|์˜์—…์†์ต/; + +const MAX_ROWS = 30; +const MAX_COLS = 14; +const MAX_CELL = 80; + +export interface SegmentTable { + rowCount: number; + cols: number; + hasOperatingProfit: boolean; + truncated: boolean; + rows: string[][]; +} + +/** A cell is "numeric" if, stripped of formatting (commas, %, parens, signs), it is a number. */ +export function isNumericCell(s: string): boolean { + const t = s.replace(/[\s,()%โ–ณโ–ฒโ–ฝโ–ผ\-+]/g, ''); + return t.length > 0 && /^\d+(\.\d+)?$/.test(t); +} + +/** + * Rank and trim the parsed tables down to the segment financial-summary tables. + * + * A table qualifies when it mentions a segment term (๋ถ€๋ฌธ/์„ธ๊ทธ๋จผํŠธ) AND a revenue term, + * is not a giant narrative table, and carries enough numeric cells. The per-๋ถ€๋ฌธ P&L + * summary (๋งค์ถœ์•ก + ์˜์—…์ด์ต rows) is what an analyst wants, so tables containing + * ์˜์—…์ด์ต sort first; compact tables outrank sprawling product/R&D listings that merely + * mention a ๋ถ€๋ฌธ. Pure so it is unit-testable without a live DART fetch. + */ +export function selectSegmentTables(tables: string[][][], limit: number): SegmentTable[] { + const scored: { score: number; table: SegmentTable }[] = []; + for (const rows of tables) { + const flat = rows.flat().join(' '); + if (!SEGMENT_KW.test(flat) || !REVENUE_KW.test(flat)) continue; + if (rows.length < 2 || rows.length > 80) continue; + const numCount = rows.flat().filter(isNumericCell).length; + if (numCount < 3) continue; + + const hasOp = OPINC_KW.test(flat); + const cols = Math.min(MAX_COLS, Math.max(...rows.map((r) => r.length))); + const trimmedRows = rows + .slice(0, MAX_ROWS) + .map((r) => r.slice(0, MAX_COLS).map((c) => (c.length > MAX_CELL ? c.slice(0, MAX_CELL) + 'โ€ฆ' : c))); + const truncated = rows.length > MAX_ROWS || rows.some((r) => r.length > MAX_COLS); + + // hasOp dominates (the segment P&L); then prefer compact tables; then numeric density. + const score = (hasOp ? 1_000_000 : 0) + (rows.length <= 40 ? 1_000 : 0) + numCount; + scored.push({ + score, + table: { rowCount: rows.length, cols, hasOperatingProfit: hasOp, truncated, rows: trimmedRows }, + }); + } + scored.sort((a, b) => b.score - a.score); + return scored.slice(0, limit).map((s) => s.table); +} + +function defaultRange(): { bgn_de: string; end_de: string } { + const today = new Date(); + const start = new Date(today); + start.setUTCMonth(today.getUTCMonth() - 18); + const fmt = (d: Date) => d.toISOString().slice(0, 10).replace(/-/g, ''); + return { bgn_de: fmt(start), end_de: fmt(today) }; +} + +export const getSegmentsKr = new DynamicStructuredTool({ + name: 'get_segments_kr', + description: GET_SEGMENTS_KR_DESCRIPTION, + schema: InputSchema, + func: async (input) => { + const sec = await resolveKrSecurity(input.ticker); + if (!sec?.corpCode) { + return formatToolResult( + { ticker: input.ticker, _error: `Could not resolve "${input.ticker}" to a DART corp_code โ€” pass a 6-digit ticker or an exact company name` }, + [], + ); + } + const identity = { ticker: sec.stockCode, corp_code: sec.corpCode, corp_name: sec.name ?? '' }; + + // 1. Find the most recent periodic report (์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ๋ณด๊ณ ์„œ). + const range = defaultRange(); + let filings: Record[] = []; + let listUrl = ''; + try { + const { data, url } = await dartApi.get( + '/list.json', + { + corp_code: sec.corpCode, + bgn_de: range.bgn_de, + end_de: range.end_de, + pblntf_ty: 'A', + page_count: 100, + page_no: 1, + sort: 'date', + sort_mth: 'desc', + }, + { cacheable: true, ttlMs: TTL_1H }, + ); + listUrl = url; + filings = Array.isArray(data.list) ? (data.list as Record[]) : []; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + if (!isNoDataError(message)) { + return formatToolResult({ ...identity, _error: `DART filing search failed: ${message}` }, []); + } + } + + const report = filings.find((f) => PERIODIC_RE.test(String(f.report_nm ?? ''))); + if (!report?.rcept_no) { + return formatToolResult( + { ...identity, segments: [], note: '์ตœ๊ทผ 18๊ฐœ์›” ๋‚ด ์ •๊ธฐ๋ณด๊ณ ์„œ(์‚ฌ์—…/๋ฐ˜๊ธฐ/๋ถ„๊ธฐ)๋ฅผ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.' }, + listUrl ? [listUrl] : [], + ); + } + const reportNm = String(report.report_nm ?? ''); + const rceptNo = String(report.rcept_no); + + // 2. Fetch the document, recover its tables, and keep the segment summaries. + let segments: SegmentTable[]; + let docUrl = ''; + try { + const { bytes, url } = await dartApi.getBinary('/document.xml', { rcept_no: rceptNo }); + docUrl = url; + const body = extractDsdBody(bytes); + segments = selectSegmentTables(parseDsdTables(body), input.limit); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return formatToolResult( + { ...identity, report_nm: reportNm, rcept_no: rceptNo, _error: `Failed to read DART document: ${message}` }, + listUrl ? [listUrl] : [], + ); + } + + const sourceUrls = [docUrl, listUrl].filter(Boolean); + if (segments.length === 0) { + return formatToolResult( + { + ...identity, + report_nm: reportNm, + rcept_no: rceptNo, + segments: [], + note: '์ด ๋ณด๊ณ ์„œ์—์„œ ์‚ฌ์—…๋ถ€๋ฌธ๋ณ„ ์š”์•ฝ ์žฌ๋ฌดํ˜„ํ™ฉ ํ‘œ๋ฅผ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค(๋‹จ์ผ ์‚ฌ์—…๋ถ€๋ฌธ ๊ธฐ์—…์ด๊ฑฐ๋‚˜ ํ‘œ ํ˜•์‹ ์ƒ์ด). ์ •์„ฑ์  ์‚ฌ์—… ๊ตฌ์„ฑ์€ read_filings_kr["business"]๋กœ ํ™•์ธํ•˜์„ธ์š”.', + }, + sourceUrls, + ); + } + + return formatToolResult( + { + ...identity, + report_nm: reportNm, + rcept_no: rceptNo, + note: '๋ถ€๋ฌธ๋ณ„ ์š”์•ฝ ์žฌ๋ฌดํ˜„ํ™ฉ ํ‘œ(์›๋ฌธ ์ถ”์ถœ). ๊ธˆ์•ก ๋‹จ์œ„๋Š” ๋ณดํ†ต ๋ฐฑ๋งŒ์›์ด๋ฉฐ ํ‘œ ๋จธ๋ฆฌ๊ธ€/์›๋ณด๊ณ ์„œ๋กœ ํ™•์ธํ•˜์„ธ์š”. 1์ˆœ์œ„ ํ‘œ๊ฐ€ ๋ถ€๋ฌธ๋ณ„ ๋งค์ถœ์•กยท์˜์—…์ด์ต ์š”์•ฝ์ผ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค.', + segments, + }, + sourceUrls, + ); + }, +}); diff --git a/src/tools/finance-kr/get-short-balance-kr.ts b/src/tools/finance-kr/get-short-balance-kr.ts index 104b0158d..d0119e54a 100644 --- a/src/tools/finance-kr/get-short-balance-kr.ts +++ b/src/tools/finance-kr/get-short-balance-kr.ts @@ -3,6 +3,7 @@ import { z } from 'zod'; import { krxApi } from './krx-api.js'; import { toDartDate, toIsoDate, parseKrxNumber } from './utils.js'; import { resolveIsin } from '../../data/krx-instrument-registry.js'; +import { resolveKrSecurity } from './resolve-kr.js'; import { formatToolResult } from '../types.js'; import { TTL_6H } from '../finance/utils.js'; @@ -15,8 +16,8 @@ Note: investors below the 0.01% reporting threshold are not aggregated here, so const InputSchema = z.object({ ticker: z .string() - .regex(/^\d{6}$/, 'Korean ticker must be a 6-digit string (e.g. 005930 for Samsung).') - .describe('6-digit Korean stock ticker (e.g. 005930 for Samsung Electronics).'), + .min(1) + .describe('6-digit Korean stock ticker (e.g. 005930) OR the company name (e.g. ์‚ผ์„ฑ์ „์ž) โ€” a name is resolved to its listing automatically.'), startDate: z .string() .regex(/^\d{4}-\d{2}-\d{2}$/, 'Use ISO YYYY-MM-DD.') @@ -70,7 +71,14 @@ export const getShortBalanceKr = new DynamicStructuredTool({ description: GET_SHORT_BALANCE_KR_DESCRIPTION, schema: InputSchema, func: async (input) => { - const ticker = input.ticker.trim(); + const sec = await resolveKrSecurity(input.ticker); + if (!sec) { + return formatToolResult( + { ticker: input.ticker, short: [] as ShortBalanceRow[], _error: `Could not resolve "${input.ticker}" to a Korean listing โ€” pass a 6-digit ticker or an exact company name` }, + [], + ); + } + const ticker = sec.stockCode; const { startDate, endDate } = defaultRange(input.startDate, input.endDate); try { diff --git a/src/tools/finance-kr/naver-api.ts b/src/tools/finance-kr/naver-api.ts index 6317e04e2..96ae30d55 100644 --- a/src/tools/finance-kr/naver-api.ts +++ b/src/tools/finance-kr/naver-api.ts @@ -10,6 +10,7 @@ import { readCache, writeCache } from '../../utils/cache.js'; import { logger } from '../../utils/logger.js'; const BASE_URL = 'https://m.stock.naver.com/api/stock'; +const AUTOCOMPLETE_URL = 'https://ac.stock.naver.com/ac'; const USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Safari/604.1'; @@ -122,3 +123,63 @@ export async function fetchNaverIntegration( } return { data, url }; } + +export interface NaverStockMatch { + /** 6-digit listing code. */ + code: string; + /** Canonical listing name as Naver indexes it. */ + name: string; + /** Market type code (KOSPI / KOSDAQ) when present. */ + market: string | null; +} + +/** + * Pick the best domestic-equity match from a Naver autocomplete `items` array. + * + * Common/preferred equities carry a 6-DIGIT NUMERIC code (005930 ๋ณดํ†ต์ฃผ, 005935 ์šฐ์„ ์ฃผ); + * ETF/ETN/fund look-alikes use a 6-char ALPHANUMERIC code (e.g. 0162Z0 "RISE ์‚ผ์„ฑ์ „์žโ€ฆ"), + * so the numeric filter drops leveraged/derivative entries that merely contain the name. + * Among survivors, prefer an exact whitespace-insensitive name hit; otherwise take the + * first (Naver ranks the canonical listing first). Pure so it is unit-testable. + */ +export function pickBestNaverMatch(items: unknown, query: string): NaverStockMatch | null { + const arr = Array.isArray(items) ? (items as Record[]) : []; + const norm = (s: string) => s.replace(/\s+/g, '').toLowerCase(); + const candidates = arr + .map((it) => ({ + code: String(it.code ?? ''), + name: String(it.name ?? ''), + market: (it.typeCode as string) ?? null, + nation: String(it.nationCode ?? ''), + category: String(it.category ?? ''), + })) + .filter((c) => /^\d{6}$/.test(c.code) && c.nation === 'KOR' && c.category === 'stock'); + if (candidates.length === 0) return null; + const q = norm(query); + const exact = candidates.find((c) => norm(c.name) === q); + const best = exact ?? candidates[0]; + return { code: best.code, name: best.name, market: best.market }; +} + +/** + * Resolve a Korean company NAME to its 6-digit listing via Naver's keyless + * autocomplete (`ac.stock.naver.com`) โ€” no DART key required. Returns the best + * domestic-equity match, or null on a miss / network error. This is the keyless + * fallback for name resolution when the DART corp registry is unavailable. + */ +export async function fetchNaverAutocomplete(query: string): Promise { + const q = query.trim(); + if (!q) return null; + const url = `${AUTOCOMPLETE_URL}?q=${encodeURIComponent(q)}&target=stock&st=111`; + let response: Response; + try { + response = await fetch(url, { headers: { 'User-Agent': USER_AGENT, Accept: 'application/json' } }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`[Naver AC] network error: ${q} โ€” ${message}`); + return null; + } + if (!response.ok) return null; + const json = (await response.json().catch(() => null)) as { items?: unknown } | null; + return pickBestNaverMatch(json?.items, q); +} diff --git a/src/tools/finance-kr/read-filings-kr.ts b/src/tools/finance-kr/read-filings-kr.ts index 65002f3c2..cbd2dfcd0 100644 --- a/src/tools/finance-kr/read-filings-kr.ts +++ b/src/tools/finance-kr/read-filings-kr.ts @@ -6,7 +6,7 @@ import { callLlm, getFastModel } from '../../model/llm.js'; import { resolveProvider } from '../../providers.js'; import { formatToolResult } from '../types.js'; import { getCurrentDate } from '../../agent/prompts.js'; -import { resolveTicker } from '../../data/ticker-registry.js'; +import { resolveKrSecurity } from './resolve-kr.js'; import { dexterPath } from '../../utils/paths.js'; import { readCache, writeCache } from '../../utils/cache.js'; import { withTimeout, TTL_1H, TTL_24H } from '../finance/utils.js'; @@ -48,7 +48,7 @@ type ReportType = 'annual' | 'semiannual' | 'quarterly_1' | 'quarterly_3'; const PlanSchema = z.object({ ticker: z .string() - .describe('6-digit Korean stock ticker (e.g. 005930 for Samsung). Resolve company names to tickers yourself.'), + .describe('6-digit Korean stock ticker (e.g. 005930) OR the company name (e.g. ์‚ผ์„ฑ์ „์ž). If unsure of the exact code, pass the name verbatim โ€” it is resolved automatically; do NOT guess a code from memory.'), report_type: z .enum(['annual', 'semiannual', 'quarterly_1', 'quarterly_3']) .describe( @@ -79,7 +79,7 @@ function buildPlanPrompt(): string { Current date: ${getCurrentDate()} Given a user query about a Korean company's report narrative, return a structured plan: -- ticker (6-digit; resolve names: ์‚ผ์„ฑ์ „์žโ†’005930, SKํ•˜์ด๋‹‰์Šคโ†’000660, ๋„ค์ด๋ฒ„โ†’035420, LGํ™”ํ•™โ†’051910, ํ˜„๋Œ€์ฐจโ†’005380, ์นด์นด์˜คโ†’035720) +- ticker (the 6-digit code if the user gave one; otherwise pass the company NAME verbatim โ€” it is resolved to a code automatically. Do not guess codes from memory for less-common names.) - report_type (annual unless the query names a quarter/half) - year (fiscal year, or null for most recent) - sections (which narrative categories answer the query) @@ -382,16 +382,17 @@ export function createReadFilingsKr(model: string): DynamicStructuredTool { return formatToolResult({ error: 'Failed to plan filing read', details: errMsg(error) }, []); } - const ticker = plan.ticker.trim(); - let resolved: { corp_code: string; corp_name: string } | null; + let sec: Awaited>; try { - resolved = await resolveTicker(ticker); + sec = await resolveKrSecurity(plan.ticker); } catch (error) { - return formatToolResult({ error: 'Ticker resolution failed', ticker, details: errMsg(error) }, []); + return formatToolResult({ error: 'Ticker resolution failed', ticker: plan.ticker, details: errMsg(error) }, []); } - if (!resolved) { - return formatToolResult({ error: `Ticker ${ticker} not found in DART corp registry` }, []); + if (!sec?.corpCode) { + return formatToolResult({ error: `Could not resolve "${plan.ticker}" to a DART corp_code โ€” pass a 6-digit ticker or an exact company name` }, []); } + const ticker = sec.stockCode; + const resolved = { corp_code: sec.corpCode, corp_name: sec.name ?? '' }; const identity = { ticker, corp_code: resolved.corp_code, corp_name: resolved.corp_name }; // 2. Find the report's rcept_no via /list.json (์ •๊ธฐ๊ณต์‹œ). diff --git a/src/tools/finance-kr/resolve-kr.test.ts b/src/tools/finance-kr/resolve-kr.test.ts new file mode 100644 index 000000000..1d78c932f --- /dev/null +++ b/src/tools/finance-kr/resolve-kr.test.ts @@ -0,0 +1,46 @@ +import { describe, it, expect } from 'bun:test'; +import { pickBestNaverMatch } from './naver-api.js'; +import { resolveKrSecurity } from './resolve-kr.js'; + +// Captured from the live ac.stock.naver.com response for "์‚ผ์„ฑ์ „์ž": the canonical +// listing first, then leveraged/mixed ETFs that merely contain the name in their title. +const SAMSUNG_ITEMS = [ + { code: '005930', name: '์‚ผ์„ฑ์ „์ž', typeCode: 'KOSPI', nationCode: 'KOR', category: 'stock' }, + { code: '0162Z0', name: 'RISE ์‚ผ์„ฑ์ „์žSKํ•˜์ด๋‹‰์Šค์ฑ„๊ถŒํ˜ผํ•ฉ50', typeCode: 'KOSPI', nationCode: 'KOR', category: 'stock' }, + { code: '0193W0', name: 'KODEX ์‚ผ์„ฑ์ „์ž๋‹จ์ผ์ข…๋ชฉ๋ ˆ๋ฒ„๋ฆฌ์ง€', typeCode: 'KOSPI', nationCode: 'KOR', category: 'stock' }, +]; + +describe('pickBestNaverMatch', () => { + it('picks the 6-digit-numeric equity and drops alphanumeric ETF codes', () => { + const hit = pickBestNaverMatch(SAMSUNG_ITEMS, '์‚ผ์„ฑ์ „์ž'); + expect(hit).not.toBeNull(); + expect(hit?.code).toBe('005930'); + expect(hit?.market).toBe('KOSPI'); + }); + + it('prefers an exact whitespace-insensitive name hit over autocomplete order', () => { + const items = [ + { code: '000661', name: 'SKํ•˜์ด๋‹‰์Šค์šฐ', typeCode: 'KOSPI', nationCode: 'KOR', category: 'stock' }, + { code: '000660', name: 'SK ํ•˜์ด๋‹‰์Šค', typeCode: 'KOSPI', nationCode: 'KOR', category: 'stock' }, + ]; + expect(pickBestNaverMatch(items, 'SKํ•˜์ด๋‹‰์Šค')?.code).toBe('000660'); + }); + + it('returns null when no domestic-equity candidate survives', () => { + expect(pickBestNaverMatch([{ code: 'AAPL', name: 'Apple', nationCode: 'USA', category: 'stock' }], 'apple')).toBeNull(); + expect(pickBestNaverMatch([], '์—†๋Š”ํšŒ์‚ฌ')).toBeNull(); + expect(pickBestNaverMatch(null, 'x')).toBeNull(); + }); +}); + +describe('resolveKrSecurity', () => { + // Empty/whitespace short-circuits BEFORE any registry/network call โ€” the only + // resolveKrSecurity branch that's pure. The 6-digit-passthrough and name paths read + // the DART corp registry (a ~12MB on-disk cache when a key is configured) and/or call + // Naver, so they're covered by the live probe in CLAUDE notes, not this unit suite โ€” + // exercising them here cold-loads 12MB and destabilizes the parallel test run. + it('returns null for empty / whitespace input', async () => { + expect(await resolveKrSecurity('')).toBeNull(); + expect(await resolveKrSecurity(' ')).toBeNull(); + }); +}); diff --git a/src/tools/finance-kr/resolve-kr.ts b/src/tools/finance-kr/resolve-kr.ts new file mode 100644 index 000000000..42428a246 --- /dev/null +++ b/src/tools/finance-kr/resolve-kr.ts @@ -0,0 +1,91 @@ +/** + * Unified resolver: turn a 6-digit ticker OR a Korean company name into a canonical + * listing (6-digit stock code + DART corp_code when available). + * + * Why this exists: every KR tool used to accept only `^\d{6}$`, so when the user gave + * a NAME the model had to recall the code from memory. A one-digit slip silently + * returns a DIFFERENT company, and the agent then builds a polished, fully-cited thesis + * on the wrong stock โ€” the worst failure mode for a research agent. Centralizing the + * nameโ†’code step makes resolution deterministic (DART registry / Naver), not a guess. + * + * Resolution order: + * 1. 6-digit passthrough โ€” authoritative stock code; enrich corp_code best-effort. + * 2. DART name registry (`resolveCorpName`) โ€” authoritative corp_code, FULL TIER ONLY + * (the corpCode.xml fetch needs a DART key). + * 3. Keyless Naver autocomplete โ€” works without any key; gives the stock code only. + */ +import { resolveTicker, resolveCorpName } from '../../data/ticker-registry.js'; +import { fetchNaverAutocomplete } from './naver-api.js'; + +const TICKER_RE = /^\d{6}$/; + +export type KrResolveSource = 'ticker' | 'registry-name' | 'naver-name'; + +export interface ResolvedKrSecurity { + /** 6-digit listing code โ€” what Naver/KRX tools key on. Always present on success. */ + stockCode: string; + /** 8-digit DART corp_code โ€” what DART tools key on. null when the DART registry is unavailable (keyless tier). */ + corpCode: string | null; + /** Canonical company name when known. */ + name: string | null; + /** Market type (KOSPI/KOSDAQ) when resolved via Naver. */ + market: string | null; + source: KrResolveSource; +} + +/** Best-effort 6-digit โ†’ DART corp_code. Swallows the "no DART key" throw so keyless callers still get a stock code. */ +async function tickerToCorp(stockCode: string): Promise<{ corp_code: string; corp_name: string } | null> { + try { + return await resolveTicker(stockCode); + } catch { + return null; + } +} + +export async function resolveKrSecurity(input: string): Promise { + const q = (input ?? '').trim(); + if (!q) return null; + + // 1. Already a 6-digit code โ€” authoritative; add corp_code if the registry is up. + if (TICKER_RE.test(q)) { + const reg = await tickerToCorp(q); + return { + stockCode: q, + corpCode: reg?.corp_code ?? null, + name: reg?.corp_name ?? null, + market: null, + source: 'ticker', + }; + } + + // 2. Name via the DART registry (authoritative corp_code) โ€” full tier only. + try { + const reg = await resolveCorpName(q); + if (reg?.stock_code && TICKER_RE.test(reg.stock_code)) { + return { + stockCode: reg.stock_code, + corpCode: reg.corp_code, + name: reg.corp_name, + market: null, + source: 'registry-name', + }; + } + } catch { + // registry unavailable (no DART key) โ€” fall through to the keyless path + } + + // 3. Keyless Naver autocomplete โ€” stock code only (corp_code best-effort if the registry is up). + const hit = await fetchNaverAutocomplete(q); + if (hit) { + const reg = await tickerToCorp(hit.code); + return { + stockCode: hit.code, + corpCode: reg?.corp_code ?? null, + name: hit.name ?? reg?.corp_name ?? null, + market: hit.market, + source: 'naver-name', + }; + } + + return null; +} diff --git a/src/tools/finance-kr/sub-tools/get-business-report.ts b/src/tools/finance-kr/sub-tools/get-business-report.ts index 816ad3896..993620f61 100644 --- a/src/tools/finance-kr/sub-tools/get-business-report.ts +++ b/src/tools/finance-kr/sub-tools/get-business-report.ts @@ -2,7 +2,7 @@ import { DynamicStructuredTool } from '@langchain/core/tools'; import { z } from 'zod'; import { existsSync, mkdirSync, writeFileSync, readdirSync, statSync, unlinkSync } from 'fs'; import { dartApi } from '../api.js'; -import { resolveTicker } from '../../../data/ticker-registry.js'; +import { resolveKrSecurity } from '../resolve-kr.js'; import { dexterPath } from '../../../utils/paths.js'; import { formatToolResult } from '../../types.js'; import { TTL_24H } from '../../finance/utils.js'; @@ -20,8 +20,8 @@ type ReportType = keyof typeof REPORT_TYPE_CODES; const InputSchema = z.object({ ticker: z .string() - .regex(/^\d{6}$/, 'Korean ticker must be a 6-digit string (e.g. 005930 for Samsung).') - .describe('6-digit Korean stock ticker (e.g. 005930 for Samsung Electronics).'), + .min(1) + .describe('6-digit Korean stock ticker (e.g. 005930) OR the company name (e.g. ์‚ผ์„ฑ์ „์ž) โ€” a name is resolved to its DART listing automatically.'), report_type: z .enum(['annual', 'semiannual', 'quarterly_1', 'quarterly_3']) .default('annual') @@ -96,13 +96,15 @@ export const getBusinessReport = new DynamicStructuredTool({ 'persisted to rawLineItemsFile for drill-down.', schema: InputSchema, func: async (input) => { - const ticker = input.ticker.trim(); - const resolved = await resolveTicker(ticker); - if (!resolved) { + const sec = await resolveKrSecurity(input.ticker); + if (!sec?.corpCode) { return formatToolResult({ - error: `Ticker ${ticker} not found in DART corp registry`, + ticker: input.ticker, + error: `Could not resolve "${input.ticker}" to a DART corp_code โ€” pass a 6-digit ticker or an exact company name`, }, []); } + const ticker = sec.stockCode; + const resolved = { corp_code: sec.corpCode, corp_name: sec.name ?? '' }; const endYear = input.year ?? defaultYear(); const years = Array.from({ length: input.period_count }, (_, i) => endYear - i); diff --git a/src/tools/registry.ts b/src/tools/registry.ts index 6121260ff..6acff9eaa 100644 --- a/src/tools/registry.ts +++ b/src/tools/registry.ts @@ -16,6 +16,7 @@ import { getFilingsKr, GET_FILINGS_KR_DESCRIPTION } from './finance-kr/get-filin import { getLargeHoldersKr, GET_LARGE_HOLDERS_KR_DESCRIPTION } from './finance-kr/get-large-holders-kr.js'; import { getInsiderTradesKr, GET_INSIDER_TRADES_KR_DESCRIPTION } from './finance-kr/get-insider-trades-kr.js'; import { createReadFilingsKr, READ_FILINGS_KR_DESCRIPTION } from './finance-kr/read-filings-kr.js'; +import { getSegmentsKr, GET_SEGMENTS_KR_DESCRIPTION } from './finance-kr/get-segments-kr.js'; import { getShortBalanceKr, GET_SHORT_BALANCE_KR_DESCRIPTION } from './finance-kr/get-short-balance-kr.js'; import { getForeignOwnershipKr, GET_FOREIGN_OWNERSHIP_KR_DESCRIPTION } from './finance-kr/get-foreign-ownership-kr.js'; import { getMarketDataKr, GET_MARKET_DATA_KR_DESCRIPTION } from './finance-kr/get-market-data-kr.js'; @@ -223,7 +224,15 @@ export function getToolRegistry(model: string): RegisteredTool[] { tool: createReadFilingsKr(model), description: READ_FILINGS_KR_DESCRIPTION, compactDescription: - 'Korean DART ์‚ฌ์—…ยท๋ฐ˜๊ธฐยท๋ถ„๊ธฐ๋ณด๊ณ ์„œ narrative (์‚ฌ์—…์˜ ๋‚ด์šฉ, ์œ„ํ—˜๊ด€๋ฆฌ, ๊ฒฝ์˜์ง„๋‹จ MD&A, ์ง€๋ฐฐ๊ตฌ์กฐยท์ตœ๋Œ€์ฃผ์ฃผยท๊ณ„์—ดํšŒ์‚ฌ) โ€” qualitative content for 6-digit tickers.', + 'Korean DART ์‚ฌ์—…ยท๋ฐ˜๊ธฐยท๋ถ„๊ธฐ๋ณด๊ณ ์„œ narrative (์‚ฌ์—…์˜ ๋‚ด์šฉ, ์œ„ํ—˜๊ด€๋ฆฌ, ๊ฒฝ์˜์ง„๋‹จ MD&A, ์ง€๋ฐฐ๊ตฌ์กฐยท์ตœ๋Œ€์ฃผ์ฃผยท๊ณ„์—ดํšŒ์‚ฌ) โ€” qualitative content for 6-digit tickers or names.', + concurrencySafe: true, + }); + tools.push({ + name: 'get_segments_kr', + tool: getSegmentsKr, + description: GET_SEGMENTS_KR_DESCRIPTION, + compactDescription: + 'Korean ์‚ฌ์—…๋ถ€๋ฌธ๋ณ„ ์š”์•ฝ ์žฌ๋ฌดํ˜„ํ™ฉ (segment ๋งค์ถœ์•กยท์˜์—…์ด์ตยท๋น„์ค‘) from the latest DART ์ •๊ธฐ๋ณด๊ณ ์„œ โ€” divisional mix for ์žฌ๋ฒŒ/๋ณตํ•ฉ ๊ธฐ์—… (6-digit tickers or names).', concurrencySafe: true, }); } From c3b7ac3400697d0bfdd14d5afddad9a0222f0a6d Mon Sep 17 00:00:00 2001 From: SH Kwon Date: Sun, 21 Jun 2026 04:51:55 +0900 Subject: [PATCH 14/70] =?UTF-8?q?resolveKrSecurity=20=EB=B6=84=EA=B8=B0=20?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=ED=85=8C=EC=8A=A4=ED=8A=B8=20(=EB=AA=A8?= =?UTF-8?q?=ED=82=B9)=20=E2=80=94=20=EC=9D=B4=EB=A6=84=E2=86=92=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=ED=95=B4=EC=84=9D=20=ED=9A=8C=EA=B7=80=20=EA=B0=80?= =?UTF-8?q?=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ์‹ ๋ขฐ ํ•ต์‹ฌ์ธ resolveKrSecurity์˜ fallback ์ˆœ์„œ(6์ž๋ฆฌ ํŒจ์Šค์Šค๋ฃจ โ†’ DART ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ์ด๋ฆ„ โ†’ keyless Naver โ†’ null)๋ฅผ resolveTicker/resolveCorpName/fetchNaverAutocomplete ๋ชจํ‚น์œผ๋กœ ๊ฒฐ์ •๋ก ์ ์œผ๋กœ ์ปค๋ฒ„. registry throw ์‹œ keyless fallback, ๋น„์ƒ์žฅ(stock_code ์—†์Œ) ์‹œ Naver ๊ฒฝ๋กœ๊นŒ์ง€ ๊ฒ€์ฆ. mock.restore()๋กœ ํƒ€ ํŒŒ์ผ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€. 248 ํ…Œ์ŠคํŠธ ํ†ต๊ณผ. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/tools/finance-kr/resolve-kr.test.ts | 97 ++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 9 deletions(-) diff --git a/src/tools/finance-kr/resolve-kr.test.ts b/src/tools/finance-kr/resolve-kr.test.ts index 1d78c932f..97024687c 100644 --- a/src/tools/finance-kr/resolve-kr.test.ts +++ b/src/tools/finance-kr/resolve-kr.test.ts @@ -1,6 +1,30 @@ -import { describe, it, expect } from 'bun:test'; -import { pickBestNaverMatch } from './naver-api.js'; -import { resolveKrSecurity } from './resolve-kr.js'; +import { describe, it, expect, mock, beforeEach, afterAll } from 'bun:test'; + +// Import the REAL modules first: we keep pickBestNaverMatch real (pure, tested below) +// and only override the three functions resolve-kr depends on. mock.module is applied +// BEFORE importing resolve-kr so it binds to the mocked deps; mock.restore() in afterAll +// prevents the override from leaking into other test files. +const realNaver = await import('./naver-api.js'); +const realRegistry = await import('../../data/ticker-registry.js'); + +const resolveTicker = mock(async (_t: string): Promise => null); +const resolveCorpName = mock(async (_n: string): Promise => null); +const fetchNaverAutocomplete = mock(async (_q: string): Promise => null); + +mock.module('../../data/ticker-registry.js', () => ({ ...realRegistry, resolveTicker, resolveCorpName })); +mock.module('./naver-api.js', () => ({ ...realNaver, fetchNaverAutocomplete })); + +const { resolveKrSecurity } = await import('./resolve-kr.js'); +const { pickBestNaverMatch } = realNaver; + +afterAll(() => mock.restore()); + +beforeEach(() => { + for (const m of [resolveTicker, resolveCorpName, fetchNaverAutocomplete]) m.mockReset(); + resolveTicker.mockImplementation(async () => null); + resolveCorpName.mockImplementation(async () => null); + fetchNaverAutocomplete.mockImplementation(async () => null); +}); // Captured from the live ac.stock.naver.com response for "์‚ผ์„ฑ์ „์ž": the canonical // listing first, then leveraged/mixed ETFs that merely contain the name in their title. @@ -34,13 +58,68 @@ describe('pickBestNaverMatch', () => { }); describe('resolveKrSecurity', () => { - // Empty/whitespace short-circuits BEFORE any registry/network call โ€” the only - // resolveKrSecurity branch that's pure. The 6-digit-passthrough and name paths read - // the DART corp registry (a ~12MB on-disk cache when a key is configured) and/or call - // Naver, so they're covered by the live probe in CLAUDE notes, not this unit suite โ€” - // exercising them here cold-loads 12MB and destabilizes the parallel test run. - it('returns null for empty / whitespace input', async () => { + it('returns null for empty / whitespace input (no registry/network call)', async () => { expect(await resolveKrSecurity('')).toBeNull(); expect(await resolveKrSecurity(' ')).toBeNull(); + expect(resolveTicker).not.toHaveBeenCalled(); + expect(fetchNaverAutocomplete).not.toHaveBeenCalled(); + }); + + it('6-digit: passes through and enriches corp_code from the registry', async () => { + resolveTicker.mockImplementation(async () => ({ corp_code: '00126380', corp_name: '์‚ผ์„ฑ์ „์ž' })); + const r = await resolveKrSecurity('005930'); + expect(r).toEqual({ stockCode: '005930', corpCode: '00126380', name: '์‚ผ์„ฑ์ „์ž', market: null, source: 'ticker' }); + expect(resolveCorpName).not.toHaveBeenCalled(); // 6-digit never hits the name path + expect(fetchNaverAutocomplete).not.toHaveBeenCalled(); + }); + + it('6-digit: still resolves stockCode when the registry is unavailable (keyless tier)', async () => { + resolveTicker.mockImplementation(async () => { + throw new Error('[DART corpCode] DART_API_KEY not set'); + }); + const r = await resolveKrSecurity('000660'); + expect(r?.stockCode).toBe('000660'); + expect(r?.corpCode).toBeNull(); + expect(r?.source).toBe('ticker'); + }); + + it('name โ†’ DART registry hit wins (authoritative corp_code), no Naver call', async () => { + resolveCorpName.mockImplementation(async () => ({ corp_code: '00164742', corp_name: '๊ณ ๋ ค์•„์—ฐ', stock_code: '010130' })); + const r = await resolveKrSecurity('๊ณ ๋ ค์•„์—ฐ'); + expect(r).toEqual({ stockCode: '010130', corpCode: '00164742', name: '๊ณ ๋ ค์•„์—ฐ', market: null, source: 'registry-name' }); + expect(fetchNaverAutocomplete).not.toHaveBeenCalled(); + }); + + it('name โ†’ falls back to keyless Naver when the registry misses', async () => { + resolveCorpName.mockImplementation(async () => null); + fetchNaverAutocomplete.mockImplementation(async () => ({ code: '034020', name: '๋‘์‚ฐ์—๋„ˆ๋นŒ๋ฆฌํ‹ฐ', market: 'KOSPI' })); + const r = await resolveKrSecurity('๋‘์‚ฐ์—๋„ˆ๋นŒ๋ฆฌํ‹ฐ'); + expect(r?.stockCode).toBe('034020'); + expect(r?.market).toBe('KOSPI'); + expect(r?.source).toBe('naver-name'); + expect(fetchNaverAutocomplete).toHaveBeenCalledTimes(1); + }); + + it('name โ†’ falls back to Naver even when the registry THROWS (no DART key)', async () => { + resolveCorpName.mockImplementation(async () => { + throw new Error('[DART corpCode] DART_API_KEY not set'); + }); + fetchNaverAutocomplete.mockImplementation(async () => ({ code: '034020', name: '๋‘์‚ฐ์—๋„ˆ๋นŒ๋ฆฌํ‹ฐ', market: 'KOSPI' })); + const r = await resolveKrSecurity('๋‘์‚ฐ์—๋„ˆ๋นŒ๋ฆฌํ‹ฐ'); + expect(r?.source).toBe('naver-name'); + expect(r?.stockCode).toBe('034020'); + }); + + it('name โ†’ null when both the registry and Naver miss', async () => { + resolveCorpName.mockImplementation(async () => null); + fetchNaverAutocomplete.mockImplementation(async () => null); + expect(await resolveKrSecurity('์กด์žฌํ•˜์ง€์•Š๋Š”ํšŒ์‚ฌ๋ช…xyz')).toBeNull(); + }); + + it('ignores a registry hit whose stock_code is not a 6-digit listing (unlisted) โ†’ Naver', async () => { + resolveCorpName.mockImplementation(async () => ({ corp_code: '00999999', corp_name: '๋น„์ƒ์žฅ์‚ฌ', stock_code: null })); + fetchNaverAutocomplete.mockImplementation(async () => ({ code: '034020', name: '๋‘์‚ฐ์—๋„ˆ๋นŒ๋ฆฌํ‹ฐ', market: 'KOSPI' })); + const r = await resolveKrSecurity('๋น„์ƒ์žฅ์‚ฌ'); + expect(r?.source).toBe('naver-name'); }); }); From b38b7362a9d82c37073d3174baabd2139950d97b Mon Sep 17 00:00:00 2001 From: SH Kwon Date: Sun, 21 Jun 2026 08:03:07 +0900 Subject: [PATCH 15/70] Desktop app (Electron + Bun sidecar) + general-user README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - desktop/: Electron+React GUI โ€” ์ฑ— / ์—…๋ฌด(์—‘์…€ ๋ณ€ํ™˜) / ๊ธฐ๋ก(history) / ์„ค์ •(ํ‚ค ๊ด€๋ฆฌ) - safeStorage ์•”ํ˜ธํ™” ํ‚ค โ†’ sqlite โ†’ spawn ์‹œ env ์ฃผ์ž… (์ฝ”์–ด ๋ฌด๋ณ€๊ฒฝ) - ๋Œ€ํ™”ยท๋ณ€ํ™˜ ์•„์นด์ด๋น™(sqlite), three.js ๋กœ๊ณ , ์‚ฌ์ด๋“œ๋ฐ” ์ ‘๊ธฐ, flat ๋ชจ๋…ธํ†ค ๋ฒ„ํŠผ - src/sidecar/: stdio JSON-Lines ํ—ค๋“œ๋ฆฌ์Šค ์‚ฌ์ด๋“œ์นด, Agent.run() ๊ตฌ๋™ (run/cancel/reset/convert) - core: text_delta ์ŠคํŠธ๋ฆฌ๋ฐ ์ด๋ฒคํŠธ ์ถ”๊ฐ€; DEXTER_DIR override (userData ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ) - README ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž์šฉ์œผ๋กœ ์žฌ์ž‘์„ฑ; ๊ฐœ๋ฐœ์ž ๋‚ด์šฉ์€ DEVELOPMENT.md๋กœ ๋ถ„๋ฆฌ Co-Authored-By: Claude Opus 4.8 (1M context) --- DEVELOPMENT.md | 149 + README.md | 220 +- desktop/.gitignore | 17 + desktop/.python-version | 1 + desktop/electron.vite.config.ts | 22 + desktop/package-lock.json | 8866 +++++++++++++++++ desktop/package.json | 92 + desktop/scripts/prepare-core.mjs | 48 + desktop/src/main/data-sources.ts | 22 + desktop/src/main/db.ts | 172 + desktop/src/main/index.ts | 65 + desktop/src/main/ipc.ts | 219 + desktop/src/main/providers.ts | 87 + desktop/src/main/secrets.ts | 34 + desktop/src/main/sidecar.ts | 115 + desktop/src/preload/index.d.ts | 9 + desktop/src/preload/index.ts | 44 + desktop/src/renderer/index.html | 16 + desktop/src/renderer/src/App.tsx | 247 + .../src/renderer/src/components/ChatView.tsx | 294 + .../src/renderer/src/components/HelpView.tsx | 97 + .../renderer/src/components/HistoryView.tsx | 130 + .../src/renderer/src/components/KeyCard.tsx | 107 + .../renderer/src/components/KrxKeyCard.tsx | 121 + .../renderer/src/components/SettingsView.tsx | 222 + .../renderer/src/components/SidebarStatus.tsx | 28 + .../src/renderer/src/components/ThreeLogo.tsx | 78 + .../src/renderer/src/components/WorkView.tsx | 170 + desktop/src/renderer/src/env.d.ts | 1 + desktop/src/renderer/src/main.tsx | 10 + desktop/src/renderer/src/styles.css | 1262 +++ desktop/src/shared/sidecar.ts | 66 + desktop/src/shared/types.ts | 79 + desktop/tsconfig.json | 7 + desktop/tsconfig.node.json | 22 + desktop/tsconfig.web.json | 25 + src/agent/agent.ts | 27 +- src/agent/types.ts | 10 + src/sidecar/index.ts | 140 + src/sidecar/protocol.ts | 63 + src/utils/paths.ts | 9 +- 41 files changed, 13236 insertions(+), 177 deletions(-) create mode 100644 DEVELOPMENT.md create mode 100644 desktop/.gitignore create mode 100644 desktop/.python-version create mode 100644 desktop/electron.vite.config.ts create mode 100644 desktop/package-lock.json create mode 100644 desktop/package.json create mode 100644 desktop/scripts/prepare-core.mjs create mode 100644 desktop/src/main/data-sources.ts create mode 100644 desktop/src/main/db.ts create mode 100644 desktop/src/main/index.ts create mode 100644 desktop/src/main/ipc.ts create mode 100644 desktop/src/main/providers.ts create mode 100644 desktop/src/main/secrets.ts create mode 100644 desktop/src/main/sidecar.ts create mode 100644 desktop/src/preload/index.d.ts create mode 100644 desktop/src/preload/index.ts create mode 100644 desktop/src/renderer/index.html create mode 100644 desktop/src/renderer/src/App.tsx create mode 100644 desktop/src/renderer/src/components/ChatView.tsx create mode 100644 desktop/src/renderer/src/components/HelpView.tsx create mode 100644 desktop/src/renderer/src/components/HistoryView.tsx create mode 100644 desktop/src/renderer/src/components/KeyCard.tsx create mode 100644 desktop/src/renderer/src/components/KrxKeyCard.tsx create mode 100644 desktop/src/renderer/src/components/SettingsView.tsx create mode 100644 desktop/src/renderer/src/components/SidebarStatus.tsx create mode 100644 desktop/src/renderer/src/components/ThreeLogo.tsx create mode 100644 desktop/src/renderer/src/components/WorkView.tsx create mode 100644 desktop/src/renderer/src/env.d.ts create mode 100644 desktop/src/renderer/src/main.tsx create mode 100644 desktop/src/renderer/src/styles.css create mode 100644 desktop/src/shared/sidecar.ts create mode 100644 desktop/src/shared/types.ts create mode 100644 desktop/tsconfig.json create mode 100644 desktop/tsconfig.node.json create mode 100644 desktop/tsconfig.web.json create mode 100644 src/sidecar/index.ts create mode 100644 src/sidecar/protocol.ts diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 000000000..a0582e0b2 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,149 @@ +# ๊ฐœ๋ฐœ ๋ฌธ์„œ + +[โ† README](README.md) ยท ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž์šฉ ์†Œ๊ฐœ๋Š” README์—, ๋„๊ตฌยท๊ฒฌ๊ณ ์„ฑยท์„ค์น˜ยทํ‰๊ฐ€ ๋“ฑ ๊ธฐ์ˆ  ๋‚ด์šฉ์€ ์—ฌ๊ธฐ์— ๋ชจ์•˜์Šต๋‹ˆ๋‹ค. + +[virattt/dexter](https://github.com/virattt/dexter)(๋ฏธ๊ตญ ์‹œ์žฅ ์ค‘์‹ฌ์˜ ์ž์œจ ๊ธˆ์œต ๋ฆฌ์„œ์น˜ ์—์ด์ „ํŠธ)๋ฅผ ํฌํฌํ•ด, ๊ทธ ์œ„์— **ํ•œ๊ตญ ์ฃผ์‹ ๋ฆฌ์„œ์น˜ ๋ ˆ์ด์–ด**์™€ ๊ทธ ๋ฐ์ดํ„ฐ๊ฐ€ ์กฐ์šฉํžˆ ๊นจ์ง€์ง€ ์•Š๊ฒŒ ๋ฐ›์ณ์ฃผ๋Š” **๊ฒฌ๊ณ ์„ฑ ๋ ˆ์ด์–ด**๋ฅผ ์–น์€ ๊ฒƒ์ด ์ด ์ €์žฅ์†Œ์˜ ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค. + +**๋ฐ”๋กœ๊ฐ€๊ธฐ** ยท [๋™์ž‘ ๋ฐฉ์‹](#๋™์ž‘-๋ฐฉ์‹) ยท [์™œ ๋”ฐ๋กœ ๋งŒ๋“ค์—ˆ๋‚˜](#์™œ-ํ•œ๊ตญ-์ฃผ์‹์€-๋”ฐ๋กœ-๋งŒ๋“ค์–ด์•ผ-ํ–ˆ๋‚˜) ยท [๋„๊ตฌ](#๋„๊ตฌ) ยท [๊ฒฌ๊ณ ์„ฑ](#๊ฒฌ๊ณ ์„ฑ--์กฐ์šฉํžˆ-๊นจ์ง€์ง€-์•Š๊ธฐ) ยท [ํ•œ๊ตญ ์‹œ์žฅ ํŠœ๋‹](#ํ•œ๊ตญ-์‹œ์žฅ-ํŠœ๋‹) ยท [๋น ๋ฅธ ์‹œ์ž‘](#๋น ๋ฅธ-์‹œ์ž‘) ยท [๊ฐœ๋ฐœยทํ‰๊ฐ€](#๊ฐœ๋ฐœ--ํ‰๊ฐ€) ยท [์•„ํ‚คํ…์ฒ˜](#์•„ํ‚คํ…์ฒ˜) + +## ๋™์ž‘ ๋ฐฉ์‹ + +์ž์—ฐ์–ด ์งˆ๋ฌธ์„ ๋˜์ง€๋ฉด โ€” ์‹œ์žฅ์„ ์ง€์ •ํ•  ํ•„์š” ์—†์ด โ€” ์—์ด์ „ํŠธ๊ฐ€ ํ‹ฐ์ปค ํ˜•ํƒœ(6์ž๋ฆฌ ์ˆซ์ž = ํ•œ๊ตญ)์™€ ์–ธ์–ด๋ฅผ ๋ณด๊ณ  ์•Œ๋งž์€ ๋„๊ตฌ๋กœ ๋ผ์šฐํŒ…ํ•˜๊ณ , 1์ฐจ ์ถœ์ฒ˜๋ฅผ ํ˜ธ์ถœํ•ด, ๊ฒฐ๊ณผ๋ฅผ ๊ต์ฐจ ์ข…ํ•ฉํ•œ ๋’ค ์ธ์šฉ๋œ ๋‹ต์„ ๋ƒ…๋‹ˆ๋‹ค. + +``` +> ์‚ผ์„ฑ์ „์ž ์ง€๊ธˆ ํˆฌ์ž ๊ด€์ ์—์„œ ์–ด๋•Œ? +``` + +์ด ํ•œ ์ค„์ด ๋‚ด๋ถ€์—์„œ๋Š” **์ •๊ทœํ™” ์‹ค์ **(๋งค์ถœยท์˜์—…์ด์ตยท์ˆœ์ด์ตยท๋งˆ์ง„ยทROEยทFCFยทYoY) + **์™ธ๊ตญ์ธ/๊ธฐ๊ด€/๊ฐœ์ธ ์ผ๋ณ„ ์ˆ˜๊ธ‰** + **๊ณต๋งค๋„ ์ž”๊ณ ** + **5%๋ฃฐ ๋Œ€๋Ÿ‰๋ณด์œ ** + **๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค**๋ฅผ ๊ฐ๊ฐ 1์ฐจ ์ถœ์ฒ˜์—์„œ ๋Œ์–ด์™€, "์ˆ˜๊ธ‰ยท์‹ค์ ยท์ง€๋ฐฐ๊ตฌ์กฐ๊ฐ€ ๊ฐ™์€ ๋ฐฉํ–ฅ์ธ์ง€ ์ถฉ๋Œ์ธ์ง€"๋ฅผ ํŒ๋‹จํ•˜๋Š” ํ•˜๋‚˜์˜ thesis๋กœ ๋ฌถ์ž…๋‹ˆ๋‹ค. + +๋ชจ๋“  ๋„๊ตฌ ํ˜ธ์ถœ์€ ์Šคํฌ๋ž˜์น˜ํŒจ๋“œ(JSONL)์— ๊ธฐ๋ก๋˜์–ด **๋ฌด์—‡์„ ์–ด๋””์„œ ๊ฐ€์ ธ์™”๋Š”์ง€ ๊ฐ์‚ฌยท์žฌํ˜„**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +## ์™œ ํ•œ๊ตญ ์ฃผ์‹์€ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด์•ผ ํ–ˆ๋‚˜ + +๋ฏธ๊ตญ์šฉ ๋„๊ตฌ๋ฅผ 6์ž๋ฆฌ ํ•œ๊ตญ ํ‹ฐ์ปค์— ๊ทธ๋Œ€๋กœ ์“ธ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค(์• ์ดˆ์— ํ‹ฐ์ปค๋ฅผ ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•จ). ๊ทธ๋ฆฌ๊ณ  ํ•œ๊ตญ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋Š” ๋ฏธ๊ตญ์˜ ๊น”๋”ํ•œ ํ‚ค ๊ธฐ๋ฐ˜ API์™€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค: + +| ์†Œ์Šค | ๋ฌด์—‡ | ํ˜„์‹ค | +|---|---|---| +| **DART** (๊ธˆ๊ฐ์› ์ „์ž๊ณต์‹œ) | ์žฌ๋ฌดยท๊ณต์‹œยท์ง€๋ฐฐ๊ตฌ์กฐ | ๋ฌด๋ฃŒ ๊ณต์‹ API์ง€๋งŒ **์ผ 20,000๊ฑด ํ•œ๋„** | +| **KRX** (ํ•œ๊ตญ๊ฑฐ๋ž˜์†Œ) | ๊ณต๋งค๋„ ์ž”๊ณ  | 2024โ€“25๋ถ€ํ„ฐ **ํšŒ์› ๋กœ๊ทธ์ธ ํ•„์ˆ˜**(์ต๋ช… ์ฐจ๋‹จ) | +| **Naver** ๋ชจ๋ฐ”์ผ | ํ˜„์žฌ๊ฐ€ยท์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ | **๊ณต์‹ ๊ณ„์•ฝ ์—†๋Š”** JSON ์—”๋“œํฌ์ธํŠธ | +| **๊ตญ๋ฏผ์—ฐ๊ธˆ** (data.go.kr) | ์—ฐ๊ธฐ๊ธˆ ๋ณด์œ  | ๋ถ„๊ธฐ ์•„๋‹Œ **์—ฐ๋ง ์Šค๋ƒ…์ƒท**, ์ข…๋ชฉ๋ช… ๋งค์นญ | + +๊ทธ๋ž˜์„œ ๋‘ ๊ฐ€์ง€๊ฐ€ ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค โ€” **(1)** ๊ฐ ์†Œ์Šค๋ฅผ ์ œ๋Œ€๋กœ ํ˜ธ์ถœํ•˜๋Š” ๋„๊ตฌ, **(2)** ๊ทธ๊ฒŒ ํ•œ๋„ ์ดˆ๊ณผยท๋กœ๊ทธ์ธ ๋งŒ๋ฃŒยท์‘๋‹ต ๊ตฌ์กฐ ๋ณ€๊ฒฝ์— **์กฐ์šฉํžˆ ๊นจ์ง€์ง€ ์•Š๊ฒŒ** ๋ฐ›์ณ์ฃผ๋Š” ๊ฒฌ๊ณ ์„ฑ ๋ ˆ์ด์–ด. + +## ๋„๊ตฌ + +**DART ๊ธฐ๋ฐ˜** โ€” `DART_API_KEY`๊ฐ€ ์„ค์ •๋˜๋ฉด ์ž๋™ ๋“ฑ๋ก. + +| ๋„๊ตฌ | ๋Œ€์‘ ๋ฏธ๊ตญ ๊ฐœ๋… | ๋‚ด์šฉ | +|---|---|---| +| `get_financials_kr` | `get_financials` (10-K/10-Q) | ์‚ฌ์—…ยท๋ฐ˜๊ธฐยท๋ถ„๊ธฐ๋ณด๊ณ ์„œ, K-IFRS ์ •๊ทœํ™” ์š”์•ฝ(์—ฐ๊ฒฐ/๋ณ„๋„) | +| `get_filings_kr` | SEC EDGAR | DART ๊ณต์‹œ ๊ฒ€์ƒ‰ | +| `read_filings_kr` | `read_filings` (10-K ๋ณธ๋ฌธ) | ๋ณด๊ณ ์„œ ๋ณธ๋ฌธ โ€” ์‚ฌ์—…์˜ ๋‚ด์šฉยท์ฃผ์š”์ œํ’ˆยท์œ„ํ—˜๊ด€๋ฆฌยทMD&A **+ ์ง€๋ฐฐ๊ตฌ์กฐยท์ตœ๋Œ€์ฃผ์ฃผยทํŠน์ˆ˜๊ด€๊ณ„์žยท๊ณ„์—ดํšŒ์‚ฌยท๋Œ€์ฃผ์ฃผ ๊ฑฐ๋ž˜** | +| `get_large_holders_kr` | 13F (5%+ ๋ณด์œ ) | ๋Œ€๋Ÿ‰๋ณด์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ | +| `get_insider_trades_kr` | Form 4 (๋‚ด๋ถ€์ž) | ์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ ๋ณด๊ณ  | + +**Korea-specific** โ€” DART์— ์—†๋Š” ๋ฐ์ดํ„ฐ. ์†Œ์Šคยทํ‚ค๊ฐ€ ๋„๊ตฌ๋งˆ๋‹ค ๋‹ค๋ฆ„. + +| ๋„๊ตฌ | ๋‚ด์šฉ (์†Œ์Šค) | ํ™œ์„ฑํ™” | +|---|---|---| +| `get_market_data_kr` | ํ˜„์žฌ๊ฐ€ยท์‹œ์ดยทPER/PBR/EPS/BPSยท์ถ”์ •PERยท๋ฐฐ๋‹นยท๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šคยทpeer (Naver) | **ํ‚ค ๋ถˆํ•„์š”** | +| `get_foreign_ownership_kr` | ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ + ์™ธ๊ตญ์ธ/๊ธฐ๊ด€/๊ฐœ์ธ ์ˆœ๋งค์ˆ˜ (Naver) | **ํ‚ค ๋ถˆํ•„์š”** | +| `get_short_balance_kr` | ๊ณต๋งค๋„ ์ˆœ๋ณด์œ ์ž”๊ณ  (KRX) | `KRX_ID`+`KRX_PW` ๋˜๋Š” `KRX_COOKIE` | +| `get_nps_holdings` | ๊ตญ๋ฏผ์—ฐ๊ธˆ ๊ตญ๋‚ด์ฃผ์‹ ๋ณด์œ  (data.go.kr) | `DATA_GO_KR_SERVICE_KEY` | + +๋ฏธ๊ตญ ์‹œ์žฅ ๋„๊ตฌ(`get_financials`/`get_market_data`/`read_filings`/`stock_screener`)์™€ `web_search`๋„ ๊ฐ™์€ ๋ฃจํ”„์—์„œ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค(ํ‚ค ์„ค์ • ์‹œ). + +> ํ˜„์žฌ๊ฐ€ยท์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ์€ **ํ‚ค ํ•˜๋‚˜ ์—†์ด** ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. DART ํ‚ค๊ฐ€ ์—†์–ด๋„ ํ•œ๊ตญ ์ข…๋ชฉ์˜ ์‹œ์„ธยท๋ฐธ๋ฅ˜์—์ด์…˜ยท์™ธ๊ตญ์ธ ์ˆ˜๊ธ‰์€ ๋ฐ”๋กœ ์กฐํšŒ๋ฉ๋‹ˆ๋‹ค. + +## ๊ฒฌ๊ณ ์„ฑ โ€” ์กฐ์šฉํžˆ ๊นจ์ง€์ง€ ์•Š๊ธฐ + +ํ•œ๊ตญ ์†Œ์Šค์˜ ๊ฑฐ์นœ ํ˜„์‹ค ์œ„์—์„œ ์ž˜๋ชป๋œ ์ˆซ์ž๋ฅผ ์‚ฌ์‹ค์ฒ˜๋Ÿผ ๋‹ตํ•˜์ง€ ์•Š๋„๋ก ๋‹ค์Œ์„ ๋‚ด์žฅํ–ˆ์Šต๋‹ˆ๋‹ค. + +- **DART ์ฟผํ„ฐ ๋ณดํ˜ธ** โ€” `get_financials_kr`(์—ฐ๋„๋ณ„) ร— ๋ผ์šฐํŒ…(์ข…๋ชฉ๋ณ„) ร— ์—์ด์ „ํŠธ(๋„๊ตฌ ๋ณ‘๋ ฌ)๊ฐ€ ๊ณฑํ•ด์ง€๋ฉด "์ด ์ข…๋ชฉ ๋ถ„์„" ํ•œ ๋ฒˆ์ด ์ˆ˜์‹ญ ๊ฐœ์˜ DART ํ˜ธ์ถœ๋กœ ๋ฒˆ์ง‘๋‹ˆ๋‹ค. **๊ณต์œ  ๋™์‹œ์„ฑ ์บก**(๊ธฐ๋ณธ 4, `DART_MAX_CONCURRENCY`๋กœ ์กฐ์ •)์ด ์ผ 20,000๊ฑด ํ•œ๋„๋ฅผ ํƒœ์šฐ์ง€ ์•Š๊ฒŒ ๋ง‰๊ณ , ํ•œ๋„ ์ดˆ๊ณผ(`020`)๊ฐ€ ๊ฐ์ง€๋˜๋ฉด **์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค**๊ฐ€ ๋‚˜๋จธ์ง€ ํ˜ธ์ถœ์„ ์ฆ‰์‹œ ์ค‘๋‹จํ•˜๊ณ  ๋ช…ํ™•ํ•œ ํ•œ๊ตญ์–ด ๋ฉ”์‹œ์ง€๋กœ ์•Œ๋ฆฐ ๋’ค ์ฟจ๋‹ค์šด ํ›„ ์ž๋™ ์žฌ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค. +- **๋ฐ์ดํ„ฐ ๋“œ๋ฆฌํ”„ํŠธ ๊ฐ์ง€(canary)** โ€” Naver JSON์€ ๋น„๊ณต์‹์ด๋ผ ์–ด๋А ๋‚  ํ•„๋“œ๋ช…์ด ๋ฐ”๋€Œ๋ฉด ๊ฐ’์ด **์กฐ์šฉํžˆ null**์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ต์‹ฌ ํ•„๋“œ(ํ˜„์žฌ๊ฐ€ยท์‹œ์ดยท์ผ๋ณ„ ์ˆ˜๊ธ‰)๊ฐ€ ๋น„๋ฉด ๊ฒฐ๊ณผ์— `_dataQualityWarning`์„ ๋ถ™์—ฌ ๋ชจ๋ธ์ด ๊นจ์ง„ ๊ฐ’์„ ๋ณด๊ณ ํ•˜์ง€ ์•Š๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ณ„ ์ข…๊ฐ€๊ฐ€ rename๋˜์–ด **์ „์ผ ์ข…๊ฐ€๋กœ ๊ฐ€๋ ค์ง€๋Š”** ์ตœ์•… ์ผ€์ด์Šค๊นŒ์ง€ ๊ฐ์ง€ํ•˜๊ณ , ETF/ETNยทํŽ€๋“œยท๋ฌด์ปค๋ฒ„ ์ข…๋ชฉ์ฒ˜๋Ÿผ ๋ณธ๋ž˜ ์ผ๋ถ€ ์ง€ํ‘œ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ๋Š” ์˜คํƒ์„ ๋‚ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +- **keyless ํ”Œ๋ ˆ์ด๋ถ ํ‹ฐ์–ด๋ง** โ€” DART ํ‚ค๊ฐ€ ์—†์–ด๋„ keyless ๋„๊ตฌ๋งŒ์œผ๋กœ **์ˆ˜๊ธ‰ยท๋ฐธ๋ฅ˜์—์ด์…˜ ๋ฆฌ์„œ์น˜ ๊ฐ€์ด๋“œ๊ฐ€ ์‚ด์•„ ์žˆ์Šต๋‹ˆ๋‹ค.** ํ‚ค๊ฐ€ ์žˆ์œผ๋ฉด DART 1์ฐจ ์ž๋ฃŒ๋ฅผ ํฌํ•จํ•œ ์ „์ฒด ๊ต์ฐจ์‹ ํ˜ธ ํ”Œ๋ ˆ์ด๋ถ์œผ๋กœ ํ™•์žฅ๋ฉ๋‹ˆ๋‹ค(๋ฏธ๋“ฑ๋ก ๋„๊ตฌ๋ฅผ ๊ฐ€๋ฆฌํ‚ค์ง€ ์•Š๋„๋ก ๋“ฑ๋ก ๊ฒŒ์ดํŠธ์™€ ๋‹จ์ผ ์ง„์‹ค์›์œผ๋กœ ์ผ์น˜). +- **๊ฒฐ์ •์  ์žฌํ˜„(record/replay)** โ€” ํ‰๊ฐ€ ํ•˜๋„ค์Šค๊ฐ€ ์‹ค์ œ DARTยทKRXยทNaver ์‘๋‹ต์„ ํ”ฝ์Šค์ฒ˜๋กœ ๊ธฐ๋กํ•ด๋‘๋ฉด, ๊ฐ™์€ ์งˆ๋ฌธ์„ **ํ‚คยท๋„คํŠธ์›Œํฌ ์—†์ด ๊ฒฐ์ •์ ์œผ๋กœ ์žฌํ˜„ยทํšŒ๊ท€ ํ…Œ์ŠคํŠธ**ํ•ฉ๋‹ˆ๋‹ค. [๊ฐœ๋ฐœยทํ‰๊ฐ€](#๊ฐœ๋ฐœ--ํ‰๊ฐ€) ์ฐธ๊ณ . + +## ํ•œ๊ตญ ์‹œ์žฅ ํŠœ๋‹ + +- **DCF ์ž๋™ ๋ถ„๊ธฐ** โ€” 6์ž๋ฆฌ ํ‹ฐ์ปค๋ฉด DCF ์Šคํ‚ฌ์ด ํ•œ๊ตญ ๊ฒฝ๋กœ๋กœ ์ „ํ™˜(๋ฒ•์ธ์„ธ์œจ ~22%ยท๊ตญ๊ณ ์ฑ„ ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ ~3%ยทKRW). **์ˆœ๋ถ€์ฑ„๋ฅผ ๋ถ€์ฑ„์ด๊ณ„๊ฐ€ ์•„๋‹ˆ๋ผ ์ด์ž๋ถ€์ฑ„(์ฐจ์ž…๊ธˆยท์‚ฌ์ฑ„)์—์„œ ํ˜„๊ธˆยท๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ์„ ๋บ€ ๊ฐ’์œผ๋กœ** ์‚ฐ์ • โ€” ์‚ผ์„ฑ์ฒ˜๋Ÿผ ์ˆœํ˜„๊ธˆ ๊ธฐ์—…์€ ์ˆœํ˜„๊ธˆ์„ ๋”ํ•ด ์ฃผ์‹๊ฐ€์น˜๊ฐ€ EV๋ณด๋‹ค ์ปค์ง‘๋‹ˆ๋‹ค(๋ถ€์ฑ„์ด๊ณ„๋กœ ์ž˜๋ชป ๋นผ๋ฉด ๋ถ€ํ˜ธ๊ฐ€ ๋’ค์ง‘ํ˜€ ์ €ํ‰๊ฐ€). ๊ฑฐ๋ž˜์„ธยท๋ฐฐ๋‹น์„ธ๋Š” ํˆฌ์ž์ž ์„ธํ›„ ์‹คํ˜„์ˆ˜์ต๋ฅ  ์ฃผ์˜์‚ฌํ•ญ์œผ๋กœ ํ‘œ๊ธฐ. +- **์ง€๋ฐฐ๊ตฌ์กฐยท์†Œ์œ ๊ตฌ์กฐ ๋ณธ๋ฌธ** โ€” ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ์˜ 1์ฐจ ๋ณ€์ˆ˜(์ตœ๋Œ€์ฃผ์ฃผยทํŠน์ˆ˜๊ด€๊ณ„์ธยท๊ณ„์—ดํšŒ์‚ฌยท์ˆœํ™˜์ถœ์žยท๋Œ€์ฃผ์ฃผ ๊ฑฐ๋ž˜)๋ฅผ DART ์‚ฌ์—…๋ณด๊ณ ์„œ ๋ณธ๋ฌธ(VI ํšŒ์‚ฌ์˜ ๊ธฐ๊ด€ยทVII ์ฃผ์ฃผ์— ๊ด€ํ•œ ์‚ฌํ•ญยทIX ๊ณ„์—ดํšŒ์‚ฌยทX ๋Œ€์ฃผ์ฃผ ๋“ฑ๊ณผ์˜ ๊ฑฐ๋ž˜๋‚ด์šฉ)์—์„œ **์ถ”์ •์ด ์•„๋‹ˆ๋ผ 1์ฐจ ๊ทผ๊ฑฐ๋กœ** ์ถ”์ถœ. +- **๋ฌผ์ ๋ถ„ํ•  ๋ถ„์„** โ€” LGํ™”ํ•™โ†’LG์—๋„ˆ์ง€์†”๋ฃจ์…˜ ๊ฐ™์€ ๋ถ„ํ•  ์ด๋ฒคํŠธ๋ฅผ ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ ๊ด€์ (ํฌ์„ยท์ง€์ฃผ์‚ฌ ๋””์Šค์นด์šดํŠธ)์—์„œ ํ‰๊ฐ€ํ•˜๋Š” ์ „์šฉ ์Šคํ‚ฌ. +- **๊ต์ฐจ์‹ ํ˜ธ ์ข…ํ•ฉ** โ€” ์žฌ๋ฌดยท์™ธ๊ตญ์ธ ์ˆ˜๊ธ‰ยท๊ณต๋งค๋„ยท๋Œ€๋Ÿ‰๋ณด์œ ยท๊ณต์‹œ๋ฅผ ๋‹จ์ˆœ ๋‚˜์—ด์ด ์•„๋‹ˆ๋ผ ํ•˜๋‚˜์˜ thesis๋กœ ์—ฎ์Œ. +- **K-IFRS ์ •๊ทœํ™”** โ€” ์—ฐ๊ฒฐ/๋ณ„๋„ ๋‘˜ ๋‹ค, ๋งค์ถœยท์˜์—…์ด์ตยท์ˆœ์ด์ตยท๋งˆ์ง„ยทROEยทFCFยทYoY๋กœ ์š”์•ฝ(์›์‹œ ๋ผ์ธ์•„์ดํ…œ์€ ํŒŒ์ผ๋กœ ๋ณด์กดํ•ด ๋“œ๋ฆด๋‹ค์šด). ์˜์—…์ด์ต ์ •์˜๊ฐ€ ๋ฏธ๊ตญ GAAP์™€ ๋ฏธ๋ฌ˜ํ•˜๊ฒŒ ๋‹ค๋ฅธ ์  ๋ฐ˜์˜. +- **๋‹จ์œ„ยท์‹œ์žฅ ๊ทœ์น™** โ€” ์›(KRW)ยท์–ตยท์กฐ ์ž๋™ ํฌ๋งท, 09:00โ€“15:30 KST ๊ฑฐ๋ž˜์‹œ๊ฐ„, ยฑ30% ์ƒํ•˜ํ•œ๊ฐ€. +- **์ข…๋ชฉ ์ฝ”๋“œ ํ•ด๊ฒฐ** โ€” "์‚ผ์„ฑ์ „์ž" โ†’ ํ‹ฐ์ปค โ†’ `corp_code`. DART ๋งˆ์Šคํ„ฐ(`corpCode.xml`)๋ฅผ ์ฒซ ์‹คํ–‰ ์‹œ ๋ฐ›์•„ `.dexter/cache/`์— ์ €์žฅํ•˜๊ณ  7์ผ๋งˆ๋‹ค ๊ฐฑ์‹ (์‹ ๊ทœ ์ƒ์žฅยท์‚ฌ๋ช… ๋ณ€๊ฒฝยท๋ฌผ์ ๋ถ„ํ•  ์ž๋™ ๋ฐ˜์˜). KRX์šฉ ํ‹ฐ์ปคโ†’ISIN ๋งคํ•‘๋„ ๋ณ„๋„ ๊ด€๋ฆฌ. + +## ๋น ๋ฅธ ์‹œ์ž‘ + +**ํ•„์š”ํ•œ ๊ฒƒ:** [Bun](https://bun.com) v1.0+ ยท LLM ํ‚ค ํ•˜๋‚˜(OpenAI/Anthropic/Google/xAI/OpenRouter/Ollama ๋“ฑ) ยท (์„ ํƒ) ์‹œ์žฅ ๋ฐ์ดํ„ฐ ํ‚ค. +ํ‚ค๊ฐ€ ํ•˜๋‚˜๋„ ์—†์–ด๋„ ํ•œ๊ตญ ์ข…๋ชฉ ํ˜„์žฌ๊ฐ€ยท์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ(Naver, ํ‚ค ๋ถˆํ•„์š”)์€ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. + +```bash +# Bun ์„ค์น˜ (์—†๋‹ค๋ฉด) +curl -fsSL https://bun.com/install | bash # macOS/Linux +# powershell -c "irm bun.sh/install.ps1|iex" # Windows + +git clone https://github.com/0xSHKWON/dexter-with-korea.git +cd dexter-with-korea +bun install + +cp env.example .env # ํ‚ค ์ฑ„์šฐ๊ธฐ (์•„๋ž˜) +bun start # ๋Œ€ํ™”ํ˜• ์‹คํ–‰ (bun run dev = watch ๋ชจ๋“œ) +``` + +**ํ‚ค** (`.env`, `your-`๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฐ’์€ ๋ฏธ์„ค์ •์œผ๋กœ ๊ฐ„์ฃผ): + +```bash +# LLM โ€” ์ตœ์†Œ ํ•˜๋‚˜ ํ•„์ˆ˜ +OPENAI_API_KEY=your-openai-api-key +# ANTHROPIC_API_KEY / GOOGLE_API_KEY / XAI_API_KEY / OPENROUTER_API_KEY / OLLAMA_BASE_URL โ€ฆ + +# ๋ฏธ๊ตญ ์‹œ์žฅ ๋ฐ์ดํ„ฐ +FINANCIAL_DATASETS_API_KEY=your-financial-datasets-api-key + +# ํ•œ๊ตญ ์‹œ์žฅ +DART_API_KEY=your-dart-api-key # ์žฌ๋ฌดยท๊ณต์‹œยท์ง€๋ฐฐ๊ตฌ์กฐ (๋ฌด๋ฃŒ, ์ผ 20,000๊ฑด โ€” opendart.fss.or.kr) +# KRX_ID / KRX_PW # ๊ณต๋งค๋„ ์ž”๊ณ  (data.krx.co.kr ๋กœ๊ทธ์ธ) +# KRX_COOKIE=JSESSIONID=... # ์†Œ์…œ ๋กœ๊ทธ์ธ ๊ณ„์ •์€ ๋ธŒ๋ผ์šฐ์ € ์ฟ ํ‚ค ๋ถ™์—ฌ๋„ฃ๊ธฐ +# DATA_GO_KR_SERVICE_KEY=... # ๊ตญ๋ฏผ์—ฐ๊ธˆ (data.go.kr, Decoded ํ‚ค) +# DART_MAX_CONCURRENCY=4 # (์„ ํƒ) DART ๋™์‹œ ํ˜ธ์ถœ ์ƒํ•œ +# READ_FILINGS_KR_MODEL=... # (์„ ํƒ) read_filings_kr ๋‚ด๋ถ€ ์š”์•ฝ ๋ชจ๋ธ + +# ์›น ๊ฒ€์ƒ‰ (์„ ํƒ, Exa โ†’ Perplexity โ†’ Tavily โ†’ LangSearch ํด๋ฐฑ) / X ์„ผํ‹ฐ๋จผํŠธ (์„ ํƒ) +# EXASEARCH_API_KEY / PERPLEXITY_API_KEY / TAVILY_API_KEY / LANGSEARCH_API_KEY / X_BEARER_TOKEN +``` + +**ํ™œ์„ฑํ™” ๊ทœ์น™:** `DART_API_KEY` โ†’ 5๊ฐœ DART ๋„๊ตฌ ยท `KRX_ID`+`KRX_PW`(๋˜๋Š” `KRX_COOKIE`) โ†’ ๊ณต๋งค๋„ ยท `DATA_GO_KR_SERVICE_KEY` โ†’ ๊ตญ๋ฏผ์—ฐ๊ธˆ. ํ˜„์žฌ๊ฐ€ยท์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ์€ ํ‚ค ์—†์ด ํ•ญ์ƒ ๋“ฑ๋ก. + +## ๊ฐœ๋ฐœ ยท ํ‰๊ฐ€ + +**๋Ÿฐํƒ€์ž„์€ Bun.** ์ž์ฃผ ์“ฐ๋Š” ๋ช…๋ น: + +```bash +bun run typecheck # tsc --noEmit (ํ‘ธ์‹œ ์ „ ๊ถŒ์žฅ) +bun test # ์ „์ฒด ํ…Œ์ŠคํŠธ (Bun ๋Ÿฌ๋„ˆ) +bun test path.test.ts +``` + +**ํ‰๊ฐ€(eval)** โ€” LLM-as-judge ์ฑ„์ : + +```bash +bun run src/evals/run.ts --sample 10 # ๋ฏธ๊ตญ ์ค‘์‹ฌ ํ‰๊ฐ€ +bun run kr-eval # ํ•œ๊ตญ ์งˆ๋ฌธ ๋ฑ…ํฌ, ๋ผ์ด๋ธŒ ์ฑ„์  +bun run kr-eval:record # ์‹ค์ œ DARTยทKRXยทNaver ์‘๋‹ต์„ ํ”ฝ์Šค์ฒ˜๋กœ ๊ธฐ๋ก +bun run kr-eval:replay # ํ”ฝ์Šค์ฒ˜๋กœ ๊ฒฐ์ •์  ์žฌํ˜„ (ํ‚คยท๋„คํŠธ์›Œํฌ ๋ถˆํ•„์š”) +``` + +KR ์ฑ„์  ์ฐจ์›: ์‹ค์  YoY ยท ๊ต์ฐจ์‹ ํ˜ธ ยท ์ง€๋ฐฐ๊ตฌ์กฐ ยท grounding(ํ™˜๊ฐ ์—ฌ๋ถ€). "๊ฐ™์€ ์งˆ๋ฌธ โ†’ ๊ฐ™์€ ๋ฐ์ดํ„ฐ ๊ฒฝ๋กœ"๋ฅผ record/replay๋กœ ๊ฒ€์ฆํ•ด ์ฑ—๋ด‡์˜ ๋น„๊ฒฐ์ •์  ๋‹ต๊ณผ ๋Œ€๋น„ํ•ฉ๋‹ˆ๋‹ค. + +**๋””๋ฒ„๊น…** โ€” ๋ชจ๋“  ์ฟผ๋ฆฌ๊ฐ€ `.dexter/scratchpad/`์— JSONL๋กœ ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค(์›๋ณธ ์ฟผ๋ฆฌยท๊ฐ ํˆด ํ˜ธ์ถœ์˜ ์ธ์ž/์›์‹œ๊ฒฐ๊ณผ/LLM ์š”์•ฝยท์ถ”๋ก  ๋‹จ๊ณ„). ์—์ด์ „ํŠธ๊ฐ€ ๋ฌด์—‡์„ ์–ด๋””์„œ ๊ฐ€์ ธ์™€ ์–ด๋–ป๊ฒŒ ํ•ด์„ํ–ˆ๋Š”์ง€ ํ•œ ์ค„์”ฉ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +## ์•„ํ‚คํ…์ฒ˜ + +- **์—์ด์ „ํŠธ ๋ฃจํ”„** โ€” `src/agent/agent.ts`. ๋ฐ˜๋ณต ๋„๊ตฌ ํ˜ธ์ถœ ๋ฃจํ”„, ์Šคํฌ๋ž˜์น˜ํŒจ๋“œ๊ฐ€ ์ฟผ๋ฆฌ ๋‚ด ๋‹จ์ผ ์ง„์‹ค์›. ๋‹ต๋ณ€ ํ’ˆ์งˆ์€ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ(`src/agent/prompts.ts`)๊ฐ€ ์ขŒ์šฐ. +- **๋„๊ตฌ ๋“ฑ๋ก** โ€” `src/tools/registry.ts`. ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๊ธฐ๋ฐ˜ ๊ฒŒ์ดํŒ…(์œ„ ํ™œ์„ฑํ™” ๊ทœ์น™). +- **์Šคํ‚ฌ** โ€” `src/skills/`. `SKILL.md` ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๋“œ๋กญํ•˜๋ฉด ์ž๋™ ๋ฐœ๊ฒฌ(DCFยท๋ฌผ์ ๋ถ„ํ• ยท๋ฉ”๋ชจ ๋“ฑ). +- **๋ฐ์Šคํฌํ†ฑ ์•ฑ** โ€” `desktop/` (Electron + React). ์ฝ”์–ด๋ฅผ `src/sidecar/`์˜ ํ—ค๋“œ๋ฆฌ์Šค ์‚ฌ์ด๋“œ์นด๋กœ ๊ตฌ๋™. +- ๋” ๊นŠ์€ ์ปจ๋ฒค์…˜ยทํ•จ์ •์€ [`CLAUDE.md`](CLAUDE.md), ์—…์ŠคํŠธ๋ฆผ ๊ธฐ์—ฌ์ž ๋ฌธ์„œ๋Š” [`AGENTS.md`](AGENTS.md) ์ฐธ๊ณ . diff --git a/README.md b/README.md index 9dcbc0841..738853bf1 100644 --- a/README.md +++ b/README.md @@ -1,201 +1,91 @@ -# Dexter ๐Ÿ‡ฐ๐Ÿ‡ท +# ๊ตญ๋‚ด ๊ธฐ์—… ๋ถ„์„ ์—์ด์ „ํŠธ ๐Ÿ‡ฐ๐Ÿ‡ท -**ํ•œ๊ตญ ์ฃผ์‹์„ 1์ฐจ ์ถœ์ฒ˜๋กœ ์ง์ ‘ ๋ฆฌ์„œ์น˜ํ•˜๋Š” CLI ์—์ด์ „ํŠธ.** ๋‰ด์Šค ์š”์•ฝ์ด ์•„๋‹ˆ๋ผ DART(์ „์ž๊ณต์‹œ)ยทKRX(๊ณต๋งค๋„)ยทNaver(์‹œ์„ธยท์™ธ๊ตญ์ธ)ยท๊ตญ๋ฏผ์—ฐ๊ธˆ์„ API๋กœ ์ง์ ‘ ํ˜ธ์ถœํ•˜๊ณ , ์ˆ˜์ง‘ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ํ•˜๋‚˜์˜ ํˆฌ์ž thesis๋กœ ์—ฎ์–ด ์ธ์šฉ๊ณผ ํ•จ๊ป˜ ๋‹ตํ•ฉ๋‹ˆ๋‹ค. ๋ฏธ๊ตญ ์ข…๋ชฉ๋„ ๊ฐ™์€ ๋ฃจํ”„์—์„œ ๋‹ค๋ฃจ๊ณ  ๊ต์ฐจ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. +**ํ•œ๊ตญ ์ฃผ์‹์„ 1์ฐจ ์ถœ์ฒ˜์—์„œ ์ง์ ‘ ๋ฆฌ์„œ์น˜ํ•ด ์ •๋ฆฌํ•ด ์ฃผ๋Š” AI ์—์ด์ „ํŠธ.** -[virattt/dexter](https://github.com/virattt/dexter)(๋ฏธ๊ตญ ์‹œ์žฅ ์ค‘์‹ฌ์˜ ์ž์œจ ๊ธˆ์œต ๋ฆฌ์„œ์น˜ ์—์ด์ „ํŠธ)๋ฅผ ํฌํฌํ•ด, ๊ทธ ์œ„์— **ํ•œ๊ตญ ์ฃผ์‹ ๋ฆฌ์„œ์น˜ ๋ ˆ์ด์–ด**์™€ ๊ทธ ๋ฐ์ดํ„ฐ๊ฐ€ ์กฐ์šฉํžˆ ๊นจ์ง€์ง€ ์•Š๊ฒŒ ๋ฐ›์ณ์ฃผ๋Š” **๊ฒฌ๊ณ ์„ฑ ๋ ˆ์ด์–ด**๋ฅผ ์–น์€ ๊ฒƒ์ด ์ด ์ €์žฅ์†Œ์˜ ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค. +๋‰ด์Šค ์š”์•ฝ์ด ์•„๋‹™๋‹ˆ๋‹ค. DART(์ „์ž๊ณต์‹œ)ยทKRX(๊ณต๋งค๋„)ยทNaver(์‹œ์„ธยท์™ธ๊ตญ์ธ)ยท๊ตญ๋ฏผ์—ฐ๊ธˆ์„ **์ง์ ‘ ํ˜ธ์ถœ**ํ•ด, ํฉ์–ด์ง„ ๋ฐ์ดํ„ฐ๋ฅผ ํ•˜๋‚˜์˜ ํˆฌ์ž ํŒ๋‹จ์œผ๋กœ ์—ฎ์–ด **์ธ์šฉ๊ณผ ํ•จ๊ป˜** ๋‹ตํ•ฉ๋‹ˆ๋‹ค. ๋ฏธ๊ตญ ์ข…๋ชฉ๋„ ๊ฐ™์€ ์ž๋ฆฌ์—์„œ ๋ฌป๊ณ  ๋น„๊ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -[![Twitter Follow](https://img.shields.io/twitter/follow/virattt?style=social)](https://twitter.com/virattt) [![Discord](https://img.shields.io/badge/Discord-Join%20Server-5865F2?style=social&logo=discord)](https://discord.gg/jpGHv2XB6T) +> **"์‚ผ์„ฑ์ „์ž ์ง€๊ธˆ ํˆฌ์ž ๊ด€์ ์—์„œ ์–ด๋•Œ?"** -> ์ด ํ•œ ์ค„์ด๋ฉด ์‹ค์ ยท์™ธ๊ตญ์ธ ์ˆ˜๊ธ‰ยท๊ณต๋งค๋„ ์ž”๊ณ ยท๋Œ€๋Ÿ‰๋ณด์œ ยท๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค๋ฅผ ๊ฐ๊ฐ ์ถœ์ฒ˜์—์„œ ๋Œ์–ด์™€, ์‹ ํ˜ธ๋“ค์ด ๊ฐ™์€ ๋ฐฉํ–ฅ์ธ์ง€ ์ถฉ๋Œ์ธ์ง€๊นŒ์ง€ ์ •๋ฆฌํ•ด ์ค๋‹ˆ๋‹ค. -Dexter TUI +## ๋ญ๊ฐ€ ๋‹ค๋ฅธ๊ฐ€ ---- +DARTยทKRX๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๋„๊ตฌ๋Š” ์ด๋ฏธ ๋งŽ์Šต๋‹ˆ๋‹ค. ์ฐจ์ด๋Š” **๊ทธ ์œ„์—์„œ ๋„๋Š” ์—์ด์ „ํŠธ ๋ฃจํ”„**์— ์žˆ์Šต๋‹ˆ๋‹ค. -**๋ฐ”๋กœ๊ฐ€๊ธฐ** ยท [๋ฌด์—‡์„ ํ•˜๋‚˜](#๋ฌด์—‡์„-ํ•˜๋‚˜) ยท [์™œ ๋”ฐ๋กœ ๋งŒ๋“ค์—ˆ๋‚˜](#์™œ-ํ•œ๊ตญ-์ฃผ์‹์€-๋”ฐ๋กœ-๋งŒ๋“ค์–ด์•ผ-ํ–ˆ๋‚˜) ยท [๋„๊ตฌ](#๋„๊ตฌ) ยท [๊ฒฌ๊ณ ์„ฑ](#๊ฒฌ๊ณ ์„ฑ--์กฐ์šฉํžˆ-๊นจ์ง€์ง€-์•Š๊ธฐ) ยท [ํ•œ๊ตญ ์‹œ์žฅ ํŠœ๋‹](#ํ•œ๊ตญ-์‹œ์žฅ-ํŠœ๋‹) ยท [๋น ๋ฅธ ์‹œ์ž‘](#๋น ๋ฅธ-์‹œ์ž‘) ยท [์˜ˆ์‹œ](#์˜ˆ์‹œ) ยท [์›น ์ฑ—๋ด‡ ๋Œ€๋น„](#์›น-์ฑ—๋ด‡-๋Œ€๋น„) ยท [ํ•œ๊ณ„](#์ •์งํ•œ-ํ•œ๊ณ„) ยท [๊ฐœ๋ฐœยทํ‰๊ฐ€](#๊ฐœ๋ฐœ--ํ‰๊ฐ€) +- **์Šค์Šค๋กœ ์ฐพ์•„์˜ต๋‹ˆ๋‹ค.** ์งˆ๋ฌธ์„ ๋ฐ›์œผ๋ฉด ์–ด๋–ค 1์ฐจ ์ถœ์ฒ˜๊ฐ€ ํ•„์š”ํ•œ์ง€ ์—์ด์ „ํŠธ๊ฐ€ ํŒ๋‹จํ•ด ๋„๊ตฌ๋ฅผ ๊ณจ๋ผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ DARTยทKRX๋ฅผ ์ผ์ผ์ด ๋’ค์งˆ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. +- **์—ฎ์–ด์„œ ๋‹ตํ•ฉ๋‹ˆ๋‹ค.** ์ง€ํ‘œ๋ฅผ ๋‚˜์—ดํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ์‹ค์ ยท์ˆ˜๊ธ‰ยท์ง€๋ฐฐ๊ตฌ์กฐยท๊ณต์‹œ๋ฅผ ํ•˜๋‚˜์˜ thesis๋กœ ์ข…ํ•ฉํ•ฉ๋‹ˆ๋‹ค. +- **์ธ์šฉํ•˜๊ณ  ์žฌํ˜„๋ฉ๋‹ˆ๋‹ค.** ๋ชจ๋“  ํ˜ธ์ถœ์ด ๊ธฐ๋ก๋˜์–ด "๋ฌด์—‡์„ ์–ด๋””์„œ ๊ฐ€์ ธ์™”๋Š”์ง€" ์ถ”์ ยท๊ฐ์‚ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +- **์กฐ์šฉํžˆ ์•ˆ ๊นจ์ง‘๋‹ˆ๋‹ค.** ํ•œ๋„ ์ดˆ๊ณผ๋‚˜ ์‘๋‹ต ๋ณ€๊ฒฝ์œผ๋กœ ๊ฐ’์ด ๋น„๋ฉด, ํ‹€๋ฆฐ ์ˆซ์ž๋ฅผ ๋‹ตํ•˜๋Š” ๋Œ€์‹  ๊ฒฝ๊ณ ๋ฅผ ๋ถ™์ž…๋‹ˆ๋‹ค. -## ๋ฌด์—‡์„ ํ•˜๋‚˜ +ํ•œ๋งˆ๋””๋กœ, Frontier LLM์˜ ๋Šฅ๋ ฅ์น˜๋ฅผ **ํ•œ๊ตญ ๊ธˆ์œต ํ•˜๋„ค์Šค**๋กœ ์ตœ๋Œ€ํ•œ ๋Œ์–ด๋‚ด๋Š” ์ œํ’ˆ์ž…๋‹ˆ๋‹ค. -์ž์—ฐ์–ด๋กœ ์งˆ๋ฌธ์„ ๋˜์ง€๋ฉด โ€” ์‹œ์žฅ์„ ์ง€์ •ํ•  ํ•„์š” ์—†์ด โ€” ์—์ด์ „ํŠธ๊ฐ€ ํ‹ฐ์ปค ํ˜•ํƒœ(6์ž๋ฆฌ ์ˆซ์ž = ํ•œ๊ตญ)์™€ ์–ธ์–ด๋ฅผ ๋ณด๊ณ  ์•Œ๋งž์€ ๋„๊ตฌ๋กœ ๋ผ์šฐํŒ…ํ•˜๊ณ , 1์ฐจ ์ถœ์ฒ˜๋ฅผ ํ˜ธ์ถœํ•ด, ๊ฒฐ๊ณผ๋ฅผ ๊ต์ฐจ ์ข…ํ•ฉํ•œ ๋’ค ์ธ์šฉ๋œ ๋‹ต์„ ๋ƒ…๋‹ˆ๋‹ค. +## ์ด๋Ÿฐ ๊ฑธ ๋ฌผ์–ด๋ณด์„ธ์š” -``` -> ์‚ผ์„ฑ์ „์ž ์ง€๊ธˆ ํˆฌ์ž ๊ด€์ ์—์„œ ์–ด๋•Œ? -``` - -์ด ํ•œ ์ค„์ด ๋‚ด๋ถ€์—์„œ๋Š”: **์ •๊ทœํ™” ์‹ค์ **(๋งค์ถœยท์˜์—…์ด์ตยท์ˆœ์ด์ตยท๋งˆ์ง„ยทROEยทFCFยทYoY) + **์™ธ๊ตญ์ธ/๊ธฐ๊ด€/๊ฐœ์ธ ์ผ๋ณ„ ์ˆ˜๊ธ‰** + **๊ณต๋งค๋„ ์ž”๊ณ ** + **5%๋ฃฐ ๋Œ€๋Ÿ‰๋ณด์œ ** + **๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค**๋ฅผ ๊ฐ๊ฐ 1์ฐจ ์ถœ์ฒ˜์—์„œ ๋Œ์–ด์™€, "์ˆ˜๊ธ‰ยท์‹ค์ ยท์ง€๋ฐฐ๊ตฌ์กฐ๊ฐ€ ๊ฐ™์€ ๋ฐฉํ–ฅ์ธ์ง€ ์ถฉ๋Œ์ธ์ง€"๋ฅผ ํŒ๋‹จํ•˜๋Š” ํ•˜๋‚˜์˜ thesis๋กœ ๋ฌถ์ž…๋‹ˆ๋‹ค. ๋‹จ์ˆœ ์ง€ํ‘œ ๋‚˜์—ด์ด ์•„๋‹ˆ๋ผ ์ข…ํ•ฉ์ด ๊ฒฐ๊ณผ๋ฌผ์ž…๋‹ˆ๋‹ค. - -๋ชจ๋“  ๋„๊ตฌ ํ˜ธ์ถœ์€ ์Šคํฌ๋ž˜์น˜ํŒจ๋“œ(JSONL)์— ๊ธฐ๋ก๋˜์–ด **๋ฌด์—‡์„ ์–ด๋””์„œ ๊ฐ€์ ธ์™”๋Š”์ง€ ๊ฐ์‚ฌยท์žฌํ˜„**ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - -## ์™œ ํ•œ๊ตญ ์ฃผ์‹์€ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด์•ผ ํ–ˆ๋‚˜ - -๋ฏธ๊ตญ์šฉ ๋„๊ตฌ๋ฅผ 6์ž๋ฆฌ ํ•œ๊ตญ ํ‹ฐ์ปค์— ๊ทธ๋Œ€๋กœ ์“ธ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค(์• ์ดˆ์— ํ‹ฐ์ปค๋ฅผ ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•จ). ๊ทธ๋ฆฌ๊ณ  ํ•œ๊ตญ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋Š” ๋ฏธ๊ตญ์˜ ๊น”๋”ํ•œ ํ‚ค ๊ธฐ๋ฐ˜ API์™€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค: - -| ์†Œ์Šค | ๋ฌด์—‡ | ํ˜„์‹ค | -|---|---|---| -| **DART** (๊ธˆ๊ฐ์› ์ „์ž๊ณต์‹œ) | ์žฌ๋ฌดยท๊ณต์‹œยท์ง€๋ฐฐ๊ตฌ์กฐ | ๋ฌด๋ฃŒ ๊ณต์‹ API์ง€๋งŒ **์ผ 20,000๊ฑด ํ•œ๋„** | -| **KRX** (ํ•œ๊ตญ๊ฑฐ๋ž˜์†Œ) | ๊ณต๋งค๋„ ์ž”๊ณ  | 2024โ€“25๋ถ€ํ„ฐ **ํšŒ์› ๋กœ๊ทธ์ธ ํ•„์ˆ˜**(์ต๋ช… ์ฐจ๋‹จ) | -| **Naver** ๋ชจ๋ฐ”์ผ | ํ˜„์žฌ๊ฐ€ยท์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ | **๊ณต์‹ ๊ณ„์•ฝ ์—†๋Š”** JSON ์—”๋“œํฌ์ธํŠธ | -| **๊ตญ๋ฏผ์—ฐ๊ธˆ** (data.go.kr) | ์—ฐ๊ธฐ๊ธˆ ๋ณด์œ  | ๋ถ„๊ธฐ ์•„๋‹Œ **์—ฐ๋ง ์Šค๋ƒ…์ƒท**, ์ข…๋ชฉ๋ช… ๋งค์นญ | - -๊ทธ๋ž˜์„œ ๋‘ ๊ฐ€์ง€๊ฐ€ ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค โ€” **(1)** ๊ฐ ์†Œ์Šค๋ฅผ ์ œ๋Œ€๋กœ ํ˜ธ์ถœํ•˜๋Š” ๋„๊ตฌ, **(2)** ๊ทธ๊ฒŒ ํ•œ๋„ ์ดˆ๊ณผยท๋กœ๊ทธ์ธ ๋งŒ๋ฃŒยท์‘๋‹ต ๊ตฌ์กฐ ๋ณ€๊ฒฝ์— **์กฐ์šฉํžˆ ๊นจ์ง€์ง€ ์•Š๊ฒŒ** ๋ฐ›์ณ์ฃผ๋Š” ๊ฒฌ๊ณ ์„ฑ ๋ ˆ์ด์–ด. ์•„๋ž˜ [๊ฒฌ๊ณ ์„ฑ](#๊ฒฌ๊ณ ์„ฑ--์กฐ์šฉํžˆ-๊นจ์ง€์ง€-์•Š๊ธฐ) ์„น์…˜์ด ์ด ํฌํฌ์˜ ์‹ค์ œ ์ฐจ๋ณ„์ ์ž…๋‹ˆ๋‹ค. - -## ๋„๊ตฌ - -**DART ๊ธฐ๋ฐ˜** โ€” `DART_API_KEY`๊ฐ€ ์„ค์ •๋˜๋ฉด ์ž๋™ ๋“ฑ๋ก. - -| ๋„๊ตฌ | ๋Œ€์‘ ๋ฏธ๊ตญ ๊ฐœ๋… | ๋‚ด์šฉ | -|---|---|---| -| `get_financials_kr` | `get_financials` (10-K/10-Q) | ์‚ฌ์—…ยท๋ฐ˜๊ธฐยท๋ถ„๊ธฐ๋ณด๊ณ ์„œ, K-IFRS ์ •๊ทœํ™” ์š”์•ฝ(์—ฐ๊ฒฐ/๋ณ„๋„) | -| `get_filings_kr` | SEC EDGAR | DART ๊ณต์‹œ ๊ฒ€์ƒ‰ | -| `read_filings_kr` | `read_filings` (10-K ๋ณธ๋ฌธ) | ๋ณด๊ณ ์„œ ๋ณธ๋ฌธ โ€” ์‚ฌ์—…์˜ ๋‚ด์šฉยท์ฃผ์š”์ œํ’ˆยท์œ„ํ—˜๊ด€๋ฆฌยทMD&A **+ ์ง€๋ฐฐ๊ตฌ์กฐยท์ตœ๋Œ€์ฃผ์ฃผยทํŠน์ˆ˜๊ด€๊ณ„์žยท๊ณ„์—ดํšŒ์‚ฌยท๋Œ€์ฃผ์ฃผ ๊ฑฐ๋ž˜** | -| `get_large_holders_kr` | 13F (5%+ ๋ณด์œ ) | ๋Œ€๋Ÿ‰๋ณด์œ ์ƒํ™ฉ๋ณด๊ณ ์„œ | -| `get_insider_trades_kr` | Form 4 (๋‚ด๋ถ€์ž) | ์ž„์›ยท์ฃผ์š”์ฃผ์ฃผ ๋ณด๊ณ  | - -**Korea-specific** โ€” DART์— ์—†๋Š” ๋ฐ์ดํ„ฐ. ์†Œ์Šคยทํ‚ค๊ฐ€ ๋„๊ตฌ๋งˆ๋‹ค ๋‹ค๋ฆ„. - -| ๋„๊ตฌ | ๋‚ด์šฉ (์†Œ์Šค) | ํ™œ์„ฑํ™” | -|---|---|---| -| `get_market_data_kr` | ํ˜„์žฌ๊ฐ€ยท์‹œ์ดยทPER/PBR/EPS/BPSยท์ถ”์ •PERยท๋ฐฐ๋‹นยท๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šคยทpeer (Naver) | **ํ‚ค ๋ถˆํ•„์š”** | -| `get_foreign_ownership_kr` | ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ + ์™ธ๊ตญ์ธ/๊ธฐ๊ด€/๊ฐœ์ธ ์ˆœ๋งค์ˆ˜ (Naver) | **ํ‚ค ๋ถˆํ•„์š”** | -| `get_short_balance_kr` | ๊ณต๋งค๋„ ์ˆœ๋ณด์œ ์ž”๊ณ  (KRX) | `KRX_ID`+`KRX_PW` ๋˜๋Š” `KRX_COOKIE` | -| `get_nps_holdings` | ๊ตญ๋ฏผ์—ฐ๊ธˆ ๊ตญ๋‚ด์ฃผ์‹ ๋ณด์œ  (data.go.kr) | `DATA_GO_KR_SERVICE_KEY` | - -> ํ˜„์žฌ๊ฐ€ยท์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ์€ **ํ‚ค ํ•˜๋‚˜ ์—†์ด** ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. DART ํ‚ค๊ฐ€ ์—†์–ด๋„ ํ•œ๊ตญ ์ข…๋ชฉ์˜ ์‹œ์„ธยท๋ฐธ๋ฅ˜์—์ด์…˜ยท์™ธ๊ตญ์ธ ์ˆ˜๊ธ‰์€ ๋ฐ”๋กœ ์กฐํšŒ๋ฉ๋‹ˆ๋‹ค. - -## ๊ฒฌ๊ณ ์„ฑ โ€” ์กฐ์šฉํžˆ ๊นจ์ง€์ง€ ์•Š๊ธฐ - -ํ•œ๊ตญ ์†Œ์Šค์˜ ๊ฑฐ์นœ ํ˜„์‹ค ์œ„์—์„œ ์ž˜๋ชป๋œ ์ˆซ์ž๋ฅผ ์‚ฌ์‹ค์ฒ˜๋Ÿผ ๋‹ตํ•˜์ง€ ์•Š๋„๋ก ๋‹ค์Œ์„ ๋‚ด์žฅํ–ˆ์Šต๋‹ˆ๋‹ค. - -- **DART ์ฟผํ„ฐ ๋ณดํ˜ธ** โ€” `get_financials_kr`(์—ฐ๋„๋ณ„) ร— ๋ผ์šฐํŒ…(์ข…๋ชฉ๋ณ„) ร— ์—์ด์ „ํŠธ(๋„๊ตฌ ๋ณ‘๋ ฌ)๊ฐ€ ๊ณฑํ•ด์ง€๋ฉด "์ด ์ข…๋ชฉ ๋ถ„์„" ํ•œ ๋ฒˆ์ด ์ˆ˜์‹ญ ๊ฐœ์˜ DART ํ˜ธ์ถœ๋กœ ๋ฒˆ์ง‘๋‹ˆ๋‹ค. **๊ณต์œ  ๋™์‹œ์„ฑ ์บก**(๊ธฐ๋ณธ 4, `DART_MAX_CONCURRENCY`๋กœ ์กฐ์ •)์ด ์ผ 20,000๊ฑด ํ•œ๋„๋ฅผ ํƒœ์šฐ์ง€ ์•Š๊ฒŒ ๋ง‰๊ณ , ํ•œ๋„ ์ดˆ๊ณผ(`020`)๊ฐ€ ๊ฐ์ง€๋˜๋ฉด **์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค**๊ฐ€ ๋‚˜๋จธ์ง€ ํ˜ธ์ถœ์„ ๋„คํŠธ์›Œํฌ ๋‚ญ๋น„ ์—†์ด ์ฆ‰์‹œ ์ค‘๋‹จํ•˜๊ณ  ๋ช…ํ™•ํ•œ ํ•œ๊ตญ์–ด ๋ฉ”์‹œ์ง€๋กœ ์•Œ๋ฆฐ ๋’ค ์ฟจ๋‹ค์šด ํ›„ ์ž๋™ ์žฌ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค. (์ผ์ผ ํ•œ๋„์—” ์žฌ์‹œ๋„ backoff๊ฐ€ ๋ฌด์˜๋ฏธํ•˜๋ฏ€๋กœ throttle + ๋ช…์‹œ์  ์•Œ๋ฆผ์œผ๋กœ ๋Œ€์‘.) -- **๋ฐ์ดํ„ฐ ๋“œ๋ฆฌํ”„ํŠธ ๊ฐ์ง€(canary)** โ€” Naver JSON์€ ๋น„๊ณต์‹์ด๋ผ ์–ด๋А ๋‚  ํ•„๋“œ๋ช…์ด ๋ฐ”๋€Œ๋ฉด ๊ฐ’์ด **์กฐ์šฉํžˆ null**์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ต์‹ฌ ํ•„๋“œ(ํ˜„์žฌ๊ฐ€ยท์‹œ์ดยท์ผ๋ณ„ ์ˆ˜๊ธ‰)๊ฐ€ ๋น„๋ฉด ๊ฒฐ๊ณผ์— `_dataQualityWarning`์„ ๋ถ™์—ฌ ๋ชจ๋ธ์ด ๊นจ์ง„ ๊ฐ’์„ ๋ณด๊ณ ํ•˜์ง€ ์•Š๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ณ„ ์ข…๊ฐ€๊ฐ€ rename๋˜์–ด **์ „์ผ ์ข…๊ฐ€๋กœ ๊ฐ€๋ ค์ง€๋Š”** ์ตœ์•… ์ผ€์ด์Šค๊นŒ์ง€ ๊ฐ์ง€ํ•˜๊ณ , ETF/ETNยทํŽ€๋“œยท๋ฌด์ปค๋ฒ„ ์ข…๋ชฉ์ฒ˜๋Ÿผ ๋ณธ๋ž˜ ์ผ๋ถ€ ์ง€ํ‘œ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ๋Š” ์˜คํƒ์„ ๋‚ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. -- **keyless ํ”Œ๋ ˆ์ด๋ถ ํ‹ฐ์–ด๋ง** โ€” DART ํ‚ค๊ฐ€ ์—†์–ด๋„ keyless ๋„๊ตฌ๋งŒ์œผ๋กœ **์ˆ˜๊ธ‰ยท๋ฐธ๋ฅ˜์—์ด์…˜ ๋ฆฌ์„œ์น˜ ๊ฐ€์ด๋“œ๊ฐ€ ์‚ด์•„ ์žˆ์Šต๋‹ˆ๋‹ค.** ํ‚ค๊ฐ€ ์žˆ์œผ๋ฉด DART 1์ฐจ ์ž๋ฃŒ๋ฅผ ํฌํ•จํ•œ ์ „์ฒด ๊ต์ฐจ์‹ ํ˜ธ ํ”Œ๋ ˆ์ด๋ถ์œผ๋กœ ํ™•์žฅ๋ฉ๋‹ˆ๋‹ค(๋ฏธ๋“ฑ๋ก ๋„๊ตฌ๋ฅผ ๊ฐ€๋ฆฌํ‚ค์ง€ ์•Š๋„๋ก ๋“ฑ๋ก ๊ฒŒ์ดํŠธ์™€ ๋‹จ์ผ ์ง„์‹ค์›์œผ๋กœ ์ผ์น˜). -- **๊ฒฐ์ •์  ์žฌํ˜„(record/replay)** โ€” ํ‰๊ฐ€ ํ•˜๋„ค์Šค๊ฐ€ ์‹ค์ œ DARTยทKRXยทNaver ์‘๋‹ต์„ ํ”ฝ์Šค์ฒ˜๋กœ ๊ธฐ๋กํ•ด๋‘๋ฉด, ๊ฐ™์€ ์งˆ๋ฌธ์„ **ํ‚คยท๋„คํŠธ์›Œํฌ ์—†์ด ๊ฒฐ์ •์ ์œผ๋กœ ์žฌํ˜„ยทํšŒ๊ท€ ํ…Œ์ŠคํŠธ**ํ•ฉ๋‹ˆ๋‹ค. [๊ฐœ๋ฐœยทํ‰๊ฐ€](#๊ฐœ๋ฐœ--ํ‰๊ฐ€) ์ฐธ๊ณ . - -## ํ•œ๊ตญ ์‹œ์žฅ ํŠœ๋‹ +์‹œ์žฅ์„ ์ง€์ •ํ•  ํ•„์š” ์—†์ด ํ•œ๊ตญ์–ด๋กœ ๊ทธ๋ƒฅ ์งˆ๋ฌธํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค. (6์ž๋ฆฌ ์ˆซ์ž ํ‹ฐ์ปค๋Š” ํ•œ๊ตญ์œผ๋กœ ์ž๋™ ์ธ์‹) -- **DCF ์ž๋™ ๋ถ„๊ธฐ** โ€” 6์ž๋ฆฌ ํ‹ฐ์ปค๋ฉด DCF ์Šคํ‚ฌ์ด ํ•œ๊ตญ ๊ฒฝ๋กœ๋กœ ์ „ํ™˜(๋ฒ•์ธ์„ธ์œจ ~22%ยท๊ตญ๊ณ ์ฑ„ ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ ~3%ยทKRW). **์ˆœ๋ถ€์ฑ„๋ฅผ ๋ถ€์ฑ„์ด๊ณ„๊ฐ€ ์•„๋‹ˆ๋ผ ์ด์ž๋ถ€์ฑ„(์ฐจ์ž…๊ธˆยท์‚ฌ์ฑ„)์—์„œ ํ˜„๊ธˆยท๋‹จ๊ธฐ๊ธˆ์œต์ƒํ’ˆ์„ ๋บ€ ๊ฐ’์œผ๋กœ** ์‚ฐ์ • โ€” ์‚ผ์„ฑ์ฒ˜๋Ÿผ ์ˆœํ˜„๊ธˆ ๊ธฐ์—…์€ ์ˆœํ˜„๊ธˆ์„ ๋”ํ•ด ์ฃผ์‹๊ฐ€์น˜๊ฐ€ EV๋ณด๋‹ค ์ปค์ง‘๋‹ˆ๋‹ค(๋ถ€์ฑ„์ด๊ณ„๋กœ ์ž˜๋ชป ๋นผ๋ฉด ๋ถ€ํ˜ธ๊ฐ€ ๋’ค์ง‘ํ˜€ ์ €ํ‰๊ฐ€). ๊ฑฐ๋ž˜์„ธยท๋ฐฐ๋‹น์„ธ๋Š” ํˆฌ์ž์ž ์„ธํ›„ ์‹คํ˜„์ˆ˜์ต๋ฅ  ์ฃผ์˜์‚ฌํ•ญ์œผ๋กœ ํ‘œ๊ธฐ. -- **์ง€๋ฐฐ๊ตฌ์กฐยท์†Œ์œ ๊ตฌ์กฐ ๋ณธ๋ฌธ** โ€” ์ฝ”๋ฆฌ์•„ ๋””์Šค์นด์šดํŠธ์˜ 1์ฐจ ๋ณ€์ˆ˜(์ตœ๋Œ€์ฃผ์ฃผยทํŠน์ˆ˜๊ด€๊ณ„์ธยท๊ณ„์—ดํšŒ์‚ฌยท์ˆœํ™˜์ถœ์žยท๋Œ€์ฃผ์ฃผ ๊ฑฐ๋ž˜)๋ฅผ DART ์‚ฌ์—…๋ณด๊ณ ์„œ ๋ณธ๋ฌธ(VI ํšŒ์‚ฌ์˜ ๊ธฐ๊ด€ยทVII ์ฃผ์ฃผ์— ๊ด€ํ•œ ์‚ฌํ•ญยทIX ๊ณ„์—ดํšŒ์‚ฌยทX ๋Œ€์ฃผ์ฃผ ๋“ฑ๊ณผ์˜ ๊ฑฐ๋ž˜๋‚ด์šฉ)์—์„œ **์ถ”์ •์ด ์•„๋‹ˆ๋ผ 1์ฐจ ๊ทผ๊ฑฐ๋กœ** ์ถ”์ถœ. -- **๋ฌผ์ ๋ถ„ํ•  ๋ถ„์„** โ€” LGํ™”ํ•™โ†’LG์—๋„ˆ์ง€์†”๋ฃจ์…˜ ๊ฐ™์€ ๋ถ„ํ•  ์ด๋ฒคํŠธ๋ฅผ ๋ชจํšŒ์‚ฌ ์ฃผ์ฃผ ๊ด€์ (ํฌ์„ยท์ง€์ฃผ์‚ฌ ๋””์Šค์นด์šดํŠธ)์—์„œ ํ‰๊ฐ€ํ•˜๋Š” ์ „์šฉ ์Šคํ‚ฌ. -- **๊ต์ฐจ์‹ ํ˜ธ ์ข…ํ•ฉ** โ€” ์žฌ๋ฌดยท์™ธ๊ตญ์ธ ์ˆ˜๊ธ‰ยท๊ณต๋งค๋„ยท๋Œ€๋Ÿ‰๋ณด์œ ยท๊ณต์‹œ๋ฅผ ๋‹จ์ˆœ ๋‚˜์—ด์ด ์•„๋‹ˆ๋ผ ํ•˜๋‚˜์˜ thesis๋กœ ์—ฎ์Œ(๋Œ€๋Ÿ‰๋ณด์œ  ์ง‘์ค‘์€ ๋ฐธ๋ฅ˜์—์ด์…˜ ์š”์ธ์œผ๋กœ ์ฒ˜๋ฆฌ). -- **K-IFRS ์ •๊ทœํ™”** โ€” ์—ฐ๊ฒฐ/๋ณ„๋„ ๋‘˜ ๋‹ค, ๋งค์ถœยท์˜์—…์ด์ตยท์ˆœ์ด์ตยท๋งˆ์ง„ยทROEยทFCFยทYoY๋กœ ์š”์•ฝ(์›์‹œ ๋ผ์ธ์•„์ดํ…œ์€ ํŒŒ์ผ๋กœ ๋ณด์กดํ•ด ๋“œ๋ฆด๋‹ค์šด). ์˜์—…์ด์ต ์ •์˜๊ฐ€ ๋ฏธ๊ตญ GAAP์™€ ๋ฏธ๋ฌ˜ํ•˜๊ฒŒ ๋‹ค๋ฅธ ์  ๋ฐ˜์˜. -- **๋‹จ์œ„ยท์‹œ์žฅ ๊ทœ์น™** โ€” ์›(KRW)ยท์–ตยท์กฐ ์ž๋™ ํฌ๋งท, 09:00โ€“15:30 KST ๊ฑฐ๋ž˜์‹œ๊ฐ„, ยฑ30% ์ƒํ•˜ํ•œ๊ฐ€. -- **์ข…๋ชฉ ์ฝ”๋“œ ํ•ด๊ฒฐ** โ€” "์‚ผ์„ฑ์ „์ž" โ†’ ํ‹ฐ์ปค โ†’ `corp_code`. DART ๋งˆ์Šคํ„ฐ(`corpCode.xml`)๋ฅผ ์ฒซ ์‹คํ–‰ ์‹œ ๋ฐ›์•„ `.dexter/cache/`์— ์ €์žฅํ•˜๊ณ  7์ผ๋งˆ๋‹ค ๊ฐฑ์‹ (์‹ ๊ทœ ์ƒ์žฅยท์‚ฌ๋ช… ๋ณ€๊ฒฝยท๋ฌผ์ ๋ถ„ํ•  ์ž๋™ ๋ฐ˜์˜). KRX์šฉ ํ‹ฐ์ปคโ†’ISIN ๋งคํ•‘๋„ ๋ณ„๋„ ๊ด€๋ฆฌ. +**ํ•œ ์ค„ ์ข…ํ•ฉ โ€” ๋ฃจํ”„๊ฐ€ ๊ฐ€์žฅ ๋น›๋‚˜๋Š” ์งˆ๋ฌธ** -## ๋น ๋ฅธ ์‹œ์ž‘ - -**ํ•„์š”ํ•œ ๊ฒƒ:** [Bun](https://bun.com) v1.0+ ยท LLM ํ‚ค ํ•˜๋‚˜(OpenAI/Anthropic/Google/xAI/OpenRouter/Ollama ๋“ฑ) ยท (์„ ํƒ) ์‹œ์žฅ ๋ฐ์ดํ„ฐ ํ‚ค. -ํ‚ค๊ฐ€ ํ•˜๋‚˜๋„ ์—†์–ด๋„ ํ•œ๊ตญ ์ข…๋ชฉ ํ˜„์žฌ๊ฐ€ยท์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ(Naver, ํ‚ค ๋ถˆํ•„์š”)์€ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. - -```bash -# Bun ์„ค์น˜ (์—†๋‹ค๋ฉด) -curl -fsSL https://bun.com/install | bash # macOS/Linux -# powershell -c "irm bun.sh/install.ps1|iex" # Windows - -git clone https://github.com/SangHyeonKwon/dexter-with-korea.git -cd dexter-with-korea -bun install - -cp env.example .env # ํ‚ค ์ฑ„์šฐ๊ธฐ (์•„๋ž˜) -bun start # ๋Œ€ํ™”ํ˜• ์‹คํ–‰ (bun dev = watch ๋ชจ๋“œ) +``` +์‚ผ์„ฑ์ „์ž ์ง€๊ธˆ ํˆฌ์ž ๊ด€์ ์—์„œ ์–ด๋•Œ? +โ†ณ ์‹ค์ ยท์™ธ๊ตญ์ธ ์ˆ˜๊ธ‰ยท๊ณต๋งค๋„ยท๋Œ€๋Ÿ‰๋ณด์œ ยท๋ชฉํ‘œ์ฃผ๊ฐ€๋ฅผ ํ•œ ๋ฒˆ์— ๋Œ์–ด์™€ ๊ต์ฐจ ์ข…ํ•ฉ ``` -**ํ‚ค** (`.env`, `your-`๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฐ’์€ ๋ฏธ์„ค์ •์œผ๋กœ ๊ฐ„์ฃผ): - -```bash -# LLM โ€” ์ตœ์†Œ ํ•˜๋‚˜ ํ•„์ˆ˜ -OPENAI_API_KEY=your-openai-api-key -# ANTHROPIC_API_KEY / GOOGLE_API_KEY / XAI_API_KEY / OPENROUTER_API_KEY / OLLAMA_BASE_URL โ€ฆ +**๋ฐธ๋ฅ˜์—์ด์…˜** -# ๋ฏธ๊ตญ ์‹œ์žฅ ๋ฐ์ดํ„ฐ -FINANCIAL_DATASETS_API_KEY=your-financial-datasets-api-key +``` +SKํ•˜์ด๋‹‰์Šค DCF๋กœ ์ ์ •์ฃผ๊ฐ€ ๊ณ„์‚ฐํ•˜๊ณ  ํ˜„์žฌ๊ฐ€๋ž‘ ๋น„๊ตํ•ด์ค˜ +์‚ผ์„ฑ์ „์ž ์ตœ๊ทผ 5๋…„ ๋งค์ถœยท์˜์—…์ด์ต ์ถ”์„ธ ์ •๋ฆฌํ•ด์ค˜ +์—”๋น„๋””์•„๋ž‘ SKํ•˜์ด๋‹‰์Šค HBM ์‹ค์  ๋น„๊ตํ•ด์ค˜ โ† ํ•œยท๋ฏธ ํ†ตํ•ฉ +``` -# ํ•œ๊ตญ ์‹œ์žฅ -DART_API_KEY=your-dart-api-key # ์žฌ๋ฌดยท๊ณต์‹œยท์ง€๋ฐฐ๊ตฌ์กฐ (๋ฌด๋ฃŒ, ์ผ 20,000๊ฑด โ€” opendart.fss.or.kr) -# KRX_ID / KRX_PW # ๊ณต๋งค๋„ ์ž”๊ณ  (data.krx.co.kr ๋กœ๊ทธ์ธ) -# KRX_COOKIE=JSESSIONID=... # ์†Œ์…œ ๋กœ๊ทธ์ธ ๊ณ„์ •์€ ๋ธŒ๋ผ์šฐ์ € ์ฟ ํ‚ค ๋ถ™์—ฌ๋„ฃ๊ธฐ -# DATA_GO_KR_SERVICE_KEY=... # ๊ตญ๋ฏผ์—ฐ๊ธˆ (data.go.kr, Decoded ํ‚ค) -# DART_MAX_CONCURRENCY=4 # (์„ ํƒ) DART ๋™์‹œ ํ˜ธ์ถœ ์ƒํ•œ -# READ_FILINGS_KR_MODEL=... # (์„ ํƒ) read_filings_kr ๋‚ด๋ถ€ ์š”์•ฝ ๋ชจ๋ธ +**๊ณต์‹œ ยท ์ง€๋ฐฐ๊ตฌ์กฐ (DART ๋ณธ๋ฌธ์—์„œ ์ง์ ‘)** -# ์›น ๊ฒ€์ƒ‰ (์„ ํƒ, Exa โ†’ Perplexity โ†’ Tavily โ†’ LangSearch ํด๋ฐฑ) / X ์„ผํ‹ฐ๋จผํŠธ (์„ ํƒ) -# EXASEARCH_API_KEY / PERPLEXITY_API_KEY / TAVILY_API_KEY / LANGSEARCH_API_KEY / X_BEARER_TOKEN +``` +์นด์นด์˜ค ์ตœ๊ทผ 1๋…„ ์ฃผ์š” ๊ณต์‹œ ์ •๋ฆฌํ•ด์ค˜ +์‚ผ์„ฑ์ „์ž ์ตœ๋Œ€์ฃผ์ฃผยทํŠน์ˆ˜๊ด€๊ณ„์ธยท๊ณ„์—ดํšŒ์‚ฌ ๊ด€๊ณ„ ์‚ฌ์—…๋ณด๊ณ ์„œ ๊ธฐ์ค€์œผ๋กœ ์ •๋ฆฌํ•ด์ค˜ +LGํ™”ํ•™ ๋ฌผ์ ๋ถ„ํ• ์ด ๊ธฐ์กด ์ฃผ์ฃผ๊ฐ€์น˜์— ์–ด๋–ค ์˜ํ–ฅ์ด์—ˆ๋Š”์ง€ ๋ถ„์„ํ•ด์ค˜ ``` -**ํ™œ์„ฑํ™” ๊ทœ์น™:** `DART_API_KEY` โ†’ 5๊ฐœ DART ๋„๊ตฌ ยท `KRX_ID`+`KRX_PW`(๋˜๋Š” `KRX_COOKIE`) โ†’ ๊ณต๋งค๋„ ยท `DATA_GO_KR_SERVICE_KEY` โ†’ ๊ตญ๋ฏผ์—ฐ๊ธˆ. ํ˜„์žฌ๊ฐ€ยท์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ์€ ํ‚ค ์—†์ด ํ•ญ์ƒ ๋“ฑ๋ก. +**ํ•œ๊ตญ ํŠนํ™” ๋ฐ์ดํ„ฐ** -## ์˜ˆ์‹œ +``` +์‚ผ์„ฑ์ „์ž ํ˜„์žฌ๊ฐ€๋ž‘ ๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค ์•Œ๋ ค์ค˜ โ† ํ‚ค ์—†์ด ๋™์ž‘ +์‚ผ์„ฑ์ „์ž ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ ์ตœ๊ทผ ์ถ”์„ธ ๋ณด์—ฌ์ค˜ โ† ํ‚ค ์—†์ด ๋™์ž‘ +์—์ฝ”ํ”„๋กœ๋น„์—  ๊ณต๋งค๋„ ์ˆœ๋ณด์œ ์ž”๊ณ  ์ถ”์ด ์–ด๋•Œ? +๊ตญ๋ฏผ์—ฐ๊ธˆ์ด ๋ณด์œ ํ•œ ์‚ผ์„ฑ์ „์ž ์ง€๋ถ„ ์•Œ๋ ค์ค˜ +``` -์‹œ์žฅ์„ ์ง€์ •ํ•  ํ•„์š” ์—†์ด ํ‹ฐ์ปค ํ˜•ํƒœ์™€ ์–ธ์–ด๋กœ ๋ผ์šฐํŒ…๋ฉ๋‹ˆ๋‹ค. +**๋ฉ”๋ชจ๋กœ ์ •๋ฆฌ** ``` -# ์žฌ๋ฌด ยท ๋ฐธ๋ฅ˜์—์ด์…˜ -> ์‚ผ์„ฑ์ „์ž ์ตœ๊ทผ 5๋…„ ๋งค์ถœ๊ณผ ์˜์—…์ด์ต ์ถ”์„ธ ์ •๋ฆฌํ•ด์ค˜ -> ์‚ผ์„ฑ์ „์ž DCF๋กœ ์ ์ •์ฃผ๊ฐ€ ๊ณ„์‚ฐํ•˜๊ณ  ํ˜„์žฌ๊ฐ€๋ž‘ ๋น„๊ตํ•ด์ค˜ # 6์ž๋ฆฌ๋ฉด KR ๊ฒฝ๋กœ ์ž๋™ ๋ถ„๊ธฐ -> ์—”๋น„๋””์•„์™€ SKํ•˜์ด๋‹‰์Šค HBM ๊ด€๋ จ ์‹ค์  ๋น„๊ต ๋ถ„์„ํ•ด์ค˜ # ํฌ๋กœ์Šค๋งˆ์ผ“ - -# ๊ณต์‹œ ยท ์ง€๋ฐฐ๊ตฌ์กฐ (DART) -> ์นด์นด์˜ค ์ตœ๊ทผ 1๋…„ ์ฃผ์š” ๊ณต์‹œ ์ •๋ฆฌํ•ด์ค˜ -> ์‚ผ์„ฑ์ „์ž ์ตœ๋Œ€์ฃผ์ฃผยทํŠน์ˆ˜๊ด€๊ณ„์ธ ์ง€๋ถ„์ด๋ž‘ ๊ณ„์—ดํšŒ์‚ฌ ๊ด€๊ณ„ ์‚ฌ์—…๋ณด๊ณ ์„œ ๊ธฐ์ค€์œผ๋กœ ์ •๋ฆฌํ•ด์ค˜ -> ์‚ผ์„ฑ์ „์ž ์ฃผ์š” ๋ฆฌ์Šคํฌ๊ฐ€ ๋ญ์•ผ? - -# ํ•œ๊ตญ ํŠนํ™” ๋ฐ์ดํ„ฐ -> ์‚ผ์„ฑ์ „์ž ํ˜„์žฌ๊ฐ€๋ž‘ ๋ชฉํ‘œ์ฃผ๊ฐ€ ์ปจ์„ผ์„œ์Šค ์•Œ๋ ค์ค˜ # ํ‚ค ๋ถˆํ•„์š” (Naver) -> ์‚ผ์„ฑ์ „์ž ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ ์ตœ๊ทผ ์ถ”์„ธ ๋ณด์—ฌ์ค˜ # ํ‚ค ๋ถˆํ•„์š” (Naver) -> ์—์ฝ”ํ”„๋กœ๋น„์—  ๊ณต๋งค๋„ ์ˆœ๋ณด์œ ์ž”๊ณ  ์ถ”์ด ์–ด๋•Œ? # KRX ํ‚ค ํ•„์š” -> ๊ตญ๋ฏผ์—ฐ๊ธˆ์ด ๋ณด์œ ํ•œ ์‚ผ์„ฑ์ „์ž ์ง€๋ถ„์œจ ์•Œ๋ ค์ค˜ # data.go.kr ํ‚ค ํ•„์š” - -# ์ด๋ฒคํŠธ ยท ๋ฉ”๋ชจ ยท ์ข…ํ•ฉ -> LGํ™”ํ•™ ๋ฌผ์ ๋ถ„ํ• ์ด ๊ธฐ์กด ์ฃผ์ฃผ๊ฐ€์น˜์— ์–ด๋–ค ์˜ํ–ฅ์ด์—ˆ๋Š”์ง€ ๋ถ„์„ํ•ด์ค˜ -> ์‚ผ์„ฑ์ „์ž ์ง€๊ธˆ ํˆฌ์ž ๊ด€์ ์—์„œ ์–ด๋•Œ? # ์ˆ˜๊ธ‰ยท์‹ค์ ยท์ง€๋ฐฐ๊ตฌ์กฐ ๊ต์ฐจ ์ข…ํ•ฉ -> ์‚ผ์„ฑ์ „์ž ๋งค์ˆ˜ ๋…ผ๊ฑฐ๋ฅผ ํˆฌ์ž ๋ฉ”๋ชจ๋กœ ์ž‘์„ฑํ•ด์ค˜ +์‚ผ์„ฑ์ „์ž ๋งค์ˆ˜ ๋…ผ๊ฑฐ๋ฅผ ํˆฌ์ž ๋ฉ”๋ชจ๋กœ ์ž‘์„ฑํ•ด์ค˜ ``` -## ์›น ์ฑ—๋ด‡ ๋Œ€๋น„ +## ์›น ์ฑ—๋ด‡์ด๋ž‘ ๋ญ๊ฐ€ ๋‹ฌ๋ผ? -2026๋…„ 5์›”, ๋™์ผ ์งˆ๋ฌธ 5๊ฐœ๋ฅผ **Dexter(GPT-5.5)** ์™€ **claude.ai(Opus 4.8 + ์›น๊ฒ€์ƒ‰)** ์— ๋˜์ ธ ์‹ค์ธกํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค ํ”„๋Ÿฐํ‹ฐ์–ด ๋ชจ๋ธ์ด๋ฏ€๋กœ ์ฐจ์ด๋Š” ๋ชจ๋ธ์ด ์•„๋‹ˆ๋ผ **๊ธˆ์œต ํ•˜๋„ค์Šค โ€” 1์ฐจ ์ถœ์ฒ˜ ๋„๊ตฌ + ํ•œ๊ตญ ์‹œ์žฅ ์Šคํ‚ฌ** ์—์„œ ์˜ต๋‹ˆ๋‹ค. *(๊ฐ ๋„๊ตฌ๊ฐ€ ๋ฐ˜ํ™˜ํ•œ ๊ฐ’ ๊ทธ๋Œ€๋กœ์ด๋ฉฐ ๋ณ„๋„ ์ •๋ฐ€ ๊ฐ์‚ฌ๋Š” ์•ˆ ํ•จ. ์‚ผ์„ฑ ์†์ต์€ ์ •๊ทœํ™” ๊ฐœ์„  ํ›„ ์žฌ์ธก์ •, ๋„ค์ด๋ฒ„ DCF๋Š” ์ˆœ๋ถ€์ฑ„ ๋ณด์ • ์ด์ „ ๊ฐ’์ด๋ผ ํ˜„ํ–‰ ์ฝ”๋“œ๋ก  ๋” ๋†’๊ฒŒ ๋‚˜์˜ฌ ์ˆ˜ ์žˆ์Œ.)* +๊ฐ™์€ ์งˆ๋ฌธ์„ ์ผ๋ฐ˜ AI ์ฑ—๋ด‡(+์›น๊ฒ€์ƒ‰)๊ณผ ๋น„๊ตํ•ด ๋ณด๋ฉด, ์ฐจ์ด๋Š” ๋ชจ๋ธ์ด ์•„๋‹ˆ๋ผ **1์ฐจ ์ถœ์ฒ˜๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋Š” ํ•˜๋„ค์Šค**์—์„œ ์˜ต๋‹ˆ๋‹ค. -| ์งˆ๋ฌธ | claude.ai (Opus 4.8 + ์›น๊ฒ€์ƒ‰) | Dexter (GPT-5.5 + ํ•˜๋„ค์Šค) | +| | ์ผ๋ฐ˜ ์ฑ—๋ด‡ (+์›น๊ฒ€์ƒ‰) | Dexter | |---|---|---| -| ์‚ผ์„ฑ ๋งค์ถœ/์˜์—…์ด์ต | 333.6์กฐ / 43.6์กฐ, ๋‰ด์Šค ์ถœ์ฒ˜ โœ“ (๋‹จ "1Q 57์กฐ" ํ™˜๊ฐ) | **333.6์กฐ / 43.6์กฐ (+10.9%/+33.2% YoY)** + ๋งˆ์ง„ยทROEยทFCF, DART ์ •๊ทœํ™” ์ง์ ‘ ์ถ”์ถœ | -| ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ | 48.42% (05-20), ๋‰ด์Šค | **48.27% (05-29)** ์ผ๋ณ„ + ์ˆœ๋งค๋„๋Ÿ‰, Naver ์ง์ ‘ | -| ๋„ค์ด๋ฒ„ DCF | 161,700์›, ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌ 4.15% | ~200,000์›, **KR ์ž๋™ ๋ถ„๊ธฐ**, ์‹ค์‹œ์„ธ ๊ธฐ๋ฐ˜ | -| LGํ™”ํ•™ ๋ฌผ์ ๋ถ„ํ•  | ๋‹ค์ถœ์ฒ˜ ์„œ์ˆ  | DART ๊ณต์‹œ 9๊ฑด ์ง์ ‘ ์ธ์šฉ + ๊ตฌ์กฐ ๋ถ„์„ | -| ์—์ฝ”ํ”„๋กœ๋น„์—  ๊ณต๋งค๋„ | 1๊ฐœ ์‹œ์ (5/07) + "KRX์„œ ์กฐํšŒ" ์•ˆ๋‚ด | **18์ผ ์ผ๋ณ„ ์ž”๊ณ ์œจยท๊ธˆ์•กยท์ˆ˜๋Ÿ‰** (KRX ์ง์ ‘) | - -ํ•ต์‹ฌ ์ฐจ์ด๋Š” **1์ฐจ ์ถœ์ฒ˜ ์ง์ ‘ ํ˜ธ์ถœ**(๋‰ด์Šค 2์ฐจ๊ฐ€ ์•„๋‹˜) ยท **๊ตฌ์กฐํ™” ์‹œ๊ณ„์—ด**(๊ณต๋งค๋„ 18์ผยท์™ธ๊ตญ์ธ ์ผ๋ณ„) ยท **์ •ํ™•ํ•œ ๋ฐธ๋ฅ˜์—์ด์…˜**(์ˆœํ˜„๊ธˆ ๊ธฐ์—…์„ ์ €ํ‰๊ฐ€ํ•˜์ง€ ์•Š๋Š” ์ˆœ๋ถ€์ฑ„ ๊ณ„์‚ฐ) ยท **๊นจ์ ธ๋„ ์กฐ์šฉํ•˜์ง€ ์•Š์Œ**(์ฟผํ„ฐ/๋“œ๋ฆฌํ”„ํŠธ ๋ช…์‹œ) ยท **๊ฐ์‚ฌยท์žฌํ˜„ ๊ฐ€๋Šฅ**(์Šคํฌ๋ž˜์น˜ํŒจ๋“œ + record/replay)์ž…๋‹ˆ๋‹ค. - -## ์ •์งํ•œ ํ•œ๊ณ„ - -ํ•ญ์ƒ ์ด๊ธฐ๋Š” ๋„๊ตฌ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค: +| ์ถœ์ฒ˜ | ๋‰ด์Šคยท๋ธ”๋กœ๊ทธ 2์ฐจ | **DARTยทKRXยทNaver 1์ฐจ** | +| ๊ณต๋งค๋„ ยท ์™ธ๊ตญ์ธ | 1๊ฐœ ์‹œ์  + "์ง์ ‘ ์กฐํšŒํ•˜์„ธ์š”" | **์ˆ˜์‹ญ ์ผ ์ผ๋ณ„ ์‹œ๊ณ„์—ด** | +| ๋ฐธ๋ฅ˜์—์ด์…˜ | ์ผ๋ฐ˜ ๊ฐ€์ • | **ํ•œ๊ตญ ์‹œ์žฅ ์ž๋™ ๋ถ„๊ธฐ** (๋ฒ•์ธ์„ธยท๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌยทKRW) | +| ์‹ ๋ขฐ | ๊ฐ€๋” ํ™˜๊ฐ | **์ธ์šฉ + ์žฌํ˜„ ๊ฐ€๋Šฅ** | -- ํ‘œ์ค€ ์ œ์กฐ์—… ์†์ต์€ ๋งค๋„๋Ÿฝ๊ฒŒ ์ •๊ทœํ™”๋˜์ง€๋งŒ, ์€ํ–‰ยท์ง€์ฃผ์‚ฌ์˜ ๋น„ํ‘œ์ค€ K-IFRS ๋ผ๋ฒจ(์˜์—…์ˆ˜์ต ๋“ฑ)์€ ์ผ๋ถ€ ์ง€ํ‘œ๊ฐ€ ๋น„์–ด raw ๋“œ๋ฆด๋‹ค์šด์œผ๋กœ ๋ณด์™„ โ€” ์ปค๋ฒ„๋ฆฌ์ง€๊ฐ€ ๊ท ์ผํ•˜์ง€ ์•Š์Œ. -- ์ •๋ฐ€ ์ปจ์„ผ์„œ์Šค(๋ธŒ๋กœ์ปค๋ณ„ ๋ชฉํ‘œ์ฃผ๊ฐ€ยท์ถ”์ •์น˜ ๋ถ„์‚ฐ)ยท์„ธ๊ทธ๋จผํŠธ ์žฌ๋ฌด๋Š” FnGuide ๋“ฑ ์œ ๋ฃŒ ๋‹จ๋ง์— ๋ชป ๋ฏธ์นจ(keyless ์†Œ์Šค๋Š” ๋‹จ์ผ ํ‰๊ท  ๋ชฉํ‘œ์ฃผ๊ฐ€๋ฟ). -- DCF ๋ฌด์œ„ํ—˜๊ธˆ๋ฆฌยทWACC๋Š” ๋ฐด๋“œ/๊ทผ์‚ฌ๊ฐ’ ๊ณ ์ •์ด๋ผ ๊ฐ€์ •์ด ๋‚ก์„ ์ˆ˜ ์žˆ์Œ. -- KRX ๊ณต๋งค๋„๋Š” ๋กœ๊ทธ์ธ ์˜์กด, Naver๋Š” ๋น„๊ณต์‹ ์—”๋“œํฌ์ธํŠธ โ€” ๊ฒฌ๊ณ ์„ฑ ๋ ˆ์ด์–ด๊ฐ€ ์ถฉ๊ฒฉ์„ ์ค„์ด์ง€๋งŒ ์ƒ๋ฅ˜ ๋ณ€๊ฒฝ ์ž์ฒด๋ฅผ ๋ง‰์ง„ ๋ชปํ•จ. -- ์ „์ฒด ํŠน์ˆ˜๊ด€๊ณ„์ž ๊ฑฐ๋ž˜ *์ฃผ์„*์€ ํ‘œ ์ค‘์‹ฌ์ด๋ผ ๋ณธ๋ฌธ narrative(๋Œ€์ฃผ์ฃผ ๊ฑฐ๋ž˜)๊นŒ์ง€๋งŒ ์ปค๋ฒ„. +## ์‹œ์ž‘ํ•˜๊ธฐ -Dexter์˜ ์šฐ์œ„๋Š” "๋” ๋˜‘๋˜‘ํ•จ"์ด ์•„๋‹ˆ๋ผ **1์ฐจ ์ถœ์ฒ˜ยท๊ตฌ์กฐํ™”ยท๊ฐ์‚ฌ๊ฐ€๋Šฅยทํ•œ๊ตญ ํŠœ๋‹**์— ์žˆ์Šต๋‹ˆ๋‹ค. +ํ‚ค๊ฐ€ ํ•˜๋‚˜๋„ ์—†์–ด๋„ **ํ•œ๊ตญ ์ข…๋ชฉ ํ˜„์žฌ๊ฐ€ยท์™ธ๊ตญ์ธ ์ง€๋ถ„์œจ(Naver)** ์€ ๋ฐ”๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ์žฌ๋ฌดยท๊ณต์‹œยท๊ณต๋งค๋„ยท์—ฐ๊ธฐ๊ธˆ ๊ฐ™์€ ๊นŠ์€ ๋ฆฌ์„œ์น˜๋Š” ํ•ด๋‹น ํ‚ค๋ฅผ ๋„ฃ์œผ๋ฉด ์ž๋™์œผ๋กœ ์ผœ์ง‘๋‹ˆ๋‹ค. -## ๊ฐœ๋ฐœ ยท ํ‰๊ฐ€ +- **๋ฐ์Šคํฌํ†ฑ ์•ฑ (Windows/macOS)** โ€” ์„ค์น˜ ํ›„ ์„ค์ • ํ™”๋ฉด์—์„œ ํ‚ค๋งŒ ์ž…๋ ฅํ•˜๋ฉด ๋. [Releases](https://github.com/0xSHKWON/dexter-with-korea/releases)์—์„œ ๋ฐ›์œผ์„ธ์š”. *(๋ฐฐํฌ ์ค€๋น„ ์ค‘)* +- **CLI ยท ๊ฐœ๋ฐœ์ž** โ€” Bun์œผ๋กœ ๋ฐ”๋กœ ์‹คํ–‰. ์„ค์น˜ยทํ‚คยท๋„๊ตฌ ์ƒ์„ธยท๊ฒฌ๊ณ ์„ฑยทํ‰๊ฐ€๊นŒ์ง€ ์ „๋ถ€ โ†’ **[๊ฐœ๋ฐœ ๋ฌธ์„œ](DEVELOPMENT.md)** -**๋Ÿฐํƒ€์ž„์€ Bun.** ์ž์ฃผ ์“ฐ๋Š” ๋ช…๋ น: - -```bash -bun run typecheck # tsc --noEmit (ํ‘ธ์‹œ ์ „ ๊ถŒ์žฅ) -bun test # ์ „์ฒด ํ…Œ์ŠคํŠธ (Bun ๋Ÿฌ๋„ˆ) -bun test path.test.ts -``` - -**ํ‰๊ฐ€(eval)** โ€” LLM-as-judge ์ฑ„์ : +## ์ •์งํ•œ ํ•œ๊ณ„ -```bash -bun run src/evals/run.ts --sample 10 # ๋ฏธ๊ตญ ์ค‘์‹ฌ ํ‰๊ฐ€ -bun run kr-eval # ํ•œ๊ตญ ์งˆ๋ฌธ ๋ฑ…ํฌ, ๋ผ์ด๋ธŒ ์ฑ„์  -bun run kr-eval:record # ์‹ค์ œ DARTยทKRXยทNaver ์‘๋‹ต์„ ํ”ฝ์Šค์ฒ˜๋กœ ๊ธฐ๋ก -bun run kr-eval:replay # ํ”ฝ์Šค์ฒ˜๋กœ ๊ฒฐ์ •์  ์žฌํ˜„ (ํ‚คยท๋„คํŠธ์›Œํฌ ๋ถˆํ•„์š”) -``` +ํ•ญ์ƒ ์ด๊ธฐ๋Š” ๋„๊ตฌ๋Š” ์•„๋‹™๋‹ˆ๋‹ค. -KR ์ฑ„์  ์ฐจ์›: ์‹ค์  YoY ยท ๊ต์ฐจ์‹ ํ˜ธ ยท ์ง€๋ฐฐ๊ตฌ์กฐ ยท grounding(ํ™˜๊ฐ ์—ฌ๋ถ€). "๊ฐ™์€ ์งˆ๋ฌธ โ†’ ๊ฐ™์€ ๋ฐ์ดํ„ฐ ๊ฒฝ๋กœ"๋ฅผ record/replay๋กœ ๊ฒ€์ฆํ•ด ์ฑ—๋ด‡์˜ ๋น„๊ฒฐ์ •์  ๋‹ต๊ณผ ๋Œ€๋น„ํ•ฉ๋‹ˆ๋‹ค. +- ์€ํ–‰ยท์ง€์ฃผ์‚ฌ์ฒ˜๋Ÿผ ํšŒ๊ณ„ ๋ผ๋ฒจ์ด ๋น„ํ‘œ์ค€์ธ ๊ณณ์€ ์ผ๋ถ€ ์ง€ํ‘œ๊ฐ€ ๋น„์–ด raw ๋“œ๋ฆด๋‹ค์šด์œผ๋กœ ๋ณด์™„ โ€” ์ปค๋ฒ„๋ฆฌ์ง€๊ฐ€ ๊ท ์ผํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +- ๋ธŒ๋กœ์ปค๋ณ„ ๋ชฉํ‘œ์ฃผ๊ฐ€ ๋ถ„์‚ฐยท์„ธ๊ทธ๋จผํŠธ ์žฌ๋ฌด ๊ฐ™์€ ์ •๋ฐ€ ๋ฐ์ดํ„ฐ๋Š” ์œ ๋ฃŒ ๋‹จ๋ง(FnGuide ๋“ฑ)์— ๋ชป ๋ฏธ์นฉ๋‹ˆ๋‹ค. +- KRX๋Š” ๋กœ๊ทธ์ธ, Naver๋Š” ๋น„๊ณต์‹ ์—”๋“œํฌ์ธํŠธ์— ์˜์กด โ€” ๊ฒฌ๊ณ ์„ฑ ๋ ˆ์ด์–ด๊ฐ€ ์ถฉ๊ฒฉ์€ ์ค„์ด์ง€๋งŒ ์ƒ๋ฅ˜ ๋ณ€๊ฒฝ ์ž์ฒด๋ฅผ ๋ง‰์ง„ ๋ชปํ•ฉ๋‹ˆ๋‹ค. -**๋””๋ฒ„๊น…** โ€” ๋ชจ๋“  ์ฟผ๋ฆฌ๊ฐ€ `.dexter/scratchpad/`์— JSONL๋กœ ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค(์›๋ณธ ์ฟผ๋ฆฌยท๊ฐ ํˆด ํ˜ธ์ถœ์˜ ์ธ์ž/์›์‹œ๊ฒฐ๊ณผ/LLM ์š”์•ฝยท์ถ”๋ก  ๋‹จ๊ณ„). ์—์ด์ „ํŠธ๊ฐ€ ๋ฌด์—‡์„ ์–ด๋””์„œ ๊ฐ€์ ธ์™€ ์–ด๋–ป๊ฒŒ ํ•ด์„ํ–ˆ๋Š”์ง€ ํ•œ ์ค„์”ฉ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์šฐ์œ„๋Š” "๋” ๋˜‘๋˜‘ํ•จ"์ด ์•„๋‹ˆ๋ผ **1์ฐจ ์ถœ์ฒ˜ ยท ๊ตฌ์กฐํ™” ยท ๊ฐ์‚ฌ ๊ฐ€๋Šฅ ยท ํ•œ๊ตญ ํŠœ๋‹**์— ์žˆ์Šต๋‹ˆ๋‹ค. ## ๋ผ์ด์„ ์Šค @@ -203,4 +93,4 @@ MIT. ์—…์ŠคํŠธ๋ฆผ: [virattt/dexter](https://github.com/virattt/dexter). ## โš ๏ธ ๋ฉด์ฑ… ์กฐํ•ญ -**๊ต์œกยท์ •๋ณด ์ œ๊ณต ๋ชฉ์  ์ „์šฉ.** ์‹ค๊ฑฐ๋ž˜/ํˆฌ์ž๋ฅผ ์œ„ํ•œ ๊ฒƒ์ด ์•„๋‹ˆ๋ฉฐ, ๊ธˆ์œตยทํˆฌ์žยท์„ธ๋ฌดยท๋ฒ•๋ฅ  ์ž๋ฌธ์ด ์•„๋‹™๋‹ˆ๋‹ค. ์ •ํ™•์„ฑยท์™„์ „์„ฑ์„ ๋ณด์žฅํ•˜์ง€ ์•Š๊ณ , ์ถœ๋ ฅ์ด ๋ถ€์ •ํ™•ํ•˜๊ฑฐ๋‚˜ ์‹œ์ ์ด ์ง€๋‚œ ๊ฒƒ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ œ์ž‘์žยท๊ธฐ์—ฌ์ž๋Š” ์–ด๋– ํ•œ ์†์‹ค์—๋„ ์ฑ…์ž„์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํˆฌ์ž ๊ฒฐ์ • ์ „ ๋ฉดํ—ˆ ๋ณด์œ  ์ „๋ฌธ๊ฐ€์™€ ์ƒ๋‹ดํ•˜์„ธ์š”. ์ด ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ๋ชจ๋“  ์œ„ํ—˜์„ ๋ณธ์ธ์ด ๊ฐ์ˆ˜ํ•˜๋Š” ๋ฐ ๋™์˜ํ•ฉ๋‹ˆ๋‹ค. +**๊ต์œกยท์ •๋ณด ์ œ๊ณต ๋ชฉ์  ์ „์šฉ.** ์‹ค๊ฑฐ๋ž˜ยทํˆฌ์ž๋ฅผ ์œ„ํ•œ ๊ฒƒ์ด ์•„๋‹ˆ๋ฉฐ ๊ธˆ์œตยทํˆฌ์žยท์„ธ๋ฌดยท๋ฒ•๋ฅ  ์ž๋ฌธ์ด ์•„๋‹™๋‹ˆ๋‹ค. ์ •ํ™•์„ฑยท์™„์ „์„ฑ์„ ๋ณด์žฅํ•˜์ง€ ์•Š๊ณ , ์ถœ๋ ฅ์ด ๋ถ€์ •ํ™•ํ•˜๊ฑฐ๋‚˜ ์‹œ์ ์ด ์ง€๋‚œ ๊ฒƒ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํˆฌ์ž ๊ฒฐ์ • ์ „ ๋ฉดํ—ˆ ๋ณด์œ  ์ „๋ฌธ๊ฐ€์™€ ์ƒ๋‹ดํ•˜์„ธ์š”. ์‚ฌ์šฉ์— ๋”ฐ๋ฅธ ๋ชจ๋“  ์œ„ํ—˜์€ ๋ณธ์ธ์ด ๊ฐ์ˆ˜ํ•ฉ๋‹ˆ๋‹ค. diff --git a/desktop/.gitignore b/desktop/.gitignore new file mode 100644 index 000000000..54920dc6a --- /dev/null +++ b/desktop/.gitignore @@ -0,0 +1,17 @@ +node_modules/ +out/ +dist/ +*.local +*.log + +# native rebuild artifacts +.cache/ + +# packaged apps +release/ + +# staged bun + core for packaging (generated by scripts/prepare-core.mjs) +resources/ + +# TS incremental build cache +*.tsbuildinfo diff --git a/desktop/.python-version b/desktop/.python-version new file mode 100644 index 000000000..2419ad5b0 --- /dev/null +++ b/desktop/.python-version @@ -0,0 +1 @@ +3.11.9 diff --git a/desktop/electron.vite.config.ts b/desktop/electron.vite.config.ts new file mode 100644 index 000000000..028fc1bb5 --- /dev/null +++ b/desktop/electron.vite.config.ts @@ -0,0 +1,22 @@ +import { resolve } from 'node:path'; +import { defineConfig, externalizeDepsPlugin } from 'electron-vite'; +import react from '@vitejs/plugin-react'; + +// main + preload are Node/Electron processes โ€” keep native/node deps external +// (better-sqlite3 is a native addon and must NOT be bundled). +export default defineConfig({ + main: { + plugins: [externalizeDepsPlugin()], + }, + preload: { + plugins: [externalizeDepsPlugin()], + }, + renderer: { + resolve: { + alias: { + '@renderer': resolve('src/renderer/src'), + }, + }, + plugins: [react()], + }, +}); diff --git a/desktop/package-lock.json b/desktop/package-lock.json new file mode 100644 index 000000000..ca453e4c3 --- /dev/null +++ b/desktop/package-lock.json @@ -0,0 +1,8866 @@ +{ + "name": "dexter-desktop", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dexter-desktop", + "version": "0.0.1", + "hasInstallScript": true, + "dependencies": { + "better-sqlite3": "^11.7.0", + "exceljs": "^4.4.0", + "react-markdown": "^9.1.0", + "remark-gfm": "^4.0.1", + "three": "^0.184.0", + "xlsx": "^0.18.5" + }, + "devDependencies": { + "@electron/rebuild": "^3.7.1", + "@types/better-sqlite3": "^7.6.12", + "@types/node": "^22.10.2", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@types/three": "^0.184.1", + "@vitejs/plugin-react": "^4.3.4", + "electron": "^33.2.1", + "electron-builder": "^25.1.8", + "electron-vite": "^2.3.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "typescript": "^5.7.2", + "vite": "^5.4.11" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", + "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@develar/schema-utils": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", + "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.0", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@dimforge/rapier3d-compat": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", + "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/asar/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/get": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@electron/get/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@electron/get/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/get/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@electron/get/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@electron/node-gyp": { + "version": "10.2.0-electron.1", + "resolved": "git+ssh://git@github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2", + "integrity": "sha512-lBSgDMQqt7QWMuIjS8zNAq5FI5o5RVBAcJUGWGI6GgoQITJt3msAkUrHp8YHj3RTVE+h70ndqMGqURjp3IfRyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^8.1.0", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.2.1", + "nopt": "^6.0.0", + "proc-log": "^2.0.1", + "semver": "^7.3.5", + "tar": "^6.2.1", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/@electron/notarize": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", + "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.1.tgz", + "integrity": "sha512-BAfviURMHpmb1Yb50YbCxnOY0wfwaLXH5KJ4+80zS0gUkzDX3ec23naTlEqKsN+PwYn+a1cCzM7BJ4Wcd3sGzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/rebuild": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-3.7.2.tgz", + "integrity": "sha512-19/KbIR/DAxbsCkiaGMXIdPnMCJLkcf8AvGnduJtWBs/CBwiAjY1apCqOLVxrXg+rtXFCngbXhBanWjxLUt1Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/node-gyp": "git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2", + "@malept/cross-spawn-promise": "^2.0.0", + "chalk": "^4.0.0", + "debug": "^4.1.1", + "detect-libc": "^2.0.1", + "fs-extra": "^10.0.0", + "got": "^11.7.0", + "node-abi": "^3.45.0", + "node-api-version": "^0.2.0", + "ora": "^5.1.0", + "read-binary-file-arch": "^1.0.6", + "semver": "^7.3.5", + "tar": "^6.0.5", + "yargs": "^17.0.1" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/@electron/universal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.1.tgz", + "integrity": "sha512-fKpv9kg4SPmt+hY7SVBnIYULE9QJl8L3sCfcBsnqbJwwBwAeTLokJ9TRt9y7bK0JAzIW2y78TVVjvnQEms/yyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.2.7", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" + }, + "engines": { + "node": ">=16.4" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" + } + }, + "node_modules/@fast-csv/format/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, + "node_modules/@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/@fast-csv/parse/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "license": "MIT", + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.1.tgz", + "integrity": "sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.0.tgz", + "integrity": "sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/plist": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", + "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*", + "xmlbuilder": ">=11.0.1" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/stats.js": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", + "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/three": { + "version": "0.184.1", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.184.1.tgz", + "integrity": "sha512-6q4VdiqVsrTRqmk62/BnlcAvIrnDM0zf2ZDVKI5kZiniWrSaOHaQzmbp+BNzoggc/8tgW412pL//wZIxu2PPTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dimforge/rapier3d-compat": "~0.12.0", + "@tweenjs/tween.js": "~23.1.3", + "@types/stats.js": "*", + "@types/webxr": ">=0.5.17", + "fflate": "~0.8.2", + "meshoptimizer": "~1.1.1" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/verror": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", + "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@types/webxr": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", + "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.9.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz", + "integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.6" + } + }, + "node_modules/7zip-bin": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", + "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/app-builder-bin": { + "version": "5.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-5.0.0-alpha.10.tgz", + "integrity": "sha512-Ev4jj3D7Bo+O0GPD2NMvJl+PGiBAfS7pUGawntBNpCbxtpncfUixqFj9z9Jme7V7s3LBGqsWZZP54fxBX3JKJw==", + "dev": true, + "license": "MIT" + }, + "node_modules/app-builder-lib": { + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-25.1.8.tgz", + "integrity": "sha512-pCqe7dfsQFBABC1jeKZXQWhGcCPF3rPCXDdfqVKjIeWBcXzyC1iOWZdfFhGl+S9MyE/k//DFmC6FzuGAUudNDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@develar/schema-utils": "~2.6.5", + "@electron/notarize": "2.5.0", + "@electron/osx-sign": "1.3.1", + "@electron/rebuild": "3.6.1", + "@electron/universal": "2.0.1", + "@malept/flatpak-bundler": "^0.4.0", + "@types/fs-extra": "9.0.13", + "async-exit-hook": "^2.0.1", + "bluebird-lst": "^1.0.9", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "chromium-pickle-js": "^0.2.0", + "config-file-ts": "0.2.8-rc1", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6", + "ejs": "^3.1.8", + "electron-publish": "25.1.7", + "form-data": "^4.0.0", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "is-ci": "^3.0.0", + "isbinaryfile": "^5.0.0", + "js-yaml": "^4.1.0", + "json5": "^2.2.3", + "lazy-val": "^1.0.5", + "minimatch": "^10.0.0", + "resedit": "^1.7.0", + "sanitize-filename": "^1.6.3", + "semver": "^7.3.8", + "tar": "^6.1.12", + "temp-file": "^3.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "25.1.8", + "electron-builder-squirrel-windows": "25.1.8" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/rebuild": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-3.6.1.tgz", + "integrity": "sha512-f6596ZHpEq/YskUd8emYvOUne89ij8mQgjYFA5ru25QwbrRO+t1SImofdDv7kKOuWCmVOuU5tvfkbgGxIl3E/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "chalk": "^4.0.0", + "debug": "^4.1.1", + "detect-libc": "^2.0.1", + "fs-extra": "^10.0.0", + "got": "^11.7.0", + "node-abi": "^3.45.0", + "node-api-version": "^0.2.0", + "node-gyp": "^9.0.0", + "ora": "^5.1.0", + "read-binary-file-arch": "^1.0.6", + "semver": "^7.3.5", + "tar": "^6.0.5", + "yargs": "^17.0.1" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/app-builder-lib/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/app-builder-lib/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/app-builder-lib/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "dev": true, + "license": "ISC" + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "license": "MIT", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.38", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", + "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/better-sqlite3": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz", + "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + } + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "license": "MIT", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/bluebird-lst": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.9.tgz", + "integrity": "sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "^3.5.5" + } + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, + "node_modules/builder-util": { + "version": "25.1.7", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-25.1.7.tgz", + "integrity": "sha512-7jPjzBwEGRbwNcep0gGNpLXG9P94VA3CPAZQCzxkFXiV2GMQKlziMbY//rXPI7WKfhsvGgFXjTcXdBEwgXw9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "7zip-bin": "~5.2.0", + "app-builder-bin": "5.0.0-alpha.10", + "bluebird-lst": "^1.0.9", + "builder-util-runtime": "9.2.10", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "is-ci": "^3.0.0", + "js-yaml": "^4.1.0", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.2.10", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.10.tgz", + "integrity": "sha512-6p/gfG1RJSQeIbz8TK5aPNkoztgY1q5TgmGFMAXcY8itsGW6Y2ld1ALsZ5UJn8rog7hKF3zHx5iQbNQ8uLcRlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/builder-util/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/builder-util/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/builder-util/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "license": "MIT/X11", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/config-file-ts": { + "version": "0.2.8-rc1", + "resolved": "https://registry.npmjs.org/config-file-ts/-/config-file-ts-0.2.8-rc1.tgz", + "integrity": "sha512-GtNECbVI82bT4RiDIzBSVuTKoSHufnU7Ce7/42bkWZJZFLjmDF2WBpVsvRkhKCfKBnTBb3qZrBwPpFBU/Myvhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.3.12", + "typescript": "^5.4.3" + } + }, + "node_modules/config-file-ts/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/config-file-ts/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/config-file-ts/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "license": "MIT" + }, + "node_modules/crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.1.0" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dir-compare": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", + "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5", + "p-limit": "^3.1.0 " + } + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dmg-builder": { + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-25.1.8.tgz", + "integrity": "sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "25.1.8", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "fs-extra": "^10.1.0", + "iconv-lite": "^0.6.2", + "js-yaml": "^4.1.0" + }, + "optionalDependencies": { + "dmg-license": "^1.0.11" + } + }, + "node_modules/dmg-license": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", + "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "@types/plist": "^3.0.1", + "@types/verror": "^1.10.3", + "ajv": "^6.10.0", + "crc": "^3.8.0", + "iconv-corefoundation": "^1.1.7", + "plist": "^3.0.4", + "smart-buffer": "^4.0.2", + "verror": "^1.10.0" + }, + "bin": { + "dmg-license": "bin/dmg-license.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron": { + "version": "33.4.11", + "resolved": "https://registry.npmjs.org/electron/-/electron-33.4.11.tgz", + "integrity": "sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^20.9.0", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron-builder": { + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-25.1.8.tgz", + "integrity": "sha512-poRgAtUHHOnlzZnc9PK4nzG53xh74wj2Jy7jkTrqZ0MWPoHGh1M2+C//hGeYdA+4K8w4yiVCNYoLXF7ySj2Wig==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "25.1.8", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "chalk": "^4.1.2", + "dmg-builder": "25.1.8", + "fs-extra": "^10.1.0", + "is-ci": "^3.0.0", + "lazy-val": "^1.0.5", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-25.1.8.tgz", + "integrity": "sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "25.1.8", + "archiver": "^5.3.1", + "builder-util": "25.1.7", + "fs-extra": "^10.1.0" + } + }, + "node_modules/electron-publish": { + "version": "25.1.7", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-25.1.7.tgz", + "integrity": "sha512-+jbTkR9m39eDBMP4gfbqglDd6UvBC7RLh5Y0MhFSsc6UkGHj9Vj9TWobxevHYMMqmoujL11ZLjfPpMX+Pt6YEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "chalk": "^4.1.2", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.376", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.376.tgz", + "integrity": "sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==", + "dev": true, + "license": "ISC" + }, + "node_modules/electron-vite": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/electron-vite/-/electron-vite-2.3.0.tgz", + "integrity": "sha512-lsN2FymgJlp4k6MrcsphGqZQ9fKRdJKasoaiwIrAewN1tapYI/KINLdfEL7n10LuF0pPSNf/IqjzZbB5VINctg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.7", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "cac": "^6.7.14", + "esbuild": "^0.21.5", + "magic-string": "^0.30.10", + "picocolors": "^1.0.1" + }, + "bin": { + "electron-vite": "bin/electron-vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@swc/core": "^1.0.0", + "vite": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + } + } + }, + "node_modules/electron/node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/exceljs": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz", + "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==", + "license": "MIT", + "dependencies": { + "archiver": "^5.0.0", + "dayjs": "^1.8.34", + "fast-csv": "^4.3.1", + "jszip": "^3.10.1", + "readable-stream": "^3.6.0", + "saxes": "^5.0.1", + "tmp": "^0.2.0", + "unzipper": "^0.10.11", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extsprintf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true + }, + "node_modules/fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "license": "MIT", + "dependencies": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/fstream/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/fstream/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fstream/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/fstream/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/fstream/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-corefoundation": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", + "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "cli-truncate": "^2.1.0", + "node-addon-api": "^1.6.3" + }, + "engines": { + "node": "^8.11.2 || >=10" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true, + "license": "ISC" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", + "license": "ISC" + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "license": "MIT" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "license": "MIT" + }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "license": "MIT" + }, + "node_modules/lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==", + "license": "MIT" + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "license": "ISC", + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/meshoptimizer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.1.1.tgz", + "integrity": "sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.14", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.14.tgz", + "integrity": "sha512-U9kYi5bpVMEI31yC8iw4bJJp0avcHXA0W8/wNfLfnvJYzihQo2ZRPYPvpAAd570HAcCBjCTN7vnr+v4StKl1IQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.92.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", + "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/node-gyp": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", + "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.13 || ^14.13 || >=16" + } + }, + "node_modules/node-gyp/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/node-releases": { + "version": "2.0.48", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz", + "integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/pe-library": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", + "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/plist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.1.tgz", + "integrity": "sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.9.10", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proc-log": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz", + "integrity": "sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-markdown": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.1.0.tgz", + "integrity": "sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resedit": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", + "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pe-library": "^0.4.1" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true, + "license": "ISC" + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/three": { + "version": "0.184.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.184.0.tgz", + "integrity": "sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg==", + "license": "MIT" + }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "license": "MIT/X11", + "engines": { + "node": "*" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "license": "MIT", + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/unzipper/node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "license": "MIT" + }, + "node_modules/unzipper/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/unzipper/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/unzipper/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "license": "MIT", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/zip-stream/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/zip-stream/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/desktop/package.json b/desktop/package.json new file mode 100644 index 000000000..7f64b6997 --- /dev/null +++ b/desktop/package.json @@ -0,0 +1,92 @@ +{ + "name": "dexter-desktop", + "version": "0.0.1", + "description": "Dexter desktop app (Windows/macOS) โ€” GUI shell for the Dexter financial research agent.", + "main": "./out/main/index.js", + "author": "Dexter", + "scripts": { + "dev": "electron-vite dev", + "build": "electron-vite build", + "preview": "electron-vite preview", + "start": "electron-vite preview", + "typecheck:node": "tsc --noEmit -p tsconfig.node.json", + "typecheck:web": "tsc --noEmit -p tsconfig.web.json", + "typecheck": "npm run typecheck:node && npm run typecheck:web", + "rebuild": "electron-rebuild -f -w better-sqlite3", + "postinstall": "electron-rebuild -f -w better-sqlite3", + "stage": "node scripts/prepare-core.mjs", + "dist": "npm run stage && electron-vite build && electron-builder", + "dist:dir": "npm run stage && electron-vite build && electron-builder --dir" + }, + "dependencies": { + "better-sqlite3": "^11.7.0", + "exceljs": "^4.4.0", + "react-markdown": "^9.1.0", + "remark-gfm": "^4.0.1", + "three": "^0.184.0", + "xlsx": "^0.18.5" + }, + "devDependencies": { + "@electron/rebuild": "^3.7.1", + "@types/better-sqlite3": "^7.6.12", + "@types/node": "^22.10.2", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@types/three": "^0.184.1", + "@vitejs/plugin-react": "^4.3.4", + "electron": "^33.2.1", + "electron-builder": "^25.1.8", + "electron-vite": "^2.3.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "typescript": "^5.7.2", + "vite": "^5.4.11" + }, + "build": { + "appId": "com.dexter.desktop", + "productName": "Dexter", + "directories": { + "output": "release" + }, + "files": [ + "out/**/*", + "package.json" + ], + "extraResources": [ + { + "from": "resources/bin", + "to": "bin" + }, + { + "from": "resources/core", + "to": "core" + } + ], + "mac": { + "target": [ + { + "target": "dmg", + "arch": [ + "arm64" + ] + } + ], + "category": "public.app-category.finance" + }, + "win": { + "target": [ + "nsis" + ] + }, + "linux": { + "target": [ + "AppImage" + ] + }, + "publish": { + "provider": "github", + "owner": "0xSHKWON", + "repo": "dexter-with-korea" + } + } +} diff --git a/desktop/scripts/prepare-core.mjs b/desktop/scripts/prepare-core.mjs new file mode 100644 index 000000000..1e0c83565 --- /dev/null +++ b/desktop/scripts/prepare-core.mjs @@ -0,0 +1,48 @@ +/** + * Stages the bun runtime + core sources into desktop/resources/ so electron-builder + * can bundle them (extraResources). Run before `electron-builder` in the `dist` script. + * + * At runtime the packaged app spawns `resources/bin/bun run resources/core/src/sidecar/index.ts` + * (see src/main/sidecar.ts โ†’ sidecarTarget()). + * + * Note: Playwright's Chromium binary lives in a separate cache (~/.cache/ms-playwright), + * NOT in node_modules, so it is NOT bundled here โ€” browser/short-balance(KRX) tools + * won't work in the packaged build until Chromium is bundled too. All other tools + * (DART, Naver, data.go.kr, web_search, LLMs) work. + */ +import { execSync } from 'node:child_process'; +import { cpSync, mkdirSync, rmSync, existsSync } from 'node:fs'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const here = dirname(fileURLToPath(import.meta.url)); +const desktopRoot = join(here, '..'); +const coreRoot = join(desktopRoot, '..'); +const resBin = join(desktopRoot, 'resources', 'bin'); +const resCore = join(desktopRoot, 'resources', 'core'); + +console.log('Staging bun + core into resources/ โ€ฆ'); + +// 1) bun runtime (current build platform) +const whichCmd = process.platform === 'win32' ? 'where bun' : 'which bun'; +const bunSrc = execSync(whichCmd).toString().trim().split('\n')[0].trim(); +if (!bunSrc || !existsSync(bunSrc)) { + throw new Error('bun not found on PATH โ€” install bun before building'); +} +mkdirSync(resBin, { recursive: true }); +const bunDest = join(resBin, process.platform === 'win32' ? 'bun.exe' : 'bun'); +cpSync(bunSrc, bunDest); +console.log(` bun: ${bunSrc} -> ${bunDest}`); + +// 2) core sources + deps +rmSync(resCore, { recursive: true, force: true }); +mkdirSync(resCore, { recursive: true }); +const ITEMS = ['src', 'node_modules', 'package.json', 'tsconfig.json', 'SOUL.md', 'AGENTS.md']; +for (const item of ITEMS) { + const from = join(coreRoot, item); + if (!existsSync(from)) continue; + cpSync(from, join(resCore, item), { recursive: true }); + console.log(` core: ${item}`); +} + +console.log('Done staging resources/.'); diff --git a/desktop/src/main/data-sources.ts b/desktop/src/main/data-sources.ts new file mode 100644 index 000000000..681312553 --- /dev/null +++ b/desktop/src/main/data-sources.ts @@ -0,0 +1,22 @@ +/** + * Non-LLM API keys the agent core reads from process.env. + * Env var names match the core exactly (src/utils/env.ts, CLAUDE.md activation rules). + * The sidecar will decrypt these and inject them into the child process env. + */ +import type { DataSource } from '../shared/types'; + +export const DATA_SOURCES: DataSource[] = [ + // ํ•œ๊ตญ ์ฃผ์‹ + { envVar: 'DART_API_KEY', label: 'DART ์ „์ž๊ณต์‹œ', group: 'kr', note: '์žฌ๋ฌดยท๊ณต์‹œยท์ง€๋ฐฐ๊ตฌ์กฐ ๋“ฑ 5๊ฐœ ๋„๊ตฌ๋ฅผ ํ™œ์„ฑํ™” (opendart.fss.or.kr)' }, + { envVar: 'KRX_ID', label: 'KRX ์•„์ด๋””', group: 'kr', note: '๊ณต๋งค๋„ ์ˆœ๋ณด์œ ์ž”๊ณ  (data.krx.co.kr ๋กœ๊ทธ์ธ ๊ณ„์ •)' }, + { envVar: 'KRX_PW', label: 'KRX ๋น„๋ฐ€๋ฒˆํ˜ธ', group: 'kr' }, + { envVar: 'DATA_GO_KR_SERVICE_KEY', label: '๊ตญ๋ฏผ์—ฐ๊ธˆ (data.go.kr)', group: 'kr', note: 'Decoded(๋””์ฝ”๋”ฉ) ์„œ๋น„์Šคํ‚ค' }, + // ์›น ๊ฒ€์ƒ‰ + { envVar: 'EXASEARCH_API_KEY', label: 'Exa', group: 'search' }, + { envVar: 'PERPLEXITY_API_KEY', label: 'Perplexity', group: 'search' }, + { envVar: 'TAVILY_API_KEY', label: 'Tavily', group: 'search' }, + { envVar: 'LANGSEARCH_API_KEY', label: 'LangSearch', group: 'search' }, + // ๊ธฐํƒ€ + { envVar: 'X_BEARER_TOKEN', label: 'X (ํŠธ์œ„ํ„ฐ)', group: 'other', note: '๊ณต๊ฐœ ํŠธ์œ— ๊ฐ์„ฑ ๋ฆฌ์„œ์น˜' }, + { envVar: 'FINANCIAL_DATASETS_API_KEY', label: '๋ฏธ๊ตญ ์ฃผ์‹ ๋ฐ์ดํ„ฐ', group: 'other' }, +]; diff --git a/desktop/src/main/db.ts b/desktop/src/main/db.ts new file mode 100644 index 000000000..3d232a27e --- /dev/null +++ b/desktop/src/main/db.ts @@ -0,0 +1,172 @@ +/** + * Local SQLite store for the desktop app. + * + * - `settings`: plain key/value app config (provider, modelId, UI prefs โ€ฆ), + * value JSON-encoded. Mirrors the core's `.dexter/settings.json` shape. + * - `secrets`: API keys, stored as safeStorage ciphertext (NEVER plaintext). + * See `secrets.ts` for encrypt/decrypt. + * + * DB lives in Electron's per-user `userData` dir, so it is naturally isolated + * from the CLI's CWD-relative `.dexter/` dir until the sidecar wires them up. + */ +import { join } from 'node:path'; +import { app } from 'electron'; +import Database from 'better-sqlite3'; + +let db: Database.Database | null = null; + +export function initDb(): Database.Database { + if (db) return db; + const dbPath = join(app.getPath('userData'), 'dexter-desktop.db'); + db = new Database(dbPath); + db.pragma('journal_mode = WAL'); + db.exec(` + CREATE TABLE IF NOT EXISTS settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + ); + CREATE TABLE IF NOT EXISTS secrets ( + env_var TEXT PRIMARY KEY, + ciphertext BLOB NOT NULL, + updated_at INTEGER NOT NULL + ); + CREATE TABLE IF NOT EXISTS work_conversions ( + id TEXT PRIMARY KEY, + created_at INTEGER NOT NULL, + title TEXT NOT NULL, + raw TEXT NOT NULL, + result TEXT NOT NULL + ); + CREATE TABLE IF NOT EXISTS chat_conversations ( + id TEXT PRIMARY KEY, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + title TEXT NOT NULL, + messages TEXT NOT NULL + ); + `); + return db; +} + +function getDb(): Database.Database { + if (!db) throw new Error('DB not initialized โ€” call initDb() first'); + return db; +} + +// โ”€โ”€ settings โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +export function getSetting(key: string, defaultValue: T): T { + const row = getDb().prepare('SELECT value FROM settings WHERE key = ?').get(key) as + | { value: string } + | undefined; + if (!row) return defaultValue; + try { + return JSON.parse(row.value) as T; + } catch { + return defaultValue; + } +} + +export function setSetting(key: string, value: unknown): void { + getDb() + .prepare('INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)') + .run(key, JSON.stringify(value)); +} + +export function getAllSettings(): Record { + const rows = getDb().prepare('SELECT key, value FROM settings').all() as { + key: string; + value: string; + }[]; + const out: Record = {}; + for (const r of rows) { + try { + out[r.key] = JSON.parse(r.value); + } catch { + out[r.key] = r.value; + } + } + return out; +} + +// โ”€โ”€ secrets (ciphertext only) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +export function setSecret(envVar: string, ciphertext: Buffer): void { + getDb() + .prepare('INSERT OR REPLACE INTO secrets (env_var, ciphertext, updated_at) VALUES (?, ?, ?)') + .run(envVar, ciphertext, Date.now()); +} + +export function getSecret(envVar: string): Buffer | null { + const row = getDb().prepare('SELECT ciphertext FROM secrets WHERE env_var = ?').get(envVar) as + | { ciphertext: Buffer } + | undefined; + return row ? row.ciphertext : null; +} + +export function getSecretUpdatedAt(envVar: string): number | null { + const row = getDb().prepare('SELECT updated_at FROM secrets WHERE env_var = ?').get(envVar) as + | { updated_at: number } + | undefined; + return row ? row.updated_at : null; +} + +export function deleteSecret(envVar: string): void { + getDb().prepare('DELETE FROM secrets WHERE env_var = ?').run(envVar); +} + +// โ”€โ”€ work conversions (archive) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +export interface ConversionRow { + id: string; + created_at: number; + title: string; + raw: string; + result: string; +} + +export function insertConversion(r: ConversionRow): void { + getDb() + .prepare('INSERT INTO work_conversions (id, created_at, title, raw, result) VALUES (?, ?, ?, ?, ?)') + .run(r.id, r.created_at, r.title, r.raw, r.result); +} + +export function listConversionRows(): ConversionRow[] { + return getDb() + .prepare('SELECT id, created_at, title, raw, result FROM work_conversions ORDER BY created_at DESC') + .all() as ConversionRow[]; +} + +export function deleteConversion(id: string): void { + getDb().prepare('DELETE FROM work_conversions WHERE id = ?').run(id); +} + +// โ”€โ”€ chat conversations (archive) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +export interface ChatRow { + id: string; + created_at: number; + updated_at: number; + title: string; + messages: string; +} + +export function upsertChat(r: ChatRow): void { + getDb() + .prepare( + `INSERT INTO chat_conversations (id, created_at, updated_at, title, messages) + VALUES (?, ?, ?, ?, ?) + ON CONFLICT(id) DO UPDATE SET updated_at = excluded.updated_at, title = excluded.title, messages = excluded.messages`, + ) + .run(r.id, r.created_at, r.updated_at, r.title, r.messages); +} + +export function listChatRows(): ChatRow[] { + return getDb() + .prepare('SELECT id, created_at, updated_at, title, messages FROM chat_conversations ORDER BY updated_at DESC') + .all() as ChatRow[]; +} + +export function deleteChat(id: string): void { + getDb().prepare('DELETE FROM chat_conversations WHERE id = ?').run(id); +} diff --git a/desktop/src/main/index.ts b/desktop/src/main/index.ts new file mode 100644 index 000000000..2674eec3e --- /dev/null +++ b/desktop/src/main/index.ts @@ -0,0 +1,65 @@ +import { join } from 'node:path'; +import { app, BrowserWindow, shell } from 'electron'; +import { initDb } from './db'; +import { registerIpc } from './ipc'; +import { sidecar } from './sidecar'; + +function createWindow(): BrowserWindow { + const win = new BrowserWindow({ + width: 1100, + height: 760, + minWidth: 720, + minHeight: 520, + show: false, + title: 'Dexter', + backgroundColor: '#0d1117', + titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default', + webPreferences: { + preload: join(__dirname, '../preload/index.js'), + contextIsolation: true, + nodeIntegration: false, + sandbox: false, + }, + }); + + win.on('ready-to-show', () => win.show()); + + // Open external links in the OS browser, never in-app. + win.webContents.setWindowOpenHandler(({ url }) => { + shell.openExternal(url); + return { action: 'deny' }; + }); + + // electron-vite injects ELECTRON_RENDERER_URL in dev (HMR server). + if (process.env.ELECTRON_RENDERER_URL) { + win.loadURL(process.env.ELECTRON_RENDERER_URL); + } else { + win.loadFile(join(__dirname, '../renderer/index.html')); + } + + return win; +} + +app.whenReady().then(() => { + initDb(); + registerIpc(); + createWindow(); + + // Stream sidecar messages to all open renderer windows. + sidecar.start((msg) => { + for (const w of BrowserWindow.getAllWindows()) { + if (!w.isDestroyed()) w.webContents.send('chat:event', msg); + } + }); + + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) createWindow(); + }); +}); + +app.on('window-all-closed', () => { + sidecar.stop(); + if (process.platform !== 'darwin') app.quit(); +}); + +app.on('before-quit', () => sidecar.stop()); diff --git a/desktop/src/main/ipc.ts b/desktop/src/main/ipc.ts new file mode 100644 index 000000000..bf27ff328 --- /dev/null +++ b/desktop/src/main/ipc.ts @@ -0,0 +1,219 @@ +import { randomUUID } from 'node:crypto'; +import { ipcMain, dialog, BrowserWindow } from 'electron'; +import ExcelJS from 'exceljs'; +import { PROVIDERS } from './providers'; +import { DATA_SOURCES } from './data-sources'; +import type { + ConvertResult, + AccountMapping, + ConversionRecord, + ChatConversation, + StoredMessage, +} from '../shared/sidecar'; +import { + getAllSettings, + getSetting, + setSetting, + getSecret, + setSecret, + deleteSecret, + getSecretUpdatedAt, + insertConversion, + listConversionRows, + deleteConversion, + upsertChat, + listChatRows, + deleteChat, +} from './db'; +import { encryptSecret, previewLast4, isEncryptionAvailable } from './secrets'; +import { sidecar } from './sidecar'; +import type { SecretStatus } from '../shared/types'; + +function statusFor(envVar: string): SecretStatus { + const buf = getSecret(envVar); + if (!buf) return { envVar, exists: false, last4: null, updatedAt: null }; + return { + envVar, + exists: true, + last4: previewLast4(buf), + updatedAt: getSecretUpdatedAt(envVar), + }; +} + +export function registerIpc(): void { + ipcMain.handle('providers:list', () => PROVIDERS); + ipcMain.handle('datasources:list', () => DATA_SOURCES); + + ipcMain.handle('settings:getAll', () => getAllSettings()); + ipcMain.handle('settings:set', (_e, key: string, value: unknown) => { + setSetting(key, value); + }); + + ipcMain.handle('secrets:statusAll', () => { + const envVars = [ + ...PROVIDERS.filter((p) => p.apiKeyEnvVar).map((p) => p.apiKeyEnvVar as string), + ...DATA_SOURCES.map((d) => d.envVar), + ]; + return envVars.map(statusFor); + }); + ipcMain.handle('secrets:set', (_e, envVar: string, value: string): SecretStatus => { + const trimmed = (value ?? '').trim(); + if (!trimmed) throw new Error('API key is empty'); + setSecret(envVar, encryptSecret(trimmed)); + sidecar.stop(); // respawn with fresh keys on next request + return statusFor(envVar); + }); + ipcMain.handle('secrets:remove', (_e, envVar: string) => { + deleteSecret(envVar); + sidecar.stop(); + }); + ipcMain.handle('secrets:encryptionAvailable', () => isEncryptionAvailable()); + + // โ”€โ”€ chat (sidecar) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + ipcMain.handle('chat:send', (_e, query: string) => { + const provider = getSetting('provider', 'openai'); + const model = getSetting('modelId', 'gpt-5.5'); + const runId = randomUUID(); + sidecar.send({ type: 'run', id: runId, query, model, modelProvider: provider }); + return { runId }; + }); + ipcMain.handle('chat:cancel', (_e, runId: string) => { + sidecar.send({ type: 'cancel', id: runId }); + }); + ipcMain.handle('chat:reset', () => { + sidecar.send({ type: 'reset' }); + }); + ipcMain.handle('chat:listConv', (): ChatConversation[] => + listChatRows().map((r) => ({ + id: r.id, + createdAt: r.created_at, + updatedAt: r.updated_at, + title: r.title, + messages: JSON.parse(r.messages) as StoredMessage[], + })), + ); + ipcMain.handle('chat:saveConv', (_e, conv: ChatConversation) => { + upsertChat({ + id: conv.id, + created_at: conv.createdAt, + updated_at: Date.now(), + title: conv.title, + messages: JSON.stringify(conv.messages), + }); + }); + ipcMain.handle('chat:deleteConv', (_e, id: string) => { + deleteChat(id); + }); + + // โ”€โ”€ work (ledger โ†’ DART accounts) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + ipcMain.handle('work:convert', (_e, rawData: string) => { + const provider = getSetting('provider', 'openai'); + const model = getSetting('modelId', 'gpt-5.5'); + const runId = randomUUID(); + sidecar.send({ type: 'convert', id: runId, rawData, model, modelProvider: provider }); + return { runId }; + }); + + ipcMain.handle('work:save', (_e, raw: string, result: ConvertResult): ConversionRecord => { + const id = randomUUID(); + const createdAt = Date.now(); + const first = result.mappings[0]?.standard; + const title = `${result.mappings.length}๊ฐœ ๊ณ„์ •${first ? ` ยท ${first}` : ''}`; + insertConversion({ id, created_at: createdAt, title, raw, result: JSON.stringify(result) }); + return { id, createdAt, title, raw, result }; + }); + ipcMain.handle('work:list', (): ConversionRecord[] => + listConversionRows().map((r) => ({ + id: r.id, + createdAt: r.created_at, + title: r.title, + raw: r.raw, + result: JSON.parse(r.result) as ConvertResult, + })), + ); + ipcMain.handle('work:delete', (_e, id: string) => { + deleteConversion(id); + }); + + ipcMain.handle('work:export', async (_e, result: ConvertResult) => { + const win = BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0]; + const opts = { + defaultPath: 'dart-์žฌ๋ฌด์ œํ‘œ.xlsx', + filters: [{ name: 'Excel', extensions: ['xlsx'] }], + }; + const { canceled, filePath } = win + ? await dialog.showSaveDialog(win, opts) + : await dialog.showSaveDialog(opts); + if (canceled || !filePath) return { saved: false }; + await buildWorkbook(result, filePath); + return { saved: true, path: filePath }; + }); +} + +const STMT_NAMES: Record = { + BS: '์žฌ๋ฌด์ƒํƒœํ‘œ', + IS: '์†์ต๊ณ„์‚ฐ์„œ', + CF: 'ํ˜„๊ธˆํ๋ฆ„ํ‘œ', + ๊ธฐํƒ€: '๊ธฐํƒ€', +}; +const STMT_ORDER = ['BS', 'IS', 'CF', '๊ธฐํƒ€']; + +/** Build a styled .xlsx (one sheet per statement) from the converted mappings. */ +async function buildWorkbook(result: ConvertResult, filePath: string): Promise { + const wb = new ExcelJS.Workbook(); + const groups: Record = {}; + for (const m of result.mappings) { + (groups[m.statement || '๊ธฐํƒ€'] ??= []).push(m); + } + const keys = [ + ...STMT_ORDER.filter((k) => groups[k]), + ...Object.keys(groups).filter((k) => !STMT_ORDER.includes(k)), + ]; + + for (const stmt of keys) { + const rows = groups[stmt]; + const ws = wb.addWorksheet(STMT_NAMES[stmt] ?? stmt); + ws.columns = [ + { header: 'ํ‘œ์ค€๊ณ„์ •๊ณผ๋ชฉ', key: 'standard', width: 24 }, + { header: '๊ธˆ์•ก', key: 'amount', width: 18 }, + { header: '์›๋ณธ๊ณ„์ •', key: 'original', width: 18 }, + { header: '๋น„๊ณ ', key: 'note', width: 50 }, + ]; + + const head = ws.getRow(1); + head.font = { bold: true }; + head.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFEDEFF2' } }; + + for (const r of rows) { + ws.addRow({ standard: r.standard, amount: r.amount, original: r.original, note: r.note }); + } + + const amountCol = ws.getColumn('amount'); + amountCol.numFmt = '#,##0'; + amountCol.alignment = { horizontal: 'right' }; + ws.getColumn('note').alignment = { wrapText: true, vertical: 'top' }; + + ws.eachRow((row) => { + row.eachCell((cell) => { + cell.border = { + top: { style: 'thin', color: { argb: 'FFE0E0E0' } }, + bottom: { style: 'thin', color: { argb: 'FFE0E0E0' } }, + left: { style: 'thin', color: { argb: 'FFE0E0E0' } }, + right: { style: 'thin', color: { argb: 'FFE0E0E0' } }, + }; + }); + }); + } + + if (result.warnings.length > 0) { + const ws = wb.addWorksheet('๊ฒ€ํ† ์‚ฌํ•ญ'); + ws.columns = [{ header: '๊ฒ€ํ†  ํ•„์š” ์‚ฌํ•ญ', key: 'w', width: 90 }]; + ws.getRow(1).font = { bold: true }; + for (const w of result.warnings) { + const row = ws.addRow({ w }); + row.getCell('w').alignment = { wrapText: true }; + } + } + + await wb.xlsx.writeFile(filePath); +} diff --git a/desktop/src/main/providers.ts b/desktop/src/main/providers.ts new file mode 100644 index 000000000..b7459887c --- /dev/null +++ b/desktop/src/main/providers.ts @@ -0,0 +1,87 @@ +/** + * LLM provider metadata for the desktop settings UI. + * + * โš ๏ธ Kept in sync (by hand for now) with the core source of truth at + * `../../../src/providers.ts`. Once the Bun core sidecar lands, the desktop app + * should fetch this list over the sidecar protocol instead of duplicating it. + * + * `apiKeyEnvVar` is the env var name the core (`src/model/llm.ts:getApiKey`) + * reads at runtime โ€” so storing a key here under that name is exactly what the + * sidecar will inject into `process.env`. + */ + +import type { ProviderMeta } from '../shared/types'; + +export type { ProviderMeta }; + +export const PROVIDERS: ProviderMeta[] = [ + { + id: 'openai', + displayName: 'OpenAI', + apiKeyEnvVar: 'OPENAI_API_KEY', + requiresKey: true, + defaultModel: 'gpt-5.5', + suggestedModels: ['gpt-5.5', 'gpt-5.4', 'gpt-5.4-mini'], + }, + { + id: 'anthropic', + displayName: 'Anthropic', + apiKeyEnvVar: 'ANTHROPIC_API_KEY', + requiresKey: true, + defaultModel: 'claude-sonnet-4-6', + suggestedModels: ['claude-opus-4-8', 'claude-sonnet-4-6', 'claude-haiku-4-5'], + }, + { + id: 'google', + displayName: 'Google', + apiKeyEnvVar: 'GOOGLE_API_KEY', + requiresKey: true, + defaultModel: 'gemini-3', + suggestedModels: ['gemini-3', 'gemini-3-flash-preview'], + }, + { + id: 'xai', + displayName: 'xAI', + apiKeyEnvVar: 'XAI_API_KEY', + requiresKey: true, + defaultModel: 'grok-4-1', + suggestedModels: ['grok-4-1', 'grok-4-1-fast-reasoning'], + }, + { + id: 'moonshot', + displayName: 'Moonshot', + apiKeyEnvVar: 'MOONSHOT_API_KEY', + requiresKey: true, + defaultModel: 'kimi-k2-5', + suggestedModels: ['kimi-k2-5'], + }, + { + id: 'deepseek', + displayName: 'DeepSeek', + apiKeyEnvVar: 'DEEPSEEK_API_KEY', + requiresKey: true, + defaultModel: 'deepseek-v4-pro', + suggestedModels: ['deepseek-v4-pro', 'deepseek-v4-flash'], + }, + { + id: 'openrouter', + displayName: 'OpenRouter', + apiKeyEnvVar: 'OPENROUTER_API_KEY', + requiresKey: true, + defaultModel: 'openrouter:openai/gpt-4o-mini', + suggestedModels: ['openrouter:openai/gpt-4o-mini', 'openrouter:anthropic/claude-3.5-sonnet'], + note: 'Prefix model ids with "openrouter:"', + }, + { + id: 'ollama', + displayName: 'Ollama', + requiresKey: false, + defaultModel: 'ollama:llama3.1', + suggestedModels: ['ollama:llama3.1', 'ollama:qwen2.5'], + note: 'Local โ€” no API key required. Prefix model ids with "ollama:"', + }, +]; + +export function getProviderById(id: string): ProviderMeta | undefined { + return PROVIDERS.find((p) => p.id === id); +} diff --git a/desktop/src/main/secrets.ts b/desktop/src/main/secrets.ts new file mode 100644 index 000000000..ba417755e --- /dev/null +++ b/desktop/src/main/secrets.ts @@ -0,0 +1,34 @@ +/** + * API-key encryption via Electron safeStorage. + * + * safeStorage binds the encryption key to the OS keychain (macOS Keychain, + * Windows DPAPI), so ciphertext at rest in the SQLite DB is useless without the + * logged-in user's session. We never persist or hand plaintext keys to the + * renderer โ€” only an "exists" flag plus the last 4 chars for visual confirmation. + */ +import { safeStorage } from 'electron'; + +export function isEncryptionAvailable(): boolean { + return safeStorage.isEncryptionAvailable(); +} + +export function encryptSecret(plaintext: string): Buffer { + if (!safeStorage.isEncryptionAvailable()) { + throw new Error('OS secure storage is unavailable on this system'); + } + return safeStorage.encryptString(plaintext); +} + +export function decryptSecret(ciphertext: Buffer): string { + return safeStorage.decryptString(ciphertext); +} + +/** Last 4 chars of a stored key for UI confirmation, or null if undecryptable. */ +export function previewLast4(ciphertext: Buffer): string | null { + try { + const plain = decryptSecret(ciphertext); + return plain.length >= 4 ? plain.slice(-4) : plain; + } catch { + return null; + } +} diff --git a/desktop/src/main/sidecar.ts b/desktop/src/main/sidecar.ts new file mode 100644 index 000000000..c301012fd --- /dev/null +++ b/desktop/src/main/sidecar.ts @@ -0,0 +1,115 @@ +/** + * Spawns and talks to the Bun core sidecar (src/sidecar/index.ts). + * + * - Keys: decrypts all stored secrets and injects them as env vars at spawn + * time (the core's getApiKey reads process.env). Changing a key calls stop() + * so the next request respawns with fresh env. + * - Paths: points the core's .dexter dir at the app's userData via DEXTER_DIR. + */ +import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process'; +import { createInterface } from 'node:readline'; +import { join } from 'node:path'; +import { app } from 'electron'; +import { PROVIDERS } from './providers'; +import { DATA_SOURCES } from './data-sources'; +import { getSecret } from './db'; +import { decryptSecret } from './secrets'; +import type { MainToSidecar, SidecarToMain } from '../shared/sidecar'; + +interface SidecarTarget { + bun: string; + entry: string; + cwd: string; +} + +/** + * Where to find the bun runtime + core sources. + * - Packaged: bundled under the app's resources (bin/bun + core/). + * - Dev: system `bun` + repo root (desktop/out/main โ†’ ../../..). + */ +function sidecarTarget(): SidecarTarget { + if (app.isPackaged) { + const res = process.resourcesPath; + return { + bun: join(res, 'bin', process.platform === 'win32' ? 'bun.exe' : 'bun'), + entry: join(res, 'core', 'src', 'sidecar', 'index.ts'), + cwd: join(res, 'core'), + }; + } + const root = process.env.DEXTER_CORE_ROOT || join(__dirname, '../../..'); + return { bun: 'bun', entry: join(root, 'src', 'sidecar', 'index.ts'), cwd: root }; +} + +function collectKeyEnv(): Record { + const env: Record = {}; + const envVars = [ + ...PROVIDERS.filter((p) => p.apiKeyEnvVar).map((p) => p.apiKeyEnvVar as string), + ...DATA_SOURCES.map((d) => d.envVar), + ]; + for (const v of envVars) { + const buf = getSecret(v); + if (!buf) continue; + try { + env[v] = decryptSecret(buf); + } catch { + /* undecryptable โ€” skip */ + } + } + return env; +} + +class SidecarManager { + private proc: ChildProcessWithoutNullStreams | null = null; + private onMessage: ((msg: SidecarToMain) => void) | null = null; + + start(onMessage: (msg: SidecarToMain) => void): void { + this.onMessage = onMessage; + } + + private ensureProc(): ChildProcessWithoutNullStreams { + if (this.proc) return this.proc; + + const target = sidecarTarget(); + const dexterDir = join(app.getPath('userData'), 'core-data'); + + const proc = spawn(target.bun, ['run', target.entry], { + cwd: target.cwd, + env: { ...process.env, DEXTER_DIR: dexterDir, ...collectKeyEnv() }, + }) as ChildProcessWithoutNullStreams; + + const rl = createInterface({ input: proc.stdout }); + rl.on('line', (line) => { + const t = line.trim(); + if (!t) return; + try { + this.onMessage?.(JSON.parse(t) as SidecarToMain); + } catch { + /* ignore non-JSON */ + } + }); + proc.stderr.on('data', (d: Buffer) => process.stderr.write(`[sidecar] ${d.toString()}`)); + proc.on('error', (err) => { + process.stderr.write(`[sidecar] spawn error: ${err.message}\n`); + if (this.proc === proc) this.proc = null; + }); + proc.on('exit', (code) => { + process.stderr.write(`[sidecar] exited (code ${code})\n`); + if (this.proc === proc) this.proc = null; + }); + + this.proc = proc; + return proc; + } + + send(req: MainToSidecar): void { + this.ensureProc().stdin.write(JSON.stringify(req) + '\n'); + } + + /** Kill the process so the next send() respawns with fresh keys/env. */ + stop(): void { + this.proc?.kill(); + this.proc = null; + } +} + +export const sidecar = new SidecarManager(); diff --git a/desktop/src/preload/index.d.ts b/desktop/src/preload/index.d.ts new file mode 100644 index 000000000..7f4f70bc5 --- /dev/null +++ b/desktop/src/preload/index.d.ts @@ -0,0 +1,9 @@ +import type { DexterApi } from '../shared/types'; + +declare global { + interface Window { + dexter: DexterApi; + } +} + +export {}; diff --git a/desktop/src/preload/index.ts b/desktop/src/preload/index.ts new file mode 100644 index 000000000..b175e08a6 --- /dev/null +++ b/desktop/src/preload/index.ts @@ -0,0 +1,44 @@ +import { contextBridge, ipcRenderer, type IpcRendererEvent } from 'electron'; +import type { DexterApi } from '../shared/types'; +import type { SidecarToMain } from '../shared/sidecar'; + +const api: DexterApi = { + providers: { + list: () => ipcRenderer.invoke('providers:list'), + }, + datasources: { + list: () => ipcRenderer.invoke('datasources:list'), + }, + settings: { + getAll: () => ipcRenderer.invoke('settings:getAll'), + set: (key, value) => ipcRenderer.invoke('settings:set', key, value), + }, + secrets: { + statusAll: () => ipcRenderer.invoke('secrets:statusAll'), + set: (envVar, value) => ipcRenderer.invoke('secrets:set', envVar, value), + remove: (envVar) => ipcRenderer.invoke('secrets:remove', envVar), + encryptionAvailable: () => ipcRenderer.invoke('secrets:encryptionAvailable'), + }, + chat: { + send: (query) => ipcRenderer.invoke('chat:send', query), + cancel: (runId) => ipcRenderer.invoke('chat:cancel', runId), + onEvent: (cb) => { + const listener = (_e: IpcRendererEvent, msg: SidecarToMain) => cb(msg); + ipcRenderer.on('chat:event', listener); + return () => ipcRenderer.off('chat:event', listener); + }, + reset: () => ipcRenderer.invoke('chat:reset'), + listConversations: () => ipcRenderer.invoke('chat:listConv'), + saveConversation: (conv) => ipcRenderer.invoke('chat:saveConv', conv), + deleteConversation: (id) => ipcRenderer.invoke('chat:deleteConv', id), + }, + work: { + convert: (rawData) => ipcRenderer.invoke('work:convert', rawData), + export: (result) => ipcRenderer.invoke('work:export', result), + save: (raw, result) => ipcRenderer.invoke('work:save', raw, result), + list: () => ipcRenderer.invoke('work:list'), + delete: (id) => ipcRenderer.invoke('work:delete', id), + }, +}; + +contextBridge.exposeInMainWorld('dexter', api); diff --git a/desktop/src/renderer/index.html b/desktop/src/renderer/index.html new file mode 100644 index 000000000..825176e26 --- /dev/null +++ b/desktop/src/renderer/index.html @@ -0,0 +1,16 @@ + + + + + + + Dexter + + +
+ + + diff --git a/desktop/src/renderer/src/App.tsx b/desktop/src/renderer/src/App.tsx new file mode 100644 index 000000000..cf9d50316 --- /dev/null +++ b/desktop/src/renderer/src/App.tsx @@ -0,0 +1,247 @@ +import { useEffect, useState } from 'react'; +import SettingsView from './components/SettingsView'; +import HelpView from './components/HelpView'; +import ChatView from './components/ChatView'; +import WorkView from './components/WorkView'; +import HistoryView from './components/HistoryView'; +import SidebarStatus, { type SideStatus } from './components/SidebarStatus'; +import type { ChatConversation, ConversionRecord } from '../../shared/sidecar'; + +type View = 'chat' | 'work' | 'history' | 'settings' | 'help'; + +const SEARCH_ENVS = [ + 'EXASEARCH_API_KEY', + 'PERPLEXITY_API_KEY', + 'TAVILY_API_KEY', + 'LANGSEARCH_API_KEY', +]; + +function HelpIcon(): JSX.Element { + return ( + + + + + + ); +} + +function GearIcon(): JSX.Element { + return ( + + + + + ); +} + +function PanelIcon(): JSX.Element { + return ( + + + + + ); +} + +function ClockIcon(): JSX.Element { + return ( + + + + + ); +} + +export default function App(): JSX.Element { + const [view, setView] = useState('chat'); + const [collapsed, setCollapsed] = useState(false); + const [status, setStatus] = useState(null); + + const [chats, setChats] = useState([]); + const [chatId, setChatId] = useState(null); + const [works, setWorks] = useState([]); + const [workId, setWorkId] = useState(null); + + async function loadStatus(): Promise { + try { + const [provs, setts, secs] = await Promise.all([ + window.dexter.providers.list(), + window.dexter.settings.getAll(), + window.dexter.secrets.statusAll(), + ]); + const sec: Record = {}; + for (const s of secs) sec[s.envVar] = s.exists; + const llm = provs.some((p) => p.apiKeyEnvVar && sec[p.apiKeyEnvVar]); + setStatus({ + modelId: setts.modelId, + llm, + items: [ + { label: 'DART', connected: !!sec['DART_API_KEY'] }, + { label: 'KRX', connected: !!sec['KRX_ID'] && !!sec['KRX_PW'] }, + { label: '๊ตญ๋ฏผ์—ฐ๊ธˆ', connected: !!sec['DATA_GO_KR_SERVICE_KEY'] }, + { label: '์›น ๊ฒ€์ƒ‰', connected: SEARCH_ENVS.some((e) => sec[e]) }, + ], + }); + } catch { + /* ignore */ + } + } + + useEffect(() => { + void loadStatus(); + void window.dexter.chat.listConversations().then(setChats).catch(() => {}); + void window.dexter.work.list().then(setWorks).catch(() => {}); + }, []); + + function newChat(): void { + setView('chat'); + setChatId(null); + void window.dexter.chat.reset(); + } + function selectChat(id: string): void { + setView('chat'); + setChatId(id); + void window.dexter.chat.reset(); + } + function onChatSaved(conv: ChatConversation): void { + setChats((prev) => [conv, ...prev.filter((c) => c.id !== conv.id)]); + setChatId(conv.id); + } + + function newWork(): void { + setView('work'); + setWorkId(null); + } + function selectWork(id: string): void { + setView('work'); + setWorkId(id); + } + function onWorkSaved(rec: ConversionRecord): void { + setWorks((prev) => [rec, ...prev.filter((w) => w.id !== rec.id)]); + setWorkId(rec.id); + } + + function onDeleteChat(id: string): void { + void window.dexter.chat.deleteConversation(id); + setChats((prev) => prev.filter((c) => c.id !== id)); + if (chatId === id) setChatId(null); + } + function onDeleteWork(id: string): void { + void window.dexter.work.delete(id); + setWorks((prev) => prev.filter((w) => w.id !== id)); + if (workId === id) setWorkId(null); + } + + const currentChat = chats.find((c) => c.id === chatId) ?? null; + const currentWork = works.find((w) => w.id === workId) ?? null; + + return ( +
+ + +
+ {collapsed && ( + + )} +
+ setView('settings')} + /> +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ); +} diff --git a/desktop/src/renderer/src/components/ChatView.tsx b/desktop/src/renderer/src/components/ChatView.tsx new file mode 100644 index 000000000..0487b4664 --- /dev/null +++ b/desktop/src/renderer/src/components/ChatView.tsx @@ -0,0 +1,294 @@ +import { type KeyboardEvent, useEffect, useRef, useState } from 'react'; +import ReactMarkdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; +import ThreeLogo from './ThreeLogo'; +import type { AgentEvent, ChatConversation, SidecarToMain } from '../../../shared/sidecar'; + +interface ChatMessage { + id: string; + role: 'user' | 'assistant'; + content: string; + pending?: boolean; + status?: string; +} + +interface Props { + conversation: ChatConversation | null; + onSaved: (conv: ChatConversation) => void; + onOpenSettings: () => void; +} + +const EXAMPLES = [ + '์‚ผ์„ฑ์ „์ž ์‚ฌ์—…๋ณด๊ณ ์„œ์—์„œ ํ•ต์‹ฌ ๋ฆฌ์Šคํฌ์™€ ์‚ฌ์—… ํ˜„ํ™ฉ ์ •๋ฆฌํ•ด์ค˜', + '์—์ฝ”ํ”„๋กœ๋น„์—  ์™ธ๊ตญ์ธ ์ง€๋ถ„์œจยท๊ณต๋งค๋„ ์ž”๊ณ  ์ถ”์ด ๊ฐ™์ด ๋ณด์—ฌ์ค˜', + 'SKํ•˜์ด๋‹‰์Šค DCF๋กœ ์ ์ •์ฃผ๊ฐ€ ๊ณ„์‚ฐํ•ด์ค˜', + '๊ตญ๋ฏผ์—ฐ๊ธˆ์ด ๋ณด์œ ํ•œ ํ˜„๋Œ€์ฐจ ์ง€๋ถ„๊ณผ 5% ์ด์ƒ ๋Œ€๋Ÿ‰๋ณด์œ  ํ˜„ํ™ฉ ์•Œ๋ ค์ค˜', +]; + +const TOOL_LABELS: Record = { + get_financials: '์žฌ๋ฌด์ œํ‘œ ์กฐํšŒ', + get_financials_kr: '์žฌ๋ฌด์ œํ‘œ ์กฐํšŒ', + get_market_data: '์‹œ์„ธ ์กฐํšŒ', + get_market_data_kr: '์‹œ์„ธ ์กฐํšŒ', + read_filings: '๊ณต์‹œ ์ •๋…', + read_filings_kr: '๊ณต์‹œ ์ •๋…', + get_filings_kr: '๊ณต์‹œ ๊ฒ€์ƒ‰', + get_foreign_ownership_kr: '์™ธ๊ตญ์ธ ์ง€๋ถ„ ์กฐํšŒ', + get_short_balance_kr: '๊ณต๋งค๋„ ์ž”๊ณ  ์กฐํšŒ', + get_nps_holdings: '๊ตญ๋ฏผ์—ฐ๊ธˆ ๋ณด์œ  ์กฐํšŒ', + get_large_holders_kr: '๋Œ€๋Ÿ‰๋ณด์œ  ์กฐํšŒ', + get_insider_trades_kr: '์ž„์› ๊ฑฐ๋ž˜ ์กฐํšŒ', + stock_screener: '์ข…๋ชฉ ์Šคํฌ๋ฆฌ๋‹', + web_search: '์›น ๊ฒ€์ƒ‰', + web_fetch: '์›น ํŽ˜์ด์ง€ ์ฝ๊ธฐ', + skill: '๋ถ„์„ ์Šคํ‚ฌ ๋กœ๋“œ', +}; + +function argSummary(args?: Record): string { + if (!args) return ''; + for (const k of ['ticker', 'symbol', 'corp', 'skill', 'query', 'name']) { + const v = args[k]; + if (typeof v === 'string' && v) return v; + } + return ''; +} + +function toolStatus(ev: AgentEvent): string { + const label = TOOL_LABELS[ev.tool ?? ''] ?? ev.tool ?? '๋ฐ์ดํ„ฐ ์กฐํšŒ'; + const arg = argSummary(ev.args); + return arg ? `${label} ยท ${arg} โ€ฆ` : `${label} โ€ฆ`; +} + +export default function ChatView({ conversation, onSaved, onOpenSettings }: Props): JSX.Element { + const [messages, setMessages] = useState([]); + const [input, setInput] = useState(''); + const [sending, setSending] = useState(false); + const [hasLlmKey, setHasLlmKey] = useState(null); + + const scrollRef = useRef(null); + const taRef = useRef(null); + const activeRef = useRef<{ runId: string; pendingId: string } | null>(null); + const currentIdRef = useRef(null); + + // LLM key check (drives empty-state guidance). + useEffect(() => { + (async () => { + try { + const [provs, statuses] = await Promise.all([ + window.dexter.providers.list(), + window.dexter.secrets.statusAll(), + ]); + const llmEnvs = new Set(provs.filter((p) => p.apiKeyEnvVar).map((p) => p.apiKeyEnvVar as string)); + setHasLlmKey(statuses.some((s) => llmEnvs.has(s.envVar) && s.exists)); + } catch { + setHasLlmKey(false); + } + })(); + }, []); + + // Load messages when the selected conversation changes (or new chat = null). + useEffect(() => { + currentIdRef.current = conversation?.id ?? null; + setMessages( + conversation + ? conversation.messages.map((m) => ({ id: crypto.randomUUID(), role: m.role, content: m.content })) + : [], + ); + }, [conversation?.id]); + + function persist(msgs: ChatMessage[]): void { + const id = currentIdRef.current; + if (!id) return; + const stored = msgs + .filter((m) => !m.pending && m.content.trim()) + .map((m) => ({ role: m.role, content: m.content })); + if (stored.length < 2) return; + const title = (stored.find((m) => m.role === 'user')?.content ?? '๋Œ€ํ™”').slice(0, 40); + const conv: ChatConversation = { id, createdAt: Date.now(), updatedAt: Date.now(), title, messages: stored }; + void window.dexter.chat + .saveConversation(conv) + .then(() => onSaved(conv)) + .catch(() => {}); + } + + // Subscribe once to sidecar messages. + useEffect(() => { + function patch(patchFn: (msg: ChatMessage) => ChatMessage): void { + const active = activeRef.current; + if (!active) return; + setMessages((m) => m.map((msg) => (msg.id === active.pendingId ? patchFn(msg) : msg))); + } + + function handle(msg: SidecarToMain): void { + const active = activeRef.current; + if (!active) return; + if ('id' in msg && msg.id !== active.runId) return; + + if (msg.type === 'event') { + const ev = msg.event; + switch (ev.type) { + case 'text_delta': + if (typeof ev.text === 'string') { + patch((m) => ({ ...m, content: m.content + ev.text, status: undefined })); + } + break; + case 'thinking': + patch((m) => ({ ...m, status: m.content ? undefined : '๋ถ„์„ ์ค‘โ€ฆ' })); + break; + case 'tool_start': + patch((m) => ({ ...m, content: '', status: toolStatus(ev) })); + break; + case 'tool_end': + case 'tool_error': + patch((m) => ({ ...m, status: '์ •๋ฆฌ ์ค‘โ€ฆ' })); + break; + case 'done': + if (typeof ev.answer === 'string') { + patch((m) => ({ ...m, content: ev.answer as string, pending: false, status: undefined })); + } + break; + } + } else if (msg.type === 'done') { + activeRef.current = null; + setSending(false); + setMessages((prev) => { + persist(prev); + return prev; + }); + } else if (msg.type === 'error') { + patch((m) => ({ ...m, content: `์˜ค๋ฅ˜: ${msg.message}`, pending: false, status: undefined })); + activeRef.current = null; + setSending(false); + } + } + + return window.dexter.chat.onEvent(handle); + }, []); + + useEffect(() => { + scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight }); + }, [messages]); + + async function send(): Promise { + const text = input.trim(); + if (!text || sending) return; + setInput(''); + if (!currentIdRef.current) currentIdRef.current = crypto.randomUUID(); + const pendingId = crypto.randomUUID(); + setMessages((m) => [ + ...m, + { id: crypto.randomUUID(), role: 'user', content: text }, + { id: pendingId, role: 'assistant', content: '', pending: true, status: '์‹œ์ž‘ํ•˜๋Š” ์ค‘โ€ฆ' }, + ]); + setSending(true); + try { + const { runId } = await window.dexter.chat.send(text); + activeRef.current = { runId, pendingId }; + } catch (e) { + const message = e instanceof Error ? e.message : String(e); + setMessages((m) => + m.map((msg) => (msg.id === pendingId ? { ...msg, content: `์˜ค๋ฅ˜: ${message}`, pending: false } : msg)), + ); + setSending(false); + } + } + + function cancel(): void { + const active = activeRef.current; + if (active) { + void window.dexter.chat.cancel(active.runId); + setMessages((m) => + m.map((msg) => + msg.id === active.pendingId + ? { ...msg, content: msg.content || '์ค‘๋‹จ๋จ', pending: false, status: undefined } + : msg, + ), + ); + activeRef.current = null; + } + setSending(false); + } + + function onKeyDown(e: KeyboardEvent): void { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + void send(); + } + } + + function useExample(text: string): void { + setInput(text); + taRef.current?.focus(); + } + + const empty = messages.length === 0; + + return ( +
+
+ {empty ? ( +
+ +

๋ฌด์—‡์ด๋“  ๋ฌผ์–ด๋ณด์„ธ์š”

+

DARTยทKRX์— ์ง์ ‘ ๊ฐ€์ง€ ์•Š์•„๋„, ์งˆ๋ฌธํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ์•„ ์ •๋ฆฌํ•ด ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

+ {hasLlmKey === false ? ( +
+

์‹œ์ž‘ํ•˜๋ ค๋ฉด LLM API ํ‚ค๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

+ +
+ ) : ( +
+ {EXAMPLES.map((ex) => ( + + ))} +
+ )} +
+ ) : ( +
+ {messages.map((m) => ( +
+ {m.role === 'assistant' ? ( + m.content ? ( + {m.content} + ) : ( + {m.status ?? 'โ—โ—โ—'} + ) + ) : ( + m.content + )} + {m.pending && m.content && } +
+ ))} +
+ )} +
+ +
+
+