Closer/seed_generator.py

188 lines
6.9 KiB
Python
Raw Permalink Normal View History

#!/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)
kt_content = """package app.closer.data.questions
import app.closer.domain.model.*
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()