| diataxis_type | how-to |
|---|---|
| diataxis_goal | Add new questions, modules, or branching rules to the voice question bank |
-
Open the module file in
question-bank/modules/. Files follow the patternMxx-<slug>.json(e.g.,M02-voice-personality.json). -
Append a new question object to the top-level JSON array. Every question requires these fields:
{ "question_id": "M02-Q11", "module_id": "M02", "module_name": "Voice Personality & Values", "text": "Your question text here.", "type": "forced_choice", "metadata": { "self_reportability_tier": 2, "estimated_seconds": 20, "format_category": "forced_choice" } } -
Set
question_idusing the patternMxx-Qnnwherexxis the module number andnnis the next sequential question number within that module. For sub-questions, append a lowercase letter (e.g.,M02-Q11a). Semantic differential questions useSD-nn. -
Set
typeto one of the allowed values:likert,forced_choice,semantic_differential,scenario,open_ended,projective,writing_sample,calibration,select,select_multiple,behavioral,process_narration. -
If the question type requires response options (
likert,forced_choice,select,select_multiple,semantic_differential), add anoptionsarray with at least two entries, each containingvalueandlabel:"options": [ { "value": "clarity", "label": "Being clear and easily understood" }, { "value": "authenticity", "label": "Sounding genuine and true to myself" } ]
-
If the question feeds into scoring, add a
scoringblock:"scoring": { "dimensions": ["voice_values", "communication_priority"], "weights": { "voice_values": 0.9, "communication_priority": 0.8 }, "scoring_map": { "clarity": { "voice_values": 3, "communication_priority": 5 }, "authenticity": { "voice_values": 5, "communication_priority": 3 } } }
Weight values range from 0 to 1. Scoring map keys must match option
valuefields exactly. -
If you want to add optional metadata, include any of these fields inside
metadata:finding_refs-- array of research finding reference IDs (e.g.,["PL-001", "EF-003"])bias_mitigation-- description of the bias mitigation strategy appliedbranching.required_branches-- writer type branches required for this question to appear (use["*"]for all)branching.excluded_branches-- writer type branches for which this question is skippedposition.module_sequence-- ordinal position within the moduleposition.funnel_stage-- one ofscreening,general,core,deep_dive,wrap_up
-
If the question should trigger a conditional follow-up, add a
follow_upblock:"follow_up": { "condition": "some_value", "question_id": "M02-Q11a" }
The condition can be a string, number, or boolean that matches a response value.
-
Choose a module ID. Use the next available
Mxxnumber (check existing files inquestion-bank/modules/). -
Create the module file as
question-bank/modules/Mxx-<descriptive-slug>.json. The filename prefix before the first letter-bearing hyphen segment must match the module ID. For example, moduleM13becomesM13-my-new-module.json. -
Structure the file as a JSON array of question objects:
[ { "question_id": "M13-Q01", "module_id": "M13", "module_name": "My New Module", "text": "First question text.", "type": "likert", "options": [ ... ], "metadata": { "self_reportability_tier": 1, "estimated_seconds": 15, "format_category": "likert", "position": { "module_sequence": 1, "funnel_stage": "core" } } } ] -
Register the module in
question-bank/branching/module-sequence.json. Add the module ID to the appropriate phase'smodulesarray:{ "phase": 4, "name": "Contextual", "modules": ["M07", "M08", "M13"] } -
Decide whether the module is core or branch-activated. Add the module ID to either the
core_modulesorbranch_activated_modulesarray at the bottom ofmodule-sequence.json. Core modules are always administered; branch-activated modules can be skipped based on the respondent's writer type path. -
If you need an engagement reset before or after the module, add an entry to the phase's
engagement_reset_pointsarray:{ "position": "after_M13", "type": "engagement_reset", "description": "Reset between M13 and next module" } -
Update the
total_estimated_minutesfield inmodule-sequence.jsonto account for the new module's expected duration.
-
Open
question-bank/branching/deep-dive-triggers.json. -
Add a new key under
triggerswith a descriptive name:"high_precision_drive": { "source_module": "M09", "condition": { "metric": "precision_score", "operator": ">", "threshold": 80 }, "injected_items": ["M09-DD01", "M09-DD02"], "description": "When precision score exceeds 80, inject deep-dive items probing precision preferences", "injection_point": "after_M09" }
-
The
conditionobject supports these fields:metric-- the score or measurement to evaluateoperator-- comparison operator:>,>=,<,<=,==threshold-- numeric threshold value- For semantic differential triggers, also include
sd_pairnaming the pair - For cross-score comparisons, include
comparison, andoperandsarray
-
Set
injection_pointto control when the deep-dive questions appear:after_Mxx-- inject after the named module completesimmediate-- inject immediately when the condition fires
-
If you want the trigger to apply only to specific dimensions, add
applies_to_dimensions(use"all"for all dimensions). -
Create the deep-dive question items (e.g.,
M09-DD01) in the source module's JSON file following the standard question format. Use theDDinfix in the question ID to distinguish deep-dive items from standard questions. -
Check the
notessection at the bottom ofdeep-dive-triggers.jsonfor session constraints:max_deep_dives_per_sessioncaps total deep-dive injections (currently 5)deep_dive_time_budget_minutescaps total deep-dive time (currently 3 minutes)injection_prioritydefines processing order when multiple triggers fire simultaneously
-
Open
question-bank/scoring/dimension-item-mapping.json. -
Find the target dimension under
gold_standard_dimensionsorgap_dimensions. -
Add the new question ID to the dimension's
contributing_itemsobject, under the appropriate module key:"formality": { "contributing_items": { "M03": ["M03-Q01", "M03-Q02", "M03-Q03", "M03-Q04", "M03-Q05", "M03-Q06", "M03-Q07"], "SD": ["formal_casual", "accessible_specialized"] }, "total_items": 9, "min_items_for_score": 5 }
-
Update
total_itemsto reflect the new count. -
If you are introducing a new dimension, add a new key under the appropriate section (
gold_standard_dimensionsfor primary output dimensions,gap_dimensionsfor supplementary dimensions). Includecontributing_items,total_items,min_items_for_score, anddescription. -
If the new dimension uses semantic differential pairs, also add the pair name to the
semantic_differential_pairsarray at the bottom of the file.
-
Open
question-bank/scoring/scoring-weights.json. -
Under
dimension_weights, find the target dimension and add the new question ID with its weight:"formality": { "items": { "M03-Q01": 1.0, "M03-Q07": 1.0 }, "sd_pairs": { ... }, "self_reportability_tier": 1 }
Items carry equal weight (1.0) by default. Adjust only if the item should contribute more or less than standard items.
-
If you are adding a new dimension, create a new key under
dimension_weightswith itsitems,sd_pairs(if applicable), andself_reportability_tier. The tier determines how self-report vs. projective items are weighted in the integration formula. -
The integration formula for final dimension scores is:
dimension_score = (0.7 * module_items_mean) + (0.3 * sd_normalized_mean). These weights are configured in theintegration_formulasection. If the new dimension has no semantic differential component, the full score comes from module items alone.
-
Run the validation script from the repository root:
./scripts/validate-question-bank.sh
This checks:
- All module JSON files parse correctly
- Each question's
module_idmatches its filename prefix - No duplicate
question_idvalues exist across modules
-
The script outputs a summary table showing each module file, its module ID, question count, and status. A successful run ends with
All validations passed. -
To additionally check JSON Schema compliance, validate individual module files against the schema:
# Using ajv-cli npx ajv validate -s question-bank/schemas/question.schema.json \ -d "question-bank/modules/M02-voice-personality.json" \ --spec=draft2020 --all-errors # Using check-jsonschema check-jsonschema --schemafile question-bank/schemas/question.schema.json \ question-bank/modules/M02-voice-personality.json
-
Verify cross-file consistency manually:
- Every question ID referenced in
dimension-item-mapping.jsonexists in a module file - Every question ID referenced in
scoring-weights.jsonexists in a module file - Every module ID in
module-sequence.jsonhas a corresponding module file - Deep-dive trigger
injected_itemsreference valid question IDs
- Every question ID referenced in
-
Update the schema. In
question-bank/schemas/question.schema.json, add the new type to thetypeproperty'senumarray:"enum": [ "likert", "forced_choice", ... "your_new_type" ]
-
Define option requirements. If the new type requires response options, no schema change is needed -- the
optionsarray is already optional. If the new type requires a specific option structure different from{value, label}, you will need to extend theoptionsitem schema or add conditional validation usingif/thenblocks. -
Update the renderer. The interview engine's renderer must know how to present the new type. Add a rendering handler that maps the type string to the appropriate UI component. See the existing renderer implementations for the pattern.
-
Update scoring. If the new type produces responses that score differently from existing types:
- Define how response values map to numeric scores in each question's
scoring.scoring_map - If the type produces multi-valued or structured responses (unlike a single selection), update the scoring engine to handle the new response shape
- Define how response values map to numeric scores in each question's
-
Add questions using the new type. Follow the standard process in "How to add a question to an existing module" above, setting
typeto your new type string. -
Update format rotation. Set
metadata.format_categoryon questions using the new type. The engine uses this to prevent consecutive questions of the same format. Choose a category name that matches the type or groups it with similar formats.
After making changes, confirm the following:
-
./scripts/validate-question-bank.shexits withAll validations passed. - JSON Schema validation passes for every modified module file
- New question IDs follow the
Mxx-Qnnpattern and are unique across all modules - Every question referenced in
dimension-item-mapping.jsonandscoring-weights.jsonexists in a module file - Every module referenced in
module-sequence.jsonhas a corresponding file inquestion-bank/modules/ - Deep-dive trigger
injected_itemsreference question IDs that exist -
total_itemscounts indimension-item-mapping.jsonare accurate -
total_estimated_minutesinmodule-sequence.jsonreflects any added module time
PARSE ERROR from validation script
: The module file contains invalid JSON. Run python3 -m json.tool question-bank/modules/Mxx-slug.json to locate the syntax error.
MODULE MISMATCH from validation script
: A question's module_id does not match the filename prefix. The filename M03-formality-register.json expects all questions to have "module_id": "M03".
DUPLICATE ID from validation script
: Two questions share the same question_id. Search across all module files for the ID and rename one of them.
Schema validation fails on question_id pattern
: The ID must match ^(M\d{2}-[A-Z]{1,2}\d{2}[a-z]?|SD-\d{2})$. Common mistakes: lowercase module prefix, missing hyphen, three-digit question number. Valid: M02-Q01, M02-Q01a, SD-01. Invalid: m02-Q01, M02Q01, M02-Q001.
Schema validation fails on metadata required fields
: Every question must include self_reportability_tier (integer 1-4), estimated_seconds (integer >= 1), and format_category (string) in its metadata block.
Deep-dive trigger does not fire
: Check that the source_module matches the module where the condition metric is calculated. Verify the operator and threshold values. Check session limits in the notes section -- the trigger may be suppressed by max_deep_dives_per_session or deep_dive_time_budget_minutes.
New module not appearing in the interview
: Confirm the module ID is listed in the correct phase in module-sequence.json and appears in either core_modules or branch_activated_modules. If branch-activated, verify the respondent's writer type path includes the required branch.
See question.schema.json for full schema reference. See the explanation docs for design rationale behind the module structure, scoring integration formula, and self-reportability tiers.