Add JSON-configured dynamic forms for regular task interviews
Problem
Regular task interviews are currently defined as client-bundled templates and question lists. Adding or substantially changing an interview therefore requires an RS App code change and deployment. Course administrators also cannot define a complete interview—sections, randomized pools, guidance, response fields, localization, and scoring—as one versioned artifact.
For the redesigned JS/TS Web Dev course, an interview such as I3 — JavaScript, Fetch/HTTP, async, errors, and project ownership needs to:
- draw theory and practical items from curated pools using quotas;
- guide the Interviewer through project-based ownership evidence without requiring advance repository study;
- support fixed and observational sections;
- provide Interviewer-only suggested answers and evidence guidance;
- work in English or Russian;
- calculate one structured numeric result;
- remain immutable once started.
Other courses should be able to define different sections and rubrics without another hard-coded form.
Current implementation
The existing implementation provides useful rendering and result-storage building blocks, but definitions are static:
- client/src/data/interviews/index.ts registers templates bundled with the client.
- client/src/data/interviews/types.ts models a fixed category/question structure.
- client/src/data/interviews/templateValidator.ts validates only client-side template IDs.
- client/src/modules/Interviews/pages/StageInterviewFeedback/StepForm.tsx renders the existing form flow.
- common/models/interview.ts and nestjs/src/models/taskInterviewResult.ts already store submitted question/form data.
- Interview tasks already have a template reference, which can become the link to a published definition version.
The required change is therefore not just a larger client template. Definitions, generation, authorization, versioning, and scoring must become server-owned.
Proposed administrator workflow
Prepare and review one interview-definition.json in the course repository
↓
Upload the complete JSON in RS App
↓
Server validates it and the admin previews generated examples
↓
Admin publishes an immutable definition version
↓
Interview task references that published version
↓
Interviewer clicks Start Interview and chooses en or ru
↓
Server selects items and atomically freezes this student's packet
↓
Interviewer conducts and scores the interviewUploading a replacement creates a new version. It must never change packets already generated from an older version.
For the MVP, JSON file upload is sufficient. An in-app question editor is not required. Import from a raw GitHub URL and CSV-to-JSON authoring tools can be follow-ups; CSV should not be the authoritative format because the full definition is nested.
Definition model
Publish a documented, versioned JSON Schema. A complete definition contains:
- metadata: stable ID, title, linked project, expected duration;
- supported languages and default language;
- ordered dynamic sections;
- item pools and selection rules;
- Interviewer-only answer/evidence guidance;
- configured response fields;
- ordered scoring dimensions, weights, 0–5 anchors, and calculation rule;
- feedback requirements.
Illustrative shape:
{
"schemaVersion": 1,
"interview": {
"id": "i3",
"title": {
"en": "I3 — JS, Fetch/HTTP, async, errors, and ownership",
"ru": "I3 — JS, Fetch/HTTP, асинхронность, ошибки и владение проектом"
},
"linkedProject": "P3",
"expectedDurationMinutes": 45,
"languages": {
"available": ["en", "ru"],
"default": "en",
"selectedBy": "interviewer-on-start"
}
},
"sections": [
{
"id": "theory",
"title": { "en": "Theory", "ru": "Теория" },
"behavior": "random-items",
"selection": {
"count": 6,
"quotas": [
{ "field": "difficulty", "value": "easy", "count": 2 },
{ "field": "difficulty", "value": "medium", "count": 2 },
{ "field": "difficulty", "value": "hard", "count": 2 }
],
"coverage": [
{
"field": "topic",
"values": ["javascript", "http", "async", "errors"],
"minimumEach": 1
}
],
},
"items": [
{
"id": "i3-theory-001",
"kind": "theory-question",
"topic": "http",
"difficulty": "easy",
"prompt": {
"en": "What happens when fetch receives an HTTP 404 response?",
"ru": "Что происходит, если fetch получает HTTP-ответ 404?"
},
"suggestedAnswer": {
"en": "The promise normally fulfils with a Response. Application code must inspect response.ok or status.",
"ru": "Промис обычно выполняется с Response. Код приложения должен проверить response.ok или status."
},
"answerGuide": {
"minimum": {
"en": ["Distinguishes an HTTP failure response from a network rejection."],
"ru": ["Отличает HTTP-ответ с ошибкой от сетевого отклонения промиса."]
}
}
}
]
},
{
"id": "project-ownership",
"title": { "en": "Project ownership", "ru": "Владение проектом" },
"behavior": "project-evidence",
"selection": { "count": 2 },
"items": [
{
"id": "i3-ownership-http-01",
"kind": "project-evidence",
"projectRequirement": "P3-HTTP-01",
"prompt": {
"en": "Ask the student to locate and trace the implementation of this requirement, then make a bounded change.",
"ru": "Попросите студента найти и проследить реализацию требования, затем внести ограниченное изменение."
},
"expectedBehavior": {
"en": "The submitted application implements P3-HTTP-01.",
"ru": "В представленном приложении реализовано P3-HTTP-01."
},
"observe": {
"en": ["Locates the code", "Explains the flow", "Changes it", "Explains verification"],
"ru": ["Находит код", "Объясняет поток", "Изменяет его", "Объясняет проверку"]
}
}
],
"responseFields": [
{
"id": "evidence-notes",
"type": "textarea",
"label": { "en": "Evidence notes", "ru": "Заметки о подтверждении" },
"appliesTo": "each-item",
"required": true
}
]
}
],
"outcome": {
"scoreScale": { "minimum": 0, "maximum": 5 },
"dimensions": [
{ "id": "theory", "title": { "en": "Theory and mental models", "ru": "Теория и ментальные модели" }, "weight": 20 },
{ "id": "live-work", "title": { "en": "Live modification/debugging", "ru": "Изменение или отладка кода" }, "weight": 25 },
{ "id": "ai-judgment", "title": { "en": "AI usage and judgment", "ru": "Использование ИИ и инженерная оценка" }, "weight": 10 },
{ "id": "technical-communication", "title": { "en": "Technical communication", "ru": "Техническая коммуникация" }, "weight": 15 },
{ "id": "professional-interaction", "title": { "en": "Professional interaction", "ru": "Профессиональное взаимодействие" }, "weight": 10 },
{ "id": "project-ownership", "title": { "en": "Project ownership", "ru": "Владение проектом" }, "weight": 20 }
],
"calculation": { "type": "weighted-percentage", "rounding": "one-decimal" },
"feedback": { "required": true }
}
}Supported dynamic behavior
The form renderer should support a small, explicit set of section behaviors rather than arbitrary executable JSON:
- random-items — server selects items using count, quota, and coverage rules.
- fixed-items — every configured prompt is shown.
- project-evidence — selects prompts tied to stable project requirement IDs and collects evidence notes.
- observation — collects evidence and/or anchored ratings for behavior observed across the interview.
Supported response controls should include:
- read-only instructions;
- Interviewer-only guidance;
- checkbox;
- single choice;
- number;
- anchored 0–5 scale;
- short text and multiline notes.
Each response field declares whether it applies once to the section or once to every selected item; omission defaults to section-level. This permits per-question evidence notes without hard-coding them into a specific section type.
Section IDs such as theory or ownership have no hard-coded semantic meaning. Authors can add, remove, rename, reorder, and combine sections using supported behaviors and controls without an RS App release. A new renderer capability—such as a code runner, diagram editor, or file upload—still requires an RS App change and a new schema version.
Localization and form generation
- Every human-readable field uses declared language keys; identifiers, tags, weights, and rules remain language-neutral.
- Publication fails if any required field is missing any declared language.
- Do not silently mix languages through per-field fallback.
- Start Interview first requires the Interviewer to choose a declared language.
- After confirmation, the server selects items and atomically stores a frozen packet before returning the form.
- The packet stores definition ID/version/hash, chosen language, selected item IDs, Interviewer/student/interview IDs, attempt, generation time, and the complete language-resolved form snapshot (including selected prompts, guidance, controls, and outcome configuration). It must not be reconstructed later from the source definition.
- Reload/resume returns the same packet.
- Language does not influence item selection or scoring.
Guidance and access control
Theory items may contain an Interviewer-only suggested answer plus:
- minimum evidence;
- strong-answer evidence;
- acceptable alternatives;
- important misconceptions;
- follow-up questions.
Suggested answers are reference guidance, not scripts and not keyword-matching rules. Practical/project items use expected behavior, observable evidence, success criteria, bounded changes, and allowed hints.
Backend authorization must ensure that student-facing APIs never expose:
- the complete item bank or unselected items;
- Interviewer-only suggested answers;
- answer/evidence guidance, misconceptions, scoring hints, or allowed-hint metadata;
- exact closed question text through the ordinary post-interview result view.
Admin/moderator access to frozen packets must remain available for audit and appeals.
Configured outcome feedback is student-facing after the interview is completed. In the conducting form, label the field Feedback for the student and show a localized notice beside it: The student will see this feedback after you submit the interview. The disclosure must be visible at the point of entry, not only in documentation. The definition controls whether feedback is required; it does not provide a switch for exposing closed questions, answer guidance, or internal evidence notes.
Scoring and stored result
Each configured dimension is rated from 0 to 5. The server calculates one final interview score:
final score = Σ((dimension level / 5) × dimension weight)For the example above, weights total 100 and the result is a percentage from 0 to 100.
The stored result must contain:
- packet reference and immutable definition metadata;
- selected dimension level 0–5 for every dimension;
- optional evidence notes per dimension;
- calculated final score;
- configured overall feedback;
- submitter and timestamps.
Student result card
The submitted feedback must be visible directly on the student's completed interview card on the course Interviews page (/course/student/interviews), not only in a profile modal.
Use the existing card structure shown for completed interviews:
- keep the final numeric score/status in the card's Result area;
- replace or extend the large informational message panel below it with a Feedback section containing the submitted outcome feedback;
- preserve line breaks and render feedback safely as text for the MVP;
- if a definition has no feedback field, or an optional field has no submitted value, do not render an empty Feedback heading;
- the profile result view may reuse the same student-safe DTO and display the feedback as a secondary access point.
Only the assessed student and already-authorized course roles may retrieve this feedback. The result-card DTO must not include selected closed prompts, suggested answers, answer/evidence guidance, or internal evidence notes.
Project ownership is one weighted dimension, not a separate score or a binary present/absent flag. Its 0–5 anchors should distinguish no evidence, minimal recognition, limited explanation, guided ownership, independent ownership, and transfer/stewardship.
The Interviewer form should show assessment anchors but should not display downstream certificate/qualification thresholds or a derived pass/fail verdict. Those policies are outside the interview definition.
Validation
Before publication, validate at least:
- JSON Schema and supported schema version;
- unique section, item, response-field, dimension, and scale IDs;
- complete localization for every declared language;
- valid references between dimensions and evidence sections;
- all selected item kinds being compatible with their section;
- random selection count, quota, and coverage rules being satisfiable;
- complete, non-overlapping anchored scale levels;
- score scale 0–5;
- positive dimension weights totaling 100;
- supported field/control types only.
Errors should include a JSON path and a human-readable explanation. Admin preview should generate several sample packets so selection problems are visible before publication.
Acceptance criteria
- An authorized course admin can upload one complete JSON definition without creating questions individually in RS App.
- The server validates it against a documented versioned schema and displays actionable errors.
- The admin can preview sample generated forms and publish an immutable version.
- A regular interview task can reference a published definition version.
- The Interviewer chooses English or Russian at Start Interview.
- The server atomically generates and persists the frozen packet before displaying it.
- Two students may receive different valid packets from the same definition.
- Reloading, resuming, or publishing a newer definition does not change an existing packet because the complete resolved form is stored.
- Configured response fields can be recorded once per section or once per selected item.
- The form is rendered from ordered configured sections and supported response controls, not a course-specific hard-coded component.
- A course can add/reorder/remove supported sections through JSON alone.
- Interviewer-only answers and guidance are enforced by backend authorization.
- All six configured 0–5 dimension levels, evidence notes, feedback, and the single server-calculated 0–100 score are stored.
- The conducting form labels the field Feedback for the student and visibly states that the student will see it after submission.
- After completion, the student's interview card on
/course/student/interviewsdisplays the final score and submitted configured feedback without requiring a visit to the profile. - Definitions without feedback, and optional feedback left empty, do not produce an empty Feedback section.
- The student result response does not expose selected prompts, suggested answers, guidance, or internal evidence notes.
- No interview pass/fail or qualification indicator is shown in the conducting/scoring form.
- Existing regular interview templates continue to work during migration or can be converted without losing stored results.
Out of scope
- Technical Screening and mentor-selection decisions (see #2849).
- Interviewer role, approval, distribution, or waitlist changes (see #3092).
- Downstream certificate/qualification policy.
Source: rolling-scopes/rsschool-app