22 AgentMediatorTemplate ,
33 AgentMediatorPersonaConfig ,
44 AgentParticipantPersonaConfig ,
5- AgentPersonaType ,
65 AgentParticipantTemplate ,
76 ApiKeyType ,
87 CohortParticipantConfig ,
@@ -15,18 +14,17 @@ import {
1514 ProlificConfig ,
1615 StageConfig ,
1716 StageKind ,
18- StageManager ,
1917 VariableConfig ,
2018 MultiValueVariableConfigType ,
2119 requiresValues ,
2220 STAGE_MANAGER ,
2321 checkApiKeyExists ,
22+ SurveyQuestion ,
23+ SurveyQuestionKind ,
24+ MultipleChoiceItem ,
2425 createAgentMediatorPersonaConfig ,
2526 createAgentParticipantPersonaConfig ,
2627 createExperimentConfig ,
27- createMetadataConfig ,
28- createPermissionsConfig ,
29- createProlificConfig ,
3028 generateId ,
3129} from '@deliberation-lab/utils' ;
3230import { Timestamp } from 'firebase/firestore' ;
@@ -106,6 +104,76 @@ export class ExperimentEditor extends Service {
106104 ) ;
107105 } ;
108106
107+ const validateSurveyQuestion = (
108+ question : SurveyQuestion ,
109+ stageName : string ,
110+ index : number ,
111+ ) : string [ ] => {
112+ const errors : string [ ] = [ ] ;
113+ const questionPrefix = `${ stageName } question ${ index + 1 } ` ;
114+
115+ if ( question . questionTitle === '' ) {
116+ errors . push ( `${ questionPrefix } is missing a title` ) ;
117+ }
118+
119+ if ( question . kind === SurveyQuestionKind . SCALE ) {
120+ if (
121+ ! Number . isInteger ( question . lowerValue ) ||
122+ ! Number . isInteger ( question . upperValue ) ||
123+ ( question . stepSize !== undefined &&
124+ ! Number . isInteger ( question . stepSize ) )
125+ ) {
126+ errors . push (
127+ `${ questionPrefix } ("${ question . questionTitle } "): values must be integers` ,
128+ ) ;
129+ return errors ;
130+ }
131+ if ( question . lowerValue >= question . upperValue ) {
132+ errors . push (
133+ `${ questionPrefix } ("${ question . questionTitle } "): lower value must be less than upper value` ,
134+ ) ;
135+ return errors ;
136+ }
137+ const range = question . upperValue - question . lowerValue ;
138+ const step = question . stepSize ?? 1 ;
139+ if ( step <= 0 ) {
140+ errors . push (
141+ `${ questionPrefix } ("${ question . questionTitle } "): step size must be greater than 0` ,
142+ ) ;
143+ return errors ;
144+ }
145+ if ( range % step !== 0 ) {
146+ errors . push (
147+ `${ questionPrefix } ("${ question . questionTitle } "): step size must divide max-min (${ range } ) exactly` ,
148+ ) ;
149+ }
150+ }
151+
152+ if ( question . kind === SurveyQuestionKind . MULTIPLE_CHOICE ) {
153+ if ( ! question . options || question . options . length === 0 ) {
154+ errors . push (
155+ `${ questionPrefix } ("${ question . questionTitle } "): must have at least one option` ,
156+ ) ;
157+ return errors ;
158+ }
159+ if (
160+ question . correctAnswerId != null &&
161+ question . correctAnswerId !== ''
162+ ) {
163+ const hasOption = question . options . some (
164+ ( opt : MultipleChoiceItem ) => opt . id === question . correctAnswerId ,
165+ ) ;
166+ if ( ! hasOption ) {
167+ errors . push (
168+ `${ questionPrefix } ("${ question . questionTitle } "): correct answer ID doesn't match any option ID` ,
169+ ) ;
170+ }
171+ }
172+ }
173+
174+ return errors ;
175+ } ;
176+
109177 const renderApiErrorMessage = ( apiType : ApiKeyType ) => {
110178 if ( hasAgentsWithApiType ( apiType ) ) {
111179 errors . push (
@@ -118,17 +186,16 @@ export class ExperimentEditor extends Service {
118186 renderApiErrorMessage ( ApiKeyType . OLLAMA_CUSTOM_URL ) ;
119187
120188 for ( const stage of this . stages ) {
121- switch ( stage . kind ) {
122- case StageKind . SURVEY :
123- // Ensure all questions have a non-empty title
124- stage . questions . forEach ( ( question , index ) => {
125- if ( question . questionTitle === '' ) {
126- errors . push (
127- `${ stage . name } question ${ index + 1 } is missing a title` ,
128- ) ;
129- }
130- } ) ;
131- break ;
189+ if (
190+ stage . kind === StageKind . SURVEY ||
191+ stage . kind === StageKind . SURVEY_PER_PARTICIPANT
192+ ) {
193+ if ( ! stage . questions || stage . questions . length === 0 ) {
194+ errors . push ( `${ stage . name } must contain at least one question` ) ;
195+ }
196+ stage . questions . forEach ( ( question : SurveyQuestion , index : number ) => {
197+ errors . push ( ...validateSurveyQuestion ( question , stage . name , index ) ) ;
198+ } ) ;
132199 }
133200 }
134201
0 commit comments