Survey System
iAm implements a hybrid survey architecture that combines SurveyJS for modern UI rendering with Neo4j graph storage for rich analytics. This approach provides excellent user experience during data collection while enabling powerful cross-survey analysis through the graph.
Architecture Overview
| Layer | Technology | Purpose |
|---|---|---|
| UI | SurveyJS | Modern, accessible form rendering with validation |
| State | Survey entity | SurveyJS state serialization for pause/resume |
| Storage | Neo4j graph | Individual Response entities for question-level analytics |
The Multi-Layer Survey Model
Survey (The Instance)
A Survey represents a single completion of a survey instrument.
- Links to the SurveyTypeVersion it implements
- Stores serialized SurveyJS state (
surveyState) for persistence and resumption - Tracks timing metadata (begun, paused, completed timestamps)
- Owned by the Observer who completed it
Response (The Individual Answer)
Each answer to a survey question is stored as a separate Response entity in the graph.
- Contains the actual response value (string, number, or structured data)
- Links to the Survey via a
REPORTEDrelationship - Links to the specific Question via a
TOrelationship - Includes precise timestamp of when the question was answered
Storing responses as individual graph entities (rather than a single JSON blob) enables:
- Question-level analytics across all survey instances
- Cross-observer comparison at the question level
- Temporal analysis of individual response patterns
- Flexible querying and aggregation
Question (The Definition)
A Question defines the structure and validation rules for a survey item.
- Prompt text and question type (Scale, NumericRange, Number)
- Validation rules and ordering (index, groupIndex, page)
- Optional link to a QuestionScale for structured response options
- Maps to SurveyJS question definitions for UI rendering
SurveyTypeVersion (The Instrument)
A SurveyTypeVersion is the complete, immutable definition of a survey instrument.
- Contains ordered Questions with scales and validation rules
- Generates SurveyJS survey models for UI rendering
- Immutable once published for research reproducibility
- Includes survey metadata, instructions, and timing configuration
QuestionScale (The Response Scale)
A QuestionScale defines response options for structured questions.
- Scale values and their semantic definitions (e.g., 1-5 Likert scale)
- Maps to SurveyJS choice definitions and validation rules
- Ensures consistent response interpretation across observers
Integration Flow
1. SurveyTypeVersion → generates SurveyJS model
│
2. SurveyJS handles UI rendering, validation, navigation
│
3. Survey entity stores serialized SurveyJS state
│ (enables pause and resume)
│
4. On completion: individual Response entities created
│
5. Each Response linked to its Question via TO relationship
│
6. Graph queries enable rich analytics across all responses
Example: Likert Scale Survey
Consider a mindfulness questionnaire with a 1-5 Likert scale:
SurveyTypeVersion defines the instrument:
- 15 questions across 5 facets
- Each question uses a shared QuestionScale (1="Never true" to 5="Very often true")
During completion:
- SurveyJS renders the form with radio buttons
- Observer selects responses
- Survey entity stores partial state if paused
On submission:
- 15 Response entities created in the graph
- Each Response stores the numeric value and links to its Question
- Researchers can query: "Show me all responses to Question 3 across all participants in Cohort A"
Cypher Query Example
// Get all responses for a specific survey type across a cohort
MATCH (cohort:Cohort {id: "cohort-id"})
-[:HAS]->(task:CollectionTask)
-[:IMPLEMENTS]->(stv:SurveyTypeVersion)
-[:IMPLEMENTS]->(question:Question)
MATCH (cohort)-[:HAS]->(observer:Observer)
MATCH (observer)<-[:HAS]-(survey:Survey)
-[:IMPLEMENTS]->(stv)
MATCH (survey)-[:REPORTED]->(response:Response)
-[:TO]->(question)
RETURN observer.alias, question.prompt, response.value
ORDER BY observer.alias, question.index