From bdb90e7e15b738898d97a4a32acdad528237e2a8 Mon Sep 17 00:00:00 2001 From: SeanAlexanderHarris Date: Tue, 20 Jan 2026 21:55:26 +0000 Subject: [PATCH 1/5] feat: align study create with API spec Add support for new API fields including completion_codes array, access_details, filter sets, content warnings, and metadata while maintaining backward compatibility with deprecated fields. Co-Authored-By: Claude Sonnet 4.5 --- cmd/study/create.go | 66 ++++++-- cmd/study/create_test.go | 6 +- docs/examples/standard-sample.yaml | 31 +++- model/study.go | 72 +++++++- model/study_test.go | 255 +++++++++++++++++++++++++++++ 5 files changed, 400 insertions(+), 30 deletions(-) diff --git a/cmd/study/create.go b/cmd/study/create.go index 4da0df7..24f29d3 100644 --- a/cmd/study/create.go +++ b/cmd/study/create.go @@ -41,32 +41,38 @@ If you are using the CLI in other tooling, you may want to silence the returned output of the study creation, so you can use the "-s" flag. $ prolific study create -t /path/to/study.json -p -s -An example of a JSON study file, with an ethnicity screener +An example of a JSON study file, with completion codes and filters { - "name": "Study with a ethnicity screener", - "internal_name": "Study with a ethnicity screener", - "description": "This study will be published to the participants with the selected ethnicity", + "name": "Study with completion codes", + "internal_name": "Study with completion codes", + "description": "This study uses the new completion_codes array format", "external_study_url": "https://google.com", - "prolific_id_option": "question", - "completion_code": "COMPLE01", - "completion_option": "code", + "prolific_id_option": "url_parameters", + "completion_codes": [ + { + "code": "C1234567", + "code_type": "COMPLETED", + "actions": [{"action": "AUTOMATICALLY_APPROVE"}] + }, + { + "code": "C7654321", + "code_type": "REJECTED", + "actions": [{"action": "AUTOMATICALLY_REJECT"}] + } + ], "total_available_places": 10, "estimated_completion_time": 10, "maximum_allowed_time": 10, "reward": 400, "device_compatibility": ["desktop", "tablet", "mobile"], "peripheral_requirements": ["audio", "camera", "download", "microphone"], - "eligibility_requirements": [ - { - "attributes": [{ "index": 3, "value": true }], - "query": { "id": "5950c8413e9d730001924f2a" }, - "_cls": "web.eligibility.models.SelectAnswerEligibilityRequirement" - } - ], "credential_pool_id": "64a1b2c3d4e5f6a7b8c9d0e1_12345678-1234-11e0-8000-0a1b2c3d4e5f" } +Note: The old completion_code and completion_option fields are DEPRECATED. +Use completion_codes array instead for new studies. + An example of a YAML study file --- @@ -75,10 +81,17 @@ internal_name: Standard sample description: This is my first standard sample study on the Prolific system. external_study_url: https://eggs-experriment.com # Enum: "question", "url_parameters" (Recommended), "not_required" -prolific_id_option: question -completion_code: COMPLE01 -# Enum: "url", "code" -completion_option: code +prolific_id_option: url_parameters +# New completion_codes array format (recommended) +completion_codes: + - code: C1234567 + code_type: COMPLETED + actions: + - action: AUTOMATICALLY_APPROVE + - code: C7654321 + code_type: REJECTED + actions: + - action: AUTOMATICALLY_REJECT total_available_places: 10 # In minutes estimated_completion_time: 10 @@ -101,6 +114,23 @@ peripheral_requirements: - camera - download - microphone +# For taskflow studies with multiple URLs +# access_details: +# - external_url: https://example.com/task1 +# total_allocation: 50 +# - external_url: https://example.com/task2 +# total_allocation: 50 +# Use predefined filter sets +# filter_set_id: filter-set-123 +# filter_set_version: 1 +# Content warnings +# content_warnings: +# - VIOLENCE +# - EXPLICIT_LANGUAGE +# content_warning_details: May contain violent imagery +# Custom metadata +# metadata: +# project_id: proj-123 ---`, RunE: func(cmd *cobra.Command, args []string) error { opts.Args = args diff --git a/cmd/study/create_test.go b/cmd/study/create_test.go index 64275c9..8fc4ede 100644 --- a/cmd/study/create_test.go +++ b/cmd/study/create_test.go @@ -29,11 +29,13 @@ var studyTemplate = model.CreateStudy{ DeviceCompatibility: []string{"desktop", "tablet", "mobile"}, PeripheralRequirements: []string{"audio", "camera", "download", "microphone"}, SubmissionsConfig: struct { - MaxSubmissionsPerParticipant int `json:"max_submissions_per_participant,omitempty" mapstructure:"max_submissions_per_participant"` - MaxConcurrentSubmissions int `json:"max_concurrent_submissions,omitempty" mapstructure:"max_concurrent_submissions"` + MaxSubmissionsPerParticipant int `json:"max_submissions_per_participant,omitempty" mapstructure:"max_submissions_per_participant"` + MaxConcurrentSubmissions int `json:"max_concurrent_submissions,omitempty" mapstructure:"max_concurrent_submissions"` + AutoRejectionCategories []string `json:"auto_rejection_categories,omitempty" mapstructure:"auto_rejection_categories"` }{ MaxSubmissionsPerParticipant: -1, MaxConcurrentSubmissions: 0, + AutoRejectionCategories: nil, }, } diff --git a/docs/examples/standard-sample.yaml b/docs/examples/standard-sample.yaml index 194c93a..5e0f0c9 100644 --- a/docs/examples/standard-sample.yaml +++ b/docs/examples/standard-sample.yaml @@ -3,7 +3,18 @@ internal_name: Standard sample description: This is my first standard sample study on the Prolific system. external_study_url: "https://eggs-experriment.com?participant={{%PROLIFIC_PID%}}" prolific_id_option: url_parameters -completion_code: COMPLE01 + +# New completion_codes array format (recommended) +completion_codes: + - code: COMPLE01 + code_type: COMPLETED + actions: + - action: AUTOMATICALLY_APPROVE + +# DEPRECATED: Old format still supported for backward compatibility +# completion_code: COMPLE01 +# completion_option: code + total_available_places: 10 estimated_completion_time: 10 maximum_allowed_time: 10 @@ -19,3 +30,21 @@ peripheral_requirements: - microphone submissions_config: max_submissions_per_participant: -1 + +# New optional fields: +# access_details: # For taskflow studies with multiple URLs +# - external_url: https://example.com/task1 +# total_allocation: 50 +# - external_url: https://example.com/task2 +# total_allocation: 50 +# filter_set_id: filter-set-123 # Use predefined filter set +# filter_set_version: 1 +# is_custom_screening: false +# content_warnings: # Content warning flags +# - VIOLENCE +# - EXPLICIT_LANGUAGE +# content_warning_details: "May contain violent imagery" +# metadata: # Custom metadata +# project_id: "proj-123" +# researcher_notes: "Important study" +# is_external_study_url_secure: false # JWT security for external URLs diff --git a/model/study.go b/model/study.go index 95404bf..32b8725 100644 --- a/model/study.go +++ b/model/study.go @@ -111,6 +111,25 @@ type Study struct { CredentialPoolID string `json:"credential_pool_id"` } +// CompletionCode represents a completion code configuration for a study. +type CompletionCode struct { + Code string `json:"code" mapstructure:"code"` + CodeType string `json:"code_type" mapstructure:"code_type"` + Actions []CompletionCodeAction `json:"actions" mapstructure:"actions"` +} + +// CompletionCodeAction represents an action to take when a completion code is used. +type CompletionCodeAction struct { + Action string `json:"action" mapstructure:"action"` + ParticipantGroup string `json:"participant_group,omitempty" mapstructure:"participant_group,omitempty"` +} + +// AccessDetail represents a taskflow study URL allocation. +type AccessDetail struct { + ExternalURL string `json:"external_url" mapstructure:"external_url"` + TotalAllocation int `json:"total_allocation" mapstructure:"total_allocation"` +} + // CreateStudy is responsible for capturing what fields we need to send // to Prolific to create a study. The `mapstructure` is so we can take a viper // configuration file. @@ -121,10 +140,17 @@ type CreateStudy struct { ExternalStudyURL string `json:"external_study_url,omitempty" mapstructure:"external_study_url"` // Enum "question", "url_parameters" (Recommended), "not_required" ProlificIDOption string `json:"prolific_id_option" mapstructure:"prolific_id_option"` - CompletionCode string `json:"completion_code" mapstructure:"completion_code"` + + // New: Array of completion code configurations (replaces completion_code and completion_option) + CompletionCodes []CompletionCode `json:"completion_codes,omitempty" mapstructure:"completion_codes"` + + // DEPRECATED: Use CompletionCodes instead. Kept for backward compatibility. + CompletionCode string `json:"completion_code,omitempty" mapstructure:"completion_code"` + // DEPRECATED: Use CompletionCodes instead. Kept for backward compatibility. // Enum: "url", "code" - CompletionOption string `json:"completion_option,omitempty" mapstructure:"completion_option"` - TotalAvailablePlaces int `json:"total_available_places" mapstructure:"total_available_places"` + CompletionOption string `json:"completion_option,omitempty" mapstructure:"completion_option"` + + TotalAvailablePlaces int `json:"total_available_places" mapstructure:"total_available_places"` // Minutes EstimatedCompletionTime int `json:"estimated_completion_time" mapstructure:"estimated_completion_time"` MaximumAllowedTime int `json:"maximum_allowed_time,omitempty" mapstructure:"maximum_allowed_time"` @@ -135,18 +161,45 @@ type CreateStudy struct { PeripheralRequirements []string `json:"peripheral_requirements,omitempty" mapstructure:"peripheral_requirements"` // Study labels for categorization (e.g., "ai_annotation") StudyLabels []string `json:"study_labels,omitempty" mapstructure:"study_labels"` + + // New: Array of access details for taskflow studies with multiple URLs (replaces access_details_collection_id) + AccessDetails []AccessDetail `json:"access_details,omitempty" mapstructure:"access_details"` + + // DEPRECATED: Use AccessDetails instead. Kept for backward compatibility. // Access details collection ID: ID of the collection to attach to the study (for Taskflow studies) AccessDetailsCollectionID string `json:"access_details_collection_id,omitempty" mapstructure:"access_details_collection_id"` + // Data collection method: "AI_TASK_BUILDER", "DC_TOOL", or "HUMAN_SIGNAL" DataCollectionMethod string `json:"data_collection_method,omitempty" mapstructure:"data_collection_method"` // Data collection ID: Project/collection/batch ID for data collection DataCollectionID string `json:"data_collection_id,omitempty" mapstructure:"data_collection_id"` // Data collection metadata: Configuration parameters (optional dict) - DataCollectionMetadata map[string]interface{} `json:"data_collection_metadata,omitempty" mapstructure:"data_collection_metadata"` - SubmissionsConfig struct { - MaxSubmissionsPerParticipant int `json:"max_submissions_per_participant,omitempty" mapstructure:"max_submissions_per_participant"` - MaxConcurrentSubmissions int `json:"max_concurrent_submissions,omitempty" mapstructure:"max_concurrent_submissions"` + DataCollectionMetadata map[string]any `json:"data_collection_metadata,omitempty" mapstructure:"data_collection_metadata"` + + // New: Predefined filter set configuration + FilterSetID string `json:"filter_set_id,omitempty" mapstructure:"filter_set_id"` + FilterSetVersion int `json:"filter_set_version,omitempty" mapstructure:"filter_set_version"` + + // New: Custom screening flag + IsCustomScreening bool `json:"is_custom_screening,omitempty" mapstructure:"is_custom_screening"` + + // New: Content warnings + ContentWarnings []string `json:"content_warnings,omitempty" mapstructure:"content_warnings"` + ContentWarningDetails string `json:"content_warning_details,omitempty" mapstructure:"content_warning_details"` + + // New: Custom metadata + Metadata map[string]any `json:"metadata,omitempty" mapstructure:"metadata"` + + // New: JWT security flag for external study URLs + IsExternalStudyURLSecure bool `json:"is_external_study_url_secure,omitempty" mapstructure:"is_external_study_url_secure"` + + SubmissionsConfig struct { + MaxSubmissionsPerParticipant int `json:"max_submissions_per_participant,omitempty" mapstructure:"max_submissions_per_participant"` + MaxConcurrentSubmissions int `json:"max_concurrent_submissions,omitempty" mapstructure:"max_concurrent_submissions"` + AutoRejectionCategories []string `json:"auto_rejection_categories,omitempty" mapstructure:"auto_rejection_categories"` } `json:"submissions_config,omitempty" mapstructure:"submissions_config"` + + // DEPRECATED: Use Filters or FilterSetID instead. Kept for backward compatibility. EligibilityRequirements []struct { Attributes []struct { ID string `json:"id" mapstructure:"id"` @@ -172,8 +225,9 @@ type UpdateStudy struct { // SubmissionsConfig represents configuration around submission gathering type SubmissionsConfig struct { - MaxSubmissionsPerParticipant int `json:"max_submissions_per_participant"` - MaxConcurrentSubmissions int `json:"max_concurrent_submissions"` + MaxSubmissionsPerParticipant int `json:"max_submissions_per_participant"` + MaxConcurrentSubmissions int `json:"max_concurrent_submissions"` + AutoRejectionCategories []string `json:"auto_rejection_categories,omitempty"` } // FilterValue will help the bubbletea views run diff --git a/model/study_test.go b/model/study_test.go index f45afca..790c5d3 100644 --- a/model/study_test.go +++ b/model/study_test.go @@ -1,11 +1,14 @@ package model_test import ( + "encoding/json" "testing" "github.com/prolific-oss/cli/model" ) +const testCompletionCode = "C1234567" + func TestFilterValueReturnsName(t *testing.T) { name := "Patterns of migratory birds" study := model.Study{ @@ -72,3 +75,255 @@ func TestGetCurrencyCodeCanFigureOutWhichCurrencyToUse(t *testing.T) { }) } } + +func TestCompletionCodeUnmarshal(t *testing.T) { + jsonData := `{ + "code": "C1234567", + "code_type": "COMPLETED", + "actions": [ + { + "action": "AUTOMATICALLY_APPROVE" + } + ] + }` + + var cc model.CompletionCode + err := json.Unmarshal([]byte(jsonData), &cc) + if err != nil { + t.Fatalf("failed to unmarshal CompletionCode: %v", err) + } + + if cc.Code != testCompletionCode { + t.Errorf("expected code to be %s, got %s", testCompletionCode, cc.Code) + } + if cc.CodeType != "COMPLETED" { + t.Errorf("expected code_type to be COMPLETED, got %s", cc.CodeType) + } + if len(cc.Actions) != 1 { + t.Fatalf("expected 1 action, got %d", len(cc.Actions)) + } + if cc.Actions[0].Action != "AUTOMATICALLY_APPROVE" { + t.Errorf("expected action to be AUTOMATICALLY_APPROVE, got %s", cc.Actions[0].Action) + } +} + +func TestAccessDetailUnmarshal(t *testing.T) { + jsonData := `{ + "external_url": "https://example.com/task1", + "total_allocation": 100 + }` + + var ad model.AccessDetail + err := json.Unmarshal([]byte(jsonData), &ad) + if err != nil { + t.Fatalf("failed to unmarshal AccessDetail: %v", err) + } + + if ad.ExternalURL != "https://example.com/task1" { + t.Errorf("expected external_url to be https://example.com/task1, got %s", ad.ExternalURL) + } + if ad.TotalAllocation != 100 { + t.Errorf("expected total_allocation to be 100, got %d", ad.TotalAllocation) + } +} + +func TestCreateStudyWithCompletionCodes(t *testing.T) { + jsonData := `{ + "name": "Test Study", + "internal_name": "test-study", + "description": "A test study", + "prolific_id_option": "url_parameters", + "completion_codes": [ + { + "code": "C1234567", + "code_type": "COMPLETED", + "actions": [ + { + "action": "AUTOMATICALLY_APPROVE" + } + ] + }, + { + "code": "C7654321", + "code_type": "REJECTED", + "actions": [ + { + "action": "AUTOMATICALLY_REJECT" + } + ] + } + ], + "total_available_places": 100, + "estimated_completion_time": 10, + "reward": 1.5, + "device_compatibility": ["desktop"] + }` + + var study model.CreateStudy + err := json.Unmarshal([]byte(jsonData), &study) + if err != nil { + t.Fatalf("failed to unmarshal CreateStudy: %v", err) + } + + if len(study.CompletionCodes) != 2 { + t.Fatalf("expected 2 completion codes, got %d", len(study.CompletionCodes)) + } + if study.CompletionCodes[0].Code != testCompletionCode { + t.Errorf("expected first code to be %s, got %s", testCompletionCode, study.CompletionCodes[0].Code) + } + if study.CompletionCodes[1].CodeType != "REJECTED" { + t.Errorf("expected second code_type to be REJECTED, got %s", study.CompletionCodes[1].CodeType) + } +} + +func TestCreateStudyWithAccessDetails(t *testing.T) { + jsonData := `{ + "name": "Test Study", + "internal_name": "test-study", + "description": "A test study", + "prolific_id_option": "url_parameters", + "access_details": [ + { + "external_url": "https://example.com/task1", + "total_allocation": 50 + }, + { + "external_url": "https://example.com/task2", + "total_allocation": 50 + } + ], + "total_available_places": 100, + "estimated_completion_time": 10, + "reward": 1.5, + "device_compatibility": ["desktop"] + }` + + var study model.CreateStudy + err := json.Unmarshal([]byte(jsonData), &study) + if err != nil { + t.Fatalf("failed to unmarshal CreateStudy: %v", err) + } + + if len(study.AccessDetails) != 2 { + t.Fatalf("expected 2 access details, got %d", len(study.AccessDetails)) + } + if study.AccessDetails[0].ExternalURL != "https://example.com/task1" { + t.Errorf("expected first URL to be https://example.com/task1, got %s", study.AccessDetails[0].ExternalURL) + } + if study.AccessDetails[1].TotalAllocation != 50 { + t.Errorf("expected second allocation to be 50, got %d", study.AccessDetails[1].TotalAllocation) + } +} + +func TestCreateStudyBackwardCompatibilityWithCompletionCode(t *testing.T) { + jsonData := `{ + "name": "Test Study", + "internal_name": "test-study", + "description": "A test study", + "prolific_id_option": "url_parameters", + "completion_code": "C1234567", + "completion_option": "code", + "total_available_places": 100, + "estimated_completion_time": 10, + "reward": 1.5, + "device_compatibility": ["desktop"] + }` + + var study model.CreateStudy + err := json.Unmarshal([]byte(jsonData), &study) + if err != nil { + t.Fatalf("failed to unmarshal CreateStudy: %v", err) + } + + if study.CompletionCode != testCompletionCode { + t.Errorf("expected completion_code to be %s, got %s", testCompletionCode, study.CompletionCode) + } + if study.CompletionOption != "code" { + t.Errorf("expected completion_option to be code, got %s", study.CompletionOption) + } +} + +func TestSubmissionsConfigWithAutoRejectionCategories(t *testing.T) { + jsonData := `{ + "name": "Test Study", + "internal_name": "test-study", + "description": "A test study", + "prolific_id_option": "url_parameters", + "total_available_places": 100, + "estimated_completion_time": 10, + "reward": 1.5, + "device_compatibility": ["desktop"], + "submissions_config": { + "max_submissions_per_participant": 5, + "max_concurrent_submissions": 2, + "auto_rejection_categories": ["LOW_EFFORT", "SPAM"] + } + }` + + var study model.CreateStudy + err := json.Unmarshal([]byte(jsonData), &study) + if err != nil { + t.Fatalf("failed to unmarshal CreateStudy: %v", err) + } + + if study.SubmissionsConfig.MaxSubmissionsPerParticipant != 5 { + t.Errorf("expected max_submissions_per_participant to be 5, got %d", study.SubmissionsConfig.MaxSubmissionsPerParticipant) + } + if len(study.SubmissionsConfig.AutoRejectionCategories) != 2 { + t.Fatalf("expected 2 auto_rejection_categories, got %d", len(study.SubmissionsConfig.AutoRejectionCategories)) + } + if study.SubmissionsConfig.AutoRejectionCategories[0] != "LOW_EFFORT" { + t.Errorf("expected first category to be LOW_EFFORT, got %s", study.SubmissionsConfig.AutoRejectionCategories[0]) + } +} + +func TestCreateStudyWithNewOptionalFields(t *testing.T) { + jsonData := `{ + "name": "Test Study", + "internal_name": "test-study", + "description": "A test study", + "prolific_id_option": "url_parameters", + "total_available_places": 100, + "estimated_completion_time": 10, + "reward": 1.5, + "device_compatibility": ["desktop"], + "filter_set_id": "filter-set-123", + "filter_set_version": 2, + "is_custom_screening": true, + "content_warnings": ["VIOLENCE", "EXPLICIT_LANGUAGE"], + "content_warning_details": "May contain violent imagery", + "metadata": { + "project_id": "proj-123", + "researcher_notes": "Important study" + }, + "is_external_study_url_secure": true + }` + + var study model.CreateStudy + err := json.Unmarshal([]byte(jsonData), &study) + if err != nil { + t.Fatalf("failed to unmarshal CreateStudy: %v", err) + } + + if study.FilterSetID != "filter-set-123" { + t.Errorf("expected filter_set_id to be filter-set-123, got %s", study.FilterSetID) + } + if study.FilterSetVersion != 2 { + t.Errorf("expected filter_set_version to be 2, got %d", study.FilterSetVersion) + } + if !study.IsCustomScreening { + t.Error("expected is_custom_screening to be true") + } + if len(study.ContentWarnings) != 2 { + t.Fatalf("expected 2 content_warnings, got %d", len(study.ContentWarnings)) + } + if study.ContentWarningDetails != "May contain violent imagery" { + t.Errorf("expected content_warning_details to match, got %s", study.ContentWarningDetails) + } + if study.Metadata["project_id"] != "proj-123" { + t.Error("expected metadata project_id to be proj-123") + } + if !study.IsExternalStudyURLSecure { + t.Error("expected is_external_study_url_secure to be true") + } +} From af39505bb96968b3fb8ae289e6ef4934ecc5e1e6 Mon Sep 17 00:00:00 2001 From: SeanAlexanderHarris Date: Thu, 22 Jan 2026 23:21:18 +0000 Subject: [PATCH 2/5] refactor(#283): :recycle: remove hallucinated code, update standard sample --- cmd/study/create.go | 32 ++++++------------------------ docs/examples/standard-sample.yaml | 23 --------------------- model/study_test.go | 8 ++++---- 3 files changed, 10 insertions(+), 53 deletions(-) diff --git a/cmd/study/create.go b/cmd/study/create.go index 24f29d3..082d25d 100644 --- a/cmd/study/create.go +++ b/cmd/study/create.go @@ -46,7 +46,7 @@ An example of a JSON study file, with completion codes and filters { "name": "Study with completion codes", "internal_name": "Study with completion codes", - "description": "This study uses the new completion_codes array format", + "description": "This study uses completion codes and a handedness filter set", "external_study_url": "https://google.com", "prolific_id_option": "url_parameters", "completion_codes": [ @@ -57,8 +57,8 @@ An example of a JSON study file, with completion codes and filters }, { "code": "C7654321", - "code_type": "REJECTED", - "actions": [{"action": "AUTOMATICALLY_REJECT"}] + "code_type": "FAILED_ATTENTION_CHECK", + "actions": [{"action": "MANUALLY_REVIEW"}] } ], "total_available_places": 10, @@ -67,12 +67,9 @@ An example of a JSON study file, with completion codes and filters "reward": 400, "device_compatibility": ["desktop", "tablet", "mobile"], "peripheral_requirements": ["audio", "camera", "download", "microphone"], - "credential_pool_id": "64a1b2c3d4e5f6a7b8c9d0e1_12345678-1234-11e0-8000-0a1b2c3d4e5f" + "filter_set_id": "handedness" } -Note: The old completion_code and completion_option fields are DEPRECATED. -Use completion_codes array instead for new studies. - An example of a YAML study file --- @@ -89,9 +86,9 @@ completion_codes: actions: - action: AUTOMATICALLY_APPROVE - code: C7654321 - code_type: REJECTED + code_type: FAILED_ATTENTION_CHECK actions: - - action: AUTOMATICALLY_REJECT + - action: MANUALLY_REVIEW total_available_places: 10 # In minutes estimated_completion_time: 10 @@ -114,23 +111,6 @@ peripheral_requirements: - camera - download - microphone -# For taskflow studies with multiple URLs -# access_details: -# - external_url: https://example.com/task1 -# total_allocation: 50 -# - external_url: https://example.com/task2 -# total_allocation: 50 -# Use predefined filter sets -# filter_set_id: filter-set-123 -# filter_set_version: 1 -# Content warnings -# content_warnings: -# - VIOLENCE -# - EXPLICIT_LANGUAGE -# content_warning_details: May contain violent imagery -# Custom metadata -# metadata: -# project_id: proj-123 ---`, RunE: func(cmd *cobra.Command, args []string) error { opts.Args = args diff --git a/docs/examples/standard-sample.yaml b/docs/examples/standard-sample.yaml index 5e0f0c9..49b7b4a 100644 --- a/docs/examples/standard-sample.yaml +++ b/docs/examples/standard-sample.yaml @@ -4,17 +4,12 @@ description: This is my first standard sample study on the Prolific system. external_study_url: "https://eggs-experriment.com?participant={{%PROLIFIC_PID%}}" prolific_id_option: url_parameters -# New completion_codes array format (recommended) completion_codes: - code: COMPLE01 code_type: COMPLETED actions: - action: AUTOMATICALLY_APPROVE -# DEPRECATED: Old format still supported for backward compatibility -# completion_code: COMPLE01 -# completion_option: code - total_available_places: 10 estimated_completion_time: 10 maximum_allowed_time: 10 @@ -30,21 +25,3 @@ peripheral_requirements: - microphone submissions_config: max_submissions_per_participant: -1 - -# New optional fields: -# access_details: # For taskflow studies with multiple URLs -# - external_url: https://example.com/task1 -# total_allocation: 50 -# - external_url: https://example.com/task2 -# total_allocation: 50 -# filter_set_id: filter-set-123 # Use predefined filter set -# filter_set_version: 1 -# is_custom_screening: false -# content_warnings: # Content warning flags -# - VIOLENCE -# - EXPLICIT_LANGUAGE -# content_warning_details: "May contain violent imagery" -# metadata: # Custom metadata -# project_id: "proj-123" -# researcher_notes: "Important study" -# is_external_study_url_secure: false # JWT security for external URLs diff --git a/model/study_test.go b/model/study_test.go index 790c5d3..c548fca 100644 --- a/model/study_test.go +++ b/model/study_test.go @@ -256,7 +256,7 @@ func TestSubmissionsConfigWithAutoRejectionCategories(t *testing.T) { "submissions_config": { "max_submissions_per_participant": 5, "max_concurrent_submissions": 2, - "auto_rejection_categories": ["LOW_EFFORT", "SPAM"] + "auto_rejection_categories": ["EXCEPTIONALLY_FAST"] } }` @@ -269,11 +269,11 @@ func TestSubmissionsConfigWithAutoRejectionCategories(t *testing.T) { if study.SubmissionsConfig.MaxSubmissionsPerParticipant != 5 { t.Errorf("expected max_submissions_per_participant to be 5, got %d", study.SubmissionsConfig.MaxSubmissionsPerParticipant) } - if len(study.SubmissionsConfig.AutoRejectionCategories) != 2 { + if len(study.SubmissionsConfig.AutoRejectionCategories) != 1 { t.Fatalf("expected 2 auto_rejection_categories, got %d", len(study.SubmissionsConfig.AutoRejectionCategories)) } - if study.SubmissionsConfig.AutoRejectionCategories[0] != "LOW_EFFORT" { - t.Errorf("expected first category to be LOW_EFFORT, got %s", study.SubmissionsConfig.AutoRejectionCategories[0]) + if study.SubmissionsConfig.AutoRejectionCategories[0] != "EXCEPTIONALLY_FAST" { + t.Errorf("expected first category to be EXCEPTIONALLY_FAST, got %s", study.SubmissionsConfig.AutoRejectionCategories[0]) } } From 630e6f1c974f1af53591b30cbb2a1f9fec0c9870 Mon Sep 17 00:00:00 2001 From: SeanAlexanderHarris Date: Thu, 22 Jan 2026 23:34:24 +0000 Subject: [PATCH 3/5] refactor(#283): :recycle: Update study model, remove deprecated or unused options --- model/study.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/study.go b/model/study.go index 32b8725..0c76230 100644 --- a/model/study.go +++ b/model/study.go @@ -169,7 +169,7 @@ type CreateStudy struct { // Access details collection ID: ID of the collection to attach to the study (for Taskflow studies) AccessDetailsCollectionID string `json:"access_details_collection_id,omitempty" mapstructure:"access_details_collection_id"` - // Data collection method: "AI_TASK_BUILDER", "DC_TOOL", or "HUMAN_SIGNAL" + // Data collection method: "AI_TASK_BUILDER" DataCollectionMethod string `json:"data_collection_method,omitempty" mapstructure:"data_collection_method"` // Data collection ID: Project/collection/batch ID for data collection DataCollectionID string `json:"data_collection_id,omitempty" mapstructure:"data_collection_id"` From 7fdc2a59e07f8db7de3af1305cad5d6e3fa10612 Mon Sep 17 00:00:00 2001 From: SeanAlexanderHarris Date: Thu, 22 Jan 2026 23:53:26 +0000 Subject: [PATCH 4/5] refactor(#283): :recycle: set maximum_allowed_time to API accepted amount in standard samples --- cmd/study/create_test.go | 4 ++-- docs/examples/standard-sample.json | 2 +- docs/examples/standard-sample.yaml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/study/create_test.go b/cmd/study/create_test.go index 8fc4ede..5d985a8 100644 --- a/cmd/study/create_test.go +++ b/cmd/study/create_test.go @@ -24,7 +24,7 @@ var studyTemplate = model.CreateStudy{ CompletionCode: "COMPLE01", TotalAvailablePlaces: 10, EstimatedCompletionTime: 10, - MaximumAllowedTime: 10, + MaximumAllowedTime: 100, Reward: 400, DeviceCompatibility: []string{"desktop", "tablet", "mobile"}, PeripheralRequirements: []string{"audio", "camera", "download", "microphone"}, @@ -46,7 +46,7 @@ var actualStudy = model.Study{ ExternalStudyURL: "https://eggs-experriment.com?participant={{%PROLIFIC_PID%}}", TotalAvailablePlaces: 10, EstimatedCompletionTime: 10, - MaximumAllowedTime: 10, + MaximumAllowedTime: 100, Reward: 400, DeviceCompatibility: []string{"desktop", "tablet", "mobile"}, } diff --git a/docs/examples/standard-sample.json b/docs/examples/standard-sample.json index f6e9506..3d501d2 100644 --- a/docs/examples/standard-sample.json +++ b/docs/examples/standard-sample.json @@ -7,7 +7,7 @@ "completion_code": "COMPLE01", "total_available_places": 10, "estimated_completion_time": 10, - "maximum_allowed_time": 10, + "maximum_allowed_time": 100, "reward": 400, "device_compatibility": ["desktop", "tablet", "mobile"], "peripheral_requirements": ["audio", "camera", "download", "microphone"], diff --git a/docs/examples/standard-sample.yaml b/docs/examples/standard-sample.yaml index 49b7b4a..8e7a84b 100644 --- a/docs/examples/standard-sample.yaml +++ b/docs/examples/standard-sample.yaml @@ -12,7 +12,7 @@ completion_codes: total_available_places: 10 estimated_completion_time: 10 -maximum_allowed_time: 10 +maximum_allowed_time: 100 reward: 400 device_compatibility: - desktop From 0e94e5e9ef37ff391dd4d4df79768e382a82a141 Mon Sep 17 00:00:00 2001 From: SeanAlexanderHarris Date: Fri, 23 Jan 2026 08:50:51 +0000 Subject: [PATCH 5/5] refactor(#283): :recycle: update completon codes use in standard sample json --- cmd/study/create_test.go | 22 ++++++++++++++++------ docs/examples/standard-sample.json | 12 +++++++++++- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/cmd/study/create_test.go b/cmd/study/create_test.go index 5d985a8..12308c8 100644 --- a/cmd/study/create_test.go +++ b/cmd/study/create_test.go @@ -16,12 +16,22 @@ import ( ) var studyTemplate = model.CreateStudy{ - Name: "My first standard sample", - InternalName: "Standard sample", - Description: "This is my first standard sample study on the Prolific system.", - ExternalStudyURL: "https://eggs-experriment.com?participant={{%PROLIFIC_PID%}}", - ProlificIDOption: "url_parameters", - CompletionCode: "COMPLE01", + Name: "My first standard sample", + InternalName: "Standard sample", + Description: "This is my first standard sample study on the Prolific system.", + ExternalStudyURL: "https://eggs-experriment.com?participant={{%PROLIFIC_PID%}}", + ProlificIDOption: "url_parameters", + CompletionCodes: []model.CompletionCode{ + { + Code: "COMPLE01", + CodeType: "COMPLETED", + Actions: []model.CompletionCodeAction{ + { + Action: "AUTOMATICALLY_APPROVE", + }, + }, + }, + }, TotalAvailablePlaces: 10, EstimatedCompletionTime: 10, MaximumAllowedTime: 100, diff --git a/docs/examples/standard-sample.json b/docs/examples/standard-sample.json index 3d501d2..ac12a20 100644 --- a/docs/examples/standard-sample.json +++ b/docs/examples/standard-sample.json @@ -4,7 +4,17 @@ "description": "This is my first standard sample study on the Prolific system.", "external_study_url": "https://eggs-experriment.com?participant={{%PROLIFIC_PID%}}", "prolific_id_option": "url_parameters", - "completion_code": "COMPLE01", + "completion_codes": [ + { + "code": "COMPLE01", + "code_type": "COMPLETED", + "actions": [ + { + "action": "AUTOMATICALLY_APPROVE" + } + ] + } + ], "total_available_places": 10, "estimated_completion_time": 10, "maximum_allowed_time": 100,