Research operations
Build, configure, and operate studies from the research admin panel.
Research operations tooling lives under /admin/research. It covers study design, exports, dual-rater coding, analytics, participant oversight, and background processing.
Who can access: Admin, Editor, Contributor, and Researcher roles (RESEARCH_ADMIN_ROLES). Researchers see the research panel only — not CMS content tools. System-level webhook replay and research-ops job panels are Admin only.
Admin route map
| Route | Purpose |
|---|---|
/admin/research |
Cross-study dashboard (enrollment, completion, recent activity) |
/admin/studies |
Create and list studies |
/admin/studies/new |
New study form |
/admin/studies/[id] |
Study settings, manual exports, export schedules, webhooks, framework links |
/admin/studies/[id]/conditions |
Experimental arms |
/admin/studies/[id]/tasks |
Task chain configuration |
/admin/studies/[id]/participants |
Enrolled participants for this study |
/admin/studies/[id]/artifacts |
Linked research artifacts |
/admin/studies/[id]/analytics |
Descriptive stats + inferential ANOVA panel |
/admin/studies/[id]/coding |
Rubric, raters, assignment generation, reliability |
/admin/participants |
Cross-study participant search |
/admin/events |
Event log and queue monitoring |
Each study page includes a tab nav: Settings · Conditions · Tasks · Participants · Artifacts · Analytics · Coding.
How to build a study (step by step)
1. Create the study shell
- Go to
/admin/studies→ New study - Fill in title, slug (auto-generated if blank), abstract, description, and public summary (shown on
/studies/[slug]) - Set study type (Experiment, Survey, Longitudinal, AI Task, etc.)
- Save — you land on the study settings page
2. Add experimental conditions
- Open Conditions tab → New condition
- For each arm, set:
- Name and slug (unique within the study)
- Control flag (one arm is typically control)
- Order — display / assignment ordering
- Config (JSON) — arm-specific settings consumed by tasks, e.g.:
{
"aiEnabled": true,
"frictionProfile": "purposeful"
}
How assignment works: On enrollment, participants are assigned uniformly at random to one condition when the study has any conditions defined.
3. Build the task chain
- Open Tasks tab → New task
- Configure each task:
| Field | Purpose |
|---|---|
| Title / slug | Identifiers; slug used in URLs and sequencing |
| Task type | WRITING, SURVEY, AI_ASSISTED, REFLECTION, TECHNICAL, LEARNING |
| Condition | Blank = all arms; or pick one arm for arm-specific tasks |
| Order | Default linear sort when no explicit nextTaskSlug |
| Config (JSON) | Instructions, validation, sequencing, survey fields, AI prompts |
Task config keys (stored in Task.config):
| Key | Purpose |
|---|---|
instructions, prompt, placeholder |
Participant-facing copy |
minWords, maxWords, timeLimitMinutes |
Validation |
fields[] |
Survey fields: text, textarea, scale, choice |
requiresTaskSlug |
Prerequisite — prior task must be completed to unlock |
nextTaskSlug |
Explicit next task after submit |
branchRules[] |
Conditional routing by response value |
optional |
Excluded from study completion requirements |
aiSystemPrompt |
System prompt for AI-assisted tasks |
Example writing task config:
{
"instructions": "Read the scenario and write a critical analysis.",
"prompt": "Evaluate the rollout plan below…",
"minWords": 100,
"requiresTaskSlug": "agency-baseline",
"nextTaskSlug": "offloading-post"
}
Example branch rule:
{
"branchRules": [
{
"field": "confidence",
"operator": "gte",
"value": "5",
"nextTaskSlug": "advanced-reflection"
}
]
}
Operators: eq, gte, lte, contains.
Sequencing logic (runtime):
- Participant sees tasks where
conditionIdis null or matches their assigned arm - A task is locked until
requiresTaskSlug(if set) is completed - After submit, next task = first matching branch rule → else
nextTaskSlug→ else next incomplete unlocked task byorderIndex - Study is complete when all non-
optionaltasks are done
4. Configure recruitment and completion
On the study Settings tab:
| Field | Purpose |
|---|---|
| Status | Lifecycle stage (see below) |
| Public study page | Show on /studies when status allows |
| Recruitment goal | Target N (informational) |
| Completion code prefix | e.g. PFDF — used in SONA handoff codes |
| External credit instructions | Shown to participants on completion |
| Variables (JSON) | Document IVs/DVs for your protocol |
| Dissemination links (JSON) | Link frameworks, media, artifacts on public study page |
Study status lifecycle:
DRAFT → INTERNAL_REVIEW → RECRUITING → ACTIVE → PAUSED → COMPLETED → PUBLISHED → ARCHIVED
| Status | Public listing | New enrollments |
|---|---|---|
RECRUITING, ACTIVE, PUBLISHED |
Yes (if public visible) | Yes |
| Others | No | No |
Set Public study page + status RECRUITING or ACTIVE to go live.
5. Link frameworks and artifacts
- Framework links — on Settings tab, connect published frameworks for dissemination
- Artifacts tab — attach research artifacts (protocols, codebooks, instruments)
6. Configure exports and schedules
On the study Settings tab (export panels):
Manual exports — download immediately:
| Export kind | Format | Contents |
|---|---|---|
PARTICIPANTS |
CSV | Anonymized IDs, status, condition, completion codes, counts |
TASK_RESPONSES |
CSV | Per-response preview + full response JSON |
EVENTS |
JSON | Anonymized event log for this study |
ANALYSIS_WIDE |
CSV | One row per participant — wide analysis dataset |
BLINDED_RATER_PACKET |
CSV | Blinded labels + response text for external coding |
Scheduled exports — one schedule per export kind:
- Frequency:
DAILYorWEEKLY - Hour (UTC), optional notify email
- Cron job
GET /api/cron/run-export-schedulesprocesses due schedules
Storage behavior:
- Exports under 512 KB (configurable via
EXPORT_S3_THRESHOLD_BYTES) stay in Postgres - Larger exports upload to S3 when
EXPORT_S3_*is configured - Download persisted exports from history or via
GET /api/admin/research/exports/[id]
7. Set up status webhooks (optional)
On Settings tab → Status webhook:
- Webhook URL — receives POST on status change
- Signing secret — enables
X-HCLAB-Signature: sha256=…HMAC header - Notify on statuses — check specific statuses, or leave all unchecked to fire on every change
Deliveries are queued and processed by cron (/api/cron/process-webhooks). View delivery history on the study webhook panel.
8. Configure dual-rater coding
Open Coding tab:
- Save rubric — JSON dimensions array:
[
{ "id": "argument_quality", "label": "Argument quality", "min": 1, "max": 4 },
{ "id": "evidence_quality", "label": "Evidence quality", "min": 1, "max": 4 }
]
- Task slugs — comma-separated slugs to include (empty = default writable task types)
- Add raters — by email; user must have signed up first
- Generate assignments — requires rubric + at least 2 raters
Generation creates one assignment per (completed response × rater). Each response gets a shared blind label (R-001, …) with no condition exposed to raters.
Raters score at /research/code. Cohen's κ appears on the Coding tab when two raters submit scores for the same response.
9. Configure inferential analysis
Open Analytics tab → Inferential analysis panel:
- Set outcome fields — column names from the wide analysis dataset, e.g.
task_agency-baseline_agency_ownership - Optionally set covariates
- Save — the panel runs descriptive stats by condition and one-way ANOVA (F, p, η²)
Outcome field names follow the pattern task_{taskSlug}_{fieldId} for survey/writing responses. Rater dimension columns use rater_{dimensionId}.
Note:
analysisConfigis saved from the Analytics page, not the main study form.
How exports and analytics connect
Participant completes tasks
↓
TaskResponse rows (JSON payloads)
↓
buildWideAnalysisRows() → ANALYSIS_WIDE CSV
↓
analysisConfig.outcomeFields → ANOVA by condition
For coding reliability, submitted CodingScore rows feed κ calculations separately from the wide export.
PFDF seed template
Running npm run db:seed creates a reference study you can inspect and clone conceptually:
| Property | Value |
|---|---|
| Slug | purposeful-friction-pilot |
| URL | /studies/purposeful-friction-pilot |
| Arms | non-ai, frictionless-ai, purposeful-friction-ai |
| Completion prefix | PFDF |
| Demo participants | pfdf-demo-*@thehclab.com / password pfdf-demo |
Seed files:
prisma/seed-pfdf-study.ts— study, conditions, tasks, rubric, analysis configprisma/seed-pfdf-demo-participants.ts— synthetic completions + coding scores
Use this as a working example of multi-arm task chains, completion codes, exports, and inferential panels.
Environment variables (research ops)
| Variable | Purpose |
|---|---|
CRON_SECRET |
Bearer auth for cron routes (required in production) |
EVENT_QUEUE_DISABLED |
"true" = write events synchronously (debug) |
EXPORT_S3_BUCKET, AWS_*, EXPORT_S3_* |
Large export storage |
OPENAI_API_KEY, OPENAI_MODEL |
AI-assisted task suggestions |
RESEND_API_KEY, RESEARCH_ADMIN_EMAIL |
Enrollment/completion/export emails |
AUTH_URL / NEXT_PUBLIC_SITE_URL |
Export download links in scheduled emails |
See Usage & configuration → Environment variables for the full reference.
Related documentation
- Studies & participation — participant-facing flows, consent, task runner
- Background jobs — cron endpoints and event queue
- Roles & permissions — researcher vs admin access