review
This commit is contained in:
parent
75a644e1ba
commit
d7d97cc8a8
|
|
@ -1,4 +1,4 @@
|
|||
# Daily Single Choice Weekday System v7
|
||||
# Daily Single Choice Weekday System v8 — Importer-Aligned
|
||||
|
||||
This document defines the Closer daily weekday question pack.
|
||||
|
||||
|
|
@ -6,26 +6,67 @@ This is a special pack. It is not a normal mixed category pack.
|
|||
|
||||
## Pack Identity
|
||||
|
||||
Recommended pack id:
|
||||
Logical pack id:
|
||||
|
||||
```text
|
||||
daily_single_choice_weekly_v1
|
||||
```
|
||||
|
||||
Recommended future file name:
|
||||
Production category id:
|
||||
|
||||
```text
|
||||
daily_single_choice_weekly_v1.json
|
||||
daily_fun_mc
|
||||
```
|
||||
|
||||
Current compatibility file name:
|
||||
Current compatibility filename:
|
||||
|
||||
```text
|
||||
daily_fun_multiple_choice_v3.json
|
||||
```
|
||||
|
||||
Use the old file name only while the app code still expects it. The content inside the file must still be single choice.
|
||||
Possible future filename:
|
||||
|
||||
```text
|
||||
daily_single_choice_weekly_v1.json
|
||||
```
|
||||
|
||||
The logical pack id and the production category id are not interchangeable.
|
||||
|
||||
- `category.id` must remain `daily_fun_mc` while the current app/database contract uses that category.
|
||||
- every Daily question must use `"category_id": "daily_fun_mc"`
|
||||
- store `"pack_id": "daily_single_choice_weekly_v1"` inside category metadata
|
||||
- the compatibility filename does not supply category identity
|
||||
- the production file must include the full `category` object and full `questions` array
|
||||
|
||||
When editing the existing production pack, preserve its current display name, description, access, and icon unless the product owner explicitly changes them.
|
||||
|
||||
## Required production wrapper
|
||||
|
||||
```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": []
|
||||
}
|
||||
```
|
||||
|
||||
The example documents the required shape. In the live file, preserve the existing category display fields and icon unless they are intentionally changed.
|
||||
|
||||
A flat document with `id`, `title`, `count`, and `questions` but no `category` object is not importable as the Daily category.
|
||||
|
||||
## Required Counts
|
||||
## Required Counts
|
||||
|
||||
This special daily pack uses:
|
||||
|
|
@ -700,6 +741,29 @@ If the app code still uses older mode tags, include the compatibility tag too, b
|
|||
|
||||
## Patch Discipline: Fix Only What Fails
|
||||
|
||||
|
||||
### Production-file protection
|
||||
|
||||
Patch discipline does not change the production file shape.
|
||||
|
||||
A document containing only:
|
||||
|
||||
```text
|
||||
id
|
||||
expected_text
|
||||
reasons
|
||||
replacement text
|
||||
replacement options
|
||||
```
|
||||
|
||||
is a patch manifest, not the Daily pack.
|
||||
|
||||
Never overwrite `daily_fun_multiple_choice_v3.json` with that manifest.
|
||||
|
||||
Apply the patch to the complete 511-question source, retain the full category object, validate the complete result, and ship the complete production JSON.
|
||||
|
||||
|
||||
|
||||
Daily pack updates must use patch discipline.
|
||||
|
||||
The writer must review the full pack, mark the failing question IDs, and then fix only those marked IDs.
|
||||
|
|
@ -824,7 +888,11 @@ These sources informed the daily fun rules. Do not copy their question lists. Us
|
|||
|
||||
Before shipping:
|
||||
|
||||
1. Run schema and count validation against the totals in Required Counts (511 · 86 free · 425 premium once the wildcard add-on is in; 511 single_choice).
|
||||
1. Confirm the file contains the full category object and all 511 questions.
|
||||
2. Confirm `category.id` and every `category_id` are `daily_fun_mc`.
|
||||
3. Confirm every depth is integer `1`, `2`, or `3`.
|
||||
4. Confirm every option set is mirrored into `answer_config.options`.
|
||||
5. Run schema and count validation against the totals in Required Counts (511 · 86 free · 425 premium once the wildcard add-on is in; 511 single_choice).
|
||||
2. Run duplicate question checks.
|
||||
3. Run duplicate option-list checks.
|
||||
4. Check repeated openers and repeated option text.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
# Importer and Question-Guide Reconciliation
|
||||
|
||||
## Final authority
|
||||
|
||||
Production content must match the importer and Room model that ship today.
|
||||
|
||||
The guides now separate:
|
||||
|
||||
1. **Current production schema** — required for content JSON now.
|
||||
2. **Future schema direction** — an engineering migration, not an authoring choice.
|
||||
|
||||
## Correct current production rules
|
||||
|
||||
| Aspect | Production rule |
|
||||
|---|---|
|
||||
| depth | integer `1`, `2`, or `3` |
|
||||
| pack wrapper | `{ "category": {...}, "questions": [...] }` |
|
||||
| scale settings | `answer_config` |
|
||||
| written settings | `answer_config` |
|
||||
| this-or-that | exactly two explicit options, mirrored into `answer_config.options` |
|
||||
| Daily category id | `daily_fun_mc` |
|
||||
| Daily logical pack id | `daily_single_choice_weekly_v1` in metadata |
|
||||
| Daily production file | complete 511-question pack, never a patch manifest |
|
||||
|
||||
## Future string-depth migration
|
||||
|
||||
String depth is still documented as a possible future direction, but it is clearly marked **not production-ready**.
|
||||
|
||||
It becomes valid only after coordinated updates to the importer, Room schema/entity, database migration path, readers, routing, validation, tests, and existing content.
|
||||
|
||||
## Catalog-wide build gate
|
||||
|
||||
A pack can be perfect by itself and still fail the catalog.
|
||||
|
||||
Before rebuilding the database, validate all production files together for:
|
||||
|
||||
- duplicate IDs
|
||||
- duplicate exact or normalized question text
|
||||
- blocked near-duplicates
|
||||
- unknown categories
|
||||
- partial packs or patch manifests under production filenames
|
||||
- values that will be coerced or silently ignored
|
||||
|
||||
The known `Comfort first or solutions first?` collision must be resolved in one category before the catalog can pass.
|
||||
|
|
@ -176,6 +176,28 @@ The writing concepts are Light, Medium, and Deep, but the production JSON field
|
|||
|
||||
Never write `"light"`, `"medium"`, or `"deep"` into a production question object. Those labels may appear only in planning notes, coverage maps, or human-readable review documents.
|
||||
|
||||
## Future depth migration — not production-ready
|
||||
|
||||
String depth values such as `"light"`, `"medium"`, and `"deep"` are a possible future schema direction.
|
||||
|
||||
They are **not valid production values today**.
|
||||
|
||||
Do not use string depth until one coordinated engineering change updates and verifies all of the following:
|
||||
|
||||
```text
|
||||
build_db.py
|
||||
Room entity and column type
|
||||
database migration or asset rebuild path
|
||||
all getInt()/integer readers
|
||||
depth routing and help text
|
||||
seed validation
|
||||
automated tests
|
||||
existing production JSON
|
||||
```
|
||||
|
||||
Until that migration is merged and deployed, production JSON must use integers `1`, `2`, or `3`.
|
||||
|
||||
|
||||
## Light
|
||||
|
||||
Light questions should be easy, fun, and low-pressure.
|
||||
|
|
@ -225,23 +247,42 @@ ECHO Sensitive questions should never pressure a partner to disclose, forgive, e
|
|||
|
||||
# 6. Question Types
|
||||
|
||||
Each question type should have a purpose.
|
||||
Every production example in this section matches the importer contract used today.
|
||||
|
||||
`sex` is omitted because it is optional for ordinary packs. Add it only when a documented feature needs it.
|
||||
|
||||
## written
|
||||
|
||||
Use for stories, memories, reflection, emotional honesty, and open-ended answers.
|
||||
Use written questions only when a choice-based format would lose the story, nuance, or personal explanation.
|
||||
|
||||
Written questions are the exception, not the default. Use them only when a choice-based format would lose the story, nuance, or personal explanation. A normal pack should contain 0 to 5 written questions unless a documented exception is approved.
|
||||
A normal pack should contain 0 to 5 written questions unless a documented exception is approved.
|
||||
|
||||
Good written examples:
|
||||
|
||||
```text
|
||||
What is one small thing I do that makes you feel cared for?
|
||||
What is a memory of us that still makes you smile?
|
||||
What is something you wish we made more time for?
|
||||
```json
|
||||
{
|
||||
"id": "emotional_intimacy_001",
|
||||
"category_id": "emotional_intimacy",
|
||||
"type": "written",
|
||||
"text": "What is one recent moment when you felt especially close to me?",
|
||||
"depth": 2,
|
||||
"access": "free",
|
||||
"tags": ["recent_memory", "closeness", "emotional_intimacy"],
|
||||
"answer_config": {
|
||||
"max_length": 500
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Avoid written questions that are too vague:
|
||||
Rules:
|
||||
|
||||
```bat
|
||||
ECHO Use answer_config for written settings.
|
||||
ECHO max_length is required when a custom limit matters.
|
||||
ECHO Do not use a top-level answer object.
|
||||
ECHO Do not use written questions for basic preferences.
|
||||
ECHO Do not use written questions to fill the pack count.
|
||||
```
|
||||
|
||||
Avoid:
|
||||
|
||||
```text
|
||||
How can we deepen our connection?
|
||||
|
|
@ -257,31 +298,42 @@ What is one thing we could do this week that would make us feel more like a team
|
|||
|
||||
## single_choice
|
||||
|
||||
Use for preferences and simple decisions.
|
||||
|
||||
Good single-choice example:
|
||||
Use when the player should choose one best answer.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "date_night_001",
|
||||
"category_id": "date_night",
|
||||
"type": "single_choice",
|
||||
"text": "Which kind of date sounds best this week?",
|
||||
"depth": 1,
|
||||
"access": "free",
|
||||
"tags": ["date_preference", "planning", "date_night"],
|
||||
"options": [
|
||||
{ "id": "cozy_at_home", "text": "Cozy at home" },
|
||||
{ "id": "dinner_out", "text": "Dinner out" },
|
||||
{ "id": "something_playful", "text": "Something playful" },
|
||||
{ "id": "something_outside", "text": "Something outside" },
|
||||
{ "id": "surprise_me", "text": "Surprise me" }
|
||||
]
|
||||
{ "id": "something_outside", "text": "Something outside" }
|
||||
],
|
||||
"answer_config": {
|
||||
"options": [
|
||||
{ "id": "cozy_at_home", "text": "Cozy at home" },
|
||||
{ "id": "dinner_out", "text": "Dinner out" },
|
||||
{ "id": "something_playful", "text": "Something playful" },
|
||||
{ "id": "something_outside", "text": "Something outside" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
```bat
|
||||
ECHO Options should be short.
|
||||
ECHO Options should not overlap too much.
|
||||
ECHO Options should sound like real choices.
|
||||
ECHO Avoid more than 6 options unless truly needed.
|
||||
ECHO Use 4 to 6 options by default.
|
||||
ECHO Options should be short and natural.
|
||||
ECHO Every option must directly answer the prompt.
|
||||
ECHO Options should be similar in effort and emotional weight.
|
||||
ECHO Mirror top-level options into answer_config.options exactly.
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -290,48 +342,32 @@ ECHO Avoid more than 6 options unless truly needed.
|
|||
|
||||
Use when more than one answer can be true.
|
||||
|
||||
Good multi-choice example:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "date_night_002",
|
||||
"category_id": "date_night",
|
||||
"type": "multi_choice",
|
||||
"text": "What helps you feel relaxed on a date with me?",
|
||||
"depth": 1,
|
||||
"access": "free",
|
||||
"tags": ["comfort", "date_preferences", "date_night"],
|
||||
"options": [
|
||||
{ "id": "no_rushing", "text": "Not feeling rushed" },
|
||||
{ "id": "phones_away", "text": "Putting phones away" },
|
||||
{ "id": "good_food", "text": "Good food" },
|
||||
{ "id": "easy_conversation", "text": "Easy conversation" },
|
||||
{ "id": "physical_affection", "text": "Physical affection" },
|
||||
{ "id": "clear_plan", "text": "Having a plan" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
```bat
|
||||
ECHO Multi-choice prompts should say or imply that multiple answers are okay.
|
||||
ECHO Options should not shame either partner.
|
||||
ECHO Include practical and emotional options when possible.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## scale
|
||||
|
||||
Use for comfort level, closeness, energy, readiness, satisfaction, or frequency.
|
||||
|
||||
Good scale example:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "scale",
|
||||
"text": "How much do you feel like we need a real date soon?",
|
||||
"scale": {
|
||||
"min": 1,
|
||||
"max": 5,
|
||||
"min_label": "Not much",
|
||||
"max_label": "Very much"
|
||||
{ "id": "physical_affection", "text": "Physical affection" }
|
||||
],
|
||||
"answer_config": {
|
||||
"options": [
|
||||
{ "id": "no_rushing", "text": "Not feeling rushed" },
|
||||
{ "id": "phones_away", "text": "Putting phones away" },
|
||||
{ "id": "good_food", "text": "Good food" },
|
||||
{ "id": "easy_conversation", "text": "Easy conversation" },
|
||||
{ "id": "physical_affection", "text": "Physical affection" }
|
||||
],
|
||||
"min_selections": 1,
|
||||
"max_selections": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -339,8 +375,43 @@ Good scale example:
|
|||
Rules:
|
||||
|
||||
```bat
|
||||
ECHO The prompt should clearly allow multiple answers.
|
||||
ECHO Options should not shame either partner.
|
||||
ECHO Options should not overlap excessively.
|
||||
ECHO Mirror top-level options into answer_config.options exactly.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## scale
|
||||
|
||||
Use for one measurable dimension such as comfort, closeness, energy, readiness, satisfaction, confidence, importance, or frequency.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "date_night_003",
|
||||
"category_id": "date_night",
|
||||
"type": "scale",
|
||||
"text": "How much would a real date help us feel connected right now?",
|
||||
"depth": 2,
|
||||
"access": "free",
|
||||
"tags": ["connection", "readiness", "date_night"],
|
||||
"answer_config": {
|
||||
"min": 1,
|
||||
"max": 5,
|
||||
"min_label": "Not much",
|
||||
"max_label": "A lot"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
```bat
|
||||
ECHO Put scale settings inside answer_config.
|
||||
ECHO Do not use a top-level scale object.
|
||||
ECHO min, max, min_label, and max_label are required for custom labels.
|
||||
ECHO Scale labels should be gentle and neutral.
|
||||
ECHO Do not make the low end sound bad or shameful.
|
||||
ECHO Scale questions should measure one thing only.
|
||||
```
|
||||
|
||||
|
|
@ -360,27 +431,42 @@ How much would some intentional time together help us right now?
|
|||
|
||||
## this_or_that
|
||||
|
||||
Use for fast, playful, low-pressure choices.
|
||||
Use for a fast two-option choice.
|
||||
|
||||
Good this-or-that examples:
|
||||
|
||||
```text
|
||||
Planned date or spontaneous date?
|
||||
Dress up or stay cozy?
|
||||
Cook together or order in?
|
||||
Sunset walk or late-night drive?
|
||||
```json
|
||||
{
|
||||
"id": "date_night_004",
|
||||
"category_id": "date_night",
|
||||
"type": "this_or_that",
|
||||
"text": "Planned date or spontaneous date?",
|
||||
"depth": 1,
|
||||
"access": "free",
|
||||
"tags": ["quick_choice", "date_style", "date_night"],
|
||||
"options": [
|
||||
{ "id": "planned_date", "text": "Planned date" },
|
||||
{ "id": "spontaneous_date", "text": "Spontaneous date" }
|
||||
],
|
||||
"answer_config": {
|
||||
"options": [
|
||||
{ "id": "planned_date", "text": "Planned date" },
|
||||
{ "id": "spontaneous_date", "text": "Spontaneous date" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
```bat
|
||||
ECHO This-or-that prompts should be quick.
|
||||
ECHO Avoid choices that imply one partner is wrong.
|
||||
ECHO Best used in fun, date, intimacy, home, and lifestyle packs.
|
||||
ECHO Supply exactly two options.
|
||||
ECHO Do not ship a bare this_or_that prompt with no options.
|
||||
ECHO Mirror top-level options into answer_config.options exactly.
|
||||
ECHO Keep both choices balanced and easy to understand.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
# 7. Free vs Premium Strategy
|
||||
|
||||
Free questions should prove the app is worth using.
|
||||
|
|
@ -800,6 +886,26 @@ Rules:
|
|||
- choice options and `answer_config` must follow `QUESTION_SCHEMA.md`
|
||||
- top-level `options` and `answer_config.options` must match when both are required by the active schema
|
||||
|
||||
|
||||
## 12.2A Type-specific importer contract
|
||||
|
||||
The active importer reads:
|
||||
|
||||
| Type | Production source |
|
||||
|---|---|
|
||||
| `single_choice` | top-level `options`; mirror into `answer_config.options` |
|
||||
| `multi_choice` | top-level `options`; mirror into `answer_config.options` |
|
||||
| `this_or_that` | exactly two top-level `options`; mirror into `answer_config.options` |
|
||||
| `scale` | `answer_config.min`, `max`, `min_label`, `max_label` |
|
||||
| `written` | `answer_config`, including `max_length` when customized |
|
||||
|
||||
Do not place scale settings in a top-level `scale` object.
|
||||
|
||||
Do not place written settings in a top-level `answer` object.
|
||||
|
||||
Do not ship `this_or_that` without two explicit choices.
|
||||
|
||||
|
||||
## 12.3 Production pack versus work artifact
|
||||
|
||||
These are not production packs:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Closer Question Quality Checklist v9
|
||||
# Closer Question Quality Checklist v10 — Importer-Aligned
|
||||
|
||||
**See also:** [QUESTION_CONTENT_GUIDE.md](QUESTION_CONTENT_GUIDE.md) | [QUESTION_SCHEMA.md](QUESTION_SCHEMA.md) | [QUESTION_REWRITE_PLAN.md](QUESTION_REWRITE_PLAN.md)
|
||||
|
||||
|
|
@ -52,6 +52,22 @@ Reject the file immediately if any item fails:
|
|||
|
||||
`sex` is optional and nullable for ordinary packs. Do not reject a normal pack merely because `sex` is absent. Require it only when a documented feature actually uses it for targeting.
|
||||
|
||||
|
||||
## Type-specific importer checks
|
||||
|
||||
Reject the question if:
|
||||
|
||||
- a `scale` stores its settings outside `answer_config`
|
||||
- a `written` question stores its settings outside `answer_config`
|
||||
- a `this_or_that` question has no options
|
||||
- a `this_or_that` question has anything other than two options
|
||||
- a choice question’s top-level options do not match `answer_config.options`
|
||||
- a production question uses string depth
|
||||
- a content note says “fix the importer later” while the production JSON remains incompatible
|
||||
|
||||
String depth is a future migration only. Until the code migration lands, valid production depth is integer `1`, `2`, or `3`.
|
||||
|
||||
|
||||
## Normal Pack Hard Checks
|
||||
|
||||
For every normal category pack, confirm before content review:
|
||||
|
|
@ -85,6 +101,9 @@ For the daily single choice weekday pack, confirm before content review:
|
|||
* no duplicate exact option lists
|
||||
* the production file contains the full `category` object and all 511 questions
|
||||
* the production file is not a patch manifest containing only changed IDs
|
||||
* the category object exists and its id matches every `category_id`
|
||||
* the production category id remains `daily_fun_mc` unless app code and data are migrated
|
||||
* logical pack id `daily_single_choice_weekly_v1` belongs in metadata, not in place of the category object
|
||||
* every daily question uses integer depth and includes category_id, access, and tags
|
||||
|
||||
## Daily Fun Gate
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Closer Question Rewrite Plan v9
|
||||
# Closer Question Rewrite Plan v10 — Importer-Aligned
|
||||
|
||||
## Purpose
|
||||
|
||||
|
|
@ -8,6 +8,27 @@ The goal is not just clean JSON.
|
|||
|
||||
The goal is questions real couples want to answer.
|
||||
|
||||
|
||||
## Importer-aligned schema snapshot
|
||||
|
||||
Every production replacement must preserve:
|
||||
|
||||
```text
|
||||
top-level category object
|
||||
top-level questions array
|
||||
category_id on every question
|
||||
integer depth 1/2/3
|
||||
tags array on every question
|
||||
answer_config for scale settings
|
||||
answer_config for written settings
|
||||
two explicit options for this_or_that
|
||||
```
|
||||
|
||||
The future string-depth plan is not part of current production work.
|
||||
|
||||
If the intended future schema differs from the importer, create a separate engineering migration task. Do not make content agents bridge the mismatch by shipping incompatible JSON.
|
||||
|
||||
|
||||
## Production Replacement Rule
|
||||
|
||||
Writing in batches does not permit shipping a partial pack.
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
# Closer Question Schema v8
|
||||
# Closer Question Schema v8 — Importer-Aligned
|
||||
|
||||
**See also:** [QUESTION_CONTENT_GUIDE.md](QUESTION_CONTENT_GUIDE.md) | [QUESTION_REWRITE_PLAN.md](QUESTION_REWRITE_PLAN.md) | [QUESTION_QUALITY_CHECKLIST.md](QUESTION_QUALITY_CHECKLIST.md)
|
||||
**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)
|
||||
|
||||
## Purpose
|
||||
## Authority
|
||||
|
||||
This document defines the production JSON contract used by the active Closer importer and Room database.
|
||||
This document defines the JSON that imports correctly **today**.
|
||||
|
||||
The active importer is authoritative. Planning labels and old authoring formats must not override the production contract.
|
||||
The active `build_db.py` behavior and the Room storage contract are authoritative for production files.
|
||||
|
||||
## Production File Shape
|
||||
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.
|
||||
|
||||
Every production question pack must use this top-level shape:
|
||||
## Production pack shape
|
||||
|
||||
Every production pack must use:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -20,21 +22,20 @@ Every production question pack must use this top-level shape:
|
|||
"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:
|
||||
Required top-level fields:
|
||||
|
||||
```text
|
||||
category
|
||||
questions
|
||||
```
|
||||
|
||||
Required `category` fields:
|
||||
Required category fields:
|
||||
|
||||
```text
|
||||
id
|
||||
|
|
@ -44,27 +45,24 @@ access
|
|||
icon_name
|
||||
```
|
||||
|
||||
Optional category fields include:
|
||||
`metadata` is optional but recommended.
|
||||
|
||||
```text
|
||||
schema_version
|
||||
metadata
|
||||
```
|
||||
|
||||
Never rely on the filename to manufacture a missing category.
|
||||
|
||||
The old simple-pack form is not production-valid:
|
||||
Do not use a flat or simple-pack wrapper such as:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "rebuilding_trust",
|
||||
"title": "Rebuilding Trust",
|
||||
"id": "quality_time",
|
||||
"title": "Quality Time",
|
||||
"count": 25,
|
||||
"questions": []
|
||||
}
|
||||
```
|
||||
|
||||
## Required Question Fields
|
||||
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
|
||||
|
||||
Every production question requires:
|
||||
|
||||
|
|
@ -80,42 +78,60 @@ tags
|
|||
|
||||
Rules:
|
||||
|
||||
- `category_id` must exactly match the enclosing `category.id`
|
||||
- `type` must be one of the supported type names
|
||||
- `category_id` must exactly match `category.id`
|
||||
- `type` must be a supported type
|
||||
- `text` must be non-empty and catalog-unique
|
||||
- `depth` must be the integer `1`, `2`, or `3`
|
||||
- `depth` must be integer `1`, `2`, or `3`
|
||||
- `access` must be `free` or `premium`
|
||||
- `tags` must be an array; use at least one meaningful tag unless a documented exception exists
|
||||
- `tags` must be an array
|
||||
- use at least one meaningful tag unless a documented product exception exists
|
||||
- `sex` is optional and nullable for ordinary packs
|
||||
- require `sex` only when a documented feature, such as feature-specific targeting, actually reads it
|
||||
- require `sex` only when a documented feature actually reads it
|
||||
|
||||
## Depth
|
||||
## Depth: production values
|
||||
|
||||
Production depth is numeric:
|
||||
|
||||
| Meaning | Value |
|
||||
| Meaning | Production value |
|
||||
|---|---:|
|
||||
| Light | `1` |
|
||||
| Medium | `2` |
|
||||
| Deep | `3` |
|
||||
|
||||
Do not ship:
|
||||
Production example:
|
||||
|
||||
```json
|
||||
"depth": "light"
|
||||
"depth": 2
|
||||
```
|
||||
|
||||
Ship:
|
||||
### Future depth migration — not production-ready
|
||||
|
||||
String values are a planned possibility:
|
||||
|
||||
```json
|
||||
"depth": 1
|
||||
"depth": "medium"
|
||||
```
|
||||
|
||||
The strings `light`, `medium`, and `deep` may appear in coverage maps or review notes only.
|
||||
Do **not** ship them today.
|
||||
|
||||
## Question Types
|
||||
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.
|
||||
|
||||
Use these type names exactly:
|
||||
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:
|
||||
|
||||
```text
|
||||
written
|
||||
|
|
@ -125,18 +141,52 @@ scale
|
|||
this_or_that
|
||||
```
|
||||
|
||||
Do not rename them unless the app code and importer are updated first.
|
||||
## `single_choice`
|
||||
|
||||
## multi_choice
|
||||
|
||||
Use when more than one answer can be true.
|
||||
|
||||
Use 4 to 6 options by default.
|
||||
Use when the player chooses one best answer.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "quality_time_001",
|
||||
"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" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Requirements:
|
||||
|
||||
- use 4 to 6 options by default
|
||||
- top-level `options` are required
|
||||
- `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
|
||||
|
||||
## `multi_choice`
|
||||
|
||||
Use when more than one answer can be true.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "quality_time_002",
|
||||
"category_id": "quality_time",
|
||||
"type": "multi_choice",
|
||||
"text": "What helps time together feel easy to enjoy?",
|
||||
"depth": 1,
|
||||
|
|
@ -161,56 +211,17 @@ Use 4 to 6 options by default.
|
|||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- top-level `options` are required
|
||||
- `answer_config.options` must 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.
|
||||
|
||||
```json
|
||||
{
|
||||
"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:
|
||||
Requirements:
|
||||
|
||||
- use 4 to 6 options by default
|
||||
- top-level `options` are required
|
||||
- `answer_config.options` must mirror them exactly
|
||||
- every option must be a plausible single best answer
|
||||
- options should be similar in effort, emotional weight, and intimacy
|
||||
- selection bounds must be valid when included
|
||||
- options must not overlap excessively
|
||||
|
||||
## scale
|
||||
## `scale`
|
||||
|
||||
Use for one measurable dimension such as comfort, closeness, energy, readiness, satisfaction, confidence, importance, or frequency.
|
||||
The importer reads scale settings from `answer_config`.
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -230,21 +241,50 @@ Use for one measurable dimension such as comfort, closeness, energy, readiness,
|
|||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
Requirements:
|
||||
|
||||
- use `answer_config`, not a top-level `scale` object
|
||||
- `min`, `max`, `min_label`, and `max_label` are required
|
||||
- labels must be gentle and neutral
|
||||
- use `answer_config`
|
||||
- include `min`, `max`, `min_label`, and `max_label`
|
||||
- measure one thing only
|
||||
- keep both labels neutral and non-shaming
|
||||
|
||||
## this_or_that
|
||||
Do not put scale settings in a separate top-level object. The importer does not read custom scale labels from there.
|
||||
|
||||
Use for a fast two-option choice.
|
||||
## `written`
|
||||
|
||||
The importer reads written settings from `answer_config`.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "quality_time_004",
|
||||
"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": {
|
||||
"max_length": 500
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Requirements:
|
||||
|
||||
- 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
|
||||
|
||||
## `this_or_that`
|
||||
|
||||
A `this_or_that` question must contain its two choices.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "quality_time_005",
|
||||
"category_id": "quality_time",
|
||||
"type": "this_or_that",
|
||||
"text": "Quiet time or playful time?",
|
||||
"depth": 1,
|
||||
|
|
@ -263,53 +303,26 @@ Use for a fast two-option choice.
|
|||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
Requirements:
|
||||
|
||||
- exactly two top-level options
|
||||
- `answer_config.options` must mirror them exactly
|
||||
- the choice must be quick and balanced
|
||||
- never ship a bare prompt with no A/B choices
|
||||
- both choices should be balanced
|
||||
|
||||
## written
|
||||
## Standard category packs
|
||||
|
||||
Use only when a short written response adds meaningful value.
|
||||
A normal pack may contain up to 150 questions.
|
||||
|
||||
```json
|
||||
{
|
||||
"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-level `answer` object
|
||||
- `max_length` is 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:
|
||||
A full 150-question target is:
|
||||
|
||||
| Type | Count |
|
||||
|---|---:|
|
||||
| multi_choice | 90 |
|
||||
| single_choice | 30 |
|
||||
| scale | 15 |
|
||||
| this_or_that | 10 |
|
||||
| written | 5 |
|
||||
| `multi_choice` | 90 |
|
||||
| `single_choice` | 30 |
|
||||
| `scale` | 15 |
|
||||
| `this_or_that` | 10 |
|
||||
| `written` | 5 |
|
||||
|
||||
Normal full-pack access target:
|
||||
|
||||
|
|
@ -318,53 +331,83 @@ Normal full-pack access target:
|
|||
105 premium
|
||||
```
|
||||
|
||||
The count is a ceiling, not a quota. Smaller packs are valid when documented and stronger.
|
||||
The count is a ceiling, not a quota.
|
||||
|
||||
## Daily Single-Choice Weekday System
|
||||
|
||||
Pack id:
|
||||
|
||||
```text
|
||||
daily_single_choice_weekly_v1
|
||||
```
|
||||
|
||||
Compatibility filename:
|
||||
|
||||
```text
|
||||
daily_fun_multiple_choice_v3.json
|
||||
```
|
||||
|
||||
Required production totals:
|
||||
Production category id:
|
||||
|
||||
| Field | Count |
|
||||
|---|---:|
|
||||
| weekday questions | 500 |
|
||||
| wildcard questions | 11 |
|
||||
| total questions | 511 |
|
||||
| free | 86 |
|
||||
| premium | 425 |
|
||||
| single_choice | 511 |
|
||||
```text
|
||||
daily_fun_mc
|
||||
```
|
||||
|
||||
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.
|
||||
Logical pack id stored in metadata:
|
||||
|
||||
Every daily question must include:
|
||||
```text
|
||||
daily_single_choice_weekly_v1
|
||||
```
|
||||
|
||||
- `category_id`
|
||||
- integer `depth`
|
||||
- `access`
|
||||
- `tags`
|
||||
- top-level `options`
|
||||
- mirrored `answer_config.options`
|
||||
The production file must use the normal top-level wrapper:
|
||||
|
||||
Every weekday question must have exactly one weekday tag.
|
||||
```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": []
|
||||
}
|
||||
```
|
||||
|
||||
Every wildcard must have `mode_wildcard` and `daily_wildcard` and no weekday tag.
|
||||
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`.
|
||||
|
||||
A daily patch manifest is not the daily production pack.
|
||||
Required totals:
|
||||
|
||||
## Work Artifacts Are Not Packs
|
||||
```text
|
||||
500 weekday questions
|
||||
11 wildcard questions
|
||||
511 total questions
|
||||
86 free
|
||||
425 premium
|
||||
511 single_choice
|
||||
```
|
||||
|
||||
The following must not be placed under a production filename or inside an importer-scanned production directory:
|
||||
Every daily question requires:
|
||||
|
||||
```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.
|
||||
|
||||
Every wildcard must carry `mode_wildcard` and `daily_wildcard`, and must not carry a weekday tag.
|
||||
|
||||
A patch manifest containing only changed IDs is not the Daily production pack.
|
||||
|
||||
## Work artifacts are not production packs
|
||||
|
||||
Do not place any of these under a production filename:
|
||||
|
||||
```text
|
||||
partial batch JSON
|
||||
|
|
@ -378,52 +421,44 @@ apply script
|
|||
review summary
|
||||
```
|
||||
|
||||
A patch should be clearly named, for example:
|
||||
A patch must be clearly named as a patch, applied to the complete source, and followed by validation of the complete resulting pack.
|
||||
|
||||
```text
|
||||
daily_fun_multiple_choice_v3.patch.json
|
||||
```
|
||||
## Per-file validation
|
||||
|
||||
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:
|
||||
Before shipping:
|
||||
|
||||
- JSON parses
|
||||
- top-level `category` object exists
|
||||
- top-level `questions` array exists
|
||||
- required category fields exist
|
||||
- every question has every required field
|
||||
- top-level `category` exists
|
||||
- top-level `questions` exists
|
||||
- all required category fields exist
|
||||
- every required question field exists
|
||||
- every `category_id` matches `category.id`
|
||||
- every depth is integer `1`, `2`, or `3`
|
||||
- every `depth` is integer `1`, `2`, or `3`
|
||||
- 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.options` match where required
|
||||
- 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
|
||||
- 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
|
||||
- the file is complete, not a batch or patch
|
||||
|
||||
## Catalog-Wide Validation Requirements
|
||||
## Catalog-wide validation
|
||||
|
||||
Before rebuilding `app.db` or shipping question content, validate all production files together.
|
||||
Before rebuilding `app.db` or shipping content, validate every production pack together.
|
||||
|
||||
The full catalog must have:
|
||||
Reject the build for:
|
||||
|
||||
- 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
|
||||
- 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
|
||||
|
||||
A duplicate question in two different files is a hard failure and may abort the entire import.
|
||||
A duplicate in two otherwise valid files can abort the entire import.
|
||||
|
||||
Per-file success does not override a catalog-wide failure.
|
||||
Per-file success does not override catalog failure.
|
||||
|
|
|
|||
Loading…
Reference in New Issue