AKN → Korean model roundtrip

kr2akn is bidirectional for the fields that affect AKN output:

raw eflaw / precedent XML
        │  kr2akn emit / emit-case
        ▼
     AKN XML
        │  kr2akn akn2kr
        ▼
kr2akn.raw-law.v1 / kr2akn.raw-case.v1
        │  kr2akn emit / emit-case
        ▼
     AKN XML   (canonically identical)

The guarantee is canonical equality, not byte equality: the second AKN document is compared after XML canonicalization, so formatting and namespace prefixes do not matter.

CLI

# AKN act → raw-law model JSON
kr2akn akn2kr --input ../data/akn/001706.akn.xml --output /tmp/001706.model.json

# AKN judgment → raw-case model JSON
kr2akn akn2kr --input ../data/akn/prec/624945.akn.xml --output /tmp/624945.model.json

# Re-emit directly from a decoded model
kr2akn emit --model /tmp/001706.model.json --data-dir ../data --validate
kr2akn emit-case --model /tmp/624945.model.json --data-dir ../data --validate

# emit → decode → emit for every law and case under data/raw
kr2akn roundtrip --all --data-dir ../data

# also compare the decoded model field by field with the parsed model
kr2akn roundtrip --all --data-dir ../data --strict-model

# preserve the original source blobs for exact model recovery
kr2akn emit --all --data-dir ../data --preserve-source-text
kr2akn roundtrip --all --data-dir ../data --strict-model --preserve-source-text

# one law / one case
kr2akn roundtrip --law-id 001706 --data-dir ../data
kr2akn roundtrip --case-id 624945 --target prec --data-dir ../data

roundtrip exits 0 only when every document is canonically identical and prints the first structural difference (element path and attribute/text) when one is not.

--strict-model additionally compares the decoded model with the parsed model field by field. It ignores raw ingestion maps and trailing whitespace on structured text. Without --preserve-source-text, source blobs such as Bylaw.text and judgment full_text may differ because the emitter stores the structured form. With --preserve-source-text, those blobs are written to kr:sourceTexts / kr:sourceText and the strict model check passes exactly.

What the decoder reconstructs

Statutes

  • FRBR identity: law ID, name, type, ministry, promulgation/effective dates, language, short/English/Hanja titles.
  • kr:lawMeta provenance: law key, type code, ministry code, promulgation number, revision type, source URL, raw path/SHA-256, fetch time.
  • Departments, contact department, and extra fields.
  • The full unit hierarchy (part … indent) with eId, num, heading, text, inline citation anchors, and authorial notes.
  • Per-unit effective dates through period and kr:effectiveDate, plus optional end dates through timeInterval end and kr:effectiveEndDate.
  • Bylaws as hcontainer name="부칙" with preamble and article units, and amendment-scoped inline citations regenerated from the paragraph text.
  • Annexes as attachment/doc/mainBody, including paragraph lines and tables with rowspan/colspan.
  • 개정문 / 제개정이유 components.

Judgments

  • kr:caseMeta: case ID, case number, name, court, decision date/type, case type, target, source, and raw provenance.
  • The packed 43-bit header from kr:caseHeader, including overflow flags.
  • judgmentBody sections, headings, and paragraph text (deliberate spacing is preserved; full-text whitespace is not collapsed).
  • references with case number, court, decision date/kind, law name, and article chain.

kr: provenance

AKN's core attribute group permits foreign-namespace attributes, so Korean fields that have no AKN equivalent travel on the element they describe:

<article eId="art_1" period="#period_20260701"
         kr:revisionType="일부개정"
         kr:effectiveDate="2026-01-01">
<hcontainer name="부칙" eId="hcontainer_buchik_1"
            kr:key="..." kr:promulgationDate="2026-01-01"
            kr:promulgationNo="00001">
<jurisprudence href="/akn/kr/judgment/456" showAs="대법원 2024다2 판결"
               eId="ref_1" kr:caseNo="2024다2" kr:court="대법원"/>

These attributes are optional: akn2kr also accepts third-party AKN and falls back to FRBR metadata, classification keywords, and showAs parsing.

Annex tables

kr2akn.statute.table parses box-drawing tables into rows and merged cells. It accepts both heavy (┏ ┳ ┓ ┠ ╋ ┨ ┗ ┷ ┛) and light (┌ ┬ ┐ ├ ┼ ┤ └ ┴ ┘) box-drawing characters. kr2akn.akn2kr.table renders the inverse geometry:

  • a cell's full text is placed in its first column, so the parser never splits a word across a colspan boundary;
  • a missing horizontal segment becomes a rowspan;
  • column widths are at least three display columns, the parser's minimum.

Three geometry details matter for real eflaw annexes:

  • Display width. Korean glyphs occupy two display columns while the borders are drawn in display columns too. Lines whose codepoint lengths are inconsistent are parsed in expanded display coordinates; codepoint-aligned tables (including the synthetic fixtures) keep the original coordinates.
  • Partial separators and wrapped cells. A header can subdivide only some of its columns (├───┼───┼───┬───┬───┤). The parser uses the widest full grid and maps the header's wider cells to colspan. Lines with no horizontal rule before them are treated as wrapped continuations of the previous row, unless every column carries new text (dense tables such as coefficient tables).
  • Multi-row and full-width rowspans. A rowspan cell stays in the parser's grid for every row it covers, so spans longer than two rows extend correctly. A rowspan that covers every column has no horizontal segment to anchor the merge; the renderer omits that separator. The parser now counts an empty full-width continuation line as another row of the same cell, recovering the span while keeping the text in the first visual row.

The real merged-table corpus includes 003793 (산업재해보상보험법 시행령) and an excerpt of the public 위험물안전관리법 시행령 [별표 1] table (tests/fixtures/merged_annex_table_2.txt). The latter exercises a ten-row rowspan and a nested colspan header.

A full-width rowspan whose text wraps over every covered row still cannot be distinguished from a multi-line cell. The text survives; only the exact span may be flattened.

Amendment-scoped citations

Bylaws and amendment documents are no longer emitted as plain text only. A bare 제N조 inside a 부칙 or 개정문 is not resolved against the consolidated main-law index, because it usually refers to the amended law or to the amending instrument. Instead it becomes an amendment-scoped reference:

<ref href="urn:kr:amendment:001697:10580:hcontainer_buchik_1:%EC%A0%9C3%EC%A1%B0%EC%A0%9C7%ED%98%B8"
     kr:refType="amendment">제3조제7호</ref>
  • urn:kr:amendment:{law_id}:{promulgation_no} identifies the instrument. A bylaw adds its eId so repeated bylaws stay distinguishable.
  • The cited article chain is percent-encoded in the final segment.
  • 이 부칙 제N조 / 같은 부칙 제N조 resolves to the bylaw's own article #eId when that article exists.
  • 이 법 / 같은 법 still resolves to the main law, because it names the amended law rather than an article inside the amendment.
  • kr:refType="amendment" is the only new attribute; ordinary main-law ref elements are unchanged.

The decoder reads the paragraph text back with the inline ref text, so the same references are regenerated deterministically on re-emission.

Temporal intervals

timeInterval now supports an end date. The reader accepts 시행종료일자, 효력종료일자, 폐지일자, 폐지일, or 종료일자 at the law level and 조문시행종료일자, 조문효력종료일자, or 조문폐지일자 on a unit. The current eflaw corpus has no such fields, so all existing intervals remain open-ended.

When an end date is present:

  • meta/lifecycle gets an eventRef type="repeal" for the end date;
  • the temporalGroup id becomes period_{start}_{end};
  • timeInterval carries both start and end;
  • the unit keeps kr:effectiveEndDate for lossless decoding.

Scope and limitations

The roundtrip covers document content and identifiers, not the raw ingestion bookkeeping:

  • Unit.raw, Bylaw.raw, and Annex.raw field maps are not emitted, so they are absent from decoded models. They remain in parse output.
  • source_key is preserved only when present (it is empty in the current corpus). revision_type, changed, and moved_from/moved_to are carried as kr: attributes.
  • Bylaw text is re-rendered from preamble and article units by default. With --preserve-source-text, the original blob is stored in kr:sourceTexts and restored exactly.
  • issues, summary, and full_text are reconstructed from judgment sections by default. With --preserve-source-text, the original fields are stored in kr:sourceText and restored exactly. The emitter only uses sections.

Validation status (2026-10-29)

  • pytest: 64 passed.
  • Laws: 11/11 AKN valid, 11/11 in-memory roundtrip, 11/11 strict-model roundtrip with --preserve-source-text.
  • Cases: 4/4 AKN valid, 4/4 in-memory roundtrip, 4/4 strict-model roundtrip with --preserve-source-text (prec 2, detc 2).
  • The 11th law (003793, 산업재해보상보험법 시행령) adds real merged and nested-header annex tables to the corpus.
  • The public 위험물안전관리법 시행령 [별표 1] excerpt adds a ten-row rowspan fixture.