groundextract 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. groundextract-0.1.2/.dockerignore +34 -0
  2. groundextract-0.1.2/.github/CODEOWNERS +24 -0
  3. groundextract-0.1.2/.github/ISSUE_TEMPLATE/bug_report.yml +108 -0
  4. groundextract-0.1.2/.github/ISSUE_TEMPLATE/config.yml +11 -0
  5. groundextract-0.1.2/.github/ISSUE_TEMPLATE/feature_request.yml +90 -0
  6. groundextract-0.1.2/.github/ISSUE_TEMPLATE/rule_pack_request.yml +105 -0
  7. groundextract-0.1.2/.github/PULL_REQUEST_TEMPLATE.md +69 -0
  8. groundextract-0.1.2/.github/workflows/ci.yml +79 -0
  9. groundextract-0.1.2/.gitignore +73 -0
  10. groundextract-0.1.2/CHANGELOG.md +355 -0
  11. groundextract-0.1.2/CITATION.cff +41 -0
  12. groundextract-0.1.2/CODE_OF_CONDUCT.md +142 -0
  13. groundextract-0.1.2/CONTRIBUTING.md +248 -0
  14. groundextract-0.1.2/Dockerfile +22 -0
  15. groundextract-0.1.2/LICENSE +201 -0
  16. groundextract-0.1.2/Makefile +22 -0
  17. groundextract-0.1.2/PKG-INFO +566 -0
  18. groundextract-0.1.2/README.ko.md +474 -0
  19. groundextract-0.1.2/README.md +527 -0
  20. groundextract-0.1.2/SECURITY.md +108 -0
  21. groundextract-0.1.2/bench/generate_golden.py +765 -0
  22. groundextract-0.1.2/bench/golden/balance_clean_01.json +13 -0
  23. groundextract-0.1.2/bench/golden/balance_misread_noncurrent_01.json +14 -0
  24. groundextract-0.1.2/bench/golden/gen_bs_001.json +53 -0
  25. groundextract-0.1.2/bench/golden/gen_bs_002.json +53 -0
  26. groundextract-0.1.2/bench/golden/gen_bs_003.json +53 -0
  27. groundextract-0.1.2/bench/golden/gen_bs_004.json +50 -0
  28. groundextract-0.1.2/bench/golden/gen_bs_005.json +53 -0
  29. groundextract-0.1.2/bench/golden/gen_bs_006.json +53 -0
  30. groundextract-0.1.2/bench/golden/gen_bs_007.json +53 -0
  31. groundextract-0.1.2/bench/golden/gen_bs_008.json +50 -0
  32. groundextract-0.1.2/bench/golden/gen_bs_009.json +53 -0
  33. groundextract-0.1.2/bench/golden/gen_bs_010.json +53 -0
  34. groundextract-0.1.2/bench/golden/gen_ctr_001.json +116 -0
  35. groundextract-0.1.2/bench/golden/gen_ctr_002.json +116 -0
  36. groundextract-0.1.2/bench/golden/gen_ctr_003.json +116 -0
  37. groundextract-0.1.2/bench/golden/gen_ctr_004.json +116 -0
  38. groundextract-0.1.2/bench/golden/gen_ctr_005.json +116 -0
  39. groundextract-0.1.2/bench/golden/gen_ctr_006.json +116 -0
  40. groundextract-0.1.2/bench/golden/gen_is_001.json +81 -0
  41. groundextract-0.1.2/bench/golden/gen_is_002.json +81 -0
  42. groundextract-0.1.2/bench/golden/gen_is_003.json +78 -0
  43. groundextract-0.1.2/bench/golden/gen_is_004.json +81 -0
  44. groundextract-0.1.2/bench/golden/gen_is_005.json +78 -0
  45. groundextract-0.1.2/bench/golden/gen_is_006.json +81 -0
  46. groundextract-0.1.2/bench/golden/gen_is_007.json +78 -0
  47. groundextract-0.1.2/bench/golden/gen_is_008.json +81 -0
  48. groundextract-0.1.2/bench/golden/gen_stmt_001.json +22 -0
  49. groundextract-0.1.2/bench/golden/gen_stmt_002.json +25 -0
  50. groundextract-0.1.2/bench/golden/gen_stmt_003.json +22 -0
  51. groundextract-0.1.2/bench/golden/gen_stmt_004.json +25 -0
  52. groundextract-0.1.2/bench/golden/gen_stmt_005.json +25 -0
  53. groundextract-0.1.2/bench/golden/gen_stmt_006.json +25 -0
  54. groundextract-0.1.2/bench/golden/gen_stmt_007.json +25 -0
  55. groundextract-0.1.2/bench/golden/gen_stmt_008.json +25 -0
  56. groundextract-0.1.2/bench/golden/gen_stmt_009.json +25 -0
  57. groundextract-0.1.2/bench/golden/gen_stmt_010.json +25 -0
  58. groundextract-0.1.2/bench/golden/gen_tax_001.json +43 -0
  59. groundextract-0.1.2/bench/golden/gen_tax_002.json +29 -0
  60. groundextract-0.1.2/bench/golden/gen_tax_003.json +32 -0
  61. groundextract-0.1.2/bench/golden/gen_tax_004.json +43 -0
  62. groundextract-0.1.2/bench/golden/gen_tax_005.json +32 -0
  63. groundextract-0.1.2/bench/golden/gen_tax_006.json +32 -0
  64. groundextract-0.1.2/bench/golden/gen_tax_007.json +46 -0
  65. groundextract-0.1.2/bench/golden/gen_tax_008.json +32 -0
  66. groundextract-0.1.2/bench/golden/gen_tax_009.json +32 -0
  67. groundextract-0.1.2/bench/golden/gen_tax_010.json +43 -0
  68. groundextract-0.1.2/bench/golden/gen_tax_011.json +32 -0
  69. groundextract-0.1.2/bench/golden/gen_tax_012.json +29 -0
  70. groundextract-0.1.2/bench/golden/gen_tax_013.json +46 -0
  71. groundextract-0.1.2/bench/golden/gen_tax_014.json +32 -0
  72. groundextract-0.1.2/bench/golden/gen_tax_015.json +32 -0
  73. groundextract-0.1.2/bench/golden/gen_tax_016.json +43 -0
  74. groundextract-0.1.2/bench/golden/gen_tax_017.json +29 -0
  75. groundextract-0.1.2/bench/golden/gen_tax_018.json +32 -0
  76. groundextract-0.1.2/bench/golden/gen_tax_019.json +46 -0
  77. groundextract-0.1.2/bench/golden/gen_tax_020.json +32 -0
  78. groundextract-0.1.2/bench/golden/gen_tax_021.json +32 -0
  79. groundextract-0.1.2/bench/golden/gen_tax_022.json +46 -0
  80. groundextract-0.1.2/bench/golden/gen_tax_023.json +32 -0
  81. groundextract-0.1.2/bench/golden/gen_tax_024.json +32 -0
  82. groundextract-0.1.2/bench/golden/gen_tax_025.json +46 -0
  83. groundextract-0.1.2/bench/golden/gen_tax_026.json +29 -0
  84. groundextract-0.1.2/bench/golden/gen_tax_027.json +29 -0
  85. groundextract-0.1.2/bench/golden/gen_tax_028.json +46 -0
  86. groundextract-0.1.2/bench/golden/gen_tax_029.json +32 -0
  87. groundextract-0.1.2/bench/golden/gen_tax_030.json +32 -0
  88. groundextract-0.1.2/bench/golden/statement_clean_01.json +9 -0
  89. groundextract-0.1.2/bench/golden/statement_hallucinated_debit_01.json +9 -0
  90. groundextract-0.1.2/bench/golden/tax_clean_01.json +10 -0
  91. groundextract-0.1.2/bench/golden/tax_clean_02.json +10 -0
  92. groundextract-0.1.2/bench/golden/tax_hallucinated_total_01.json +10 -0
  93. groundextract-0.1.2/bench/golden/tax_hallucinated_vat_01.json +10 -0
  94. groundextract-0.1.2/bench/sample_pdf/tax_invoice_sample.html +35 -0
  95. groundextract-0.1.2/bench/sample_pdf/tax_invoice_sample.pdf +0 -0
  96. groundextract-0.1.2/bench/verify_llm_only.py +62 -0
  97. groundextract-0.1.2/bench/verify_real.py +117 -0
  98. groundextract-0.1.2/groundextract/__init__.py +68 -0
  99. groundextract-0.1.2/groundextract/__main__.py +262 -0
  100. groundextract-0.1.2/groundextract/adapters/__init__.py +10 -0
  101. groundextract-0.1.2/groundextract/adapters/docling_adapter.py +206 -0
  102. groundextract-0.1.2/groundextract/bench.py +188 -0
  103. groundextract-0.1.2/groundextract/export.py +126 -0
  104. groundextract-0.1.2/groundextract/gate.py +383 -0
  105. groundextract-0.1.2/groundextract/grounding.py +414 -0
  106. groundextract-0.1.2/groundextract/llm/__init__.py +7 -0
  107. groundextract-0.1.2/groundextract/llm/base.py +35 -0
  108. groundextract-0.1.2/groundextract/llm/mock.py +23 -0
  109. groundextract-0.1.2/groundextract/llm/openweight.py +313 -0
  110. groundextract-0.1.2/groundextract/mcp_server.py +500 -0
  111. groundextract-0.1.2/groundextract/models.py +112 -0
  112. groundextract-0.1.2/groundextract/rules.py +372 -0
  113. groundextract-0.1.2/pyproject.toml +108 -0
  114. groundextract-0.1.2/rules/README.md +155 -0
  115. groundextract-0.1.2/rules/balance_sheet.yaml +27 -0
  116. groundextract-0.1.2/rules/corporate_tax_return.yaml +47 -0
  117. groundextract-0.1.2/rules/income_statement.yaml +41 -0
  118. groundextract-0.1.2/rules/statement.yaml +24 -0
  119. groundextract-0.1.2/rules/tax_invoice.yaml +31 -0
  120. groundextract-0.1.2/tests/test_balance_sheet.py +72 -0
  121. groundextract-0.1.2/tests/test_bench.py +233 -0
  122. groundextract-0.1.2/tests/test_cli.py +212 -0
  123. groundextract-0.1.2/tests/test_docling_adapter.py +158 -0
  124. groundextract-0.1.2/tests/test_export.py +89 -0
  125. groundextract-0.1.2/tests/test_fault_localization.py +167 -0
  126. groundextract-0.1.2/tests/test_filed_return_packs.py +205 -0
  127. groundextract-0.1.2/tests/test_gate.py +299 -0
  128. groundextract-0.1.2/tests/test_gate_bypass.py +404 -0
  129. groundextract-0.1.2/tests/test_grounding.py +135 -0
  130. groundextract-0.1.2/tests/test_mcp_server.py +426 -0
  131. groundextract-0.1.2/tests/test_openweight.py +294 -0
  132. groundextract-0.1.2/tests/test_rules.py +162 -0
  133. groundextract-0.1.2/viewer/README.md +41 -0
  134. groundextract-0.1.2/viewer/assets/balance_sheet_demo.png +0 -0
  135. groundextract-0.1.2/viewer/assets/tax_invoice_demo.png +0 -0
  136. groundextract-0.1.2/viewer/build_combined_fixture.py +277 -0
  137. groundextract-0.1.2/viewer/fixtures/clean.json +169 -0
  138. groundextract-0.1.2/viewer/fixtures/fixtures.js +341 -0
  139. groundextract-0.1.2/viewer/fixtures/image_data.js +352 -0
  140. groundextract-0.1.2/viewer/fixtures/injected.json +169 -0
  141. groundextract-0.1.2/viewer/index.html +353 -0
  142. groundextract-0.1.2/viewer/samples/balance_sheet_sample.pdf +0 -0
@@ -0,0 +1,34 @@
1
+ # The Dockerfile uses an allowlist of COPY paths, so this file is only a second
2
+ # line of defence (and keeps the build context small). Anything private listed
3
+ # here is also absent from the image because it was never named for copying.
4
+ .git
5
+ .github
6
+ .venv
7
+ venv
8
+ env
9
+ **/__pycache__
10
+ *.py[cod]
11
+ *.egg-info
12
+ build
13
+ dist
14
+ .pytest_cache
15
+ .mypy_cache
16
+ .ruff_cache
17
+ .coverage
18
+ coverage.xml
19
+ htmlcov
20
+ sbom
21
+ # Private / unmasked sources — mirrors .gitignore. Never let these into a layer.
22
+ local
23
+ submission
24
+ docs
25
+ CLAUDE.md
26
+ bench/sample_pdf/real_*
27
+ .env
28
+ .env.*
29
+ *.local
30
+ *.local.*
31
+ .idea
32
+ .vscode
33
+ .DS_Store
34
+ Thumbs.db
@@ -0,0 +1,24 @@
1
+ # Code owners for GroundExtract-KR.
2
+ # Listed owners are requested for review automatically on matching pull requests.
3
+ # Syntax: https://docs.github.com/articles/about-code-owners
4
+
5
+ # Default owner for everything in the repository.
6
+ * @sos37591-prog
7
+
8
+ # Trust boundary: the deterministic gate. Changes here can turn a discarded
9
+ # hallucination into a verified one, so they always need an owner review.
10
+ /groundextract/grounding.py @sos37591-prog
11
+ /groundextract/rules.py @sos37591-prog
12
+ /groundextract/gate.py @sos37591-prog
13
+ /groundextract/models.py @sos37591-prog
14
+
15
+ # Domain invariants and the labeled benchmark that measures them.
16
+ /rules/ @sos37591-prog
17
+ /bench/ @sos37591-prog
18
+ /groundextract/bench.py @sos37591-prog
19
+
20
+ # Public interfaces and supply chain.
21
+ /groundextract/mcp_server.py @sos37591-prog
22
+ /pyproject.toml @sos37591-prog
23
+ /.github/ @sos37591-prog
24
+ /SECURITY.md @sos37591-prog
@@ -0,0 +1,108 @@
1
+ name: Bug report
2
+ description: A wrong verdict, a crash, or anything that does not behave as documented
3
+ title: "bug: "
4
+ labels: ["bug"]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ Thanks for the report.
10
+
11
+ **Before you file:** if a *wrong number came out `verified`* (a gate bypass /
12
+ false negative), please do not open a public issue — report it privately via
13
+ [Security Advisory](https://github.com/sos37591-prog/groundextract-kr/security/advisories/new)
14
+ so the fix can ship before the technique is public. See [SECURITY.md](https://github.com/sos37591-prog/groundextract-kr/blob/main/SECURITY.md).
15
+
16
+ **Never paste real documents.** No real tax invoices, financial statements,
17
+ registration numbers, or personal data — reproduce with synthetic text.
18
+
19
+ - type: checkboxes
20
+ id: preflight
21
+ attributes:
22
+ label: Preflight
23
+ options:
24
+ - label: This is not a gate bypass (a hallucinated value coming out `verified`) — those go to Security Advisories
25
+ required: true
26
+ - label: My reproduction uses synthetic data only, with no real personal or corporate information
27
+ required: true
28
+ - label: I searched existing issues and did not find a duplicate
29
+ required: true
30
+
31
+ - type: dropdown
32
+ id: area
33
+ attributes:
34
+ label: Area
35
+ options:
36
+ - grounding (verbatim matching)
37
+ - rules (arithmetic rule engine / rule packs)
38
+ - gate (verdicts, confidence, sibling discard)
39
+ - bench (NumHall-KR, golden set generator)
40
+ - mcp_server
41
+ - llm / adapters (Ollama, Docling)
42
+ - viewer
43
+ - packaging / CI / docs
44
+ - not sure
45
+ validations:
46
+ required: true
47
+
48
+ - type: textarea
49
+ id: what-happened
50
+ attributes:
51
+ label: What happened
52
+ description: What you observed, and what you expected instead.
53
+ placeholder: |
54
+ `total` came out verified=... but I expected ...
55
+ validations:
56
+ required: true
57
+
58
+ - type: textarea
59
+ id: repro
60
+ attributes:
61
+ label: Minimal reproduction
62
+ description: |
63
+ The smallest self-contained snippet that shows the problem — document text,
64
+ extracted values, and `doc_type`. Synthetic data only.
65
+ render: python
66
+ value: |
67
+ from groundextract import ExtractedValue, load_rule_pack, run_gate
68
+
69
+ doc = "공급가액 1,000,000원\n세액 100,000원\n합계금액 1,100,000원"
70
+ pack = load_rule_pack("rules/tax_invoice.yaml")
71
+ values = [
72
+ ExtractedValue("supply", "1,000,000원", 1_000_000, "공급가액 1,000,000원"),
73
+ ]
74
+ for f in run_gate(values, doc, pack):
75
+ print(f.field, f.verdict.value, f.confidence, [c.detail for c in f.failed_checks])
76
+
77
+ - type: textarea
78
+ id: output
79
+ attributes:
80
+ label: Actual output
81
+ description: Full traceback, or the verdicts and failed checks you got.
82
+ render: text
83
+ validations:
84
+ required: true
85
+
86
+ - type: input
87
+ id: version
88
+ attributes:
89
+ label: Version / commit
90
+ description: "`python -c \"import groundextract; print(groundextract.__version__)\"`, or the commit SHA."
91
+ placeholder: "0.1.0 (or commit 5aeff8b)"
92
+ validations:
93
+ required: true
94
+
95
+ - type: input
96
+ id: env
97
+ attributes:
98
+ label: Python and OS
99
+ description: "`python -V` and your operating system."
100
+ placeholder: "Python 3.11.9 / Ubuntu 24.04"
101
+ validations:
102
+ required: true
103
+
104
+ - type: textarea
105
+ id: extra
106
+ attributes:
107
+ label: Anything else
108
+ description: Optional context — related issues, why the input is realistic, a suggested fix.
@@ -0,0 +1,11 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Report a gate bypass (security)
4
+ url: https://github.com/sos37591-prog/groundextract-kr/security/advisories/new
5
+ about: A wrong number that comes out `verified` is a false negative and the most serious bug class here. Report it privately, not as a public issue.
6
+ - name: Contributing guide and good first issues
7
+ url: https://github.com/sos37591-prog/groundextract-kr/blob/main/CONTRIBUTING.md
8
+ about: Dev setup, how to add a rule pack for a new document type, PR rules, and a list of good first issues.
9
+ - name: How the gate works (README)
10
+ url: https://github.com/sos37591-prog/groundextract-kr/blob/main/README.md
11
+ about: Quick start, the two disjoint checks, MCP usage, honest benchmark numbers, and known limitations.
@@ -0,0 +1,90 @@
1
+ name: Feature request
2
+ description: Propose a change to how the gate works, or a new capability
3
+ title: "feat: "
4
+ labels: ["enhancement"]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ Requesting support for a **new document type**? Use the
10
+ [Rule pack request](https://github.com/sos37591-prog/groundextract-kr/issues/new?template=rule_pack_request.yml)
11
+ template instead — it asks the right questions.
12
+
13
+ Two things shape what gets merged here:
14
+
15
+ - **Recall is the product.** A change that lets a wrong number through to
16
+ `verified` will not be accepted, however much precision it buys.
17
+ - **One runtime dependency.** PyYAML is the whole runtime dependency list.
18
+ Anything heavier has to live in an optional extra and be imported lazily.
19
+
20
+ - type: textarea
21
+ id: problem
22
+ attributes:
23
+ label: The problem
24
+ description: What goes wrong today? Concrete situations beat abstract wishes.
25
+ placeholder: |
26
+ When a statement is printed in 천원 units, every arithmetic rule fires a false
27
+ violation, so a clean document comes out fully discarded.
28
+ validations:
29
+ required: true
30
+
31
+ - type: textarea
32
+ id: proposal
33
+ attributes:
34
+ label: Proposed solution
35
+ description: What should happen instead? Sketch the API or behavior if you can.
36
+ validations:
37
+ required: true
38
+
39
+ - type: dropdown
40
+ id: area
41
+ attributes:
42
+ label: Area
43
+ options:
44
+ - grounding (verbatim matching)
45
+ - rules (rule engine expressiveness)
46
+ - gate (verdicts, confidence, fault localization)
47
+ - bench (metrics, golden set)
48
+ - mcp_server (tools, schema)
49
+ - llm / adapters
50
+ - viewer
51
+ - packaging / CI / docs
52
+ validations:
53
+ required: true
54
+
55
+ - type: dropdown
56
+ id: impact
57
+ attributes:
58
+ label: Effect on the benchmark
59
+ description: Your best guess. We report precision and recall movements in the open.
60
+ options:
61
+ - No effect on verdicts (tooling, docs, performance)
62
+ - Improves precision, recall unchanged at 100%
63
+ - Improves recall coverage (catches a class of error we currently miss)
64
+ - Trades recall for something else (needs strong justification)
65
+ - Not sure
66
+ validations:
67
+ required: true
68
+
69
+ - type: textarea
70
+ id: alternatives
71
+ attributes:
72
+ label: Alternatives considered
73
+ description: Including "why not just do it outside the gate?"
74
+
75
+ - type: input
76
+ id: deps
77
+ attributes:
78
+ label: New dependencies required
79
+ description: Name them, or write "none". Runtime dependencies beyond PyYAML are very unlikely to be accepted.
80
+ placeholder: "none"
81
+ validations:
82
+ required: true
83
+
84
+ - type: checkboxes
85
+ id: help
86
+ attributes:
87
+ label: Participation
88
+ options:
89
+ - label: I am willing to open a pull request for this
90
+ - label: I would like to discuss the design first
@@ -0,0 +1,105 @@
1
+ name: Rule pack request (new document type)
2
+ description: Ask for — or propose — arithmetic invariants for a document type we don't support yet
3
+ title: "rules: <document type>"
4
+ labels: ["rule-pack", "enhancement"]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ A rule pack is how this project grows. It is one YAML file listing the
10
+ arithmetic identities a document type must satisfy, plus three registrations
11
+ (rule pack → extractor field specs → MCP doc-type enum). See
12
+ [CONTRIBUTING.md](https://github.com/sos37591-prog/groundextract-kr/blob/main/CONTRIBUTING.md#adding-a-rule-pack-new-document-type).
13
+
14
+ You do **not** need to know Python to file this. Domain knowledge — *which
15
+ numbers on this form must add up to which other numbers* — is the part we
16
+ cannot look up, and it is what makes a rule pack worth having.
17
+
18
+ Currently supported: `tax_invoice`, `statement` (trial balance),
19
+ `balance_sheet`.
20
+
21
+ **Do not attach a real document.** Retype the layout with made-up numbers.
22
+
23
+ - type: input
24
+ id: doc-type
25
+ attributes:
26
+ label: Document type
27
+ description: Korean name and the proposed `doc_type` identifier (snake_case, ASCII).
28
+ placeholder: "급여명세서 (payslip) → payslip"
29
+ validations:
30
+ required: true
31
+
32
+ - type: textarea
33
+ id: why
34
+ attributes:
35
+ label: Why this document type
36
+ description: |
37
+ Who processes it, at what volume, and what goes wrong when a number is wrong?
38
+ A wrong number that someone eventually pays for is the strongest case.
39
+ placeholder: |
40
+ Payslips are re-keyed into payroll every month; a misread deduction is only
41
+ caught at year-end settlement, if at all.
42
+ validations:
43
+ required: true
44
+
45
+ - type: textarea
46
+ id: fields
47
+ attributes:
48
+ label: Fields to extract
49
+ description: >-
50
+ One per line as `field_name: 한국어 라벨 (English gloss)`. Field names are
51
+ ASCII snake_case and are what the rules reference.
52
+ render: text
53
+ value: |
54
+ gross_pay: 지급총액 (gross pay)
55
+ total_deductions: 공제총액 (total deductions)
56
+ net_pay: 실지급액 (net pay)
57
+
58
+ - type: textarea
59
+ id: invariants
60
+ attributes:
61
+ label: Arithmetic invariants
62
+ description: |
63
+ The identities that must hold. Plain arithmetic is fine — someone can translate
64
+ it into YAML. Note any legal rounding tolerance (`tol`), since Korean forms
65
+ often round to the won.
66
+ render: text
67
+ value: |
68
+ net_pay = gross_pay - total_deductions (tol 1 KRW)
69
+ gross_pay = sum of allowance line items (tol 1 KRW)
70
+
71
+ - type: textarea
72
+ id: sample
73
+ attributes:
74
+ label: Synthetic sample document
75
+ description: |
76
+ Retype the layout as plain text with **made-up** numbers that satisfy the
77
+ invariants above. This becomes the test fixture — it is the single most useful
78
+ thing you can attach.
79
+ render: text
80
+ placeholder: |
81
+ 급여명세서
82
+ 지급총액 3,500,000원
83
+ 공제총액 420,000원
84
+ 실지급액 3,080,000원
85
+ validations:
86
+ required: true
87
+
88
+ - type: textarea
89
+ id: pitfalls
90
+ attributes:
91
+ label: Formatting pitfalls
92
+ description: |
93
+ Negative sign conventions (△, parentheses), unit headers (단위: 천원), full-width
94
+ digits, values split across columns, subtotals that legitimately do not add up.
95
+ placeholder: "Amounts are printed in 천원 units; deductions use △ for negatives."
96
+
97
+ - type: checkboxes
98
+ id: participation
99
+ attributes:
100
+ label: Participation
101
+ options:
102
+ - label: I can write the YAML rule pack and the test myself
103
+ - label: I can review a rule pack someone else writes, as a domain expert
104
+ - label: I confirm the sample above is synthetic and contains no real personal or corporate data
105
+ required: true
@@ -0,0 +1,69 @@
1
+ <!--
2
+ Thanks for contributing to GroundExtract-KR.
3
+ One concern per PR. Delete sections that genuinely do not apply.
4
+ -->
5
+
6
+ ## What and why
7
+
8
+ <!-- What does this change, and what problem does it solve? Link the issue: Fixes #123 -->
9
+
10
+ ## Type of change
11
+
12
+ - [ ] Bug fix (a wrong verdict, a crash, or documented behavior that wasn't true)
13
+ - [ ] New rule pack / document type
14
+ - [ ] New feature or behavior change
15
+ - [ ] Docs only
16
+ - [ ] Build, CI, or packaging
17
+
18
+ ## Verification
19
+
20
+ ```text
21
+ python -m pytest -q →
22
+ python -m ruff check . →
23
+ ```
24
+
25
+ <!-- Paste the actual result lines. Both must be green. -->
26
+
27
+ - [ ] `python -m pytest -q` passes
28
+ - [ ] `python -m ruff check .` passes
29
+ - [ ] New behavior has a test; a bug fix has a regression test that fails without the fix
30
+
31
+ ## Benchmark impact
32
+
33
+ Run `python -m groundextract.bench` if this touches `grounding.py`, `rules.py`,
34
+ `gate.py`, the rule packs, or the golden set. Paste before/after — we track these
35
+ numbers in the open, including when they get worse.
36
+
37
+ ```text
38
+ before: NHR 19.9% -> 0.0% | recall 100.0% | precision 35.8% | grounded-accuracy 64.4%
39
+ after:
40
+ ```
41
+
42
+ - [ ] No benchmark change (this PR cannot affect verdicts)
43
+ - [ ] Numbers changed and are pasted above, with `CHANGELOG.md` updated
44
+ - [ ] **`Auto-Discard Recall` is still 100.0%** — if not, explain below why letting a
45
+ labeled bad value through is acceptable
46
+
47
+ ## Rule pack PRs only
48
+
49
+ - [ ] `rules/<doc_type>.yaml` added (`doc_type` matches the filename stem)
50
+ - [ ] `FIELD_SPECS["<doc_type>"]` added in `groundextract/llm/openweight.py`
51
+ - [ ] `<doc_type>` added to `_DOC_TYPE_SCHEMA["enum"]` in `groundextract/mcp_server.py`
52
+ - [ ] Test covering one clean document (all `verified`) and one violating document
53
+ (`discarded`)
54
+
55
+ ## Project invariants
56
+
57
+ - [ ] No new **runtime** dependency (PyYAML is the whole list; heavier things go in an
58
+ optional extra and are imported lazily)
59
+ - [ ] The gate path stays deterministic and offline — no randomness, no wall-clock
60
+ dependence, no network in `grounding.py` / `rules.py` / `gate.py`
61
+ - [ ] No real documents, personal data, registration numbers, or company-identifying
62
+ information anywhere in the diff — fixtures are synthetic
63
+ - [ ] `CHANGELOG.md` updated under `## [Unreleased]` for user-visible changes
64
+ - [ ] Docs updated if behavior or commands changed (`README.md` **and**
65
+ `README.ko.md` stay in sync)
66
+
67
+ ## Notes for reviewers
68
+
69
+ <!-- Trade-offs you made, things you are unsure about, what to look at first. -->
@@ -0,0 +1,79 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ # A moved tag would silently change what runs here, so third-party actions are
9
+ # pinned to commit SHAs (the trailing comment records the human-readable tag).
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ test:
15
+ runs-on: ubuntu-latest
16
+ timeout-minutes: 15
17
+ steps:
18
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
19
+
20
+ - name: Set up Python 3.11
21
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
22
+ with:
23
+ python-version: "3.11"
24
+
25
+ - name: Install package + dev deps
26
+ run: |
27
+ python -m pip install --upgrade pip
28
+ python -m pip install -e ".[dev]"
29
+
30
+ - name: Lint (ruff)
31
+ run: ruff check .
32
+
33
+ - name: Test (pytest + coverage)
34
+ run: pytest -q --cov=groundextract --cov-report=term-missing
35
+
36
+ sbom:
37
+ runs-on: ubuntu-latest
38
+ timeout-minutes: 15
39
+ needs: test
40
+ steps:
41
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
42
+
43
+ - name: Set up Python 3.11
44
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
45
+ with:
46
+ python-version: "3.11"
47
+
48
+ - name: Install package + SBOM tooling
49
+ run: |
50
+ python -m pip install --upgrade pip
51
+ # Install the extras a user can actually pull in, not just the runtime
52
+ # dep — the licence question lives in the optional surface. `docling`
53
+ # is left out deliberately: it drags in a multi-GB ML stack that would
54
+ # dominate this job, and it is scanned separately when it changes.
55
+ python -m pip install -e ".[fixture]"
56
+ python -m pip install pip-licenses cyclonedx-bom
57
+
58
+ - name: License allowlist (deny GPL/AGPL copyleft)
59
+ run: |
60
+ # Fail the build if a strong-copyleft license sneaks into deps.
61
+ pip-licenses --format=markdown --output-file=licenses.md
62
+ cat licenses.md
63
+ if pip-licenses --format=csv | grep -Ei 'GPL|AGPL' | grep -vi 'LGPL'; then
64
+ echo "::error::Disallowed copyleft license found in dependencies"; exit 1
65
+ fi
66
+
67
+ - name: Generate CycloneDX SBOM
68
+ run: |
69
+ mkdir -p sbom
70
+ cyclonedx-py environment -o sbom/bom.cdx.json
71
+ echo "SBOM generated:"; head -c 400 sbom/bom.cdx.json; echo
72
+
73
+ - name: Upload SBOM artifact
74
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
75
+ with:
76
+ name: sbom
77
+ path: |
78
+ sbom/bom.cdx.json
79
+ licenses.md
@@ -0,0 +1,73 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ venv/
10
+ env/
11
+
12
+ # Test / coverage
13
+ .pytest_cache/
14
+ .coverage
15
+ coverage.xml
16
+ htmlcov/
17
+ .mypy_cache/
18
+ .ruff_cache/
19
+
20
+ # SBOM (CI-generated; regenerated each run)
21
+ sbom/*.json
22
+
23
+ # Notebooks / JS tooling (viewer is build-step-free today; keep it that way)
24
+ .ipynb_checkpoints/
25
+ node_modules/
26
+ .tox/
27
+ .nox/
28
+
29
+ # Editors / OS
30
+ .idea/
31
+ .vscode/
32
+ .DS_Store
33
+ Thumbs.db
34
+ desktop.ini
35
+
36
+ # Secrets / local env.
37
+ # Patterns, not filenames: every rule below exists because *this* repository
38
+ # handles real 세무·재무 documents, and a denylist of known names only protects
39
+ # against the mistakes already made. `.env` alone missed `.env.production`;
40
+ # `*.local` alone missed `config.local.json`.
41
+ .env
42
+ .env.*
43
+ .envrc
44
+ *.local
45
+ *.local.*
46
+ *.key
47
+ *.pem
48
+ *.pfx
49
+ *.p12
50
+ *token*
51
+ credentials.json
52
+ service-account.json
53
+
54
+ # Runtime artifacts
55
+ *.log
56
+ *.sqlite
57
+ *.db
58
+
59
+ # Contest submission artifacts (개인정보/PII 포함 — keep out of public repo)
60
+ submission/
61
+
62
+ # Internal planning notes (직원 실명·IP·리소스 리스크 — 로컬 보관, 공개 저장소 제외)
63
+ docs/
64
+
65
+ # Local agent notes: contest schedule, submission paths, unreleased risks.
66
+ # Do not rely on a global gitignore for this — it must be excluded on any machine.
67
+ CLAUDE.md
68
+
69
+ # Unmasked real sources (PII / 실제 기업 재무·세무 원본) — never commit.
70
+ # Prefixed by convention rather than named one by one: the next real document
71
+ # someone drops in must be excluded before it exists, not after.
72
+ bench/sample_pdf/real_*
73
+ local/