2026-06-15 21:38:22 -05:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""
|
|
|
|
|
Generate QuestionSeed.kt from the v2 JSON question file.
|
|
|
|
|
|
|
|
|
|
Usage:
|
|
|
|
|
python seed_generator.py /path/to/questions.json > /path/to/QuestionSeed.kt
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import sys
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_options(options_list):
|
|
|
|
|
"""Parse options array for single_choice, multi_choice, this_or_that."""
|
|
|
|
|
if not options_list:
|
|
|
|
|
return ""
|
|
|
|
|
opts = []
|
|
|
|
|
for opt in options_list:
|
|
|
|
|
opt_id = opt.get("id", "")
|
|
|
|
|
opt_text = opt.get("text", "")
|
|
|
|
|
opts.append(f' ChoiceOption(id = "{opt_id}", text = "{opt_text}")')
|
|
|
|
|
return ",\n".join(opts)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_answer_config(question):
|
|
|
|
|
"""Generate AnswerConfig code for a question."""
|
|
|
|
|
answer_config = question.get("answer_config", {})
|
|
|
|
|
qtype = question.get("type", "written")
|
|
|
|
|
|
|
|
|
|
if qtype == "written":
|
|
|
|
|
min_len = answer_config.get("min_length", 1)
|
|
|
|
|
max_len = answer_config.get("max_length", 1000)
|
|
|
|
|
placeholder = answer_config.get("placeholder", "Write your answer...")
|
|
|
|
|
return f"WrittenAnswerConfig(\n minLength = {min_len},\n maxLength = {max_len},\n placeholder = \"{placeholder}\"\n )"
|
|
|
|
|
|
|
|
|
|
elif qtype == "single_choice":
|
|
|
|
|
options = question.get("options", [])
|
|
|
|
|
opts = parse_options(options)
|
|
|
|
|
return f"ChoiceAnswerConfig(\n options = listOf(\n{opts}\n )\n )"
|
|
|
|
|
|
|
|
|
|
elif qtype == "scale":
|
|
|
|
|
min_scale = answer_config.get("min_scale", 1)
|
|
|
|
|
max_scale = answer_config.get("max_scale", 5)
|
|
|
|
|
min_label = answer_config.get("min_label", "")
|
|
|
|
|
max_label = answer_config.get("max_label", "")
|
|
|
|
|
step = answer_config.get("scale_step", 1)
|
|
|
|
|
return f"ScaleAnswerConfig(\n minScale = {min_scale},\n maxScale = {max_scale},\n minLabel = \"{min_label}\",\n maxLabel = \"{max_label}\",\n scaleStep = {step}\n )"
|
|
|
|
|
|
|
|
|
|
elif qtype == "this_or_that":
|
|
|
|
|
options = question.get("options", [])
|
|
|
|
|
if len(options) >= 2:
|
|
|
|
|
opt_a = options[0]
|
|
|
|
|
opt_b = options[1]
|
|
|
|
|
return f"ThisOrThatAnswerConfig(\n optionA = ChoiceOption(id = \"{opt_a.get('id', '')}\", text = \"{opt_a.get('text', '')}\"),\n optionB = ChoiceOption(id = \"{opt_b.get('id', '')}\", text = \"{opt_b.get('text', '')}\")\n )"
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
elif qtype == "multi_choice":
|
|
|
|
|
options = question.get("options", [])
|
|
|
|
|
opts = parse_options(options)
|
|
|
|
|
return f"ChoiceAnswerConfig(\n options = listOf(\n{opts}\n )\n )"
|
|
|
|
|
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_question_code(question):
|
|
|
|
|
"""Generate a Question constructor call for a single question."""
|
|
|
|
|
qid = question.get("id", "")
|
|
|
|
|
text = question.get("text", "").replace('"', '\\"')
|
|
|
|
|
category = question.get("category_id", "")
|
|
|
|
|
depth = question.get("depth", 1)
|
|
|
|
|
access = question.get("access", "free")
|
|
|
|
|
is_premium = access == "premium"
|
|
|
|
|
qtype = question.get("type", "written")
|
|
|
|
|
tags = question.get("tags", [])
|
|
|
|
|
|
|
|
|
|
tags_str = ", ".join(f'"{t}"' for t in tags) if tags else ""
|
|
|
|
|
answer_config_code = parse_answer_config(question)
|
|
|
|
|
|
|
|
|
|
lines = []
|
|
|
|
|
lines.append(f'Question(')
|
|
|
|
|
lines.append(f' id = "{qid}",')
|
|
|
|
|
lines.append(f' text = "{text}",')
|
|
|
|
|
lines.append(f' category = "{category}",')
|
|
|
|
|
lines.append(f' depthLevel = {depth},')
|
|
|
|
|
lines.append(f' isPremium = {str(is_premium).lower()},')
|
|
|
|
|
lines.append(f' type = "{qtype}",')
|
|
|
|
|
lines.append(f' tags = listOf({tags_str}),')
|
|
|
|
|
|
|
|
|
|
if answer_config_code:
|
|
|
|
|
if qtype == "written":
|
|
|
|
|
lines.append(f' answerConfig = WrittenAnswerConfigImpl(config = {answer_config_code})')
|
|
|
|
|
elif qtype == "single_choice":
|
|
|
|
|
lines.append(f' answerConfig = ChoiceAnswerConfigImpl(config = {answer_config_code})')
|
|
|
|
|
elif qtype == "scale":
|
|
|
|
|
lines.append(f' answerConfig = ScaleAnswerConfigImpl(config = {answer_config_code})')
|
|
|
|
|
elif qtype == "this_or_that":
|
|
|
|
|
lines.append(f' answerConfig = ThisOrThatAnswerConfigImpl(config = {answer_config_code})')
|
|
|
|
|
elif qtype == "multi_choice":
|
|
|
|
|
lines.append(f' answerConfig = ChoiceAnswerConfigImpl(type = "multi_choice", config = {answer_config_code})')
|
|
|
|
|
|
|
|
|
|
lines.append(')')
|
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_seed_file(json_data):
|
|
|
|
|
"""Generate the complete QuestionSeed.kt file content."""
|
|
|
|
|
category = json_data.get("category", {})
|
|
|
|
|
questions = json_data.get("questions", [])
|
|
|
|
|
|
|
|
|
|
# Category info for comments
|
|
|
|
|
cat_id = category.get("id", "")
|
|
|
|
|
cat_name = category.get("display_name", "")
|
|
|
|
|
total = category.get("total_questions", 0)
|
|
|
|
|
free_q = category.get("free_questions", 0)
|
|
|
|
|
premium_q = category.get("premium_questions", 0)
|
|
|
|
|
|
|
|
|
|
# Generate question list
|
|
|
|
|
question_code_list = []
|
|
|
|
|
for q in questions:
|
|
|
|
|
question_code_list.append(generate_question_code(q))
|
|
|
|
|
|
|
|
|
|
question_list = ",\n\n".join(question_code_list)
|
|
|
|
|
|
2026-06-16 20:03:58 -05:00
|
|
|
kt_content = """package app.closer.data.questions
|
2026-06-15 21:38:22 -05:00
|
|
|
|
2026-06-16 20:03:58 -05:00
|
|
|
import app.closer.domain.model.*
|
2026-06-15 21:38:22 -05:00
|
|
|
|
|
|
|
|
object QuestionSeed {
|
|
|
|
|
val questions: List<Question> = listOf(
|
|
|
|
|
"""
|
|
|
|
|
kt_content += question_list
|
|
|
|
|
kt_content += """
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// ── Helpers ─────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
fun getDailyQuestion(): Question = questions.filter { it.category in mvpCategoryIds }.random()
|
|
|
|
|
|
|
|
|
|
fun getQuestionsByCategory(categoryId: String): List<Question> = questions.filter { it.category == categoryId }
|
|
|
|
|
|
|
|
|
|
fun getPremiumQuestions(): List<Question> = questions.filter { it.isPremium }
|
|
|
|
|
|
|
|
|
|
fun getFreeQuestions(): List<Question> = questions.filter { !it.isPremium }
|
|
|
|
|
|
|
|
|
|
fun getMvpQuestions(): List<Question> = questions.filter { it.category in mvpCategoryIds }
|
|
|
|
|
|
|
|
|
|
fun getRandomQuestionsByCategory(categoryId: String, count: Int): List<Question> =
|
|
|
|
|
questions.filter { it.category == categoryId }.shuffled().take(count)
|
|
|
|
|
|
|
|
|
|
fun getQuestionById(id: String): Question? = questions.find { it.id == id }
|
|
|
|
|
|
|
|
|
|
/** MVP categories available in version 0.1 */
|
|
|
|
|
private val mvpCategoryIds = setOf(
|
|
|
|
|
"communication", "fun", "gratitude", "stress",
|
|
|
|
|
"trust", "conflict", "future", "emotional_intimacy", "date_night", "values"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
return kt_content
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
if len(sys.argv) < 2:
|
|
|
|
|
print("Usage: python seed_generator.py /path/to/questions.json", file=sys.stderr)
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
json_path = sys.argv[1]
|
|
|
|
|
|
|
|
|
|
if not Path(json_path).exists():
|
|
|
|
|
print(f"Error: File not found: {json_path}", file=sys.stderr)
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
with open(json_path, 'r', encoding='utf-8') as f:
|
|
|
|
|
json_data = json.load(f)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error reading JSON: {e}", file=sys.stderr)
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
kt_content = generate_seed_file(json_data)
|
|
|
|
|
print(kt_content)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|