@mcp-gen-ui/core 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +32 -0
- package/dist/consistency.d.ts +19 -0
- package/dist/consistency.d.ts.map +1 -0
- package/dist/consistency.js +53 -0
- package/dist/consistency.js.map +1 -0
- package/dist/deadlines.d.ts +10 -0
- package/dist/deadlines.d.ts.map +1 -0
- package/dist/deadlines.js +29 -0
- package/dist/deadlines.js.map +1 -0
- package/dist/fixtures.d.ts +8 -0
- package/dist/fixtures.d.ts.map +1 -0
- package/dist/fixtures.js +96 -0
- package/dist/fixtures.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/personas.d.ts +100 -0
- package/dist/personas.d.ts.map +1 -0
- package/dist/personas.js +88 -0
- package/dist/personas.js.map +1 -0
- package/dist/recommender.d.ts +15 -0
- package/dist/recommender.d.ts.map +1 -0
- package/dist/recommender.js +222 -0
- package/dist/recommender.js.map +1 -0
- package/dist/repository.d.ts +18 -0
- package/dist/repository.d.ts.map +1 -0
- package/dist/repository.js +16 -0
- package/dist/repository.js.map +1 -0
- package/dist/sqlite-store.d.ts +19 -0
- package/dist/sqlite-store.d.ts.map +1 -0
- package/dist/sqlite-store.js +103 -0
- package/dist/sqlite-store.js.map +1 -0
- package/dist/tool-service.d.ts +29 -0
- package/dist/tool-service.d.ts.map +1 -0
- package/dist/tool-service.js +140 -0
- package/dist/tool-service.js.map +1 -0
- package/package.json +42 -0
package/dist/personas.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export const uniformRecommendationWeights = {
|
|
2
|
+
region: 1,
|
|
3
|
+
age: 1,
|
|
4
|
+
student: 1,
|
|
5
|
+
employment: 1,
|
|
6
|
+
household: 1,
|
|
7
|
+
category: 1,
|
|
8
|
+
query: 1
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Built-in starter personas for public-benefit ranking.
|
|
12
|
+
*
|
|
13
|
+
* These are intentionally small, documented presets rather than hidden policy:
|
|
14
|
+
* embedders can replace the registry to reflect their own audience. `general`
|
|
15
|
+
* preserves the previous uniform-weight default, while the other presets make a
|
|
16
|
+
* single user context more prominent without disabling any score dimension.
|
|
17
|
+
*/
|
|
18
|
+
export const defaultPersonaRegistry = {
|
|
19
|
+
youth_jobseeker: {
|
|
20
|
+
id: "youth_jobseeker",
|
|
21
|
+
description: "Youth job seekers prioritizing employment fit, age fit, and query intent.",
|
|
22
|
+
weights: {
|
|
23
|
+
...uniformRecommendationWeights,
|
|
24
|
+
age: 2,
|
|
25
|
+
employment: 3,
|
|
26
|
+
category: 1.5,
|
|
27
|
+
query: 2
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
university_student: {
|
|
31
|
+
id: "university_student",
|
|
32
|
+
description: "University students prioritizing student eligibility, age fit, and benefit category.",
|
|
33
|
+
weights: {
|
|
34
|
+
...uniformRecommendationWeights,
|
|
35
|
+
age: 2,
|
|
36
|
+
student: 3,
|
|
37
|
+
category: 2
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
newlywed_family: {
|
|
41
|
+
id: "newlywed_family",
|
|
42
|
+
description: "Newlywed families prioritizing household, housing/category, and regional fit.",
|
|
43
|
+
weights: {
|
|
44
|
+
...uniformRecommendationWeights,
|
|
45
|
+
region: 2,
|
|
46
|
+
age: 1.5,
|
|
47
|
+
household: 3,
|
|
48
|
+
category: 2
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
single_parent: {
|
|
52
|
+
id: "single_parent",
|
|
53
|
+
description: "Single-parent households prioritizing household, family/category, region, and employment fit.",
|
|
54
|
+
weights: {
|
|
55
|
+
...uniformRecommendationWeights,
|
|
56
|
+
region: 2,
|
|
57
|
+
employment: 1.5,
|
|
58
|
+
household: 3,
|
|
59
|
+
category: 2
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
senior: {
|
|
63
|
+
id: "senior",
|
|
64
|
+
description: "Seniors prioritizing age fit, local/regional availability, and category fit.",
|
|
65
|
+
weights: {
|
|
66
|
+
...uniformRecommendationWeights,
|
|
67
|
+
region: 1.5,
|
|
68
|
+
age: 3,
|
|
69
|
+
category: 2
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
general: {
|
|
73
|
+
id: "general",
|
|
74
|
+
description: "General-purpose default with uniform weights across all scoring dimensions.",
|
|
75
|
+
weights: { ...uniformRecommendationWeights }
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
export function resolveWeights(persona, overrides = {}, registry = defaultPersonaRegistry) {
|
|
79
|
+
const preset = persona ? registry[persona] : registry.general;
|
|
80
|
+
return {
|
|
81
|
+
...(preset?.weights ?? registry.general?.weights ?? uniformRecommendationWeights),
|
|
82
|
+
...overrides
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function listPersonaPresets(registry = defaultPersonaRegistry) {
|
|
86
|
+
return Object.values(registry);
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=personas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"personas.js","sourceRoot":"","sources":["../src/personas.ts"],"names":[],"mappings":"AAeA,MAAM,CAAC,MAAM,4BAA4B,GAAkC;IACzE,MAAM,EAAE,CAAC;IACT,GAAG,EAAE,CAAC;IACN,OAAO,EAAE,CAAC;IACV,UAAU,EAAE,CAAC;IACb,SAAS,EAAE,CAAC;IACZ,QAAQ,EAAE,CAAC;IACX,KAAK,EAAE,CAAC;CACT,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,eAAe,EAAE;QACf,EAAE,EAAE,iBAAiB;QACrB,WAAW,EAAE,2EAA2E;QACxF,OAAO,EAAE;YACP,GAAG,4BAA4B;YAC/B,GAAG,EAAE,CAAC;YACN,UAAU,EAAE,CAAC;YACb,QAAQ,EAAE,GAAG;YACb,KAAK,EAAE,CAAC;SACT;KACF;IACD,kBAAkB,EAAE;QAClB,EAAE,EAAE,oBAAoB;QACxB,WAAW,EAAE,sFAAsF;QACnG,OAAO,EAAE;YACP,GAAG,4BAA4B;YAC/B,GAAG,EAAE,CAAC;YACN,OAAO,EAAE,CAAC;YACV,QAAQ,EAAE,CAAC;SACZ;KACF;IACD,eAAe,EAAE;QACf,EAAE,EAAE,iBAAiB;QACrB,WAAW,EAAE,+EAA+E;QAC5F,OAAO,EAAE;YACP,GAAG,4BAA4B;YAC/B,MAAM,EAAE,CAAC;YACT,GAAG,EAAE,GAAG;YACR,SAAS,EAAE,CAAC;YACZ,QAAQ,EAAE,CAAC;SACZ;KACF;IACD,aAAa,EAAE;QACb,EAAE,EAAE,eAAe;QACnB,WAAW,EAAE,+FAA+F;QAC5G,OAAO,EAAE;YACP,GAAG,4BAA4B;YAC/B,MAAM,EAAE,CAAC;YACT,UAAU,EAAE,GAAG;YACf,SAAS,EAAE,CAAC;YACZ,QAAQ,EAAE,CAAC;SACZ;KACF;IACD,MAAM,EAAE;QACN,EAAE,EAAE,QAAQ;QACZ,WAAW,EAAE,8EAA8E;QAC3F,OAAO,EAAE;YACP,GAAG,4BAA4B;YAC/B,MAAM,EAAE,GAAG;YACX,GAAG,EAAE,CAAC;YACN,QAAQ,EAAE,CAAC;SACZ;KACF;IACD,OAAO,EAAE;QACP,EAAE,EAAE,SAAS;QACb,WAAW,EAAE,6EAA6E;QAC1F,OAAO,EAAE,EAAE,GAAG,4BAA4B,EAAE;KAC7C;CAC+C,CAAC;AAEnD,MAAM,UAAU,cAAc,CAC5B,OAAmD,EACnD,YAAmC,EAAE,EACrC,WAA4B,sBAAsB;IAElD,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;IAC9D,OAAO;QACL,GAAG,CAAC,MAAM,EAAE,OAAO,IAAI,QAAQ,CAAC,OAAO,EAAE,OAAO,IAAI,4BAA4B,CAAC;QACjF,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,WAA4B,sBAAsB;IACnF,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AACjC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { BenefitRecord, BenefitSearchRequest, BenefitSummary } from "@mcp-gen-ui/schema";
|
|
2
|
+
import { type PersonaRegistry } from "./personas.js";
|
|
3
|
+
export { defaultPersonaRegistry, resolveWeights } from "./personas.js";
|
|
4
|
+
/**
|
|
5
|
+
* Rule-based, LLM-free recommender.
|
|
6
|
+
*
|
|
7
|
+
* Each benefit is classified as `candidate`, `needs_more_info`, or
|
|
8
|
+
* `not_applicable` against the non-identifying profile, with human-readable
|
|
9
|
+
* evidence attached. Results never claim definitive eligibility — by design
|
|
10
|
+
* the host LLM presents them as candidates.
|
|
11
|
+
*/
|
|
12
|
+
export declare function recommendBenefits(benefits: BenefitRecord[], request: BenefitSearchRequest, options?: {
|
|
13
|
+
personas?: PersonaRegistry;
|
|
14
|
+
}): BenefitSummary[];
|
|
15
|
+
//# sourceMappingURL=recommender.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recommender.d.ts","sourceRoot":"","sources":["../src/recommender.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,oBAAoB,EACpB,cAAc,EAOf,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAGL,KAAK,eAAe,EAErB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEvE;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,aAAa,EAAE,EACzB,OAAO,EAAE,oBAAoB,EAC7B,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,eAAe,CAAA;CAAO,GAC3C,cAAc,EAAE,CAmBlB"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { defaultPersonaRegistry, resolveWeights } from "./personas.js";
|
|
2
|
+
export { defaultPersonaRegistry, resolveWeights } from "./personas.js";
|
|
3
|
+
/**
|
|
4
|
+
* Rule-based, LLM-free recommender.
|
|
5
|
+
*
|
|
6
|
+
* Each benefit is classified as `candidate`, `needs_more_info`, or
|
|
7
|
+
* `not_applicable` against the non-identifying profile, with human-readable
|
|
8
|
+
* evidence attached. Results never claim definitive eligibility — by design
|
|
9
|
+
* the host LLM presents them as candidates.
|
|
10
|
+
*/
|
|
11
|
+
export function recommendBenefits(benefits, request, options = {}) {
|
|
12
|
+
const queryTerms = tokenize(`${request.query} ${request.profile.interests.join(" ")}`);
|
|
13
|
+
const scorePlan = buildScorePlan(request.profile.persona, request.weights ?? {}, options.personas ?? defaultPersonaRegistry);
|
|
14
|
+
return benefits
|
|
15
|
+
.map((benefit) => classifyBenefit(benefit, request.profile, queryTerms, scorePlan))
|
|
16
|
+
.sort((a, b) => statusRank(a.status) - statusRank(b.status) ||
|
|
17
|
+
b.score - a.score ||
|
|
18
|
+
b.reasons.length - a.reasons.length ||
|
|
19
|
+
a.title.localeCompare(b.title, "ko"));
|
|
20
|
+
}
|
|
21
|
+
function classifyBenefit(benefit, profile, queryTerms, scorePlan) {
|
|
22
|
+
const reasons = [];
|
|
23
|
+
const missingInfo = [];
|
|
24
|
+
const blockers = [];
|
|
25
|
+
const searchable = searchableTextFor(benefit);
|
|
26
|
+
const queryMatched = queryTerms.some((term) => searchable.includes(term));
|
|
27
|
+
if (queryMatched) {
|
|
28
|
+
reasons.push("검색어와 혜택 설명이 일치합니다.");
|
|
29
|
+
}
|
|
30
|
+
evaluateRegion(benefit, profile, reasons, missingInfo, blockers);
|
|
31
|
+
evaluateAge(benefit, profile, reasons, missingInfo, blockers);
|
|
32
|
+
evaluateStudent(benefit, profile, reasons, missingInfo, blockers);
|
|
33
|
+
evaluateEmployment(benefit, profile, reasons, missingInfo, blockers);
|
|
34
|
+
evaluateHousehold(benefit, profile, reasons, missingInfo);
|
|
35
|
+
const status = decideStatus(blockers, missingInfo, reasons);
|
|
36
|
+
const scoreBreakdown = computeScoreBreakdown(benefit, profile, queryTerms, scorePlan);
|
|
37
|
+
const score = normalizeScore(scoreBreakdown);
|
|
38
|
+
return {
|
|
39
|
+
id: benefit.id,
|
|
40
|
+
title: benefit.title,
|
|
41
|
+
provider: benefit.provider,
|
|
42
|
+
category: benefit.category,
|
|
43
|
+
summary: benefit.summary,
|
|
44
|
+
status,
|
|
45
|
+
score,
|
|
46
|
+
scoreBreakdown,
|
|
47
|
+
reasons: status === "not_applicable" ? blockers : reasons,
|
|
48
|
+
missingInfo
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function evaluateRegion(benefit, profile, reasons, missingInfo, blockers) {
|
|
52
|
+
if (benefit.regionTags.length === 0)
|
|
53
|
+
return;
|
|
54
|
+
if (!profile.region) {
|
|
55
|
+
missingInfo.push("거주 지역 확인이 필요합니다.");
|
|
56
|
+
}
|
|
57
|
+
else if (benefit.regionTags.includes(profile.region)) {
|
|
58
|
+
reasons.push(`${profile.region} 지역 조건과 일치합니다.`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
blockers.push(`${profile.region} 지역 대상 혜택이 아닙니다.`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function evaluateAge(benefit, profile, reasons, missingInfo, blockers) {
|
|
65
|
+
if (benefit.ageRanges.length === 0)
|
|
66
|
+
return;
|
|
67
|
+
if (!profile.ageRange) {
|
|
68
|
+
missingInfo.push("나이대 확인이 필요합니다.");
|
|
69
|
+
}
|
|
70
|
+
else if (benefit.ageRanges.includes(profile.ageRange)) {
|
|
71
|
+
reasons.push("나이대 조건과 일치합니다.");
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
blockers.push("나이대 조건이 맞지 않을 수 있습니다.");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function evaluateStudent(benefit, profile, reasons, missingInfo, blockers) {
|
|
78
|
+
if (!benefit.studentOnly)
|
|
79
|
+
return;
|
|
80
|
+
if (profile.studentStatus === "student") {
|
|
81
|
+
reasons.push("학생 조건과 일치합니다.");
|
|
82
|
+
}
|
|
83
|
+
else if (profile.studentStatus === "unknown") {
|
|
84
|
+
missingInfo.push("학생 여부 확인이 필요합니다.");
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
blockers.push("학생 대상 혜택입니다.");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function evaluateEmployment(benefit, profile, reasons, missingInfo, blockers) {
|
|
91
|
+
if (benefit.employmentStatuses.length === 0)
|
|
92
|
+
return;
|
|
93
|
+
if (benefit.employmentStatuses.includes(profile.employmentStatus)) {
|
|
94
|
+
reasons.push("고용 상태 조건과 일치합니다.");
|
|
95
|
+
}
|
|
96
|
+
else if (profile.employmentStatus === "unknown") {
|
|
97
|
+
missingInfo.push("고용 상태 확인이 필요합니다.");
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
blockers.push("고용 상태 조건이 맞지 않을 수 있습니다.");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function evaluateHousehold(benefit, profile, reasons, _missingInfo) {
|
|
104
|
+
const householdTypes = benefit.householdTypes ?? [];
|
|
105
|
+
if (householdTypes.length === 0 || profile.householdType === "unknown")
|
|
106
|
+
return;
|
|
107
|
+
if (householdTypes.includes(profile.householdType)) {
|
|
108
|
+
reasons.push("가구 유형 조건과 일치합니다.");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function decideStatus(blockers, missingInfo, reasons) {
|
|
112
|
+
if (blockers.length > 0)
|
|
113
|
+
return "not_applicable";
|
|
114
|
+
if (missingInfo.length > 0 || reasons.length === 0)
|
|
115
|
+
return "needs_more_info";
|
|
116
|
+
return "candidate";
|
|
117
|
+
}
|
|
118
|
+
const SCORE_DIMENSIONS = [
|
|
119
|
+
"region",
|
|
120
|
+
"age",
|
|
121
|
+
"student",
|
|
122
|
+
"employment",
|
|
123
|
+
"household",
|
|
124
|
+
"category",
|
|
125
|
+
"query"
|
|
126
|
+
];
|
|
127
|
+
function buildScorePlan(persona, overrides, registry) {
|
|
128
|
+
return {
|
|
129
|
+
dimensions: SCORE_DIMENSIONS,
|
|
130
|
+
weights: resolveWeights(persona ?? "general", overrides, registry)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function computeScoreBreakdown(benefit, profile, queryTerms, scorePlan) {
|
|
134
|
+
return scorePlan.dimensions.map((dimension) => {
|
|
135
|
+
const signal = scoreSignal(dimension, benefit, profile, queryTerms);
|
|
136
|
+
const weight = scorePlan.weights[dimension];
|
|
137
|
+
return {
|
|
138
|
+
dimension,
|
|
139
|
+
signal: signal.value,
|
|
140
|
+
weight,
|
|
141
|
+
contribution: roundScore(signal.value * weight),
|
|
142
|
+
explanation: signal.explanation
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function normalizeScore(scoreBreakdown) {
|
|
147
|
+
const totalWeight = scoreBreakdown.reduce((sum, item) => sum + item.weight, 0);
|
|
148
|
+
if (totalWeight === 0)
|
|
149
|
+
return 0;
|
|
150
|
+
const totalContribution = scoreBreakdown.reduce((sum, item) => sum + item.contribution, 0);
|
|
151
|
+
return roundScore(totalContribution / totalWeight);
|
|
152
|
+
}
|
|
153
|
+
function scoreSignal(dimension, benefit, profile, queryTerms) {
|
|
154
|
+
switch (dimension) {
|
|
155
|
+
case "region":
|
|
156
|
+
return constrainedStringSignal(benefit.regionTags, profile.region, "지역 조건이 없어서 감점하지 않았습니다.", "지역 조건과 일치합니다.", "거주 지역 정보가 없어 부분 점수를 적용했습니다.", "지역 조건과 일치하지 않습니다.");
|
|
157
|
+
case "age":
|
|
158
|
+
return constrainedStringSignal(benefit.ageRanges, profile.ageRange, "나이대 조건이 없어서 감점하지 않았습니다.", "나이대 조건과 일치합니다.", "나이대 정보가 없어 부분 점수를 적용했습니다.", "나이대 조건과 일치하지 않습니다.");
|
|
159
|
+
case "student":
|
|
160
|
+
if (!benefit.studentOnly) {
|
|
161
|
+
return { value: 1, explanation: "학생 전용 조건이 없어서 감점하지 않았습니다." };
|
|
162
|
+
}
|
|
163
|
+
if (profile.studentStatus === "student") {
|
|
164
|
+
return { value: 1, explanation: "학생 조건과 일치합니다." };
|
|
165
|
+
}
|
|
166
|
+
if (profile.studentStatus === "unknown") {
|
|
167
|
+
return { value: 0.5, explanation: "학생 여부 정보가 없어 부분 점수를 적용했습니다." };
|
|
168
|
+
}
|
|
169
|
+
return { value: 0, explanation: "학생 조건과 일치하지 않습니다." };
|
|
170
|
+
case "employment":
|
|
171
|
+
return constrainedStringSignal(benefit.employmentStatuses, profile.employmentStatus === "unknown" ? undefined : profile.employmentStatus, "고용 상태 조건이 없어서 감점하지 않았습니다.", "고용 상태 조건과 일치합니다.", "고용 상태 정보가 없어 부분 점수를 적용했습니다.", "고용 상태 조건과 일치하지 않습니다.");
|
|
172
|
+
case "household":
|
|
173
|
+
return constrainedStringSignal(benefit.householdTypes ?? [], profile.householdType === "unknown" ? undefined : profile.householdType, "가구 유형 조건이 없어서 감점하지 않았습니다.", "가구 유형 조건과 일치합니다.", "가구 유형 정보가 없어 부분 점수를 적용했습니다.", "가구 유형 조건과 일치하지 않습니다.");
|
|
174
|
+
case "category":
|
|
175
|
+
if (profile.interests.length === 0) {
|
|
176
|
+
return { value: 0.5, explanation: "관심 분야가 없어 부분 점수를 적용했습니다." };
|
|
177
|
+
}
|
|
178
|
+
return profile.interests.includes(benefit.category)
|
|
179
|
+
? { value: 1, explanation: "관심 분야와 혜택 분야가 일치합니다." }
|
|
180
|
+
: { value: 0, explanation: "관심 분야와 혜택 분야가 일치하지 않습니다." };
|
|
181
|
+
case "query":
|
|
182
|
+
if (queryTerms.length === 0) {
|
|
183
|
+
return { value: 0, explanation: "검색어 신호가 없습니다." };
|
|
184
|
+
}
|
|
185
|
+
return queryTerms.some((term) => searchableTextFor(benefit).includes(term))
|
|
186
|
+
? { value: 1, explanation: "검색어와 혜택 설명이 일치합니다." }
|
|
187
|
+
: { value: 0, explanation: "검색어와 혜택 설명이 일치하지 않습니다." };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function constrainedStringSignal(requiredValues, profileValue, unconstrainedExplanation, matchExplanation, missingExplanation, mismatchExplanation) {
|
|
191
|
+
if (requiredValues.length === 0) {
|
|
192
|
+
return { value: 1, explanation: unconstrainedExplanation };
|
|
193
|
+
}
|
|
194
|
+
if (!profileValue) {
|
|
195
|
+
return { value: 0.5, explanation: missingExplanation };
|
|
196
|
+
}
|
|
197
|
+
if (requiredValues.includes(profileValue)) {
|
|
198
|
+
return { value: 1, explanation: matchExplanation };
|
|
199
|
+
}
|
|
200
|
+
return { value: 0, explanation: mismatchExplanation };
|
|
201
|
+
}
|
|
202
|
+
function searchableTextFor(benefit) {
|
|
203
|
+
return `${benefit.title} ${benefit.summary} ${benefit.searchableText}`.toLowerCase();
|
|
204
|
+
}
|
|
205
|
+
function roundScore(value) {
|
|
206
|
+
return Math.round(value * 1000) / 1000;
|
|
207
|
+
}
|
|
208
|
+
function tokenize(input) {
|
|
209
|
+
return input
|
|
210
|
+
.toLowerCase()
|
|
211
|
+
.split(/[\s,./]+/)
|
|
212
|
+
.map((term) => term.trim())
|
|
213
|
+
.filter((term) => term.length >= 2);
|
|
214
|
+
}
|
|
215
|
+
function statusRank(status) {
|
|
216
|
+
if (status === "candidate")
|
|
217
|
+
return 0;
|
|
218
|
+
if (status === "needs_more_info")
|
|
219
|
+
return 1;
|
|
220
|
+
return 2;
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=recommender.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recommender.js","sourceRoot":"","sources":["../src/recommender.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type BenefitRecord } from "@mcp-gen-ui/schema";
|
|
2
|
+
/**
|
|
3
|
+
* Transport- and backend-neutral access to benefit records. The MVP ships a
|
|
4
|
+
* fixture-backed implementation so the whole suite runs without live
|
|
5
|
+
* government-site dependencies; future backends (API, cache) implement the
|
|
6
|
+
* same interface.
|
|
7
|
+
*/
|
|
8
|
+
export interface BenefitRepository {
|
|
9
|
+
search(): Promise<BenefitRecord[]>;
|
|
10
|
+
getById(id: string): Promise<BenefitRecord | undefined>;
|
|
11
|
+
}
|
|
12
|
+
export declare class FixtureBenefitRepository implements BenefitRepository {
|
|
13
|
+
private readonly benefits;
|
|
14
|
+
constructor(benefits?: BenefitRecord[]);
|
|
15
|
+
search(): Promise<BenefitRecord[]>;
|
|
16
|
+
getById(id: string): Promise<BenefitRecord | undefined>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../src/repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAG7E;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IACnC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAAC;CACzD;AAED,qBAAa,wBAAyB,YAAW,iBAAiB;IAChE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;gBAE/B,QAAQ,GAAE,aAAa,EAAoB;IAKjD,MAAM,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;IAIlC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;CAG9D"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BenefitRecordSchema } from "@mcp-gen-ui/schema";
|
|
2
|
+
import { fixtureBenefits } from "./fixtures.js";
|
|
3
|
+
export class FixtureBenefitRepository {
|
|
4
|
+
benefits;
|
|
5
|
+
constructor(benefits = fixtureBenefits) {
|
|
6
|
+
// Parse on construction so malformed fixtures fail fast at the boundary.
|
|
7
|
+
this.benefits = benefits.map((benefit) => BenefitRecordSchema.parse(benefit));
|
|
8
|
+
}
|
|
9
|
+
async search() {
|
|
10
|
+
return this.benefits;
|
|
11
|
+
}
|
|
12
|
+
async getById(id) {
|
|
13
|
+
return this.benefits.find((benefit) => benefit.id === id);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=repository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repository.js","sourceRoot":"","sources":["../src/repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAsB,MAAM,oBAAoB,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAahD,MAAM,OAAO,wBAAwB;IAClB,QAAQ,CAAkB;IAE3C,YAAY,WAA4B,eAAe;QACrD,yEAAyE;QACzE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,mBAAmB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IAChF,CAAC;IAED,KAAK,CAAC,MAAM;QACV,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,EAAU;QACtB,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5D,CAAC;CACF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BenefitRecord, ChangeLogEntry } from "@mcp-gen-ui/schema";
|
|
2
|
+
/**
|
|
3
|
+
* SQLite-backed snapshot + change log.
|
|
4
|
+
*
|
|
5
|
+
* Uses Node's built-in `node:sqlite` (DatabaseSync) so the gateway has no
|
|
6
|
+
* native build step. Each recorded benefit is content-hashed; a differing hash
|
|
7
|
+
* for a known id produces an `updated` change-log entry, an unseen id produces
|
|
8
|
+
* `created`, and an identical hash produces `unchanged`.
|
|
9
|
+
*
|
|
10
|
+
* Only normalized public benefit data is stored — never sensitive identifiers.
|
|
11
|
+
*/
|
|
12
|
+
export declare class SnapshotStore {
|
|
13
|
+
private readonly db;
|
|
14
|
+
constructor(path?: string);
|
|
15
|
+
recordBenefitSnapshot(benefit: BenefitRecord): ChangeLogEntry;
|
|
16
|
+
getChangeLog(entityId?: string): ChangeLogEntry[];
|
|
17
|
+
close(): void;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=sqlite-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../src/sqlite-store.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAUxE;;;;;;;;;GASG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAuB;gBAE9B,IAAI,SAA0B;IAuB1C,qBAAqB,CAAC,OAAO,EAAE,aAAa,GAAG,cAAc;IA4C7D,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE;IAsBjD,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
// `node:sqlite` is a Node builtin that is only importable *with* the `node:`
|
|
4
|
+
// prefix. Some bundlers/test transformers (Vite/Vitest) strip the prefix on a
|
|
5
|
+
// static value import, which then fails to resolve. Loading it through
|
|
6
|
+
// createRequire keeps the literal specifier intact at runtime and works
|
|
7
|
+
// identically under plain Node and under Vitest.
|
|
8
|
+
const nodeRequire = createRequire(import.meta.url);
|
|
9
|
+
const { DatabaseSync } = nodeRequire("node:sqlite");
|
|
10
|
+
/**
|
|
11
|
+
* SQLite-backed snapshot + change log.
|
|
12
|
+
*
|
|
13
|
+
* Uses Node's built-in `node:sqlite` (DatabaseSync) so the gateway has no
|
|
14
|
+
* native build step. Each recorded benefit is content-hashed; a differing hash
|
|
15
|
+
* for a known id produces an `updated` change-log entry, an unseen id produces
|
|
16
|
+
* `created`, and an identical hash produces `unchanged`.
|
|
17
|
+
*
|
|
18
|
+
* Only normalized public benefit data is stored — never sensitive identifiers.
|
|
19
|
+
*/
|
|
20
|
+
export class SnapshotStore {
|
|
21
|
+
db;
|
|
22
|
+
constructor(path = "mcp-gen-ui-gateway.db") {
|
|
23
|
+
this.db = new DatabaseSync(path);
|
|
24
|
+
this.db.exec("PRAGMA journal_mode = WAL;");
|
|
25
|
+
this.db.exec(`
|
|
26
|
+
create table if not exists snapshots (
|
|
27
|
+
entity_id text primary key,
|
|
28
|
+
entity_type text not null,
|
|
29
|
+
content_hash text not null,
|
|
30
|
+
payload text not null,
|
|
31
|
+
updated_at text not null
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
create table if not exists change_log (
|
|
35
|
+
id text primary key,
|
|
36
|
+
entity_id text not null,
|
|
37
|
+
entity_type text not null,
|
|
38
|
+
change_type text not null,
|
|
39
|
+
summary text not null,
|
|
40
|
+
created_at text not null
|
|
41
|
+
);
|
|
42
|
+
`);
|
|
43
|
+
}
|
|
44
|
+
recordBenefitSnapshot(benefit) {
|
|
45
|
+
const payload = JSON.stringify(benefit);
|
|
46
|
+
const contentHash = hash(payload);
|
|
47
|
+
const now = new Date().toISOString();
|
|
48
|
+
const existing = this.db
|
|
49
|
+
.prepare("select content_hash from snapshots where entity_id = ?")
|
|
50
|
+
.get(benefit.id);
|
|
51
|
+
const changeType = existing
|
|
52
|
+
? existing.content_hash === contentHash
|
|
53
|
+
? "unchanged"
|
|
54
|
+
: "updated"
|
|
55
|
+
: "created";
|
|
56
|
+
this.db
|
|
57
|
+
.prepare(`insert into snapshots (entity_id, entity_type, content_hash, payload, updated_at)
|
|
58
|
+
values (?, 'benefit', ?, ?, ?)
|
|
59
|
+
on conflict(entity_id) do update set
|
|
60
|
+
content_hash = excluded.content_hash,
|
|
61
|
+
payload = excluded.payload,
|
|
62
|
+
updated_at = excluded.updated_at`)
|
|
63
|
+
.run(benefit.id, contentHash, payload, now);
|
|
64
|
+
const entry = {
|
|
65
|
+
id: randomUUID(),
|
|
66
|
+
entityId: benefit.id,
|
|
67
|
+
entityType: "benefit",
|
|
68
|
+
changeType,
|
|
69
|
+
summary: `${benefit.title} ${changeType}.`,
|
|
70
|
+
createdAt: now
|
|
71
|
+
};
|
|
72
|
+
this.db
|
|
73
|
+
.prepare(`insert into change_log (id, entity_id, entity_type, change_type, summary, created_at)
|
|
74
|
+
values (?, ?, 'benefit', ?, ?, ?)`)
|
|
75
|
+
.run(entry.id, entry.entityId, entry.changeType, entry.summary, entry.createdAt);
|
|
76
|
+
return entry;
|
|
77
|
+
}
|
|
78
|
+
getChangeLog(entityId) {
|
|
79
|
+
const rows = entityId
|
|
80
|
+
? this.db
|
|
81
|
+
.prepare("select * from change_log where entity_id = ? order by created_at desc")
|
|
82
|
+
.all(entityId)
|
|
83
|
+
: this.db.prepare("select * from change_log order by created_at desc").all();
|
|
84
|
+
return rows.map((row) => {
|
|
85
|
+
const record = row;
|
|
86
|
+
return {
|
|
87
|
+
id: record.id,
|
|
88
|
+
entityId: record.entity_id,
|
|
89
|
+
entityType: "benefit",
|
|
90
|
+
changeType: record.change_type,
|
|
91
|
+
summary: record.summary,
|
|
92
|
+
createdAt: record.created_at
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
close() {
|
|
97
|
+
this.db.close();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function hash(payload) {
|
|
101
|
+
return createHash("sha256").update(payload).digest("hex");
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=sqlite-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-store.js","sourceRoot":"","sources":["../src/sqlite-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ApplicationGuideResponse, type BenefitDetail, type BenefitSearchResponse, type ChangeLogResponse, type ChecklistResponse, type UpcomingDeadlinesResponse } from "@mcp-gen-ui/schema";
|
|
2
|
+
import type { BenefitRepository } from "./repository.js";
|
|
3
|
+
import type { SnapshotStore } from "./sqlite-store.js";
|
|
4
|
+
import { type PersonaPreset, type PersonaRegistry } from "./personas.js";
|
|
5
|
+
export type BenefitToolServiceOptions = {
|
|
6
|
+
personas?: PersonaRegistry;
|
|
7
|
+
};
|
|
8
|
+
export declare const NON_ELIGIBILITY_DISCLAIMER = "Recommendations are candidates, not eligibility decisions, and users must verify final requirements on the official source.";
|
|
9
|
+
/**
|
|
10
|
+
* Transport-neutral entry point for the gateway tools. The MCP server (or any
|
|
11
|
+
* other transport) calls these methods; there is no LLM here — orchestration is
|
|
12
|
+
* the host's responsibility. Every input and output is validated against the
|
|
13
|
+
* shared Zod contracts.
|
|
14
|
+
*/
|
|
15
|
+
export declare class BenefitToolService {
|
|
16
|
+
private readonly repository;
|
|
17
|
+
private readonly snapshots?;
|
|
18
|
+
private readonly personas;
|
|
19
|
+
constructor(repository: BenefitRepository, snapshots?: SnapshotStore | undefined, options?: BenefitToolServiceOptions);
|
|
20
|
+
listPersonas(): Promise<PersonaPreset[]>;
|
|
21
|
+
searchBenefits(input: unknown): Promise<BenefitSearchResponse>;
|
|
22
|
+
getBenefitDetail(id: string): Promise<BenefitDetail>;
|
|
23
|
+
getUpcomingDeadlines(input: unknown): Promise<UpcomingDeadlinesResponse>;
|
|
24
|
+
buildChecklist(benefitId: string): Promise<ChecklistResponse>;
|
|
25
|
+
getApplicationGuide(benefitId: string): Promise<ApplicationGuideResponse>;
|
|
26
|
+
getChangeLog(entityId?: string): Promise<ChangeLogResponse>;
|
|
27
|
+
private recordSnapshots;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=tool-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-service.d.ts","sourceRoot":"","sources":["../src/tool-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,wBAAwB,EAC7B,KAAK,aAAa,EAElB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC/B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,eAAe,EACrB,MAAM,eAAe,CAAC;AAEvB,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B,CAAC;AAEF,eAAO,MAAM,0BAA0B,gIACwF,CAAC;AAEhI;;;;;GAKG;AACH,qBAAa,kBAAkB;IAI3B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;IAJ7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;gBAGxB,UAAU,EAAE,iBAAiB,EAC7B,SAAS,CAAC,EAAE,aAAa,YAAA,EAC1C,OAAO,GAAE,yBAA8B;IAKnC,YAAY,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;IAIxC,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAc9D,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IASpD,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAsDxE,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAS7D,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC;IA+BzE,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAOjE,OAAO,CAAC,eAAe;CAMxB"}
|