@heripo/research-radar 5.2.1 → 5.2.3
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/dist/index.js +144 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -317,6 +317,17 @@ function toIsoDate$2(compact) {
|
|
|
317
317
|
? `${compact.slice(0, 4)}-${compact.slice(4, 6)}-${compact.slice(6, 8)}`
|
|
318
318
|
: '';
|
|
319
319
|
}
|
|
320
|
+
/** `resultCode` this service returns on success. */
|
|
321
|
+
const SUCCESS_RESULT_CODE$2 = 200;
|
|
322
|
+
function parseResultCode(body) {
|
|
323
|
+
try {
|
|
324
|
+
const parsed = JSON.parse(body);
|
|
325
|
+
return typeof parsed.resultCode === 'number' ? parsed.resultCode : null;
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
320
331
|
/**
|
|
321
332
|
* Serves the 알리오 target from the 재정경제부 open API.
|
|
322
333
|
*
|
|
@@ -328,7 +339,7 @@ function toIsoDate$2(compact) {
|
|
|
328
339
|
* Only postings still open (`ongoingYn=Y`) are requested.
|
|
329
340
|
*/
|
|
330
341
|
const createAlioFetch = (baseFetch = fetch, options) => {
|
|
331
|
-
const { apiKey, rowsPerPage = 3000 } = options;
|
|
342
|
+
const { apiKey, rowsPerPage = 3000, onError } = options;
|
|
332
343
|
return async (input, init) => {
|
|
333
344
|
const requestUrl = typeof input === 'string'
|
|
334
345
|
? input
|
|
@@ -350,8 +361,25 @@ const createAlioFetch = (baseFetch = fetch, options) => {
|
|
|
350
361
|
return Response.json({ result: [] });
|
|
351
362
|
}
|
|
352
363
|
if (url.pathname === '/recruit.do') {
|
|
353
|
-
|
|
364
|
+
const response = await baseFetch(`${API_BASE$2}/list?serviceKey=${apiKey}&resultType=json` +
|
|
354
365
|
`&numOfRows=${rowsPerPage}&pageNo=1&ongoingYn=Y`, init);
|
|
366
|
+
if (!response.ok) {
|
|
367
|
+
return response;
|
|
368
|
+
}
|
|
369
|
+
// Same reasoning as the 나라일터 adapter: data.go.kr answers its own
|
|
370
|
+
// errors with HTTP 200 and a result code, and passing one through turns a
|
|
371
|
+
// rejected key or an outage into a board that simply has no postings.
|
|
372
|
+
const body = await response.text();
|
|
373
|
+
const resultCode = parseResultCode(body);
|
|
374
|
+
if (resultCode !== null && resultCode !== SUCCESS_RESULT_CODE$2) {
|
|
375
|
+
const reason = `알리오 list returned resultCode ${resultCode}`;
|
|
376
|
+
onError?.(reason);
|
|
377
|
+
return new Response(reason, { status: 502, statusText: 'Bad Gateway' });
|
|
378
|
+
}
|
|
379
|
+
return new Response(body, {
|
|
380
|
+
status: 200,
|
|
381
|
+
headers: { 'content-type': 'application/json' },
|
|
382
|
+
});
|
|
355
383
|
}
|
|
356
384
|
if (url.pathname === '/recruitview.do') {
|
|
357
385
|
const serial = url.searchParams.get('idx');
|
|
@@ -798,7 +826,7 @@ const buildG2bDetailUrl = (notice, order) => `${SITE_BASE$1}/link/PNPE027_01/sin
|
|
|
798
826
|
*/
|
|
799
827
|
const OPERATIONS = ['Servc', 'Cnstwk'];
|
|
800
828
|
/** `resultCode` the service returns on success. */
|
|
801
|
-
const SUCCESS_RESULT_CODE = '00';
|
|
829
|
+
const SUCCESS_RESULT_CODE$1 = '00';
|
|
802
830
|
/** Offset the service's wall clock runs on. */
|
|
803
831
|
const KST_OFFSET_MS = 9 * 60 * 60 * 1000;
|
|
804
832
|
/**
|
|
@@ -835,7 +863,11 @@ function jsonResponse(payload) {
|
|
|
835
863
|
* daily quota of 1,000 calls is barely touched.
|
|
836
864
|
*/
|
|
837
865
|
const createG2bFetch = (baseFetch = fetch, options) => {
|
|
838
|
-
const { apiKey, windowHours = 48, rowsPerPage = 999, maxPages = 4, triage, } = options;
|
|
866
|
+
const { apiKey, windowHours = 48, rowsPerPage = 999, maxPages = 4, triage, onError, } = options;
|
|
867
|
+
const fail = (reason) => {
|
|
868
|
+
onError?.(reason);
|
|
869
|
+
return new Response(reason, { status: 502, statusText: 'Bad Gateway' });
|
|
870
|
+
};
|
|
839
871
|
/** Notices from the most recent list call, keyed by `<bidNtceNo>:<bidNtceOrd>`. */
|
|
840
872
|
const noticeCache = new Map();
|
|
841
873
|
return async (input, init) => {
|
|
@@ -885,7 +917,7 @@ const createG2bFetch = (baseFetch = fetch, options) => {
|
|
|
885
917
|
const body = (await response.json());
|
|
886
918
|
const resultCode = body.response?.header?.resultCode;
|
|
887
919
|
// data.go.kr answers its own errors with HTTP 200 and a result code.
|
|
888
|
-
if (resultCode !== SUCCESS_RESULT_CODE) {
|
|
920
|
+
if (resultCode !== SUCCESS_RESULT_CODE$1) {
|
|
889
921
|
return `나라장터 ${operation} list returned resultCode ${resultCode ?? 'none'}`;
|
|
890
922
|
}
|
|
891
923
|
const batch = body.response?.body?.items ?? [];
|
|
@@ -915,10 +947,7 @@ const createG2bFetch = (baseFetch = fetch, options) => {
|
|
|
915
947
|
}));
|
|
916
948
|
const failure = pages.find((result) => typeof result === 'string');
|
|
917
949
|
if (typeof failure === 'string') {
|
|
918
|
-
return
|
|
919
|
-
status: 502,
|
|
920
|
-
statusText: 'Bad Gateway',
|
|
921
|
-
});
|
|
950
|
+
return fail(failure);
|
|
922
951
|
}
|
|
923
952
|
const collected = pages.flat();
|
|
924
953
|
// Deduplicate before judging: the same notice arrives on more than one
|
|
@@ -1152,6 +1181,8 @@ function toIsoDate(compact) {
|
|
|
1152
1181
|
? `${compact.slice(0, 4)}-${compact.slice(4, 6)}-${compact.slice(6, 8)}`
|
|
1153
1182
|
: '';
|
|
1154
1183
|
}
|
|
1184
|
+
/** `resultCode` the service returns on success. */
|
|
1185
|
+
const SUCCESS_RESULT_CODE = '00';
|
|
1155
1186
|
/**
|
|
1156
1187
|
* Serves the 나라일터 target from the 인사혁신처 open API.
|
|
1157
1188
|
*
|
|
@@ -1164,7 +1195,7 @@ function toIsoDate(compact) {
|
|
|
1164
1195
|
* returns the announcement body directly — no HTML page is fetched.
|
|
1165
1196
|
*/
|
|
1166
1197
|
const createGojobsFetch = (baseFetch = fetch, options) => {
|
|
1167
|
-
const { apiKey, windowDays = 7, rowsPerPage = 3000 } = options;
|
|
1198
|
+
const { apiKey, windowDays = 7, rowsPerPage = 3000, onError } = options;
|
|
1168
1199
|
return async (input, init) => {
|
|
1169
1200
|
const requestUrl = typeof input === 'string'
|
|
1170
1201
|
? input
|
|
@@ -1192,8 +1223,29 @@ const createGojobsFetch = (baseFetch = fetch, options) => {
|
|
|
1192
1223
|
if (url.pathname === '/apmList.do') {
|
|
1193
1224
|
const end = new Date();
|
|
1194
1225
|
const begin = new Date(end.getTime() - windowDays * 24 * 60 * 60 * 1000);
|
|
1195
|
-
|
|
1226
|
+
const response = await baseFetch(`${API_BASE}/getList?serviceKey=${apiKey}&numOfRows=${rowsPerPage}` +
|
|
1196
1227
|
`&pageNo=1&Begin_de=${toApiDate(begin)}&End_de=${toApiDate(end)}`, init);
|
|
1228
|
+
if (!response.ok) {
|
|
1229
|
+
return response;
|
|
1230
|
+
}
|
|
1231
|
+
// data.go.kr answers its own errors with HTTP 200 and a result code, so a
|
|
1232
|
+
// rejected key or a service outage arrives looking like a successful
|
|
1233
|
+
// response with no postings in it. Passing that through made the board
|
|
1234
|
+
// report zero vacancies and the run look healthy, which is how two days
|
|
1235
|
+
// of an empty 나라일터 went unnoticed. Fail instead, the same way the
|
|
1236
|
+
// 나라장터 adapter does.
|
|
1237
|
+
const xml = await response.text();
|
|
1238
|
+
const resultCode = /<resultCode>\s*([^<]*)<\/resultCode>/.exec(xml)?.[1];
|
|
1239
|
+
if (resultCode !== undefined &&
|
|
1240
|
+
resultCode.trim() !== SUCCESS_RESULT_CODE) {
|
|
1241
|
+
const reason = `나라일터 list returned resultCode ${resultCode.trim()}`;
|
|
1242
|
+
onError?.(reason);
|
|
1243
|
+
return new Response(reason, { status: 502, statusText: 'Bad Gateway' });
|
|
1244
|
+
}
|
|
1245
|
+
return new Response(xml, {
|
|
1246
|
+
status: 200,
|
|
1247
|
+
headers: { 'content-type': 'application/xml' },
|
|
1248
|
+
});
|
|
1197
1249
|
}
|
|
1198
1250
|
if (url.pathname === '/apmView.do') {
|
|
1199
1251
|
const idx = url.searchParams.get('empmnsn');
|
|
@@ -2250,6 +2302,23 @@ const extractNttId = (html) => {
|
|
|
2250
2302
|
}
|
|
2251
2303
|
return '';
|
|
2252
2304
|
};
|
|
2305
|
+
/**
|
|
2306
|
+
* Reads a JSON body without trusting that it is JSON.
|
|
2307
|
+
*
|
|
2308
|
+
* These parsers fetch their own API through the crawling fetch, which does not
|
|
2309
|
+
* always carry the API's answer: a request the origin's robots.txt disallows is
|
|
2310
|
+
* answered locally with an empty document, and `response.json()` throws on it.
|
|
2311
|
+
* A refusal should leave the target with nothing to report, not raise a parse
|
|
2312
|
+
* error that reads like a broken parser.
|
|
2313
|
+
*/
|
|
2314
|
+
async function readJson(response) {
|
|
2315
|
+
try {
|
|
2316
|
+
return (await response.json());
|
|
2317
|
+
}
|
|
2318
|
+
catch {
|
|
2319
|
+
return null;
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2253
2322
|
/**
|
|
2254
2323
|
* Parses the list page from seamuse.go.kr (국립해양유산연구소).
|
|
2255
2324
|
* The site uses client-side rendering — list data is fetched from a JSON API.
|
|
@@ -2267,9 +2336,9 @@ const parseSeamuseList = async (_html, listDataPath, infoPathPrefix, customFetch
|
|
|
2267
2336
|
},
|
|
2268
2337
|
body,
|
|
2269
2338
|
});
|
|
2270
|
-
const data = await response
|
|
2339
|
+
const data = await readJson(response);
|
|
2271
2340
|
const posts = [];
|
|
2272
|
-
for (const item of data
|
|
2341
|
+
for (const item of data?.resultList ?? []) {
|
|
2273
2342
|
posts.push({
|
|
2274
2343
|
uniqId: String(item.nttId),
|
|
2275
2344
|
title: item.nttSj?.trim() ?? '',
|
|
@@ -2302,11 +2371,11 @@ const parseSeamuseDetail = async (html, infoDataPath, customFetch) => {
|
|
|
2302
2371
|
'Content-Type': 'application/json',
|
|
2303
2372
|
},
|
|
2304
2373
|
});
|
|
2305
|
-
const data = await response
|
|
2306
|
-
const content = data
|
|
2374
|
+
const data = await readJson(response);
|
|
2375
|
+
const content = data?.result?.nttCn ?? '';
|
|
2307
2376
|
return {
|
|
2308
2377
|
detailContent: new TurndownService().turndown(content),
|
|
2309
|
-
hasAttachedFile: (data
|
|
2378
|
+
hasAttachedFile: (data?.files?.length ?? 0) > 0,
|
|
2310
2379
|
hasAttachedImage: content.includes('<img'),
|
|
2311
2380
|
};
|
|
2312
2381
|
};
|
|
@@ -3067,6 +3136,15 @@ const maximumImportanceScoreByDomain = {
|
|
|
3067
3136
|
*/
|
|
3068
3137
|
const robotsExemptOrigins = [
|
|
3069
3138
|
'http://www.yngogo.or.kr',
|
|
3139
|
+
// data.go.kr open APIs. robots.txt governs crawlers reading a site's
|
|
3140
|
+
// documents; these are authorised API calls made with a registered service
|
|
3141
|
+
// key, and the terms that bind them are the service's own. The host does not
|
|
3142
|
+
// publish a robots.txt at all: its gateway answers any unknown path — that
|
|
3143
|
+
// one included — with HTTP 400 and `NO_OPENAPI_SERVICE_ERROR`, so every run
|
|
3144
|
+
// was spending a request to be told the file does not exist. The adapters
|
|
3145
|
+
// rewrite 나라일터/알리오/나라장터 board URLs to this origin and the gate sits
|
|
3146
|
+
// inside them, which is how an API call ended up being asked about at all.
|
|
3147
|
+
'https://apis.data.go.kr',
|
|
3070
3148
|
];
|
|
3071
3149
|
/**
|
|
3072
3150
|
* LLM configuration
|
|
@@ -4440,9 +4518,10 @@ class ContentGenerateProvider {
|
|
|
4440
4518
|
*
|
|
4441
4519
|
* Applied at the fetch layer, the same seam the KRAS and excavation-report
|
|
4442
4520
|
* adapters use, so core's crawling pipeline is untouched: a disallowed request
|
|
4443
|
-
* is answered with
|
|
4444
|
-
*
|
|
4445
|
-
*
|
|
4521
|
+
* is answered locally with an empty document instead of being sent, and the
|
|
4522
|
+
* target simply yields no articles. The refusal is reported through
|
|
4523
|
+
* `onBlocked`, not as a fetch failure — see {@link createRobotsGate} for why
|
|
4524
|
+
* that distinction matters.
|
|
4446
4525
|
*/
|
|
4447
4526
|
/**
|
|
4448
4527
|
* Parses robots.txt into user-agent groups.
|
|
@@ -4648,13 +4727,31 @@ function createRobotsGate(baseFetch = fetch, options = {}) {
|
|
|
4648
4727
|
return baseFetch(input, init);
|
|
4649
4728
|
}
|
|
4650
4729
|
onBlocked?.({ url: requestUrl, userAgent, rule: verdict.rule });
|
|
4651
|
-
|
|
4730
|
+
// An empty document rather than a 4xx. A site's crawling policy is not a
|
|
4731
|
+
// fault on our side, but core cannot tell the difference: it turns any
|
|
4732
|
+
// non-2xx list response into a thrown error and logs
|
|
4733
|
+
// `crawl.list.fetch.failed` at error level, which applications forward to
|
|
4734
|
+
// their alerting. Fourteen targets are disallowed at all times, so every
|
|
4735
|
+
// run raised fourteen alerts that no one could act on, and real fetch
|
|
4736
|
+
// failures were buried among them.
|
|
4737
|
+
//
|
|
4738
|
+
// Answering 200 with no content makes the target yield nothing, which is
|
|
4739
|
+
// what being disallowed means. The refusal is not hidden: it is still
|
|
4740
|
+
// reported through `onBlocked`, which the provider logs as
|
|
4741
|
+
// `crawl.robots.blocked`, and the health-check decides what to skip from
|
|
4742
|
+
// `isAllowed` rather than from this status. Every parser in this package
|
|
4743
|
+
// returns an empty list for this body.
|
|
4744
|
+
return new Response(`<!-- Blocked by robots.txt (${verdict.rule}) - ${new URL(requestUrl).origin}/robots.txt -->`, {
|
|
4745
|
+
status: 200,
|
|
4746
|
+
statusText: 'Blocked by robots.txt',
|
|
4747
|
+
headers: { 'content-type': 'text/html; charset=utf-8' },
|
|
4748
|
+
});
|
|
4652
4749
|
};
|
|
4653
4750
|
return { isAllowed, fetch: gatedFetch };
|
|
4654
4751
|
}
|
|
4655
4752
|
/**
|
|
4656
4753
|
* Wraps a fetch so that requests disallowed by the origin's robots.txt are
|
|
4657
|
-
*
|
|
4754
|
+
* answered with an empty document instead of sent. See {@link createRobotsGate}.
|
|
4658
4755
|
*/
|
|
4659
4756
|
function createRobotsAwareFetch(baseFetch = fetch, options = {}) {
|
|
4660
4757
|
return createRobotsGate(baseFetch, options).fetch;
|
|
@@ -4693,7 +4790,32 @@ class CrawlingProvider {
|
|
|
4693
4790
|
// The two public job boards are read from data.go.kr open APIs. Without a
|
|
4694
4791
|
// key they answer with an empty list and make no request, so the targets
|
|
4695
4792
|
// stay configured and simply collect nothing.
|
|
4696
|
-
const withPublicJobs = createG2bFetch(createAlioFetch(createGojobsFetch(withKras, {
|
|
4793
|
+
const withPublicJobs = createG2bFetch(createAlioFetch(createGojobsFetch(withKras, {
|
|
4794
|
+
apiKey: publicDataApiKey ?? '',
|
|
4795
|
+
onError: (reason) => {
|
|
4796
|
+
logger?.error({
|
|
4797
|
+
event: 'crawl.gojobs.list.failed',
|
|
4798
|
+
data: { reason },
|
|
4799
|
+
});
|
|
4800
|
+
},
|
|
4801
|
+
}), {
|
|
4802
|
+
apiKey: publicDataApiKey ?? '',
|
|
4803
|
+
onError: (reason) => {
|
|
4804
|
+
logger?.error({
|
|
4805
|
+
event: 'crawl.alio.list.failed',
|
|
4806
|
+
data: { reason },
|
|
4807
|
+
});
|
|
4808
|
+
},
|
|
4809
|
+
}), {
|
|
4810
|
+
apiKey: publicDataApiKey ?? '',
|
|
4811
|
+
triage: bidTriage,
|
|
4812
|
+
onError: (reason) => {
|
|
4813
|
+
logger?.error({
|
|
4814
|
+
event: 'crawl.g2b.list.failed',
|
|
4815
|
+
data: { reason },
|
|
4816
|
+
});
|
|
4817
|
+
},
|
|
4818
|
+
});
|
|
4697
4819
|
// When the application supplies excavation reports, that board is served
|
|
4698
4820
|
// from the injected source and never requested over the network. Every
|
|
4699
4821
|
// other target keeps going through the same fetch as before.
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@heripo/research-radar",
|
|
3
3
|
"private": false,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "5.2.
|
|
5
|
+
"version": "5.2.3",
|
|
6
6
|
"description": "AI-driven intelligence for Korean cultural heritage. This package serves as both a ready-to-use newsletter service and a practical implementation example for the LLM-Newsletter-Kit.",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|