gov-scraper: 본문 지원자격 지역제한 필터 추가

- generate_checklist.js: 본문에 '비서울 지역 + 거주/소재/관내/재학' 정방향 패턴이면 제외
- 서울/수도권/전국 포함 시 유지(서울 거주자 가능), 서울 기관 사업도 유지
- 역방향(주소+지역)은 기관 연락처 푸터 오탐이라 미검사
- apply-checklist.md: 지역(제목+주관+본문)+연령+성별/대상 → 109건

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-11 08:13:29 +00:00
parent f3587eb130
commit 7bc0464afc
2 changed files with 28 additions and 14 deletions

View File

@@ -99,6 +99,25 @@ function applicableInSeoul(x) {
if (NON_SEOUL_BODY.some((t) => x.TITLE.includes(t))) return false; // 제목 본문 도/권역 → 제거
return true; // 지역 신호 없음 → 전국 사업으로 유지
}
// 본문 지원자격에 '비서울 지역 + 거주/소재/재학/관내' 가 있으면 지역 제한으로 제외.
// 단 '수도권/서울/전국' 거주·소재면 서울 거주자도 가능 → 유지.
const RES_KW = '거주|소재|관내|재학|주소|소재지|사업장|위치';
const SEOUL_OK_RE = new RegExp(
`(수도권|서울|전국)[가-힣A-Za-z0-9\\s(),·~/]{0,18}(${RES_KW})|(${RES_KW})[가-힣A-Za-z0-9\\s(),·~/]{0,18}(수도권|서울|전국)`
);
function regionRestrictedInBody(x) {
const body = x.BODY_TEXT || '';
if (!body) return false;
// 서울 기관/사업이면(제목·주관기관에 서울/Seoul) 본문에 타지역 언급돼도 유지
if (/서울|seoul/i.test(`${x.TITLE} ${x.AGENCY || ''}`)) return false;
if (SEOUL_OK_RE.test(body)) return false; // 수도권/서울/전국 포함 → 제한 아님
// 정방향만 검사('지역 + 거주/소재/관내'). 역방향('주소 + 지역')은 기관 연락처 푸터 오탐이라 제외.
for (const t of NON_SEOUL) {
if (new RegExp(`${t}[가-힣A-Za-z0-9\\s(),·~/]{0,8}(${RES_KW})`).test(body)) return true;
}
return false;
}
function ymd(d) {
return d ? new Date(d).toISOString().slice(0, 10) : '';
}
@@ -120,9 +139,12 @@ const allRows = await withConnection(async (conn) => {
});
await closePool();
// 서울 거주 기준: 타 지역 한정 공고 제외
const seoulRows = allRows.filter(applicableInSeoul);
// 서울 거주 기준: 타 지역 한정 공고 제외 (제목/주관기관 + 본문 지원자격)
const afterTitleRegion = allRows.filter(applicableInSeoul);
const seoulRows = afterTitleRegion.filter((x) => !regionRestrictedInBody(x));
const removedRegion = allRows.length - seoulRows.length;
const removedByBody = afterTitleRegion.length - seoulRows.length;
console.log(` (그중 본문 지원자격 지역제한: ${removedByBody}건)`);
// 연령(46세) 기준: 청년 한정 등 연령 초과 공고 제외
const ageRows = seoulRows.filter(ageAllows);
const removedAge = seoulRows.length - ageRows.length;