9.9 KiB
Closer Question Schema v8
See also: QUESTION_CONTENT_GUIDE.md | QUESTION_REWRITE_PLAN.md | QUESTION_QUALITY_CHECKLIST.md
Purpose
This document defines the production JSON contract used by the active Closer importer and Room database.
The active importer is authoritative. Planning labels and old authoring formats must not override the production contract.
Production File Shape
Every production question pack must use this top-level shape:
{
"category": {
"id": "quality_time",
"display_name": "Quality Time",
"description": "Questions about being present and enjoying meaningful time together.",
"access": "mixed",
"icon_name": "schedule",
"schema_version": "question_v2",
"metadata": {}
},
"questions": []
}
Required top-level keys:
category
questions
Required category fields:
id
display_name
description
access
icon_name
Optional category fields include:
schema_version
metadata
Never rely on the filename to manufacture a missing category.
The old simple-pack form is not production-valid:
{
"id": "rebuilding_trust",
"title": "Rebuilding Trust",
"count": 25,
"questions": []
}
Required Question Fields
Every production question requires:
id
category_id
type
text
depth
access
tags
Rules:
category_idmust exactly match the enclosingcategory.idtypemust be one of the supported type namestextmust be non-empty and catalog-uniquedepthmust be the integer1,2, or3accessmust befreeorpremiumtagsmust be an array; use at least one meaningful tag unless a documented exception existssexis optional and nullable for ordinary packs- require
sexonly when a documented feature, such as feature-specific targeting, actually reads it
Depth
Production depth is numeric:
| Meaning | Value |
|---|---|
| Light | 1 |
| Medium | 2 |
| Deep | 3 |
Do not ship:
"depth": "light"
Ship:
"depth": 1
The strings light, medium, and deep may appear in coverage maps or review notes only.
Question Types
Use these type names exactly:
written
single_choice
multi_choice
scale
this_or_that
Do not rename them unless the app code and importer are updated first.
multi_choice
Use when more than one answer can be true.
Use 4 to 6 options by default.
{
"id": "quality_time_001",
"category_id": "quality_time",
"type": "multi_choice",
"text": "What helps time together feel easy to enjoy?",
"depth": 1,
"access": "free",
"tags": ["presence", "comfort", "quality_time"],
"options": [
{ "id": "no_rushing", "text": "Not feeling rushed" },
{ "id": "phones_away", "text": "Putting phones away" },
{ "id": "easy_conversation", "text": "Easy conversation" },
{ "id": "clear_plan", "text": "Knowing the general plan" }
],
"answer_config": {
"options": [
{ "id": "no_rushing", "text": "Not feeling rushed" },
{ "id": "phones_away", "text": "Putting phones away" },
{ "id": "easy_conversation", "text": "Easy conversation" },
{ "id": "clear_plan", "text": "Knowing the general plan" }
],
"min_selections": 1,
"max_selections": 3
}
}
Rules:
- top-level
optionsare required answer_config.optionsmust mirror the top-level options exactly- selection bounds, when present, must be valid for the number of options
- options must directly and grammatically answer the prompt
- options must not overlap excessively
single_choice
Use when the user selects one best answer.
Use 4 to 6 options by default.
{
"id": "quality_time_002",
"category_id": "quality_time",
"type": "single_choice",
"text": "Which low-key plan sounds best tonight?",
"depth": 1,
"access": "free",
"tags": ["low_energy", "easy_plan", "quality_time"],
"options": [
{ "id": "short_walk", "text": "A short walk" },
{ "id": "one_episode", "text": "One episode together" },
{ "id": "snack_and_talk", "text": "A snack and a talk" },
{ "id": "music_on_the_couch", "text": "Music on the couch" }
],
"answer_config": {
"options": [
{ "id": "short_walk", "text": "A short walk" },
{ "id": "one_episode", "text": "One episode together" },
{ "id": "snack_and_talk", "text": "A snack and a talk" },
{ "id": "music_on_the_couch", "text": "Music on the couch" }
]
}
}
Rules:
- top-level
optionsare required answer_config.optionsmust mirror them exactly- every option must be a plausible single best answer
- options should be similar in effort, emotional weight, and intimacy
scale
Use for one measurable dimension such as comfort, closeness, energy, readiness, satisfaction, confidence, importance, or frequency.
{
"id": "quality_time_003",
"category_id": "quality_time",
"type": "scale",
"text": "How easy is it to be present when we finally get time together?",
"depth": 2,
"access": "free",
"tags": ["presence", "attention", "quality_time"],
"answer_config": {
"min": 1,
"max": 5,
"min_label": "Not easy yet",
"max_label": "Very easy"
}
}
Rules:
- use
answer_config, not a top-levelscaleobject min,max,min_label, andmax_labelare required- labels must be gentle and neutral
- measure one thing only
this_or_that
Use for a fast two-option choice.
{
"id": "quality_time_004",
"category_id": "quality_time",
"type": "this_or_that",
"text": "Quiet time or playful time?",
"depth": 1,
"access": "free",
"tags": ["quick_choice", "mood", "quality_time"],
"options": [
{ "id": "quiet_time", "text": "Quiet time" },
{ "id": "playful_time", "text": "Playful time" }
],
"answer_config": {
"options": [
{ "id": "quiet_time", "text": "Quiet time" },
{ "id": "playful_time", "text": "Playful time" }
]
}
}
Rules:
- exactly two top-level options
answer_config.optionsmust mirror them exactly- the choice must be quick and balanced
written
Use only when a short written response adds meaningful value.
{
"id": "quality_time_005",
"category_id": "quality_time",
"type": "written",
"text": "What is one recent moment together that felt worth slowing down for?",
"depth": 2,
"access": "free",
"tags": ["recent_memory", "presence", "quality_time"],
"answer_config": {
"min_length": 1,
"max_length": 500,
"placeholder": "Write your answer..."
}
}
Rules:
- use
answer_config, not a top-levelanswerobject max_lengthis required- do not use written questions for basic preferences
- normal packs should contain 0 to 5 written questions
Standard Category Packs
A normal category pack may contain up to 150 questions.
A full 150-question mixed pack normally targets:
| Type | Count |
|---|---|
| multi_choice | 90 |
| single_choice | 30 |
| scale | 15 |
| this_or_that | 10 |
| written | 5 |
Normal full-pack access target:
45 free
105 premium
The count is a ceiling, not a quota. Smaller packs are valid when documented and stronger.
Daily Single-Choice Weekday System
Pack id:
daily_single_choice_weekly_v1
Compatibility filename:
daily_fun_multiple_choice_v3.json
Required production totals:
| Field | Count |
|---|---|
| weekday questions | 500 |
| wildcard questions | 11 |
| total questions | 511 |
| free | 86 |
| premium | 425 |
| single_choice | 511 |
The compatibility filename does not change the schema. The file must still be a complete production pack with a required category object and questions array.
Every daily question must include:
category_id- integer
depth accesstags- top-level
options - mirrored
answer_config.options
Every weekday question must have exactly one weekday tag.
Every wildcard must have mode_wildcard and daily_wildcard and no weekday tag.
A daily patch manifest is not the daily production pack.
Work Artifacts Are Not Packs
The following must not be placed under a production filename or inside an importer-scanned production directory:
partial batch JSON
simple-pack batch JSON
patch manifest
marked-fix list
validation report
coverage map
continuation note
apply script
review summary
A patch should be clearly named, for example:
daily_fun_multiple_choice_v3.patch.json
It must be applied to the complete production source, and the complete resulting pack must pass validation before shipping.
Per-File Validation Requirements
Before shipping a pack:
- JSON parses
- top-level
categoryobject exists - top-level
questionsarray exists - required category fields exist
- every question has every required field
- every
category_idmatchescategory.id - every depth is integer
1,2, or3 - all IDs are unique inside the file
- all question text is unique inside the file
- all types are valid
- tags arrays exist
- access values are valid
- option IDs are unique within each question
- top-level options and
answer_config.optionsmatch where required - metadata counts match actual counts
- normal packs contain no more than 150 questions
- normal packs contain no more than 5 written questions unless an approved exception exists
- special packs match their exact documented contract
- the file is a complete pack, not a patch or partial batch
Catalog-Wide Validation Requirements
Before rebuilding app.db or shipping question content, validate all production files together.
The full catalog must have:
- globally unique question IDs
- globally unique exact question text
- no duplicate text after case and whitespace normalization
- no blocked near-duplicates across categories
- no category resolving to
unknown - no production file containing a work artifact
- no field coercion caused by a schema mismatch
A duplicate question in two different files is a hard failure and may abort the entire import.
Per-file success does not override a catalog-wide failure.