pi-interview 0.8.2 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/schema.ts +55 -2
package/package.json
CHANGED
package/schema.ts
CHANGED
|
@@ -23,6 +23,8 @@ export type ContentBlock = MarkdownContentBlock | CodeContentBlock;
|
|
|
23
23
|
export interface RichOption {
|
|
24
24
|
label: string;
|
|
25
25
|
content?: ContentBlock;
|
|
26
|
+
recommended?: boolean;
|
|
27
|
+
conviction?: "strong" | "slight";
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export type OptionValue = string | RichOption;
|
|
@@ -75,6 +77,50 @@ export function isRichOption(option: OptionValue): option is RichOption {
|
|
|
75
77
|
return typeof option === "object" && option !== null && "label" in option;
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
function normalizeOptionLevelRecommendations(question: Question): void {
|
|
81
|
+
if (!question.options) return;
|
|
82
|
+
|
|
83
|
+
const recommendedOptions: RichOption[] = [];
|
|
84
|
+
const convictions = new Set<"strong" | "slight">();
|
|
85
|
+
|
|
86
|
+
for (const option of question.options) {
|
|
87
|
+
if (!isRichOption(option)) continue;
|
|
88
|
+
if (option.conviction !== undefined && option.recommended !== true) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Question "${question.id}" option "${option.label}": conviction requires recommended`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (option.recommended !== true) continue;
|
|
94
|
+
recommendedOptions.push(option);
|
|
95
|
+
if (option.conviction) convictions.add(option.conviction);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (recommendedOptions.length === 0) return;
|
|
99
|
+
|
|
100
|
+
if (question.recommended !== undefined || question.conviction !== undefined) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Question "${question.id}": use either question-level recommended/conviction or option-level recommended flags, not both`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (question.type === "single" && recommendedOptions.length !== 1) {
|
|
107
|
+
throw new Error(`Question "${question.id}": exactly one option must be recommended for single-select`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (convictions.size > 1) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Question "${question.id}": recommended options must use the same conviction`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const recommendedLabels = recommendedOptions.map((option) => option.label);
|
|
117
|
+
question.recommended = question.type === "single" ? recommendedLabels[0] : recommendedLabels;
|
|
118
|
+
const conviction = convictions.values().next().value;
|
|
119
|
+
if (conviction !== undefined) {
|
|
120
|
+
question.conviction = conviction;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
78
124
|
function validateMediaBlock(block: unknown, context: string): MediaBlock {
|
|
79
125
|
if (!block || typeof block !== "object") {
|
|
80
126
|
throw new Error(`${context}: media must be an object`);
|
|
@@ -224,12 +270,18 @@ function validateOption(option: unknown, questionId: string, index: number): Opt
|
|
|
224
270
|
);
|
|
225
271
|
}
|
|
226
272
|
if (o.content !== undefined) {
|
|
227
|
-
|
|
273
|
+
const result: RichOption = {
|
|
228
274
|
label: o.label,
|
|
229
275
|
content: validateContentBlock(o.content, `Question "${questionId}" option "${o.label}"`),
|
|
230
276
|
};
|
|
277
|
+
if (o.recommended === true) result.recommended = true;
|
|
278
|
+
if (o.conviction === "strong" || o.conviction === "slight") result.conviction = o.conviction;
|
|
279
|
+
return result;
|
|
231
280
|
}
|
|
232
|
-
|
|
281
|
+
const result: RichOption = { label: o.label };
|
|
282
|
+
if (o.recommended === true) result.recommended = true;
|
|
283
|
+
if (o.conviction === "strong" || o.conviction === "slight") result.conviction = o.conviction;
|
|
284
|
+
return result;
|
|
233
285
|
}
|
|
234
286
|
throw new Error(
|
|
235
287
|
`Question "${questionId}": option at index ${index} must be a string or object with label`
|
|
@@ -354,6 +406,7 @@ export function validateQuestions(data: unknown): QuestionsFile {
|
|
|
354
406
|
if (!q.options || q.options.length === 0) {
|
|
355
407
|
throw new Error(`Question "${q.id}": options required for type "${q.type}"`);
|
|
356
408
|
}
|
|
409
|
+
normalizeOptionLevelRecommendations(q);
|
|
357
410
|
} else if (q.type === "text" || q.type === "image" || q.type === "info") {
|
|
358
411
|
if (q.options) {
|
|
359
412
|
throw new Error(`Question "${q.id}": options not allowed for type "${q.type}"`);
|