review
This commit is contained in:
parent
157f080ee6
commit
75a644e1ba
|
|
@ -102,3 +102,4 @@ revenucat_secret_api.md
|
||||||
# Local ship-readiness working plan — operator doc, not repo history
|
# Local ship-readiness working plan — operator doc, not repo history
|
||||||
release_guide.md
|
release_guide.md
|
||||||
seed/questions/closer_question_guides_150_max.zip
|
seed/questions/closer_question_guides_150_max.zip
|
||||||
|
seed/questions/rebuilding_trust_BATCH1_NOTE.md
|
||||||
|
|
|
||||||
12
Future.md
12
Future.md
|
|
@ -113,6 +113,18 @@ Still open:
|
||||||
- **`scheduledOutcomesReminder` scaling cap** — the daily cron scans `couples` with a flat `.limit(200)` and no pagination (`functions/src/couples/scheduledOutcomesReminder.ts:35`), so couples beyond the first 200 silently never get 30/60/90-day outcome nudges. Fine pre-launch; **before the userbase approaches ~200 couples**, paginate with the same `orderBy(__name__)+startAfter` page loop `assignDailyQuestion` already uses (see `assignDailyQuestion.ts` PAGE_SIZE pattern).
|
- **`scheduledOutcomesReminder` scaling cap** — the daily cron scans `couples` with a flat `.limit(200)` and no pagination (`functions/src/couples/scheduledOutcomesReminder.ts:35`), so couples beyond the first 200 silently never get 30/60/90-day outcome nudges. Fine pre-launch; **before the userbase approaches ~200 couples**, paginate with the same `orderBy(__name__)+startAfter` page loop `assignDailyQuestion` already uses (see `assignDailyQuestion.ts` PAGE_SIZE pattern).
|
||||||
- **Compose stability: adopt `ImmutableList` for `*UiState` list/set fields (codebase-wide).** Strong-skipping (K2 default) is on, and the Home refactor added `@Immutable` to the Home card models — but `List<>`/`Set<>` params still only reference-skip. Add the `kotlinx.collections.immutable` dependency (JetBrains, first-party) and switch `*UiState` list/set fields to `ImmutableList`/`ImmutableSet` (with `.toImmutableList()` at each VM construction site) so repeated card/list params become structurally skippable. **Do it consistently across all ~20 screens in one pass — not per-screen — to avoid a snowflake.** `ui/home/HomeModels.kt` is already partly there (`@Immutable` on `HomeAction`/`PendingActionCard`/`HomeCategorySummary`), so Home is the natural reference implementation.
|
- **Compose stability: adopt `ImmutableList` for `*UiState` list/set fields (codebase-wide).** Strong-skipping (K2 default) is on, and the Home refactor added `@Immutable` to the Home card models — but `List<>`/`Set<>` params still only reference-skip. Add the `kotlinx.collections.immutable` dependency (JetBrains, first-party) and switch `*UiState` list/set fields to `ImmutableList`/`ImmutableSet` (with `.toImmutableList()` at each VM construction site) so repeated card/list params become structurally skippable. **Do it consistently across all ~20 screens in one pass — not per-screen — to avoid a snowflake.** `ui/home/HomeModels.kt` is already partly there (`@Immutable` on `HomeAction`/`PendingActionCard`/`HomeCategorySummary`), so Home is the natural reference implementation.
|
||||||
|
|
||||||
|
- **Test `HomeViewModel.loadHome` (the honest follow-up to the Home decomposition).** The R31 Home split made
|
||||||
|
Home navigable, extracted its pure action logic into `HomeActionMapper` (12 JVM tests) and added a render
|
||||||
|
smoke — but deliberately did **not** touch `loadHome`, the ~180-line Firestore orchestration (5 parallel
|
||||||
|
retention fetches + streak/outcome calc + error handling) that remains the **highest-risk, still-untested**
|
||||||
|
code on the screen. The render-smoke exercises the render path, not `loadHome`'s data assembly. Real next
|
||||||
|
priority: a `HomeViewModel` test with fake repos covering the parallel-fetch success/partial-failure paths.
|
||||||
|
Needs the repo interfaces faked (no Hilt/Firebase) — its own follow-up, not a Home-UI change.
|
||||||
|
- **De-duplicate `dueFollowUpDay` across Home / Settings / YourProgress (3 copies).** The 30/60/90-day
|
||||||
|
outcome-reminder day math is implemented three times (now `HomeActionMapper.dueFollowUpDay`,
|
||||||
|
`SettingsViewModel`, `YourProgressViewModel`), all on the API-safe `Instant.atZone().toLocalDate()` path
|
||||||
|
since R31. Lift to one shared helper so the three can't drift; low-risk once `loadHome` has a test around it.
|
||||||
|
|
||||||
|
|
||||||
Improvement & feature ideas surfaced while QA-testing as a consumer (each works today — none are defects).
|
Improvement & feature ideas surfaced while QA-testing as a consumer (each works today — none are defects).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,27 @@ Only a documented product-specific special pack may exceed 150 questions. The cu
|
||||||
|
|
||||||
Written questions must remain rare. A normal pack may use **0 to 5 written questions by default**. More than 5 requires a documented pack-specific reason and explicit product-owner approval. Never use written questions to fill a count.
|
Written questions must remain rare. A normal pack may use **0 to 5 written questions by default**. More than 5 requires a documented pack-specific reason and explicit product-owner approval. Never use written questions to fill a count.
|
||||||
|
|
||||||
|
## Importer and Production Contract Authority
|
||||||
|
|
||||||
|
The active app importer and Room database contract are authoritative for production JSON.
|
||||||
|
|
||||||
|
Human-friendly authoring labels, batch notes, patch manifests, and planning files may help writers work, but they must never replace or override the production schema.
|
||||||
|
|
||||||
|
Current production rules:
|
||||||
|
|
||||||
|
- a production pack must use the top-level shape `{ "category": {...}, "questions": [...] }`
|
||||||
|
- the `category` object is required and must include `id`, `display_name`, `description`, `access`, and `icon_name`
|
||||||
|
- every question must include `id`, `category_id`, `type`, `text`, integer `depth`, `access`, and `tags`
|
||||||
|
- `category_id` must exactly match `category.id`
|
||||||
|
- `depth` must be an integer: `1` = light, `2` = medium, `3` = deep
|
||||||
|
- `tags` must be present as an array; use at least one meaningful tag unless a documented exception exists
|
||||||
|
- `sex` is optional and nullable for ordinary packs; require it only when a product feature actually filters or routes by sex
|
||||||
|
- question-type-specific fields must match `QUESTION_SCHEMA.md`
|
||||||
|
- never rely on the filename to create or repair a missing category
|
||||||
|
- a batch file, continuation note, validation report, or patch manifest is not a production question pack
|
||||||
|
|
||||||
|
When a writing document conflicts with the importer, stop and correct the writing document. Do not ship compatibility-breaking JSON and do not defer the mismatch to a future importer change.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 1. The Closer Voice
|
# 1. The Closer Voice
|
||||||
|
|
@ -145,6 +166,16 @@ SET PREMIUM_VALUE=Deeper romance, planning preferences, rut-breaking, and memory
|
||||||
|
|
||||||
Use depth intentionally.
|
Use depth intentionally.
|
||||||
|
|
||||||
|
The writing concepts are Light, Medium, and Deep, but the production JSON field is numeric:
|
||||||
|
|
||||||
|
| Writing concept | Production `depth` |
|
||||||
|
|---|---:|
|
||||||
|
| Light | `1` |
|
||||||
|
| Medium | `2` |
|
||||||
|
| Deep | `3` |
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
## Light
|
## Light
|
||||||
|
|
||||||
Light questions should be easy, fun, and low-pressure.
|
Light questions should be easy, fun, and low-pressure.
|
||||||
|
|
@ -698,39 +729,160 @@ What tends to make date night harder for you to enjoy?
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 12. JSON Quality Rules
|
# 12. Production JSON Contract and Quality Rules
|
||||||
|
|
||||||
Every JSON file should pass these checks.
|
A JSON file is production-ready only when it is both valid content and a complete importable pack.
|
||||||
|
|
||||||
|
## 12.1 Required top-level shape
|
||||||
|
|
||||||
|
Every production pack must use:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"category": {
|
||||||
|
"id": "example_category",
|
||||||
|
"display_name": "Example Category",
|
||||||
|
"description": "User-facing description.",
|
||||||
|
"access": "mixed",
|
||||||
|
"icon_name": "favorite"
|
||||||
|
},
|
||||||
|
"questions": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Required category fields:
|
||||||
|
|
||||||
|
```text
|
||||||
|
id
|
||||||
|
display_name
|
||||||
|
description
|
||||||
|
access
|
||||||
|
icon_name
|
||||||
|
```
|
||||||
|
|
||||||
|
Optional category fields such as `schema_version` and `metadata` may be included, but they do not replace the required fields.
|
||||||
|
|
||||||
|
Do not use the old simple-pack shape:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "example",
|
||||||
|
"title": "Example",
|
||||||
|
"count": 25,
|
||||||
|
"questions": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not rely on filename parsing to create a category. A missing `category` object is a hard production failure.
|
||||||
|
|
||||||
|
## 12.2 Required question fields
|
||||||
|
|
||||||
|
Every production question must include:
|
||||||
|
|
||||||
|
```text
|
||||||
|
id
|
||||||
|
category_id
|
||||||
|
type
|
||||||
|
text
|
||||||
|
depth
|
||||||
|
access
|
||||||
|
tags
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- `category_id` must exactly equal the enclosing `category.id`
|
||||||
|
- `depth` must be the integer `1`, `2`, or `3`
|
||||||
|
- `tags` must be an array and should contain at least one meaningful tag
|
||||||
|
- `access` must be `free` or `premium`
|
||||||
|
- `sex` is optional and nullable for ordinary category packs
|
||||||
|
- `sex` becomes required only for a documented feature that routes or filters by it
|
||||||
|
- 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.3 Production pack versus work artifact
|
||||||
|
|
||||||
|
These are not production packs:
|
||||||
|
|
||||||
|
```text
|
||||||
|
25-question batch files
|
||||||
|
continuation notes
|
||||||
|
coverage maps
|
||||||
|
validation reports
|
||||||
|
marked-fix lists
|
||||||
|
patch manifests
|
||||||
|
apply scripts
|
||||||
|
review summaries
|
||||||
|
```
|
||||||
|
|
||||||
|
A work artifact must never overwrite a production filename.
|
||||||
|
|
||||||
|
A partial batch may be saved only outside importer-scanned production directories and must be clearly labeled as work in progress. The last complete validated production pack stays in place until the full replacement is ready.
|
||||||
|
|
||||||
|
A patch manifest must:
|
||||||
|
|
||||||
|
- use a clearly non-production name such as `*.patch.json`
|
||||||
|
- live outside any importer-scanned production directory
|
||||||
|
- identify the complete source pack it applies to
|
||||||
|
- be applied to the full source pack
|
||||||
|
- produce a complete validated production JSON before shipping
|
||||||
|
|
||||||
|
A patch manifest is never itself renamed to the production filename.
|
||||||
|
|
||||||
|
## 12.4 Daily pack protection
|
||||||
|
|
||||||
|
`daily_fun_multiple_choice_v3.json` is the compatibility filename for the Daily Single-Choice Weekday System.
|
||||||
|
|
||||||
|
That production file must always contain the complete documented pack:
|
||||||
|
|
||||||
|
```text
|
||||||
|
500 weekday questions
|
||||||
|
11 wildcard questions
|
||||||
|
511 total questions
|
||||||
|
86 free
|
||||||
|
425 premium
|
||||||
|
511 single_choice
|
||||||
|
```
|
||||||
|
|
||||||
|
A daily patch document containing only changed IDs is not the daily pack. Restore or retain the complete 511-question source, apply the patch to that source, then validate and ship the complete result.
|
||||||
|
|
||||||
|
## 12.5 Per-file hard checks
|
||||||
|
|
||||||
```bat
|
```bat
|
||||||
ECHO [ ] Valid JSON.
|
ECHO [ ] Valid JSON.
|
||||||
ECHO [ ] Category id matches file purpose.
|
ECHO [ ] Top-level category object exists.
|
||||||
ECHO [ ] Category title sounds user-facing.
|
ECHO [ ] Top-level questions array exists.
|
||||||
ECHO [ ] Category access matches actual free/premium strategy.
|
ECHO [ ] Category contains id, display_name, description, access, and icon_name.
|
||||||
|
ECHO [ ] Category id matches the file purpose.
|
||||||
|
ECHO [ ] Category title and description are user-facing.
|
||||||
ECHO [ ] Metadata counts match actual question counts.
|
ECHO [ ] Metadata counts match actual question counts.
|
||||||
ECHO [ ] All question IDs are unique.
|
ECHO [ ] Every question has category_id matching category.id.
|
||||||
ECHO [ ] All question texts are unique.
|
ECHO [ ] Every question has integer depth 1, 2, or 3.
|
||||||
ECHO [ ] No near-duplicate blocks.
|
ECHO [ ] Every question has an access value of free or premium.
|
||||||
|
ECHO [ ] Every question has a tags array.
|
||||||
|
ECHO [ ] All question IDs are unique inside the file.
|
||||||
|
ECHO [ ] All question texts are unique inside the file.
|
||||||
|
ECHO [ ] No near-duplicate blocks remain.
|
||||||
ECHO [ ] Type counts match metadata.
|
ECHO [ ] Type counts match metadata.
|
||||||
ECHO [ ] Free/premium counts match metadata.
|
ECHO [ ] Free/premium counts match metadata.
|
||||||
ECHO [ ] Depth values are valid.
|
|
||||||
ECHO [ ] Access values are valid.
|
|
||||||
ECHO [ ] Options have unique IDs inside each question.
|
ECHO [ ] Options have unique IDs inside each question.
|
||||||
ECHO [ ] Option IDs are neutral and clean.
|
ECHO [ ] Option IDs are neutral and clean.
|
||||||
|
ECHO [ ] Choice options mirror answer_config where required.
|
||||||
ECHO [ ] Scale labels are present when needed.
|
ECHO [ ] Scale labels are present when needed.
|
||||||
ECHO [ ] No malformed keys like m a x _ l e n g t h.
|
ECHO [ ] Written answer limits are present when needed.
|
||||||
|
ECHO [ ] No malformed keys.
|
||||||
ECHO [ ] No placeholder text.
|
ECHO [ ] No placeholder text.
|
||||||
ECHO [ ] No accidental old gendered IDs unless intentionally used.
|
ECHO [ ] The file is a complete pack, not a batch or patch artifact.
|
||||||
```
|
```
|
||||||
|
|
||||||
Recommended access values:
|
Valid access values:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
free
|
free
|
||||||
premium
|
premium
|
||||||
```
|
```
|
||||||
|
|
||||||
Recommended category access values:
|
Valid category access values:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
free
|
free
|
||||||
|
|
@ -738,15 +890,15 @@ premium
|
||||||
mixed
|
mixed
|
||||||
```
|
```
|
||||||
|
|
||||||
Recommended depth values:
|
Valid production depth values:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
light
|
1
|
||||||
medium
|
2
|
||||||
deep
|
3
|
||||||
```
|
```
|
||||||
|
|
||||||
Recommended question types:
|
Valid question types:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
written
|
written
|
||||||
|
|
@ -756,7 +908,41 @@ scale
|
||||||
this_or_that
|
this_or_that
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
## 12.6 Catalog-wide hard gate
|
||||||
|
|
||||||
|
Per-file validation is not enough.
|
||||||
|
|
||||||
|
Before rebuilding `app.db` or shipping question content, validate every production pack together.
|
||||||
|
|
||||||
|
The full catalog must have:
|
||||||
|
|
||||||
|
- globally unique question IDs
|
||||||
|
- globally unique exact question text
|
||||||
|
- no normalized duplicate question text after case and whitespace normalization
|
||||||
|
- no blocked near-duplicates across categories
|
||||||
|
- no production filename containing a patch or partial batch
|
||||||
|
- no category resolving to `unknown`
|
||||||
|
- no schema mismatch that would coerce or zero a field
|
||||||
|
|
||||||
|
A duplicate in two different category files is still a hard failure. The importer may abort the entire build, not merely skip the duplicate question.
|
||||||
|
|
||||||
|
When the same idea belongs in two packs, rewrite each prompt around its category-specific purpose rather than copying the same text.
|
||||||
|
|
||||||
|
## 12.7 Catalog impact reporting
|
||||||
|
|
||||||
|
When replacing or trimming an existing pack, report:
|
||||||
|
|
||||||
|
```text
|
||||||
|
old question count
|
||||||
|
new question count
|
||||||
|
net catalog change
|
||||||
|
old free/premium counts
|
||||||
|
new free/premium counts
|
||||||
|
whether the change is intentional and approved
|
||||||
|
```
|
||||||
|
|
||||||
|
A planned reduction from a legacy 250-question pack to a stronger 150-question pack is not a schema defect, but the live catalog shrink must be visible in the completion report.
|
||||||
|
|
||||||
|
|
||||||
# 13. Pack Structure Recommendation
|
# 13. Pack Structure Recommendation
|
||||||
|
|
||||||
|
|
@ -792,6 +978,23 @@ Question packs must not be written in one uninterrupted bulk-generation pass.
|
||||||
|
|
||||||
A normal category pack may contain up to 150 questions. Only a documented special product pack may exceed that limit. Regardless of final size, question creation must be divided into controlled batches so quality, continuity, counts, and repetition can be reviewed throughout the work.
|
A normal category pack may contain up to 150 questions. Only a documented special product pack may exceed that limit. Regardless of final size, question creation must be divided into controlled batches so quality, continuity, counts, and repetition can be reviewed throughout the work.
|
||||||
|
|
||||||
|
## 14.0 Production Replacement Rule
|
||||||
|
|
||||||
|
Batching controls the writing process; it does not redefine the production file.
|
||||||
|
|
||||||
|
During a multi-batch rewrite:
|
||||||
|
|
||||||
|
1. Keep the last complete production pack intact.
|
||||||
|
2. Draft and review each batch in a clearly labeled work artifact or controlled workspace.
|
||||||
|
3. Never overwrite the production filename with only the completed batches so far.
|
||||||
|
4. Never change the production file to a simple batch schema.
|
||||||
|
5. Merge reviewed batches into the complete `{ "category": ..., "questions": [...] }` pack.
|
||||||
|
6. Run per-file validation.
|
||||||
|
7. Run the catalog-wide duplicate and import validation.
|
||||||
|
8. Replace the production file only with the complete validated result.
|
||||||
|
|
||||||
|
A continuation note may say `Next ID: ...`; that does not make the partial JSON shippable.
|
||||||
|
|
||||||
## 14.1 Batch Size
|
## 14.1 Batch Size
|
||||||
|
|
||||||
Write exactly 25 new questions per batch.
|
Write exactly 25 new questions per batch.
|
||||||
|
|
@ -1104,40 +1307,51 @@ Even an approved mass rewrite must still use 25-question batches.
|
||||||
After all planned questions have been written or corrected:
|
After all planned questions have been written or corrected:
|
||||||
|
|
||||||
1. Validate the complete JSON file.
|
1. Validate the complete JSON file.
|
||||||
2. Confirm category metadata.
|
2. Confirm the top-level shape is `{ "category": ..., "questions": [...] }`.
|
||||||
3. Confirm all IDs.
|
3. Confirm the category object contains every required field.
|
||||||
4. Confirm all IDs are unique.
|
4. Confirm every question has `category_id` matching `category.id`.
|
||||||
5. Confirm all question texts are unique.
|
5. Confirm every question uses integer depth `1`, `2`, or `3`.
|
||||||
6. Confirm total question count.
|
6. Confirm every question has a tags array.
|
||||||
7. Confirm question-type distribution.
|
7. Confirm all IDs are unique inside the file.
|
||||||
8. Confirm free and premium distribution.
|
8. Confirm all question texts are unique inside the file.
|
||||||
9. Confirm depth distribution.
|
9. Confirm total question count.
|
||||||
10. Confirm targeting fields and tags.
|
10. Confirm question-type distribution.
|
||||||
11. Run exact duplicate checks.
|
11. Confirm free and premium distribution.
|
||||||
12. Run near-duplicate checks.
|
12. Confirm targeting fields.
|
||||||
13. Run repeated-stem and repeated-option checks.
|
13. Run exact duplicate checks inside the file.
|
||||||
14. Review subtopic coverage.
|
14. Run near-duplicate checks inside the file.
|
||||||
15. Review emotional-depth spacing.
|
15. Run repeated-stem and repeated-option checks.
|
||||||
16. Review mechanic distribution.
|
16. Review subtopic coverage.
|
||||||
17. Review free-to-premium progression.
|
17. Review emotional-depth spacing.
|
||||||
18. Read a representative sample from every section aloud.
|
18. Review mechanic distribution.
|
||||||
19. Mark every failure by ID and reason.
|
19. Review free-to-premium progression.
|
||||||
20. Patch only failed IDs.
|
20. Read a representative sample from every section aloud.
|
||||||
21. Re-run every affected validation and review check.
|
21. Mark every failure by ID and reason.
|
||||||
|
22. Patch only failed IDs.
|
||||||
|
23. Re-run every affected validation and review check.
|
||||||
|
24. Run the full-catalog global ID and question-text duplicate gate.
|
||||||
|
25. Confirm no production filename contains a patch manifest or partial batch.
|
||||||
|
26. Report the old count, new count, and net catalog change.
|
||||||
|
|
||||||
The pack may ship only when:
|
The pack may ship only when:
|
||||||
|
|
||||||
```bat
|
```bat
|
||||||
ECHO The JSON parses.
|
ECHO The JSON parses.
|
||||||
|
ECHO The production top-level shape is category plus questions.
|
||||||
|
ECHO Required category fields exist.
|
||||||
|
ECHO Every category_id matches category.id.
|
||||||
ECHO Metadata counts match actual counts.
|
ECHO Metadata counts match actual counts.
|
||||||
ECHO IDs are unique.
|
ECHO IDs are unique inside the pack and across the catalog.
|
||||||
ECHO Question texts are unique.
|
ECHO Question texts are unique inside the pack and across the catalog.
|
||||||
ECHO Free/premium counts match.
|
ECHO Free/premium counts match.
|
||||||
ECHO Type counts match.
|
ECHO Type counts match.
|
||||||
ECHO Depth values are valid.
|
ECHO Depth values are integers 1, 2, or 3.
|
||||||
|
ECHO Tags exist on every question.
|
||||||
ECHO Required targeting fields are valid.
|
ECHO Required targeting fields are valid.
|
||||||
|
ECHO The file is a complete pack, not a patch or partial batch.
|
||||||
ECHO No unresolved hard failures remain.
|
ECHO No unresolved hard failures remain.
|
||||||
ECHO No unacceptable repetition remains.
|
ECHO No unacceptable repetition remains.
|
||||||
|
ECHO The full catalog import gate passes.
|
||||||
ECHO The final sample sounds natural and enjoyable aloud.
|
ECHO The final sample sounds natural and enjoyable aloud.
|
||||||
ECHO The tone guide was followed.
|
ECHO The tone guide was followed.
|
||||||
```
|
```
|
||||||
|
|
@ -1268,15 +1482,22 @@ When Claude rewrites a pack:
|
||||||
|
|
||||||
```bat
|
```bat
|
||||||
ECHO Read the existing JSON first.
|
ECHO Read the existing JSON first.
|
||||||
ECHO Preserve required schema unless told otherwise.
|
ECHO Treat the active importer and Room contract as schema authority.
|
||||||
ECHO Preserve required counts unless told otherwise.
|
ECHO Preserve the production shape: category object plus questions array.
|
||||||
|
ECHO Use integer depth 1, 2, or 3.
|
||||||
|
ECHO Add category_id and tags to every production question.
|
||||||
|
ECHO Keep sex optional unless the pack-specific feature requires it.
|
||||||
|
ECHO Preserve required counts unless an approved rewrite changes them.
|
||||||
|
ECHO Never overwrite a production filename with a partial batch.
|
||||||
|
ECHO Never overwrite a production filename with a patch manifest.
|
||||||
|
ECHO Apply patches to the complete source pack and return the complete JSON.
|
||||||
ECHO Improve wording, variety, and product feel.
|
ECHO Improve wording, variety, and product feel.
|
||||||
ECHO Do not create repetitive template blocks.
|
ECHO Do not create repetitive template blocks.
|
||||||
ECHO Do not use weird phrasing.
|
ECHO Do not use weird phrasing.
|
||||||
ECHO Do not overuse therapy language.
|
ECHO Do not overuse therapy language.
|
||||||
ECHO Do not make sensitive prompts coercive.
|
ECHO Do not make sensitive prompts coercive.
|
||||||
ECHO Validate the final JSON.
|
ECHO Validate the final JSON per file and across the full catalog.
|
||||||
ECHO Report count totals.
|
ECHO Report old count, new count, and net catalog change.
|
||||||
ECHO Report any schema changes.
|
ECHO Report any schema changes.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -1284,11 +1505,16 @@ Claude should not say a file is done unless:
|
||||||
|
|
||||||
```bat
|
```bat
|
||||||
ECHO The JSON parses.
|
ECHO The JSON parses.
|
||||||
|
ECHO The file is a complete production pack.
|
||||||
|
ECHO The category object and questions array exist.
|
||||||
|
ECHO Required category and question fields exist.
|
||||||
ECHO The counts match.
|
ECHO The counts match.
|
||||||
ECHO IDs are unique.
|
ECHO Depth values are integers 1, 2, or 3.
|
||||||
ECHO Question texts are unique.
|
ECHO IDs are unique inside the pack and across the catalog.
|
||||||
|
ECHO Question texts are unique inside the pack and across the catalog.
|
||||||
ECHO Free/premium counts match.
|
ECHO Free/premium counts match.
|
||||||
ECHO Type counts match.
|
ECHO Type counts match.
|
||||||
|
ECHO The catalog import gate passes.
|
||||||
ECHO The tone guide was followed.
|
ECHO The tone guide was followed.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
# Closer Question Guide Correction Summary
|
||||||
|
|
||||||
|
## Why this update was required
|
||||||
|
|
||||||
|
The writing documents and the active importer disagreed about production depth and file shape.
|
||||||
|
|
||||||
|
The old guide allowed human-readable depth strings such as `light`, `medium`, and `deep`. The active Room-backed importer expects an integer field, so string depth can be read as `0`.
|
||||||
|
|
||||||
|
The old workflow also did not clearly forbid a partial batch or patch manifest from replacing a production JSON filename.
|
||||||
|
|
||||||
|
## Correct production authority
|
||||||
|
|
||||||
|
Production packs now require:
|
||||||
|
|
||||||
|
```text
|
||||||
|
top-level category object
|
||||||
|
top-level questions array
|
||||||
|
category.id
|
||||||
|
category.display_name
|
||||||
|
category.description
|
||||||
|
category.access
|
||||||
|
category.icon_name
|
||||||
|
question.category_id
|
||||||
|
question.type
|
||||||
|
question.text
|
||||||
|
question.depth as integer 1/2/3
|
||||||
|
question.access
|
||||||
|
question.tags array
|
||||||
|
```
|
||||||
|
|
||||||
|
`sex` remains optional and nullable unless a documented feature specifically reads it.
|
||||||
|
|
||||||
|
## Work artifacts
|
||||||
|
|
||||||
|
The corrected guides explicitly classify these as non-production:
|
||||||
|
|
||||||
|
```text
|
||||||
|
partial batches
|
||||||
|
simple-pack batch JSON
|
||||||
|
patch manifests
|
||||||
|
validation reports
|
||||||
|
coverage maps
|
||||||
|
continuation notes
|
||||||
|
apply scripts
|
||||||
|
review summaries
|
||||||
|
```
|
||||||
|
|
||||||
|
They must not overwrite production filenames or live in importer-scanned production locations.
|
||||||
|
|
||||||
|
## Daily pack rule
|
||||||
|
|
||||||
|
`daily_fun_multiple_choice_v3.json` must always remain the complete 511-question Daily Single-Choice Weekday System.
|
||||||
|
|
||||||
|
A JSON document containing only changed IDs is a patch manifest. It must be applied to the complete 511-question source; the resulting complete pack is the file that ships.
|
||||||
|
|
||||||
|
## Catalog-wide rule
|
||||||
|
|
||||||
|
Every production file must pass individually, then the complete catalog must pass together.
|
||||||
|
|
||||||
|
The catalog gate rejects:
|
||||||
|
|
||||||
|
- duplicate IDs across files
|
||||||
|
- duplicate exact or normalized question text across files
|
||||||
|
- blocked cross-category near-duplicates
|
||||||
|
- unknown categories
|
||||||
|
- partial or patch artifacts under production filenames
|
||||||
|
- depth or other field coercion caused by schema mismatches
|
||||||
|
|
||||||
|
## Immediate file remediation implied by the corrected guide
|
||||||
|
|
||||||
|
### `rebuilding_trust.json`
|
||||||
|
|
||||||
|
Do not ship the 25-question Batch 1 artifact.
|
||||||
|
|
||||||
|
Rebuild or complete the production pack using:
|
||||||
|
|
||||||
|
- full `{ "category": ..., "questions": [...] }` shape
|
||||||
|
- required category object
|
||||||
|
- `category_id` on every question
|
||||||
|
- integer depth `1`, `2`, or `3`
|
||||||
|
- tags arrays
|
||||||
|
- the approved final count and access/type distribution
|
||||||
|
|
||||||
|
Keep the last complete production version in place until the full replacement passes validation.
|
||||||
|
|
||||||
|
### `daily_fun_multiple_choice_v3.json`
|
||||||
|
|
||||||
|
Restore the complete 511-question source.
|
||||||
|
|
||||||
|
Apply the 17-ID patch to that complete source.
|
||||||
|
|
||||||
|
Ship the complete validated 511-question result, not the patch manifest.
|
||||||
|
|
||||||
|
### Cross-file duplicate
|
||||||
|
|
||||||
|
Reword one instance of:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Comfort first or solutions first?
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not allow identical question text in both Emotional Intimacy and Stress. Give each version a category-specific angle, then rerun the complete catalog gate.
|
||||||
|
|
||||||
|
## Catalog count changes
|
||||||
|
|
||||||
|
Intentional 250-to-150 pack reductions are allowed under the 150-question cap.
|
||||||
|
|
||||||
|
Every completion report must still show:
|
||||||
|
|
||||||
|
```text
|
||||||
|
old pack count
|
||||||
|
new pack count
|
||||||
|
net catalog change
|
||||||
|
free/premium change
|
||||||
|
approval or product rationale
|
||||||
|
```
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Closer Question Quality Checklist v8
|
# Closer Question Quality Checklist v9
|
||||||
|
|
||||||
**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)
|
||||||
|
|
||||||
|
|
@ -31,6 +31,27 @@ Reject any question that contains or strongly resembles:
|
||||||
|
|
||||||
These are therapy worksheet patterns.
|
These are therapy worksheet patterns.
|
||||||
|
|
||||||
|
## Production File Hard Checks
|
||||||
|
|
||||||
|
Run these before tone or content review.
|
||||||
|
|
||||||
|
Reject the file immediately if any item fails:
|
||||||
|
|
||||||
|
- top-level `category` object is missing
|
||||||
|
- top-level `questions` array is missing
|
||||||
|
- category is missing `id`, `display_name`, `description`, `access`, or `icon_name`
|
||||||
|
- any question is missing `id`, `category_id`, `type`, `text`, `depth`, `access`, or `tags`
|
||||||
|
- any `category_id` does not exactly match `category.id`
|
||||||
|
- any production `depth` is not the integer `1`, `2`, or `3`
|
||||||
|
- any `tags` value is missing or is not an array
|
||||||
|
- the file uses the old simple-pack shape
|
||||||
|
- the file is a partial batch
|
||||||
|
- the file is a patch manifest
|
||||||
|
- the production filename was overwritten by a work artifact
|
||||||
|
- the filename is being used as a substitute for a missing category
|
||||||
|
|
||||||
|
`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.
|
||||||
|
|
||||||
## 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:
|
||||||
|
|
@ -43,6 +64,7 @@ For every normal category pack, confirm before content review:
|
||||||
* free and premium counts match the documented pack plan
|
* free and premium counts match the documented pack plan
|
||||||
* all IDs and question texts are unique
|
* all IDs and question texts are unique
|
||||||
* no exact or near-duplicate blocks remain
|
* no exact or near-duplicate blocks remain
|
||||||
|
* old count, new count, and net catalog change are reported when replacing a legacy pack
|
||||||
|
|
||||||
## Daily Pack Hard Checks
|
## Daily Pack Hard Checks
|
||||||
|
|
||||||
|
|
@ -61,6 +83,9 @@ For the daily single choice weekday pack, confirm before content review:
|
||||||
* no duplicate IDs
|
* no duplicate IDs
|
||||||
* no duplicate question text
|
* no duplicate question text
|
||||||
* 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 is not a patch manifest containing only changed IDs
|
||||||
|
* every daily question uses integer depth and includes category_id, access, and tags
|
||||||
|
|
||||||
## Daily Fun Gate
|
## Daily Fun Gate
|
||||||
|
|
||||||
|
|
@ -241,6 +266,28 @@ Better:
|
||||||
Save me the best couch spot
|
Save me the best couch spot
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Catalog-Wide Hard Gate
|
||||||
|
|
||||||
|
Per-file checks are not enough.
|
||||||
|
|
||||||
|
Before rebuilding `app.db` or shipping any question change, scan every production pack together.
|
||||||
|
|
||||||
|
Reject the entire content build if any of these remain:
|
||||||
|
|
||||||
|
- duplicate question ID across files
|
||||||
|
- duplicate exact question text across files
|
||||||
|
- duplicate question text after case and whitespace normalization
|
||||||
|
- blocked near-duplicate prompts across related categories
|
||||||
|
- a category resolving to `unknown`
|
||||||
|
- a production filename containing a patch, partial batch, validation report, or other work artifact
|
||||||
|
- a depth value that would be coerced or read as `0`
|
||||||
|
- a question missing `category_id`
|
||||||
|
- a question missing its tags array
|
||||||
|
|
||||||
|
A duplicate across two otherwise valid packs still fails the whole catalog.
|
||||||
|
|
||||||
|
When two categories need similar ideas, rewrite each prompt around its own category purpose instead of copying identical text.
|
||||||
|
|
||||||
## Patch Discipline Checks
|
## Patch Discipline Checks
|
||||||
|
|
||||||
Before updating a daily pack, confirm the workflow is patch mode.
|
Before updating a daily pack, confirm the workflow is patch mode.
|
||||||
|
|
@ -253,10 +300,16 @@ Required:
|
||||||
* only marked IDs are edited
|
* only marked IDs are edited
|
||||||
* passing IDs are left unchanged
|
* passing IDs are left unchanged
|
||||||
* metadata is preserved unless metadata failed
|
* metadata is preserved unless metadata failed
|
||||||
|
* the patch manifest is stored outside importer-scanned production files
|
||||||
|
* the complete source pack remains available
|
||||||
|
* the patch is applied to the complete source pack before shipping
|
||||||
|
* the result is a complete production pack, not a patch-only deliverable
|
||||||
* the report lists marked count, patched count, and remaining flag count
|
* the report lists marked count, patched count, and remaining flag count
|
||||||
|
|
||||||
Reject the update if it rewrites passing questions without a mass rewrite exception.
|
Reject the update if it rewrites passing questions without a mass rewrite exception.
|
||||||
|
|
||||||
|
Also reject the update if a patch manifest or partial batch replaces the production JSON, even when the patch itself is valid JSON.
|
||||||
|
|
||||||
Mass rewrite exception requires:
|
Mass rewrite exception requires:
|
||||||
|
|
||||||
* more than 60 percent of the weekday or pack fails
|
* more than 60 percent of the weekday or pack fails
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# QUESTION_REWRITE_PLAN.md
|
# Closer Question Rewrite Plan v9
|
||||||
|
|
||||||
## 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.
|
||||||
|
|
||||||
|
## Production Replacement Rule
|
||||||
|
|
||||||
|
Writing in batches does not permit shipping a partial pack.
|
||||||
|
|
||||||
|
During a rewrite:
|
||||||
|
|
||||||
|
1. Keep the last complete production JSON in place.
|
||||||
|
2. Draft each batch in a controlled workspace or clearly labeled WIP artifact.
|
||||||
|
3. Do not save a 25-question batch under the production filename.
|
||||||
|
4. Do not switch the production file to the old simple-pack shape.
|
||||||
|
5. Merge reviewed batches into the full `{ "category": ..., "questions": [...] }` structure.
|
||||||
|
6. Use integer depth `1`, `2`, or `3`.
|
||||||
|
7. Include `category_id` and `tags` on every production question.
|
||||||
|
8. Validate the complete pack.
|
||||||
|
9. Validate the full catalog across every production file.
|
||||||
|
10. Replace the production file only after both gates pass.
|
||||||
|
|
||||||
|
A continuation note such as `Next ID: rt_026` proves that work is incomplete. It is not permission to replace the live pack with the first 25 questions.
|
||||||
|
|
||||||
|
A patch manifest is also a work artifact. It must never replace the source pack.
|
||||||
|
|
||||||
## Standard Pack Workflow
|
## Standard Pack Workflow
|
||||||
|
|
||||||
For normal category packs of up to 150 questions:
|
For normal category packs of up to 150 questions:
|
||||||
|
|
|
||||||
|
|
@ -1,153 +1,307 @@
|
||||||
# Closer Question Schema v7
|
# Closer Question Schema v8
|
||||||
|
|
||||||
**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) | [QUESTION_REWRITE_PLAN.md](QUESTION_REWRITE_PLAN.md) | [QUESTION_QUALITY_CHECKLIST.md](QUESTION_QUALITY_CHECKLIST.md)
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
This document defines the JSON schema, question types, validation rules, and required counts for Closer question packs.
|
This document defines the production JSON contract used by the active Closer importer and Room database.
|
||||||
|
|
||||||
For writing philosophy and tone, see `QUESTION_CONTENT_GUIDE.md`.
|
The active importer is authoritative. Planning labels and old authoring formats must not override the production contract.
|
||||||
|
|
||||||
For rewrite workflow and special pack exceptions, see `QUESTION_REWRITE_PLAN.md`.
|
## Production File Shape
|
||||||
|
|
||||||
|
Every production question pack must use this top-level shape:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"category": {
|
||||||
|
"id": "quality_time",
|
||||||
|
"display_name": "Quality Time",
|
||||||
|
"description": "Questions about being present and enjoying meaningful time together.",
|
||||||
|
"access": "mixed",
|
||||||
|
"icon_name": "schedule",
|
||||||
|
"schema_version": "question_v2",
|
||||||
|
"metadata": {}
|
||||||
|
},
|
||||||
|
"questions": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Required top-level keys:
|
||||||
|
|
||||||
|
```text
|
||||||
|
category
|
||||||
|
questions
|
||||||
|
```
|
||||||
|
|
||||||
|
Required `category` fields:
|
||||||
|
|
||||||
|
```text
|
||||||
|
id
|
||||||
|
display_name
|
||||||
|
description
|
||||||
|
access
|
||||||
|
icon_name
|
||||||
|
```
|
||||||
|
|
||||||
|
Optional category fields include:
|
||||||
|
|
||||||
|
```text
|
||||||
|
schema_version
|
||||||
|
metadata
|
||||||
|
```
|
||||||
|
|
||||||
|
Never rely on the filename to manufacture a missing category.
|
||||||
|
|
||||||
|
The old simple-pack form is not production-valid:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "rebuilding_trust",
|
||||||
|
"title": "Rebuilding Trust",
|
||||||
|
"count": 25,
|
||||||
|
"questions": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Required Question Fields
|
||||||
|
|
||||||
|
Every production question requires:
|
||||||
|
|
||||||
|
```text
|
||||||
|
id
|
||||||
|
category_id
|
||||||
|
type
|
||||||
|
text
|
||||||
|
depth
|
||||||
|
access
|
||||||
|
tags
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- `category_id` must exactly match the enclosing `category.id`
|
||||||
|
- `type` must be one of the supported type names
|
||||||
|
- `text` must be non-empty and catalog-unique
|
||||||
|
- `depth` must be the 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
|
||||||
|
- `sex` is optional and nullable for ordinary packs
|
||||||
|
- require `sex` only when a documented feature, such as feature-specific targeting, actually reads it
|
||||||
|
|
||||||
|
## Depth
|
||||||
|
|
||||||
|
Production depth is numeric:
|
||||||
|
|
||||||
|
| Meaning | Value |
|
||||||
|
|---|---:|
|
||||||
|
| Light | `1` |
|
||||||
|
| Medium | `2` |
|
||||||
|
| Deep | `3` |
|
||||||
|
|
||||||
|
Do not ship:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"depth": "light"
|
||||||
|
```
|
||||||
|
|
||||||
|
Ship:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"depth": 1
|
||||||
|
```
|
||||||
|
|
||||||
|
The strings `light`, `medium`, and `deep` may appear in coverage maps or review notes only.
|
||||||
|
|
||||||
## Question Types
|
## Question Types
|
||||||
|
|
||||||
Use these type names exactly:
|
Use these type names exactly:
|
||||||
|
|
||||||
* written
|
```text
|
||||||
* single_choice
|
written
|
||||||
* multi_choice
|
single_choice
|
||||||
* scale
|
multi_choice
|
||||||
* this_or_that
|
scale
|
||||||
|
this_or_that
|
||||||
|
```
|
||||||
|
|
||||||
Do not rename them unless the app code is updated first.
|
Do not rename them unless the app code and importer are updated first.
|
||||||
|
|
||||||
## multi_choice
|
## multi_choice
|
||||||
|
|
||||||
Select every option that applies.
|
Use when more than one answer can be true.
|
||||||
|
|
||||||
Use 4 to 6 options.
|
Use 4 to 6 options by default.
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"id": "quality_time_001",
|
||||||
|
"category_id": "quality_time",
|
||||||
"type": "multi_choice",
|
"type": "multi_choice",
|
||||||
"text": "What helps you feel relaxed on a date with me?",
|
"text": "What helps time together feel easy to enjoy?",
|
||||||
|
"depth": 1,
|
||||||
|
"access": "free",
|
||||||
|
"tags": ["presence", "comfort", "quality_time"],
|
||||||
"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": "easy_conversation", "text": "Easy conversation" },
|
||||||
{ "id": "easy_conversation", "text": "Easy conversation" }
|
{ "id": "clear_plan", "text": "Knowing the general plan" }
|
||||||
]
|
],
|
||||||
|
"answer_config": {
|
||||||
|
"options": [
|
||||||
|
{ "id": "no_rushing", "text": "Not feeling rushed" },
|
||||||
|
{ "id": "phones_away", "text": "Putting phones away" },
|
||||||
|
{ "id": "easy_conversation", "text": "Easy conversation" },
|
||||||
|
{ "id": "clear_plan", "text": "Knowing the general plan" }
|
||||||
|
],
|
||||||
|
"min_selections": 1,
|
||||||
|
"max_selections": 3
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
|
|
||||||
* Prompt should clearly allow multiple answers.
|
- top-level `options` are required
|
||||||
* Options should not shame either partner.
|
- `answer_config.options` must mirror the top-level options exactly
|
||||||
* Options should be practical, emotional, or playful when possible.
|
- selection bounds, when present, must be valid for the number of options
|
||||||
* Options must not overlap too much.
|
- options must directly and grammatically answer the prompt
|
||||||
|
- options must not overlap excessively
|
||||||
|
|
||||||
## single_choice
|
## single_choice
|
||||||
|
|
||||||
Select one best answer.
|
Use when the user selects one best answer.
|
||||||
|
|
||||||
Use 4 to 6 options.
|
Use 4 to 6 options by default.
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"id": "quality_time_002",
|
||||||
|
"category_id": "quality_time",
|
||||||
"type": "single_choice",
|
"type": "single_choice",
|
||||||
"text": "Which kind of date sounds best this week?",
|
"text": "Which low-key plan sounds best tonight?",
|
||||||
|
"depth": 1,
|
||||||
|
"access": "free",
|
||||||
|
"tags": ["low_energy", "easy_plan", "quality_time"],
|
||||||
"options": [
|
"options": [
|
||||||
{ "id": "cozy_at_home", "text": "Cozy at home" },
|
{ "id": "short_walk", "text": "A short walk" },
|
||||||
{ "id": "dinner_out", "text": "Dinner out" },
|
{ "id": "one_episode", "text": "One episode together" },
|
||||||
{ "id": "something_playful", "text": "Something playful" },
|
{ "id": "snack_and_talk", "text": "A snack and a talk" },
|
||||||
{ "id": "something_outside", "text": "Something outside" }
|
{ "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:
|
Rules:
|
||||||
|
|
||||||
* Options should be short.
|
- top-level `options` are required
|
||||||
* Options should not overlap too much.
|
- `answer_config.options` must mirror them exactly
|
||||||
* Options should sound like real choices.
|
- every option must be a plausible single best answer
|
||||||
* Every option must directly answer the prompt.
|
- options should be similar in effort, emotional weight, and intimacy
|
||||||
* Every option should be similar in weight.
|
|
||||||
|
|
||||||
## scale
|
## scale
|
||||||
|
|
||||||
Rate agreement, comfort, importance, confidence, or frequency.
|
Use for one measurable dimension such as comfort, closeness, energy, readiness, satisfaction, confidence, importance, or frequency.
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"id": "quality_time_003",
|
||||||
|
"category_id": "quality_time",
|
||||||
"type": "scale",
|
"type": "scale",
|
||||||
"text": "How much do you feel like we need a real date soon?",
|
"text": "How easy is it to be present when we finally get time together?",
|
||||||
"scale": {
|
"depth": 2,
|
||||||
|
"access": "free",
|
||||||
|
"tags": ["presence", "attention", "quality_time"],
|
||||||
|
"answer_config": {
|
||||||
"min": 1,
|
"min": 1,
|
||||||
"max": 5,
|
"max": 5,
|
||||||
"min_label": "Not much",
|
"min_label": "Not easy yet",
|
||||||
"max_label": "Very much"
|
"max_label": "Very easy"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
|
|
||||||
* Scale labels should be gentle and neutral.
|
- use `answer_config`, not a top-level `scale` object
|
||||||
* Do not make the low end sound bad or shameful.
|
- `min`, `max`, `min_label`, and `max_label` are required
|
||||||
* Scale questions should measure one thing only.
|
- labels must be gentle and neutral
|
||||||
|
- measure one thing only
|
||||||
|
|
||||||
## this_or_that
|
## this_or_that
|
||||||
|
|
||||||
Very fast playful questions.
|
Use for a fast two-option choice.
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"id": "quality_time_004",
|
||||||
|
"category_id": "quality_time",
|
||||||
"type": "this_or_that",
|
"type": "this_or_that",
|
||||||
"text": "Planned date or spontaneous date?"
|
"text": "Quiet time or playful time?",
|
||||||
|
"depth": 1,
|
||||||
|
"access": "free",
|
||||||
|
"tags": ["quick_choice", "mood", "quality_time"],
|
||||||
|
"options": [
|
||||||
|
{ "id": "quiet_time", "text": "Quiet time" },
|
||||||
|
{ "id": "playful_time", "text": "Playful time" }
|
||||||
|
],
|
||||||
|
"answer_config": {
|
||||||
|
"options": [
|
||||||
|
{ "id": "quiet_time", "text": "Quiet time" },
|
||||||
|
{ "id": "playful_time", "text": "Playful time" }
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
|
|
||||||
* Prompts should be quick.
|
- exactly two top-level options
|
||||||
* Avoid choices that imply one partner is wrong.
|
- `answer_config.options` must mirror them exactly
|
||||||
* Best used in fun, date, intimacy, home, and lifestyle packs.
|
- the choice must be quick and balanced
|
||||||
|
|
||||||
## written
|
## written
|
||||||
|
|
||||||
Reserved for questions where a short written response adds meaningful value.
|
Use only when a short written response adds meaningful value.
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"id": "quality_time_005",
|
||||||
|
"category_id": "quality_time",
|
||||||
"type": "written",
|
"type": "written",
|
||||||
"text": "What is one small thing I do that makes you feel cared for?",
|
"text": "What is one recent moment together that felt worth slowing down for?",
|
||||||
"answer": {
|
"depth": 2,
|
||||||
"max_length": 500
|
"access": "free",
|
||||||
|
"tags": ["recent_memory", "presence", "quality_time"],
|
||||||
|
"answer_config": {
|
||||||
|
"min_length": 1,
|
||||||
|
"max_length": 500,
|
||||||
|
"placeholder": "Write your answer..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Written questions require an `answer` configuration with `max_length`.
|
Rules:
|
||||||
|
|
||||||
Do not use written questions for basic preferences.
|
- 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
|
## Standard Category Packs
|
||||||
|
|
||||||
A normal category pack may contain **up to 150 questions**. The maximum is not a required count. Smaller packs are valid when they provide stronger coverage with less repetition.
|
A normal category pack may contain up to 150 questions.
|
||||||
|
|
||||||
For a full 150-question mixed pack, use this planning target:
|
A full 150-question mixed pack normally targets:
|
||||||
|
|
||||||
| Type | Count |
|
| Type | Count |
|
||||||
|---|---:|
|
|---|---:|
|
||||||
|
|
@ -157,27 +311,16 @@ For a full 150-question mixed pack, use this planning target:
|
||||||
| this_or_that | 10 |
|
| this_or_that | 10 |
|
||||||
| written | 5 |
|
| written | 5 |
|
||||||
|
|
||||||
Free and premium split:
|
Normal full-pack access target:
|
||||||
|
|
||||||
* 45 free
|
```text
|
||||||
* 105 premium
|
45 free
|
||||||
|
105 premium
|
||||||
|
```
|
||||||
|
|
||||||
Normal-pack rules:
|
The count is a ceiling, not a quota. Smaller packs are valid when documented and stronger.
|
||||||
|
|
||||||
* total question count must be 150 or fewer
|
## Daily Single-Choice Weekday System
|
||||||
* metadata counts must match the actual file
|
|
||||||
* written questions should normally total 0 to 5
|
|
||||||
* written questions are optional and must not be used as filler
|
|
||||||
* more than 5 written questions requires a documented pack-specific exception
|
|
||||||
* a smaller pack may use a different type mix when the mix is documented and fits the category
|
|
||||||
|
|
||||||
## Special Packs
|
|
||||||
|
|
||||||
Special product packs may exceed the normal 150-question maximum or override the standard type mix only when documented.
|
|
||||||
|
|
||||||
The daily single choice weekday pack is the current documented 511-question special exception.
|
|
||||||
|
|
||||||
## Daily Single Choice Weekday Pack
|
|
||||||
|
|
||||||
Pack id:
|
Pack id:
|
||||||
|
|
||||||
|
|
@ -185,167 +328,102 @@ Pack id:
|
||||||
daily_single_choice_weekly_v1
|
daily_single_choice_weekly_v1
|
||||||
```
|
```
|
||||||
|
|
||||||
Compatibility file name:
|
Compatibility filename:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
daily_fun_multiple_choice_v3.json
|
daily_fun_multiple_choice_v3.json
|
||||||
```
|
```
|
||||||
|
|
||||||
Target counts — weekday pack (frozen; do not change these 500):
|
Required production totals:
|
||||||
|
|
||||||
| Field | Count |
|
| Field | Count |
|
||||||
|---|---:|
|
|---|---:|
|
||||||
| weekday total | 500 |
|
| weekday questions | 500 |
|
||||||
| weekday free | 75 |
|
| wildcard questions | 11 |
|
||||||
| weekday premium | 425 |
|
| total questions | 511 |
|
||||||
| single_choice | 500 |
|
| free | 86 |
|
||||||
|
| premium | 425 |
|
||||||
|
| single_choice | 511 |
|
||||||
|
|
||||||
Plus the wildcard add-on (surprise days; see `DAILY_SINGLE_CHOICE_WEEKDAY_SYSTEM.md` → Wildcard Mode).
|
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.
|
||||||
The wildcard pool size must stay **coprime with 10** (the wildcard-day spacing), so 11, not 10/12:
|
|
||||||
|
|
||||||
| Field | Count |
|
Every daily question must include:
|
||||||
|---|---:|
|
|
||||||
| wildcard free | 11 |
|
|
||||||
|
|
||||||
**New pack totals (weekday + wildcard):** 511 total · 86 free · 425 premium · 511 single_choice.
|
- `category_id`
|
||||||
|
- integer `depth`
|
||||||
|
- `access`
|
||||||
|
- `tags`
|
||||||
|
- top-level `options`
|
||||||
|
- mirrored `answer_config.options`
|
||||||
|
|
||||||
These counts do not apply to standard category packs.
|
Every weekday question must have exactly one weekday tag.
|
||||||
|
|
||||||
## Daily Pack Metadata Example
|
Every wildcard must have `mode_wildcard` and `daily_wildcard` and no weekday tag.
|
||||||
|
|
||||||
```json
|
A daily patch manifest is not the daily production pack.
|
||||||
{
|
|
||||||
"id": "daily_single_choice_weekly_v1",
|
## Work Artifacts Are Not Packs
|
||||||
"title": "Daily Single Choice",
|
|
||||||
"access": "mixed",
|
The following must not be placed under a production filename or inside an importer-scanned production directory:
|
||||||
"description": "One weekday themed single choice question per day, plus wildcard surprise days.",
|
|
||||||
"count": 511,
|
```text
|
||||||
"free_count": 86,
|
partial batch JSON
|
||||||
"premium_count": 425,
|
simple-pack batch JSON
|
||||||
"question_type_policy": "single_choice_only",
|
patch manifest
|
||||||
"review_policy": "weekday_batch_loop_required",
|
marked-fix list
|
||||||
"patch_policy": "fix_marked_ids_only",
|
validation report
|
||||||
"mass_rewrite_policy": "requires_over_60_percent_shared_failure",
|
coverage map
|
||||||
"content_policy": "fun_first_daily_game_questions",
|
continuation note
|
||||||
"research_note": "Use relationship question research patterns, not copied prompts.",
|
apply script
|
||||||
"questions": []
|
review summary
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If the compatibility filename `daily_fun_multiple_choice_v3.json` is used, the metadata should still make clear that the content is `single_choice` only.
|
A patch should be clearly named, for example:
|
||||||
|
|
||||||
|
```text
|
||||||
## Daily Content Metadata
|
daily_fun_multiple_choice_v3.patch.json
|
||||||
|
|
||||||
Daily packs should include metadata that makes the content standard impossible to miss.
|
|
||||||
|
|
||||||
Recommended fields:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"question_type_policy": "single_choice_only",
|
|
||||||
"review_policy": "weekday_batch_loop_required",
|
|
||||||
"patch_policy": "fix_marked_ids_only",
|
|
||||||
"mass_rewrite_policy": "requires_over_60_percent_shared_failure",
|
|
||||||
"content_policy": "fun_first_daily_game_questions",
|
|
||||||
"research_note": "Use relationship question research patterns, not copied prompts."
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
These fields are not a replacement for validation. They document the intended behavior so future rewrites do not quietly turn the pack into chores, therapy, or generic wellness sludge.
|
It must be applied to the complete production source, and the complete resulting pack must pass validation before shipping.
|
||||||
|
|
||||||
## Daily Question Object Example
|
## Per-File Validation Requirements
|
||||||
|
|
||||||
```json
|
Before shipping a pack:
|
||||||
{
|
|
||||||
"id": "daily_monday_001",
|
|
||||||
"type": "single_choice",
|
|
||||||
"text": "What tiny date move sounds best tonight?",
|
|
||||||
"depth": "light",
|
|
||||||
"access": "free",
|
|
||||||
"sex": "neutral",
|
|
||||||
"tags": ["daily_monday_mood_check"],
|
|
||||||
"options": [
|
|
||||||
{ "id": "dessert_couch", "text": "Dessert on the couch" },
|
|
||||||
{ "id": "kitchen_dance", "text": "A two-song kitchen dance" },
|
|
||||||
{ "id": "snack_walk", "text": "A short walk with snacks" },
|
|
||||||
{ "id": "movie_pick", "text": "A ridiculous movie pick" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Daily Single Choice Content Rules
|
- JSON parses
|
||||||
|
- top-level `category` object exists
|
||||||
|
- top-level `questions` array exists
|
||||||
|
- required category fields exist
|
||||||
|
- every question has every required field
|
||||||
|
- every `category_id` matches `category.id`
|
||||||
|
- 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
|
||||||
|
- 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
|
||||||
|
|
||||||
Daily options must be:
|
## Catalog-Wide Validation Requirements
|
||||||
|
|
||||||
* complete answers to the prompt
|
Before rebuilding `app.db` or shipping question content, validate all production files together.
|
||||||
* short and natural
|
|
||||||
* similar in weight
|
|
||||||
* fun, sweet, flirty, silly, or date-like when possible
|
|
||||||
* easy to pick in under 10 seconds
|
|
||||||
|
|
||||||
Daily options must not be mostly chores, household admin, or therapy phrasing.
|
The full catalog must have:
|
||||||
|
|
||||||
Reject options centered on clean counters, bills, laundry, dishes, appointment scheduling, bedtime planning, saved blankets, or oddly specific domestic logistics unless the question clearly frames the option as playful and worth choosing.
|
- 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
|
||||||
|
|
||||||
Schema validation only proves the file can be parsed. It does not prove the content is good.
|
A duplicate question in two different files is a hard failure and may abort the entire import.
|
||||||
|
|
||||||
Daily packs must also pass the fun gate in `QUESTION_QUALITY_CHECKLIST.md` and the loop in `QUESTION_REWRITE_PLAN.md`.
|
Per-file success does not override a catalog-wide failure.
|
||||||
|
|
||||||
## Weekday Tags
|
|
||||||
|
|
||||||
Daily questions must include the correct weekday tag:
|
|
||||||
|
|
||||||
* daily_monday_mood_check
|
|
||||||
* daily_tuesday_tiny_win
|
|
||||||
* daily_wednesday_real_one
|
|
||||||
* daily_thursday_laugh
|
|
||||||
* daily_friday_flirty
|
|
||||||
* daily_saturday_side_quest
|
|
||||||
* daily_sunday_slow_burn
|
|
||||||
|
|
||||||
If app code still uses older mode tags, include compatibility tags as needed without removing the new weekday tag.
|
|
||||||
|
|
||||||
## Depth
|
|
||||||
|
|
||||||
Documented depth values:
|
|
||||||
|
|
||||||
* light
|
|
||||||
* medium
|
|
||||||
* deep
|
|
||||||
|
|
||||||
Do not use numeric depth values unless the active app import code still requires numeric depth for compatibility.
|
|
||||||
|
|
||||||
If compatibility requires numeric depth, document the exception in the pack README and fix the import code later.
|
|
||||||
|
|
||||||
## Access
|
|
||||||
|
|
||||||
Valid access values:
|
|
||||||
|
|
||||||
* free
|
|
||||||
* premium
|
|
||||||
|
|
||||||
## Validation Requirements
|
|
||||||
|
|
||||||
Before shipping any pack:
|
|
||||||
|
|
||||||
* JSON must parse
|
|
||||||
* all IDs must be unique
|
|
||||||
* all question text must be unique
|
|
||||||
* every type must be valid
|
|
||||||
* every required field must exist
|
|
||||||
* option IDs must be unique within a question
|
|
||||||
* options must directly answer the prompt
|
|
||||||
* access values must be valid
|
|
||||||
* depth values must match current app compatibility rules
|
|
||||||
* normal category packs must contain no more than 150 questions
|
|
||||||
* normal category packs should contain no more than 5 written questions unless an exception is documented
|
|
||||||
* special packs must match their documented counts and exception rules
|
|
||||||
|
|
||||||
## Content Validation Reminder
|
|
||||||
|
|
||||||
Do not confuse schema validity with quality.
|
|
||||||
|
|
||||||
A file can be valid JSON and still be bad content.
|
|
||||||
|
|
||||||
Daily packs require schema validation plus human content review.
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
12022
seed/questions/fun.json
12022
seed/questions/fun.json
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue