lexara 0.1.0a1__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.
Files changed (96) hide show
  1. lexara-0.1.0a1/.env.example +32 -0
  2. lexara-0.1.0a1/.gitignore +16 -0
  3. lexara-0.1.0a1/LICENSE.txt +16 -0
  4. lexara-0.1.0a1/PKG-INFO +485 -0
  5. lexara-0.1.0a1/README.md +435 -0
  6. lexara-0.1.0a1/demo/demo.py +152 -0
  7. lexara-0.1.0a1/demo/demo_script.md +164 -0
  8. lexara-0.1.0a1/eval/results/rewrite_eval_mock_2026-05-31.json +200 -0
  9. lexara-0.1.0a1/eval/results/rewrite_eval_openai_2026-05-31.json +200 -0
  10. lexara-0.1.0a1/eval/review/manual_review_mock_2026-05-31.md +18 -0
  11. lexara-0.1.0a1/eval/review/manual_review_openai_2026-05-31.md +18 -0
  12. lexara-0.1.0a1/examples/canonical/rewrite_already_at_target.json +136 -0
  13. lexara-0.1.0a1/examples/canonical/rewrite_hit_target.json +141 -0
  14. lexara-0.1.0a1/examples/canonical/rewrite_improved_miss.json +139 -0
  15. lexara-0.1.0a1/examples/demo_showcase.py +111 -0
  16. lexara-0.1.0a1/examples/sdk_rewrite_inspect_scores.py +44 -0
  17. lexara-0.1.0a1/examples/sdk_rewrite_to_target.py +34 -0
  18. lexara-0.1.0a1/examples/sdk_score_only.py +31 -0
  19. lexara-0.1.0a1/pyproject.toml +67 -0
  20. lexara-0.1.0a1/scripts/generate_review_table.py +164 -0
  21. lexara-0.1.0a1/scripts/pre_demo_check.sh +127 -0
  22. lexara-0.1.0a1/src/lexara/__init__.py +28 -0
  23. lexara-0.1.0a1/src/lexara/api/__init__.py +3 -0
  24. lexara-0.1.0a1/src/lexara/api/app.py +72 -0
  25. lexara-0.1.0a1/src/lexara/api/dependencies.py +51 -0
  26. lexara-0.1.0a1/src/lexara/api/errors.py +72 -0
  27. lexara-0.1.0a1/src/lexara/api/middleware.py +46 -0
  28. lexara-0.1.0a1/src/lexara/api/routes/__init__.py +0 -0
  29. lexara-0.1.0a1/src/lexara/api/routes/health.py +17 -0
  30. lexara-0.1.0a1/src/lexara/api/routes/readability.py +77 -0
  31. lexara-0.1.0a1/src/lexara/client.py +223 -0
  32. lexara-0.1.0a1/src/lexara/config.py +53 -0
  33. lexara-0.1.0a1/src/lexara/eval/__init__.py +14 -0
  34. lexara-0.1.0a1/src/lexara/eval/data/rewrite_effectiveness.json +123 -0
  35. lexara-0.1.0a1/src/lexara/eval/data/rewrite_fast.json +52 -0
  36. lexara-0.1.0a1/src/lexara/eval/harness.py +106 -0
  37. lexara-0.1.0a1/src/lexara/eval/models.py +137 -0
  38. lexara-0.1.0a1/src/lexara/eval/runner.py +91 -0
  39. lexara-0.1.0a1/src/lexara/exceptions.py +83 -0
  40. lexara-0.1.0a1/src/lexara/logging_config.py +54 -0
  41. lexara-0.1.0a1/src/lexara/models/__init__.py +38 -0
  42. lexara-0.1.0a1/src/lexara/models/common.py +27 -0
  43. lexara-0.1.0a1/src/lexara/models/errors.py +18 -0
  44. lexara-0.1.0a1/src/lexara/models/rewrite.py +162 -0
  45. lexara-0.1.0a1/src/lexara/models/scoring.py +87 -0
  46. lexara-0.1.0a1/src/lexara/options.py +28 -0
  47. lexara-0.1.0a1/src/lexara/rewriting/__init__.py +42 -0
  48. lexara-0.1.0a1/src/lexara/rewriting/pipeline.py +239 -0
  49. lexara-0.1.0a1/src/lexara/rewriting/prompts.py +111 -0
  50. lexara-0.1.0a1/src/lexara/rewriting/providers/__init__.py +32 -0
  51. lexara-0.1.0a1/src/lexara/rewriting/providers/base.py +55 -0
  52. lexara-0.1.0a1/src/lexara/rewriting/providers/errors.py +35 -0
  53. lexara-0.1.0a1/src/lexara/rewriting/providers/mock.py +128 -0
  54. lexara-0.1.0a1/src/lexara/rewriting/providers/openai.py +232 -0
  55. lexara-0.1.0a1/src/lexara/rewriting/providers/parsing.py +75 -0
  56. lexara-0.1.0a1/src/lexara/rewriting/providers/retry.py +42 -0
  57. lexara-0.1.0a1/src/lexara/rewriting/providers/usage.py +81 -0
  58. lexara-0.1.0a1/src/lexara/scoring/__init__.py +14 -0
  59. lexara-0.1.0a1/src/lexara/scoring/base.py +39 -0
  60. lexara-0.1.0a1/src/lexara/scoring/familiar_words.py +78 -0
  61. lexara-0.1.0a1/src/lexara/scoring/frameworks/__init__.py +21 -0
  62. lexara-0.1.0a1/src/lexara/scoring/frameworks/atos_estimated.py +33 -0
  63. lexara-0.1.0a1/src/lexara/scoring/frameworks/dale_chall.py +52 -0
  64. lexara-0.1.0a1/src/lexara/scoring/frameworks/flesch_kincaid.py +37 -0
  65. lexara-0.1.0a1/src/lexara/scoring/frameworks/lexile_estimated.py +72 -0
  66. lexara-0.1.0a1/src/lexara/scoring/interpret.py +66 -0
  67. lexara-0.1.0a1/src/lexara/scoring/registry.py +59 -0
  68. lexara-0.1.0a1/src/lexara/scoring/sentence_split.py +85 -0
  69. lexara-0.1.0a1/src/lexara/scoring/text_stats.py +74 -0
  70. lexara-0.1.0a1/src/lexara/services/__init__.py +13 -0
  71. lexara-0.1.0a1/src/lexara/services/rewrite_response.py +147 -0
  72. lexara-0.1.0a1/src/lexara/services/rewrite_service.py +55 -0
  73. lexara-0.1.0a1/src/lexara/services/scoring_service.py +87 -0
  74. lexara-0.1.0a1/tests/__init__.py +0 -0
  75. lexara-0.1.0a1/tests/conftest.py +29 -0
  76. lexara-0.1.0a1/tests/fixtures/scoring_calibration.json +80 -0
  77. lexara-0.1.0a1/tests/test_api_rewrite.py +122 -0
  78. lexara-0.1.0a1/tests/test_api_rewrite_workflow.py +42 -0
  79. lexara-0.1.0a1/tests/test_api_score.py +59 -0
  80. lexara-0.1.0a1/tests/test_api_score_edge_cases.py +15 -0
  81. lexara-0.1.0a1/tests/test_canonical_examples.py +33 -0
  82. lexara-0.1.0a1/tests/test_client.py +185 -0
  83. lexara-0.1.0a1/tests/test_eval_harness.py +183 -0
  84. lexara-0.1.0a1/tests/test_openai_contract.py +88 -0
  85. lexara-0.1.0a1/tests/test_openai_provider.py +192 -0
  86. lexara-0.1.0a1/tests/test_provider_retry.py +54 -0
  87. lexara-0.1.0a1/tests/test_rewrite_effectiveness.py +114 -0
  88. lexara-0.1.0a1/tests/test_rewrite_pipeline.py +63 -0
  89. lexara-0.1.0a1/tests/test_rewrite_pipeline_core.py +141 -0
  90. lexara-0.1.0a1/tests/test_rewrite_provider_failures.py +75 -0
  91. lexara-0.1.0a1/tests/test_rewrite_workflow_alpha.py +146 -0
  92. lexara-0.1.0a1/tests/test_scoring.py +89 -0
  93. lexara-0.1.0a1/tests/test_scoring_calibration.py +63 -0
  94. lexara-0.1.0a1/tests/test_scoring_formulas.py +74 -0
  95. lexara-0.1.0a1/tests/test_sentence_split.py +57 -0
  96. lexara-0.1.0a1/tests/test_text_stats.py +64 -0
@@ -0,0 +1,32 @@
1
+ # Lexara API configuration
2
+ # Copy to `.env` and adjust. Loaded via pydantic-settings (prefix LEXARA_).
3
+
4
+ # Comma-separated list of valid API keys. Requests must send one of these as
5
+ # the `Authorization: Bearer <key>` header (or `x-api-key`).
6
+ LEXARA_API_KEYS=dev-local-key
7
+
8
+ # deployment: development | production
9
+ LEXARA_ENV=development
10
+
11
+ # LLM provider for the rewrite endpoint: "mock" | "openai"
12
+ #
13
+ # ALPHA NOTE: mock is for local dev/tests ONLY (word substitution, not an LLM).
14
+ # External demos and alpha customers require openai + LEXARA_OPENAI_API_KEY.
15
+ LEXARA_LLM_PROVIDER=mock
16
+
17
+ # Block mock when LEXARA_ENV=production (recommended for external alpha)
18
+ LEXARA_ALLOW_MOCK_PROVIDER=true
19
+
20
+ # Only needed when LEXARA_LLM_PROVIDER=openai
21
+ LEXARA_OPENAI_API_KEY=
22
+ LEXARA_OPENAI_MODEL=gpt-4o-mini
23
+ LEXARA_OPENAI_TIMEOUT_SECONDS=60
24
+ LEXARA_OPENAI_MAX_RETRIES=2
25
+ # Cap completion length per LLM call (cost + truncation safety).
26
+ LEXARA_OPENAI_MAX_COMPLETION_TOKENS=4096
27
+
28
+ # Logging: DEBUG | INFO | WARNING | ERROR
29
+ LEXARA_LOG_LEVEL=INFO
30
+
31
+ # Safety cap so a single request can't loop forever.
32
+ LEXARA_MAX_REWRITE_PASSES=5
@@ -0,0 +1,16 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+ build/
6
+ dist/
7
+ .venv/
8
+ venv/
9
+ env/
10
+ .env
11
+ .pytest_cache/
12
+ .mypy_cache/
13
+ .ruff_cache/
14
+ .coverage
15
+ htmlcov/
16
+ .DS_Store
@@ -0,0 +1,16 @@
1
+ Copyright (c) 2026 Chanel Johnson
2
+
3
+ All rights reserved.
4
+
5
+ This software and associated documentation files (the "Software") are the
6
+ proprietary and confidential property of Chanel Johnson. Unauthorized copying,
7
+ distribution, modification, or use of this Software, in whole or in part, is
8
+ strictly prohibited without the express written permission of the copyright holder.
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
11
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
12
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
13
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
14
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
15
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
16
+ SOFTWARE.
@@ -0,0 +1,485 @@
1
+ Metadata-Version: 2.4
2
+ Name: lexara
3
+ Version: 0.1.0a1
4
+ Summary: Reading level scoring and rewrite API for edtech
5
+ Project-URL: Homepage, https://github.com/ChanelYJ19/lexara
6
+ Project-URL: Documentation, https://github.com/ChanelYJ19/lexara#readme
7
+ Author-email: Chanel Johnson <chanel_johnson@alumni.brown.edu>
8
+ License: Copyright (c) 2026 Chanel Johnson
9
+
10
+ All rights reserved.
11
+
12
+ This software and associated documentation files (the "Software") are the
13
+ proprietary and confidential property of Chanel Johnson. Unauthorized copying,
14
+ distribution, modification, or use of this Software, in whole or in part, is
15
+ strictly prohibited without the express written permission of the copyright holder.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
24
+ License-File: LICENSE.txt
25
+ Keywords: ai,edtech,education,nlp,readability
26
+ Classifier: Development Status :: 3 - Alpha
27
+ Classifier: Intended Audience :: Developers
28
+ Classifier: Programming Language :: Python :: 3
29
+ Classifier: Programming Language :: Python :: 3.9
30
+ Classifier: Programming Language :: Python :: 3.10
31
+ Classifier: Programming Language :: Python :: 3.11
32
+ Classifier: Programming Language :: Python :: 3.12
33
+ Classifier: Programming Language :: Python :: 3.13
34
+ Classifier: Topic :: Education
35
+ Classifier: Topic :: Text Processing :: Linguistic
36
+ Requires-Python: >=3.9
37
+ Requires-Dist: httpx>=0.24
38
+ Requires-Dist: pydantic>=2.0
39
+ Provides-Extra: dev
40
+ Requires-Dist: httpx>=0.24; extra == 'dev'
41
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
42
+ Requires-Dist: pytest>=8.0; extra == 'dev'
43
+ Provides-Extra: openai
44
+ Requires-Dist: openai>=1.30; extra == 'openai'
45
+ Provides-Extra: server
46
+ Requires-Dist: fastapi>=0.110; extra == 'server'
47
+ Requires-Dist: pydantic-settings>=2.2; extra == 'server'
48
+ Requires-Dist: uvicorn[standard]>=0.29; extra == 'server'
49
+ Description-Content-Type: text/markdown
50
+
51
+ # Lexara
52
+
53
+ **Rewrite educational text to a target grade level. Return multi-framework proof of what changed.**
54
+
55
+ Lexara is developer infrastructure for edtech builders. One API call scores a passage, rewrites it toward a target US grade level using OpenAI (gpt-4o-mini), rescores after each pass, and returns `input` → `output` with per-framework before/after proof.
56
+
57
+ **Measured against a 12-passage K-12 dataset (science, social studies, math, ELA, grades 2–12):**
58
+ - **58% of passages hit the target grade** within ±1 grade level (7/12 with OpenAI gpt-4o-mini)
59
+ - **Average grade reduction: −8.9 levels** per rewrite
60
+ - **Works best for grade 4–12 targets.** Passages targeting below grade 4 on academic-register text consistently fall short — see [Accuracy & Limitations](#accuracy--limitations).
61
+
62
+ ```bash
63
+ curl -s http://localhost:8000/v1/readability/rewrite \
64
+ -H "Authorization: Bearer dev-local-key" \
65
+ -H "Content-Type: application/json" \
66
+ -d '{"text":"Photosynthesis is the biochemical process by which chlorophyll-containing organisms convert light energy into chemical energy.","target_grade":6,"max_passes":5}'
67
+ ```
68
+
69
+ Not a readability score you stare at. A rewrite you can act on — with scored evidence before and after.
70
+
71
+ ---
72
+
73
+ ## Why Lexara exists
74
+
75
+ ### Score-only is not enough
76
+
77
+ A score-only API tells you a worksheet reads at grade 11. It does not give you a grade-5 version. Your user still has to rewrite by hand, guess whether it worked, and hope one readability number reflects their district's expectations.
78
+
79
+ That is a diagnostic. Edtech products need an **action**: adjust the text, verify the result, retry if needed.
80
+
81
+ ### Why multi-framework scoring + rewrite together
82
+
83
+ No single readability formula is authoritative — districts, publishers, and literacy tools reference different frameworks (Flesch-Kincaid, Dale-Chall, Lexile-style, ATOS-style). Lexara:
84
+
85
+ 1. **Scores across frameworks** before and after the rewrite
86
+ 2. **Rewrites toward your target grade**, not a single opaque number
87
+ 3. **Returns proof** — `outcome.hit_target`, `frameworks_improved`, and per-framework grades on both sides
88
+
89
+ Scoring is the **verification layer** for the rewrite. The product is the loop: **rewrite → rescore → prove**.
90
+
91
+ ---
92
+
93
+ ## Quick start
94
+
95
+ ```bash
96
+ python3 -m venv .venv && source .venv/bin/activate
97
+ pip install -e ".[dev]"
98
+ cp .env.example .env
99
+ lexara-api # → http://localhost:8000/docs
100
+ ```
101
+
102
+ > **Alpha note:** Default `LEXARA_LLM_PROVIDER=mock` is for **local dev and tests only**. It uses word substitution, not an LLM — expect low `hit_target` on hard passages. **External demos and alpha require `openai`.** Run `python examples/demo_showcase.py` to see a mock warning banner when the API uses mock.
103
+
104
+ ### 1. Rewrite (curl) — start here
105
+
106
+ ```bash
107
+ curl -s http://localhost:8000/v1/readability/rewrite \
108
+ -H "Authorization: Bearer dev-local-key" \
109
+ -H "Content-Type: application/json" \
110
+ -d '{
111
+ "text": "Photosynthesis is the biochemical process by which chlorophyll-containing organisms convert light energy into chemical energy, subsequently producing glucose and releasing oxygen as a byproduct of cellular metabolism.",
112
+ "target_grade": 6,
113
+ "preserve_meaning": true,
114
+ "max_passes": 5
115
+ }' | python3 -m json.tool
116
+ ```
117
+
118
+ **Actual output** (OpenAI gpt-4o-mini, measured):
119
+
120
+ ```
121
+ input.estimated_grade_level: 18.3
122
+ output.estimated_grade_level: 7.4
123
+ outcome.hit_target: true
124
+ output.text: "Photosynthesis helps plants make food. Plants have a green color
125
+ called chlorophyll. This color captures light from the sun. Then,
126
+ plants change this light into chemical energy to produce glucose,
127
+ and they release oxygen as a result."
128
+ ```
129
+
130
+ Look for: `outcome.summary`, `outcome.hit_target`, `input.estimated_grade_level`, `output.estimated_grade_level`, `output.text`.
131
+
132
+ ### 2. Rewrite (Python SDK)
133
+
134
+ ```python
135
+ from lexara import LexaraClient, Tone
136
+
137
+ client = LexaraClient(api_key="dev-local-key")
138
+
139
+ result = client.readability.rewrite(
140
+ "Photosynthesis is the biochemical process by which chlorophyll-containing "
141
+ "organisms convert light energy into chemical energy.",
142
+ target_grade=6,
143
+ preserve_meaning=True,
144
+ max_passes=5,
145
+ )
146
+
147
+ print(result.outcome.summary)
148
+ print(f"{result.input.estimated_grade_level} → {result.output.estimated_grade_level} (target {result.target.grade})")
149
+ print(f"Hit target: {result.hit_target} | Frameworks improved: {result.outcome.frameworks_improved}")
150
+ print(result.output.text)
151
+ ```
152
+
153
+ ### 3. Score only (when you don't need a rewrite yet)
154
+
155
+ ```python
156
+ scored = client.readability.score(
157
+ passage,
158
+ frameworks=["flesch_kincaid", "lexile"],
159
+ )
160
+ print(scored.aggregate.estimated_grade_level)
161
+ ```
162
+
163
+ Use `POST /v1/readability/score` for diagnostics. Use `rewrite` when you need to **act** on the result.
164
+
165
+ ---
166
+
167
+ ## Demo-ready examples
168
+
169
+ Three passages you can run in a customer call, Show HN thread, or live demo.
170
+
171
+ > **Mock provider:** Demos against local `lexara-api` with default settings use mock. Rewrite quality is not production-representative. For customer-facing demos set `LEXARA_LLM_PROVIDER=openai`.
172
+
173
+ ### Demo A — Science passage: college → grade 6
174
+
175
+ **Story:** *"Your curriculum import is too hard for middle school. One call fixes and proves it."*
176
+
177
+ ```bash
178
+ curl -s http://localhost:8000/v1/readability/rewrite \
179
+ -H "Authorization: Bearer dev-local-key" \
180
+ -H "Content-Type: application/json" \
181
+ -d '{
182
+ "text": "Photosynthesis is the biochemical process by which chlorophyll-containing organisms convert light energy into chemical energy, subsequently producing glucose and releasing oxygen as a byproduct of cellular metabolism.",
183
+ "target_grade": 6,
184
+ "max_passes": 5,
185
+ "tolerance": 1.5
186
+ }'
187
+ ```
188
+
189
+ ```python
190
+ from lexara import LexaraClient
191
+
192
+ PASSAGE = (
193
+ "Photosynthesis is the biochemical process by which chlorophyll-containing "
194
+ "organisms convert light energy into chemical energy, subsequently producing "
195
+ "glucose and releasing oxygen as a byproduct of cellular metabolism."
196
+ )
197
+
198
+ client = LexaraClient(api_key="dev-local-key")
199
+ r = client.readability.rewrite(PASSAGE, target_grade=6, max_passes=5, tolerance=1.5)
200
+ print(f"Before: grade {r.input.estimated_grade_level} After: grade {r.output.estimated_grade_level}")
201
+ print(r.output.text)
202
+ ```
203
+
204
+ **Talk track:** Show `input.frameworks[]` vs `output.frameworks[]` — four frameworks moved, not one proprietary number.
205
+
206
+ ---
207
+
208
+ ### Demo B — Worksheet instructions: teacher tone, grade 4
209
+
210
+ **Story:** *"Teachers paste LMS instructions. Lexara returns student-ready text with meaning preserved."*
211
+
212
+ ```python
213
+ from lexara import LexaraClient, Tone
214
+
215
+ client = LexaraClient(api_key="dev-local-key")
216
+
217
+ result = client.readability.rewrite(
218
+ "Students will subsequently utilize the provided manipulatives to demonstrate "
219
+ "their comprehension of fractional equivalence, and they must obtain sufficient "
220
+ "evidence before completing the assessment.",
221
+ target_grade=4,
222
+ tone=Tone.friendly,
223
+ preserve_meaning=True,
224
+ max_passes=4,
225
+ )
226
+
227
+ if result.hit_target:
228
+ ship_to_lms(result.output.text)
229
+ else:
230
+ print(result.outcome.summary, result.execution.warnings)
231
+ ```
232
+
233
+ Run: `python examples/sdk_rewrite_to_target.py`
234
+
235
+ ---
236
+
237
+ ### Demo C — Already at target: no LLM call
238
+
239
+ **Story:** *"Lexara doesn't burn tokens when content is already right — it scores, verifies, and skips."*
240
+
241
+ ```python
242
+ from lexara import LexaraClient
243
+
244
+ client = LexaraClient(api_key="dev-local-key")
245
+
246
+ result = client.readability.rewrite(
247
+ "Photosynthesis lets plants make food from sunlight. Plants need sun and water to grow.",
248
+ target_grade=5,
249
+ tolerance=1.0,
250
+ )
251
+
252
+ assert result.execution.skipped is True
253
+ assert result.execution.passes_used == 0
254
+ assert result.hit_target is True
255
+ assert result.output.text == result.input.text
256
+ print(result.outcome.summary)
257
+ # → "Already at grade 5.1 (target 5, within ±1). No rewrite needed."
258
+ ```
259
+
260
+ Full payload: `examples/canonical/rewrite_already_at_target.json`
261
+
262
+ **Run all three:** `python examples/demo_showcase.py`
263
+
264
+ ---
265
+
266
+ ## API
267
+
268
+ | Method | Path | Purpose |
269
+ |--------|------|---------|
270
+ | `POST` | `/v1/readability/rewrite` | **Primary** — rewrite to target grade + multi-framework proof |
271
+ | `POST` | `/v1/readability/score` | Diagnostics only — no rewrite |
272
+ | `GET` | `/health` | Liveness |
273
+
274
+ Auth: `Authorization: Bearer <key>` or `x-api-key: <key>`
275
+
276
+ ### Rewrite response (read in this order)
277
+
278
+ | Block | Meaning |
279
+ |-------|---------|
280
+ | `input` / `output` | Original vs rewritten text + per-framework grades |
281
+ | `outcome` | `hit_target`, grade from → to, `frameworks_improved`, `summary` |
282
+ | `target` | Goal grade, tolerance, grade band |
283
+ | `execution` | Passes used, provider, `skipped`, `warnings` |
284
+
285
+ Shortcuts: `rewritten_text`, `hit_target`.
286
+
287
+ Canonical JSON: [`examples/canonical/`](examples/canonical/)
288
+
289
+ ---
290
+
291
+ ## Python SDK
292
+
293
+ ```python
294
+ from lexara import LexaraClient, Tone, RewriteOptions
295
+
296
+ client = LexaraClient(api_key="...", timeout=120, max_retries=2)
297
+
298
+ # Hero workflow
299
+ result = client.readability.rewrite(
300
+ text,
301
+ target_grade=5,
302
+ preserve_meaning=True,
303
+ tone=Tone.friendly,
304
+ max_passes=4,
305
+ )
306
+
307
+ # Or bundle options (all fields optional except target_grade on rewrite call)
308
+ opts = RewriteOptions(preserve_meaning=True, tone=Tone.friendly, max_passes=4)
309
+ result = client.readability.rewrite(text, target_grade=5, options=opts)
310
+ ```
311
+
312
+ | Parameter | Default | Meaning |
313
+ |-----------|---------|---------|
314
+ | `target_grade` | required | Desired US grade level (1–16) |
315
+ | `preserve_meaning` | `True` | Keep facts, numbers, names, steps |
316
+ | `tone` | `neutral` | `friendly`, `formal`, `playful`, `academic`, … |
317
+ | `max_passes` | `3` | Max rewrite → rescore iterations |
318
+ | `frameworks` | all | Frameworks used to judge progress |
319
+ | `tolerance` | `1.0` | Grade levels within target = hit |
320
+
321
+ **Examples:** `examples/sdk_rewrite_to_target.py` · `examples/sdk_rewrite_inspect_scores.py` · `examples/sdk_score_only.py`
322
+
323
+ **Exceptions:** `AuthenticationError` · `ValidationError` · `LexaraTimeoutError` · `LexaraConnectionError` · `LexaraAPIError`
324
+
325
+ ---
326
+
327
+ ## How rewrite works
328
+
329
+ Each `POST /v1/readability/rewrite` request:
330
+
331
+ 1. Scores `input` across selected frameworks
332
+ 2. Skips the LLM if already within `tolerance` of `target_grade`
333
+ 3. Otherwise **rewrite → rescore → retry** up to `max_passes`
334
+ 4. Returns the best attempt with full before/after snapshots
335
+
336
+ Provider failures become `execution.warnings` — the endpoint returns 200 with the best text so far. Check `outcome.hit_target` before shipping to users.
337
+
338
+ `execution.degraded` means **no usable rewrite was produced** (e.g. all provider calls failed). Warnings alone do not set `degraded` when a rewrite succeeded.
339
+
340
+ ---
341
+
342
+ ## Frameworks & safe claims
343
+
344
+ | Framework | ID | Confidence | What to tell customers |
345
+ |-----------|-----|------------|------------------------|
346
+ | Flesch-Kincaid Grade | `flesch_kincaid` | **exact** | Standard formula; syllable counts use an estimated syllable heuristic |
347
+ | New Dale-Chall | `dale_chall` | **estimated** | Published formula; familiar-word list is approximate, not the licensed 3,000-word list |
348
+ | ATOS-style | `atos_estimated` | **estimated** | ATOS-style estimate — **not** an official Accelerated Reader level |
349
+ | Lexile-equivalent | `lexile_estimated` | **estimated** | Lexile-scale estimate — **not** an official MetaMetrics Lexile score |
350
+
351
+ Legacy aliases: `atos` → `atos_estimated`, `lexile` → `lexile_estimated`.
352
+
353
+ ### Safe to say
354
+
355
+ - Rewrite toward a target US grade level in one API call
356
+ - Multi-framework before/after proof (`input.frameworks` → `output.frameworks`)
357
+ - Flesch-Kincaid uses the standard grade formula; other frameworks are clearly labeled `estimated`
358
+ - Developer-friendly SDK: `client.readability.rewrite(text, target_grade=6)`
359
+
360
+ ### Do not say
361
+
362
+ - Official Lexile, ATOS, or Dale-Chall certification
363
+ - Guaranteed rewrite success on every passage — always check `outcome.hit_target`
364
+ - Single "true" reading level — Lexara returns multiple frameworks intentionally
365
+
366
+ ---
367
+
368
+ ## Accuracy & Limitations
369
+
370
+ Measured with OpenAI gpt-4o-mini against a 12-passage K-12 dataset (v2, May 2026):
371
+
372
+ - **Below grade 4 on academic-register text: expect misses.** A passage scoring 17.1 targeting grade 2 landed at 4.5 after five passes — still 2.5 levels off. The pipeline simplifies vocabulary and sentence structure, but readability formulas score short function words and sentence count, not whether the content is truly accessible to a 7-year-old.
373
+ - **Borderline passages vary run to run.** A grade-4 target passage hit 4.5 in one run and 7.0 in the next with the same prompt and model. Always check `outcome.hit_target` at call time; do not cache a one-time pass as a permanent quality signal.
374
+ - **Extreme grade gaps (> 12 levels) reliably miss — this is expected.** The economics stress-test passage (original grade 22.3 → target 5.0) landed at 9.8. A single rewrite loop cannot close a 17-level gap; this failure mode is intentional and documented in the dataset.
375
+ - **Human review is recommended before shipping to students.** `outcome.hit_target` confirms the scored grade landed in range — it does not verify that meaning was preserved, that the text is age-appropriate in tone, or that domain-specific terms were handled correctly. Treat the output as a draft, not a final.
376
+
377
+ ---
378
+
379
+ ## Configuration
380
+
381
+ See `.env.example`.
382
+
383
+ | Var | Default | Meaning |
384
+ |-----|---------|---------|
385
+ | `LEXARA_ENV` | `development` | `development` or `production` |
386
+ | `LEXARA_ALLOW_MOCK_PROVIDER` | `true` | Set `false` with `LEXARA_ENV=production` to block mock |
387
+ | `LEXARA_LLM_PROVIDER` | `mock` | `mock` (dev/test) or `openai` (external alpha) |
388
+ | `LEXARA_OPENAI_API_KEY` | – | Required when provider is `openai` |
389
+ | `LEXARA_OPENAI_MODEL` | `gpt-4o-mini` | Chat model for rewrites |
390
+ | `LEXARA_API_KEYS` | `dev-local-key` | Valid API keys |
391
+
392
+ Real provider setup:
393
+
394
+ ```bash
395
+ pip install -e ".[openai]"
396
+ export LEXARA_LLM_PROVIDER=openai
397
+ export LEXARA_OPENAI_API_KEY=sk-...
398
+ lexara-api
399
+ ```
400
+
401
+ ---
402
+
403
+ ## Rewrite effectiveness evals
404
+
405
+ Measure rewrite quality before demos — not just formula correctness:
406
+
407
+ ```bash
408
+ lexara-eval # table summary (mock)
409
+ lexara-eval --output eval/results.json # save JSON for review
410
+ ```
411
+
412
+ Dataset: `src/lexara/eval/data/rewrite_effectiveness.json` (grades 2–12, five passage types).
413
+
414
+ With OpenAI: `lexara-eval --provider openai --output eval/results-openai.json`
415
+
416
+ ---
417
+
418
+ ## Homepage & API copy (suggested)
419
+
420
+ **Headline:** Rewrite text to your target grade. Prove it across four frameworks.
421
+
422
+ **Subhead:** Lexara is edtech API infrastructure — score, rewrite, rescore, and return before/after proof in one call. Not a number. A number you acted on.
423
+
424
+ **API one-liner:** `POST /v1/readability/rewrite` — rewrite toward a target grade; get `input`, `output`, and `outcome.hit_target` with Flesch-Kincaid, Dale-Chall, ATOS-style, and Lexile-equivalent verification.
425
+
426
+ **Bullets for landing page:**
427
+ - Score-only APIs diagnose. Lexara rewrites and proves the result.
428
+ - Multi-framework before/after — not one proprietary readability number.
429
+ - One SDK call: `client.readability.rewrite(passage, target_grade=6)`
430
+ - Skips the LLM when text is already at target.
431
+
432
+ ---
433
+
434
+ ## Demo script (5 minutes)
435
+
436
+ Use this flow in a customer call or Show HN live demo:
437
+
438
+ 1. **Problem (30s)** — Paste the Demo A science passage. *"This reads at college level. Your middle-school product can't use it as-is."*
439
+ 2. **Rewrite (60s)** — Run curl or SDK. Show `outcome.summary` and grade drop in `input` → `output`.
440
+ 3. **Proof (60s)** — Expand `input.frameworks` and `output.frameworks`. *"We don't trust one formula — we show four, before and after."*
441
+ 4. **Teacher workflow (60s)** — Demo B worksheet instructions with `tone=friendly`. Show rewritten bullet-friendly text.
442
+ 5. **Efficiency (30s)** — Demo C already-at-target. *"No LLM call when content is already right."*
443
+ 6. **Ship check (30s)** — Point at `outcome.hit_target`, `execution.warnings`, and the safe-claims table. *"You decide when to ship; Lexara gives you structured proof."*
444
+
445
+ Runnable: `python examples/demo_showcase.py`
446
+
447
+ ---
448
+
449
+ ## Alpha release checklist
450
+
451
+ Before any external demo or alpha customer:
452
+
453
+ - [ ] `LEXARA_LLM_PROVIDER=openai` and `LEXARA_OPENAI_API_KEY` set
454
+ - [ ] `LEXARA_ENV=production` and `LEXARA_ALLOW_MOCK_PROVIDER=false` (optional guard)
455
+ - [ ] `lexara-eval --provider openai --output eval/results-openai.json` — review `hit_target` rate
456
+ - [ ] Manual semantic review on Demo A/B/C (`python examples/demo_showcase.py`)
457
+ - [ ] `pytest -m integration` with live OpenAI key
458
+ - [ ] Safe-claims table reviewed in customer-facing materials
459
+ - [ ] Confirm `execution.degraded` vs `execution.warnings` behavior with integrators
460
+
461
+ ---
462
+
463
+ ## Architecture
464
+
465
+ | Layer | Role |
466
+ |-------|------|
467
+ | `api/` | HTTP, auth, middleware |
468
+ | `services/` | Rewrite + scoring orchestration |
469
+ | `scoring/` | Pure readability formulas |
470
+ | `rewriting/` | LLM provider + rewrite/rescore pipeline |
471
+ | `eval/` | Rewrite effectiveness harness |
472
+ | `client.py` | Python SDK |
473
+
474
+ ---
475
+
476
+ ## Tests
477
+
478
+ ```bash
479
+ pytest # full suite (mock, no network)
480
+ pytest tests/test_eval_harness.py -v
481
+ pytest tests/test_client.py -v
482
+
483
+ # Live OpenAI — opt-in:
484
+ LEXARA_OPENAI_API_KEY=sk-... pytest -m integration -v
485
+ ```