@heripo/research-radar 5.2.2 → 5.2.4
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 +187 -29
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -300,6 +300,36 @@ const createHeritageBidTriage = (options) => {
|
|
|
300
300
|
};
|
|
301
301
|
};
|
|
302
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Reads what data.go.kr said when it refused a request.
|
|
305
|
+
*
|
|
306
|
+
* Its gateway answers a refusal with HTTP 400 and an XML envelope, whatever
|
|
307
|
+
* format the service itself speaks. The envelope names the cause — a spent
|
|
308
|
+
* daily quota, an unregistered key, a withdrawn service and a denied caller
|
|
309
|
+
* are all different `returnReasonCode`s — but an adapter that reports only the
|
|
310
|
+
* status throws that away.
|
|
311
|
+
*
|
|
312
|
+
* It cost a week to learn that. A nightly run failed at the same minute for
|
|
313
|
+
* days with nothing in the log but `Request failed (status=502)`, and the
|
|
314
|
+
* reason the gateway had supplied each time was discarded before anyone could
|
|
315
|
+
* read it.
|
|
316
|
+
*/
|
|
317
|
+
function describeOpenDataError(body) {
|
|
318
|
+
const read = (tag) => new RegExp(`<${tag}>\\s*([^<]*)</${tag}>`).exec(body)?.[1]?.trim();
|
|
319
|
+
const parts = [
|
|
320
|
+
read('errMsg'),
|
|
321
|
+
read('returnReasonCode') && `code ${read('returnReasonCode')}`,
|
|
322
|
+
read('returnAuthMsg'),
|
|
323
|
+
].filter((part) => Boolean(part));
|
|
324
|
+
if (parts.length > 0) {
|
|
325
|
+
return parts.join(' / ');
|
|
326
|
+
}
|
|
327
|
+
// Not the documented envelope. A short body is still worth quoting; a long
|
|
328
|
+
// one is a page rather than an error and only adds noise to the log.
|
|
329
|
+
const trimmed = body.trim().replace(/\s+/g, ' ');
|
|
330
|
+
return trimmed.length > 0 && trimmed.length <= 200 ? trimmed : null;
|
|
331
|
+
}
|
|
332
|
+
|
|
303
333
|
const API_BASE$2 = 'https://apis.data.go.kr/1051000/recruitment';
|
|
304
334
|
const SITE_BASE$2 = 'https://job.alio.go.kr';
|
|
305
335
|
/**
|
|
@@ -317,6 +347,17 @@ function toIsoDate$2(compact) {
|
|
|
317
347
|
? `${compact.slice(0, 4)}-${compact.slice(4, 6)}-${compact.slice(6, 8)}`
|
|
318
348
|
: '';
|
|
319
349
|
}
|
|
350
|
+
/** `resultCode` this service returns on success. */
|
|
351
|
+
const SUCCESS_RESULT_CODE$2 = 200;
|
|
352
|
+
function parseResultCode(body) {
|
|
353
|
+
try {
|
|
354
|
+
const parsed = JSON.parse(body);
|
|
355
|
+
return typeof parsed.resultCode === 'number' ? parsed.resultCode : null;
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
320
361
|
/**
|
|
321
362
|
* Serves the 알리오 target from the 재정경제부 open API.
|
|
322
363
|
*
|
|
@@ -328,7 +369,7 @@ function toIsoDate$2(compact) {
|
|
|
328
369
|
* Only postings still open (`ongoingYn=Y`) are requested.
|
|
329
370
|
*/
|
|
330
371
|
const createAlioFetch = (baseFetch = fetch, options) => {
|
|
331
|
-
const { apiKey, rowsPerPage = 3000 } = options;
|
|
372
|
+
const { apiKey, rowsPerPage = 3000, onError } = options;
|
|
332
373
|
return async (input, init) => {
|
|
333
374
|
const requestUrl = typeof input === 'string'
|
|
334
375
|
? input
|
|
@@ -350,8 +391,33 @@ const createAlioFetch = (baseFetch = fetch, options) => {
|
|
|
350
391
|
return Response.json({ result: [] });
|
|
351
392
|
}
|
|
352
393
|
if (url.pathname === '/recruit.do') {
|
|
353
|
-
|
|
394
|
+
const response = await baseFetch(`${API_BASE$2}/list?serviceKey=${apiKey}&resultType=json` +
|
|
354
395
|
`&numOfRows=${rowsPerPage}&pageNo=1&ongoingYn=Y`, init);
|
|
396
|
+
if (!response.ok) {
|
|
397
|
+
// See the 나라일터 adapter: keep the upstream status, report the reason.
|
|
398
|
+
const body = await response.text();
|
|
399
|
+
const detail = describeOpenDataError(body);
|
|
400
|
+
onError?.(`알리오 list failed with HTTP ${response.status}` +
|
|
401
|
+
(detail ? ` — ${detail}` : ''));
|
|
402
|
+
return new Response(body, {
|
|
403
|
+
status: response.status,
|
|
404
|
+
statusText: response.statusText,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
// Same reasoning as the 나라일터 adapter: data.go.kr answers its own
|
|
408
|
+
// errors with HTTP 200 and a result code, and passing one through turns a
|
|
409
|
+
// rejected key or an outage into a board that simply has no postings.
|
|
410
|
+
const body = await response.text();
|
|
411
|
+
const resultCode = parseResultCode(body);
|
|
412
|
+
if (resultCode !== null && resultCode !== SUCCESS_RESULT_CODE$2) {
|
|
413
|
+
const reason = `알리오 list returned resultCode ${resultCode}`;
|
|
414
|
+
onError?.(reason);
|
|
415
|
+
return new Response(reason, { status: 502, statusText: 'Bad Gateway' });
|
|
416
|
+
}
|
|
417
|
+
return new Response(body, {
|
|
418
|
+
status: 200,
|
|
419
|
+
headers: { 'content-type': 'application/json' },
|
|
420
|
+
});
|
|
355
421
|
}
|
|
356
422
|
if (url.pathname === '/recruitview.do') {
|
|
357
423
|
const serial = url.searchParams.get('idx');
|
|
@@ -798,7 +864,7 @@ const buildG2bDetailUrl = (notice, order) => `${SITE_BASE$1}/link/PNPE027_01/sin
|
|
|
798
864
|
*/
|
|
799
865
|
const OPERATIONS = ['Servc', 'Cnstwk'];
|
|
800
866
|
/** `resultCode` the service returns on success. */
|
|
801
|
-
const SUCCESS_RESULT_CODE = '00';
|
|
867
|
+
const SUCCESS_RESULT_CODE$1 = '00';
|
|
802
868
|
/** Offset the service's wall clock runs on. */
|
|
803
869
|
const KST_OFFSET_MS = 9 * 60 * 60 * 1000;
|
|
804
870
|
/**
|
|
@@ -835,7 +901,11 @@ function jsonResponse(payload) {
|
|
|
835
901
|
* daily quota of 1,000 calls is barely touched.
|
|
836
902
|
*/
|
|
837
903
|
const createG2bFetch = (baseFetch = fetch, options) => {
|
|
838
|
-
const { apiKey, windowHours = 48, rowsPerPage = 999, maxPages = 4, triage, } = options;
|
|
904
|
+
const { apiKey, windowHours = 48, rowsPerPage = 999, maxPages = 4, triage, onError, } = options;
|
|
905
|
+
const fail = (reason) => {
|
|
906
|
+
onError?.(reason);
|
|
907
|
+
return new Response(reason, { status: 502, statusText: 'Bad Gateway' });
|
|
908
|
+
};
|
|
839
909
|
/** Notices from the most recent list call, keyed by `<bidNtceNo>:<bidNtceOrd>`. */
|
|
840
910
|
const noticeCache = new Map();
|
|
841
911
|
return async (input, init) => {
|
|
@@ -880,12 +950,14 @@ const createG2bFetch = (baseFetch = fetch, options) => {
|
|
|
880
950
|
`&numOfRows=${rowsPerPage}&pageNo=${page}&type=json&inqryDiv=1` +
|
|
881
951
|
`&inqryBgnDt=${toApiDateTime(begin)}&inqryEndDt=${toApiDateTime(end)}`, init);
|
|
882
952
|
if (!response.ok) {
|
|
883
|
-
|
|
953
|
+
const detail = describeOpenDataError(await response.text());
|
|
954
|
+
return (`나라장터 ${operation} list failed with HTTP ${response.status}` +
|
|
955
|
+
(detail ? ` — ${detail}` : ''));
|
|
884
956
|
}
|
|
885
957
|
const body = (await response.json());
|
|
886
958
|
const resultCode = body.response?.header?.resultCode;
|
|
887
959
|
// data.go.kr answers its own errors with HTTP 200 and a result code.
|
|
888
|
-
if (resultCode !== SUCCESS_RESULT_CODE) {
|
|
960
|
+
if (resultCode !== SUCCESS_RESULT_CODE$1) {
|
|
889
961
|
return `나라장터 ${operation} list returned resultCode ${resultCode ?? 'none'}`;
|
|
890
962
|
}
|
|
891
963
|
const batch = body.response?.body?.items ?? [];
|
|
@@ -915,10 +987,7 @@ const createG2bFetch = (baseFetch = fetch, options) => {
|
|
|
915
987
|
}));
|
|
916
988
|
const failure = pages.find((result) => typeof result === 'string');
|
|
917
989
|
if (typeof failure === 'string') {
|
|
918
|
-
return
|
|
919
|
-
status: 502,
|
|
920
|
-
statusText: 'Bad Gateway',
|
|
921
|
-
});
|
|
990
|
+
return fail(failure);
|
|
922
991
|
}
|
|
923
992
|
const collected = pages.flat();
|
|
924
993
|
// Deduplicate before judging: the same notice arrives on more than one
|
|
@@ -1152,6 +1221,8 @@ function toIsoDate(compact) {
|
|
|
1152
1221
|
? `${compact.slice(0, 4)}-${compact.slice(4, 6)}-${compact.slice(6, 8)}`
|
|
1153
1222
|
: '';
|
|
1154
1223
|
}
|
|
1224
|
+
/** `resultCode` the service returns on success. */
|
|
1225
|
+
const SUCCESS_RESULT_CODE = '00';
|
|
1155
1226
|
/**
|
|
1156
1227
|
* Serves the 나라일터 target from the 인사혁신처 open API.
|
|
1157
1228
|
*
|
|
@@ -1164,7 +1235,7 @@ function toIsoDate(compact) {
|
|
|
1164
1235
|
* returns the announcement body directly — no HTML page is fetched.
|
|
1165
1236
|
*/
|
|
1166
1237
|
const createGojobsFetch = (baseFetch = fetch, options) => {
|
|
1167
|
-
const { apiKey, windowDays = 7, rowsPerPage = 3000 } = options;
|
|
1238
|
+
const { apiKey, windowDays = 7, rowsPerPage = 3000, onError } = options;
|
|
1168
1239
|
return async (input, init) => {
|
|
1169
1240
|
const requestUrl = typeof input === 'string'
|
|
1170
1241
|
? input
|
|
@@ -1192,8 +1263,39 @@ const createGojobsFetch = (baseFetch = fetch, options) => {
|
|
|
1192
1263
|
if (url.pathname === '/apmList.do') {
|
|
1193
1264
|
const end = new Date();
|
|
1194
1265
|
const begin = new Date(end.getTime() - windowDays * 24 * 60 * 60 * 1000);
|
|
1195
|
-
|
|
1266
|
+
const response = await baseFetch(`${API_BASE}/getList?serviceKey=${apiKey}&numOfRows=${rowsPerPage}` +
|
|
1196
1267
|
`&pageNo=1&Begin_de=${toApiDate(begin)}&End_de=${toApiDate(end)}`, init);
|
|
1268
|
+
if (!response.ok) {
|
|
1269
|
+
// Core logs the status; the gateway's own explanation is in the body and
|
|
1270
|
+
// would otherwise be dropped. Keep the status so the log still shows what
|
|
1271
|
+
// the service answered, and report the reason alongside it.
|
|
1272
|
+
const body = await response.text();
|
|
1273
|
+
const detail = describeOpenDataError(body);
|
|
1274
|
+
onError?.(`나라일터 list failed with HTTP ${response.status}` +
|
|
1275
|
+
(detail ? ` — ${detail}` : ''));
|
|
1276
|
+
return new Response(body, {
|
|
1277
|
+
status: response.status,
|
|
1278
|
+
statusText: response.statusText,
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
// data.go.kr answers its own errors with HTTP 200 and a result code, so a
|
|
1282
|
+
// rejected key or a service outage arrives looking like a successful
|
|
1283
|
+
// response with no postings in it. Passing that through made the board
|
|
1284
|
+
// report zero vacancies and the run look healthy, which is how two days
|
|
1285
|
+
// of an empty 나라일터 went unnoticed. Fail instead, the same way the
|
|
1286
|
+
// 나라장터 adapter does.
|
|
1287
|
+
const xml = await response.text();
|
|
1288
|
+
const resultCode = /<resultCode>\s*([^<]*)<\/resultCode>/.exec(xml)?.[1];
|
|
1289
|
+
if (resultCode !== undefined &&
|
|
1290
|
+
resultCode.trim() !== SUCCESS_RESULT_CODE) {
|
|
1291
|
+
const reason = `나라일터 list returned resultCode ${resultCode.trim()}`;
|
|
1292
|
+
onError?.(reason);
|
|
1293
|
+
return new Response(reason, { status: 502, statusText: 'Bad Gateway' });
|
|
1294
|
+
}
|
|
1295
|
+
return new Response(xml, {
|
|
1296
|
+
status: 200,
|
|
1297
|
+
headers: { 'content-type': 'application/xml' },
|
|
1298
|
+
});
|
|
1197
1299
|
}
|
|
1198
1300
|
if (url.pathname === '/apmView.do') {
|
|
1199
1301
|
const idx = url.searchParams.get('empmnsn');
|
|
@@ -3084,6 +3186,15 @@ const maximumImportanceScoreByDomain = {
|
|
|
3084
3186
|
*/
|
|
3085
3187
|
const robotsExemptOrigins = [
|
|
3086
3188
|
'http://www.yngogo.or.kr',
|
|
3189
|
+
// data.go.kr open APIs. robots.txt governs crawlers reading a site's
|
|
3190
|
+
// documents; these are authorised API calls made with a registered service
|
|
3191
|
+
// key, and the terms that bind them are the service's own. The host does not
|
|
3192
|
+
// publish a robots.txt at all: its gateway answers any unknown path — that
|
|
3193
|
+
// one included — with HTTP 400 and `NO_OPENAPI_SERVICE_ERROR`, so every run
|
|
3194
|
+
// was spending a request to be told the file does not exist. The adapters
|
|
3195
|
+
// rewrite 나라일터/알리오/나라장터 board URLs to this origin and the gate sits
|
|
3196
|
+
// inside them, which is how an API call ended up being asked about at all.
|
|
3197
|
+
'https://apis.data.go.kr',
|
|
3087
3198
|
];
|
|
3088
3199
|
/**
|
|
3089
3200
|
* LLM configuration
|
|
@@ -3203,22 +3314,44 @@ const SUBJECT_OVER_INSTITUTION = `## 발주·공고 기관이 아니라 사업
|
|
|
3203
3314
|
|
|
3204
3315
|
유산 기관이 낸 공고라도 **내용이 유산 업무가 아니면 낮게 준다.** 기관명은 단서일 뿐 근거가 아니다.
|
|
3205
3316
|
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3317
|
+
**유산 업무 — 정상 채점**
|
|
3318
|
+
|
|
3319
|
+
- 발굴·시굴조사, 매장유산, 문화재 수리·보수정비, 보존처리, 기록화, 유적 정비
|
|
3320
|
+
- 학술연구, 유물 조사·분석
|
|
3321
|
+
- 전시·해설의 기획과 **콘텐츠 개발** — 무엇을 보여주고 어떻게 설명할지 정하는 일
|
|
3322
|
+
(예: "달천철장 홍보관 무장애 관광 콘텐츠 개발 용역")
|
|
3323
|
+
- **유산 정보를 다루는 데이터베이스·아카이브·디지털화의 구축과 유지보수**
|
|
3324
|
+
(예: "B-헤리티지 웹 아카이빙 플랫폼 구축", "국외문화유산DB관리시스템 유지관리",
|
|
3325
|
+
"AI 학습데이터 구축 기반 아카이브 자료 디지털화")
|
|
3326
|
+
- **유산 교육 프로그램의 기획·운영** (예: "평창올림픽 유산교육 프로그램 기획·운영 대행용역")
|
|
3327
|
+
|
|
3328
|
+
**유산 업무가 아닌 것 → 발주처가 국가유산청·국립박물관이어도 1점**
|
|
3329
|
+
|
|
3330
|
+
- 소방·전기·냉난방·창호·정보통신 같은 건물 설비와 그 유지보수, 청사 환경개선
|
|
3331
|
+
- **구조안전진단**, 건물 철거와 폐기물 처리
|
|
3332
|
+
- 기관 홈페이지, 사무용 전산 시스템
|
|
3333
|
+
- 차량 임차, **운송·운반** (예: "국내 주요 화석산지 표본 헬기 운반")
|
|
3334
|
+
- 홍보물·영상 제작
|
|
3335
|
+
- **전시를 물리적으로 만들고 옮기고 설치하는 일** — 전시설계, 전시물·실감영상 제작·설치,
|
|
3336
|
+
전시품 포장·운송 (예: "상설전시실3 실감영상 제작·설치", "특별전 전시품 포장, 운송 및 설치")
|
|
3337
|
+
- 축제·행사의 운영과 그 부대 용역(무대, 홍보, 교통·안전관리)
|
|
3338
|
+
|
|
3339
|
+
발주처가 유산 기관이라는 이유로 2-3점으로 올리지 않는다. 유산 업무가 아니면 1점이다.
|
|
3340
|
+
|
|
3341
|
+
**갈리는 지점 세 가지**
|
|
3342
|
+
|
|
3343
|
+
1. **전산**: 다루는 대상이 무엇인지로 가른다. 유산 자료를 담고 보여주고 보존하는
|
|
3344
|
+
시스템이면 유산 업무이고, 기관을 운영하기 위한 전산이면 아니다.
|
|
3345
|
+
2. **전시**: 무엇을 보여줄지 정하는 일은 유산 업무이고, 그것을 만들고 옮기고 설치하는
|
|
3346
|
+
일은 아니다.
|
|
3347
|
+
3. **프로그램**: 유산을 가르치고 해설하는 프로그램은 유산 업무이고, 축제·기념행사를
|
|
3348
|
+
진행하는 일은 아니다.
|
|
3349
|
+
|
|
3350
|
+
그 밖의 예: "창덕궁 미분무 소화설비 성능개선 공사"는 궁궐 소재지만 소방 설비 공사다.
|
|
3351
|
+
"국립항공박물관 정보통신설비 유지보수"도 건물 설비다. 반대로 "팔만대장경 P-XRF 분석"은
|
|
3352
|
+
발주처가 사찰이어도 명백한 유산 조사이고, "안양암 아미타괘불도 보존처리",
|
|
3353
|
+
"중요동산문화유산 기록화 3D 스캐닝", "소장유물 복제", "소장품도록 발간"은
|
|
3354
|
+
유물을 직접 다루므로 정상 채점한다.`;
|
|
3222
3355
|
const EMPLOYMENT_FILTER = `## 채용 공고 판별
|
|
3223
3356
|
|
|
3224
3357
|
채용 공고는 **직무가 아래 지원 직무 목록에 해당하는지**를 먼저 본다.
|
|
@@ -4729,7 +4862,32 @@ class CrawlingProvider {
|
|
|
4729
4862
|
// The two public job boards are read from data.go.kr open APIs. Without a
|
|
4730
4863
|
// key they answer with an empty list and make no request, so the targets
|
|
4731
4864
|
// stay configured and simply collect nothing.
|
|
4732
|
-
const withPublicJobs = createG2bFetch(createAlioFetch(createGojobsFetch(withKras, {
|
|
4865
|
+
const withPublicJobs = createG2bFetch(createAlioFetch(createGojobsFetch(withKras, {
|
|
4866
|
+
apiKey: publicDataApiKey ?? '',
|
|
4867
|
+
onError: (reason) => {
|
|
4868
|
+
logger?.error({
|
|
4869
|
+
event: 'crawl.gojobs.list.failed',
|
|
4870
|
+
data: { reason },
|
|
4871
|
+
});
|
|
4872
|
+
},
|
|
4873
|
+
}), {
|
|
4874
|
+
apiKey: publicDataApiKey ?? '',
|
|
4875
|
+
onError: (reason) => {
|
|
4876
|
+
logger?.error({
|
|
4877
|
+
event: 'crawl.alio.list.failed',
|
|
4878
|
+
data: { reason },
|
|
4879
|
+
});
|
|
4880
|
+
},
|
|
4881
|
+
}), {
|
|
4882
|
+
apiKey: publicDataApiKey ?? '',
|
|
4883
|
+
triage: bidTriage,
|
|
4884
|
+
onError: (reason) => {
|
|
4885
|
+
logger?.error({
|
|
4886
|
+
event: 'crawl.g2b.list.failed',
|
|
4887
|
+
data: { reason },
|
|
4888
|
+
});
|
|
4889
|
+
},
|
|
4890
|
+
});
|
|
4733
4891
|
// When the application supplies excavation reports, that board is served
|
|
4734
4892
|
// from the injected source and never requested over the network. Every
|
|
4735
4893
|
// 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.4",
|
|
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",
|