Add head-to-head record lookup to rank snapshot tool
- get_team_rank_snapshot에 vs 인자 추가 — 순위표의 팀간 승패표(vsRecords)에서 두 팀의 올 시즌 맞대결 승패무를 반환 - formatH2hResult 순수 함수 분리 및 단위 테스트 추가 - 오늘 상대가 아닌 임의 팀과의 상대 전적 질문을 도구 1회 호출로 처리(일정→전적 체이닝도 가능). 기존에는 vsRecords가 도구 계층에서 버려져 모델이 접근할 경로가 없었음
This commit is contained in:
parent
9e24611db0
commit
148e26d1bb
@ -11,7 +11,7 @@ import type { ChatTool } from "./chatProviderService";
|
|||||||
import type { ChatToolCallInfo } from "../types/chat";
|
import type { ChatToolCallInfo } from "../types/chat";
|
||||||
import type { Lineup, KeyPlayerRanking } from "../kbo/game-detail";
|
import type { Lineup, KeyPlayerRanking } from "../kbo/game-detail";
|
||||||
import type { PlayerRecord } from "../kbo/player/common";
|
import type { PlayerRecord } from "../kbo/player/common";
|
||||||
import type { TeamRank } from "../kbo/team-rank";
|
import type { TeamRank, TeamVsRecord } from "../kbo/team-rank";
|
||||||
import type { ScheduleGame } from "../types/kbo";
|
import type { ScheduleGame } from "../types/kbo";
|
||||||
import { TeamCode, type AttendanceMonthDoc, type VoteHistoryDoc } from "../types/panit";
|
import { TeamCode, type AttendanceMonthDoc, type VoteHistoryDoc } from "../types/panit";
|
||||||
import { addDays, type DateString } from "../types/dateString";
|
import { addDays, type DateString } from "../types/dateString";
|
||||||
@ -218,6 +218,21 @@ export function formatRankResult(teams: TeamRank[], teamCode: TeamCode | null):
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 팀간 시즌 상대 전적(h2h) 포맷(순수 함수). KBO 순위표의 팀간 승패표(vsRecords)에서
|
||||||
|
* 두 팀의 맞대결 성적을 꺼낸다 — 오늘 상대가 아닌 임의 팀과의 전적 질문용.
|
||||||
|
*/
|
||||||
|
export function formatH2hResult(team: TeamCode, vs: TeamCode, vsRecords: TeamVsRecord[]): string {
|
||||||
|
const myName = KBO_RANK_TEAM_NAMES[team];
|
||||||
|
const oppName = KBO_RANK_TEAM_NAMES[vs];
|
||||||
|
const wld = vsRecords.find((r) => r.team === myName)?.headToHead[oppName];
|
||||||
|
if (!wld) return `${teamLabel(team)}와 ${teamLabel(vs)}의 상대 전적 정보를 못 찾았어.`;
|
||||||
|
return (
|
||||||
|
`[${teamLabel(team)} vs ${teamLabel(vs)}] 올 시즌 상대 전적 ` +
|
||||||
|
`${wld.wins}승 ${wld.losses}패 ${wld.draws}무 (${teamLabel(team)} 기준)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** 출석 결과 포맷(순수 함수). 이번 달 출석 일자·누적·포인트 잔액. */
|
/** 출석 결과 포맷(순수 함수). 이번 달 출석 일자·누적·포인트 잔액. */
|
||||||
export function formatAttendanceResult(
|
export function formatAttendanceResult(
|
||||||
monthLabel: string,
|
monthLabel: string,
|
||||||
@ -438,7 +453,8 @@ export function buildChatTools(ctx: ChatToolContext): ChatTool[] {
|
|||||||
description:
|
description:
|
||||||
"KBO 팀 순위와 성적을 조회한다. team 지정 시 그 팀의 순위·승패·승률·게임차·최근10경기·" +
|
"KBO 팀 순위와 성적을 조회한다. team 지정 시 그 팀의 순위·승패·승률·게임차·최근10경기·" +
|
||||||
"연승연패·홈원정 성적을 상세히, all=true면 전체 순위표를 돌려준다. " +
|
"연승연패·홈원정 성적을 상세히, all=true면 전체 순위표를 돌려준다. " +
|
||||||
"\"우리 몇 위?\", \"LG 순위\", \"전체 순위 보여줘\" 류에 사용한다.",
|
"vs에 상대 팀을 함께 주면 두 팀의 올 시즌 맞대결 상대 전적(승패무)을 돌려준다. " +
|
||||||
|
"\"우리 몇 위?\", \"LG 순위\", \"전체 순위 보여줘\", \"LG전 상대 전적 어때?\" 류에 사용한다.",
|
||||||
parameters: {
|
parameters: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@ -450,11 +466,31 @@ export function buildChatTools(ctx: ChatToolContext): ChatTool[] {
|
|||||||
type: "boolean",
|
type: "boolean",
|
||||||
description: "전체 순위표를 원하면 true. 특정 팀 상세면 생략.",
|
description: "전체 순위표를 원하면 true. 특정 팀 상세면 생략.",
|
||||||
},
|
},
|
||||||
|
vs: {
|
||||||
|
type: "string",
|
||||||
|
description:
|
||||||
|
"상대 전적을 조회할 상대 팀 코드(예: LG). team(생략 시 응원팀)과 이 팀의 " +
|
||||||
|
"올 시즌 맞대결 승패무를 돌려준다. 두 팀 간 전적 질문에만 사용.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
},
|
},
|
||||||
async run(args) {
|
async run(args) {
|
||||||
try {
|
try {
|
||||||
|
// 상대 전적(h2h) 경로 — vs 지정 시 순위표의 팀간 승패표(vsRecords)에서 꺼낸다
|
||||||
|
const vsRaw = typeof args.vs === "string" && args.vs.trim() !== "" ? args.vs.trim() : null;
|
||||||
|
if (vsRaw) {
|
||||||
|
const vs = resolveTeamCode(vsRaw);
|
||||||
|
if (!vs) return "상대 팀은 HH, LG, OB 같은 팀 코드로 알려줘.";
|
||||||
|
const team = pickTeam(args, ctx);
|
||||||
|
if (!team) return NO_TEAM_NOTICE;
|
||||||
|
if (team === vs) return "같은 팀끼리는 상대 전적이 없어 — 서로 다른 두 팀을 알려줘.";
|
||||||
|
const result = await getRank([year]);
|
||||||
|
if (!result || result.length === 0 || result[0].vsRecords.length === 0) {
|
||||||
|
return "상대 전적 정보를 지금은 못 가져왔어.";
|
||||||
|
}
|
||||||
|
return formatH2hResult(team, vs, result[0].vsRecords);
|
||||||
|
}
|
||||||
const team = args.all === true ? null : (resolveTeamCode(args.team) ?? ctx.teamCode);
|
const team = args.all === true ? null : (resolveTeamCode(args.team) ?? ctx.teamCode);
|
||||||
const result = await getRank([year]);
|
const result = await getRank([year]);
|
||||||
if (!result || result.length === 0 || !result[0].teams) {
|
if (!result || result.length === 0 || !result[0].teams) {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
buildChatTools,
|
buildChatTools,
|
||||||
formatAttendanceResult,
|
formatAttendanceResult,
|
||||||
formatGameStandouts,
|
formatGameStandouts,
|
||||||
|
formatH2hResult,
|
||||||
formatLineupResult,
|
formatLineupResult,
|
||||||
formatPredictionBreakdown,
|
formatPredictionBreakdown,
|
||||||
formatRankResult,
|
formatRankResult,
|
||||||
@ -14,7 +15,7 @@ import {
|
|||||||
type ChatToolContext,
|
type ChatToolContext,
|
||||||
} from "../../src/services/chatToolService";
|
} from "../../src/services/chatToolService";
|
||||||
import type { KeyPlayerRanking } from "../../src/kbo/game-detail";
|
import type { KeyPlayerRanking } from "../../src/kbo/game-detail";
|
||||||
import type { TeamRank } from "../../src/kbo/team-rank";
|
import type { TeamRank, TeamVsRecord } from "../../src/kbo/team-rank";
|
||||||
import type { AttendanceMonthDoc, VoteHistoryDoc } from "../../src/types/panit";
|
import type { AttendanceMonthDoc, VoteHistoryDoc } from "../../src/types/panit";
|
||||||
import { getChatProvider } from "../../src/services/chatProviderService";
|
import { getChatProvider } from "../../src/services/chatProviderService";
|
||||||
import { DEFAULT_PROVIDER_CONFIG } from "../../src/services/chatConfigService";
|
import { DEFAULT_PROVIDER_CONFIG } from "../../src/services/chatConfigService";
|
||||||
@ -231,6 +232,27 @@ describe("chatToolService", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("formatH2hResult — 팀간 시즌 상대 전적(vsRecords)", () => {
|
||||||
|
const vsRecords: TeamVsRecord[] = [
|
||||||
|
{
|
||||||
|
team: "한화",
|
||||||
|
headToHead: { LG: { wins: 9, losses: 5, draws: 1 } },
|
||||||
|
total: { wins: 9, losses: 5, draws: 1 },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
it("두 팀의 맞대결 승패무를 조회 팀 기준으로 전한다", () => {
|
||||||
|
const out = formatH2hResult(TeamCode.HH, TeamCode.LG, vsRecords);
|
||||||
|
expect(out).toContain("한화 이글스 vs LG 트윈스");
|
||||||
|
expect(out).toContain("9승 5패 1무");
|
||||||
|
expect(out).toContain("한화 이글스 기준");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("승패표에 상대 항목이 없으면 부재를 알린다", () => {
|
||||||
|
expect(formatH2hResult(TeamCode.LG, TeamCode.OB, vsRecords)).toContain("못 찾았어");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("formatAttendanceResult — 출석·포인트", () => {
|
describe("formatAttendanceResult — 출석·포인트", () => {
|
||||||
it("출석 일자·누적·잔액을 전한다", () => {
|
it("출석 일자·누적·잔액을 전한다", () => {
|
||||||
const doc = { days: [3, 1, 2], lastCheckedInAt: null } as unknown as AttendanceMonthDoc;
|
const doc = { days: [3, 1, 2], lastCheckedInAt: null } as unknown as AttendanceMonthDoc;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user