Filing Proof Desk

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_yearintY귀속연도 (파라미터 파일 키)
case_idstrY호출 측 식별자 (로그 상관용, 모듈 비저장)
wageWageIncome | nullN근로소득
freelanceFreelanceIncome | nullN사업자등록 없는 인적용역
flagsFlagsN수기 판정 플래그
param_dirstr | nullN파라미터 디렉터리 오버라이드
taxpayerTaxpayerIdentity | nullN공식 후보 파일 생성용 신고인 정보. 결과 메타에 에코하지 않음
filingFilingSettingsN신고유형·납부기한·신고인 설정
unsupported_deductionstuple[str, ...]N미지원 공제 항목. 입력되면 수기 분기

2.2 WageIncome

필드타입설명
gross_payint총급여 (원)
payer_namestr홈택스 근로소득 지급자 상호
payer_registration_numberstr홈택스 지급자 사업자·주민등록번호(10/13자리)
non_taxableint비과세
national_pensionint국민연금
health_insuranceint건강보험
long_term_care_insuranceint장기요양보험
employment_insuranceint고용보험
industrial_accident_insuranceint산재보험 입력액
social_insurance_totalint | null있으면 공제 가능한 개별 사회보험 합계를 대체
income_tax_withheldint근로소득세 원천징수
local_tax_withheldint근로 지방소득세 특별징수

산재보험 입력액은 보존하지만 개인 소득공제 합계에는 포함하지 않는다. 기존 7개 위치 인자

(gross_pay부터 local_tax_withheld까지)의 순서는 0.1.x와 동일하며 신규 필드는 그 뒤에 있다.

2.3 FreelanceIncome

필드타입설명
revenueint수입금액
withheld_taxint3.3% 등 원천징수세액
actual_expenseint | null실제경비. expense_rate보다 우선
expense_ratefloat | null해당 건의 확인된 업종별 경비율(0..1). 두 경비 입력이 모두 없으면 수기
regional_national_pensionint지역가입 국민연금
regional_health_insuranceint지역가입 건강보험
regional_long_term_care_insuranceint지역가입 장기요양보험

2.4 Flags

필드기본수기 코드
has_business_registrationfalseBUSINESS_REG
financial_income_comprehensivefalseFINANCIAL_COMPREHENSIVE
has_real_estate_rentalfalseREAL_ESTATE_RENTAL
is_non_residentfalseNON_RESIDENT
other_manual_reasonnullOTHER
year_end_settlement_completedfalse(신고의무 판정 입력)
multiple_employersnull(복수 근무지 여부. null은 미확인)
all_income_sources_reviewedfalse(금융·연금·기타소득 등 전체 소득원 확인 완료 여부)

year_end_settlement_completed=falseall_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

TaxpayerIdentityname, 13자리 resident_registration_number, address, 2자리 sido_code,

3자리 sigungu_code, 5/10자리 legal_dong_code, 선택 phone을 받는다.

FilingSettings 주요 값은 다음과 같다.

공식 후보 생성 입력에 근로소득이 있으면 미구현 세액공제 확인을 위해 TAX_CREDIT_SCOPE_REQUIRED,

환급 예상이면 공식 부호 규칙 확인을 위해 REFUND_FILING_REVIEW로 수기 분기한다.

3. 출력 계약

3.1 ProcessResult

필드타입설명
okbool처리 성공(수기 정상 반환 포함). 파일 검증 실패·입력 오류 시 false
case_idstr입력 에코
tax_yearint입력 에코
route"auto" | "manual" | "error"판정. 에러 시 error — 수기 큐로 취급 금지
manual_reasonslist[{code, message}]수기 사유 (auto/error면 [])
param_versionstr적용 파라미터 버전
breakdownTaxBreakdown | null수기·에러 시 null
basislist[BasisStep]단계별 중간값 (버전·세액공제 고지 포함)
filingslist[FilingArtifact]홈택스·위택스 파일 (수기 시 [])
errordict | 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여도 자진신고는 가능하다.

basisfiling_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.

4. 에러 규약

code의미
INVALID_INPUT필수값·음수 등 입력 위반
UNSUPPORTED_TAX_YEAR해당 연도 파라미터 파일 없음
PARAM_LOAD_FAILED파라미터 파일 손상·불일치
FILE_VALIDATION_FAILED생성 파일 사전 검증 실패 (ok=false, filings에 상세)
INTERNAL예상 밖 예외

에러는 예외 전파 대신 ProcessResult.ok=false + error 필드로 반환한다 (ModuleError는 내부·선택적 raise).

5. 파라미터

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

MethodPath설명
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. 경계

검사 하네스로 원문 Markdown