Add Phase 2+3 features: antonyms, backing, pronunciation, etymology, difficulty, affix expansion

New endpoints: /antonyms, /backing, /pronunciation, /etymology with difficulty
scoring on /random. Cross-language synset backing links French/Portuguese words
to English equivalents via WordNet 3.0 synset IDs (matching WOLF and OMW offsets).

New loaders: Hunspell affix expansion (fr, pt-PT), English programmatic inflector,
CMU Pronouncing Dictionary, SUBTLEX-US frequency, CETEMPúblico frequency,
Open Multilingual Wordnet (Portuguese), and SQL-based difficulty scoring.

Schema v2 adds tables: antonyms, synsets, word_synsets, pronunciations, etymology
with migration support for existing databases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-04-02 01:01:06 -07:00
parent d5e4ce0d36
commit 207086da08
22 changed files with 3102 additions and 120 deletions

View File

@@ -89,30 +89,30 @@ func TestRandomWord(t *testing.T) {
d := NewFromDB(db)
// Basic random word
word, err := d.RandomWord("en", Options{})
result, err := d.RandomWord("en", Options{})
if err != nil {
t.Fatalf("RandomWord: %v", err)
}
if word == "" {
if result.Word == "" {
t.Error("RandomWord returned empty string")
}
// With POS filter
word, err = d.RandomWord("en", Options{POS: "adjective"})
result, err = d.RandomWord("en", Options{POS: "adjective"})
if err != nil {
t.Fatalf("RandomWord with POS: %v", err)
}
if word != "happy" {
t.Errorf("RandomWord(adjective) = %q, want happy", word)
if result.Word != "happy" {
t.Errorf("RandomWord(adjective) = %q, want happy", result.Word)
}
// With length filters
word, err = d.RandomWord("en", Options{MinLength: 4, MaxLength: 5})
result, err = d.RandomWord("en", Options{MinLength: 4, MaxLength: 5})
if err != nil {
t.Fatalf("RandomWord with length: %v", err)
}
if len(word) < 4 || len(word) > 5 {
t.Errorf("RandomWord length %d not in [4,5]", len(word))
if len(result.Word) < 4 || len(result.Word) > 5 {
t.Errorf("RandomWord length %d not in [4,5]", len(result.Word))
}
// No match