bps-mcp-server 0.16.4 → 0.17.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/README.md +13 -5
- package/dist/client/bps-client.d.ts +10 -1
- package/dist/client/bps-client.d.ts.map +1 -1
- package/dist/client/bps-client.js +66 -13
- package/dist/client/bps-client.js.map +1 -1
- package/dist/client/request-scheduler.d.ts +29 -0
- package/dist/client/request-scheduler.d.ts.map +1 -0
- package/dist/client/request-scheduler.js +75 -0
- package/dist/client/request-scheduler.js.map +1 -0
- package/dist/config/kabkota-indicators.d.ts +30 -0
- package/dist/config/kabkota-indicators.d.ts.map +1 -0
- package/dist/config/kabkota-indicators.js +49 -0
- package/dist/config/kabkota-indicators.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +30 -0
- package/dist/server.js.map +1 -1
- package/dist/services/data-formatter.d.ts +28 -0
- package/dist/services/data-formatter.d.ts.map +1 -1
- package/dist/services/data-formatter.js +74 -0
- package/dist/services/data-formatter.js.map +1 -1
- package/dist/services/intent-detector.d.ts.map +1 -1
- package/dist/services/intent-detector.js +1 -0
- package/dist/services/intent-detector.js.map +1 -1
- package/dist/services/kabkota-ranking.d.ts +61 -0
- package/dist/services/kabkota-ranking.d.ts.map +1 -0
- package/dist/services/kabkota-ranking.js +378 -0
- package/dist/services/kabkota-ranking.js.map +1 -0
- package/dist/tools/analysis.tools.d.ts.map +1 -1
- package/dist/tools/analysis.tools.js +78 -9
- package/dist/tools/analysis.tools.js.map +1 -1
- package/dist/tools/dynamic-data.tools.d.ts.map +1 -1
- package/dist/tools/dynamic-data.tools.js +19 -7
- package/dist/tools/dynamic-data.tools.js.map +1 -1
- package/dist/tools/smart.tools.d.ts.map +1 -1
- package/dist/tools/smart.tools.js +12 -2
- package/dist/tools/smart.tools.js.map +1 -1
- package/dist/utils/error.d.ts +11 -1
- package/dist/utils/error.d.ts.map +1 -1
- package/dist/utils/error.js +50 -2
- package/dist/utils/error.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { getPeriodId, getPeriodLabel, isNeutralPeriodLabel } from "../client/fields.js";
|
|
2
|
+
import { buildStructuredPayload } from "./data-formatter.js";
|
|
3
|
+
import { computeRelevanceScore, searchVariables } from "./variable-search.js";
|
|
4
|
+
import { findKabkotaIndicator } from "../config/kabkota-indicators.js";
|
|
5
|
+
import { logger } from "../utils/logger.js";
|
|
6
|
+
const DEFAULT_CONCURRENCY = 3;
|
|
7
|
+
/** Share of provinces that must publish a year before it is used for all. */
|
|
8
|
+
const COMMON_YEAR_COVERAGE = 0.6;
|
|
9
|
+
/** Kab/kota series older than this are reported as gaps, not ranked. */
|
|
10
|
+
const MAX_DATA_AGE_YEARS = 2;
|
|
11
|
+
export async function rankKabkota(client, resolver, options) {
|
|
12
|
+
const hint = findKabkotaIndicator(options.query);
|
|
13
|
+
const provinces = await resolveProvinces(resolver, options.provinces);
|
|
14
|
+
const missing = [];
|
|
15
|
+
// Provinces we know only publish province totals: report them as gaps
|
|
16
|
+
// instead of spending 3 requests each to rediscover that.
|
|
17
|
+
const included = [];
|
|
18
|
+
for (const province of provinces) {
|
|
19
|
+
const knownGap = hint?.unavailable[province.domain];
|
|
20
|
+
if (knownGap) {
|
|
21
|
+
missing.push({ province: province.name, reason: knownGap });
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
included.push(province);
|
|
25
|
+
}
|
|
26
|
+
const concurrency = Math.max(1, options.concurrency ?? DEFAULT_CONCURRENCY);
|
|
27
|
+
// 1. Which variable holds the kab/kota breakdown in each province.
|
|
28
|
+
const targets = [];
|
|
29
|
+
for (const province of included) {
|
|
30
|
+
const candidates = await candidateVariables(client, province, options.query, hint);
|
|
31
|
+
if (candidates.length === 0) {
|
|
32
|
+
missing.push({ province: province.name, reason: "tidak ada tabel kabupaten/kota yang cocok" });
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
targets.push({ province, candidates });
|
|
36
|
+
}
|
|
37
|
+
// 2. Pick one comparison year. Each BPS province releases Sakernas at its own
|
|
38
|
+
// pace; defaulting to "latest per province" mixes 2024/2025/2026 and makes
|
|
39
|
+
// the ranking incomparable (an agent then re-sweeps everything by hand to
|
|
40
|
+
// normalise the year — exactly what this tool must prevent).
|
|
41
|
+
const targetYear = options.year ??
|
|
42
|
+
(options.periodMode === "latest"
|
|
43
|
+
? undefined
|
|
44
|
+
: await selectTargetYear(client, targets, concurrency));
|
|
45
|
+
// 3. Fetch and merge.
|
|
46
|
+
const collected = [];
|
|
47
|
+
for (let index = 0; index < targets.length; index += concurrency) {
|
|
48
|
+
const batch = targets.slice(index, index + concurrency);
|
|
49
|
+
const settled = await Promise.allSettled(batch.map((target) => fetchProvinceRows(client, target, options.query, targetYear)));
|
|
50
|
+
settled.forEach((outcome, position) => {
|
|
51
|
+
const target = batch[position];
|
|
52
|
+
if (outcome.status === "fulfilled") {
|
|
53
|
+
if (outcome.value.rows.length === 0) {
|
|
54
|
+
missing.push({
|
|
55
|
+
province: target.province.name,
|
|
56
|
+
reason: outcome.value.reason ?? "tidak ada tabel kabupaten/kota yang cocok",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
collected.push(...outcome.value.rows);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
const reason = outcome.reason instanceof Error ? outcome.reason.message : String(outcome.reason);
|
|
65
|
+
logger.debug(`rankKabkota: ${target.province.name} failed: ${reason}`);
|
|
66
|
+
missing.push({ province: target.province.name, reason: `gagal diambil (${reason.slice(0, 120)})` });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const sorted = collected.sort((a, b) => options.order === "highest" ? b.value - a.value : a.value - b.value);
|
|
71
|
+
const periods = [...new Set(sorted.map((row) => row.period))];
|
|
72
|
+
const series = [
|
|
73
|
+
...new Set(sorted
|
|
74
|
+
.map((row) => row.series?.trim())
|
|
75
|
+
.filter((value) => !!value && !/^tidak ada$/i.test(value))
|
|
76
|
+
.map((value) => value.replace(/\s+/g, " "))),
|
|
77
|
+
];
|
|
78
|
+
return {
|
|
79
|
+
rows: sorted.slice(0, Math.max(1, options.topN)),
|
|
80
|
+
totalRegions: sorted.length,
|
|
81
|
+
provincesCovered: new Set(collected.map((row) => row.domain)).size,
|
|
82
|
+
provincesConsidered: provinces.length,
|
|
83
|
+
periods,
|
|
84
|
+
series,
|
|
85
|
+
indicatorLabel: hint?.label ?? (collected[0]?.indicator ?? options.query),
|
|
86
|
+
missing,
|
|
87
|
+
targetYear,
|
|
88
|
+
yearRequested: options.year,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
async function resolveProvinces(resolver, requested) {
|
|
92
|
+
await resolver.ensureLoaded();
|
|
93
|
+
const isProvince = (domain) => domain.length === 4 && domain.endsWith("00") && domain !== "0000";
|
|
94
|
+
const all = resolver
|
|
95
|
+
.getDomains()
|
|
96
|
+
.filter((domain) => isProvince(domain.domain_id))
|
|
97
|
+
.map((domain) => ({ domain: domain.domain_id, name: domain.domain_name }));
|
|
98
|
+
const unique = new Map(all.map((province) => [province.domain, province]));
|
|
99
|
+
if (!requested || requested.length === 0) {
|
|
100
|
+
return [...unique.values()];
|
|
101
|
+
}
|
|
102
|
+
const selected = [];
|
|
103
|
+
for (const query of requested) {
|
|
104
|
+
const resolved = await resolver.resolve(query);
|
|
105
|
+
if (resolved && unique.has(resolved.domainId)) {
|
|
106
|
+
selected.push(unique.get(resolved.domainId));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return selected;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Newest year that enough provinces actually publish. Using the newest *shared*
|
|
113
|
+
* year keeps the ranking apples-to-apples instead of mixing 2024/2025/2026.
|
|
114
|
+
*/
|
|
115
|
+
async function selectTargetYear(client, targets, concurrency) {
|
|
116
|
+
if (targets.length === 0)
|
|
117
|
+
return undefined;
|
|
118
|
+
const yearCounts = new Map();
|
|
119
|
+
let reporting = 0;
|
|
120
|
+
for (let index = 0; index < targets.length; index += concurrency) {
|
|
121
|
+
const batch = targets.slice(index, index + concurrency);
|
|
122
|
+
const settled = await Promise.allSettled(batch.map(async (target) => {
|
|
123
|
+
const periods = await client.listPeriods(target.province.domain, Number(target.candidates[0].varId));
|
|
124
|
+
const years = new Set(periods
|
|
125
|
+
.map((period) => getPeriodLabel(period).match(/\d{4}/)?.[0])
|
|
126
|
+
.filter((year) => !!year));
|
|
127
|
+
return years;
|
|
128
|
+
}));
|
|
129
|
+
for (const outcome of settled) {
|
|
130
|
+
if (outcome.status !== "fulfilled" || outcome.value.size === 0)
|
|
131
|
+
continue;
|
|
132
|
+
reporting += 1;
|
|
133
|
+
for (const year of outcome.value) {
|
|
134
|
+
yearCounts.set(year, (yearCounts.get(year) ?? 0) + 1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (yearCounts.size === 0)
|
|
139
|
+
return undefined;
|
|
140
|
+
const threshold = Math.max(2, Math.ceil(reporting * COMMON_YEAR_COVERAGE));
|
|
141
|
+
const eligible = [...yearCounts.entries()]
|
|
142
|
+
.filter(([, count]) => count >= threshold)
|
|
143
|
+
.map(([year]) => year)
|
|
144
|
+
.sort();
|
|
145
|
+
if (eligible.length > 0)
|
|
146
|
+
return eligible[eligible.length - 1];
|
|
147
|
+
// No shared year: take the most common one rather than mixing all of them.
|
|
148
|
+
return [...yearCounts.entries()]
|
|
149
|
+
.sort((a, b) => b[1] - a[1] || b[0].localeCompare(a[0]))[0]?.[0];
|
|
150
|
+
}
|
|
151
|
+
async function fetchProvinceRows(client, target, query, year) {
|
|
152
|
+
const { province, candidates } = target;
|
|
153
|
+
for (const candidate of candidates) {
|
|
154
|
+
try {
|
|
155
|
+
const periodId = await findPeriodId(client, province.domain, candidate.varId, year);
|
|
156
|
+
if (!periodId) {
|
|
157
|
+
if (year)
|
|
158
|
+
return { rows: [], reason: `belum ada data ${year}` };
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const response = await client.getDynamicData(province.domain, candidate.varId, periodId);
|
|
162
|
+
const payload = buildStructuredPayload(response, province.domain);
|
|
163
|
+
const rows = pickRegionRows(payload.rows, query, province);
|
|
164
|
+
if (rows.length >= 2) {
|
|
165
|
+
const staleYear = stalenessYear(rows);
|
|
166
|
+
if (staleYear !== null) {
|
|
167
|
+
return {
|
|
168
|
+
rows: [],
|
|
169
|
+
reason: `data terbaru ${staleYear} (lebih dari ${MAX_DATA_AGE_YEARS} tahun)`,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
logger.debug(`rankKabkota: ${province.name} → var ${candidate.varId} (${rows.length} kab/kota)`);
|
|
173
|
+
return { rows };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
logger.debug(`rankKabkota: ${province.name} var ${candidate.varId} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return { rows: [], reason: "tidak ada tabel kabupaten/kota yang cocok" };
|
|
181
|
+
}
|
|
182
|
+
/** Year of the newest row, or null when the whole series is recent enough. */
|
|
183
|
+
function stalenessYear(rows) {
|
|
184
|
+
const years = rows
|
|
185
|
+
.map((row) => Number(row.period.match(/\d{4}/)?.[0] ?? 0))
|
|
186
|
+
.filter((year) => year > 1900);
|
|
187
|
+
if (years.length === 0)
|
|
188
|
+
return null;
|
|
189
|
+
const newest = Math.max(...years);
|
|
190
|
+
return new Date().getFullYear() - newest > MAX_DATA_AGE_YEARS ? newest : null;
|
|
191
|
+
}
|
|
192
|
+
async function candidateVariables(client, province, query, hint) {
|
|
193
|
+
const hinted = hint?.variables[province.domain];
|
|
194
|
+
if (hinted) {
|
|
195
|
+
return [{ varId: hinted, title: hint.label }];
|
|
196
|
+
}
|
|
197
|
+
const discovered = await searchVariables(client, province.domain, query, {
|
|
198
|
+
maxSubjects: 6,
|
|
199
|
+
maxPagesPerSubject: 3,
|
|
200
|
+
maxCandidates: 12,
|
|
201
|
+
});
|
|
202
|
+
return discovered
|
|
203
|
+
.slice(0, 4)
|
|
204
|
+
.map((candidate) => ({ varId: String(candidate.var_id), title: candidate.title }));
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Resolve the period to request. BPS requires `th`, so with no year we take the
|
|
208
|
+
* newest period the variable publishes (periods come back newest-first).
|
|
209
|
+
*/
|
|
210
|
+
async function findPeriodId(client, domain, varId, year) {
|
|
211
|
+
const periods = await client.listPeriods(domain, Number(varId));
|
|
212
|
+
if (periods.length === 0)
|
|
213
|
+
return undefined;
|
|
214
|
+
if (!year)
|
|
215
|
+
return getPeriodId(periods[0]) || undefined;
|
|
216
|
+
const matching = periods.filter((period) => getPeriodLabel(period).includes(year));
|
|
217
|
+
if (matching.length === 0)
|
|
218
|
+
return undefined;
|
|
219
|
+
// Prefer the annual/August period when a year has several (Feb vs Aug).
|
|
220
|
+
const withAgustus = matching.find((period) => /agustus|tahunan/i.test(getPeriodLabel(period)));
|
|
221
|
+
return getPeriodId(withAgustus ?? matching[matching.length - 1]) || undefined;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Keep one value per kabupaten/kota, choosing the series that matches the query
|
|
225
|
+
* (e.g. TPT rather than TPAK when a table packs several indicators).
|
|
226
|
+
*/
|
|
227
|
+
function pickRegionRows(rows, query, province) {
|
|
228
|
+
const regions = rows.filter((row) => isRegionRow(row, province));
|
|
229
|
+
if (regions.length === 0)
|
|
230
|
+
return [];
|
|
231
|
+
const series = selectSeries(regions, query);
|
|
232
|
+
let candidates = regions;
|
|
233
|
+
if (series !== null) {
|
|
234
|
+
const matching = regions.filter((row) => normalize(row.series) === series);
|
|
235
|
+
if (matching.length > 0)
|
|
236
|
+
candidates = matching;
|
|
237
|
+
}
|
|
238
|
+
const subperiod = selectSubperiod(candidates);
|
|
239
|
+
if (subperiod !== null) {
|
|
240
|
+
const matching = candidates.filter((row) => normalize(row.subperiod) === subperiod);
|
|
241
|
+
if (matching.length > 0)
|
|
242
|
+
candidates = matching;
|
|
243
|
+
}
|
|
244
|
+
const byRegion = new Map();
|
|
245
|
+
for (const row of candidates) {
|
|
246
|
+
const region = (row.category ?? "").replace(/^\d+\s*[-–]?\s*/, "").trim();
|
|
247
|
+
if (!region || byRegion.has(region))
|
|
248
|
+
continue;
|
|
249
|
+
if (typeof row.value !== "number" || !Number.isFinite(row.value))
|
|
250
|
+
continue;
|
|
251
|
+
const unit = row.unit ?? "";
|
|
252
|
+
// Guard against broken source rows (e.g. an unemployment count of 9.154
|
|
253
|
+
// published as a percentage). Percentages above 100 are data errors.
|
|
254
|
+
if (/persen|percent|%/i.test(unit) && (row.value < 0 || row.value > 100))
|
|
255
|
+
continue;
|
|
256
|
+
byRegion.set(region, {
|
|
257
|
+
region: titleCaseRegion(region),
|
|
258
|
+
province: province.name,
|
|
259
|
+
domain: province.domain,
|
|
260
|
+
value: row.value,
|
|
261
|
+
period: [row.period, isNeutralPeriodLabel(row.subperiod) ? undefined : row.subperiod]
|
|
262
|
+
.filter(Boolean)
|
|
263
|
+
.join(" "),
|
|
264
|
+
series: row.series?.trim(),
|
|
265
|
+
unit,
|
|
266
|
+
indicator: row.indicator,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return [...byRegion.values()];
|
|
270
|
+
}
|
|
271
|
+
function isRegionRow(row, province) {
|
|
272
|
+
const label = (row.category ?? "").trim();
|
|
273
|
+
if (!label)
|
|
274
|
+
return false;
|
|
275
|
+
if (/^(indonesia|nasional|total|jumlah)$/i.test(label))
|
|
276
|
+
return false;
|
|
277
|
+
if (/provinsi/i.test(label))
|
|
278
|
+
return false;
|
|
279
|
+
// Rows often carry the domain code: "7600 - Provinsi Sulawesi Barat",
|
|
280
|
+
// "3301 Kabupaten Cilacap". The province row starts with the province code.
|
|
281
|
+
if (label.startsWith(province.domain))
|
|
282
|
+
return false;
|
|
283
|
+
const stripped = normalize(label.replace(/^\d+\s*[-–]?\s*/, ""));
|
|
284
|
+
if (stripped === normalize(province.name))
|
|
285
|
+
return false;
|
|
286
|
+
return stripped.length > 1;
|
|
287
|
+
}
|
|
288
|
+
/** Pick the series (derived variable) that best matches the query. */
|
|
289
|
+
function selectSeries(rows, query) {
|
|
290
|
+
const labels = [
|
|
291
|
+
...new Set(rows
|
|
292
|
+
.map((row) => row.series?.trim())
|
|
293
|
+
.filter((label) => !!label && !/^tidak ada$/i.test(label))),
|
|
294
|
+
];
|
|
295
|
+
if (labels.length === 0)
|
|
296
|
+
return null;
|
|
297
|
+
if (labels.length === 1)
|
|
298
|
+
return normalize(labels[0]);
|
|
299
|
+
const scored = labels
|
|
300
|
+
.map((label) => {
|
|
301
|
+
let score = computeRelevanceScore(query, label);
|
|
302
|
+
if (/persentase|tingkat|rasio|%/i.test(label))
|
|
303
|
+
score += 15;
|
|
304
|
+
return { label, score };
|
|
305
|
+
})
|
|
306
|
+
.sort((a, b) => b.score - a.score);
|
|
307
|
+
return scored[0] && scored[0].score > 0 ? normalize(scored[0].label) : normalize(labels[0]);
|
|
308
|
+
}
|
|
309
|
+
/** Prefer the August/annual series when a table has several sub-periods. */
|
|
310
|
+
function selectSubperiod(rows) {
|
|
311
|
+
const labels = [...new Set(rows.map((row) => row.subperiod).filter((label) => !!label))];
|
|
312
|
+
if (labels.length <= 1)
|
|
313
|
+
return null;
|
|
314
|
+
const preference = [/^agustus$/i, /^tahunan$/i, /semester\s*2/i, /desember/i, /november/i];
|
|
315
|
+
for (const pattern of preference) {
|
|
316
|
+
const match = labels.find((label) => pattern.test(label.trim()));
|
|
317
|
+
if (match)
|
|
318
|
+
return normalize(match);
|
|
319
|
+
}
|
|
320
|
+
return normalize(labels[labels.length - 1]);
|
|
321
|
+
}
|
|
322
|
+
function normalize(value) {
|
|
323
|
+
return (value ?? "").trim().toLowerCase();
|
|
324
|
+
}
|
|
325
|
+
function titleCaseRegion(value) {
|
|
326
|
+
// Banten publishes "Kab Pandeglang"/"Kab Serang"; expand for readability.
|
|
327
|
+
const expanded = value.replace(/^kab\.?\s+/i, "Kabupaten ");
|
|
328
|
+
if (expanded !== expanded.toUpperCase())
|
|
329
|
+
return expanded;
|
|
330
|
+
return expanded
|
|
331
|
+
.toLowerCase()
|
|
332
|
+
.split(/\s+/)
|
|
333
|
+
.map((word) => (word.length > 2 ? word[0].toUpperCase() + word.slice(1) : word.toUpperCase()))
|
|
334
|
+
.join(" ");
|
|
335
|
+
}
|
|
336
|
+
export function formatKabkotaRanking(result, options) {
|
|
337
|
+
const lines = [];
|
|
338
|
+
const unit = result.rows[0]?.unit ? ` (${result.rows[0].unit})` : "";
|
|
339
|
+
const direction = options.order === "highest" ? "Tertinggi" : "Terendah";
|
|
340
|
+
const periodLabel = result.periods.length === 1
|
|
341
|
+
? result.periods[0]
|
|
342
|
+
: result.periods.length === 0
|
|
343
|
+
? "-"
|
|
344
|
+
: `${result.periods[0]} … ${result.periods[result.periods.length - 1]}`;
|
|
345
|
+
const periodHeader = result.targetYear
|
|
346
|
+
? `${result.targetYear} (tahun yang sama untuk semua provinsi)`
|
|
347
|
+
: periodLabel;
|
|
348
|
+
lines.push(`## Ranking Kabupaten/Kota: ${result.indicatorLabel}${unit}`);
|
|
349
|
+
lines.push(`**Urutan:** ${direction} | **Periode:** ${periodHeader}`);
|
|
350
|
+
lines.push("");
|
|
351
|
+
lines.push("| # | Kabupaten/Kota | Provinsi | Nilai | Periode |");
|
|
352
|
+
lines.push("| --- | --- | --- | --- | --- |");
|
|
353
|
+
result.rows.forEach((row, index) => {
|
|
354
|
+
lines.push(`| ${index + 1} | ${row.region} | ${row.province} | ${row.value.toLocaleString("id-ID")} | ${row.period} |`);
|
|
355
|
+
});
|
|
356
|
+
lines.push("");
|
|
357
|
+
lines.push(`_Cakupan: ${result.totalRegions} kabupaten/kota dari ${result.provincesCovered} provinsi. ` +
|
|
358
|
+
`Menampilkan ${result.rows.length} teratas._`);
|
|
359
|
+
if (result.targetYear) {
|
|
360
|
+
lines.push(`_Angka di atas sudah diseragamkan ke tahun ${result.targetYear}; tidak perlu menyisir ulang tabel provinsi. ` +
|
|
361
|
+
`Untuk tahun lain: get_ranking(..., year="2025"). Untuk seri terbaru per provinsi: period_mode="latest"._`);
|
|
362
|
+
}
|
|
363
|
+
else if (result.periods.length > 1) {
|
|
364
|
+
lines.push(`_Catatan: periode tidak seragam (${result.periods.join(", ")}) karena period_mode="latest". ` +
|
|
365
|
+
`Sebutkan tahun per baris, atau panggil ulang dengan year="<tahun>" agar sebanding._`);
|
|
366
|
+
}
|
|
367
|
+
if (result.series.length > 1) {
|
|
368
|
+
lines.push(`_Seri yang dipakai: ${result.series[0]}; seri lain di tabel: ${result.series.slice(1).join(", ")}._`);
|
|
369
|
+
}
|
|
370
|
+
if (result.missing.length > 0) {
|
|
371
|
+
const listed = result.missing.slice(0, 12).map((item) => `${item.province} (${item.reason})`);
|
|
372
|
+
lines.push("");
|
|
373
|
+
lines.push(`**Provinsi tanpa data kabupaten/kota:** ${listed.join("; ")}${result.missing.length > 12 ? "; …" : ""}`);
|
|
374
|
+
lines.push(`_Untuk provinsi tersebut, TPT hanya tersedia level provinsi — gunakan get_ranking(level="provinsi") atau find_data._`);
|
|
375
|
+
}
|
|
376
|
+
return lines.join("\n");
|
|
377
|
+
}
|
|
378
|
+
//# sourceMappingURL=kabkota-ranking.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kabkota-ranking.js","sourceRoot":"","sources":["../../src/services/kabkota-ranking.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analysis.tools.d.ts","sourceRoot":"","sources":["../../src/tools/analysis.tools.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"analysis.tools.d.ts","sourceRoot":"","sources":["../../src/tools/analysis.tools.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AA2BxE;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,SAAS,EACjB,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,gBAAgB,GAAG,IAAI,GAC7B,IAAI,CAmeN"}
|
|
@@ -2,6 +2,8 @@ import { z } from "zod/v4";
|
|
|
2
2
|
import { appendAttribution } from "../services/attribution.js";
|
|
3
3
|
import { invalidateVar, normalizeKeyword } from "../services/learning.js";
|
|
4
4
|
import { resolveVariable as resolveVariableShared, resolveVariableForYears, searchVariables, } from "../services/variable-search.js";
|
|
5
|
+
import { formatKabkotaRanking, rankKabkota } from "../services/kabkota-ranking.js";
|
|
6
|
+
import { findKabkotaIndicator } from "../config/kabkota-indicators.js";
|
|
5
7
|
import { extractVervarId, getAggregateValue, getVervarEntries, selectAggregateVervar, } from "../services/dynamic-data-utils.js";
|
|
6
8
|
import { formatUnit, getPeriodId, getPeriodLabel, getTurthId, getTurthLabel, getVariableTitle, getVariableUnit, isNeutralPeriodLabel, } from "../client/fields.js";
|
|
7
9
|
/**
|
|
@@ -270,21 +272,80 @@ Contoh query user yang cocok untuk tool ini:
|
|
|
270
272
|
}
|
|
271
273
|
});
|
|
272
274
|
// ---------- get_ranking ----------
|
|
273
|
-
server.tool("get_ranking", `Ambil peringkat/ranking
|
|
274
|
-
Gunakan tool ini ketika user ingin melihat peringkat, top-N, atau perbandingan seluruh provinsi.
|
|
275
|
+
server.tool("get_ranking", `Ambil peringkat/ranking wilayah berdasarkan indikator tertentu dalam 1 langkah.
|
|
275
276
|
|
|
276
|
-
|
|
277
|
+
Mendukung dua level wilayah:
|
|
278
|
+
- **level="kabkota"** — semua kabupaten/kota se-Indonesia (BPS tidak punya tabel
|
|
279
|
+
nasional untuk ini; server menyapu tabel tiap provinsi sekaligus).
|
|
280
|
+
- **level="provinsi"** — 34–38 provinsi dari tabel nasional.
|
|
281
|
+
|
|
282
|
+
level="auto" (default) memilih kabkota bila query menyebut kabupaten/kota/daerah,
|
|
283
|
+
selain itu provinsi. Untuk ranking kabupaten/kota, sebutkan level secara eksplisit
|
|
284
|
+
agar tidak ambigu.
|
|
285
|
+
|
|
286
|
+
Contoh query user yang cocok:
|
|
287
|
+
- "10 kabupaten/kota dengan tingkat pengangguran tertinggi" → level="kabkota"
|
|
277
288
|
- "10 provinsi termiskin di Indonesia"
|
|
278
289
|
- "peringkat IPM seluruh provinsi"
|
|
279
290
|
- "provinsi dengan pengangguran tertinggi"
|
|
280
|
-
- "
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
top_n: z.number().int().min(1).max(40).default(10).describe("Jumlah data yang ditampilkan (default 10, max 40 untuk semua provinsi)"),
|
|
291
|
+
- "5 kota dengan pengangguran tertinggi di Jawa Timur" → level="kabkota", provinces="Jawa Timur"`, {
|
|
292
|
+
query: z.string().describe("Indikator untuk ranking (misal: kemiskinan, pengangguran/TPT, IPM, penduduk)"),
|
|
293
|
+
top_n: z.number().int().min(1).max(200).default(10).describe("Jumlah data yang ditampilkan (default 10, max 200 untuk kabupaten/kota, 40 untuk provinsi)"),
|
|
284
294
|
order: z.enum(["highest", "lowest"]).default("highest").describe("Urutan: 'highest' (tertinggi dulu) atau 'lowest' (terendah dulu)"),
|
|
285
|
-
year: z.string().optional().describe("Tahun data. Kosongkan untuk terbaru."),
|
|
286
|
-
|
|
295
|
+
year: z.string().optional().describe("Tahun data. Kosongkan untuk tahun bersama terbaru (default) agar semua wilayah sebanding."),
|
|
296
|
+
period_mode: z.enum(["common", "latest"]).default("common").describe("'common' (default): seragamkan ke tahun bersama terbaru; 'latest': pakai periode terbaru tiap provinsi (tahun bisa campur)"),
|
|
297
|
+
level: z.enum(["provinsi", "kabkota"]).describe("WAJIB dipilih. 'kabkota' bila jawaban perlu kabupaten/kota (satu panggilan, ~380 wilayah, tahun diseragamkan); 'provinsi' untuk peringkat 34-38 provinsi"),
|
|
298
|
+
provinces: z.string().optional().describe("Batasi ke provinsi tertentu (nama dipisah koma, misal 'Jawa Timur, Jawa Tengah'). Hanya untuk level=kabkota."),
|
|
299
|
+
format: z.enum(["markdown", "json"]).default("markdown").describe("Format keluaran: 'markdown' untuk dibaca, 'json' untuk diproses lebih lanjut"),
|
|
300
|
+
}, async ({ query, top_n, order, year, level, provinces, format, period_mode }) => {
|
|
287
301
|
try {
|
|
302
|
+
const useKabkota = level === "kabkota";
|
|
303
|
+
if (useKabkota) {
|
|
304
|
+
const provinceList = provinces
|
|
305
|
+
? provinces.split(",").map((name) => name.trim()).filter(Boolean)
|
|
306
|
+
: undefined;
|
|
307
|
+
const ranking = await rankKabkota(client, resolver, {
|
|
308
|
+
query,
|
|
309
|
+
year,
|
|
310
|
+
topN: Math.min(top_n, 200),
|
|
311
|
+
order,
|
|
312
|
+
provinces: provinceList,
|
|
313
|
+
periodMode: period_mode,
|
|
314
|
+
});
|
|
315
|
+
if (ranking.rows.length === 0) {
|
|
316
|
+
return {
|
|
317
|
+
content: [{
|
|
318
|
+
type: "text",
|
|
319
|
+
text: appendAttribution(`Tidak ada data kabupaten/kota untuk "${query}".\n\n` +
|
|
320
|
+
`**Saran:**\n` +
|
|
321
|
+
`1. Panggil get_ranking(level="provinsi") untuk angka provinsi\n` +
|
|
322
|
+
`2. Gunakan find_variable(keyword="${query}", domain="<kode provinsi>") lalu get_dynamic_data\n` +
|
|
323
|
+
`3. Gunakan allstats_search(query="${query} kabupaten", content="table")`),
|
|
324
|
+
}],
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
if (format === "json") {
|
|
328
|
+
return {
|
|
329
|
+
content: [{
|
|
330
|
+
type: "text",
|
|
331
|
+
text: JSON.stringify({
|
|
332
|
+
level: "kabkota",
|
|
333
|
+
indicator: ranking.indicatorLabel,
|
|
334
|
+
order,
|
|
335
|
+
rows: ranking.rows,
|
|
336
|
+
totalRegions: ranking.totalRegions,
|
|
337
|
+
provincesCovered: ranking.provincesCovered,
|
|
338
|
+
periods: ranking.periods,
|
|
339
|
+
targetYear: ranking.targetYear,
|
|
340
|
+
missingProvinces: ranking.missing,
|
|
341
|
+
}, null, 2),
|
|
342
|
+
}],
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
content: [{ type: "text", text: appendAttribution(formatKabkotaRanking(ranking, { order, query })) }],
|
|
347
|
+
};
|
|
348
|
+
}
|
|
288
349
|
// For ranking, we need national-level data that breaks down by province
|
|
289
350
|
// Prefer variables with "Provinsi" in title for proper provincial breakdown
|
|
290
351
|
const varData = await resolveVariableForRanking(client, store, query);
|
|
@@ -349,6 +410,14 @@ Contoh query user yang cocok untuk tool ini:
|
|
|
349
410
|
lines.push("");
|
|
350
411
|
lines.push(`_Menampilkan ${display.length} dari ${rankings.length} wilayah._`);
|
|
351
412
|
}
|
|
413
|
+
// Point agents at the kab/kota equivalent so they do not sweep the
|
|
414
|
+
// provinces by hand after getting a province-only answer.
|
|
415
|
+
const kabkotaIndicator = findKabkotaIndicator(query);
|
|
416
|
+
if (kabkotaIndicator) {
|
|
417
|
+
lines.push("");
|
|
418
|
+
lines.push(`_Butuh level kabupaten/kota? Panggil \`get_ranking(query="${query}", level="kabkota")\` — ` +
|
|
419
|
+
`satu panggilan, semua kab/kota se-Indonesia, tahun diseragamkan._`);
|
|
420
|
+
}
|
|
352
421
|
return { content: [{ type: "text", text: appendAttribution(lines.join("\n")) }] };
|
|
353
422
|
}
|
|
354
423
|
catch (error) {
|