2026-07-12 16:07:09 -05:00
# Closer Question Schema v8 — Importer-Aligned
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
**See also:** [QUESTION_CONTENT_GUIDE.md ](QUESTION_CONTENT_GUIDE.md ) | [DAILY_SINGLE_CHOICE_WEEKDAY_SYSTEM.md ](DAILY_SINGLE_CHOICE_WEEKDAY_SYSTEM.md ) | [QUESTION_REWRITE_PLAN.md ](QUESTION_REWRITE_PLAN.md ) | [QUESTION_QUALITY_CHECKLIST.md ](QUESTION_QUALITY_CHECKLIST.md )
2026-06-25 18:48:37 -05:00
2026-07-12 16:07:09 -05:00
## Authority
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
This document defines the JSON that imports correctly **today** .
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
The active `build_db.py` behavior and the Room storage contract are authoritative for production files.
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
A future schema direction may be documented separately, but it must not appear in production examples until the importer, Room model, migrations, readers, tests, and existing content are updated together.
2026-07-12 16:02:05 -05:00
2026-07-12 16:07:09 -05:00
## Production pack shape
Every production pack must use:
2026-07-12 16:02:05 -05:00
```json
{
"category": {
"id": "quality_time",
"display_name": "Quality Time",
"description": "Questions about being present and enjoying meaningful time together.",
"access": "mixed",
"icon_name": "schedule",
"metadata": {}
},
"questions": []
}
```
2026-07-12 16:07:09 -05:00
Required top-level fields:
2026-07-12 16:02:05 -05:00
```text
category
questions
```
2026-07-12 16:07:09 -05:00
Required category fields:
2026-07-12 16:02:05 -05:00
```text
id
display_name
description
access
icon_name
```
2026-07-12 16:07:09 -05:00
`metadata` is optional but recommended.
2026-07-12 16:02:05 -05:00
2026-07-12 16:07:09 -05:00
Do not use a flat or simple-pack wrapper such as:
2026-07-12 16:02:05 -05:00
```json
{
2026-07-12 16:07:09 -05:00
"id": "quality_time",
"title": "Quality Time",
2026-07-12 16:02:05 -05:00
"count": 25,
"questions": []
}
```
2026-07-12 16:07:09 -05:00
The importer reads `data["category"]` . A missing category object can cause the file to resolve to `unknown` .
Do not rely on the filename to recover category identity.
## Required question fields
2026-07-12 16:02:05 -05:00
Every production question requires:
```text
id
category_id
type
text
depth
access
tags
```
Rules:
2026-07-12 16:07:09 -05:00
- `category_id` must exactly match `category.id`
- `type` must be a supported type
2026-07-12 16:02:05 -05:00
- `text` must be non-empty and catalog-unique
2026-07-12 16:07:09 -05:00
- `depth` must be integer `1` , `2` , or `3`
2026-07-12 16:02:05 -05:00
- `access` must be `free` or `premium`
2026-07-12 16:07:09 -05:00
- `tags` must be an array
- use at least one meaningful tag unless a documented product exception exists
2026-07-12 16:02:05 -05:00
- `sex` is optional and nullable for ordinary packs
2026-07-12 16:07:09 -05:00
- require `sex` only when a documented feature actually reads it
2026-07-12 16:02:05 -05:00
2026-07-12 16:07:09 -05:00
## Depth: production values
2026-07-12 16:02:05 -05:00
2026-07-12 16:07:09 -05:00
| Meaning | Production value |
2026-07-12 16:02:05 -05:00
|---|---:|
| Light | `1` |
| Medium | `2` |
| Deep | `3` |
2026-07-12 16:07:09 -05:00
Production example:
2026-07-12 16:02:05 -05:00
```json
2026-07-12 16:07:09 -05:00
"depth": 2
2026-07-12 16:02:05 -05:00
```
2026-07-12 16:07:09 -05:00
### Future depth migration — not production-ready
String values are a planned possibility:
2026-07-12 16:02:05 -05:00
```json
2026-07-12 16:07:09 -05:00
"depth": "medium"
2026-07-12 16:02:05 -05:00
```
2026-07-12 16:07:09 -05:00
Do **not** ship them today.
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
The current importer writes depth into an integer column, and Room reads the field as an integer. A string can be stored or coerced incorrectly and then read as `0` , breaking depth routing and help text.
2026-06-30 23:33:48 -05:00
2026-07-12 16:07:09 -05:00
String depth becomes valid only after a coordinated code and data migration updates:
```text
build_db.py
Room entity and schema
database migration or asset rebuild path
all integer depth readers
depth routing and help text
validation
tests
all production content
```
Until that migration lands, integers are mandatory.
## Supported question types
Use these names exactly:
2026-06-30 23:33:48 -05:00
2026-07-12 16:02:05 -05:00
```text
written
single_choice
multi_choice
scale
this_or_that
```
2026-06-30 23:33:48 -05:00
2026-07-12 16:07:09 -05:00
## `single_choice`
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
Use when the player chooses one best answer.
2026-06-25 11:37:10 -05:00
```json
{
2026-07-12 16:02:05 -05:00
"id": "quality_time_001",
"category_id": "quality_time",
2026-07-12 16:07:09 -05:00
"type": "single_choice",
"text": "Which low-key plan sounds best tonight?",
2026-07-12 16:02:05 -05:00
"depth": 1,
"access": "free",
2026-07-12 16:07:09 -05:00
"tags": ["low_energy", "easy_plan", "quality_time"],
2026-06-25 11:37:10 -05:00
"options": [
2026-07-12 16:07:09 -05:00
{ "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" }
2026-07-12 16:02:05 -05:00
],
"answer_config": {
"options": [
2026-07-12 16:07:09 -05:00
{ "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" }
]
2026-07-12 16:02:05 -05:00
}
2026-06-25 11:37:10 -05:00
}
```
2026-07-12 16:07:09 -05:00
Requirements:
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
- use 4 to 6 options by default
2026-07-12 16:02:05 -05:00
- top-level `options` are required
2026-07-12 16:07:09 -05:00
- `answer_config.options` must mirror top-level options exactly
- every option must directly answer the prompt
- options should be similar in effort, emotional weight, and intimacy
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
## `multi_choice`
2026-06-30 23:33:48 -05:00
2026-07-12 16:07:09 -05:00
Use when more than one answer can be true.
2026-06-30 23:33:48 -05:00
2026-06-25 11:37:10 -05:00
```json
{
2026-07-12 16:02:05 -05:00
"id": "quality_time_002",
"category_id": "quality_time",
2026-07-12 16:07:09 -05:00
"type": "multi_choice",
"text": "What helps time together feel easy to enjoy?",
2026-07-12 16:02:05 -05:00
"depth": 1,
"access": "free",
2026-07-12 16:07:09 -05:00
"tags": ["presence", "comfort", "quality_time"],
2026-06-25 11:37:10 -05:00
"options": [
2026-07-12 16:07:09 -05:00
{ "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" }
2026-07-12 16:02:05 -05:00
],
"answer_config": {
"options": [
2026-07-12 16:07:09 -05:00
{ "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
2026-07-12 16:02:05 -05:00
}
2026-06-25 11:37:10 -05:00
}
```
2026-07-12 16:07:09 -05:00
Requirements:
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
- use 4 to 6 options by default
2026-07-12 16:02:05 -05:00
- top-level `options` are required
- `answer_config.options` must mirror them exactly
2026-07-12 16:07:09 -05:00
- selection bounds must be valid when included
- options must not overlap excessively
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
## `scale`
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
The importer reads scale settings from `answer_config` .
2026-06-30 23:33:48 -05:00
2026-06-25 11:37:10 -05:00
```json
{
2026-07-12 16:02:05 -05:00
"id": "quality_time_003",
"category_id": "quality_time",
2026-06-25 11:37:10 -05:00
"type": "scale",
2026-07-12 16:02:05 -05:00
"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": {
2026-06-25 11:37:10 -05:00
"min": 1,
"max": 5,
2026-07-12 16:02:05 -05:00
"min_label": "Not easy yet",
"max_label": "Very easy"
2026-06-25 11:37:10 -05:00
}
}
```
2026-07-12 16:07:09 -05:00
Requirements:
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
- use `answer_config`
- include `min` , `max` , `min_label` , and `max_label`
2026-07-12 16:02:05 -05:00
- measure one thing only
2026-07-12 16:07:09 -05:00
- keep both labels neutral and non-shaming
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
Do not put scale settings in a separate top-level object. The importer does not read custom scale labels from there.
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
## `written`
The importer reads written settings from `answer_config` .
2026-06-25 11:37:10 -05:00
```json
{
2026-07-12 16:02:05 -05:00
"id": "quality_time_004",
"category_id": "quality_time",
2026-07-12 16:07:09 -05:00
"type": "written",
"text": "What is one recent moment together that felt worth slowing down for?",
"depth": 2,
2026-07-12 16:02:05 -05:00
"access": "free",
2026-07-12 16:07:09 -05:00
"tags": ["recent_memory", "presence", "quality_time"],
2026-07-12 16:02:05 -05:00
"answer_config": {
2026-07-12 16:07:09 -05:00
"max_length": 500
2026-07-12 16:02:05 -05:00
}
2026-06-25 11:37:10 -05:00
}
```
2026-07-12 16:07:09 -05:00
Requirements:
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
- use `answer_config`
- include `max_length` when a custom limit matters
- do not use a separate top-level `answer` object
- use written questions only when typing adds real value
- normal packs should contain 0 to 5 written questions
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
## `this_or_that`
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
A `this_or_that` question must contain its two choices.
2026-06-25 11:37:10 -05:00
```json
{
2026-07-12 16:02:05 -05:00
"id": "quality_time_005",
"category_id": "quality_time",
2026-07-12 16:07:09 -05:00
"type": "this_or_that",
"text": "Quiet time or playful time?",
"depth": 1,
2026-07-12 16:02:05 -05:00
"access": "free",
2026-07-12 16:07:09 -05:00
"tags": ["quick_choice", "mood", "quality_time"],
"options": [
{ "id": "quiet_time", "text": "Quiet time" },
{ "id": "playful_time", "text": "Playful time" }
],
2026-07-12 16:02:05 -05:00
"answer_config": {
2026-07-12 16:07:09 -05:00
"options": [
{ "id": "quiet_time", "text": "Quiet time" },
{ "id": "playful_time", "text": "Playful time" }
]
2026-06-25 11:40:56 -05:00
}
2026-06-25 11:37:10 -05:00
}
```
2026-07-12 16:07:09 -05:00
Requirements:
2026-06-25 11:40:56 -05:00
2026-07-12 16:07:09 -05:00
- exactly two top-level options
- `answer_config.options` must mirror them exactly
- never ship a bare prompt with no A/B choices
- both choices should be balanced
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
## Standard category packs
2026-06-30 21:24:56 -05:00
2026-07-12 16:07:09 -05:00
A normal pack may contain up to 150 questions.
2026-07-12 12:55:14 -05:00
2026-07-12 16:07:09 -05:00
A full 150-question target is:
2026-06-25 11:37:10 -05:00
2026-06-30 23:33:48 -05:00
| Type | Count |
|---|---:|
2026-07-12 16:07:09 -05:00
| `multi_choice` | 90 |
| `single_choice` | 30 |
| `scale` | 15 |
| `this_or_that` | 10 |
| `written` | 5 |
2026-06-25 11:37:10 -05:00
2026-07-12 16:02:05 -05:00
Normal full-pack access target:
2026-07-12 12:55:14 -05:00
2026-07-12 16:02:05 -05:00
```text
45 free
105 premium
```
2026-06-30 21:24:56 -05:00
2026-07-12 16:07:09 -05:00
The count is a ceiling, not a quota.
2026-06-30 21:24:56 -05:00
2026-07-12 16:02:05 -05:00
## Daily Single-Choice Weekday System
2026-06-30 21:24:56 -05:00
2026-07-12 16:07:09 -05:00
Compatibility filename:
2026-06-30 21:24:56 -05:00
2026-06-30 23:33:48 -05:00
```text
2026-07-12 16:07:09 -05:00
daily_fun_multiple_choice_v3.json
2026-06-30 23:33:48 -05:00
```
2026-06-30 21:24:56 -05:00
2026-07-12 16:07:09 -05:00
Production category id:
2026-06-30 21:24:56 -05:00
2026-06-30 23:33:48 -05:00
```text
2026-07-12 16:07:09 -05:00
daily_fun_mc
2026-06-30 23:33:48 -05:00
```
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
Logical pack id stored in metadata:
2026-07-11 16:48:18 -05:00
2026-07-12 16:07:09 -05:00
```text
daily_single_choice_weekly_v1
```
2026-07-11 16:48:18 -05:00
2026-07-12 16:07:09 -05:00
The production file must use the normal top-level wrapper:
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
```json
{
"category": {
"id": "daily_fun_mc",
"display_name": "Daily Fun",
"description": "One quick couples-game question for each day, including wildcard surprise days.",
"access": "mixed",
"icon_name": "calendar_today",
"metadata": {
"pack_id": "daily_single_choice_weekly_v1",
"question_type_policy": "single_choice_only",
"total_questions": 511,
"free_questions": 86,
"premium_questions": 425
}
},
"questions": []
}
```
When editing the existing production file, preserve its current category display fields and icon unless the product owner explicitly changes them. The important contract is that the category object exists and its `id` matches every question’ s `category_id` .
Required totals:
```text
500 weekday questions
11 wildcard questions
511 total questions
86 free
425 premium
511 single_choice
```
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
Every daily question requires:
2026-06-25 11:37:10 -05:00
2026-07-12 16:07:09 -05:00
```text
category_id: daily_fun_mc
integer depth
access
tags
top-level options
mirrored answer_config.options
```
Every weekday question must carry exactly one documented weekday tag.
2026-06-30 21:24:56 -05:00
2026-07-12 16:07:09 -05:00
Every wildcard must carry `mode_wildcard` and `daily_wildcard` , and must not carry a weekday tag.
2026-06-30 23:45:47 -05:00
2026-07-12 16:07:09 -05:00
A patch manifest containing only changed IDs is not the Daily production pack.
2026-06-30 23:45:47 -05:00
2026-07-12 16:07:09 -05:00
## Work artifacts are not production packs
2026-06-30 23:45:47 -05:00
2026-07-12 16:07:09 -05:00
Do not place any of these under a production filename:
2026-06-30 23:45:47 -05:00
2026-07-12 16:02:05 -05:00
```text
partial batch JSON
simple-pack batch JSON
patch manifest
marked-fix list
validation report
coverage map
continuation note
apply script
review summary
2026-06-30 23:45:47 -05:00
```
2026-07-12 16:07:09 -05:00
A patch must be clearly named as a patch, applied to the complete source, and followed by validation of the complete resulting pack.
2026-07-12 16:02:05 -05:00
2026-07-12 16:07:09 -05:00
## Per-file validation
2026-07-12 16:02:05 -05:00
2026-07-12 16:07:09 -05:00
Before shipping:
2026-07-12 16:02:05 -05:00
- JSON parses
2026-07-12 16:07:09 -05:00
- top-level `category` exists
- top-level `questions` exists
- all required category fields exist
- every required question field exists
2026-07-12 16:02:05 -05:00
- every `category_id` matches `category.id`
2026-07-12 16:07:09 -05:00
- every `depth` is integer `1` , `2` , or `3`
2026-07-12 16:02:05 -05:00
- all IDs are unique inside the file
- all question text is unique inside the file
2026-07-12 16:07:09 -05:00
- all access values are valid
- every tags value is an array
- choice option IDs are unique per question
- mirrored option arrays match exactly
- scale and written settings are in `answer_config`
- every `this_or_that` has exactly two choices
2026-07-12 16:02:05 -05:00
- metadata counts match actual counts
2026-07-12 16:07:09 -05:00
- the file is complete, not a batch or patch
2026-07-12 16:02:05 -05:00
2026-07-12 16:07:09 -05:00
## Catalog-wide validation
2026-07-12 16:02:05 -05:00
2026-07-12 16:07:09 -05:00
Before rebuilding `app.db` or shipping content, validate every production pack together.
2026-07-12 16:02:05 -05:00
2026-07-12 16:07:09 -05:00
Reject the build for:
2026-07-12 16:02:05 -05:00
2026-07-12 16:07:09 -05:00
- duplicate question IDs across files
- duplicate exact question text across files
- duplicate normalized question text after case and whitespace normalization
- blocked near-duplicates across categories
- any category resolving to `unknown`
- any production filename containing a work artifact
- any schema mismatch that can coerce a value or silently discard configuration
2026-07-12 16:02:05 -05:00
2026-07-12 16:07:09 -05:00
A duplicate in two otherwise valid files can abort the entire import.
2026-07-12 16:02:05 -05:00
2026-07-12 16:07:09 -05:00
Per-file success does not override catalog failure.