cjk-semantic-split 0.1.0__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 (73) hide show
  1. cjk_semantic_split-0.1.0/.github/workflows/ci.yml +41 -0
  2. cjk_semantic_split-0.1.0/.gitignore +44 -0
  3. cjk_semantic_split-0.1.0/.hypothesis/.gitignore +9 -0
  4. cjk_semantic_split-0.1.0/.hypothesis/constants/2b726390919ba924 +4 -0
  5. cjk_semantic_split-0.1.0/.hypothesis/constants/447154ffb3443956 +4 -0
  6. cjk_semantic_split-0.1.0/.hypothesis/constants/448896e190e1a139 +4 -0
  7. cjk_semantic_split-0.1.0/.hypothesis/constants/44fdbf8fc7dec723 +4 -0
  8. cjk_semantic_split-0.1.0/.hypothesis/constants/7d0a29d738e12257 +4 -0
  9. cjk_semantic_split-0.1.0/.hypothesis/constants/a686eee4be3e3873 +4 -0
  10. cjk_semantic_split-0.1.0/.hypothesis/constants/a80ac1b9c26466c7 +4 -0
  11. cjk_semantic_split-0.1.0/.hypothesis/constants/cd01bf70c5c5b577 +4 -0
  12. cjk_semantic_split-0.1.0/.hypothesis/constants/dc1aa6aba7748109 +4 -0
  13. cjk_semantic_split-0.1.0/.hypothesis/constants/eb3de9a3db1e637c +4 -0
  14. cjk_semantic_split-0.1.0/.hypothesis/unicode_data/15.1.0/charmap.json.gz +0 -0
  15. cjk_semantic_split-0.1.0/.superpowers/sdd/.gitignore +1 -0
  16. cjk_semantic_split-0.1.0/.superpowers/sdd/progress.md +43 -0
  17. cjk_semantic_split-0.1.0/.superpowers/sdd/review-22d28a8..564c3e6.diff +227 -0
  18. cjk_semantic_split-0.1.0/.superpowers/sdd/review-564c3e6..0b4b3f6.diff +146 -0
  19. cjk_semantic_split-0.1.0/.superpowers/sdd/task-1-brief.md +237 -0
  20. cjk_semantic_split-0.1.0/.superpowers/sdd/task-1-report.md +79 -0
  21. cjk_semantic_split-0.1.0/.superpowers/sdd/task-10-report.md +12 -0
  22. cjk_semantic_split-0.1.0/.superpowers/sdd/task-11-report.md +7 -0
  23. cjk_semantic_split-0.1.0/.superpowers/sdd/task-12-report.md +8 -0
  24. cjk_semantic_split-0.1.0/.superpowers/sdd/task-13-report.md +13 -0
  25. cjk_semantic_split-0.1.0/.superpowers/sdd/task-14-report.md +7 -0
  26. cjk_semantic_split-0.1.0/.superpowers/sdd/task-15-report.md +11 -0
  27. cjk_semantic_split-0.1.0/.superpowers/sdd/task-2-brief.md +161 -0
  28. cjk_semantic_split-0.1.0/.superpowers/sdd/task-2-report.md +76 -0
  29. cjk_semantic_split-0.1.0/.superpowers/sdd/task-3-brief.md +249 -0
  30. cjk_semantic_split-0.1.0/.superpowers/sdd/task-3-report.md +74 -0
  31. cjk_semantic_split-0.1.0/.superpowers/sdd/task-4-report.md +9 -0
  32. cjk_semantic_split-0.1.0/.superpowers/sdd/task-5-report.md +19 -0
  33. cjk_semantic_split-0.1.0/.superpowers/sdd/task-6-report.md +13 -0
  34. cjk_semantic_split-0.1.0/.superpowers/sdd/task-7-report.md +21 -0
  35. cjk_semantic_split-0.1.0/.superpowers/sdd/task-8-report.md +14 -0
  36. cjk_semantic_split-0.1.0/.superpowers/sdd/task-9-report.md +19 -0
  37. cjk_semantic_split-0.1.0/LICENSE +21 -0
  38. cjk_semantic_split-0.1.0/PKG-INFO +102 -0
  39. cjk_semantic_split-0.1.0/README.md +55 -0
  40. cjk_semantic_split-0.1.0/docs/superpowers/plans/2026-07-16-cjk-semantic-split-implementation.md +2813 -0
  41. cjk_semantic_split-0.1.0/docs/superpowers/specs/2026-07-16-cjk-semantic-split-design.md +393 -0
  42. cjk_semantic_split-0.1.0/pyproject.toml +67 -0
  43. cjk_semantic_split-0.1.0/scripts/__init__.py +0 -0
  44. cjk_semantic_split-0.1.0/scripts/harvest.py +52 -0
  45. cjk_semantic_split-0.1.0/scripts/review_conflicts.py +24 -0
  46. cjk_semantic_split-0.1.0/src/chinese_decompose/__init__.py +2 -0
  47. cjk_semantic_split-0.1.0/src/chinese_decompose/__main__.py +134 -0
  48. cjk_semantic_split-0.1.0/src/chinese_decompose/api.py +135 -0
  49. cjk_semantic_split-0.1.0/src/chinese_decompose/data/decomp_l1.json +12 -0
  50. cjk_semantic_split-0.1.0/src/chinese_decompose/data/decomp_l2.json +1 -0
  51. cjk_semantic_split-0.1.0/src/chinese_decompose/data/decomp_l3.json +5 -0
  52. cjk_semantic_split-0.1.0/src/chinese_decompose/data/decomp_patches.json +1 -0
  53. cjk_semantic_split-0.1.0/src/chinese_decompose/data/primitives.json +21 -0
  54. cjk_semantic_split-0.1.0/src/chinese_decompose/dsl.py +102 -0
  55. cjk_semantic_split-0.1.0/src/chinese_decompose/errors.py +26 -0
  56. cjk_semantic_split-0.1.0/src/chinese_decompose/loader.py +132 -0
  57. cjk_semantic_split-0.1.0/src/chinese_decompose/primitives.py +41 -0
  58. cjk_semantic_split-0.1.0/src/chinese_decompose/split.py +52 -0
  59. cjk_semantic_split-0.1.0/src/chinese_decompose/structures.py +127 -0
  60. cjk_semantic_split-0.1.0/tests/__init__.py +1 -0
  61. cjk_semantic_split-0.1.0/tests/conftest.py +22 -0
  62. cjk_semantic_split-0.1.0/tests/test_api.py +67 -0
  63. cjk_semantic_split-0.1.0/tests/test_cli.py +70 -0
  64. cjk_semantic_split-0.1.0/tests/test_compose.py +96 -0
  65. cjk_semantic_split-0.1.0/tests/test_decompose.py +86 -0
  66. cjk_semantic_split-0.1.0/tests/test_dsl.py +116 -0
  67. cjk_semantic_split-0.1.0/tests/test_harvest.py +39 -0
  68. cjk_semantic_split-0.1.0/tests/test_loader.py +121 -0
  69. cjk_semantic_split-0.1.0/tests/test_primitives.py +67 -0
  70. cjk_semantic_split-0.1.0/tests/test_properties.py +77 -0
  71. cjk_semantic_split-0.1.0/tests/test_smoke.py +13 -0
  72. cjk_semantic_split-0.1.0/tests/test_split.py +76 -0
  73. cjk_semantic_split-0.1.0/tests/test_structures.py +76 -0
@@ -0,0 +1,41 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ name: Test (Python ${{ matrix.python-version }})
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Set up Python ${{ matrix.python-version }}
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: ${{ matrix.python-version }}
25
+ cache: pip
26
+
27
+ - name: Install dependencies
28
+ run: |
29
+ python -m pip install --upgrade pip
30
+ pip install -e ".[dev]"
31
+
32
+ - name: Lint with ruff
33
+ run: ruff check src tests
34
+
35
+ - name: Run tests with coverage
36
+ run: |
37
+ pytest -v --cov=chinese_decompose --cov-report=xml --cov-report=term
38
+
39
+ - name: Check coverage gate
40
+ run: |
41
+ coverage report --fail-under=80
@@ -0,0 +1,44 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+ MANIFEST
23
+
24
+ # Virtual envs
25
+ .venv/
26
+ venv/
27
+ env/
28
+ ENV/
29
+
30
+ # Test / coverage
31
+ .pytest_cache/
32
+ .coverage
33
+ .coverage.*
34
+ htmlcov/
35
+ .tox/
36
+ .cache
37
+ coverage.xml
38
+ *.cover
39
+
40
+ # IDE
41
+ .idea/
42
+ .vscode/
43
+ *.swp
44
+ .DS_Store
@@ -0,0 +1,9 @@
1
+ # This .gitignore file was automatically created by Hypothesis. Hypothesis gitignores
2
+ # .hypothesis by default, because we generally recommend that .hypothesis not be checked
3
+ # into version control.
4
+ #
5
+ # If you *would* like to check .hypothesis into version control, you should delete this
6
+ # file. Hypothesis will not re-create this .gitignore unless .hypothesis is deleted (and
7
+ # if it does, that's a bug - please report it!)
8
+
9
+ *
@@ -0,0 +1,4 @@
1
+ # file: /Users/mac/Develop/gitstore/full_brain_intelligence/Semantic-Character-Splitting-and-Structural-Coding-for-Chinese-Script/src/chinese_decompose/dsl.py
2
+ # hypothesis_version: 6.156.6
3
+
4
+ [':', 'Empty DSL string', 'Missing root segment', '|']
@@ -0,0 +1,4 @@
1
+ # file: /Users/mac/Develop/gitstore/full_brain_intelligence/Semantic-Character-Splitting-and-Structural-Coding-for-Chinese-Script/src/chinese_decompose/loader.py
2
+ # hypothesis_version: 6.156.6
3
+
4
+ ['Loader | None', '_meta', 'data', 'decomp_l1.json', 'decomp_l2.json', 'decomp_l3.json', 'decomp_patches.json', 'utf-8']
@@ -0,0 +1,4 @@
1
+ # file: /Users/mac/Develop/gitstore/full_brain_intelligence/Semantic-Character-Splitting-and-Structural-Coding-for-Chinese-Script/src/chinese_decompose/dsl.py
2
+ # hypothesis_version: 6.156.6
3
+
4
+ [':', 'Empty DSL string', 'Missing root segment', '|']
@@ -0,0 +1,4 @@
1
+ # file: /Users/mac/Develop/gitstore/full_brain_intelligence/Semantic-Character-Splitting-and-Structural-Coding-for-Chinese-Script/src/chinese_decompose/api.py
2
+ # hypothesis_version: 6.156.6
3
+
4
+ [':', '?', 'DecompositionTree', 'decompose', '|']
@@ -0,0 +1,4 @@
1
+ # file: /Users/mac/Develop/gitstore/full_brain_intelligence/Semantic-Character-Splitting-and-Structural-Coding-for-Chinese-Script/src/chinese_decompose/primitives.py
2
+ # hypothesis_version: 6.156.6
3
+
4
+ ['data', 'primitives', 'primitives.json', 'utf-8']
@@ -0,0 +1,4 @@
1
+ # file: /Users/mac/Develop/gitstore/full_brain_intelligence/Semantic-Character-Splitting-and-Structural-Coding-for-Chinese-Script/src/chinese_decompose/errors.py
2
+ # hypothesis_version: 6.156.6
3
+
4
+ []
@@ -0,0 +1,4 @@
1
+ # file: /Users/mac/Develop/gitstore/full_brain_intelligence/Semantic-Character-Splitting-and-Structural-Coding-for-Chinese-Script/src/chinese_decompose/structures.py
2
+ # hypothesis_version: 6.156.6
3
+
4
+ []
@@ -0,0 +1,4 @@
1
+ # file: /Users/mac/Develop/gitstore/full_brain_intelligence/Semantic-Character-Splitting-and-Structural-Coding-for-Chinese-Script/src/chinese_decompose/__init__.py
2
+ # hypothesis_version: 6.156.6
3
+
4
+ ['0.1.0']
@@ -0,0 +1,4 @@
1
+ # file: /Users/mac/Develop/gitstore/full_brain_intelligence/Semantic-Character-Splitting-and-Structural-Coding-for-Chinese-Script/src/chinese_decompose/split.py
2
+ # hypothesis_version: 6.156.6
3
+
4
+ ['Loader']
@@ -0,0 +1,4 @@
1
+ # file: /Users/mac/Develop/gitstore/full_brain_intelligence/Semantic-Character-Splitting-and-Structural-Coding-for-Chinese-Script/src/chinese_decompose/api.py
2
+ # hypothesis_version: 6.156.6
3
+
4
+ [':', '?', 'DecompositionTree', 'decompose', '|']
@@ -0,0 +1,43 @@
1
+ # Subagent-Driven Development Progress Ledger
2
+
3
+ ## Completed Tasks
4
+
5
+ - **Task 1: Project Scaffold** — DONE (commits `22d28a8`..`564c3e6`). Fix: relaxed `hatch>=1.18` to `hatch>=1.0`.
6
+ - **Task 2: Position Codes Module** — DONE (commit `0b4b3f6`). Brief self-contradiction: 98 was in code but excluded from set; resolved by keeping constant, excluding from set.
7
+ - **Task 3: Errors + Composition Operator** — DONE (commit `9f3918d`). Brief had contradictory assertions for `compose(P_TOP, P_BOTTOM)`; corrected to match spec §2.3.
8
+ - **Task 4: Primitive Set Module** — DONE (commit after `0b4b3f6`). Inline execution.
9
+ - **Task 5: Multi-Tier Loader** — DONE. Brief had `女旁` (2-char) as sub-component and 踩/采 nested inside `_meta`; both fixed.
10
+ - **Task 6: Recursive Split Walker** — DONE. Side fix: extended COMPOSE_TABLE row 0 to include surround codes 90-99 (center passthrough).
11
+ - **Task 7: DecompositionTree** — DONE. Brief had wrong atoms/positions expectations and a circular-import trap; resolved.
12
+ - **Task 8: DSL Serializer + Parser** — DONE. Brief's parser raised on bare last segment; relaxed parser.
13
+ - **Task 9: Public decompose() API** — DONE. Removed circular api.py ↔ dsl.py import.
14
+ - **Task 10: CLI** — DONE. CLI now includes exception class name in error output.
15
+ - **Task 11: README** — DONE.
16
+ - **Task 12: Property Tests** — DONE. Suppressed `function_scoped_fixture` health check.
17
+ - **Task 13: Data Expansion** — DONE. Brief's `decomp_l1.json` had `休` listed twice (one is silent dup).
18
+ - **Task 14: CI Workflow** — DONE. Removed Codecov upload (no token configured).
19
+ - **Task 15: Harvest Script** — DONE.
20
+ - **Post-Task fix**: 囗 missing from primitives set; added.
21
+
22
+ ## Final Stats
23
+
24
+ - 78 tests passing, 90% test coverage (above 80% gate).
25
+ - 19 commits since the spec doc.
26
+ - All 15 plan tasks complete.
27
+
28
+ ## In-Progress Tasks
29
+
30
+ (none)
31
+
32
+ ## Blocked Tasks
33
+
34
+ (none)
35
+
36
+ ## Open Items (Not Blocking — for Final Review)
37
+
38
+ - **`pyproject.toml:25` authors = []**: PyPI upload will fail until a real author is configured.
39
+ - **`pyproject.toml:[project.urls]`**: placeholder `https://github.com/USER/REPO` should be set to the real repo URL before publish.
40
+ - **`tests/conftest.py:sample_fixture_data`** fixture: defined but not used by any current test (scaffolding for future tasks).
41
+ - **Strip codes (14/16/34/36)**: have no composition rules in spec §2.3 table; calling `compose(14, X)` raises CompositionError. Acceptable per spec since these are wall-strip positions, not standalone.
42
+ - **`P_SURROUND_LL_LO (98)`**: orphan named constant; semantics TBD per spec.
43
+ - **Codecov token**: not configured in CI; coverage gate via `--fail-under=80` works without it.
@@ -0,0 +1,227 @@
1
+ # Review package: 22d28a8..HEAD
2
+
3
+ ## Commits
4
+ 564c3e6 fix(deps): relax hatch pin to >=1.0 (1.18 doesn't exist; 1.17 is latest)
5
+ b832b39 feat: scaffold python project with hatchling
6
+
7
+ ## Files changed
8
+ .gitignore | 44 ++++++++++++++++++++++++++
9
+ LICENSE | 21 +++++++++++++
10
+ pyproject.toml | 65 +++++++++++++++++++++++++++++++++++++++
11
+ src/chinese_decompose/__init__.py | 2 ++
12
+ tests/__init__.py | 1 +
13
+ tests/conftest.py | 22 +++++++++++++
14
+ tests/test_smoke.py | 13 ++++++++
15
+ 7 files changed, 168 insertions(+)
16
+
17
+ ## Diff
18
+ diff --git a/.gitignore b/.gitignore
19
+ new file mode 100644
20
+ index 0000000..36b59b4
21
+ --- /dev/null
22
+ +++ b/.gitignore
23
+ @@ -0,0 +1,44 @@
24
+ +# Byte-compiled / optimized / DLL files
25
+ +__pycache__/
26
+ +*.py[cod]
27
+ +*$py.class
28
+ +*.so
29
+ +.Python
30
+ +build/
31
+ +develop-eggs/
32
+ +dist/
33
+ +downloads/
34
+ +eggs/
35
+ +.eggs/
36
+ +lib/
37
+ +lib64/
38
+ +parts/
39
+ +sdist/
40
+ +var/
41
+ +wheels/
42
+ +*.egg-info/
43
+ +.installed.cfg
44
+ +*.egg
45
+ +MANIFEST
46
+ +
47
+ +# Virtual envs
48
+ +.venv/
49
+ +venv/
50
+ +env/
51
+ +ENV/
52
+ +
53
+ +# Test / coverage
54
+ +.pytest_cache/
55
+ +.coverage
56
+ +.coverage.*
57
+ +htmlcov/
58
+ +.tox/
59
+ +.cache
60
+ +coverage.xml
61
+ +*.cover
62
+ +
63
+ +# IDE
64
+ +.idea/
65
+ +.vscode/
66
+ +*.swp
67
+ +.DS_Store
68
+ diff --git a/LICENSE b/LICENSE
69
+ new file mode 100644
70
+ index 0000000..14fac91
71
+ --- /dev/null
72
+ +++ b/LICENSE
73
+ @@ -0,0 +1,21 @@
74
+ +MIT License
75
+ +
76
+ +Copyright (c) 2026
77
+ +
78
+ +Permission is hereby granted, free of charge, to any person obtaining a copy
79
+ +of this software and associated documentation files (the "Software"), to deal
80
+ +in the Software without restriction, including without limitation the rights
81
+ +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
82
+ +copies of the Software, and to permit persons to whom the Software is
83
+ +furnished to do so, subject to the following conditions:
84
+ +
85
+ +The above copyright notice and this permission notice shall be included in all
86
+ +copies or substantial portions of the Software.
87
+ +
88
+ +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
89
+ +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
90
+ +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
91
+ +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
92
+ +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
93
+ +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
94
+ +SOFTWARE.
95
+ diff --git a/pyproject.toml b/pyproject.toml
96
+ new file mode 100644
97
+ index 0000000..26d4258
98
+ --- /dev/null
99
+ +++ b/pyproject.toml
100
+ @@ -0,0 +1,65 @@
101
+ +[build-system]
102
+ +requires = ["hatchling>=1.18"]
103
+ +build-backend = "hatchling.build"
104
+ +
105
+ +[project]
106
+ +name = "cjk-semantic-split"
107
+ +version = "0.1.0"
108
+ +description = "Recursive atomic decomposition of CJK characters with 9-grid spatial encoding"
109
+ +readme = "README.md"
110
+ +requires-python = ">=3.9"
111
+ +license = { file = "LICENSE" }
112
+ +authors = []
113
+ +keywords = ["cjk", "chinese", "decomposition", "ideograph"]
114
+ +classifiers = [
115
+ + "Programming Language :: Python :: 3",
116
+ + "Programming Language :: Python :: 3.9",
117
+ + "Programming Language :: Python :: 3.10",
118
+ + "Programming Language :: Python :: 3.11",
119
+ + "Programming Language :: Python :: 3.12",
120
+ + "License :: OSI Approved :: MIT License",
121
+ + "Operating System :: OS Independent",
122
+ + "Topic :: Software Development :: Libraries :: Python Modules",
123
+ +]
124
+ +dependencies = []
125
+ +
126
+ +[project.optional-dependencies]
127
+ +dev = [
128
+ + "pytest>=7.0",
129
+ + "pytest-cov>=4.0",
130
+ + "hypothesis>=6.0",
131
+ + "ruff>=0.1",
132
+ + "hatch>=1.0",
133
+ +]
134
+ +
135
+ +[project.urls]
136
+ +"Source" = "https://github.com/USER/REPO"
137
+ +"Bug Tracker" = "https://github.com/USER/REPO/issues"
138
+ +
139
+ +[tool.hatch.build.targets.wheel]
140
+ +packages = ["src/chinese_decompose"]
141
+ +include = ["src/chinese_decompose/data/*.json"]
142
+ +
143
+ +[tool.pytest.ini_options]
144
+ +testpaths = ["tests"]
145
+ +pythonpath = ["src"]
146
+ +addopts = "-ra --strict-markers"
147
+ +
148
+ +[tool.coverage.run]
149
+ +source = ["src/chinese_decompose"]
150
+ +
151
+ +[tool.coverage.report]
152
+ +exclude_lines = [
153
+ + "pragma: no cover",
154
+ + "raise NotImplementedError",
155
+ + "if __name__ == .__main__.:",
156
+ +]
157
+ +show_missing = true
158
+ +fail_under = 80
159
+ +
160
+ +[tool.ruff]
161
+ +line-length = 100
162
+ +target-version = "py39"
163
+ +
164
+ +[tool.ruff.lint]
165
+ +select = ["E", "F", "W", "I", "N", "UP"]
166
+ diff --git a/src/chinese_decompose/__init__.py b/src/chinese_decompose/__init__.py
167
+ new file mode 100644
168
+ index 0000000..c94b366
169
+ --- /dev/null
170
+ +++ b/src/chinese_decompose/__init__.py
171
+ @@ -0,0 +1,2 @@
172
+ +"""chinese_decompose: recursive atomic decomposition of CJK characters."""
173
+ +__version__ = "0.1.0"
174
+ diff --git a/tests/__init__.py b/tests/__init__.py
175
+ new file mode 100644
176
+ index 0000000..38bb211
177
+ --- /dev/null
178
+ +++ b/tests/__init__.py
179
+ @@ -0,0 +1 @@
180
+ +"""Test package."""
181
+ diff --git a/tests/conftest.py b/tests/conftest.py
182
+ new file mode 100644
183
+ index 0000000..225de0f
184
+ --- /dev/null
185
+ +++ b/tests/conftest.py
186
+ @@ -0,0 +1,22 @@
187
+ +"""Shared test fixtures."""
188
+ +import pytest
189
+ +
190
+ +
191
+ +@pytest.fixture
192
+ +def sample_fixture_data() -> dict[str, list[list]]:
193
+ + """Tiny hand-curated decomposition data for tests.
194
+ +
195
+ + Each entry: char -> [[component_char, position_code], ...].
196
+ + Positions are 2-digit codes per spec §2.1.
197
+ + """
198
+ + return {
199
+ + # One-level primitive (no entry needed but included for completeness)
200
+ + "木": [],
201
+ + # 2-cell horizontal split
202
+ + "好": [["女", 30], ["子", 40]],
203
+ + # 2-cell with nested decomposition
204
+ + "踩": [["足", 30], ["采", 40]],
205
+ + "采": [["爪", 10], ["木", 20]],
206
+ + # 3-cell horizontal + vertical composition
207
+ + "囚": [["囗", 90], ["人", 0]],
208
+ + }
209
+ diff --git a/tests/test_smoke.py b/tests/test_smoke.py
210
+ new file mode 100644
211
+ index 0000000..f81646c
212
+ --- /dev/null
213
+ +++ b/tests/test_smoke.py
214
+ @@ -0,0 +1,13 @@
215
+ +"""Smoke tests: package imports cleanly."""
216
+ +
217
+ +
218
+ +def test_package_imports():
219
+ + """Verify the package and version are importable."""
220
+ + import chinese_decompose
221
+ +
222
+ + assert chinese_decompose.__version__ == "0.1.0"
223
+ +
224
+ +
225
+ +def test_pytest_runs():
226
+ + """Sanity check: pytest discovers and runs this test."""
227
+ + assert True
@@ -0,0 +1,146 @@
1
+ # Review package: 564c3e6..HEAD
2
+
3
+ ## Commits
4
+ 0b4b3f6 feat(structures): add 2-digit position code constants
5
+
6
+ ## Files changed
7
+ src/chinese_decompose/structures.py | 47 +++++++++++++++++++++++
8
+ tests/test_structures.py | 76 +++++++++++++++++++++++++++++++++++++
9
+ 2 files changed, 123 insertions(+)
10
+
11
+ ## Diff
12
+ diff --git a/src/chinese_decompose/structures.py b/src/chinese_decompose/structures.py
13
+ new file mode 100644
14
+ index 0000000..b70bd35
15
+ --- /dev/null
16
+ +++ b/src/chinese_decompose/structures.py
17
+ @@ -0,0 +1,47 @@
18
+ +"""Position code constants for the 9-grid spatial encoding.
19
+ +
20
+ +Codes are 2-digit numbers (spec §2.1): digit A is the spatial zone, digit B
21
+ +is the sub-zone within the parent zone (0 means atomic slot, non-zero for
22
+ +specific sub-strips or surround variants).
23
+ +"""
24
+ +from __future__ import annotations
25
+ +
26
+ +# Atomic single-slot positions
27
+ +P_CENTER = 0
28
+ +P_TOP = 10
29
+ +P_BOTTOM = 20
30
+ +P_LEFT = 30
31
+ +P_RIGHT = 40
32
+ +P_TOP_LEFT = 50
33
+ +P_TOP_RIGHT = 60
34
+ +P_BOTTOM_LEFT = 70
35
+ +P_BOTTOM_RIGHT = 80
36
+ +
37
+ +# 4-cell layout strip positions
38
+ +P_STRIP_VERT_RIGHT = 14 # inner-right-edge of upper zone (e.g. 目's right strip)
39
+ +P_STRIP_VERT_LEFT = 16 # inner-left-edge of lower zone
40
+ +P_STRIP_HORIZ_RIGHT = 34 # inner-right-edge of left zone (e.g. 罒's right strip)
41
+ +P_STRIP_HORIZ_LEFT = 36 # inner-left-edge of right zone
42
+ +
43
+ +# Surround envelopes (90-99)
44
+ +P_SURROUND_FULL = 90
45
+ +P_SURROUND_BELOW = 91
46
+ +P_SURROUND_LEFT = 92
47
+ +P_SURROUND_ABOVE = 93
48
+ +P_SURROUND_RIGHT = 94
49
+ +P_SURROUND_UL = 95
50
+ +P_SURROUND_LR = 96
51
+ +P_SURROUND_UR = 97
52
+ +P_SURROUND_LL_LO = 98 # lower-bottom extension (semantics TBD per spec)
53
+ +P_SURROUND_LL = 99
54
+ +
55
+ +VALID_POSITIONS: frozenset[int] = frozenset({
56
+ + P_CENTER, P_TOP, P_BOTTOM, P_LEFT, P_RIGHT,
57
+ + P_TOP_LEFT, P_TOP_RIGHT, P_BOTTOM_LEFT, P_BOTTOM_RIGHT,
58
+ + P_STRIP_VERT_RIGHT, P_STRIP_VERT_LEFT,
59
+ + P_STRIP_HORIZ_RIGHT, P_STRIP_HORIZ_LEFT,
60
+ + P_SURROUND_FULL, P_SURROUND_BELOW, P_SURROUND_LEFT,
61
+ + P_SURROUND_ABOVE, P_SURROUND_RIGHT, P_SURROUND_UL,
62
+ + P_SURROUND_LR, P_SURROUND_UR, P_SURROUND_LL,
63
+ + # NOTE: P_SURROUND_LL_LO (98) intentionally excluded — semantics TBD per spec §2.1
64
+ +})
65
+ diff --git a/tests/test_structures.py b/tests/test_structures.py
66
+ new file mode 100644
67
+ index 0000000..79ecfeb
68
+ --- /dev/null
69
+ +++ b/tests/test_structures.py
70
+ @@ -0,0 +1,76 @@
71
+ +"""Tests for structures.py — position codes and constants."""
72
+ +from chinese_decompose.structures import (
73
+ + VALID_POSITIONS,
74
+ + P_CENTER,
75
+ + P_TOP,
76
+ + P_BOTTOM,
77
+ + P_LEFT,
78
+ + P_RIGHT,
79
+ + P_TOP_LEFT,
80
+ + P_TOP_RIGHT,
81
+ + P_BOTTOM_LEFT,
82
+ + P_BOTTOM_RIGHT,
83
+ + P_STRIP_VERT_RIGHT,
84
+ + P_STRIP_VERT_LEFT,
85
+ + P_STRIP_HORIZ_RIGHT,
86
+ + P_STRIP_HORIZ_LEFT,
87
+ + P_SURROUND_FULL,
88
+ + P_SURROUND_BELOW,
89
+ + P_SURROUND_LEFT,
90
+ + P_SURROUND_ABOVE,
91
+ + P_SURROUND_RIGHT,
92
+ + P_SURROUND_UL,
93
+ + P_SURROUND_LR,
94
+ + P_SURROUND_UR,
95
+ + P_SURROUND_LL_LO,
96
+ + P_SURROUND_LL,
97
+ +)
98
+ +
99
+ +
100
+ +def test_valid_positions_count():
101
+ + """All 22 valid 2-digit position codes are exposed."""
102
+ + assert len(VALID_POSITIONS) == 22
103
+ +
104
+ +
105
+ +def test_valid_positions_contents():
106
+ + """Specific 2-digit codes from spec §2.1 must be in the set."""
107
+ + expected = {0, 10, 20, 30, 40, 50, 60, 70, 80,
108
+ + 14, 16, 34, 36,
109
+ + 90, 91, 92, 93, 94, 95, 96, 97, 99}
110
+ + # NOTE: 98 not exposed by named constant (semantics TBD per spec)
111
+ + assert expected == VALID_POSITIONS
112
+ +
113
+ +
114
+ +def test_atomic_positions_are_in_set():
115
+ + """Atomic single-slot positions."""
116
+ + assert P_CENTER == 0
117
+ + assert P_TOP == 10
118
+ + assert P_BOTTOM == 20
119
+ + assert P_LEFT == 30
120
+ + assert P_RIGHT == 40
121
+ + assert P_TOP_LEFT == 50
122
+ + assert P_TOP_RIGHT == 60
123
+ + assert P_BOTTOM_LEFT == 70
124
+ + assert P_BOTTOM_RIGHT == 80
125
+ +
126
+ +
127
+ +def test_strip_positions():
128
+ + """4-cell layout strip positions."""
129
+ + assert P_STRIP_VERT_RIGHT == 14
130
+ + assert P_STRIP_VERT_LEFT == 16
131
+ + assert P_STRIP_HORIZ_RIGHT == 34
132
+ + assert P_STRIP_HORIZ_LEFT == 36
133
+ +
134
+ +
135
+ +def test_surround_positions():
136
+ + """Full surround code set 90-99 (excluding 98)."""
137
+ + assert P_SURROUND_FULL == 90
138
+ + assert P_SURROUND_BELOW == 91
139
+ + assert P_SURROUND_LEFT == 92
140
+ + assert P_SURROUND_ABOVE == 93
141
+ + assert P_SURROUND_RIGHT == 94
142
+ + assert P_SURROUND_UL == 95
143
+ + assert P_SURROUND_LR == 96
144
+ + assert P_SURROUND_UR == 97
145
+ + assert P_SURROUND_LL_LO == 98
146
+ + assert P_SURROUND_LL == 99