Skip to main content

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:

  1. Dropped book-specific timeline tokens. The source _TIME_TOKEN also matched a novel's place-name timeline labels (柏林 / 图卢兹 / 基地). In ordinary prose those are pure false positives (柏林墙倒塌那年, 回到基地), so they are removed here.
  2. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zh_narrative_guard-0.1.0.tar.gz (18.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zh_narrative_guard-0.1.0-py3-none-any.whl (14.0 kB view details)

Uploaded Python 3

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

Hashes for zh_narrative_guard-0.1.0.tar.gz
Algorithm Hash digest
SHA256 9d54dbdccad50d012f3c42895a48ede8d2bc39f12d11b338a804cf997e20c0af
MD5 5d7e9bd944f80d6833dfafd8d0c0963a
BLAKE2b-256 701fa7dcaacfd61ed2e1b5df5cdd7470ee9aa4a31d7cacaec81785f08dd50e4b

See more details on using hashes here.

Provenance

The following attestation bundles were made for zh_narrative_guard-0.1.0.tar.gz:

Publisher: release.yml on felixchaos/zh-narrative-guard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zh_narrative_guard-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for zh_narrative_guard-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fa715af6348670214f73a762f34fffcd02264b31d02af0a3a1f0b3eb31bd75ad
MD5 f047fa1b549277d63e026f1641aeb86c
BLAKE2b-256 c4fa045e01765e7ec729e46f16ecedd777389c745f36e42a6096461171439ad8

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page