Skip to main content

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

LayerTechnologyPurpose
UISurveyJSModern, accessible form rendering with validation
StateSurvey entitySurveyJS state serialization for pause/resume
StorageNeo4j graphIndividual 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 REPORTED relationship
  • Links to the specific Question via a TO relationship
  • 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