A ruler for prose texture in Russian, English, Spanish and Italian. Pure stdlib, zero dependencies.
prosemeter measures the surface texture of a text — sentence rhythm,
punctuation habits, lexical texture, voice — and hands you a flat dict of
numbers. That's the whole product.
It is explicitly not an AI detector and not a quality metric:
it describes, it does not pass judgment. Some of the features it reports (filler
density, contrast frames, opener repetition) happen to be popular
tells of generated or padded prose, but prosemeter attaches no verdict to
any value. What counts as "too much" depends on your corpus, your genre and
your taste — that's your call, on your data.
pip install prosemeter
from prosemeter import profile
text = (
"The kettle clicked off and the kitchen went quiet. I had been rewriting "
"the same paragraph for an hour, and it still read like a brochure. So I "
"did what editors do: cut the openers, moved the numbers up front, and "
"read it aloud. Better. Not perfect, but better."
)
print(profile(text)) # lang="auto" detects English here
print(profile(text, lang="en")) # or say it explicitlyReal output (English adds five extra keys on top of the common core):
{
"words": 49, "sentences": 5,
"mean_sentence_len": 9.8, "cv_sentence_len": 0.69,
"pct_short": 0.4, "pct_long": 0.0, "staccato_share": 0.4,
"anaphora_top_share": 0.2,
"dash_per_100w": 0.0, "guillemet_per_100w": 0.0, "questions_per_sent": 0.0,
"contrast_frames_per_100w": 0.0,
"address_per_1k": 0.0, "first_person_per_1k": 40.8,
"digit_density_per_1k": 0.0,
"filler_per_100w": 0.0, "nominal_per_100w": 0.0,
"mean_word_len": 4.14, "ttr": 0.8,
"syllables_per_word": 1.33, "flesch_reading_ease": 84.7,
"polysyllable_per_100w": 0.0, "passive_per_100sent": 0.0,
"ly_adverbs_per_100w": 0.0
}The public API is three names: profile(text, lang="auto"),
SUPPORTED_LANGS (("ru", "en", "es", "it")) and __version__.
lang="auto" uses a cheap script/function-word heuristic that is reliable
on a paragraph of ordinary prose; pass the language explicitly for short
fragments or mixed-language text.
Counts are ints; every other value is a rate, so profiles of texts of
different lengths are comparable. Rates are per word (per_100w /
per_1k), per sentence (per_sent / per_100sent) or a share in 0..1.
| Key | What it measures | Languages |
|---|---|---|
words |
Word count. | all |
sentences |
Sentence count (regex splitter — see Honest limits). | all |
mean_sentence_len |
Mean sentence length in words. | all |
cv_sentence_len |
Coefficient of variation (std/mean) of sentence length — the classic dispersion measure used as a rhythm signal. Metronomic prose sits low; prose alternating long and short sentences sits higher. | all |
pct_short |
Share of sentences with ≤ 6 words. | all |
pct_long |
Share of sentences with ≥ 25 words. | all |
staccato_share |
Share of stub sentences (≤ 4 words) — the "Short. Punchy. Fragments." register. | all |
anaphora_top_share |
Opener repetition: share of sentences beginning with the single most frequent opening word ("This… This… This…"). English baselines are naturally noisy (articles, pronouns). | all |
dash_per_100w |
Em/en dashes (plus the spaced hyphen used as a dash) per 100 words. Dash-for-everything punctuation vs. dash-as-rare-accent. | all |
guillemet_per_100w |
Guillemets («») per 100 words — quote/dialogue-heavy vs. plain expository prose. | all |
questions_per_sent |
Question marks per sentence — rhetorical-question density. | all |
contrast_frames_per_100w |
Density of the "it's not X, it's Y" contrast frame (per-language patterns). One is rhetoric; a high rate is a texture of its own. | all |
address_per_1k |
Second-person reader-address words per 1000 words. | all |
first_person_per_1k |
First-person words per 1000 words — personal voice. | all |
digit_density_per_1k |
Numeric tokens per 1000 words — concreteness of the text's factual grounding. | all |
filler_per_100w |
Hits from a per-language filler/officialese phrase list per 100 words ("it is important to note", «важно отметить», "cabe destacar", "va sottolineato"). The Russian list follows the officialese-hunting tradition of Nora Gal. | all |
nominal_per_100w |
Abstract nominalizations per 100 words, matched by derivational suffix ("-tion", "-ность", "-ción", "-zione") — Helen Sword's "zombie nouns", Nora Gal's hidden verbs. | all |
mean_word_len |
Mean word length in characters. | all |
ttr |
Type-token ratio — lexical diversity. Length-sensitive: only compare texts of similar length. | all |
syllables_per_word |
Mean syllables per word (heuristic counter). | en |
flesch_reading_ease |
Flesch Reading Ease (Rudolf Flesch, 1948): higher = plainer. | en |
polysyllable_per_100w |
Words with ≥ 4 syllables per 100 words. | en |
passive_per_100sent |
Passive constructions ("to be" + past participle, regex approximation) per 100 sentences — the Orwell / Strunk & White signal. | en |
ly_adverbs_per_100w |
-ly adverbs per 100 words — the modifier crutch Stephen King warns about. |
en |
Tokenization is regex-only: sentences split on runs of .!?, words on
per-language character classes. Abbreviations, decimals and ellipses can
over- or under-split. Over a paragraph or an article these errors average
out; do not trust the numbers on a single sentence, and do not compare
ttr across texts of very different lengths.
This package does one thing: it measures. Bug reports (wrong counts, crash on valid input, broken language detection) are very welcome. Feature requests — new languages, new metrics, verdicts of any kind — are out of scope for now.
prosemeter is the measuring half of the writing engine behind
Laspi — an AI social media manager for small
businesses. You record one short voice note a week; Laspi turns it into a
week of ready-to-publish posts — captions written natively per network,
images and short videos — in English, Spanish, Russian or Italian. These
metrics are part of how Laspi keeps that writing sounding human.
The measuring is open source. The writing is the product: try a free week at laspi.pro — no card needed.
MIT © 2026 GradeBuilder S.L.