Zero-dependency deterministic consistency checkers for Chinese narrative text (time-jump directives, weekday arithmetic, clichéd similes)
Project description
zh-narrative-guard
Zero-dependency, deterministic consistency checkers for Chinese narrative text — the kind of self-consistency bugs an LLM produces while writing roleplay or fiction. Pure functions in, findings out. No LLM, no I/O, no state machine.
- No dependencies. Pure standard library (
re,dataclasses). - Deterministic. Same text always yields the same findings — safe to run in a hot path.
- Advisory, not destructive. Every checker reports; it never rewrites your text.
- Dormant when irrelevant. The weekday checker stays silent unless the text actually pins a concrete weekday, so calendar-free settings pay nothing.
The design philosophy: prefer a miss to a misfire (宁漏勿误)
Chinese single characters are hopelessly polysemous as signals:
| Character | Time meaning | But also… |
|---|---|---|
| 周 | week | 四周 = all around |
| 后 | after / later | 后先用真气 = then first uses qi |
| 次 | time(s) | 第二次 = the second attempt |
| 天 | day | 天空 = the sky |
Treating any single character as a time signal produces false positives, and these detectors were forged by fixing three successive production bug families that were all exactly that. So the rule throughout is: a missed issue is recoverable downstream, a false alarm directly harasses the writer. Every checker here errs toward silence.
Concretely, a time value is only accepted when it is a time-shaped token — a
quantity + unit + after/before (三天后), an ordinal chapter/day (第3章 /
第二天), a year (公元1024年), a clock time (八点半), or a named
time-of-day / season (清晨 / 傍晚 / 入冬). A lone polysemous character is
never enough. Likewise a recall/flashback/fantasy framing (回想 / 梦见 /
想象) suppresses time-jump detection entirely — reminiscing is not jumping.
Install
pip install zh-narrative-guard
Requires Python 3.10+.
Quickstart
from zh_narrative_guard import (
detect_time_directives,
is_recall_framing,
detect_weekday_violations,
detect_cliche_violations,
)
# 1. Explicit "jump the timeline to <T>" directives.
d = detect_time_directives("时间跳到三天后")
assert d[0].target == "三天后"
# ...but action narration and flashbacks are NOT jumps:
assert detect_time_directives("进入后先用真气感知四周环境") == []
assert is_recall_framing("我继续回想:在进入主神空间前的日子")
# 2. Weekday arithmetic the model got wrong.
v = detect_weekday_violations("今天周日,明天周六")
assert v[0]["expected_name"] == "周一" and v[0]["claimed_name"] == "周六"
# Dormant with no concrete "today = 周X" anchor (fantasy / xianxia):
assert detect_weekday_violations("三天后我们在城门口集合") == []
# 3. Clichéd simile phrasing (surface it, do not strip).
assert detect_cliche_violations("他像投石入水般沉了下去")
# Literal usage and function words are never flagged:
assert detect_cliche_violations("他用投石机攻城") == []
assert detect_cliche_violations("如果投石入水就糟了") == []
API
timeline
| Function | Signature | Description |
|---|---|---|
detect_time_directives |
(text: str) -> list[TimeDirective] |
Explicit "jump to <T>" requests. Suppressed entirely if the text reads as a flashback. Each TimeDirective has .target and .raw. |
is_recall_framing |
(text: str) -> bool |
Is the writer reminiscing / dreaming / imagining (a flashback), rather than advancing the clock? |
looks_like_time_value |
(value: str) -> bool |
Does value name a time (a time-shaped token, no personal pronoun), rather than an action clause or a place? |
clean_time_value |
(value: str) -> str |
Trim punctuation, strip leading prepositions and trailing action verbs from a captured value. |
is_time_key |
(key: str) -> bool |
Does a state-key name denote a timeline-anchor field? |
weekday
| Function | Signature | Description |
|---|---|---|
parse_today_weekday |
(text: str) -> int | None |
The concrete "今天 = 周X" anchor (0=Mon … 6=Sun), or None. Only 今天/今日/当天/本日 count; a relative day is never a base. |
detect_weekday_violations |
(text: str, base_weekday: int | None = None) -> list[dict] |
Relative-day mentions (明天/后天/…) paired with an arithmetically wrong weekday. Pass base_weekday to supply an external "today"; otherwise the text's own "今天 = 周X" is the base. No anchor → [] (dormant). |
Each weekday violation is {"rel", "match", "claimed", "expected", "claimed_name", "expected_name", "base_name"}.
cliche
| Function | Signature | Description |
|---|---|---|
detect_cliche_violations |
(text: str) -> list[dict] |
Clichéd "throwing a stone" (投石) simile phrasing. Matches the simile construction only — literal words (投石机 / 投石车 / 用投石砸) and function words (如果 / 例如) are never flagged. |
Each cliché violation is {"pattern_label", "match", "position"}.
Behavior differences from the source platform
This library is extracted from a Chinese roleplay platform. Two deliberate changes were made for the general case:
- Dropped book-specific timeline tokens. The source
_TIME_TOKENalso matched a novel's place-name timeline labels (柏林/图卢兹/基地). In ordinary prose those are pure false positives (柏林墙倒塌那年,回到基地), so they are removed here. - Fixed a simile false positive. The source simile marker
如同?also matched the lone如inside the common function words如果(if),例如(for example),假如(if),如今(nowadays), flagging non-similes such as "如果投石入水就糟了". The marker is now guarded (如同plus a bare如with look-behind/look-ahead against those function words), while real similes —像投石,如同投石, literary bare如投石问路— still match.
Known limitations
Consistent with prefer a miss to a misfire: the weekday token accepts only
ASCII 1-7, so full-width (周6) or Arabic-Indic (周٦) digits are silently
missed rather than mis-checked (a miss is the safe direction). Chinese
numerals (周三) and ASCII (周3) are fully supported.
License
MIT — see LICENSE.
Acknowledgements
Extracted from the RPG Roleplay platform (https://github.com/felixchaos/rpg-roleplay-platform).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zh_narrative_guard-0.1.0.tar.gz.
File metadata
- Download URL: zh_narrative_guard-0.1.0.tar.gz
- Upload date:
- Size: 18.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9d54dbdccad50d012f3c42895a48ede8d2bc39f12d11b338a804cf997e20c0af
|
|
| MD5 |
5d7e9bd944f80d6833dfafd8d0c0963a
|
|
| BLAKE2b-256 |
701fa7dcaacfd61ed2e1b5df5cdd7470ee9aa4a31d7cacaec81785f08dd50e4b
|
Provenance
The following attestation bundles were made for zh_narrative_guard-0.1.0.tar.gz:
Publisher:
release.yml on felixchaos/zh-narrative-guard
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zh_narrative_guard-0.1.0.tar.gz -
Subject digest:
9d54dbdccad50d012f3c42895a48ede8d2bc39f12d11b338a804cf997e20c0af - Sigstore transparency entry: 2168011893
- Sigstore integration time:
-
Permalink:
felixchaos/zh-narrative-guard@de55a797f06fe229f26a26cb9b17d5a59d99a065 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/felixchaos
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@de55a797f06fe229f26a26cb9b17d5a59d99a065 -
Trigger Event:
push
-
Statement type:
File details
Details for the file zh_narrative_guard-0.1.0-py3-none-any.whl.
File metadata
- Download URL: zh_narrative_guard-0.1.0-py3-none-any.whl
- Upload date:
- Size: 14.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fa715af6348670214f73a762f34fffcd02264b31d02af0a3a1f0b3eb31bd75ad
|
|
| MD5 |
f047fa1b549277d63e026f1641aeb86c
|
|
| BLAKE2b-256 |
c4fa045e01765e7ec729e46f16ecedd777389c745f36e42a6096461171439ad8
|
Provenance
The following attestation bundles were made for zh_narrative_guard-0.1.0-py3-none-any.whl:
Publisher:
release.yml on felixchaos/zh-narrative-guard
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zh_narrative_guard-0.1.0-py3-none-any.whl -
Subject digest:
fa715af6348670214f73a762f34fffcd02264b31d02af0a3a1f0b3eb31bd75ad - Sigstore transparency entry: 2168011897
- Sigstore integration time:
-
Permalink:
felixchaos/zh-narrative-guard@de55a797f06fe229f26a26cb9b17d5a59d99a065 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/felixchaos
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@de55a797f06fe229f26a26cb9b17d5a59d99a065 -
Trigger Event:
push
-
Statement type: