Skip to content

feat(sdk): add a textstat-exact Flesch-Kincaid and a benchmark report - #236

Draft
adnanrhussain wants to merge 4 commits into
mainfrom
sdk-fk-parity
Draft

feat(sdk): add a textstat-exact Flesch-Kincaid and a benchmark report#236
adnanrhussain wants to merge 4 commits into
mainfrom
sdk-fk-parity

Conversation

@adnanrhussain

@adnanrhussain adnanrhussain commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Adds a Flesch-Kincaid implementation to the SDK that returns the same number as Python textstat, to the last bit, plus a benchmark report comparing it against every JS alternative: sdks/typescript/docs/flesch-kincaid.md.

Nothing is wired up yet — no evaluator uses it, and it is not in the public API or the built bundle. This is the implementation, its tests, and the evidence. Migration is a separate PR.

Why

fk_score reaches the model as a fact about the text, so the same text must not be graded differently depending on which language ran the evaluator. Over the 100 fixture texts in evals/:

implementation mean abs. err worst exact
this port 0.0000 0.0000 100/100
text-readability (declared for TS) 1.0315 8.9395 0/100
textstat-ts 0.3951 1.2421 24/100
readability-scores 0.6272 6.2800 15/100
compromise + syllable (in use today) 0.5392 6.5540 3/100

A worst case of 8.94 is not a rounding difference — it is a different answer to "what grade is this text?". Full results, broken down by input class and by which count diverges, are in the doc.

Audit: what each surface uses

Notebooks (all 10) and the Python SDK use textstat. The TS SDK has two paths — text-readability (contract-declared) in preprocessing.ts, and a hand-rolled compromise + syllable in readability.ts. The three evaluators in PREPROCESSING_GAPS call the hand-rolled one, which no contract declares.

⚠️ Needs a licensing decision before the next publish

Exact parity requires shipping pyphen's hyph_en_US.dic, which comes from LibreOffice under a GPL 2.0+ / LGPL 2.1+ / MPL 1.1 tri-licence. I have used the MPL 1.1 option (file-level copyleft: that module stays MPL and keeps its notice, the rest of the SDK stays MIT) and documented it in THIRD_PARTY_LICENSES.md.

It is not optional — dropping it takes fixture-prose exactness from 100% to ~30%. It is also not in the built bundle today, because nothing imports it from the public entry, so this PR does not change what gets published. The decision lands with the migration PR. CMUdict, the other table, is BSD-style and needs only attribution.

Verification

  • 1,786 tests pass (1,086 existing + 700 new); typecheck, lint, build, scripts/check.py
  • 674 parity cases assert a delta of exactly 0, not a tolerance — same operations, same order, same doubles
  • Counts asserted per component too, since three counts feed one formula and errors can cancel
  • Hyphenation verified separately against pyphen across 141,028 words: 0 mismatches
  • Every guard confirmed load-bearing by breaking it: ASCII \b → 4 fail; ASCII \w → 297; honouring LEFTHYPHENMIN → 21; pattern-parser trailing zero → 40; Math.round → 2; textstat's ≤2-word sentence rule → 150

Notes

  • The two data modules are generated and checked in (npm run generate:fk-data), so no Python is needed to install or test. Parsed lazily.
  • roundGrade() exists because Python's round() rounds half to even and Math.round(v*100)/100 does not — they disagree on 7 of 4,027 values.
  • The report's results section is generated by npm run benchmark:fk; the prose around it is hand-written.

Copilot AI lite review requested due to automatic review settings August 29, 2026 19:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new standalone Node/JS package (@learning-commons/flesch-kincaid) intended to reproduce Python textstat’s Flesch–Kincaid grade (and its component counts) exactly, and wires it into the repo’s release + CI automation.

Changes:

  • Add a new packages/flesch-kincaid package implementing textstat-aligned tokenization, sentence counting, syllable counting (CMUdict + pyphen-style hyphenation fallback), FK grade calculation, and Python-style rounding.
  • Add Vitest unit + parity tests (driven by a checked-in corpus generated from Python textstat) plus Python scripts to regenerate the data tables and the parity corpus.
  • Add release-please config/manifest entries and a dedicated GitHub Actions workflow to typecheck/test/build the new package.

Reviewed changes

Copilot reviewed 17 out of 21 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
release-please-config.json Adds release-please configuration for the new package.
.release-please-manifest.json Registers the new package in the release-please manifest.
.github/workflows/test-flesch-kincaid.yml Adds CI to typecheck, test, and build the new package across Node versions.
.gitattributes Marks generated package artifacts (data tables + parity corpus) as generated.
packages/flesch-kincaid/package.json Defines the new package metadata, exports, scripts, engines, and dev deps.
packages/flesch-kincaid/package-lock.json Locks dependencies for the new package.
packages/flesch-kincaid/tsconfig.json Adds a TS config for building/testing the package.
packages/flesch-kincaid/tsup.config.ts Adds tsup bundling config for ESM+CJS+types output.
packages/flesch-kincaid/src/index.ts Exposes the public API (counts, grade, rounding, combined stats).
packages/flesch-kincaid/src/counts.ts Implements tokenization, punctuation stripping, word and sentence counting aligned to textstat rules.
packages/flesch-kincaid/src/syllables.ts Implements syllable counting via a CMUdict-derived table with pyphen-style hyphenation fallback.
packages/flesch-kincaid/src/hyphenation.ts Implements a pyphen-style hyphenation pattern parser and breakpoint finder.
packages/flesch-kincaid/tests/units.test.ts Adds focused unit tests for tokenization/counting/hyphenation behaviors.
packages/flesch-kincaid/tests/parity.test.ts Adds corpus-driven parity tests against checked-in textstat outputs.
packages/flesch-kincaid/scripts/generate-data.py Generates checked-in JS data tables from textstat/NLTK/pyphen sources.
packages/flesch-kincaid/scripts/generate-parity-corpus.py Generates the checked-in parity corpus JSON from Python textstat.
packages/flesch-kincaid/.gitignore Ignores build/test artifacts while keeping generated-but-checked-in tables/corpus.
packages/flesch-kincaid/README.md Documents rationale, parity requirements, API surface, and regeneration steps.
Files not reviewed (1)
  • packages/flesch-kincaid/package-lock.json: Generated file

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/flesch-kincaid/tests/parity.test.ts
@adnanrhussain adnanrhussain changed the title feat(flesch-kincaid): add a JS Flesch-Kincaid that matches Python textstat exactly feat(sdk): add a textstat-exact Flesch-Kincaid and a benchmark report Aug 29, 2026
Comment thread sdks/typescript/scripts/benchmark-flesch-kincaid.ts Fixed
@codecov

codecov Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.26087% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...escript/src/features/flesch-kincaid/hyphenation.ts 95.91% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants