instruction.md
# LCK Results + Advanced Analysis
## What this skill does
이 스킬은 LCK 조회/분석 전용이다.
- 특정 날짜 LCK 경기 결과 조회
- 특정 팀 alias 정규화 후 필터링
- 현재 스플릿 순위 조회
- 진행 중 경기 live stats 조회
- live timeline 기반 turning point 분석
- Oracle's Elixir 스타일 historical row / CSV 기반
- 팀 파워 레이팅
- 챔피언 matchup / synergy 분석
- patch meta 요약
- 날짜별 match analysis 생성
## Origin / attribution
이 스킬은 `jerjangmin` 님이 만든 원본 [`lck-analytics` skill pack](https://github.com/jerjangmin/share/tree/main/SKILL/lck-analytics)을 k-skill 저장소 안으로 옮기고, 이 저장소의 npm workspace / Changesets 배포 방식에 맞게 정리한 버전이다.
## When to use
- "오늘 LCK 경기 결과 알려줘"
- "2026-04-01 한화 경기 결과랑 순위 보여줘"
- "지금 T1 경기 킬/골드/오브젝트 요약해줘"
- "이 경기 turning point가 뭐였어?"
- "이 밴픽에서 어느 쪽 조합이 더 좋았는지 설명해줘"
- "현재 패치에서 어떤 챔피언이 메타 픽인지 보여줘"
- "LCK 팀 파워 레이팅 보여줘"
## Prerequisites
- Node.js 18+
- `npm install -g lck-analytics`
패키지가 없으면 다른 방법으로 우회하지 말고 먼저 전역 설치를 시도한다.
```bash
npm install -g lck-analytics
```
## Inputs
### 기본 입력
- 날짜: `YYYY-MM-DD`
- 선택 사항: 팀명, 과거 팀명, 한글/영문 약칭 alias
### 고급 분석 입력
- Oracle's Elixir 스타일 CSV 문자열 또는 row 배열
- game id / match id
- live window/details payload 또는 실시간 fetch 권한
- patch version
## Team alias normalization
다음 이름들은 같은 canonical team 으로 인식한다.
- `DN SOOPers`
- `DN FREECS`
- `광동 프릭스`
- `Afreeca Freecs`
추가로 `T1`, `SKT T1`, `담원`, `Dplus KIA`, `브리온`, `한화`, `젠지`, `피어엑스` 등도 alias 정규화를 지원한다.
## Official surfaces
이 스킬은 Riot 공식 / 공식 웹앱 표면을 우선 사용한다.
- 일정/결과: `getSchedule`
- 토너먼트 목록: `getTournamentsForLeague`
- 순위: `getStandings`
- 이벤트 상세: `getEventDetails`
- 라이브 window: `https://feed.lolesports.com/livestats/v1/window/{gameId}`
- 라이브 details: `https://feed.lolesports.com/livestats/v1/details/{gameId}`
historical 고급 분석은 Oracle's Elixir 스타일 데이터 입력을 사용한다.
## Workflow
### Included lightweight local pipeline
이 k-skill 팩에는 경량 로컬 파일 기반 파이프라인 스크립트가 포함된다.
- `scripts/sync-oracle.js`: Oracle-style CSV → historical cache JSON
- `scripts/build-match-report.js`: 날짜별 match analysis 생성
- `scripts/analyze-live-game.js`: game analysis 생성
- 기본 cache 위치: `.openclaw-lck-cache/`
### 1. Basic scoreboard / standings query
```bash
GLOBAL_NPM_ROOT="$(npm root -g)" node --input-type=module - <<'JS'
import path from "node:path";
import { pathToFileURL } from "node:url";
const entry = pathToFileURL(
path.join(process.env.GLOBAL_NPM_ROOT, "lck-analytics", "src", "index.js"),
).href;
const { getLckSummary } = await import(entry);
const summary = await getLckSummary("2026-04-01", {
team: "한화",
includeStandings: true,
});
console.log(JSON.stringify(summary, null, 2));
JS
```
### 2. Historical analytics from Oracle-style CSV
직접 API를 호출해도 되지만, local skill pipeline에서는 아래 스크립트 사용을 우선 권장한다.
```bash
npx -y @nomadamas/k-skill@0 exec lck-analytics scripts/sync-oracle.js -- \
--csv ./lck-analytics/samples/oracle-lck-sample.csv
```
### 3. Match analysis via local pipeline script
```bash
npx -y @nomadamas/k-skill@0 exec lck-analytics scripts/build-match-report.js -- \
--date 2026-04-01
```
필요하면 팀 필터도 같이 준다.
```bash
npx -y @nomadamas/k-skill@0 exec lck-analytics scripts/build-match-report.js -- \
--date 2026-04-01 \
--team 한화
```
### 4. Game analysis with turning points via local pipeline script
```bash
npx -y @nomadamas/k-skill@0 exec lck-analytics scripts/analyze-live-game.js -- \
--game game-id
```
fixture 기반으로 분석할 때는 `--window`, `--details` 를 같이 줄 수 있다.
## Output guidelines
사용자에게는 원본 JSON을 길게 그대로 던지지 말고 먼저 아래 순서로 정리한다.
### 경기 결과 요청
- 경기 시각
- 팀1 vs 팀2
- 상태
- 세트 스코어
- 요청 팀 경기만 있으면 해당 경기 우선
- standings 요청이 있으면 현재 순위 같이 표시
### 진행 중 경기 요청
- 현재 게임 번호
- 킬 차이
- 골드 차이
- 드래곤/바론/타워 차이
- turning point 1~3개
### historical / meta 요청
- sample 수를 먼저 표시
- 팀 파워 레이팅은 상위 팀부터 정렬
- champion matchup / synergy는 표본 수가 적으면 낮은 확신도로 표시
- patch meta는 top picks / risers 위주로 짧게 요약
## Done when
- 날짜 기준 경기 요약이 있다
- 요청 팀 필터가 적용된다
- standings 요청이면 현재 순위가 같이 정리된다
- live 요청이면 현재 게임 요약과 turning point가 있다
- historical 입력이 있으면 patch meta 또는 power rating까지 설명할 수 있다
## Failure modes
- Riot 웹앱 API 구조/헤더가 바뀌면 패키지 수정이 필요할 수 있다
- `LOLESPORTS_API_KEY` public fallback이 회전되면 환경변수 override가 필요할 수 있다
- historical CSV 컬럼명이 너무 다르면 Oracle-style 정규화 전에 전처리가 필요할 수 있다
## Notes
- 이 스킬은 조회/분석 전용이다
- 사용자의 "오늘/어제" 요청은 항상 절대 날짜(`YYYY-MM-DD`)로 변환해서 실행한다
- 이 저장소에서 `main` 으로 머지되면 Changesets가 Version Packages PR을 만들고, 그 PR이 merge된 뒤 npm publish가 실행된다
README.md
# LCK Analytics skill pack
k-skill 버전의 `lck-analytics` 스킬 팩입니다.
- Original source: <https://github.com/jerjangmin/share/tree/main/SKILL/lck-analytics>
- Original author: `jerjangmin`
- This repo adaptation: npm workspace / Changesets 릴리스 흐름에 맞춘 k-skill 배포용 패키징
포함 항목:
- `SKILL.md`: 에이전트에 바로 줄 수 있는 스킬 문서
- `scripts/sync-oracle.js`: Oracle-style CSV → historical cache JSON
- `scripts/build-match-report.js`: 날짜별 match analysis 생성
- `scripts/analyze-live-game.js`: live game analysis 생성
- `samples/oracle-lck-sample.csv`: local smoke test용 샘플 CSV
references/DISCLAIMER.md
# DISCLAIMER — `lck-analytics`
이 스킬은 Riot Games·LCK 또는 데이터 제공자의 공식 기능 또는 공식 지원 도구가 아니며, 공식 제휴·후원·승인·인증 또는 협업한 사실이 전혀 없습니다. 상표와 서비스명은 경기·리그 분석 기능과 조회 대상을 설명하기 위해서만 사용합니다.
대법원 2005. 6. 10. 선고 [2005도1637 판결](https://www.law.go.kr/LSW/precInfoP.do?precSeq=83920)(소니용 리모컨 사건)은 기능 설명용 표장과 출처표시를 구별했습니다. [상표법 제2조](https://www.law.go.kr/법령/상표법/제2조), [제89조](https://www.law.go.kr/법령/상표법/제89조), [제90조](https://www.law.go.kr/법령/상표법/제90조), [제108조](https://www.law.go.kr/법령/상표법/제108조)에 따른 출처 혼동 판단은 별도입니다.
대법원 2022. 5. 12. 선고 [2021도1533 판결](https://www.law.go.kr/LSW/precInfoP.do?precSeq=221765)은 공개정보 수집만으로 곧바로 [정보통신망법 제48조](https://www.law.go.kr/법령/정보통신망이용촉진및정보보호등에관한법률/제48조) 위반이 되는 것은 아닌 사정을 제시했지만, 크롤링을 일반적으로 허용하지 않습니다. [저작권법 제93조](https://www.law.go.kr/법령/저작권법/제93조)의 데이터베이스 권리와 [형법 제314조 제2항](https://www.law.go.kr/법령/형법/제314조)의 서비스 장애·영업 방해 책임은 별도입니다.
- 공개 경기정보 자동 수집은 반드시 개인의 정보 조회용으로만 사용합니다.
- 조직적·대량 크롤링, 별도 경기 DB 구축·재배포, 방송·영상·이미지 복제를 하지 않습니다.
- Riot/LCK 데이터 정책과 attribution 요구를 지키고 접근통제·차단을 우회하지 않습니다.
이 문서는 적법성을 보증하는 법률 자문이 아닙니다.
references/TRADEMARK-LEGAL-STATEMENT.md
# 상표 사용 법적 고지 — `lck-analytics`
이 스킬에서 `LCK`, `Riot Games`, `LoL Esports` 명칭은 경기 결과·순위·밴픽·메타 분석의 **대상 리그, 게임, 데이터 출처**를 식별하기 위해 사용한다. k-skill의 출처를 Riot Games 또는 LCK 운영주체로 표시하려는 사용이 아니다.
대법원 2005. 6. 10. 선고 [2005도1637 판결](https://www.law.go.kr/LSW/precInfoP.do?precSeq=83920)은 타인의 표장을 출처표시가 아니라 상품 기능 또는 적용 기종을 밝히기 위해 사용하고 상표 사용으로 인식될 수 없는 경우 침해가 아니라고 판시했다. [상표법 제2조](https://www.law.go.kr/법령/상표법/제2조), [제89조](https://www.law.go.kr/법령/상표법/제89조), [제90조](https://www.law.go.kr/법령/상표법/제90조), [제108조](https://www.law.go.kr/법령/상표법/제108조)와 대법원 [2011다18802](https://www.law.go.kr/LSW/precInfoP.do?precSeq=167457), [2019후10418](https://law.go.kr/LSW/precInfoP.do?mode=0&precSeq=230725) 판결에 따라 실제 거래계에서 출처표시로 기능하는지는 표시 태양과 사용 경위 등을 종합해 판단해야 한다.
따라서 이 명칭은 필요한 범위의 평문으로만 사용하고, 별도 근거 없이 로고, 공식·제휴·후원·인증·파트너 표현을 사용하지 않는다. 각 상표의 권리는 해당 권리자에게 있다. 이 문서는 경기·게임 데이터의 저작권·데이터베이스권, 중계권, 커뮤니티 정책, 약관·계약 또는 접근 방식의 적법성을 판단하지 않는다.
전체 검토: [제3자 상표의 기능 설명·호환 대상 표시 검토](https://github.com/NomaDamas/k-skill/blob/dev/docs/legal/trademark-use-review.md)
samples/oracle-lck-sample.csv
league,matchid,date,patch,side,teamname,opponentteam,playername,position,champion,opponentchampion,result,gd15,csd15,xpd15,drg,bn,blindpick,counterpick
LCK,match-1,2026-04-01,16.6.753.8272,blue,Hanwha Life Esports,T1,HLE Zeus,top,Aatrox,Gnar,win,1200,18,340,100,100,0,1
LCK,match-1,2026-04-01,16.6.753.8272,blue,Hanwha Life Esports,T1,HLE Peanut,jungle,Vi,Sejuani,win,800,5,280,100,100,1,0
LCK,match-1,2026-04-01,16.6.753.8272,red,T1,Hanwha Life Esports,T1 Doran,top,Gnar,Aatrox,loss,-1200,-18,-340,0,0,1,0
LCK,match-1,2026-04-01,16.6.753.8272,red,T1,Hanwha Life Esports,T1 Oner,jungle,Sejuani,Vi,loss,-800,-5,-280,0,0,0,1
scripts/_lib.js
const fs = require("node:fs");
const path = require("node:path");
const { pathToFileURL } = require("node:url");
async function loadLckResults() {
const candidates = [];
const packageNames = ["lck-analytics", "lck-results"];
if (process.env.GLOBAL_NPM_ROOT) {
for (const packageName of packageNames) {
candidates.push(path.join(process.env.GLOBAL_NPM_ROOT, packageName, "src", "index.js"));
}
}
try {
const globalRoot = await detectGlobalNpmRoot();
for (const packageName of packageNames) {
candidates.push(path.join(globalRoot, packageName, "src", "index.js"));
}
} catch {
// ignore detection failure and continue to local fallback
}
candidates.push(path.resolve(__dirname, "..", "..", "packages", "lck-analytics", "src", "index.js"));
const entryPath = candidates.find((candidate) => fs.existsSync(candidate));
if (!entryPath) {
throw new Error("Could not find lck-analytics package. Install it globally with `npm install -g lck-analytics` or run from the k-skill repo.");
}
return import(pathToFileURL(entryPath).href);
}
function ensureDir(dirPath) {
fs.mkdirSync(dirPath, { recursive: true });
}
function readJson(filePath, fallback = null) {
if (!fs.existsSync(filePath)) {
return fallback;
}
return JSON.parse(fs.readFileSync(filePath, "utf8"));
}
function writeJson(filePath, value) {
ensureDir(path.dirname(filePath));
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
}
function readText(filePath, fallback = "") {
if (!fs.existsSync(filePath)) {
return fallback;
}
return fs.readFileSync(filePath, "utf8");
}
function parseArgs(argv) {
const args = {};
for (let index = 0; index < argv.length; index += 1) {
const token = argv[index];
if (!token.startsWith("--")) {
continue;
}
const key = token.slice(2);
const next = argv[index + 1];
if (!next || next.startsWith("--")) {
args[key] = true;
continue;
}
args[key] = next;
index += 1;
}
return args;
}
function formatOutput(value) {
return `${JSON.stringify(value, null, 2)}\n`;
}
function resolveCachePaths(baseDir) {
return {
root: baseDir,
historical: path.join(baseDir, "historical-analysis.json"),
live: path.join(baseDir, "live"),
reports: path.join(baseDir, "reports"),
};
}
async function detectGlobalNpmRoot() {
const { execFileSync } = require("node:child_process");
return execFileSync("npm", ["root", "-g"], { encoding: "utf8" }).trim();
}
module.exports = {
ensureDir,
formatOutput,
loadLckResults,
parseArgs,
readJson,
readText,
resolveCachePaths,
writeJson,
};
scripts/analyze-live-game.js
#!/usr/bin/env node
const fs = require("node:fs");
const path = require("node:path");
const {
formatOutput,
loadLckResults,
parseArgs,
readJson,
resolveCachePaths,
writeJson,
} = require("./_lib");
async function main() {
const args = parseArgs(process.argv.slice(2));
const gameId = args.game;
if (!gameId) {
throw new Error("--game <gameId> is required");
}
const cacheDir = path.resolve(args.cache || path.join(process.cwd(), ".openclaw-lck-cache"));
const paths = resolveCachePaths(cacheDir);
const historicalWrapper = readJson(paths.historical, { data: {} });
const pkg = await loadLckResults();
const liveWindowPayload = args.window ? JSON.parse(fs.readFileSync(path.resolve(args.window), "utf8")) : undefined;
const liveDetailsPayload = args.details ? JSON.parse(fs.readFileSync(path.resolve(args.details), "utf8")) : undefined;
const analysis = await pkg.getGameAnalysis(gameId, {
matchId: args.match,
number: args.number ? Number(args.number) : null,
state: args.state || undefined,
historicalDataset: historicalWrapper.data,
liveWindowPayload,
liveDetailsPayload,
});
const reportFile = path.join(paths.reports, `game-${gameId}.json`);
writeJson(reportFile, analysis);
process.stdout.write(formatOutput({
ok: true,
reportFile,
patch: analysis.patch,
turningPoints: analysis.turningPoints,
draftEdge: analysis.draft?.overallEdge || null,
}));
}
main().catch((error) => {
console.error(error.stack || String(error));
process.exitCode = 1;
});
scripts/build-match-report.js
#!/usr/bin/env node
const path = require("node:path");
const {
formatOutput,
loadLckResults,
parseArgs,
readJson,
resolveCachePaths,
writeJson,
} = require("./_lib");
async function main() {
const args = parseArgs(process.argv.slice(2));
const date = args.date;
if (!date) {
throw new Error("--date <YYYY-MM-DD> is required");
}
const cacheDir = path.resolve(args.cache || path.join(process.cwd(), ".openclaw-lck-cache"));
const paths = resolveCachePaths(cacheDir);
const historicalWrapper = readJson(paths.historical, { data: {} });
const pkg = await loadLckResults();
const analysis = await pkg.getMatchAnalysis(date, {
team: args.team || undefined,
historicalDataset: historicalWrapper.data,
});
const reportFile = path.join(paths.reports, `match-${date}${args.team ? `-${args.team}` : ""}.json`);
writeJson(reportFile, analysis);
process.stdout.write(formatOutput({
ok: true,
reportFile,
queryDate: analysis.queryDate,
matchCount: analysis.matches.length,
teams: analysis.matches.map((match) => `${match.team1?.name} vs ${match.team2?.name}`),
}));
}
main().catch((error) => {
console.error(error.stack || String(error));
process.exitCode = 1;
});
scripts/sync-oracle.js
#!/usr/bin/env node
const path = require("node:path");
const {
formatOutput,
loadLckResults,
parseArgs,
readText,
resolveCachePaths,
writeJson,
} = require("./_lib");
async function main() {
const args = parseArgs(process.argv.slice(2));
const cacheDir = path.resolve(args.cache || path.join(process.cwd(), ".openclaw-lck-cache"));
const csvPath = args.csv ? path.resolve(args.csv) : path.join(__dirname, "..", "samples", "oracle-lck-sample.csv");
const league = args.league || "LCK";
const csvText = readText(csvPath);
if (!csvText.trim()) {
throw new Error(`CSV not found or empty: ${csvPath}`);
}
const pkg = await loadLckResults();
const historical = pkg.buildHistoricalAnalytics(csvText, { league });
const paths = resolveCachePaths(cacheDir);
writeJson(paths.historical, {
source: {
type: "oracle-style-csv",
csvPath,
league,
updatedAt: new Date().toISOString(),
},
data: historical,
});
process.stdout.write(formatOutput({
ok: true,
cacheFile: paths.historical,
teamRatings: historical.teamPowerRatings.length,
matchupStats: historical.matchupStats.length,
synergyStats: historical.synergyStats.length,
patchMeta: historical.patchMeta.length,
}));
}
main().catch((error) => {
console.error(error.stack || String(error));
process.exitCode = 1;
});
skill.json
{
"name": "lck-analytics",
"description": "Riot 공식 LoL Esports 데이터와 Oracle's Elixir 스타일 historical 데이터로 LCK 경기 결과, 현재 순위, live turning point, 밴픽 matchup/synergy, patch meta, 팀 파워 레이팅을 조회한다.",
"profiles": [
"vault",
"lookup"
],
"frontmatter": "name: lck-analytics\ndescription: Riot 공식 LoL Esports 데이터와 Oracle's Elixir 스타일 historical 데이터로 LCK 경기 결과, 현재 순위, live turning point, 밴픽 matchup/synergy, patch meta, 팀 파워 레이팅을 조회한다.\nlicense: MIT\nmetadata:\n category: sports\n locale: ko-KR\n phase: v1"
}
SKILL.md
---
name: lck-analytics
description: Riot 공식 LoL Esports 데이터와 Oracle's Elixir 스타일 historical 데이터로 LCK 경기 결과, 현재 순위, live turning point, 밴픽 matchup/synergy, patch meta, 팀 파워 레이팅을 조회한다.
license: MIT
metadata:
category: sports
locale: ko-KR
phase: v1
---
# lck-analytics
<!-- k-skill:cli-stub — generated by scripts/generate-skill-stubs.js; edit skill.json / instruction.md instead -->
## Get the full instructions (required first step)
Run this and follow its output as the primary instructions for this skill:
```bash
npx -y @nomadamas/k-skill@0 instruct lck-analytics
```
The CLI detects the current runtime (Dolshoi vault/CloakBrowser vs generic) and prints only the applicable instructions, always up to date. Helper files bundled with the CLI are listed by:
```bash
npx -y @nomadamas/k-skill@0 files lck-analytics
```
If `npx` is unavailable, install Node.js 18+ or follow https://github.com/NomaDamas/k-skill#readme, or read the source instructions at https://github.com/NomaDamas/k-skill/blob/main/lck-analytics/instruction.md.
## Legal disclaimer (required)
This skill is not an official feature of, officially supported by, affiliated with, sponsored by, approved by, or developed in collaboration with any third-party trademark owner or service operator it identifies. Third-party names are used only to describe the skill's function, lookup target, or compatibility.
Any automated collection of publicly accessible information must be limited to personal, non-organizational lookup. Do not use this skill for systematic or bulk crawling, database building, access-control or block circumvention, or conduct that interferes with a third party's business or service.
Read the full Korean legal disclaimer, including the cited Korean Supreme Court precedents and statutory limits, before use:
```bash
npx -y @nomadamas/k-skill@0 read lck-analytics references/DISCLAIMER.md
```
## Hard rules even without the CLI
- Never execute payment, message/email delivery, final submission, cancellation, or public posting without the user's explicit approval immediately beforehand.
- Never ask for, print, or store plaintext credentials in chat, files, or shell arguments.
- Never bypass legal, physical-presence, CAPTCHA, identity-proofing, or electronic-signature boundaries.