Add /difficulty and /rhyme endpoints, CMU tail migration for rhyme matching

New /difficulty endpoint returns word difficulty scores (0.0-1.0).
New /rhyme endpoint uses CMU phoneme tail matching to find rhyming words,
sorted by frequency. Adds cmu_tail column migration with batch population
at startup, and index for efficient reverse lookups.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-04-02 06:45:29 -07:00
parent 207086da08
commit 25ed087c3e
3 changed files with 245 additions and 0 deletions

View File

@@ -379,6 +379,82 @@ func (d *Dictionary) Etymology(word, lang string) (string, error) {
return text, nil
}
// Difficulty returns the difficulty score for a word in a language.
// Returns -1 if the word is not found.
func (d *Dictionary) Difficulty(word, lang string) (float64, error) {
var diff sql.NullFloat64
err := d.db.QueryRow(
"SELECT difficulty FROM words WHERE word = ? AND lang = ?",
strings.ToLower(word), lang,
).Scan(&diff)
if err == sql.ErrNoRows {
return -1, nil
}
if err != nil {
return 0, fmt.Errorf("dictionary: difficulty: %w", err)
}
if !diff.Valid {
return -1, nil
}
return diff.Float64, nil
}
// Rhymes returns words that rhyme with the given word by matching CMU phoneme tails.
// English only. Returns up to `limit` results sorted by frequency descending.
func (d *Dictionary) Rhymes(word string, limit int) ([]string, error) {
if limit <= 0 {
limit = 10
}
if limit > 50 {
limit = 50
}
// Get the CMU tail for the input word.
var tail sql.NullString
err := d.db.QueryRow(`
SELECT p.cmu_tail
FROM pronunciations p
JOIN words w ON w.id = p.word_id
WHERE w.word = ? AND w.lang = 'en' AND p.format = 'cmu' AND p.cmu_tail IS NOT NULL
LIMIT 1`,
strings.ToLower(word),
).Scan(&tail)
if err == sql.ErrNoRows || !tail.Valid || tail.String == "" {
return []string{}, nil
}
if err != nil {
return nil, fmt.Errorf("dictionary: rhymes: %w", err)
}
// Find other words with the same tail.
rows, err := d.db.Query(`
SELECT DISTINCT w.word
FROM pronunciations p
JOIN words w ON w.id = p.word_id
WHERE p.cmu_tail = ? AND p.format = 'cmu' AND w.lang = 'en' AND w.word != ?
ORDER BY w.frequency DESC
LIMIT ?`,
tail.String, strings.ToLower(word), limit,
)
if err != nil {
return nil, fmt.Errorf("dictionary: rhymes: %w", err)
}
defer rows.Close()
var rhymes []string
for rows.Next() {
var r string
if err := rows.Scan(&r); err != nil {
return nil, fmt.Errorf("dictionary: rhymes scan: %w", err)
}
rhymes = append(rhymes, r)
}
if rhymes == nil {
rhymes = []string{}
}
return rhymes, rows.Err()
}
func (d *Dictionary) DefCount() (map[string]int, error) {
rows, err := d.db.Query(`
SELECT w.lang, COUNT(*)