-
Notifications
You must be signed in to change notification settings - Fork 0
Use Cases
Real-world scenarios for the EFL classroom, each with the input you give the
tools and the output you get back. Vocabulary examples are real runs of
vocab_profile.py; grammar examples follow the profiler's documented JSON/pretty
shape (see GRAMMARPROFILE.md).
Reading the results. Typical is the busiest band (where most of the text sits); Reaches / 90% coverage is the highest band you need to account for. A B1 class can usually handle a text that is typical A2, reaches B1; a text that reaches C1 will need pre-teaching or adaptation first.
Jump to a scenario
- Screening a reading text for a specific class
- Vocabulary pre-teaching — pull the hard words
- Checking grammatical range in a student's writing
- The A2-vocabulary / B2-grammar trap
- Auditing and comparing textbook passages
- Writing or simplifying a graded reader
- Curriculum mapping — did this unit cover the target grammar?
- Profiling a whole document — PDF, Word, Markdown
- Inside Claude Code / Cowork — just ask
- Scripting a classroom pipeline with JSON
- Which of these 20 articles suits my B1 class?
- Spaced introduction across a folder of readings
- The app consumes the report contract (RubricMaker)
Scenario. You teach a B1 group and found an article you might assign. Before handing it out, you want to know whether the vocabulary is in reach.
Input
python3 vocab_profile.py --type cefr --text \
"The researchers analysed the philosophical implications of photosynthesis and chlorophyll synthesis in aquatic ecosystems."Output (pretty, in a terminal)
Vocabulary Profile
Total words: 14
Typical: A1 90% coverage: C2
Most words are A1; you need C2 to cover ~90%.
■ A1 36% ■ B1 29% ■ B2 14% ■ C1 7% ■ C2 14%
B1 analysed · ecosystems · researchers · synthesis
B2 implications · philosophical
C1 aquatic
C2 chlorophyll · photosynthesis
What you learn. Grammatically simple, but the content words shoot up to C1–C2 (aquatic, chlorophyll, photosynthesis). For a B1 class this text needs the academic terms pre-taught or glossed — it is not a cold read. Compare with a text that stays inside A1/A2 and can be handed straight over:
python3 vocab_profile.py --type cefr --text "Yesterday I walked to the shop to buy bread and milk."
# Typical: A1 90% coverage: A1 → hand it out as-isWant the exam equivalent of those bands instead of raw CEFR?
text_report.py --cambridge maps the report's own bands to the matching
Cambridge Qualification — a B2 reaches line reads B2 First (FCE), the exam a
candidate at that level is working toward.
Scenario. You've decided to use the science text anyway. You want the list of words above your class level to put on a pre-teaching slide.
Input — ask for JSON and pull the upper bands (here with jq):
python3 vocab_profile.py --type cefr --format json --text \
"The researchers analysed the philosophical implications of photosynthesis and chlorophyll synthesis in aquatic ecosystems." \
| jq -r '.results.cefr | to_entries[] | select(.key|test("B2|C1|C2")) | .value.words[].word'Output
implications
philosophical
aquatic
chlorophyll
photosynthesis
What you learn. A ready-made pre-teaching list of exactly the words above B1.
The same JSON also drives the academic-vocabulary view — swap --type cefr for
--type awl to get just Coxhead's Academic Word List hits:
Awl 29% (4)
analysed, implications, philosophical, researchers
Nearly a third of the running words are academic vocabulary — a strong signal the text belongs in an EAP / upper-secondary setting, not a general A2 lesson.
Scenario. A student is aiming for B2. You want to see whether their essay actually uses B2-level structures or just strings A2 sentences together.
Input
python3 grammar_profile.py --file student-essay.docxOutput (pretty)
Grammar Profile
Sentences: 24 Tokens: 410
Typical: A2 Reaches: B2
A1 Present simple ×31 · to-infinitive ×12 · Relative: that ×3
A2 Past simple ×18 · Present perfect ×4 · Passive (past) ×2 · First conditional ×2
B1 Modal: could ×2 · Past perfect ×1
B2 Comparison of equality (as … as) ×1 · Passive (perfect) ×1
What you learn. The writing reaches B2, but only just — one as…as comparison and a single perfect passive carry the whole upper band, while the bulk is A1/A2. Concrete, evidence-based feedback: "to push toward B2, bring in more of the passive, add a relative clause with which, try a second conditional." This is the same rule-based grammar evidence RubricMaker surfaces per-criterion during grading (see RubricMaker Alignment).
Scenario. Two texts look equally "easy" on a quick skim. You profile both ways and discover they are not.
Input
python3 vocab_profile.py --type cefr --text "The house was built by workers who had been trained abroad."
python3 grammar_profile.py --text "The house was built by workers who had been trained abroad."Output
# vocabulary
Typical: A1 90% coverage: A2 ← looks easy
# grammar
Typical: A2 Reaches: B2
A1 Relative: who ×1
A2 Passive (past) ×1
B1 Past perfect ×1
B2 Passive (perfect) ×1
What you learn. The words are almost all A1/A2, so a readability formula would call this "easy" — but the sentence stacks a past passive, a past perfect, a perfect passive, and a relative clause. For a real learner it's a B2 sentence. This is the case that justifies running both profilers: difficulty is lexical and grammatical, and the two can diverge sharply.
Scenario. You're choosing between two coursebook units for the same B1 group and want an objective difficulty comparison rather than a gut feeling.
Input — one command over the whole folder (vocabulary + grammar per text, in a spreadsheet-ready summary):
python3 class_profile.py --file units/ --format csvOutput (units.csv on stdout)
file,total_words,vocab_typical,vocab_reached,grammar_typical,grammar_reaches,estimated_level
unit-3.txt,412,A1,A2,A2,B1,A2
unit-7.txt,398,A1,B1,B1,B2,B1
...
(Add --target-level B1 for each text's %-above-target and fits verdict, or
--targets A2,B1,B2 to see how the set splits across several classes at once.)
What you learn. Unit 7 is a step up on both axes — more upper-band vocabulary and B2 grammar. If your group has just reached B1, Unit 3 is the safer opener and Unit 7 the stretch text for later in the term. The old way — a shell loop diffing JSON summaries per file — still works and remains the way to pull a single number, but the built-in CSV replaces the loop for the comparison itself.
Scenario. You're rewriting an authentic article down to A2 for a graded reader. You want a fast feedback loop: edit, re-profile, repeat until the upper bands are gone.
Input — check where you are, then let the rewrite aid name the swaps:
python3 vocab_profile.py --type cefr --file draft.md # where am I?
python3 text_report.py --file draft.md --target-level A2 --suggest # what do I change?Output — the vocabulary view tints every word by its band and lists the
offenders; --suggest shows a simpler alternative next to each one above the
target (and the --export md handout gains a Simpler alternative column):
Typical: A2 90% coverage: B1
■ A1 61% ■ A2 24% ■ B1 11% ■ Off List 4%
B1 acquire · obtain · beneath ← replace these
Above A2 — words: acquire (B1) → get (A1), obtain (B1) → get (A1),
beneath (B1) → under (A1)
→ suggests a simpler alternative (rewrite aid)
What you learn. Three words keep the text at B1 — and --suggest names the
swap straight from the bundled curated list (acquire → get, obtain → get,
beneath → under). Swap them and re-run; when the B1 band empties, you've hit
your A2 target. This is the CLI echo of the original VocabKitchen's "adjust a
text to a target level" workflow, now with the suggestions surfaced automatically
(see Roadmap).
Scenario. Your B1 scheme of work promises the present perfect, the first and second conditionals, and relative clauses. You want to confirm the unit's texts actually contain those structures — and the vocabulary it expects students to meet.
Input — write the unit's promises as a checklist file, then run it:
cat > unit-checklist.txt <<'EOF'
# Unit 3 — what the scheme of work promises
[vocabulary]
purchase
circumstances
equipment
[grammar]
present perfect
first conditional
second conditional
relative clause: that
EOF
python3 text_report.py --file unit-reading.txt --curriculum unit-checklist.txtOutput (terminal)
Curriculum checklist — 2 of 3 vocabulary, 3 of 4 grammar ✗
✓ purchase (B2) ✗ circumstances
✓ equipment (B1) ✓ Present perfect
✓ First conditional ✓ Second conditional
✗ Relative clause: that
missing: circumstances, Relative clause: that
What you learn. The unit covers most of its promises — but circumstances
never appears and no relative clause shows up, two gaps to fill before teaching.
Grammar items resolve against the grammar profiler's construction names (or
ids), case-insensitively; vocabulary items carry their CEFR band when
recognised. The same checklist threads through class_profile.py --export md
(per-text sections) and --export csv (a folder-level coverage grid — one row
per text, one column per required item), so a folder of candidate texts can be
compared against the unit's requirements in one run; the set-level summary
handout mirrors the same grid in its Curriculum coverage section, the
JSON report carries it as curriculumCoverage (items × rows × cells) for
scripts, and text_report.py --watch re-checks the checklist on every save
while you edit. The checklist file is schema-validated before any
profiling — a typo like [grammer] fails fast with a did you mean
hint, and an empty required section — or a grammar item that doesn't
resolve against the construction list (e.g. second conditinal) — warns
with a suggestion before the run. With class_profile.py --cando --export md, each per-text handout also gains its CEFR Can-Do
descriptors plus an Above the target list — the demands beyond what
the class is expected to do yet — and --cando-diff adds the set-level
Can-Do demands across the set table to the summary handout: which
above-target descriptors the texts share, most-common first, or ordered by
the CEFR ladder with --cando-diff-sort band.
Scenario. The reading you want to assess arrived as a PDF handout or a Word document; you don't want to copy-paste it.
Input
python3 vocab_profile.py --type all --file article.pdf # needs: pip install pypdf
python3 grammar_profile.py --file worksheet.docx
echo "Some pasted prose." | python3 vocab_profile.py --type cefrOutput. Identical reports to the --text examples above — the file's format is
detected from its extension and the prose is extracted first:
| Extension | How it's read |
|---|---|
.txt / other |
UTF-8 text |
.md / .markdown
|
Markdown stripped to prose |
.docx |
paragraph text from the Word XML (stdlib only) |
.pdf |
text layer via pypdf (no OCR for scanned pages) |
What you learn. The same profiling works on the materials you already have, in the formats teachers actually receive them in — no reformatting step.
Scenario. You're in a Claude Code or Cowork session and don't want to remember flags. All four tools ship as plugins, so you can ask in plain language.
Input
/plugin install vocab-profiler@vocabkitchen
/plugin install grammar-profiler@vocabkitchen
/plugin install text-report@vocabkitchen
/plugin install class-profile@vocabkitchen
then simply:
"What CEFR level is this paragraph, and what grammar does it use?" (paste the paragraph)
"Which of the articles in this folder suit my B1 class?" (point at the folder)
Output. Claude runs the plugins and answers conversationally — e.g. "The
vocabulary is typically A2 and reaches B1 (purchase, enormous); grammatically
it reaches B2 on the strength of two passives and a relative clause with
which." — and for the folder: "Three of the eight articles fit B1; two just
miss it with ~15% of words above level — here are the pre-teaching lists." —
with the option to show the full per-band breakdown.
What you learn. The same analysis, with zero CLI syntax — useful for teachers who live in the chat rather than the terminal.
Scenario. You maintain a shared folder of reading texts and want a spreadsheet of every text's level for colleagues.
Input — the built-in batch summary (vocabulary + grammar per text, one row per text, ready for a spreadsheet):
python3 class_profile.py --file readings/ --format csv > levels.csvOutput (levels.csv)
file,total_words,vocab_typical,vocab_reached,grammar_typical,grammar_reaches,estimated_level
climate.txt,312,A1,B1,A2,B2,B1
recipe.txt,208,A1,A2,A2,A2,A2
sports-news.txt,287,A1,B1,A2,B2,B1
For pipelines that need the full JSON, the automatic switch to JSON whenever output is piped still applies to every tool; the loop below is the "manual" version the batch summary replaces:
for f in readings/*.txt; do
level=$(python3 grammar_profile.py --format json --file "$f" | jq -r '.estimatedLevel.reaches')
vocab=$(python3 vocab_profile.py --type cefr --format json --file "$f" \
| jq -r '[.results.cefr | to_entries[] | select(.value.percentage|rtrimstr("%")|tonumber>0) | .key] | last')
printf '%s,%s,%s\n' "$(basename "$f")" "$vocab" "$level"
done > levels.csvWhat you learn. One row per text, vocabulary and grammar side by side — exactly the shape a batch report or a gradebook import expects — now without maintaining the loop. For per-file detail beyond the summary, pipe the single- text tools to JSON as before (see Roadmap).
Scenario. You've collected a folder of candidate readings for next term and need to shortlist the ones a B1 group can handle — and know which are the close calls worth pre-teaching.
Input — rank the set by level and keep only the texts whose estimated level is at or below B1:
python3 class_profile.py --file candidates/ --max-level B1 --target-level B1Output (pretty, in a terminal)
Class Profile — candidates/ (20 texts)
Aggregate vocabulary (20 texts, 6 412 words)
Typical: A1 90% coverage: B2 7% off list
■■■■■■■■□□□□□□□□□□□□□□□□□□□□□□□□□
# file words typical reached grammar est %above fits
1 lakes-and-rivers 214 A1 A1 A2→B1 A2 0% ✓
2 city-birds 187 A1 A2 A2→B1 A2 2% ✓
...
14 synthetic-dyes 302 A1 B2 B1→B2 B2 18% ✗
What you learn. With --max-level B1, only the texts that fit the band are
listed — the "which of these suits B1?" question answered in one command
instead of twenty single-text reports. The close calls are the ones just over the
line: their % above B1 tells you how much pre-teaching they'd need, and
--export md --target-level B1 hands you the per-text word lists to teach them
with (see Roadmap).
Scenario. You've picked a folder of texts for a term of extensive reading — but if reading 1 throws all its hard words at once, students drown. You want the new vocabulary to enter in a spaced pattern across the repeated readings: a controlled number of new words each time, with earlier words coming back for review.
Input — build the schedule across the whole folder, capped at 4 new above-B1 words per reading:
python3 class_profile.py --file candidates/ --target-level B1 \
--interleave --new-words-per-reading 4 --export md --output plan/Output — plan/candidates-interleave-B1.md, one handout for the whole set:
# Vocabulary interleaving — Target B1 (5 readings, 4 new words/reading)
## Reading 1 — lakes-and-rivers.txt
**Introduce (4):** `absorb` (B2); `pollutants` (B2); `sediment` (C1); `tributary` (C1)
## Reading 2 — city-birds.txt
**Introduce (4):** `habitat` (B2) — deferred from reading 1; ...
**Due for review (4):** `absorb` (B2 — last seen reading 1); ...
What you learn. Overflow from a crowded reading is deferred to the next
with room, so every reading introduces at most the budget; words that recur in a
later reading are listed for spaced review, and words absent for two or more
readings are flagged due — the teacher's plan for introducing a folder's
vocabulary at a controlled rate (see Roadmap). The same schedule is
available as --export csv (one row per word: level, introduction reading,
appearances, deferral) and in JSON (interleave in the payload).
Per-reading handouts. With --export md the run also writes one
<set>-interleave-<LEVEL>-reading-<N>.md per reading — that reading's
Introduce / Review / Due words, each with a definition: the in-text sentence
it appears in by default, upgraded to a real dictionary definition when
the cache is primed. A --pre-enrich --interleave run primes the cache for
exactly the words the schedule introduces in one rate-limited pass, so the
handouts are fully enriched offline afterwards.
The single-text report is a versioned JSON contract, not an ad-hoc dump:
python3 -c "import analysis" is the entry point, every payload carries
schemaVersion, and the JSON Schema is checked in at analysis.schema.json
(kept byte-equal to analysis.payload_schema() by the tests and CI, and
printable with --schema on either CLI).
Input — profile one uploaded essay exactly like the CLI does, from code:
import analysis
payload = analysis.analyze(essay_text, target_level="B1", cando=True)
assert payload["schemaVersion"] == analysis.SCHEMA_VERSION # 1.0What you learn. The payload carries the whole report under one roof:
vocabulary bands + counts, grammar (when spaCy is installed), the
above-target words/structures, coverage figure, verdict, readability, the
curriculum pass/fail, and — the shape RubricMaker's grammar linker
consumes — grammarCriteria: one entry per registered construction, each
judged used (pass, with count + up to two example sentences) or
not used (fail). The app can attach a comment per criterion — "Uses the
passive correctly" / "Doesn't use present perfect yet" — without
deriving anything, and because the schema is versioned, a consumer can
check schemaVersion before trusting the shape.
With --comments the same payload also carries the full apply-as-comment
reference implementation — the rubric covers the whole report:
grammarComments (one ready-to-apply rubric comment per construction,
Uses the … — E.g. "…" with the detected span as evidence for each used one,
Doesn't use the … yet for the rest) plus vocabComments (one comment
per above-target word — Above B1: "anticipate" (B2) — used 1×. E.g. "…",
with the curated simpler synonym appended under --suggest), so the linker
can attach them verbatim. Under a target level the grammar half is filtered
to the class level: at/below constructions keep their pass/fail comments
(kind "rubric"), a used above-target construction becomes a
"pre-teach" note (Uses the Modal + perfect (B2) — above the B1 target: pre-teach or rewrite. E.g. "would have passed" Rewrite: swap for a past simple or a present modal ("would have passed" → "passed") — the rewrite
suggestion comes from the curated WordLists/structure-rewrites.csv,
validated by build_wordlists.py --check), and unused above-target
constructions are dropped. The CLI renders them as Rubric comments and
Vocabulary comments sections in the --export md handout, and
class_profile.py --comments puts both sections in every per-text handout
of a folder run — plus a Demand scan table in the set summary (per-text
above-target word and pre-teach structure counts) and a combined
rubric-comment deck per --targets level, so the same construction is
plain rubric at its own level and pre-teach above it.
- Roadmap — where these workflows are heading, phase by phase.
- RubricMaker Alignment — how the profilers feed the RubricMaker grading platform.
Vocabkitchen CLI — vocabulary & grammar profilers for the EFL classroom. CEFR-anchored, rule-based, no AI. · Companion to RubricMaker.