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.
- cjk_semantic_split-0.1.0/.github/workflows/ci.yml +41 -0
- cjk_semantic_split-0.1.0/.gitignore +44 -0
- cjk_semantic_split-0.1.0/.hypothesis/.gitignore +9 -0
- cjk_semantic_split-0.1.0/.hypothesis/constants/2b726390919ba924 +4 -0
- cjk_semantic_split-0.1.0/.hypothesis/constants/447154ffb3443956 +4 -0
- cjk_semantic_split-0.1.0/.hypothesis/constants/448896e190e1a139 +4 -0
- cjk_semantic_split-0.1.0/.hypothesis/constants/44fdbf8fc7dec723 +4 -0
- cjk_semantic_split-0.1.0/.hypothesis/constants/7d0a29d738e12257 +4 -0
- cjk_semantic_split-0.1.0/.hypothesis/constants/a686eee4be3e3873 +4 -0
- cjk_semantic_split-0.1.0/.hypothesis/constants/a80ac1b9c26466c7 +4 -0
- cjk_semantic_split-0.1.0/.hypothesis/constants/cd01bf70c5c5b577 +4 -0
- cjk_semantic_split-0.1.0/.hypothesis/constants/dc1aa6aba7748109 +4 -0
- cjk_semantic_split-0.1.0/.hypothesis/constants/eb3de9a3db1e637c +4 -0
- cjk_semantic_split-0.1.0/.hypothesis/unicode_data/15.1.0/charmap.json.gz +0 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/.gitignore +1 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/progress.md +43 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/review-22d28a8..564c3e6.diff +227 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/review-564c3e6..0b4b3f6.diff +146 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/task-1-brief.md +237 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/task-1-report.md +79 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/task-10-report.md +12 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/task-11-report.md +7 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/task-12-report.md +8 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/task-13-report.md +13 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/task-14-report.md +7 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/task-15-report.md +11 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/task-2-brief.md +161 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/task-2-report.md +76 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/task-3-brief.md +249 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/task-3-report.md +74 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/task-4-report.md +9 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/task-5-report.md +19 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/task-6-report.md +13 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/task-7-report.md +21 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/task-8-report.md +14 -0
- cjk_semantic_split-0.1.0/.superpowers/sdd/task-9-report.md +19 -0
- cjk_semantic_split-0.1.0/LICENSE +21 -0
- cjk_semantic_split-0.1.0/PKG-INFO +102 -0
- cjk_semantic_split-0.1.0/README.md +55 -0
- cjk_semantic_split-0.1.0/docs/superpowers/plans/2026-07-16-cjk-semantic-split-implementation.md +2813 -0
- cjk_semantic_split-0.1.0/docs/superpowers/specs/2026-07-16-cjk-semantic-split-design.md +393 -0
- cjk_semantic_split-0.1.0/pyproject.toml +67 -0
- cjk_semantic_split-0.1.0/scripts/__init__.py +0 -0
- cjk_semantic_split-0.1.0/scripts/harvest.py +52 -0
- cjk_semantic_split-0.1.0/scripts/review_conflicts.py +24 -0
- cjk_semantic_split-0.1.0/src/chinese_decompose/__init__.py +2 -0
- cjk_semantic_split-0.1.0/src/chinese_decompose/__main__.py +134 -0
- cjk_semantic_split-0.1.0/src/chinese_decompose/api.py +135 -0
- cjk_semantic_split-0.1.0/src/chinese_decompose/data/decomp_l1.json +12 -0
- cjk_semantic_split-0.1.0/src/chinese_decompose/data/decomp_l2.json +1 -0
- cjk_semantic_split-0.1.0/src/chinese_decompose/data/decomp_l3.json +5 -0
- cjk_semantic_split-0.1.0/src/chinese_decompose/data/decomp_patches.json +1 -0
- cjk_semantic_split-0.1.0/src/chinese_decompose/data/primitives.json +21 -0
- cjk_semantic_split-0.1.0/src/chinese_decompose/dsl.py +102 -0
- cjk_semantic_split-0.1.0/src/chinese_decompose/errors.py +26 -0
- cjk_semantic_split-0.1.0/src/chinese_decompose/loader.py +132 -0
- cjk_semantic_split-0.1.0/src/chinese_decompose/primitives.py +41 -0
- cjk_semantic_split-0.1.0/src/chinese_decompose/split.py +52 -0
- cjk_semantic_split-0.1.0/src/chinese_decompose/structures.py +127 -0
- cjk_semantic_split-0.1.0/tests/__init__.py +1 -0
- cjk_semantic_split-0.1.0/tests/conftest.py +22 -0
- cjk_semantic_split-0.1.0/tests/test_api.py +67 -0
- cjk_semantic_split-0.1.0/tests/test_cli.py +70 -0
- cjk_semantic_split-0.1.0/tests/test_compose.py +96 -0
- cjk_semantic_split-0.1.0/tests/test_decompose.py +86 -0
- cjk_semantic_split-0.1.0/tests/test_dsl.py +116 -0
- cjk_semantic_split-0.1.0/tests/test_harvest.py +39 -0
- cjk_semantic_split-0.1.0/tests/test_loader.py +121 -0
- cjk_semantic_split-0.1.0/tests/test_primitives.py +67 -0
- cjk_semantic_split-0.1.0/tests/test_properties.py +77 -0
- cjk_semantic_split-0.1.0/tests/test_smoke.py +13 -0
- cjk_semantic_split-0.1.0/tests/test_split.py +76 -0
- 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/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']
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*
|
|
@@ -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
|