Skip to content

RFC-016: §6 Employment Income (DPČ/DPP/HPP) with OCR - #1

Merged
zajca merged 5 commits into
mainfrom
rfc/016-employment-income-ocr
May 2, 2026
Merged

zajca merged 5 commits into
mainfrom
rfc/016-employment-income-ocr

Conversation

@zajca

@zajca zajca commented May 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds plan (RFC-016) for full §6 employment income support in DPFO -- DPČ, DPP, HPP -- with OCR extraction from "Potvrzení o zdanitelných příjmech" (form 25 5460 vzor 33) and "Potvrzení o vyplacených příjmech a sražené dani" (25 5460/A vzor 12)
  • Fills already-existing XSD attributes (kc_prij6, kc_zd6, kc_dan_zah, kc_zalzavc, kc_sraz_6_4, potv_zam/potv_36/potv_dazvyh) -- no schema invention
  • Fixes hardcoded assumption in internal/annualtaxxml/income_tax_gen.go:210 (// ř.42 -- assumes §6 employment base = 0)
  • All field meanings cross-checked against DPFO 2025 vzor 33 official pokyny (financnisprava.gov.cz) and the local docs/xml-schemas/epo/dpfdp7_epo2.xsd

Verified corrections vs. initial draft

Initial assumption Verified fact
ř.32 = úhrn povinného pojistného ř.32 is "Neobsazeno" in vzor 33; superhrubá mzda zrušena 2021 (zákon 609/2020 Sb.)
ř.34 = ř.31 + ř.32 ř.34 = ř.31 − ř.33 (foreign tax only)
Sražené zálohy = ř.16 Potvrzení ř.8 Potvrzení vzor 33 (per XSD doc on kc_zalzavc)
Sleva na poplatníka snížit při měsíčním uplatnění Vždy plná 30 840 Kč/rok -- rozdíl jde přes ř.84 → ř.91

Scope highlights

  • DB migration 027 (employment_documents, employment_income_certificates, §6 columns on income_tax_returns)
  • New service EmploymentCertificateService with upload + OCR + validation pipeline
  • New OCR prompt distinguishing advance vs. withholding Potvrzení
  • New page /tax/employment with dual upload tiles, modal editor with confidence display
  • 4th card on /tax, read-only §6 panel on income return detail
  • 13 new help topics covering DPČ/DPP, both Potvrzení variants, each affected DAP row, and superhrubá mzda explanation
  • §16a separate base (>36× avg wage) explicitly out of MVP -- emits warning instead

Test plan

  • Review the data model (migration 027) for soundness, especially the ON CONFLICT REPLACE choice on the UNIQUE clause
  • Confirm the §6 row mapping against the live EPO portal validator before implementation begins
  • Verify §6 + §7 ztráta combination — kc_zakldan23 = kc_zd6 per XSD critical control
  • Confirm "include withholding in DAP" UX wording vs. § 38g odst. 6 ZDP requirement to include all such income
  • Decide on the three open questions in the RFC (UNIQUE conflict policy, DPP threshold warning, OCR vendor neutrality)

References

zajca added 2 commits May 2, 2026 08:50
Plan adding full DPFO support for employment income (Potvrzení o
zdanitelných příjmech, both vzor 33 advance and vzor 12 withholding),
with AI OCR extraction. Maps verified XSD attributes (kc_prij6, kc_zd6,
kc_zalzavc, kc_sraz_6_4, potv_zam etc.) to DPFO rows 31-36, 42, 76,
84, 87, fixing the income_tax_gen.go assumption that §6 base is 0.
- ř.89 (kc_vyplbonus) for monthly bonuses, not ř.76
- Drop ChildBenefit -= MonthlyBonusPaid (would double-count)
- Rename foreign_income -> income_without_advance (ř.35 covers
  §38c/§38h, not just foreign)
- Fix §16/§16a terminology: 23% progressive is §16 odst.1, §16a is
  Příloha 4 separate base
- Lhůta § 38j odst. 3: 10 days from request, not 1. March
- OCR extracts ř.5 + ř.13 for monthly bonus, not just ř.13
- ř.32 stays "Neobsazeno" per PDF pokyny 2025 (XSD docstring stale)
@zajca

zajca commented May 2, 2026

Copy link
Copy Markdown
Owner Author

Review feedback addressed (v2 commit f8968a7)

Accepted (fixed in RFC):

  • K2 — Vyplacený měsíční bonus přemapován z chybně přiřazeného ř.76 na ř.89 (kc_vyplbonus). Ř.76 je computed = ř.72 − ř.73 (nárok na bonus po uplatnění daně), nemá s výplatou nic společného.
  • K3 — Odstraněn chybný kód itr.ChildBenefit -= itr.Section6MonthlyBonusPaid. Způsoboval by dvojí započtení: vyplacený bonus je samostatná hodnota na ř.89, ChildBenefit (ř.72) zůstává plně vypočtený existující logikou.
  • K4foreign_income přejmenováno na income_without_advance ve všech vrstvách (DB sloupec, doménové pole, JSON output, UI label). Ř.35 zahrnuje i §38c (zahraniční zastup. úřady v ČR), nejen zahraniční zdroje.
  • §16 vs §16a — Progresivní 23 % je v § 16 odst. 1 ZDP, ne v §16a. §16a je samostatný institut Přílohy 4 (vybrané zahraniční příjmy). WarningSection16aManual přejmenován na WarningProgressiveRateReview, terminologie napříč RFC i help-content opravena.
  • Lhůta vystavení Potvrzení — § 38j odst. 3 ZDP říká "do 10 dnů od podání žádosti", ne "do 1. března". Help-content potvrzeni-zalohove a zavisla-cinnost-s6 opraven.
  • OCR promptmonthly_bonus_paid_czk se extrahuje jako ř.5 + ř.13 Potvrzení vzor 33 (potvrzeno oficiálními pokyny 2025 str. 4 a XSD docstringem na kc_vyplbonus), ne jen ř.13.

Push-back na K1 (ř.32) — ponecháno bez změny:

Pokyny FS 2025 vzor 33 explicitně říkají "ř. 32 Neobsazeno" (PDF str. 2, citace z bodu K2 oddílu). Pokyny pro ř.31 zároveň instruují, že srážkové příjmy zahrnuté do DAP "jste povinen uvést veškeré tyto příjmy do ř. 31" — tedy jdou do ř.31, ne ř.32.

Stale text v XSD docstringu na kc_prij6 ("do ř. 32") je dědictví z předchozích vzorů, kde ř.32 měl jiný význam. Mapování XML je jednoznačné — kc_prij6 reprezentuje ř.31, žádný atribut pro ř.32 v XSD neexistuje. RFC tabulka tuto nesrovnalost teď explicitně dokumentuje.

Pokud máte odlišné čtení pokynů 2025 vzor 33 (např. jiná verze než ta z financnisprava.gov.cz), pošlete prosím odkaz a citaci — rád to znovu ověřím.

- N5: drop Section6CertsBonus++ in Recalculate; potv_dazvyh tracks
  separate "Potvrzení o vyplaceném daňovém bonusu" forms (kind=bonus),
  not advance certs with bonus. MVP keeps it 0.
- N4: frontend detail panel ř.76 -> ř.89 (leftover from v1)
- N2/N7: drop "ř.19" reference (does not exist in vzor 33; doplatek
  z RZ is in ř.13 per XSD doc on kc_vyplbonus)
- N1: test description rewritten — ChildBenefit must stay unchanged
  when MonthlyBonusPaid > 0 (regression test for K3)
- N6: "§16a limit warning" -> "§16 progressive rate warning"
- Integration test expectation potv_dazvyh=0 in MVP
@zajca

zajca commented May 2, 2026

Copy link
Copy Markdown
Owner Author

v3 review feedback addressed (commit 8e17f0e)

  • N1 — Test description "monthly bonus subtraction from child benefit" → "ChildBenefit zůstává nezměněn i když MonthlyBonusPaid > 0 (regression for K3)". Doplněn nový test TestIncomeTaxXML_BonusReportedSeparately ověřující, že ř.72/76 a ChildBenefit se nemění při ne-nulovém kc_vyplbonus.
  • N2 + N7 — "doplatek z RZ z ř.19" odstraněno z tabulky. Ř.19 v Potvrzení vzor 33 neexistuje; doplatek z ročního zúčtování je obsažen v ř.13 (per XSD doc na kc_vyplbonus).
  • N4 — Frontend sekce na detail income return řádek ř.76ř.89.
  • N5Section6CertsBonus++ v Recalculate odstraněn s vysvětlujícím komentářem. potv_dazvyh reflektuje počet samostatných formulářů "Potvrzení o vyplaceném daňovém bonusu" (EmploymentDocument.Kind=bonus), ne advance certifikátů s vyplaceným bonusem. V MVP zůstává 0; samostatný upload UI pro tento formulář je explicitně v Out of Scope.
  • N6 — "§16a limit warning" → "§16 progressive rate warning when totalBase > 36× průměrná mzda".
  • Integration test: potv_dazvyh=1potv_dazvyh=0 (důsledek N5).

Q7/Q8: XSD attribute table called kc_zd6p "§16a", but Legislative
notes and XML generator correctly said "§38f / Příloha 3". Unified:
table now says "§38f / Příloha 3" (allocation of §6 portion for
foreign tax credit), with explicit note it is unrelated to §16a
(Příloha 4 — separate base) and §16 odst. 1 progressive rate.
@zajca

zajca commented May 2, 2026

Copy link
Copy Markdown
Owner Author

v4 (commit efaa2a2): Q7/Q8 fix — kc_zd6p v tabulce XSD atributů sjednoceno na "§38f / Příloha 3" (alokace §6 portionu pro zápočet zahraniční daně), s explicitní poznámkou, že nesouvisí s §16a ani s progresivní sazbou §16 odst. 1. Drobné — vnitřní rozpor mezi tabulkou a Legislative notes / XML generátorem opraven.

Adds full support for §6 závislá činnost (DPČ/DPP/HPP) into the DPFO income tax
return. Users upload a Potvrzení o zdanitelných příjmech, AI extracts the
fields, and aggregates flow into rows 31, 33, 34/36, 84, 87, 89 plus EPO XML
attributes (kc_prij6, kc_zd6, kc_zalzavc, kc_sraz_6_4, kc_vyplbonus, potv_zam,
potv_36).

Backend:
- Migration 027 (employment_documents, employment_income_certificates, §6
  aggregate columns on income_tax_returns)
- Migration 028 (warnings TEXT column on income_tax_returns)
- Domain: EmploymentDocument, EmploymentCertificate, ContractType,
  CertificateType plus §6 fields on IncomeTaxReturn and Warnings []string
- Repos with soft delete + UNIQUE (year, ico, type, period) ON CONFLICT REPLACE
- Service: upload + OCR extract + CRUD + confirm with audit emits, IČO mod-11
  validation via domain.ValidateICO
- §6 aggregation in IncomeTaxReturnService.Recalculate
- TaxDue now reconciles ř.84 + ř.87 + ř.89 (paid bonus) per pokyny DPFO 2025
- WarningProgressiveRateReview emit when totalBase > 36× průměrná mzda
- ProgressiveThreshold 2025 = 1 676 052 Kč (nař. vlády 282/2024 Sb.)
- XML generator emits VetaO/D/B §6 attrs with omitempty (zero §6 → no diff)
- HTTP handler at /api/v1/tax/employment with 10 endpoints, 10 MB cap, MIME
  allowlist (PDF/JPEG/PNG/WEBP) + magic-byte sniffing
- OCR prompt for vzor 33 (advance) and vzor 12 (withholding) variants

Frontend:
- /tax/employment page with year selector, dual upload tiles, certificate
  table, recompute on confirm/delete
- EmploymentCertificateEditor modal with type toggle, validation, OCR
  confidence badge
- §6 card on /tax overview, §6 panel on income return detail
- Warnings banner with HelpTip on income return detail
- 15 help topics covering §6, both Potvrzení variants, all relevant rows,
  superhrubá mzda zrušení, §16 progresivní sazba, §16a vs §38f distinction
- Typed employmentApi in client.ts

Legislative correctness verified against pokyny DPFO 2025 vzor 33, EPO XSD
dpfdp7_epo2.xsd, and ZDP §§ 6, 16, 16a, 35ba, 35d, 36, 38c, 38f, 38g, 38h,
38ch, 38j odst. 3.

Tests: 11 doc repo + 11 cert repo + 11 OCR prompt + 22 service + 30+ handler
+ 4 XML gen + 4 progressive/warning + 4 TaxDue reconciliation + frontend
849/849 vitest.
@zajca
zajca merged commit 06c758c into main May 2, 2026
2 checks passed
@zajca
zajca deleted the rfc/016-employment-income-ocr branch May 2, 2026 11:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant