From 33d7054265b89e3ff35c7653c834b12514ce7fa9 Mon Sep 17 00:00:00 2001 From: chaeeunn4 Date: Tue, 21 Jul 2026 15:52:08 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=EC=84=B8?= =?UTF-8?q?=EC=8A=A43=EA=B0=9C=20=EA=B4=80=EB=A0=A8=20=EC=88=98=EC=A0=9C?= =?UTF-8?q?=20=EC=84=A0=EB=B3=84=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EB=B0=8F=20requirement=5Fid=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/fowoco_knowledge/curated_ingestion.py | 219 ++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 fowoco-knowledge/src/fowoco_knowledge/curated_ingestion.py diff --git a/fowoco-knowledge/src/fowoco_knowledge/curated_ingestion.py b/fowoco-knowledge/src/fowoco_knowledge/curated_ingestion.py new file mode 100644 index 0000000..34d705e --- /dev/null +++ b/fowoco-knowledge/src/fowoco_knowledge/curated_ingestion.py @@ -0,0 +1,219 @@ +""" +fowoco-knowledge/data/curated/required_documents_manual_raw를 읽어 +requirement_id를 ingestion.py 의 stable_id/_clean 과 동일한 규칙으로 채운 뒤 +data/processed/required_documents_manual.csv 로 생성(재실행 시 overwrite)하는 코드입니다. + +required_documents_manual_raw.csv 파일은 사람이 직접 작성하는 파일이며 required_id만 비워둔 형태입니다. + +""" + +from __future__ import annotations + +import argparse +import csv +from datetime import date +from pathlib import Path +from .ingestion import _clean, stable_id + +# --- 고정 설정 ------------------------------------------------------------- +# 이 파일 위치: /src/fowoco_knowledge/curated_ingestion.py +PROJECT_ROOT = Path(__file__).resolve().parents[2] +INPUT_PATH = PROJECT_ROOT / "data" / "curated" / "required_documents_manual_raw.csv" +OUTPUT_PATH = PROJECT_ROOT / "data" / "processed" / "required_documents_manual.csv" +SOURCE_ID = "SRC-CURATED-REQUIRED-DOCS" +# ---------------------------------------------------------------------------- + + + # 생성될.csv의 최종 스키마. +FIELDNAMES = [ + "requirement_id", + "application_name", + "document_name", + "applicable_scope", + "requirement_marker", + "sample_form_available", + "source_industry_text", + "source_id", + "source_version", + "is_essential", + "application_id", +] + + +def _sniff_delimiter(input_path: Path) -> str: + with input_path.open("r", encoding="utf-8-sig", newline="") as handle: + sample = handle.readline() + try: + return csv.Sniffer().sniff(sample, delimiters=",\t").delimiter + except csv.Error: + return "," + + +def _load_rows(input_path: Path) -> list[dict[str, str]]: + delimiter = _sniff_delimiter(input_path) + with input_path.open("r", encoding="utf-8-sig", newline="") as handle: + reader = csv.DictReader(handle, delimiter=delimiter) + rows = [] + for row in reader: + # key(컬럼명)와 value(데이터) 양쪽의 줄바꿈/공백을 모두 정제 + cleaned_row = { + _clean(k): _clean(v) + for k, v in row.items() + if k is not None + } + if any(cleaned_row.values()): + rows.append(cleaned_row) + return rows + + +def _norm_scope(applicable_scope: str, industry_text: str) -> str: + """applicable_scope 컬럼이 비어있는 경우를 대비해 industry_text로 보정.""" + scope = _clean(applicable_scope).upper() + if scope in {"ALL_INDUSTRIES", "MANUFACTURING"}: + return scope + return "ALL_INDUSTRIES" if _clean(industry_text) == "전업종" else "MANUFACTURING" + + +def _norm_sample_form_available(value: str | None, *, line_no: int) -> str: + """sample_form_available은 대문자 TRUE/FALSE 표기를 그대로 유지한다 (값 변환 없음).""" + v = _clean(value).upper() + if v not in {"TRUE", "FALSE"}: + raise ValueError( + f"{line_no}행: sample_form_available 값은 TRUE/FALSE여야 합니다 (입력값: '{value}')" + ) + return v + + +def _norm_is_essential(value: str | None, *, line_no: int) -> str: + """is_essential은 Y/N 표기를 그대로 유지한다 (값 변환 없음).""" + v = _clean(value).upper() + if v not in {"Y", "N"}: + raise ValueError( + f"{line_no}행: is_essential 값은 Y/N이어야 합니다 (입력값: '{value}')" + ) + return v + + +def _load_existing_output(output_path: Path) -> dict[str, dict[str, str]]: + """이전에 생성된 processed csv를 읽어 requirement_id -> 이전 행 매핑을 만든다. + 파일이 없으면(최초 실행) 빈 딕셔너리를 반환한다.""" + if not output_path.exists(): + return {} + with output_path.open("r", encoding="utf-8-sig", newline="") as handle: + reader = csv.DictReader(handle) + return {row["requirement_id"]: row for row in reader if row.get("requirement_id")} + + +def _unchanged_except_version(new_row: dict[str, str], old_row: dict[str, str]) -> bool: + """source_version을 제외한 모든 필드가 이전 행과 동일한지 확인.""" + compare_fields = [f for f in FIELDNAMES if f != "source_version"] + return all(new_row.get(f, "") == old_row.get(f, "") for f in compare_fields) + + +def generate( + input_path: Path = INPUT_PATH, + output_path: Path = OUTPUT_PATH, + *, + source_id: str = SOURCE_ID, + today_version: str | None = None, +) -> list[dict[str, str]]: + """ + 각 행의 source_version은 "그 행이 마지막으로 신규 추가되거나 내용이 바뀐 날짜"를 의미한다. + - 이전 processed 결과에 동일한 requirement_id + 동일한 나머지 값이 있으면 -> 이전 날짜 유지 + - 신규 행이거나 값이 하나라도 바뀌었으면 -> 오늘 날짜(YYYYMMDD)로 갱신 + today_version을 None으로 두면 실행 시점의 오늘 날짜를 자동으로 쓴다. + 테스트에서 날짜를 고정하고 싶을 때만 명시적으로 넘기면 된다. + """ + resolved_today = today_version or date.today().strftime("%Y%m%d") + existing_by_id = _load_existing_output(output_path) + + rows = _load_rows(input_path) + + output_rows: list[dict[str, str]] = [] + seen_ids: dict[str, tuple[str, str]] = {} # requirement_id -> (application_name, document_name) + app_id_by_name: dict[str, str] = {} # application_name -> application_id (일관성 검사용) + + for line_no, row in enumerate(rows, start=2): # 헤더가 1행이므로 데이터는 2행부터 + application_name = _clean(row.get("application_name")) + document_name = _clean(row.get("document_name")) + source_industry_text = _clean(row.get("source_industry_text")) + application_id = _clean(row.get("application_id")) + + if not application_name or not document_name: + raise ValueError(f"{line_no}행: application_name/document_name이 비어있습니다.") + + if application_id: + prev_app_id = app_id_by_name.get(application_name) + if prev_app_id is None: + app_id_by_name[application_name] = application_id + elif prev_app_id != application_id: + raise ValueError( + f"{line_no}행: '{application_name}'의 application_id가 일관되지 않습니다 " + f"(기존: {prev_app_id}, 신규: {application_id})" + ) + + # ingestion.py의 transform_required_documents()와 완전히 동일한 규칙. + # application_id는 해시 입력에서 제외 (공식데이터와 똑같이) + requirement_id = stable_id( + "REQ", application_name, document_name, source_industry_text + ) + + if requirement_id in seen_ids: + prev_app, prev_doc = seen_ids[requirement_id] + if prev_app != application_name or prev_doc != document_name: + raise ValueError( + f"{line_no}행: 해시 충돌 의심 - {requirement_id} 가 서로 다른 항목에서 재사용됨 " + f"(기존: {prev_app}/{prev_doc}, 신규: {application_name}/{document_name})" + ) + continue # 완전 동일한 행 중복 -> 제거 + seen_ids[requirement_id] = (application_name, document_name) + + candidate_row = { + "requirement_id": requirement_id, + "application_name": application_name, + "document_name": document_name, + "applicable_scope": _norm_scope( + row.get("applicable_scope", ""), source_industry_text + ), + "requirement_marker": _clean(row.get("requirement_marker")) + or "OFFICIAL_CONFIRMATION_REQUIRED", + "sample_form_available": _norm_sample_form_available( + row.get("sample_form_available"), line_no=line_no + ), + "source_industry_text": source_industry_text, + "source_id": source_id, + "source_version": "", # 아래에서 신규/변경 여부 판단 후 채운다 + "is_essential": _norm_is_essential( + row.get("is_essential"), line_no=line_no + ), + "application_id": application_id, + } + + old_row = existing_by_id.get(requirement_id) + if old_row is not None and _unchanged_except_version(candidate_row, old_row): + candidate_row["source_version"] = old_row["source_version"] + else: + candidate_row["source_version"] = resolved_today + + output_rows.append(candidate_row) + + output_rows.sort(key=lambda item: (item["application_name"], item["document_name"])) + + output_path.parent.mkdir(parents=True, exist_ok=True) + with output_path.open("w", encoding="utf-8", newline="") as handle: + writer = csv.DictWriter(handle, fieldnames=FIELDNAMES, lineterminator="\n") + writer.writeheader() + writer.writerows(output_rows) + + return output_rows + + +def main() -> None: + if not INPUT_PATH.exists(): + raise FileNotFoundError(f"curated 원본 파일을 찾을 수 없습니다: {INPUT_PATH}") + rows = generate() + print(f"완료: {len(rows)}행 -> {OUTPUT_PATH}") + + +if __name__ == "__main__": + main() From 5e0b15d7118997a5d15c6657cd1e4f8b2f31f365 Mon Sep 17 00:00:00 2001 From: chaeeunn4 Date: Tue, 21 Jul 2026 16:05:02 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat(data):=20=EC=88=98=EC=A0=9C=EC=84=A0?= =?UTF-8?q?=EB=B3=84=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A6=AC=ED=80=98=EC=8A=A4=ED=8A=B8id=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../curated/required_documents_manual_raw.csv | 31 +++++++++++++++++++ .../processed/required_documents_manual.csv | 30 ++++++++++++++++++ .../data/processed/submission_path.csv | 5 +++ 3 files changed, 66 insertions(+) create mode 100644 fowoco-knowledge/data/curated/required_documents_manual_raw.csv create mode 100644 fowoco-knowledge/data/processed/required_documents_manual.csv create mode 100644 fowoco-knowledge/data/processed/submission_path.csv diff --git a/fowoco-knowledge/data/curated/required_documents_manual_raw.csv b/fowoco-knowledge/data/curated/required_documents_manual_raw.csv new file mode 100644 index 0000000..c9955e8 --- /dev/null +++ b/fowoco-knowledge/data/curated/required_documents_manual_raw.csv @@ -0,0 +1,31 @@ +requirement_id,application_name,document_name,applicable_scope,requirement_marker,sample_form_available,source_industry_text,source_id,source_version," +is_essential",application_id +,외국인 등록증 발급,외국인 직업 신고서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 +,외국인 등록증 발급,여권,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 +,외국인 등록증 발급,여권용 사진,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 +,외국인 등록증 발급,사업자등록증,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 +,외국인 등록증 발급,표준근로계약서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 +,외국인 등록증 발급,마약검사확인서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,N,p1 +,외국인 등록증 발급,거주숙소제공확인서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 +,외국인 등록증 발급,숙소제공자신분증,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 +,외국인 등록증 발급,임대차계약서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 +,외국인 고용변동 등 신고,외국인근로자고용변동등신고서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p2 +,취업기간만료자 취업 활동기간 연장신청,취업기간 만료자 취업활동 기간연장신청서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p3 +,취업기간만료자 취업 활동기간 연장신청,표준근로계약서 ,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p3 +,취업기간만료자 취업 활동기간 연장신청,외국인등록증,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p3 +,취업기간만료자 취업 활동기간 연장신청,여권,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p3 +,취업기간만료자 취업 활동기간 연장신청,사업자등록증,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p3 +,취업기간만료자 취업 활동기간 연장신청,본사-지사간 근무중 외국인력현황표,MANUFACTURING,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,제조업,SRC-CURATED-REQUIRED-DOCS,20260721,N,p3 +,취업기간만료자 취업 활동기간 연장신청,공장등록증,MANUFACTURING,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,제조업,SRC-CURATED-REQUIRED-DOCS,20260721,N,p3 +,취업기간만료자 취업 활동기간 연장신청,결산서,MANUFACTURING,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,제조업,SRC-CURATED-REQUIRED-DOCS,20260721,N,p3 +,취업기간만료자 취업 활동기간 연장신청,원가계약서,MANUFACTURING,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,제조업,SRC-CURATED-REQUIRED-DOCS,20260721,N,p3 +,취업기간만료자 취업 활동기간 연장신청,매입매출 신고서류,MANUFACTURING,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,제조업,SRC-CURATED-REQUIRED-DOCS,20260721,N,p3 +,취업기간만료자 취업 활동기간 연장신청,민원대리인 신청서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,N,p3 +,취업기간만료자 취업 활동기간 연장신청,사업주도장,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,N,p3 +,체류기간연장허가,취업기간연장확인서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 +,체류기간연장허가,통합신청서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 +,체류기간연장허가,여권,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 +,체류기간연장허가,외국인 등록증,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 +,체류기간연장허가,거주숙소제공 확인서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 +,체류기간연장허가,숙소제공자의 신분증,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 +,체류기간연장허가,임대차계약서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 \ No newline at end of file diff --git a/fowoco-knowledge/data/processed/required_documents_manual.csv b/fowoco-knowledge/data/processed/required_documents_manual.csv new file mode 100644 index 0000000..5b623de --- /dev/null +++ b/fowoco-knowledge/data/processed/required_documents_manual.csv @@ -0,0 +1,30 @@ +requirement_id,application_name,document_name,applicable_scope,requirement_marker,sample_form_available,source_industry_text,source_id,source_version,is_essential,application_id +REQ-E1B320A0C1CD,외국인 고용변동 등 신고,외국인근로자고용변동등신고서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p2 +REQ-25784E489DD3,외국인 등록증 발급,거주숙소제공확인서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 +REQ-8B50566A5400,외국인 등록증 발급,마약검사확인서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,N,p1 +REQ-BC9C6FEC2846,외국인 등록증 발급,사업자등록증,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 +REQ-35FEF0B5C1BE,외국인 등록증 발급,숙소제공자신분증,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 +REQ-DC5CDF62CE8B,외국인 등록증 발급,여권,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 +REQ-1AFAE48CA253,외국인 등록증 발급,여권용 사진,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 +REQ-BFFB818EADB6,외국인 등록증 발급,외국인 직업 신고서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 +REQ-554F03CF5AC5,외국인 등록증 발급,임대차계약서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 +REQ-8622D60897D8,외국인 등록증 발급,표준근로계약서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 +REQ-22F04E8C6C69,체류기간연장허가,거주숙소제공 확인서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 +REQ-929153C3280B,체류기간연장허가,숙소제공자의 신분증,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 +REQ-B9ADAF144E4C,체류기간연장허가,여권,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 +REQ-FEC5F76DFA2D,체류기간연장허가,외국인 등록증,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 +REQ-72722D7A93A5,체류기간연장허가,임대차계약서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 +REQ-F8CAB7300B00,체류기간연장허가,취업기간연장확인서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 +REQ-BE1F3D524B0F,체류기간연장허가,통합신청서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 +REQ-77AFFAEEED96,취업기간만료자 취업 활동기간 연장신청,결산서,MANUFACTURING,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,제조업,SRC-CURATED-REQUIRED-DOCS,20260721,N,p3 +REQ-E84EB5B32FEB,취업기간만료자 취업 활동기간 연장신청,공장등록증,MANUFACTURING,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,제조업,SRC-CURATED-REQUIRED-DOCS,20260721,N,p3 +REQ-34E1BC2852FF,취업기간만료자 취업 활동기간 연장신청,매입매출 신고서류,MANUFACTURING,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,제조업,SRC-CURATED-REQUIRED-DOCS,20260721,N,p3 +REQ-78F9500AC076,취업기간만료자 취업 활동기간 연장신청,민원대리인 신청서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,N,p3 +REQ-6040C82C1E8A,취업기간만료자 취업 활동기간 연장신청,본사-지사간 근무중 외국인력현황표,MANUFACTURING,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,제조업,SRC-CURATED-REQUIRED-DOCS,20260721,N,p3 +REQ-38D0FD383DA1,취업기간만료자 취업 활동기간 연장신청,사업자등록증,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p3 +REQ-DF89F03B8F7F,취업기간만료자 취업 활동기간 연장신청,사업주도장,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,N,p3 +REQ-6E6B8E7234E1,취업기간만료자 취업 활동기간 연장신청,여권,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p3 +REQ-995EB110C0DF,취업기간만료자 취업 활동기간 연장신청,외국인등록증,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p3 +REQ-DB85FCF5D47D,취업기간만료자 취업 활동기간 연장신청,원가계약서,MANUFACTURING,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,제조업,SRC-CURATED-REQUIRED-DOCS,20260721,N,p3 +REQ-BD9E9595FF36,취업기간만료자 취업 활동기간 연장신청,취업기간 만료자 취업활동 기간연장신청서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p3 +REQ-2DF8B90C9E5E,취업기간만료자 취업 활동기간 연장신청,표준근로계약서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p3 diff --git a/fowoco-knowledge/data/processed/submission_path.csv b/fowoco-knowledge/data/processed/submission_path.csv new file mode 100644 index 0000000..dffe71e --- /dev/null +++ b/fowoco-knowledge/data/processed/submission_path.csv @@ -0,0 +1,5 @@ +application_id,application_name,online_submission,location,link,note +p1,외국인 등록증 발급,N,출입국사무소,www.hikorea.go.kr,예약필요 +p2,외국인 고용변동 등 신고,Y,하이코리아,https://foreigner.gov.kr/contents/foreignerContents?htmlNo=m020104, +p3,취업기간만료자 취업 활동기간 연장신청,Y,고용24,https://www.work24.go.kr/ep/a/f/0008/hpepaf0008m00Per.do?dtlContextPath=&redirectUrl=%2Fa%2Ff%2F0008%2Fhpepaf0008m00PerPost.do%3FfavType%3De9&topArea=&dtlSmtmConcCtrlId=&dtlExnwMenuConcSecd=&programMenuIdentification=EBG020000500337&menuPurpSecd=&dtlPwdCfrmYn=&serverToday=20260720¤tMenuId=X&dtlForwardPolyUrl=&dtlMenuId=&dtlMenuPurpSecd=&favType=e9&postType=PerPost.do, +p4,체류기간연장허가,Y,하이코리아,https://www.hikorea.go.kr/cvlappl/CvlapplStep1.pt#this, \ No newline at end of file From 2db664bf15840bb7b774ee930cd4ee249b506054 Mon Sep 17 00:00:00 2001 From: chaeeunn4 Date: Wed, 22 Jul 2026 10:35:43 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat(data):=20=EC=84=9C=EB=A5=98=20?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20=EC=A3=BC=EC=B2=B4=20dataset=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80,=20=EB=88=84=EB=9D=BD=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../curated/required_documents_manual_raw.csv | 3 +- .../data/processed/document_metadata.csv | 56 +++++++++++++++++++ .../processed/required_documents_manual.csv | 3 +- .../src/fowoco_knowledge/curated_ingestion.py | 1 - 4 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 fowoco-knowledge/data/processed/document_metadata.csv diff --git a/fowoco-knowledge/data/curated/required_documents_manual_raw.csv b/fowoco-knowledge/data/curated/required_documents_manual_raw.csv index c9955e8..f439429 100644 --- a/fowoco-knowledge/data/curated/required_documents_manual_raw.csv +++ b/fowoco-knowledge/data/curated/required_documents_manual_raw.csv @@ -9,6 +9,7 @@ is_essential",application_id ,외국인 등록증 발급,거주숙소제공확인서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 ,외국인 등록증 발급,숙소제공자신분증,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 ,외국인 등록증 발급,임대차계약서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 +,외국인 등록증 발급,외국인등록신청서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260722,Y,p1 ,외국인 고용변동 등 신고,외국인근로자고용변동등신고서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p2 ,취업기간만료자 취업 활동기간 연장신청,취업기간 만료자 취업활동 기간연장신청서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p3 ,취업기간만료자 취업 활동기간 연장신청,표준근로계약서 ,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p3 @@ -27,5 +28,5 @@ is_essential",application_id ,체류기간연장허가,여권,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 ,체류기간연장허가,외국인 등록증,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 ,체류기간연장허가,거주숙소제공 확인서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 -,체류기간연장허가,숙소제공자의 신분증,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 +,체류기간연장허가,숙소제공자신분증,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 ,체류기간연장허가,임대차계약서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 \ No newline at end of file diff --git a/fowoco-knowledge/data/processed/document_metadata.csv b/fowoco-knowledge/data/processed/document_metadata.csv new file mode 100644 index 0000000..aba2f9d --- /dev/null +++ b/fowoco-knowledge/data/processed/document_metadata.csv @@ -0,0 +1,56 @@ +document_id,document_name,document_provider,note +1,외국인 직업 신고서,HR, +2,여권,WORKER, +3,여권용 사진,WORKER, +4,사업자등록증,HR, +5,표준근로계약서,BOTH, +6,마약검사확인서,WORKER, +7,거주숙소제공확인서,HR, +8,숙소제공자신분증,HR, +9,임대차계약서,HR, +10,외국인등록신청서,HR, +11,외국인근로자고용변동등신고서,BOTH,근로자 서명 필요 +12,취업기간 만료자 취업활동 기간연장신청서,HR, +13,외국인등록증,WORKER, +14,민원대리인 신청서,HR, +15,본사-지사간 근무중 외국인력현황표,HR, +16,공장등록증,HR, +17,결산서,HR, +18,원가계약서,HR, +19,매입매출 신고서류,HR, +20,사업주도장,HR, +21,취업기간연장확인서,HR, +22,통합신청서,HR, +23,간행물/방송등을 통한 구인노력,HR, +24,개인정보수집이용동의 서류,HR, +25,공휴일 유급휴일 전환 서류,HR, +26,백신접종완료 증빙서류,HR, +27,뿌리산업증명서(고용허가용),HR, +28,사업장 전경 사진,HR, +29,숙소 관련 허가 및 신고자료,HR, +30,신규투자확인서,HR, +31,외국인근로자 고용허가서(원본),HR, +32,외국인근로자 주거시설표,HR, +33,외국인력현황표,HR, +34,일하는 모습 사진,HR, +35,제조업을 확인할 수 있는 서류,HR, +36,조선업 증빙서류,HR, +37,중견기업확인서,HR, +38,중소기업확인서,HR, +39,기숙사 전경 사진,HR, +40,난방시설 사진,HR, +41,냉방시설 사진,HR, +42,세면 및 목욕시설 증빙자료,HR, +43,소화기 설치 증빙자료,HR, +44,수납공간 사진,HR, +45,채광 및 환기시설 사진,HR, +46,침실 사진,HR, +47,침실 잠금장치 사진,HR, +48,화장실 및 목욕시설 잠금장치 사진,HR, +49,화장실 사진,HR, +50,화재감지기 설치 증빙자료,HR, +51,사업장 변경 사유 확인서(사업주용),HR, +52,권역별 사업장변경 동의서,HR, +53,노동시간단축계획 서류,HR, +54,외국인근로자 특례고용가능확인서,HR, +55,300인 이상 사업장 중 자본금 80억원 이하 증명 서류,HR, \ No newline at end of file diff --git a/fowoco-knowledge/data/processed/required_documents_manual.csv b/fowoco-knowledge/data/processed/required_documents_manual.csv index 5b623de..8135536 100644 --- a/fowoco-knowledge/data/processed/required_documents_manual.csv +++ b/fowoco-knowledge/data/processed/required_documents_manual.csv @@ -7,10 +7,11 @@ REQ-35FEF0B5C1BE,외국인 등록증 발급,숙소제공자신분증,ALL_INDUSTR REQ-DC5CDF62CE8B,외국인 등록증 발급,여권,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 REQ-1AFAE48CA253,외국인 등록증 발급,여권용 사진,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 REQ-BFFB818EADB6,외국인 등록증 발급,외국인 직업 신고서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 +REQ-B8D778F3B123,외국인 등록증 발급,외국인등록신청서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260722,Y,p1 REQ-554F03CF5AC5,외국인 등록증 발급,임대차계약서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 REQ-8622D60897D8,외국인 등록증 발급,표준근로계약서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p1 REQ-22F04E8C6C69,체류기간연장허가,거주숙소제공 확인서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,TRUE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 -REQ-929153C3280B,체류기간연장허가,숙소제공자의 신분증,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 +REQ-383570784CD3,체류기간연장허가,숙소제공자신분증,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260722,Y,p4 REQ-B9ADAF144E4C,체류기간연장허가,여권,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 REQ-FEC5F76DFA2D,체류기간연장허가,외국인 등록증,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 REQ-72722D7A93A5,체류기간연장허가,임대차계약서,ALL_INDUSTRIES,OFFICIAL_CONFIRMATION_REQUIRED,FALSE,전업종,SRC-CURATED-REQUIRED-DOCS,20260721,Y,p4 diff --git a/fowoco-knowledge/src/fowoco_knowledge/curated_ingestion.py b/fowoco-knowledge/src/fowoco_knowledge/curated_ingestion.py index 34d705e..a261138 100644 --- a/fowoco-knowledge/src/fowoco_knowledge/curated_ingestion.py +++ b/fowoco-knowledge/src/fowoco_knowledge/curated_ingestion.py @@ -4,7 +4,6 @@ data/processed/required_documents_manual.csv 로 생성(재실행 시 overwrite)하는 코드입니다. required_documents_manual_raw.csv 파일은 사람이 직접 작성하는 파일이며 required_id만 비워둔 형태입니다. - """ from __future__ import annotations From dfcfa18452a76f89ccf2e059a57df0392c95f516 Mon Sep 17 00:00:00 2001 From: chaeeunn4 Date: Wed, 22 Jul 2026 11:13:10 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix:check=20style=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/fowoco_knowledge/curated_ingestion.py | 102 ++++++++---------- 1 file changed, 46 insertions(+), 56 deletions(-) diff --git a/fowoco-knowledge/src/fowoco_knowledge/curated_ingestion.py b/fowoco-knowledge/src/fowoco_knowledge/curated_ingestion.py index a261138..235ad91 100644 --- a/fowoco-knowledge/src/fowoco_knowledge/curated_ingestion.py +++ b/fowoco-knowledge/src/fowoco_knowledge/curated_ingestion.py @@ -3,15 +3,16 @@ requirement_id를 ingestion.py 의 stable_id/_clean 과 동일한 규칙으로 채운 뒤 data/processed/required_documents_manual.csv 로 생성(재실행 시 overwrite)하는 코드입니다. -required_documents_manual_raw.csv 파일은 사람이 직접 작성하는 파일이며 required_id만 비워둔 형태입니다. +required_documents_manual_raw.csv 파일은 사람이 직접 작성하는 파일이며 +required_id만 비워둔 형태입니다. """ from __future__ import annotations - -import argparse + import csv from datetime import date from pathlib import Path + from .ingestion import _clean, stable_id # --- 고정 설정 ------------------------------------------------------------- @@ -22,8 +23,8 @@ SOURCE_ID = "SRC-CURATED-REQUIRED-DOCS" # ---------------------------------------------------------------------------- - - # 생성될.csv의 최종 스키마. + +# 생성될.csv의 최종 스키마. FIELDNAMES = [ "requirement_id", "application_name", @@ -34,11 +35,11 @@ "source_industry_text", "source_id", "source_version", - "is_essential", + "is_essential", "application_id", ] - + def _sniff_delimiter(input_path: Path) -> str: with input_path.open("r", encoding="utf-8-sig", newline="") as handle: sample = handle.readline() @@ -46,8 +47,8 @@ def _sniff_delimiter(input_path: Path) -> str: return csv.Sniffer().sniff(sample, delimiters=",\t").delimiter except csv.Error: return "," - - + + def _load_rows(input_path: Path) -> list[dict[str, str]]: delimiter = _sniff_delimiter(input_path) with input_path.open("r", encoding="utf-8-sig", newline="") as handle: @@ -55,24 +56,20 @@ def _load_rows(input_path: Path) -> list[dict[str, str]]: rows = [] for row in reader: # key(컬럼명)와 value(데이터) 양쪽의 줄바꿈/공백을 모두 정제 - cleaned_row = { - _clean(k): _clean(v) - for k, v in row.items() - if k is not None - } + cleaned_row = {_clean(k): _clean(v) for k, v in row.items() if k is not None} if any(cleaned_row.values()): rows.append(cleaned_row) return rows - - + + def _norm_scope(applicable_scope: str, industry_text: str) -> str: """applicable_scope 컬럼이 비어있는 경우를 대비해 industry_text로 보정.""" scope = _clean(applicable_scope).upper() if scope in {"ALL_INDUSTRIES", "MANUFACTURING"}: return scope return "ALL_INDUSTRIES" if _clean(industry_text) == "전업종" else "MANUFACTURING" - - + + def _norm_sample_form_available(value: str | None, *, line_no: int) -> str: """sample_form_available은 대문자 TRUE/FALSE 표기를 그대로 유지한다 (값 변환 없음).""" v = _clean(value).upper() @@ -81,18 +78,16 @@ def _norm_sample_form_available(value: str | None, *, line_no: int) -> str: f"{line_no}행: sample_form_available 값은 TRUE/FALSE여야 합니다 (입력값: '{value}')" ) return v - - + + def _norm_is_essential(value: str | None, *, line_no: int) -> str: """is_essential은 Y/N 표기를 그대로 유지한다 (값 변환 없음).""" v = _clean(value).upper() if v not in {"Y", "N"}: - raise ValueError( - f"{line_no}행: is_essential 값은 Y/N이어야 합니다 (입력값: '{value}')" - ) + raise ValueError(f"{line_no}행: is_essential 값은 Y/N이어야 합니다 (입력값: '{value}')") return v - - + + def _load_existing_output(output_path: Path) -> dict[str, dict[str, str]]: """이전에 생성된 processed csv를 읽어 requirement_id -> 이전 행 매핑을 만든다. 파일이 없으면(최초 실행) 빈 딕셔너리를 반환한다.""" @@ -101,14 +96,14 @@ def _load_existing_output(output_path: Path) -> dict[str, dict[str, str]]: with output_path.open("r", encoding="utf-8-sig", newline="") as handle: reader = csv.DictReader(handle) return {row["requirement_id"]: row for row in reader if row.get("requirement_id")} - - + + def _unchanged_except_version(new_row: dict[str, str], old_row: dict[str, str]) -> bool: """source_version을 제외한 모든 필드가 이전 행과 동일한지 확인.""" compare_fields = [f for f in FIELDNAMES if f != "source_version"] return all(new_row.get(f, "") == old_row.get(f, "") for f in compare_fields) - - + + def generate( input_path: Path = INPUT_PATH, output_path: Path = OUTPUT_PATH, @@ -125,22 +120,22 @@ def generate( """ resolved_today = today_version or date.today().strftime("%Y%m%d") existing_by_id = _load_existing_output(output_path) - + rows = _load_rows(input_path) - + output_rows: list[dict[str, str]] = [] seen_ids: dict[str, tuple[str, str]] = {} # requirement_id -> (application_name, document_name) app_id_by_name: dict[str, str] = {} # application_name -> application_id (일관성 검사용) - + for line_no, row in enumerate(rows, start=2): # 헤더가 1행이므로 데이터는 2행부터 application_name = _clean(row.get("application_name")) document_name = _clean(row.get("document_name")) source_industry_text = _clean(row.get("source_industry_text")) application_id = _clean(row.get("application_id")) - + if not application_name or not document_name: raise ValueError(f"{line_no}행: application_name/document_name이 비어있습니다.") - + if application_id: prev_app_id = app_id_by_name.get(application_name) if prev_app_id is None: @@ -150,30 +145,27 @@ def generate( f"{line_no}행: '{application_name}'의 application_id가 일관되지 않습니다 " f"(기존: {prev_app_id}, 신규: {application_id})" ) - + # ingestion.py의 transform_required_documents()와 완전히 동일한 규칙. # application_id는 해시 입력에서 제외 (공식데이터와 똑같이) - requirement_id = stable_id( - "REQ", application_name, document_name, source_industry_text - ) - + requirement_id = stable_id("REQ", application_name, document_name, source_industry_text) + if requirement_id in seen_ids: prev_app, prev_doc = seen_ids[requirement_id] if prev_app != application_name or prev_doc != document_name: raise ValueError( - f"{line_no}행: 해시 충돌 의심 - {requirement_id} 가 서로 다른 항목에서 재사용됨 " + f"{line_no}행: 해시 충돌 의심 -" + f"{requirement_id} 가 서로 다른 항목에서 재사용됨 " f"(기존: {prev_app}/{prev_doc}, 신규: {application_name}/{document_name})" ) continue # 완전 동일한 행 중복 -> 제거 seen_ids[requirement_id] = (application_name, document_name) - + candidate_row = { "requirement_id": requirement_id, "application_name": application_name, "document_name": document_name, - "applicable_scope": _norm_scope( - row.get("applicable_scope", ""), source_industry_text - ), + "applicable_scope": _norm_scope(row.get("applicable_scope", ""), source_industry_text), "requirement_marker": _clean(row.get("requirement_marker")) or "OFFICIAL_CONFIRMATION_REQUIRED", "sample_form_available": _norm_sample_form_available( @@ -182,37 +174,35 @@ def generate( "source_industry_text": source_industry_text, "source_id": source_id, "source_version": "", # 아래에서 신규/변경 여부 판단 후 채운다 - "is_essential": _norm_is_essential( - row.get("is_essential"), line_no=line_no - ), + "is_essential": _norm_is_essential(row.get("is_essential"), line_no=line_no), "application_id": application_id, } - + old_row = existing_by_id.get(requirement_id) if old_row is not None and _unchanged_except_version(candidate_row, old_row): candidate_row["source_version"] = old_row["source_version"] else: candidate_row["source_version"] = resolved_today - + output_rows.append(candidate_row) - + output_rows.sort(key=lambda item: (item["application_name"], item["document_name"])) - + output_path.parent.mkdir(parents=True, exist_ok=True) with output_path.open("w", encoding="utf-8", newline="") as handle: writer = csv.DictWriter(handle, fieldnames=FIELDNAMES, lineterminator="\n") writer.writeheader() writer.writerows(output_rows) - + return output_rows - - + + def main() -> None: if not INPUT_PATH.exists(): raise FileNotFoundError(f"curated 원본 파일을 찾을 수 없습니다: {INPUT_PATH}") rows = generate() print(f"완료: {len(rows)}행 -> {OUTPUT_PATH}") - - + + if __name__ == "__main__": main() From 153f8b892b7f39ccc408f0a02037503b96632338 Mon Sep 17 00:00:00 2001 From: chaeeunn4 Date: Wed, 22 Jul 2026 11:15:38 +0900 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20ruff=20format=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=20=ED=8F=AC=EB=A7=B7=ED=8C=85=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fowoco-knowledge/src/fowoco_knowledge/curated_ingestion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fowoco-knowledge/src/fowoco_knowledge/curated_ingestion.py b/fowoco-knowledge/src/fowoco_knowledge/curated_ingestion.py index 235ad91..0428775 100644 --- a/fowoco-knowledge/src/fowoco_knowledge/curated_ingestion.py +++ b/fowoco-knowledge/src/fowoco_knowledge/curated_ingestion.py @@ -154,7 +154,7 @@ def generate( prev_app, prev_doc = seen_ids[requirement_id] if prev_app != application_name or prev_doc != document_name: raise ValueError( - f"{line_no}행: 해시 충돌 의심 -" + f"{line_no}행: 해시 충돌 의심 -" f"{requirement_id} 가 서로 다른 항목에서 재사용됨 " f"(기존: {prev_app}/{prev_doc}, 신규: {application_name}/{document_name})" )