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.
|
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
|
## Pack Identity
|
||||||
|
|
||||||
Recommended pack id:
|
Logical pack id:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
daily_single_choice_weekly_v1
|
daily_single_choice_weekly_v1
|
||||||
```
|
```
|
||||||
|
|
||||||
Recommended future file name:
|
Production category id:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
daily_single_choice_weekly_v1.json
|
daily_fun_mc
|
||||||
```
|
```
|
||||||
|
|
||||||
Current compatibility file name:
|
Current compatibility filename:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
daily_fun_multiple_choice_v3.json
|
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
|
## Required Counts
|
||||||
|
|
||||||
This special daily pack uses:
|
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
|
## 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.
|
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.
|
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:
|
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.
|
2. Run duplicate question checks.
|
||||||
3. Run duplicate option-list checks.
|
3. Run duplicate option-list checks.
|
||||||
4. Check repeated openers and repeated option text.
|
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.
|
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
|
||||||
|
|
||||||
Light questions should be easy, fun, and low-pressure.
|
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
|
# 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
|
## 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:
|
```json
|
||||||
|
{
|
||||||
```text
|
"id": "emotional_intimacy_001",
|
||||||
What is one small thing I do that makes you feel cared for?
|
"category_id": "emotional_intimacy",
|
||||||
What is a memory of us that still makes you smile?
|
"type": "written",
|
||||||
What is something you wish we made more time for?
|
"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
|
```text
|
||||||
How can we deepen our connection?
|
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
|
## single_choice
|
||||||
|
|
||||||
Use for preferences and simple decisions.
|
Use when the player should choose one best answer.
|
||||||
|
|
||||||
Good single-choice example:
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"id": "date_night_001",
|
||||||
|
"category_id": "date_night",
|
||||||
"type": "single_choice",
|
"type": "single_choice",
|
||||||
"text": "Which kind of date sounds best this week?",
|
"text": "Which kind of date sounds best this week?",
|
||||||
|
"depth": 1,
|
||||||
|
"access": "free",
|
||||||
|
"tags": ["date_preference", "planning", "date_night"],
|
||||||
"options": [
|
"options": [
|
||||||
{ "id": "cozy_at_home", "text": "Cozy at home" },
|
{ "id": "cozy_at_home", "text": "Cozy at home" },
|
||||||
{ "id": "dinner_out", "text": "Dinner out" },
|
{ "id": "dinner_out", "text": "Dinner out" },
|
||||||
{ "id": "something_playful", "text": "Something playful" },
|
{ "id": "something_playful", "text": "Something playful" },
|
||||||
{ "id": "something_outside", "text": "Something outside" },
|
{ "id": "something_outside", "text": "Something outside" }
|
||||||
{ "id": "surprise_me", "text": "Surprise me" }
|
],
|
||||||
]
|
"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:
|
Rules:
|
||||||
|
|
||||||
```bat
|
```bat
|
||||||
ECHO Options should be short.
|
ECHO Use 4 to 6 options by default.
|
||||||
ECHO Options should not overlap too much.
|
ECHO Options should be short and natural.
|
||||||
ECHO Options should sound like real choices.
|
ECHO Every option must directly answer the prompt.
|
||||||
ECHO Avoid more than 6 options unless truly needed.
|
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.
|
Use when more than one answer can be true.
|
||||||
|
|
||||||
Good multi-choice example:
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"id": "date_night_002",
|
||||||
|
"category_id": "date_night",
|
||||||
"type": "multi_choice",
|
"type": "multi_choice",
|
||||||
"text": "What helps you feel relaxed on a date with me?",
|
"text": "What helps you feel relaxed on a date with me?",
|
||||||
|
"depth": 1,
|
||||||
|
"access": "free",
|
||||||
|
"tags": ["comfort", "date_preferences", "date_night"],
|
||||||
"options": [
|
"options": [
|
||||||
{ "id": "no_rushing", "text": "Not feeling rushed" },
|
{ "id": "no_rushing", "text": "Not feeling rushed" },
|
||||||
{ "id": "phones_away", "text": "Putting phones away" },
|
{ "id": "phones_away", "text": "Putting phones away" },
|
||||||
{ "id": "good_food", "text": "Good food" },
|
{ "id": "good_food", "text": "Good food" },
|
||||||
{ "id": "easy_conversation", "text": "Easy conversation" },
|
{ "id": "easy_conversation", "text": "Easy conversation" },
|
||||||
{ "id": "physical_affection", "text": "Physical affection" },
|
{ "id": "physical_affection", "text": "Physical affection" }
|
||||||
{ "id": "clear_plan", "text": "Having a plan" }
|
],
|
||||||
]
|
"answer_config": {
|
||||||
}
|
"options": [
|
||||||
```
|
{ "id": "no_rushing", "text": "Not feeling rushed" },
|
||||||
|
{ "id": "phones_away", "text": "Putting phones away" },
|
||||||
Rules:
|
{ "id": "good_food", "text": "Good food" },
|
||||||
|
{ "id": "easy_conversation", "text": "Easy conversation" },
|
||||||
```bat
|
{ "id": "physical_affection", "text": "Physical affection" }
|
||||||
ECHO Multi-choice prompts should say or imply that multiple answers are okay.
|
],
|
||||||
ECHO Options should not shame either partner.
|
"min_selections": 1,
|
||||||
ECHO Include practical and emotional options when possible.
|
"max_selections": 3
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -339,8 +375,43 @@ Good scale example:
|
||||||
Rules:
|
Rules:
|
||||||
|
|
||||||
```bat
|
```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 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.
|
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
|
## this_or_that
|
||||||
|
|
||||||
Use for fast, playful, low-pressure choices.
|
Use for a fast two-option choice.
|
||||||
|
|
||||||
Good this-or-that examples:
|
```json
|
||||||
|
{
|
||||||
```text
|
"id": "date_night_004",
|
||||||
Planned date or spontaneous date?
|
"category_id": "date_night",
|
||||||
Dress up or stay cozy?
|
"type": "this_or_that",
|
||||||
Cook together or order in?
|
"text": "Planned date or spontaneous date?",
|
||||||
Sunset walk or late-night drive?
|
"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:
|
Rules:
|
||||||
|
|
||||||
```bat
|
```bat
|
||||||
ECHO This-or-that prompts should be quick.
|
ECHO Supply exactly two options.
|
||||||
ECHO Avoid choices that imply one partner is wrong.
|
ECHO Do not ship a bare this_or_that prompt with no options.
|
||||||
ECHO Best used in fun, date, intimacy, home, and lifestyle packs.
|
ECHO Mirror top-level options into answer_config.options exactly.
|
||||||
|
ECHO Keep both choices balanced and easy to understand.
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
# 7. Free vs Premium Strategy
|
# 7. Free vs Premium Strategy
|
||||||
|
|
||||||
Free questions should prove the app is worth using.
|
Free questions should prove the app is worth using.
|
||||||
|
|
@ -800,6 +886,26 @@ Rules:
|
||||||
- choice options and `answer_config` must follow `QUESTION_SCHEMA.md`
|
- 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
|
- 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
|
## 12.3 Production pack versus work artifact
|
||||||
|
|
||||||
These are not production packs:
|
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)
|
**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.
|
`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
|
## Normal Pack Hard Checks
|
||||||
|
|
||||||
For every normal category pack, confirm before content review:
|
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
|
* no duplicate exact option lists
|
||||||
* the production file contains the full `category` object and all 511 questions
|
* 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 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
|
* every daily question uses integer depth and includes category_id, access, and tags
|
||||||
|
|
||||||
## Daily Fun Gate
|
## Daily Fun Gate
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Closer Question Rewrite Plan v9
|
# Closer Question Rewrite Plan v10 — Importer-Aligned
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
|
|
@ -8,6 +8,27 @@ The goal is not just clean JSON.
|
||||||
|
|
||||||
The goal is questions real couples want to answer.
|
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
|
## Production Replacement Rule
|
||||||
|
|
||||||
Writing in batches does not permit shipping a partial pack.
|
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
|
```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.",
|
"description": "Questions about being present and enjoying meaningful time together.",
|
||||||
"access": "mixed",
|
"access": "mixed",
|
||||||
"icon_name": "schedule",
|
"icon_name": "schedule",
|
||||||
"schema_version": "question_v2",
|
|
||||||
"metadata": {}
|
"metadata": {}
|
||||||
},
|
},
|
||||||
"questions": []
|
"questions": []
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Required top-level keys:
|
Required top-level fields:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
category
|
category
|
||||||
questions
|
questions
|
||||||
```
|
```
|
||||||
|
|
||||||
Required `category` fields:
|
Required category fields:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
id
|
id
|
||||||
|
|
@ -44,27 +45,24 @@ access
|
||||||
icon_name
|
icon_name
|
||||||
```
|
```
|
||||||
|
|
||||||
Optional category fields include:
|
`metadata` is optional but recommended.
|
||||||
|
|
||||||
```text
|
Do not use a flat or simple-pack wrapper such as:
|
||||||
schema_version
|
|
||||||
metadata
|
|
||||||
```
|
|
||||||
|
|
||||||
Never rely on the filename to manufacture a missing category.
|
|
||||||
|
|
||||||
The old simple-pack form is not production-valid:
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"id": "rebuilding_trust",
|
"id": "quality_time",
|
||||||
"title": "Rebuilding Trust",
|
"title": "Quality Time",
|
||||||
"count": 25,
|
"count": 25,
|
||||||
"questions": []
|
"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:
|
Every production question requires:
|
||||||
|
|
||||||
|
|
@ -80,42 +78,60 @@ tags
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
|
|
||||||
- `category_id` must exactly match the enclosing `category.id`
|
- `category_id` must exactly match `category.id`
|
||||||
- `type` must be one of the supported type names
|
- `type` must be a supported type
|
||||||
- `text` must be non-empty and catalog-unique
|
- `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`
|
- `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
|
- `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 | Production value |
|
||||||
|
|
||||||
| Meaning | Value |
|
|
||||||
|---|---:|
|
|---|---:|
|
||||||
| Light | `1` |
|
| Light | `1` |
|
||||||
| Medium | `2` |
|
| Medium | `2` |
|
||||||
| Deep | `3` |
|
| Deep | `3` |
|
||||||
|
|
||||||
Do not ship:
|
Production example:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"depth": "light"
|
"depth": 2
|
||||||
```
|
```
|
||||||
|
|
||||||
Ship:
|
### Future depth migration — not production-ready
|
||||||
|
|
||||||
|
String values are a planned possibility:
|
||||||
|
|
||||||
```json
|
```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
|
```text
|
||||||
written
|
written
|
||||||
|
|
@ -125,18 +141,52 @@ scale
|
||||||
this_or_that
|
this_or_that
|
||||||
```
|
```
|
||||||
|
|
||||||
Do not rename them unless the app code and importer are updated first.
|
## `single_choice`
|
||||||
|
|
||||||
## multi_choice
|
Use when the player chooses one best answer.
|
||||||
|
|
||||||
Use when more than one answer can be true.
|
|
||||||
|
|
||||||
Use 4 to 6 options by default.
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"id": "quality_time_001",
|
"id": "quality_time_001",
|
||||||
"category_id": "quality_time",
|
"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",
|
"type": "multi_choice",
|
||||||
"text": "What helps time together feel easy to enjoy?",
|
"text": "What helps time together feel easy to enjoy?",
|
||||||
"depth": 1,
|
"depth": 1,
|
||||||
|
|
@ -161,56 +211,17 @@ Use 4 to 6 options by default.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Rules:
|
Requirements:
|
||||||
|
|
||||||
- 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:
|
|
||||||
|
|
||||||
|
- use 4 to 6 options by default
|
||||||
- top-level `options` are required
|
- top-level `options` are required
|
||||||
- `answer_config.options` must mirror them exactly
|
- `answer_config.options` must mirror them exactly
|
||||||
- every option must be a plausible single best answer
|
- selection bounds must be valid when included
|
||||||
- options should be similar in effort, emotional weight, and intimacy
|
- 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
|
```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
|
- use `answer_config`
|
||||||
- `min`, `max`, `min_label`, and `max_label` are required
|
- include `min`, `max`, `min_label`, and `max_label`
|
||||||
- labels must be gentle and neutral
|
|
||||||
- measure one thing only
|
- 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
|
```json
|
||||||
{
|
{
|
||||||
"id": "quality_time_004",
|
"id": "quality_time_004",
|
||||||
"category_id": "quality_time",
|
"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",
|
"type": "this_or_that",
|
||||||
"text": "Quiet time or playful time?",
|
"text": "Quiet time or playful time?",
|
||||||
"depth": 1,
|
"depth": 1,
|
||||||
|
|
@ -263,53 +303,26 @@ Use for a fast two-option choice.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Rules:
|
Requirements:
|
||||||
|
|
||||||
- exactly two top-level options
|
- exactly two top-level options
|
||||||
- `answer_config.options` must mirror them exactly
|
- `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
|
A full 150-question target is:
|
||||||
{
|
|
||||||
"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:
|
|
||||||
|
|
||||||
| Type | Count |
|
| Type | Count |
|
||||||
|---|---:|
|
|---|---:|
|
||||||
| multi_choice | 90 |
|
| `multi_choice` | 90 |
|
||||||
| single_choice | 30 |
|
| `single_choice` | 30 |
|
||||||
| scale | 15 |
|
| `scale` | 15 |
|
||||||
| this_or_that | 10 |
|
| `this_or_that` | 10 |
|
||||||
| written | 5 |
|
| `written` | 5 |
|
||||||
|
|
||||||
Normal full-pack access target:
|
Normal full-pack access target:
|
||||||
|
|
||||||
|
|
@ -318,53 +331,83 @@ Normal full-pack access target:
|
||||||
105 premium
|
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
|
## Daily Single-Choice Weekday System
|
||||||
|
|
||||||
Pack id:
|
|
||||||
|
|
||||||
```text
|
|
||||||
daily_single_choice_weekly_v1
|
|
||||||
```
|
|
||||||
|
|
||||||
Compatibility filename:
|
Compatibility filename:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
daily_fun_multiple_choice_v3.json
|
daily_fun_multiple_choice_v3.json
|
||||||
```
|
```
|
||||||
|
|
||||||
Required production totals:
|
Production category id:
|
||||||
|
|
||||||
| Field | Count |
|
```text
|
||||||
|---|---:|
|
daily_fun_mc
|
||||||
| 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.
|
Logical pack id stored in metadata:
|
||||||
|
|
||||||
Every daily question must include:
|
```text
|
||||||
|
daily_single_choice_weekly_v1
|
||||||
|
```
|
||||||
|
|
||||||
- `category_id`
|
The production file must use the normal top-level wrapper:
|
||||||
- integer `depth`
|
|
||||||
- `access`
|
|
||||||
- `tags`
|
|
||||||
- top-level `options`
|
|
||||||
- mirrored `answer_config.options`
|
|
||||||
|
|
||||||
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
|
```text
|
||||||
partial batch JSON
|
partial batch JSON
|
||||||
|
|
@ -378,52 +421,44 @@ apply script
|
||||||
review summary
|
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
|
## Per-file validation
|
||||||
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.
|
Before shipping:
|
||||||
|
|
||||||
## Per-File Validation Requirements
|
|
||||||
|
|
||||||
Before shipping a pack:
|
|
||||||
|
|
||||||
- JSON parses
|
- JSON parses
|
||||||
- top-level `category` object exists
|
- top-level `category` exists
|
||||||
- top-level `questions` array exists
|
- top-level `questions` exists
|
||||||
- required category fields exist
|
- all required category fields exist
|
||||||
- every question has every required field
|
- every required question field exists
|
||||||
- every `category_id` matches `category.id`
|
- 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 IDs are unique inside the file
|
||||||
- all question text is unique inside the file
|
- all question text is unique inside the file
|
||||||
- all types are valid
|
- all access values are valid
|
||||||
- tags arrays exist
|
- every tags value is an array
|
||||||
- access values are valid
|
- choice option IDs are unique per question
|
||||||
- option IDs are unique within each question
|
- mirrored option arrays match exactly
|
||||||
- top-level options and `answer_config.options` match where required
|
- scale and written settings are in `answer_config`
|
||||||
|
- every `this_or_that` has exactly two choices
|
||||||
- metadata counts match actual counts
|
- metadata counts match actual counts
|
||||||
- normal packs contain no more than 150 questions
|
- the file is complete, not a batch or patch
|
||||||
- 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
|
## 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
|
- duplicate question IDs across files
|
||||||
- globally unique exact question text
|
- duplicate exact question text across files
|
||||||
- no duplicate text after case and whitespace normalization
|
- duplicate normalized question text after case and whitespace normalization
|
||||||
- no blocked near-duplicates across categories
|
- blocked near-duplicates across categories
|
||||||
- no category resolving to `unknown`
|
- any category resolving to `unknown`
|
||||||
- no production file containing a work artifact
|
- any production filename containing a work artifact
|
||||||
- no field coercion caused by a schema mismatch
|
- 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