[Bug] WritingQuestion 生成で日英文数不一致時に無効な sentenceIndex が作成される

Resolved 💬 2 comments Opened Jan 25, 2026 by yoshizaki91 Closed Feb 12, 2026

概要

WritingQuestion の自動生成ロジックに2つの異なるコードパスがあり、一方のパスで Math.min() が適用されていないため、日本語と英語の文数が異なる場合に無効な sentenceIndex を持つ問題が作成される。

再現手順

  1. 会話練習で、日本語と英語の文数が異なる評価結果を得る
  • 例: 日本語「絢美も大丈夫みたい。結構寝てくれたよ。」(2文)
  • 例: 英語「Ayami seems fine too — she slept pretty well.」(1文 - em-dash は分割されない)
  1. Writing ページにアクセス
  2. 「結構寝てくれたよ。」の問題に回答
  3. 「理想的な表現」が表示されない

原因

2つの問題生成パス

| パス | ファイル | Math.min | step_id |
|------|----------|----------|---------|
| Background Job | process-jobs.ts:generateWritingQuestions | ✅ 使用 | NULL |
| Use Case | writing-use-cases.ts:generateQuestionsFromFeedbacks | ❌ 未使用 | 実際のID |

Background Job パス(正しい実装)

// process-jobs.ts:256
const pairCount = Math.min(japaneseSentences.length, englishSentences.length);
for (let i = 0; i < pairCount; i++) {
  questions.push({ sentenceIndex: i, ... });
}

Use Case パス(バグのある実装)

// writing-use-cases.ts:240
const userSentences = splitBySentence(feedback.idealExpression);
for (let sentenceIndex = 0; sentenceIndex < userSentences.length; sentenceIndex++) {
  // ❌ 英語の文数をチェックしていない
  questions.push({ sentenceIndex, ... });
}

データベースの証拠

同じ source_feedback_id=15 に対して:

id | step_id | speaker | sentence_index | created_at
---|---------|---------|----------------|------------------
67 | NULL    | user    | 0              | 04:58:52 (Background Job)
80 | 32      | user    | 0              | 04:58:54 (Use Case)
81 | 32      | user    | 1              | 04:58:54 (Use Case) ← 無効!
  • id=67: Background Job で正しく 1 問だけ作成
  • id=80, 81: Use Case で誤って 2 問作成(sentence_index=1 は英語が存在しない)

関連する設計上の問題

1. 同じテキストを4回分割している

MessageFeedback
└── idealEnglish: "Ayami seems fine too — she slept pretty well."
    ├─[生成時] splitByEnglishSentence() → pairCount = min(2,1) = 1
    ├─[表示時] splitByEnglishSentence() → 再分割
    ├─[評価時] splitByEnglishSentence() → 再分割
    └─[シャドー生成時] splitByEnglishSentence() → 再分割

2. speaker ベースのフィールド選択が4箇所に重複

  • process-jobs.ts
  • writing-use-cases.ts
  • queries.ts
  • shadowing-use-cases.ts

3. ユニーク制約がない

SELECT conname, contype FROM pg_constraint WHERE conrelid = 'writing_questions'::regclass;
-- (source_feedback_id, speaker, sentence_index) に対するユニーク制約がない

提案する修正

短期修正(このバグの修正)

writing-use-cases.ts:generateQuestionsFromFeedbacksMath.min() を追加:

// User 問題
if (feedback.idealExpression && feedback.idealEnglish) {
  const userSentences = splitBySentence(feedback.idealExpression);
  const englishSentences = splitByEnglishSentence(feedback.idealEnglish);
  const pairCount = Math.min(userSentences.length, englishSentences.length);
  
  for (let sentenceIndex = 0; sentenceIndex < pairCount; sentenceIndex++) {
    // ...
  }
}

// Assistant 問題も同様に修正

長期修正(アーキテクチャ改善)

  1. WritingQuestion に englishSentence カラムを追加
  • 生成時に英語テキストを保存
  • 表示/評価/シャドー生成時の再分割を不要に
  1. ヘルパー関数の抽出
  • getIdealEnglishForSpeaker(feedback, speaker) を共通化
  1. ユニーク制約の追加
  • (source_feedback_id, speaker, sentence_index) に対してユニーク制約

影響範囲

  • Writing ページ: 理想的な表現が表示されない問題
  • Shadowing ページ: 対応する英語がない問題が生成される可能性
  • 評価精度: sentenceIndex が無効な場合、間違った英語テキストで評価される

関連コミット

  • a9ebbbb1: WritingQuestion 自動生成を実装(R-4)
  • 8298c937: assistant 問題も生成(この時点で Use Case パスにバグ)
  • b84d5503: splitByEnglishSentence アポストロフィ修正(別問題)
  • 4a503842: Shadowing sequence 順序修正(別問題)

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗