humanus 0.35.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.
- humanus-0.35.0/LICENSE +21 -0
- humanus-0.35.0/PKG-INFO +231 -0
- humanus-0.35.0/README.md +210 -0
- humanus-0.35.0/pyproject.toml +57 -0
- humanus-0.35.0/setup.cfg +4 -0
- humanus-0.35.0/src/humanus/__init__.py +51 -0
- humanus-0.35.0/src/humanus/__main__.py +8 -0
- humanus-0.35.0/src/humanus/_util.py +18 -0
- humanus-0.35.0/src/humanus/builtin_checks.py +312 -0
- humanus-0.35.0/src/humanus/calibration.py +131 -0
- humanus-0.35.0/src/humanus/check.py +238 -0
- humanus-0.35.0/src/humanus/classifier_lane.py +390 -0
- humanus-0.35.0/src/humanus/cli.py +80 -0
- humanus-0.35.0/src/humanus/contract_commands.py +128 -0
- humanus-0.35.0/src/humanus/contracts.py +84 -0
- humanus-0.35.0/src/humanus/data/SKILL.md +58 -0
- humanus-0.35.0/src/humanus/data/__init__.py +1 -0
- humanus-0.35.0/src/humanus/emoji.py +11 -0
- humanus-0.35.0/src/humanus/finding_ids.py +38 -0
- humanus-0.35.0/src/humanus/language_checks.py +290 -0
- humanus-0.35.0/src/humanus/languages/__init__.py +202 -0
- humanus-0.35.0/src/humanus/languages/en.yml +74 -0
- humanus-0.35.0/src/humanus/languages/es-419.yml +14 -0
- humanus-0.35.0/src/humanus/languages/es.yml +49 -0
- humanus-0.35.0/src/humanus/languages/zh-Hans.yml +90 -0
- humanus-0.35.0/src/humanus/openai_responses.py +94 -0
- humanus-0.35.0/src/humanus/packs/NOTICE.md +67 -0
- humanus-0.35.0/src/humanus/packs/__init__.py +27 -0
- humanus-0.35.0/src/humanus/packs/ai-writing-tells-es.yml +56 -0
- humanus-0.35.0/src/humanus/packs/ai-writing-tells-zh-hans.yml +105 -0
- humanus-0.35.0/src/humanus/packs/ai-writing-tells.yml +1123 -0
- humanus-0.35.0/src/humanus/packs/asd-ste100.yml +179 -0
- humanus-0.35.0/src/humanus/packs/plain-language.yml +322 -0
- humanus-0.35.0/src/humanus/packs/translationese-zh-hans.yml +72 -0
- humanus-0.35.0/src/humanus/packs/typography-es.yml +31 -0
- humanus-0.35.0/src/humanus/packs/typography-zh-hans.yml +68 -0
- humanus-0.35.0/src/humanus/packs/word-choice-es-419.yml +32 -0
- humanus-0.35.0/src/humanus/policy_pack.py +283 -0
- humanus-0.35.0/src/humanus/rules_lane.py +132 -0
- humanus-0.35.0/src/humanus/schemas/calibration-v1.json +46 -0
- humanus-0.35.0/src/humanus/schemas/check-request-v1.json +137 -0
- humanus-0.35.0/src/humanus/schemas/check-result-v1.json +316 -0
- humanus-0.35.0/src/humanus/schemas/draft-questions-request-v1.json +57 -0
- humanus-0.35.0/src/humanus/schemas/draft-questions-result-v1.json +33 -0
- humanus-0.35.0/src/humanus/schemas/feedback-labels-v1.json +47 -0
- humanus-0.35.0/src/humanus/schemas/finding-v1.json +105 -0
- humanus-0.35.0/src/humanus/schemas/policy-pack-v1.json +535 -0
- humanus-0.35.0/src/humanus/schemas/suggest-request-v1.json +79 -0
- humanus-0.35.0/src/humanus/schemas/suggest-result-v1.json +104 -0
- humanus-0.35.0/src/humanus/segmentation.py +440 -0
- humanus-0.35.0/src/humanus/skill.py +13 -0
- humanus-0.35.0/src/humanus/standard_checks.py +270 -0
- humanus-0.35.0/src/humanus/style_profile_import.py +181 -0
- humanus-0.35.0/src/humanus/suggest.py +423 -0
- humanus-0.35.0/src/humanus/usability_commands.py +38 -0
- humanus-0.35.0/src/humanus/usability_profile.py +256 -0
- humanus-0.35.0/src/humanus/usability_scan.py +1024 -0
- humanus-0.35.0/src/humanus.egg-info/PKG-INFO +231 -0
- humanus-0.35.0/src/humanus.egg-info/SOURCES.txt +68 -0
- humanus-0.35.0/src/humanus.egg-info/dependency_links.txt +1 -0
- humanus-0.35.0/src/humanus.egg-info/entry_points.txt +2 -0
- humanus-0.35.0/src/humanus.egg-info/requires.txt +10 -0
- humanus-0.35.0/src/humanus.egg-info/top_level.txt +1 -0
- humanus-0.35.0/tests/test_builtin_checks.py +69 -0
- humanus-0.35.0/tests/test_policy_pack_contracts.py +148 -0
- humanus-0.35.0/tests/test_readme_examples.py +35 -0
- humanus-0.35.0/tests/test_skill_cli.py +45 -0
- humanus-0.35.0/tests/test_standard_packs.py +106 -0
- humanus-0.35.0/tests/test_tables_and_quotations.py +75 -0
- humanus-0.35.0/tests/test_usability_scan.py +482 -0
humanus-0.35.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anthus AI Solutions
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
humanus-0.35.0/PKG-INFO
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: humanus
|
|
3
|
+
Version: 0.35.0
|
|
4
|
+
Summary: Configurable editorial compliance checks: policy packs, deterministic rules, zero-shot decision models and rewrite suggestions.
|
|
5
|
+
Author: Anthus AI Solutions
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/AnthusAI/Humanus
|
|
8
|
+
Project-URL: Repository, https://github.com/AnthusAI/Humanus
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: PyYAML<7.0.0,>=6.0.2
|
|
13
|
+
Requires-Dist: jsonschema<5,>=4.18
|
|
14
|
+
Requires-Dist: markdown-it-py<5,>=3.0
|
|
15
|
+
Provides-Extra: jev
|
|
16
|
+
Requires-Dist: typesafe-sdk<0.8,>=0.7.2; extra == "jev"
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: behave>=1.2.6; extra == "dev"
|
|
19
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# Humanus
|
|
23
|
+
|
|
24
|
+
Humanus is a configurable editorial compliance engine. A publication sends its
|
|
25
|
+
own editorial policies with each request; Humanus marks every place a draft
|
|
26
|
+
breaks them, says how sure it is, and offers rewrites when an editor asks.
|
|
27
|
+
|
|
28
|
+
## Press Release
|
|
29
|
+
|
|
30
|
+
**Anthus AI Solutions introduces Humanus, an open-source engine that checks writing against a publication's own editorial policies — and only rewrites when an editor asks**
|
|
31
|
+
|
|
32
|
+
Every publication has rules: words it avoids, claims it will not make without a
|
|
33
|
+
source, a voice it keeps. Most are enforced by an editor's memory. Humanus makes
|
|
34
|
+
them executable. A CMS sends a draft and a *policy pack* — the publication's
|
|
35
|
+
policies, each written in plain words and backed by detectors — and Humanus
|
|
36
|
+
returns the draft annotated with every finding, down to the exact words.
|
|
37
|
+
|
|
38
|
+
Two kinds of detector do the finding. Rules catch what a pattern can: banned
|
|
39
|
+
phrases, house style, the tells of AI-generated prose. A decision model, TypeSafe
|
|
40
|
+
Jev, judges what a pattern cannot — "does this sentence make a claim without
|
|
41
|
+
saying who or how much?" — sentence by sentence, with a probability attached.
|
|
42
|
+
Rewrites come from a generative model only when an editor asks for them, for a
|
|
43
|
+
flagged passage or any passage they select, and they never add facts.
|
|
44
|
+
|
|
45
|
+
"We wanted editorial policy to be something you can read, test and change, not
|
|
46
|
+
something one editor carries in their head," said the Humanus team at Anthus AI
|
|
47
|
+
Solutions. Humanus runs inside Papyrus, Anthus's newsroom system, and is
|
|
48
|
+
available as an MIT-licensed Python package.
|
|
49
|
+
|
|
50
|
+
## FAQ
|
|
51
|
+
|
|
52
|
+
**Is Humanus an AI-content detector?** No. It checks text against policies you
|
|
53
|
+
define, whoever wrote it. The starter pack of AI-writing tells is a set of
|
|
54
|
+
editable policies like any other.
|
|
55
|
+
|
|
56
|
+
**Can it enforce a published standard?** Yes. Starter packs implement ASD-STE100
|
|
57
|
+
Simplified Technical English (`asd-ste100`) and the Federal Plain Language
|
|
58
|
+
Guidelines (`plain-language`); copy one and adjust its limits and word lists.
|
|
59
|
+
|
|
60
|
+
**Does it rewrite my content?** Only when asked, and never by itself: `suggest`
|
|
61
|
+
returns options for a finding or a selection, and a person chooses.
|
|
62
|
+
|
|
63
|
+
**Where do policies live?** In the caller. Humanus is stateless; the CMS stores
|
|
64
|
+
policies, settings and editors' decisions and sends a pack with every request.
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install humanus # rules lane, contracts, suggest
|
|
70
|
+
pip install "humanus[jev]" # adds the TypeSafe Jev classifier lane
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Set `TYPESAFE_API_KEY` for the classifier lane and `OPENAI_API_KEY` for
|
|
74
|
+
rewrite suggestions and drafted questions (`gpt-6-luna` by default).
|
|
75
|
+
|
|
76
|
+
## Usage
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
humanus pack builtin ai-writing-tells > pack.json # a starter pack to edit
|
|
80
|
+
humanus pack import --profile style-profile.yml > pack.json # or convert a legacy style profile
|
|
81
|
+
humanus check --draft draft.md --pack pack.json --language en # findings, segments, lanes (check-result-v1)
|
|
82
|
+
humanus suggest --request suggest-request.json # rewrite options for a finding or selection
|
|
83
|
+
humanus draft-questions --request draft-request.json # classifier detectors for a prose policy
|
|
84
|
+
humanus calibrate --labels labels.json # calibration from editors' decisions
|
|
85
|
+
humanus schema policy-pack-v1 # print any contract schema
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from humanus import check, suggest
|
|
90
|
+
|
|
91
|
+
result = check({"schemaVersion": 1, "content": {"format": "markus", "language": "en", "text": text}, "pack": pack})
|
|
92
|
+
for finding in result["findings"]:
|
|
93
|
+
print(finding["severity"], finding["policyId"], finding["excerpt"], finding.get("probability"))
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Contracts
|
|
97
|
+
|
|
98
|
+
The payloads are JSON Schemas packaged with Humanus (`humanus schema <name>`);
|
|
99
|
+
callers in any language validate against the same files.
|
|
100
|
+
|
|
101
|
+
| Schema | Purpose |
|
|
102
|
+
| --- | --- |
|
|
103
|
+
| `policy-pack-v1` | Policies: prose guidance, severity (`info`/`warn`/`error`), examples, rewrite guidance and detectors — `rules` (`regex`, `phrase`, `builtin`) or `classifier` (a zero-shot question at sentence, paragraph or document scope, with threshold, review band and optional calibration) |
|
|
104
|
+
| `check-request-v1` / `check-result-v1` | Content (`markus`, `markdown` or `text`, with its `language`) plus a pack in; the language pack used, segments, protected regions, findings, coverage and lane status out |
|
|
105
|
+
| `finding-v1` | One located problem, with an id that is stable across rescans |
|
|
106
|
+
| `suggest-request-v1` / `suggest-result-v1` | Rewrite options for a finding (rewritten as its sentence) or an editor's selection |
|
|
107
|
+
| `draft-questions-request-v1` / `draft-questions-result-v1` | Classifier detectors drafted from a prose policy |
|
|
108
|
+
| `feedback-labels-v1` / `calibration-v1` | Editors' decisions in, per-detector calibration out |
|
|
109
|
+
|
|
110
|
+
**Offsets** count UTF-16 code units, as JavaScript string indexes do.
|
|
111
|
+
**Segments** are the document, its prose paragraphs (paragraphs, headings, list
|
|
112
|
+
items) and their sentences; each carries its plain text so a rendered preview
|
|
113
|
+
can place findings. Front matter, code, Markus directives, block quotes and raw
|
|
114
|
+
HTML are protected regions and never checked. Table cells are prose: each cell
|
|
115
|
+
that has words in it is a paragraph segment with block `tableCell`, split into
|
|
116
|
+
sentences like any other paragraph, so both lanes read it and findings point at
|
|
117
|
+
the cell text; pipes and the alignment row stay out of detection, and cells that
|
|
118
|
+
hold only numbers or code are skipped. Set `options.tables` to `false` to make
|
|
119
|
+
tables a protected region again. Whole-document measures (average sentence
|
|
120
|
+
length, reading grade, information density, paragraph length, uniform cadence,
|
|
121
|
+
overused words) leave table cells out. Paragraph, sentence and
|
|
122
|
+
finding ids derive from text, so they survive edits elsewhere.
|
|
123
|
+
|
|
124
|
+
## Languages
|
|
125
|
+
|
|
126
|
+
Every check request states the language of its content (`content.language`, a
|
|
127
|
+
BCP 47 tag) and every policy pack states the language its policies are written
|
|
128
|
+
for (`language`, or `"*"` for language-neutral policies). Humanus reads the text
|
|
129
|
+
with that language's **language pack** (`src/humanus/languages/*.yml`): where
|
|
130
|
+
sentences end (Chinese `。!?` with no space or capital after them; Spanish
|
|
131
|
+
`¿…?` and `¡…!`), what a unit of length is (words, or characters in Chinese),
|
|
132
|
+
how a phrase matches (on word boundaries, or anywhere in a script written
|
|
133
|
+
without spaces), which quotation marks enclose someone else's words, which
|
|
134
|
+
built-in checks the language can run, its word classes, its native baseline
|
|
135
|
+
rates and its typography data. Shipped: `en`, `es`, `es-419` (an overlay on
|
|
136
|
+
`es`: only what differs, such as quotation marks and number forms) and
|
|
137
|
+
`zh-Hans` (also served for `zh-CN`, `zh-SG`, `zh-MY`).
|
|
138
|
+
|
|
139
|
+
- A language with no pack is **refused** with an error that names it; text is
|
|
140
|
+
never checked as if it were English. Tags match exactly (or a documented
|
|
141
|
+
alias): `es-AR` is refused, not read as `es`.
|
|
142
|
+
- A pack written for another language is refused: English policies never run
|
|
143
|
+
on Chinese text. A pack for a base language (`es`) applies to its variants
|
|
144
|
+
(`es-419`); a pack for one variant never applies to another.
|
|
145
|
+
- A rules detector the language cannot run (an English built-in on Chinese, a
|
|
146
|
+
word class needing a tagger the pack lacks) is listed in
|
|
147
|
+
`lanes.rules.notRun` with the reason.
|
|
148
|
+
|
|
149
|
+
Generic built-ins take everything from the language pack: `frame` (ordered
|
|
150
|
+
parts with a bounded gap in one sentence, such as `不是 … 而是`), `density` (a
|
|
151
|
+
word class per paragraph against the pack's native rate, reported when the
|
|
152
|
+
count would be improbable at that rate) and `typography` (a named mechanical
|
|
153
|
+
rule: `punctuation-width`, `quotation-marks`, `ellipsis`,
|
|
154
|
+
`space-beside-full-width`, `latin-spacing`, `number-words`, `number-format`).
|
|
155
|
+
Length checks count in the language's unit.
|
|
156
|
+
|
|
157
|
+
## Lanes
|
|
158
|
+
|
|
159
|
+
- **Rules** (deterministic): phrase and regex detectors run on prose only;
|
|
160
|
+
built-in checks cover empty lead-ins, unsupported certainty, missing
|
|
161
|
+
attribution, passive voice, redundancy, uniform cadence, information density,
|
|
162
|
+
overused words, uncontracted forms, contrast caps and emoji. Checks for writing
|
|
163
|
+
standards cover sentence length (by procedure or description), average
|
|
164
|
+
sentence length, paragraph length, Flesch-Kincaid grade, double negatives,
|
|
165
|
+
undefined acronyms, one instruction per step, imperative steps, -ing forms,
|
|
166
|
+
missing articles and one meaning per word. Phrase detectors take `exceptions`,
|
|
167
|
+
such as technical names, inside which a match is not a finding.
|
|
168
|
+
Direct quotations are someone else's words: a rules finding that lies
|
|
169
|
+
entirely inside a double-quoted passage (straight or curly) or a block quote
|
|
170
|
+
is not reported. A detector or its policy sets `quotations: "check"` to opt
|
|
171
|
+
back in (the detector wins); the starter pack does so for placeholder leaks,
|
|
172
|
+
chatbot artifacts, chat citation markup and AI-tool tracking parameters.
|
|
173
|
+
`passive_voice` also ignores a passive clause inside a quotation when the
|
|
174
|
+
rest of the sentence is not passive. The classifier lane still reads quotations.
|
|
175
|
+
- **Starter packs** (`humanus pack builtin <name>`):
|
|
176
|
+
- `ai-writing-tells`: 24 policies adapted from
|
|
177
|
+
[avoid-ai-writing](https://github.com/conorbronsdon/avoid-ai-writing) (MIT).
|
|
178
|
+
- `asd-ste100`: ASD-STE100 Simplified Technical English. The official
|
|
179
|
+
dictionary is licensed by ASD, so the pack ships a small seed of
|
|
180
|
+
unapproved words, nouns and one-meaning words; extend them with yours.
|
|
181
|
+
- `plain-language`: the Federal Plain Language Guidelines (public domain),
|
|
182
|
+
with simple-word and excess-word replacements, "must" for "shall", hidden
|
|
183
|
+
verbs, length and readability limits, and Jev questions for addressing the
|
|
184
|
+
reader as "you", putting the main point first and avoiding jargon.
|
|
185
|
+
- `typography-zh-hans`, `typography-es`: mechanical punctuation, quotation
|
|
186
|
+
mark and number-form rules, each finding with its deterministic replacement.
|
|
187
|
+
- `ai-writing-tells-zh-hans`, `translationese-zh-hans`, `ai-writing-tells-es`:
|
|
188
|
+
small advisory starter sets (all `info`), each policy marked with its
|
|
189
|
+
`evidence` grade (`measured`, `normative` or `folk`).
|
|
190
|
+
- `word-choice-es-419`: Latin American word choice (computadora, falla).
|
|
191
|
+
|
|
192
|
+
See `humanus/packs/NOTICE.md` for sources and licenses.
|
|
193
|
+
- **Classifier** (TypeSafe Jev, zero-shot): one request per segment carries
|
|
194
|
+
every question that applies to it; sentences are judged with their paragraph
|
|
195
|
+
as context. Answers are cached by content, so a rescan only asks about changed
|
|
196
|
+
text. `options.classifier` sets the model, concurrency, an input-token budget,
|
|
197
|
+
a deadline (to stay inside an API timeout) and few-shot examples from past
|
|
198
|
+
decisions. Without a decision model the lane reports `unavailable` and rules
|
|
199
|
+
findings are still returned.
|
|
200
|
+
|
|
201
|
+
Editors' decisions improve the classifier: `calibrate` fits Platt scaling (from
|
|
202
|
+
10 labels) or isotonic regression (from 50) per detector; store it on the
|
|
203
|
+
detector as `calibration` and thresholds apply to the calibrated probability.
|
|
204
|
+
|
|
205
|
+
## HTML usability
|
|
206
|
+
|
|
207
|
+
A separate, read-only scan for rendered HTML pages (contrast, tap targets, focus
|
|
208
|
+
visibility, alt text, generic AI-page patterns):
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
humanus usability scan --page page.html --profile usability-profile.yml
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## For agents
|
|
215
|
+
|
|
216
|
+
`humanus skill` prints an Agent Skill describing the check → suggest → choose
|
|
217
|
+
workflow.
|
|
218
|
+
|
|
219
|
+
## Testing
|
|
220
|
+
|
|
221
|
+
Specifications are Gherkin, run with [Behave](https://behave.readthedocs.io/);
|
|
222
|
+
unit tests use pytest. Neither needs network access or API keys.
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
behave
|
|
226
|
+
python -m pytest tests -q
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## License
|
|
230
|
+
|
|
231
|
+
MIT — see [LICENSE](LICENSE).
|
humanus-0.35.0/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Humanus
|
|
2
|
+
|
|
3
|
+
Humanus is a configurable editorial compliance engine. A publication sends its
|
|
4
|
+
own editorial policies with each request; Humanus marks every place a draft
|
|
5
|
+
breaks them, says how sure it is, and offers rewrites when an editor asks.
|
|
6
|
+
|
|
7
|
+
## Press Release
|
|
8
|
+
|
|
9
|
+
**Anthus AI Solutions introduces Humanus, an open-source engine that checks writing against a publication's own editorial policies — and only rewrites when an editor asks**
|
|
10
|
+
|
|
11
|
+
Every publication has rules: words it avoids, claims it will not make without a
|
|
12
|
+
source, a voice it keeps. Most are enforced by an editor's memory. Humanus makes
|
|
13
|
+
them executable. A CMS sends a draft and a *policy pack* — the publication's
|
|
14
|
+
policies, each written in plain words and backed by detectors — and Humanus
|
|
15
|
+
returns the draft annotated with every finding, down to the exact words.
|
|
16
|
+
|
|
17
|
+
Two kinds of detector do the finding. Rules catch what a pattern can: banned
|
|
18
|
+
phrases, house style, the tells of AI-generated prose. A decision model, TypeSafe
|
|
19
|
+
Jev, judges what a pattern cannot — "does this sentence make a claim without
|
|
20
|
+
saying who or how much?" — sentence by sentence, with a probability attached.
|
|
21
|
+
Rewrites come from a generative model only when an editor asks for them, for a
|
|
22
|
+
flagged passage or any passage they select, and they never add facts.
|
|
23
|
+
|
|
24
|
+
"We wanted editorial policy to be something you can read, test and change, not
|
|
25
|
+
something one editor carries in their head," said the Humanus team at Anthus AI
|
|
26
|
+
Solutions. Humanus runs inside Papyrus, Anthus's newsroom system, and is
|
|
27
|
+
available as an MIT-licensed Python package.
|
|
28
|
+
|
|
29
|
+
## FAQ
|
|
30
|
+
|
|
31
|
+
**Is Humanus an AI-content detector?** No. It checks text against policies you
|
|
32
|
+
define, whoever wrote it. The starter pack of AI-writing tells is a set of
|
|
33
|
+
editable policies like any other.
|
|
34
|
+
|
|
35
|
+
**Can it enforce a published standard?** Yes. Starter packs implement ASD-STE100
|
|
36
|
+
Simplified Technical English (`asd-ste100`) and the Federal Plain Language
|
|
37
|
+
Guidelines (`plain-language`); copy one and adjust its limits and word lists.
|
|
38
|
+
|
|
39
|
+
**Does it rewrite my content?** Only when asked, and never by itself: `suggest`
|
|
40
|
+
returns options for a finding or a selection, and a person chooses.
|
|
41
|
+
|
|
42
|
+
**Where do policies live?** In the caller. Humanus is stateless; the CMS stores
|
|
43
|
+
policies, settings and editors' decisions and sends a pack with every request.
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install humanus # rules lane, contracts, suggest
|
|
49
|
+
pip install "humanus[jev]" # adds the TypeSafe Jev classifier lane
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Set `TYPESAFE_API_KEY` for the classifier lane and `OPENAI_API_KEY` for
|
|
53
|
+
rewrite suggestions and drafted questions (`gpt-6-luna` by default).
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
humanus pack builtin ai-writing-tells > pack.json # a starter pack to edit
|
|
59
|
+
humanus pack import --profile style-profile.yml > pack.json # or convert a legacy style profile
|
|
60
|
+
humanus check --draft draft.md --pack pack.json --language en # findings, segments, lanes (check-result-v1)
|
|
61
|
+
humanus suggest --request suggest-request.json # rewrite options for a finding or selection
|
|
62
|
+
humanus draft-questions --request draft-request.json # classifier detectors for a prose policy
|
|
63
|
+
humanus calibrate --labels labels.json # calibration from editors' decisions
|
|
64
|
+
humanus schema policy-pack-v1 # print any contract schema
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from humanus import check, suggest
|
|
69
|
+
|
|
70
|
+
result = check({"schemaVersion": 1, "content": {"format": "markus", "language": "en", "text": text}, "pack": pack})
|
|
71
|
+
for finding in result["findings"]:
|
|
72
|
+
print(finding["severity"], finding["policyId"], finding["excerpt"], finding.get("probability"))
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Contracts
|
|
76
|
+
|
|
77
|
+
The payloads are JSON Schemas packaged with Humanus (`humanus schema <name>`);
|
|
78
|
+
callers in any language validate against the same files.
|
|
79
|
+
|
|
80
|
+
| Schema | Purpose |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| `policy-pack-v1` | Policies: prose guidance, severity (`info`/`warn`/`error`), examples, rewrite guidance and detectors — `rules` (`regex`, `phrase`, `builtin`) or `classifier` (a zero-shot question at sentence, paragraph or document scope, with threshold, review band and optional calibration) |
|
|
83
|
+
| `check-request-v1` / `check-result-v1` | Content (`markus`, `markdown` or `text`, with its `language`) plus a pack in; the language pack used, segments, protected regions, findings, coverage and lane status out |
|
|
84
|
+
| `finding-v1` | One located problem, with an id that is stable across rescans |
|
|
85
|
+
| `suggest-request-v1` / `suggest-result-v1` | Rewrite options for a finding (rewritten as its sentence) or an editor's selection |
|
|
86
|
+
| `draft-questions-request-v1` / `draft-questions-result-v1` | Classifier detectors drafted from a prose policy |
|
|
87
|
+
| `feedback-labels-v1` / `calibration-v1` | Editors' decisions in, per-detector calibration out |
|
|
88
|
+
|
|
89
|
+
**Offsets** count UTF-16 code units, as JavaScript string indexes do.
|
|
90
|
+
**Segments** are the document, its prose paragraphs (paragraphs, headings, list
|
|
91
|
+
items) and their sentences; each carries its plain text so a rendered preview
|
|
92
|
+
can place findings. Front matter, code, Markus directives, block quotes and raw
|
|
93
|
+
HTML are protected regions and never checked. Table cells are prose: each cell
|
|
94
|
+
that has words in it is a paragraph segment with block `tableCell`, split into
|
|
95
|
+
sentences like any other paragraph, so both lanes read it and findings point at
|
|
96
|
+
the cell text; pipes and the alignment row stay out of detection, and cells that
|
|
97
|
+
hold only numbers or code are skipped. Set `options.tables` to `false` to make
|
|
98
|
+
tables a protected region again. Whole-document measures (average sentence
|
|
99
|
+
length, reading grade, information density, paragraph length, uniform cadence,
|
|
100
|
+
overused words) leave table cells out. Paragraph, sentence and
|
|
101
|
+
finding ids derive from text, so they survive edits elsewhere.
|
|
102
|
+
|
|
103
|
+
## Languages
|
|
104
|
+
|
|
105
|
+
Every check request states the language of its content (`content.language`, a
|
|
106
|
+
BCP 47 tag) and every policy pack states the language its policies are written
|
|
107
|
+
for (`language`, or `"*"` for language-neutral policies). Humanus reads the text
|
|
108
|
+
with that language's **language pack** (`src/humanus/languages/*.yml`): where
|
|
109
|
+
sentences end (Chinese `。!?` with no space or capital after them; Spanish
|
|
110
|
+
`¿…?` and `¡…!`), what a unit of length is (words, or characters in Chinese),
|
|
111
|
+
how a phrase matches (on word boundaries, or anywhere in a script written
|
|
112
|
+
without spaces), which quotation marks enclose someone else's words, which
|
|
113
|
+
built-in checks the language can run, its word classes, its native baseline
|
|
114
|
+
rates and its typography data. Shipped: `en`, `es`, `es-419` (an overlay on
|
|
115
|
+
`es`: only what differs, such as quotation marks and number forms) and
|
|
116
|
+
`zh-Hans` (also served for `zh-CN`, `zh-SG`, `zh-MY`).
|
|
117
|
+
|
|
118
|
+
- A language with no pack is **refused** with an error that names it; text is
|
|
119
|
+
never checked as if it were English. Tags match exactly (or a documented
|
|
120
|
+
alias): `es-AR` is refused, not read as `es`.
|
|
121
|
+
- A pack written for another language is refused: English policies never run
|
|
122
|
+
on Chinese text. A pack for a base language (`es`) applies to its variants
|
|
123
|
+
(`es-419`); a pack for one variant never applies to another.
|
|
124
|
+
- A rules detector the language cannot run (an English built-in on Chinese, a
|
|
125
|
+
word class needing a tagger the pack lacks) is listed in
|
|
126
|
+
`lanes.rules.notRun` with the reason.
|
|
127
|
+
|
|
128
|
+
Generic built-ins take everything from the language pack: `frame` (ordered
|
|
129
|
+
parts with a bounded gap in one sentence, such as `不是 … 而是`), `density` (a
|
|
130
|
+
word class per paragraph against the pack's native rate, reported when the
|
|
131
|
+
count would be improbable at that rate) and `typography` (a named mechanical
|
|
132
|
+
rule: `punctuation-width`, `quotation-marks`, `ellipsis`,
|
|
133
|
+
`space-beside-full-width`, `latin-spacing`, `number-words`, `number-format`).
|
|
134
|
+
Length checks count in the language's unit.
|
|
135
|
+
|
|
136
|
+
## Lanes
|
|
137
|
+
|
|
138
|
+
- **Rules** (deterministic): phrase and regex detectors run on prose only;
|
|
139
|
+
built-in checks cover empty lead-ins, unsupported certainty, missing
|
|
140
|
+
attribution, passive voice, redundancy, uniform cadence, information density,
|
|
141
|
+
overused words, uncontracted forms, contrast caps and emoji. Checks for writing
|
|
142
|
+
standards cover sentence length (by procedure or description), average
|
|
143
|
+
sentence length, paragraph length, Flesch-Kincaid grade, double negatives,
|
|
144
|
+
undefined acronyms, one instruction per step, imperative steps, -ing forms,
|
|
145
|
+
missing articles and one meaning per word. Phrase detectors take `exceptions`,
|
|
146
|
+
such as technical names, inside which a match is not a finding.
|
|
147
|
+
Direct quotations are someone else's words: a rules finding that lies
|
|
148
|
+
entirely inside a double-quoted passage (straight or curly) or a block quote
|
|
149
|
+
is not reported. A detector or its policy sets `quotations: "check"` to opt
|
|
150
|
+
back in (the detector wins); the starter pack does so for placeholder leaks,
|
|
151
|
+
chatbot artifacts, chat citation markup and AI-tool tracking parameters.
|
|
152
|
+
`passive_voice` also ignores a passive clause inside a quotation when the
|
|
153
|
+
rest of the sentence is not passive. The classifier lane still reads quotations.
|
|
154
|
+
- **Starter packs** (`humanus pack builtin <name>`):
|
|
155
|
+
- `ai-writing-tells`: 24 policies adapted from
|
|
156
|
+
[avoid-ai-writing](https://github.com/conorbronsdon/avoid-ai-writing) (MIT).
|
|
157
|
+
- `asd-ste100`: ASD-STE100 Simplified Technical English. The official
|
|
158
|
+
dictionary is licensed by ASD, so the pack ships a small seed of
|
|
159
|
+
unapproved words, nouns and one-meaning words; extend them with yours.
|
|
160
|
+
- `plain-language`: the Federal Plain Language Guidelines (public domain),
|
|
161
|
+
with simple-word and excess-word replacements, "must" for "shall", hidden
|
|
162
|
+
verbs, length and readability limits, and Jev questions for addressing the
|
|
163
|
+
reader as "you", putting the main point first and avoiding jargon.
|
|
164
|
+
- `typography-zh-hans`, `typography-es`: mechanical punctuation, quotation
|
|
165
|
+
mark and number-form rules, each finding with its deterministic replacement.
|
|
166
|
+
- `ai-writing-tells-zh-hans`, `translationese-zh-hans`, `ai-writing-tells-es`:
|
|
167
|
+
small advisory starter sets (all `info`), each policy marked with its
|
|
168
|
+
`evidence` grade (`measured`, `normative` or `folk`).
|
|
169
|
+
- `word-choice-es-419`: Latin American word choice (computadora, falla).
|
|
170
|
+
|
|
171
|
+
See `humanus/packs/NOTICE.md` for sources and licenses.
|
|
172
|
+
- **Classifier** (TypeSafe Jev, zero-shot): one request per segment carries
|
|
173
|
+
every question that applies to it; sentences are judged with their paragraph
|
|
174
|
+
as context. Answers are cached by content, so a rescan only asks about changed
|
|
175
|
+
text. `options.classifier` sets the model, concurrency, an input-token budget,
|
|
176
|
+
a deadline (to stay inside an API timeout) and few-shot examples from past
|
|
177
|
+
decisions. Without a decision model the lane reports `unavailable` and rules
|
|
178
|
+
findings are still returned.
|
|
179
|
+
|
|
180
|
+
Editors' decisions improve the classifier: `calibrate` fits Platt scaling (from
|
|
181
|
+
10 labels) or isotonic regression (from 50) per detector; store it on the
|
|
182
|
+
detector as `calibration` and thresholds apply to the calibrated probability.
|
|
183
|
+
|
|
184
|
+
## HTML usability
|
|
185
|
+
|
|
186
|
+
A separate, read-only scan for rendered HTML pages (contrast, tap targets, focus
|
|
187
|
+
visibility, alt text, generic AI-page patterns):
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
humanus usability scan --page page.html --profile usability-profile.yml
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## For agents
|
|
194
|
+
|
|
195
|
+
`humanus skill` prints an Agent Skill describing the check → suggest → choose
|
|
196
|
+
workflow.
|
|
197
|
+
|
|
198
|
+
## Testing
|
|
199
|
+
|
|
200
|
+
Specifications are Gherkin, run with [Behave](https://behave.readthedocs.io/);
|
|
201
|
+
unit tests use pytest. Neither needs network access or API keys.
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
behave
|
|
205
|
+
python -m pytest tests -q
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## License
|
|
209
|
+
|
|
210
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "humanus"
|
|
3
|
+
version = "0.35.0"
|
|
4
|
+
description = "Configurable editorial compliance checks: policy packs, deterministic rules, zero-shot decision models and rewrite suggestions."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Anthus AI Solutions" }
|
|
10
|
+
]
|
|
11
|
+
dependencies = [
|
|
12
|
+
"PyYAML>=6.0.2,<7.0.0",
|
|
13
|
+
"jsonschema>=4.18,<5",
|
|
14
|
+
"markdown-it-py>=3.0,<5",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
jev = [
|
|
19
|
+
"typesafe-sdk>=0.7.2,<0.8",
|
|
20
|
+
]
|
|
21
|
+
dev = [
|
|
22
|
+
"behave>=1.2.6",
|
|
23
|
+
"pytest>=8.0.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/AnthusAI/Humanus"
|
|
28
|
+
Repository = "https://github.com/AnthusAI/Humanus"
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
humanus = "humanus.cli:main"
|
|
32
|
+
|
|
33
|
+
[build-system]
|
|
34
|
+
requires = ["setuptools>=68", "wheel"]
|
|
35
|
+
build-backend = "setuptools.build_meta"
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.packages.find]
|
|
38
|
+
where = ["src"]
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.package-data]
|
|
41
|
+
humanus = ["data/SKILL.md", "schemas/*.json", "packs/*.yml", "packs/*.md", "languages/*.yml"]
|
|
42
|
+
|
|
43
|
+
[tool.semantic_release]
|
|
44
|
+
version_toml = ["pyproject.toml:project.version"]
|
|
45
|
+
version_variables = ["src/humanus/__init__.py:__version__"]
|
|
46
|
+
build_command = "python -m pip install build && python -m build"
|
|
47
|
+
commit_parser = "conventional"
|
|
48
|
+
major_on_zero = false
|
|
49
|
+
|
|
50
|
+
[tool.semantic_release.branches.main]
|
|
51
|
+
match = "main"
|
|
52
|
+
|
|
53
|
+
[tool.semantic_release.changelog]
|
|
54
|
+
exclude_commit_patterns = []
|
|
55
|
+
|
|
56
|
+
[tool.semantic_release.remote]
|
|
57
|
+
type = "github"
|
humanus-0.35.0/setup.cfg
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Humanus: a configurable editorial compliance engine.
|
|
2
|
+
|
|
3
|
+
A caller sends content and a policy pack; ``check`` returns where the content
|
|
4
|
+
breaks the pack's policies, ``suggest`` offers rewrites for a finding or any
|
|
5
|
+
selection, ``draft_questions`` helps write classifier detectors, and
|
|
6
|
+
``calibrate`` learns from editors' decisions. Every request states its
|
|
7
|
+
content's language, which a shipped language pack must serve. The JSON Schemas under
|
|
8
|
+
``humanus/schemas`` are the contract (``load_schema``).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__version__ = "0.35.0"
|
|
12
|
+
|
|
13
|
+
from .calibration import apply_calibration, calibrate
|
|
14
|
+
from .check import CheckRequestValidationError, check
|
|
15
|
+
from .contracts import ContractValidationError, load_schema, validate_payload
|
|
16
|
+
from .finding_ids import finding_id
|
|
17
|
+
from .languages import Language, UnsupportedLanguageError, list_languages, resolve_language
|
|
18
|
+
from .packs import builtin_pack, list_builtin_packs
|
|
19
|
+
from .policy_pack import PolicyPack, PolicyPackValidationError, load_policy_pack
|
|
20
|
+
from .segmentation import SegmentedDocument, segment_document
|
|
21
|
+
from .style_profile_import import StyleProfileImportError, policy_pack_from_style_profile
|
|
22
|
+
from .suggest import SuggestRequestValidationError, SuggestUnavailableError, draft_questions, suggest
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"CheckRequestValidationError",
|
|
26
|
+
"ContractValidationError",
|
|
27
|
+
"Language",
|
|
28
|
+
"PolicyPack",
|
|
29
|
+
"PolicyPackValidationError",
|
|
30
|
+
"SegmentedDocument",
|
|
31
|
+
"StyleProfileImportError",
|
|
32
|
+
"SuggestRequestValidationError",
|
|
33
|
+
"SuggestUnavailableError",
|
|
34
|
+
"UnsupportedLanguageError",
|
|
35
|
+
"__version__",
|
|
36
|
+
"apply_calibration",
|
|
37
|
+
"builtin_pack",
|
|
38
|
+
"calibrate",
|
|
39
|
+
"check",
|
|
40
|
+
"draft_questions",
|
|
41
|
+
"finding_id",
|
|
42
|
+
"list_builtin_packs",
|
|
43
|
+
"list_languages",
|
|
44
|
+
"load_policy_pack",
|
|
45
|
+
"load_schema",
|
|
46
|
+
"policy_pack_from_style_profile",
|
|
47
|
+
"resolve_language",
|
|
48
|
+
"segment_document",
|
|
49
|
+
"suggest",
|
|
50
|
+
"validate_payload",
|
|
51
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def hash_stable(value: Any) -> str:
|
|
9
|
+
if isinstance(value, str):
|
|
10
|
+
payload = value
|
|
11
|
+
else:
|
|
12
|
+
payload = json.dumps(value, sort_keys=True, separators=(",", ":"), default=str)
|
|
13
|
+
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def hash_short(value: Any) -> str:
|
|
17
|
+
return hash_stable(value)[:16]
|
|
18
|
+
|