CJK Name Stroke Onomancy¶
A CJK five-grid (Japanese seimei-handan 姓名判断 / Chinese 五格剖象) engine that
computes the heaven, person, earth, outer, and total grids from the brush-stroke
counts of a name's characters, using the standard 熊崎式 formulas (including the
single-character 霊数 "spirit number" rule). The selected school and
character_set are recorded for provenance.
from fortune_telling_core import ReadingRequest
from fortune_telling_core.traditions.cjk_name_strokes import (
CJK_NAME_STROKES_DECK,
CJK_NAME_STROKES_SPREAD,
build_engine,
)
request = ReadingRequest(
deck_id=CJK_NAME_STROKES_DECK.id,
spread_id=CJK_NAME_STROKES_SPREAD.id,
options={"surname": "山田", "given_name": "太郎"},
)
reading = build_engine().cast(request)
# reading.summary -> "CJK name stroke total 20; heaven 8; person 9; earth 12; outer 11."
Stroke sources¶
Every character needs a stroke count, supplied by a stroke-count provider.
The stroke_source option names the provider to use (default "unihan");
providers are looked up in a registry. The resolved per-character counts and the
provider's id/version are recorded on the reading (values, value_system)
so a reading is reproducible and auditable.
Bundled Unihan table (unihan, the default)¶
By default counts come from the bundled Unicode Unihan kTotalStrokes table —
no preparation is needed (the example above uses it). These are
representative-glyph totals per Unicode UAX #38, not the Kangxi / old-form
counts that some seimei-handan schools require; the reading records
value_system=cjk_unihan_strokes.v1 so the basis is explicit.
Unihan counts can differ from a school's
A character's stroke count is glyph-dependent, and Unihan records the count
for its representative glyph. For example 郎 (U+90CE) is 8 in
Unihan but 9 in the Japanese tradition (KANJIDIC lists "9, also 8"), so
田中太郎 totals 20 under the Unihan default versus 21 under the
seimei-handan count. Where Unihan lists two values (zh-Hans then zh-Hant),
the bundled table keeps the first. For tradition-faithful counts, register a
provider (below) using the convention your school expects.
Third-party dataset (a registered provider)¶
For school-specific or higher-fidelity counts, register your own provider. The library bundles no third-party data — KANJIDIC2 and KanjiVG are share-alike (CC BY-SA), whose ShareAlike terms preclude redistributing them here — so you obtain the data yourself and comply with its licence. The library ships parsers for two common formats.
1. Obtain the dataset (and follow its licence terms):
- KANJIDIC2 — EDRDG, CC BY-SA 4.0 — https://www.edrdg.org/wiki/index.php/KANJIDIC_Project
(download
kanjidic2.xml.gzand decompress it). Modern Japanese (shinjitai) counts. - KanjiVG — CC BY-SA 3.0 — https://kanjivg.tagaini.net/ (the aggregated
kanjivg-*.xml, or per-character SVGs). Counts are derived by counting stroke paths.
2. Parse it into a {character: stroke_count} mapping, wrap it in a
MappingStrokeProvider (its id/version are recorded as the reading's value
system), and register it under a name:
from fortune_telling_core.traditions.cjk_name_strokes import (
MappingStrokeProvider,
parse_kanjidic2,
register_provider,
)
with open("kanjidic2.xml", "rb") as fh:
table = parse_kanjidic2(fh) # {"山": 3, "田": 5, ...}
register_provider(MappingStrokeProvider("kanjidic2", "2024-01", table))
3. Select the provider by name via stroke_source:
request = ReadingRequest(
deck_id=CJK_NAME_STROKES_DECK.id,
spread_id=CJK_NAME_STROKES_SPREAD.id,
options={"surname": "山田", "given_name": "太郎", "stroke_source": "kanjidic2"},
)
reading = build_engine().cast(request)
# provenance notes include: stroke_source=kanjidic2, value_system=kanjidic2
register_provider adds to a process-wide default registry. To avoid global
state (e.g. in tests or a multi-tenant app), build a private registry instead
and inject it:
from fortune_telling_core.traditions.cjk_name_strokes import new_default_registry
registry = new_default_registry() # seeded with "unihan"
registry.register(MappingStrokeProvider("kanjidic2", "2024-01", table), name="kanjidic2")
reading = build_engine(registry=registry).cast(request)
Any object satisfying the StrokeCountProvider protocol (id, version,
stroke_count(char) -> int | None) can be registered, so a school table with
its own Kangxi/radical-restoration rules plugs in the same way. parse_kanjidic2
/ parse_kanjivg produce literal, as-printed counts without radical restoration,
so — like the bundled Unihan table — they are not faithful to Kangxi-based
schools.
cjk_name_strokes ¶
CJK_NAME_STROKES_DECK
module-attribute
¶
CJK_NAME_STROKES_DECK = Deck(
id="cjk_name_strokes.deck.five_grid.v1",
symbols=tuple(
(
Symbol(
id=symbol_id,
name=f"{position.title()} Grid",
attributes={
"kind": "stroke_grid",
"grid_position": position,
},
)
)
for position, symbol_id in (GRID_SYMBOLS.items())
),
)
CJK_NAME_STROKES_SPREAD
module-attribute
¶
CJK_NAME_STROKES_SPREAD = Spread(
id="cjk_name_strokes.spread.five_grid.v1",
name="CJK Name Stroke Five-Grid",
positions=(
Position(
"heaven", "Heaven", "Surname stroke grid value."
),
Position(
"person",
"Person",
"Last surname plus first given-name grid value.",
),
Position(
"earth",
"Earth",
"Given-name stroke grid value.",
),
Position(
"outer",
"Outer",
"Total less person grid value.",
),
Position(
"total", "Total", "Total stroke grid value."
),
),
)
CharacterSet ¶
Bases: StrEnum
Declared character-set basis for request-supplied counts.
CjkNameStrokesEngine ¶
Bases: AbstractEngine
CJK name stroke onomancy engine.
Stroke counts are resolved by a named :class:StrokeCountProvider selected
via the stroke_source option (default "unihan", the bundled Unihan
table). Providers are looked up in registry — the process-wide default
registry unless one is injected.
Source code in src/fortune_telling_core/traditions/cjk_name_strokes/engine.py
deck ¶
Return the CJK name strokes deck.
Source code in src/fortune_telling_core/traditions/cjk_name_strokes/engine.py
spread ¶
Return the CJK name strokes spread.
Source code in src/fortune_telling_core/traditions/cjk_name_strokes/engine.py
draw ¶
Compute the CJK five-grid stroke chart as a deterministic draw.
Source code in src/fortune_telling_core/traditions/cjk_name_strokes/engine.py
cast ¶
Compute a CJK name stroke reading without a caller RNG.
Grid ¶
Bases: StrEnum
Supported stroke grids.
School ¶
MappingStrokeProvider
dataclass
¶
A provider backed by an in-memory {character: stroke_count} mapping.
Wrap a dataset parsed by
:func:fortune_telling_core.traditions.cjk_name_strokes.parsers.parse_kanjidic2
/ parse_kanjivg (or your own school table) and register it; id and
version identify the dataset in provenance.
StrokeCountProvider ¶
Bases: Protocol
Resolves CJK characters to total stroke counts under a named source.
Implementations expose a stable id and version (recorded as the
reading's value system) and resolve single characters to counts.
stroke_count ¶
StrokeProviderRegistry ¶
A name to :class:StrokeCountProvider registry.
Source code in src/fortune_telling_core/traditions/cjk_name_strokes/providers.py
register ¶
Register provider under name (defaults to provider.id).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider
|
StrokeCountProvider
|
The provider to register. |
required |
name
|
str | None
|
The lookup name; defaults to the provider's |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The name the provider was registered under. |
Raises:
| Type | Description |
|---|---|
ValidationError
|
If the resolved name is empty. |
Source code in src/fortune_telling_core/traditions/cjk_name_strokes/providers.py
get ¶
Return the provider registered under name.
Raises:
| Type | Description |
|---|---|
ValidationError
|
If no provider is registered under |
Source code in src/fortune_telling_core/traditions/cjk_name_strokes/providers.py
names ¶
build_engine ¶
Create a CJK name stroke onomancy engine.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
registry
|
StrokeProviderRegistry | None
|
Stroke-provider registry to resolve |
None
|
Source code in src/fortune_telling_core/traditions/cjk_name_strokes/engine.py
parse_kanjidic2 ¶
Parse total stroke counts from an EDRDG KANJIDIC2 XML file.
Each <character> carries its glyph in <literal> and one or more
<misc><stroke_count> values. Per the KANJIDIC2 DTD, the first
stroke_count is the accepted count and any others are common miscounts,
so only the first is used.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
_Source
|
A path to the KANJIDIC2 XML file, or a binary file object. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, int]
|
A mapping of each character to its accepted total stroke count. |
Raises:
| Type | Description |
|---|---|
ValidationError
|
If a |
Source code in src/fortune_telling_core/traditions/cjk_name_strokes/parsers.py
parse_kanjivg ¶
Parse stroke counts from KanjiVG SVG or aggregated XML data.
Each kanji's strokes live under a group whose id is kvg:<hex> (the
bare codepoint, without the StrokePaths_/kanji_ wrappers or the
-g/-s component suffixes). The stroke count is the number of
<path> elements in that group; the <text> stroke-number annotations
live in a separate group and are not counted. This works for both a single
<svg> file and the aggregated kanjivg-*.xml release.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
_Source
|
A path to a KanjiVG SVG / XML file, or a binary file object. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, int]
|
A mapping of each character to its stroke count. |
Source code in src/fortune_telling_core/traditions/cjk_name_strokes/parsers.py
default_registry ¶
new_default_registry ¶
Return a fresh registry seeded with only the bundled unihan provider.
Source code in src/fortune_telling_core/traditions/cjk_name_strokes/providers.py
register_provider ¶
Register provider in the process-wide default registry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider
|
StrokeCountProvider
|
The provider to register. |
required |
name
|
str | None
|
The lookup name; defaults to the provider's |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The name the provider was registered under (use it as |