Deliverable · DEL-002
인터페이스 규격서 — income_tax_module (PID 157294)
납품물 DEL-002. 발주처 기존 개발자가 이 문서만으로 모듈을 호출·결합할 수 있도록 작성한다.
1. 모듈 개요
| 항목 | 값 |
|---|---|
| 패키지 | income_tax_module |
| 언어 | Python ≥ 3.11 |
| 진입점 | process_return(inp: ProcessInput) -> ProcessResult |
| 상태 | stateless — 호출 간 저장 없음 |
| 결정성 | 동일 입력 → 동일 출력 (해시 일치 테스트로 증명) |
| 민감정보 | 모듈이 디스크/DB에 저장하지 않음 |
from income_tax_module import process_return
from income_tax_module.models import ProcessInput, WageIncome
result = process_return(
ProcessInput(
tax_year=2024,
case_id="CASE-001",
wage=WageIncome(gross_pay=48_000_000, income_tax_withheld=2_100_000),
)
)
print(result.route, result.param_version, result.to_dict())
2. 입력 계약
2.1 ProcessInput
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
tax_year | int | Y | 귀속연도 (파라미터 파일 키) |
case_id | str | Y | 호출 측 식별자 (로그 상관용, 모듈 비저장) |
wage | WageIncome | null | N | 근로소득 |
freelance | FreelanceIncome | null | N | 사업자등록 없는 인적용역 |
flags | Flags | N | 수기 판정 플래그 |
param_dir | str | null | N | 파라미터 디렉터리 오버라이드 |
taxpayer | TaxpayerIdentity | null | N | 공식 후보 파일 생성용 신고인 정보. 결과 메타에 에코하지 않음 |
filing | FilingSettings | N | 신고유형·납부기한·신고인 설정 |
unsupported_deductions | tuple[str, ...] | N | 미지원 공제 항목. 입력되면 수기 분기 |
2.2 WageIncome
| 필드 | 타입 | 설명 |
|---|---|---|
gross_pay | int | 총급여 (원) |
payer_name | str | 홈택스 근로소득 지급자 상호 |
payer_registration_number | str | 홈택스 지급자 사업자·주민등록번호(10/13자리) |
non_taxable | int | 비과세 |
national_pension | int | 국민연금 |
health_insurance | int | 건강보험 |
long_term_care_insurance | int | 장기요양보험 |
employment_insurance | int | 고용보험 |
industrial_accident_insurance | int | 산재보험 입력액 |
social_insurance_total | int | null | 있으면 공제 가능한 개별 사회보험 합계를 대체 |
income_tax_withheld | int | 근로소득세 원천징수 |
local_tax_withheld | int | 근로 지방소득세 특별징수 |
산재보험 입력액은 보존하지만 개인 소득공제 합계에는 포함하지 않는다. 기존 7개 위치 인자
(gross_pay부터 local_tax_withheld까지)의 순서는 0.1.x와 동일하며 신규 필드는 그 뒤에 있다.
2.3 FreelanceIncome
| 필드 | 타입 | 설명 |
|---|---|---|
revenue | int | 수입금액 |
withheld_tax | int | 3.3% 등 원천징수세액 |
actual_expense | int | null | 실제경비. expense_rate보다 우선 |
expense_rate | float | null | 해당 건의 확인된 업종별 경비율(0..1). 두 경비 입력이 모두 없으면 수기 |
regional_national_pension | int | 지역가입 국민연금 |
regional_health_insurance | int | 지역가입 건강보험 |
regional_long_term_care_insurance | int | 지역가입 장기요양보험 |
2.4 Flags
| 필드 | 기본 | 수기 코드 |
|---|---|---|
has_business_registration | false | BUSINESS_REG |
financial_income_comprehensive | false | FINANCIAL_COMPREHENSIVE |
has_real_estate_rental | false | REAL_ESTATE_RENTAL |
is_non_resident | false | NON_RESIDENT |
other_manual_reason | null | OTHER |
year_end_settlement_completed | false | (신고의무 판정 입력) |
multiple_employers | null | (복수 근무지 여부. null은 미확인) |
all_income_sources_reviewed | false | (금융·연금·기타소득 등 전체 소득원 확인 완료 여부) |
year_end_settlement_completed=false와 all_income_sources_reviewed=false, multiple_employers=null은 미확인이다.
filing_duty=not_required는 연말정산 완료·multiple_employers=false·전체 소득원 확인 완료·인적용역 없음·수기 플래그 없음·unsupported_deductions 없음이 모두 확인될 때만 나오며,
이때 NO_FILING_DUTY로 수기 분기한다(환급 목적 자진신고는 별도 선택).
추가 수기: 인적용역 수입이 파라미터 manual_thresholds.personal_service_revenue 초과 → PERSONAL_SERVICE_OVER.
실제경비와 건별 경비율이 모두 없음 → EXPENSE_RATE_REQUIRED; 지원하지 않는 공제 입력 → UNSUPPORTED_DEDUCTION.
2.5 TaxpayerIdentity / FilingSettings
TaxpayerIdentity는 name, 13자리 resident_registration_number, address, 2자리 sido_code,
3자리 sigungu_code, 5/10자리 legal_dong_code, 선택 phone을 받는다.
FilingSettings 주요 값은 다음과 같다.
return_type:regular|amended|late|correctiondue_date: 실제 달력상 유효한YYYYMMDDtaxpayer_type:01|02|11|19bookkeeping_duty:01|02|03filing_method:11|12|14|20|31|32|35|40tax_agent_role=00: 본인신고. 그 외에는 대리인 이름·등록번호·사업자번호·전화번호가 모두 필요
공식 후보 생성 입력에 근로소득이 있으면 미구현 세액공제 확인을 위해 TAX_CREDIT_SCOPE_REQUIRED,
환급 예상이면 공식 부호 규칙 확인을 위해 REFUND_FILING_REVIEW로 수기 분기한다.
3. 출력 계약
3.1 ProcessResult
| 필드 | 타입 | 설명 |
|---|---|---|
ok | bool | 처리 성공(수기 정상 반환 포함). 파일 검증 실패·입력 오류 시 false |
case_id | str | 입력 에코 |
tax_year | int | 입력 에코 |
route | "auto" | "manual" | "error" | 판정. 에러 시 error — 수기 큐로 취급 금지 |
manual_reasons | list[{code, message}] | 수기 사유 (auto/error면 []) |
param_version | str | 적용 파라미터 버전 |
breakdown | TaxBreakdown | null | 수기·에러 시 null |
basis | list[BasisStep] | 단계별 중간값 (버전·세액공제 고지 포함) |
filings | list[FilingArtifact] | 홈택스·위택스 파일 (수기 시 []) |
error | dict | null | 실패 시 {code, message, details} |
filing_duty | "required" | "not_required" | "unknown" | 신고의무 축. 자료 미확인은 unknown — 어느 쪽으로도 합치지 않는다 |
tax_method | "comprehensive" | "unknown" | "none" | 과세방법 축. 금융·임대·비거주자 플래그가 있으면 unknown |
product_path | "auto" | "manual" | "error" | 제품경로 축. route는 이 값의 하위 호환 별칭 |
세 축은 서로 다른 판단이며 한 열로 합치지 않는다 (docs/INCOME_TAX_CASE_MATRIX.md §5).
filing_duty=required여도 product_path=manual일 수 있고, not_required여도 자진신고는 가능하다.
basis에 filing_duty·tax_method 단계가 포함된다.
3.2 TaxBreakdown
wage_income_amount, freelance_income_amount, total_taxable, income_tax, local_income_tax, total_tax,
withheld_income_tax, withheld_local_tax, withheld_total,
payable_income_tax, payable_local_tax, payable
(호환: wage_taxable/freelance_taxable = 소득금액 별칭)
프리랜서 3.3% 원천은 파라미터 withholding_33_national_share(기본 30/33)로 소득세/지방세 분해.
3.3 FilingArtifact
kind (hometax|wetax), filename, content, content_type, validation.
- 신고인 정보가 없으면 기존 JSON 중간 포맷을 반환한다.
- 신고인 정보가 있으면 CP949·CRLF 고정길이
.101/.C10~C40후보를 반환한다. content는 Unicode 전송 문자열이다. 파일 저장 시 반드시content.encode("cp949")로 바이트화한다.validation.local_validation은 길이·순서·인코딩 사전검사,authority_validation.status는 기관 검증 상태다. 로컬 PASS는 기관 접수 성공을 뜻하지 않는다.
4. 에러 규약
| code | 의미 |
|---|---|
INVALID_INPUT | 필수값·음수 등 입력 위반 |
UNSUPPORTED_TAX_YEAR | 해당 연도 파라미터 파일 없음 |
PARAM_LOAD_FAILED | 파라미터 파일 손상·불일치 |
FILE_VALIDATION_FAILED | 생성 파일 사전 검증 실패 (ok=false, filings에 상세) |
INTERNAL | 예상 밖 예외 |
에러는 예외 전파 대신 ProcessResult.ok=false + error 필드로 반환한다 (ModuleError는 내부·선택적 raise).
5. 파라미터
- 경로:
income_tax_module/params/data/{tax_year}.json - 파일 교체만으로 반영 (
load_params). 해당 연도 파일이 없으면UNSUPPORTED_TAX_YEAR로 거부한다 - 파일럿 버전:
params-2024.1.0-pilot,params-2025.1.0-pilot - 각 파일은
confirmed(근거 확인된 키)와unconfirmed(키 → 미확정 사유)를 명시한다 unconfirmed키를 읽으면PARAM_LOAD_FAILED로 거부한다. 현재personal_service_simple_expense_rate가 여기 해당하므로, 인적용역 경비는freelance.expense_rate또는actual_expense로 건별 확정값을 넣어야 한다manual_thresholds는 세법 기준이 아니라 수기 전환용 보수적 가드값이다
6. 통합 시나리오 3종
6.1 단일소득 (근로) — fixtures/wage_only.json
1. 입력: 근로만, flags 전부 false
2. 기대: route=auto, breakdown·basis·filings 2건, validation pass
6.2 겸업 — fixtures/combined.json
1. 입력: wage + freelance
2. 기대: route=auto, wage_taxable·freelance_taxable 모두 > 0
6.3 수기분기 — fixtures/manual_business.json
1. 입력: has_business_registration=true
2. 기대: route=manual, reason BUSINESS_REG, breakdown=null, filings=[]
7. 호출 예제 (CLI)
cd /path/to/wishket-pilot-157294-income-tax-module
PYTHONPATH=src python3 -c "
import json
from pathlib import Path
from income_tax_module import process_return
from income_tax_module.models import ProcessInput
raw = json.loads(Path('fixtures/combined.json').read_text())
print(json.dumps(process_return(ProcessInput.from_dict(raw)).to_dict(), ensure_ascii=False, indent=2)[:800])
"
8. 시연 하네스 HTTP
| Method | Path | 설명 |
|---|---|---|
| GET | / | 공개 제안 |
| GET | /demo | 시나리오 선택 |
| GET | /demo/run?scenario=wage|combined|manual | 호출 결과 |
| GET | /demo/params | 파라미터 버전 |
| GET | /api/process?scenario= | JSON 결과 |
| GET | /docs/interface | 본 규격서 |
| GET | /health | 헬스 |
PYTHONPATH=src uvicorn harness.app:app --host 127.0.0.1 --port 8029
9. 경계
- 스크래핑·전자신고 제출 API 연동 없음
- 신고 건 상태 관리·재처리·알림 없음
- 파일 포맷은 파일럿 중간 스키마 (
docs/FILING_SCHEMA.md) — 공식 홈택스/위택스 바이너리 레이아웃 확정 전 - 실제 세액 최종 확인은 세무 전문가 검토 전제