グカネータ作成 / 質問パターン修正 (#41) (#364)

Reviewed-on: #364
Co-authored-by: miteruzo <miteruzo@naver.com>
Co-committed-by: miteruzo <miteruzo@naver.com>
このコミットはPull リクエスト #364 でマージされました.
このコミットが含まれているのは:
2026-06-11 23:21:44 +09:00
committed by みてるぞ
コミット c361c561c2
3個のファイルの変更204行の追加22行の削除
+90 -4
ファイルの表示
@@ -10,11 +10,13 @@ import { buildGekanatorQuestions,
fetchGekanatorExtraQuestions,
fetchGekanatorQuestions,
fetchGekanatorPosts,
normalizeTitleLengthCondition,
restoreGekanatorQuestion,
saveGekanatorExtraQuestionAnswers,
saveGekanatorGame,
saveGekanatorQuestionSuggestion,
storeGekanatorQuestion } from '@/lib/gekanator'
storeGekanatorQuestion,
titleLengthMinimumForCondition } from '@/lib/gekanator'
import { gekanatorKeys } from '@/lib/queryKeys'
import { cn } from '@/lib/utils'
@@ -23,6 +25,7 @@ import type { FC } from 'react'
import type { GekanatorAnswerLog,
GekanatorAnswerValue,
GekanatorExtraQuestion,
GekanatorQuestionCondition,
GekanatorQuestion,
StoredGekanatorQuestion } from '@/lib/gekanator'
import type { Post } from '@/types'
@@ -144,6 +147,46 @@ const createGameSeed = (): string => {
}
const normalizeStoredQuestionId = (
questionId: string,
condition?: GekanatorQuestionCondition,
): string => {
if (condition?.type === 'title-length-greater-than')
return `title:length-at-least:${ condition.length + 1 }`
if (questionId.startsWith ('title:length-greater-than:'))
{
const length = Number (questionId.split (':').pop ())
if (Number.isInteger (length))
return `title:length-at-least:${ length + 1 }`
}
return questionId
}
const normalizeStoredGame = (game: StoredGekanatorGame): StoredGekanatorGame => ({
...game,
answers: game.answers.map (answer => ({
...answer,
questionId: normalizeStoredQuestionId (
answer.questionId,
answer.questionCondition),
questionCondition: answer.questionCondition
? normalizeTitleLengthCondition (answer.questionCondition)
: undefined })),
askedIds: game.askedIds.map (questionId => normalizeStoredQuestionId (questionId)),
softenedQuestionIds: game.softenedQuestionIds.map (questionId =>
normalizeStoredQuestionId (questionId)),
askedQuestionBank: game.askedQuestionBank?.map (question =>
({
...question,
id: normalizeStoredQuestionId (question.id, question.condition),
condition: normalizeTitleLengthCondition (question.condition) })),
askedQuestionBankIds: game.askedQuestionBankIds?.map (questionId =>
normalizeStoredQuestionId (questionId)) })
const sourcePriorityForMerge = (question: GekanatorQuestion): number => {
switch (question.source)
{
@@ -214,7 +257,7 @@ const loadStoredGame = (): StoredGekanatorGame | null => {
if (!(raw))
return null
return JSON.parse (raw) as StoredGekanatorGame
return normalizeStoredGame (JSON.parse (raw) as StoredGekanatorGame)
}
catch
{
@@ -548,6 +591,13 @@ const sameConditionValue = (
left: GekanatorQuestion['condition'],
right: GekanatorQuestion['condition'],
): boolean => {
const leftTitleLength = titleLengthMinimumForCondition (left)
const rightTitleLength = titleLengthMinimumForCondition (right)
if (leftTitleLength !== null || rightTitleLength !== null)
return leftTitleLength !== null
&& rightTitleLength !== null
&& leftTitleLength === rightTitleLength
if (left.type !== right.type)
return false
@@ -564,12 +614,13 @@ const sameConditionValue = (
return String (condition.month)
case 'original-month-day':
return condition.monthDay
case 'title-length-greater-than':
return String (condition.length)
case 'title-has-ascii':
return ''
case 'post-similarity':
return `${ condition.postId }:${ condition.answer }:${ condition.threshold }`
case 'title-length-at-least':
case 'title-length-greater-than':
return String (titleLengthMinimumForCondition (condition) ?? '')
}
}
@@ -591,6 +642,38 @@ const monthForCondition = (
}
const isTitleLengthContradiction = (
candidate: GekanatorQuestion['condition'],
previous: GekanatorQuestion['condition'],
answer: GekanatorAnswerValue,
): boolean => {
const candidateLength = titleLengthMinimumForCondition (candidate)
const previousLength = titleLengthMinimumForCondition (previous)
if (candidateLength === null || previousLength === null)
return false
switch (answer)
{
case 'yes':
return candidateLength <= previousLength
case 'no':
return candidateLength >= previousLength
default:
return false
}
}
const isQuestionRedundantAfterAnswers = (
question: GekanatorQuestion,
answers: GekanatorAnswerLog[],
): boolean => answers.some (answer => {
const previous = answer.questionCondition
return previous !== undefined
&& isTitleLengthContradiction (question.condition, previous, answer.answer)
})
const isMonthCrossMatch = (
candidate: GekanatorQuestion['condition'],
previous: GekanatorQuestion['condition'],
@@ -725,6 +808,9 @@ const chooseQuestion = ({
return questionsToRank
.map (question => {
if (isQuestionRedundantAfterAnswers (question, answers))
return null
const signature = signatureFor (question, candidates)
if (redundant.has (signature))
return null