Files
English/kouyu_english/tool/courses/build_word_pools.py
T
shenleiandClaude Opus 4.8 febfd30f49 feat: A1 短材料复现本单元理解词,并加入黑夜模式
课程内容
- 重写全部 21 个 A1 单元的听读材料,把本单元理解词织进听/读文本,
  单元内理解词复现率从约 20% 提升到约 77%(各单元 56–96%)。
- 修正 U01 房间号与机场大巴同为 thirty 的撞车(改为 17/30/40)。
- 校验器容差按级别读取(A1 为 8%),materials 覆盖率、字数、
  选项子串等校验全部通过;course_content_test 通过。

黑夜模式
- app_theme 拆分明/暗两套调色板,AppColors 随亮度切换;
  main 用 theme/darkTheme/themeMode + builder 镜像已解析亮度;
  主题偏好持久化到快照;进度页新增“外观主题”切换。

文档
- COURSE-PACK-JSON.md 更新 A1 词池覆盖(840/933)与材料复现约定。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-18 11:16:09 +09:00

168 lines
20 KiB
Python

"""按《学习引擎规格》5.4 从 CEFR-J 1.5 建立 A1、A2、B1 等级词池。
分类是离线草稿,等待用户抽样审核。运行:
python3 tool/courses/build_word_pools.py
输出 tool/courses/pools/{a1,a2,b1}-word-pool.csv。
"""
import collections
import csv
import pathlib
ROOT = pathlib.Path(__file__).resolve().parent
LEXICON = ROOT / 'lexicon' / 'cefrj-vocabulary-profile-1.5.csv'
POOLS = ROOT / 'pools'
def S(text):
return set(text.split('|'))
# ---------------------------------------------------------------- A1
A1_SCHOOL = S("classmate|classroom|dictionary|elementary|grade|grammar|homework|math/maths|paragraph|poem|science|sentence|subject|underline|blank|survey|topic|contest|ruler|sum|period|speech|article|review|strict|character")
A1_SCHOOL_ALSO = S("teacher|student|college|history|notebook|pencil|class|lesson|test|study|learn|teach|practice|practice/practise|example|correct|answer|question|spell|repeat|word|read|write|draw|page|paper|pen|school")
A1_OTHER = S("fairy|ghost|prince|princess|king|spy|kite|doll|lily|butterfly|bee|frog|bear|lion|tiger|monkey|rabbit|rat|snake|sheep|pig|cow|horse|mouse|Olympics|cartoon|cop|sailor|smith|war|kill|fight|peace|pray|dig|bat|drum|CD|CD player|DVD|jet|row|apron|bucket|vase|ribbon|bell|mommy/mommie|daddy|hide|kick|self|farm|farmer|field|stone|leaf|grass|gold|flag|judge|vote|role|ideal|poster|stage|brain|bone|cloth|tube|circle|scientist|spot|volleyball|baseball|reporter|merry|pop")
A1_WORK_ONLY = S("business|interview|interviewer|meeting|factory|engineer|technology|file|skill|focus|successful|suggestion|leader|discuss|officer")
A1_WORK_ALSO = S("office|job|work|worker|company|email/e-mail/E-mail|message|phone|call|busy|plan|team|problem|result|information|computer|partner|owner|machine|type|site|section")
A1_TRAVEL = S("palace|tower|opera|surf|arrive|leave|visit|stay|map|ticket|hotel|airport|station|left|right|straight|near|far|foreign|foreigner|city|town|street|way|sea|beach|mountain|island|river|bridge|temple|church|museum|restaurant|cafe/café|toilet|money|dollar|pay|price|credit card|passport|trip|travel|vacation|holiday|sight|seat|camera|photo|bus|taxi|train|subway|plane|airplane/aeroplane|boat|ship|bike|car|drive|ride|wait|excuse|pardon|help|lost|room|key|shower|towel|umbrella|weather|rain|snow|hot|cold|sick|headache|fever|medicine|hospital|zoo|park|festival|culture|language|guest|welcome|o'clock|a.m./A.M./am/AM|p.m./P.M./pm/PM|minute|hour|late|early|open|closed|close|size|order|menu|water|breakfast|lunch|dinner|meal|coffee|tea|juice|food|delicious|expensive|cheap|sale|shop|store|supermarket|bag|gift|present|shopping")
A1_FORMS = S("am|are|is|was|were|'s|'re|'m|did|does|doing|had|has|saw|thanks|living")
# ---------------------------------------------------------------- A2
A2_SCHOOL = S("adjective|blackboard|capital letter|chapter|column|comparative|essay|exam|headteacher|high school|noun|plural|punctuation|schoolchild|schoolwork|semester|singular|superlative|textbook|verb|vocabulary|campus|principal|research|scientific|summary|context|contrast|statement|structure|quiz|education|educational|debate|graduate|junior|learner")
A2_OTHER = S("angel|ancestor|astronaut|atom|bear|bury|cassette|chimpanzee|chirp|clone|clown|crown|curse|dinosaur|flea|gun|invade|killer|kingdom|liberty|military|murder|oak|orphan|pilgrim|queen|ray|robin|royal|slave|soldier|spaceship|terrorist|treasure|weep|dam|armed|fighter|hunter|cricket|rugby|hockey|MP3 player|political|association|committee|chairman|independence|establish|pacific|grace|sake|pro|symphony|composer|championship|medal|Olympic|athlete|bloom|harvest|feather|endangered|drill|script|gender|trap|wisdom|honor/honour|pursue|oppose|represent|progressive|logical|precise|symbol|function|indicate|evidence|affair|nation|citizen|government|atom|max|rap|lyric|romance|sweetheart|lover|clue|brainstorm|violent|horror|headphone|net|pal|penfriend/pen friend|handicapped|dishonest|laziness|flood|earthquake|planet|globe|continent|intelligence|imagination|inspiration|invent|invention|scale|source|range|unit|zone|mark|crime|rob|robbery|pence|cheque")
A2_WORK_ONLY = S("agency|businessman|businesswoman|boss|secretary|bonus|labor/labour|strategy|target|staff|colleague|occupation")
A2_FORMS = S("been|done|given|being")
# A2 里词形或拼写变体,已在别处覆盖:
A2_KEEP_AS_DAILY = S("crime|rob|robbery|thief|steal|flood|earthquake|planet")
# ---------------------------------------------------------------- B1
B1_SCHOOL = S("abstract|academic|academy|adverb|adverbial|analysis|analyze/analyse|antonym|arithmetic|assign|assignment|auxiliary|biochemistry|biography|biology|chemistry|chronological|classify|comma|consonant|curriculum|definite article|diagram|diameter|economics|encyclopedia/encyclopaedia|examiner|experiment|formula|freshman|full stop|geography|geology|graph|historian|idiom|indefinite article|infinitive|intermediate|lab|laboratory|lecture|literary|literature|mathematician|mathematics|modal verb/modal|participle|phrasal verb|physics|possessive|prefix|preposition|preschool|primary school|professor|pronoun|proverb|pupil|question mark|recite|revise|revision|scholar|scholarship|schoolmate|schoolteacher|secondary school|sophomore|suffix|theory|vowel|flunk|examination|institute|narrate|narrative|summarize/summarise|essence|notion|objective|principle|probability|proportion|phenomenon|analysis|systematic|graphics|definition|define|biography|characteristic|characterize/characterise|categorize/categorise|category|chemist")
B1_OTHER = S("aborigine|abundance|abundant|accustom|acknowledge|acknowledgment/acknowledgement|agricultural|agriculture|air force|ape|army|arrow|astronomer|atomic|attain|barrel|battle|beast|beaver|bishop|bless|blessing|bomb|bond|boom|brass|breakthrough|breed|breeding|brotherhood|bull|bullet|bulletin|bureau|bust|calf|capsule|capture|cattle|CD-ROM|cell|chariot|Christian|citizenship|civil|civilization/civilisation|coal|cod|coffin|comet|command|compound|conquer|constitute|constitution|daze|dazzle|debris|deed|defeat|defend|defender|defense/defence|deficiency|democracy|democratic|deprive|derive|devastate|dioxide|diligence|divine|dynasty|ecological|ecology|ecosystem|ecstasy|electron|element|eliminate|emperor|empire|enemy|Englishman|era|eternal|eternity|extinct|extinction|fax|first lady|foundation|fragment|gall|gene|genetic|genetically|genetics|geographical|glint|glory|goddess|gorilla|govern|governor|habitat|harness|hatch|haunt|haven|heavenly|herd|HIV|holy|humanity|illuminate|inscribe|inscription|innovator|invasion|isle|ivory|jade|journal|justice|lawful|league|liberate|liberation|logic|mankind|marine|Mediterranean|merchant|mineral|mischief|mission|modem|molecule|monk|monster|moral|motherland|motto|mourn|mule|murderer|myth|nationalist|nectar|ness|nuclear|nucleus|nutrient|oppress|oppression|orbit|organism|outlaw|owing|oxygen|paralyze/paralyse|parrot|patrol|phantom|philosopher|philosophy|phoenix|piety|poet|poetry|pollutant|priest|primitive|proclaim|procrastination|prohibition|prosperity|prosperous|racial|radiation|ration|rational|reclaim|reef|religion|religious|remembrance|reproduce|republic|retrospect|reunification|reunify|riddle|sacred|satellite|seize|settlement|settler|sex|sheriff|skeleton|slavery|soul|sphere|spiritual|sportsmanship|strategic|subconscious|submarine|sundial|superstition|swarm|sword|syndrome|technological|telecommunications|telegram|terrorism|thunderous|transitional|triumph|trumpet|underage|unify|union|universe|vein|vessel|vice|vice president|victory|wagon|ward|warrior|washbowl|weapon|westward|wither|wizard|wax|flute|choir|gymnastics|handball|ice hockey|goalkeeper|referee|tournament|champion|boxing|arise|assume|authority|capacity|conduct|constantly|construct|consume|consumption|contrary|convey|council|county|decline|dedicate|devotion|dissolve|distribute|distribution|dynasty|emerge|endure|engage|enrich|essence|evident|excess|expose|extent|firmly|fund|generate|grant|guilt|hatred|imitate|immigrate|impair|inevitable|inhabitant|inhale|inject|innermost|insane|intend|intense|internal|invest|involuntarily|lay|leadership|liar|limp|mass|mere|merely|minimize/minimise|minority|mount|mutual|neutral|nonetheless|notorious|numerous|observation|obtain|occupy|opposition|outward|outweigh|overwhelm|partial|peculiar|phrase|policy|politician|politics|possess|possession|precious|prejudice|preservation|presence|president|primarily|prison|prisoner|proceed|prominent|proof|property|prove|provided|psychological|publisher|purify|quake|rage|rank|recall|reject|remainder|representative|resemble|resist|resolve|restore|retain|retail|scope|sensation|sequence|shadowy|sheer|simultaneously|slogan|sorrow|stem|strain|stricken|substantial|sufficient|suicide|superior|supposedly|surgeon|suspect|suspicion|swear|tax|tempt|tendency|tension|terror|thus|tide|trace|tragedy|tragic|trigger|tumble|unheard|unite|uplifted|urge|usage|vain|vast|vessel|viewpoint|virtual|visible|vision|visually|vividly|warrior|willingness|wound|zone|abandon|absorb|accomplish|accuse|acquire|admiration|advance|affection|aid|allowance|amount|annually|anti|anyhow|appeal|appoint|approach|associate|beloved|beneath|bet|bold|boldly|bound|burden|capable|cherish|chuckle|circular|clip|collapse|commit|complement|composition|concept|conclude|conclusion|confirmation|conscious|consequently|conservation|conservative|considerable|consideration|continual|continually|contribute|contribution|controversial|copyright|craft|creator|crisis|critic|critical|crop|crow|crush|cultivate|curve|dare|deadly|debt|declaration|declare|decrease|delicate|demand|demonstrate|demonstration|deny|dependent|depth|despair|destructive|detective|determination|determine|devastating|differ|disability|disable|discrimination|distinction|distinctly|distinguish|drag|dramatic|drown|dude|dull|eager|eagerness|earnest|efficiency|emphasis|emphasize/emphasize|enable|enclose|encounter|endanger|endless|enthusiast|entire|environmentalist|equality|establishment|estimate|evenly|exclusive|existence|expand|exploration|explosion|extensively|fade|faithful|fantasy|farthest|fellow|fetch|feverishly|firm|flock|fond|fondness|formally|format|former|forth|fright|frustration|furthermore|furthest|gee|generate|gifted|glide|glimpse|glorious|grave|guidance|hasty|hearted|hearty|highlight|hop|horrify|imaginary|improper|incident|increasingly|indirect|indirectly|individual|industrial|industry|initial|initially|insight|inspection|inspire|instance|intensive|intention|intentionally|interact|interaction|internationally|irregular|irritate|isolation|killing|largely|lasting|launch|lean|leap|lessen|likeness|limitation|literary|loyal|loyalty|lovingly|magnificent|maintain|majority|marked|massive|maximum|meaningful|meanwhile|mechanical|mental|mentally|minimum|minor|miracle|misery|mislead|moderate|modify|moisture|motionless|motive|movement|nevertheless|newly|nor|noticeable|numerous|object|objection|obstacle|occasion|occur|offensive|oneself|opening|organic|origin|outer|outline|outstanding|overcome|overjoyed|overwhelming|pace|parental|participant|pattern|penniless|permanent|permanently|persuasive|pit|pole|positively|potential|pottery|poverty|praise|prayer|preference|preparation|previous|proceed|process|productive|profession|progress|promote|promotion|properly|proposal|propose|protest|publicly|punish|punishment|pure|quantity|rapid|rapidly|react|realistic|reality|rearrange|recognize/recognise|regain|regard|regarding|region|regional|regulation|relate|relation|relatively|relative|reliable|rely|remark|remarkable|remedy|reputation|require|requirement|reserve|residential|resource|respectable|respond|responsibility|restrict|retell|reward|rewrite|rid|rolling|rough|runaway|safeguard|scatter|scold|seawater|secure|selection|separation|series|session|setting|settle|severe|severely|shameful|shift|shortage|shortly|sigh|signal|significance|similarity|similarly|simplify|slight|slightly|socially|solid|somehow|someplace|sometime|sort|sparkle|specialist|specialize/specialise|specially|spiral|spirit|spoil|sponsor|stable|standard|status|steadily|steady|steam|steel|storyteller|strangely|stream|strengthen|stretch|strip|struggle|stumble|stun|subconscious|substitute|suffer|supply|supportive|suppose|surely|surface|surround|survival|survivor|swell|sympathy|systematic|tease|technique|teen|temper|tend|tense|term|terribly|terrific|thorough|threat|threatening|throughout|thump|tick|tighten|timely|toss|totally|transfer|transform|transformation|tremble|tremendous|trend|twist|typically|uncontrollable|uncover|undergo|underneath|undo|unfold|unrelated|upward|valuable|variety|various|vary|vague|violence|violently|visual|vivid|volume|warmth|wasteful|weakness|whichever|whoever|widespread|wildly|worthy|yell|arrange|arrangement|attract|attraction|beat|capable")
# 上面 B1_OTHER 里混入了生活与旅行中仍然有用的词,这里拉回词池。
B1_KEEP = S("amount|arrange|arrangement|attract|attraction|beat|affection|aid|approach|demand|deny|detective|dramatic|drown|eager|entire|estimate|expand|explosion|fade|fetch|firm|fond|former|guidance|incident|increasingly|individual|initial|insight|inspire|instance|intention|launch|lean|loyal|magnificent|maintain|majority|maximum|meanwhile|mental|minimum|minor|miracle|moderate|occasion|occur|opening|organic|origin|outline|outstanding|overcome|pace|pattern|permanent|permanently|praise|preference|preparation|previous|process|progress|promote|proposal|propose|protest|properly|quantity|rapid|rapidly|react|realistic|reality|recognize/recognise|region|regional|regulation|relate|relation|relative|relatively|reliable|rely|remark|remarkable|remedy|reputation|require|requirement|reserve|resource|respond|responsibility|restrict|reward|rough|safeguard|secure|selection|series|session|setting|settle|severe|severely|shortage|shortly|sigh|signal|similarly|slight|slightly|solid|somehow|sometime|sort|specially|spoil|stable|standard|steady|steam|strangely|stretch|struggle|suffer|supply|suppose|surely|surface|surround|survival|sympathy|technique|tend|terribly|terrific|thorough|threat|throughout|totally|transfer|tremendous|trend|typically|valuable|variety|various|vary|vague|violence|volume|warmth|weakness|yell|tax|prison|property|president|politics|politician|policy|precious|proof|prove|suspect|wound|visible|vast|recall|reject|resolve|restore|visual|vivid|capable|nor|object|objection|obstacle|pure|punish|punishment|relatively|scold|shift|status|stream|substitute|swell|tease|twist|virtual|viewpoint|tragedy|tide|thus|trace|urge|intend|internal|invest|mass|merely|numerous|obtain|possess|possession|presence|proceed|reliable|retail|former|sometime|beneath|bet|bold|burden|crop|critical|debt|decrease|declare|delicate|depth|despair|differ|disability|dull|endless|entire|existence|fantasy|format|furthermore|highlight|imaginary|incident|indirect|industry|interaction|killing|lasting|likeness|loyalty|meaningful|minimum|moderate|motive|movement|nevertheless|newly|occasion|outer|overjoyed|potential|pottery|poverty|prayer|productive|profession|promotion|publicly|regard|regarding|relation|reliable|remainder|respectable|rolling|runaway|seawater|separation|similarity|simplify|socially|specialist|spirit|sponsor|steel|storyteller|strengthen|strip|survivor|temper|term|tighten|toss|transform|tremble|uncover|undo|unfold|upward|wildly|worthy|equality|creator|craft|clip|circular|chuckle|conclusion|conscious|consequently|considerable|contribution|controversial|critic|curve|deadly|demonstration|dependent|destructive|determination|determine|devastating|distinguish|drag|emphasis|enable|encounter|establishment|excess|exploration|fellow|gifted|glimpse|grave|hasty|hop|intensive|isolation|leap|mechanical|mentally|misery|modify|moisture|noticeable|offensive|parental|participant|pole|positively|punish|rearrange|regain|remark|residential|retell|rewrite|rid|scatter|shameful|significance|spiral|steadily|stun|stumble|supportive|terrified|threatening|tick|timely|uncontrollable|unrelated|violently|whoever|whichever|widespread")
B1_WORK_ONLY = S("accountant|administration|agenda|CV|employment|interviewee|management|overwork|workplace|workshop|teamwork|qualified|qualify|finance|financial|investment|bureau|unemployed|unemployment|applicant|deadline|colleague")
B1_FORMS = S("paid|worn|written|fallen|able|according to")
FUNCTION_POS = {
'pronoun', 'determiner', 'preposition', 'conjunction', 'modal auxiliary',
'be-verb', 'do-verb', 'have-verb', 'interjection', 'article',
'infinitive-to', 'number',
}
def load():
rows = list(csv.reader(LEXICON.open(encoding='utf-8-sig')))[1:]
by_level = collections.defaultdict(collections.OrderedDict)
lowest = {}
order = {'A1': 1, 'A2': 2, 'B1': 3, 'B2': 4}
for headword, pos, level, *_rest in rows:
topic = _rest[2] if len(_rest) > 2 else ''
entry = by_level[level].setdefault(headword, {'pos': set(), 'topic': set()})
entry['pos'].add(pos)
if topic:
entry['topic'].add(topic)
if headword not in lowest or order[level] < order[lowest[headword]]:
lowest[headword] = level
return by_level, lowest
def a1_categories(word, entry):
cats = set()
if word in A1_SCHOOL or word in A1_SCHOOL_ALSO:
cats.add('学校与学术')
if word in {'teacher', 'student', 'college', 'history', 'notebook', 'pencil'}:
cats.add('日常')
if word in A1_OTHER:
cats.add('其他')
if word in A1_WORK_ONLY or word in A1_WORK_ALSO:
cats.add('工作')
if word in A1_TRAVEL or 'Travel' in entry['topic']:
cats.add('旅行')
topics = entry['topic']
if topics and not topics <= {'Education'} and not (
word in A1_SCHOOL or word in A1_OTHER or word in A1_WORK_ONLY
):
cats.add('日常')
if word in A1_SCHOOL_ALSO:
cats.add('通用')
if not cats or (cats <= {'工作'} and word in A1_WORK_ALSO):
cats.add('通用')
return cats
def generic_categories(word, entry, school, other, work_only, keep):
if word in keep:
pass
elif word in school:
return {'学校与学术'}
elif word in other:
return {'其他'}
if word in work_only:
return {'工作'}
cats = set()
if 'Travel' in entry['topic']:
cats.add('旅行')
if entry['topic'] - {'Education', 'Travel'}:
cats.add('日常')
if not cats:
cats.add('通用')
return cats
def write_pool(level, entries, categorize, forms):
path = POOLS / f'{level.lower()}-word-pool.csv'
counts = collections.Counter()
with path.open('w', newline='', encoding='utf-8') as file:
writer = csv.writer(file)
writer.writerow([
'headword', 'pos', 'cefrj_topic', 'categories', 'status',
'word_form_variant', 'function_word',
])
for word, entry in entries.items():
cats = categorize(word, entry)
if cats & {'通用', '日常', '旅行'}:
status = 'pool'
elif cats == {'工作'}:
status = 'work_only'
else:
status = 'excluded'
variant = word in forms
function = entry['pos'] <= FUNCTION_POS
if status == 'pool' and not variant:
counts['pool'] += 1
if function:
counts['function'] += 1
elif status != 'pool':
counts[status] += 1
writer.writerow([
word, '/'.join(sorted(entry['pos'])),
'; '.join(sorted(entry['topic'])), '/'.join(sorted(cats)),
status, 'yes' if variant else '', 'yes' if function else '',
])
print(level, dict(counts), '->', path.relative_to(ROOT.parent.parent))
def main():
by_level, lowest = load()
POOLS.mkdir(exist_ok=True)
write_pool('A1', by_level['A1'], a1_categories, A1_FORMS)
for level, school, other, work, keep, forms in [
('A2', A2_SCHOOL, A2_OTHER, A2_WORK_ONLY, A2_KEEP_AS_DAILY, A2_FORMS),
('B1', B1_SCHOOL, B1_OTHER, B1_WORK_ONLY, B1_KEEP, B1_FORMS),
]:
# 已在更低等级出现过的词头(换词性再出现)归入更低等级的词池。
entries = collections.OrderedDict(
(word, entry) for word, entry in by_level[level].items()
if lowest[word] == level
)
write_pool(
level, entries,
lambda w, e, s=school, o=other, k=work, p=keep:
generic_categories(w, e, s, o, k, p),
forms,
)
if __name__ == '__main__':
main()