---
lang: en-GB
title: "Building a survey instrument: questions, plan, and model"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Building a survey instrument: questions, plan, and model}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{=html}
<style>
/* WCAG 2.2 AA pass: darker link and syntax-token colours (at least
   4.5:1 on the #f7f7f7 code background), wrapped code lines instead of a
   keyboard-inaccessible scroll region, empty per-line anchors removed from
   the accessibility tree, and 24px minimum TOC link targets. */
a { color: #0e7c7a; }
code span.at { color: #576419; }
code span.dv, code span.fl, code span.bn { color: #276245; }
code span.co { color: #396a80; }
/* Pandoc emits `pre > code.sourceCode { white-space: pre }` for screen, which
   outranks a bare `pre code` rule on specificity, so source chunks have to be
   named explicitly or they scroll sideways instead of wrapping. */
pre, pre code, pre > code.sourceCode { white-space: pre-wrap; word-break: break-word; }
pre > code.sourceCode > span { text-indent: -2em; padding-left: 2em; }
div.sourceCode { overflow: visible; }
pre.sourceCode a:empty { display: none; }
#TOC a { display: inline-block; min-height: 24px; }
</style>
```

```{r setup, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
library(surveyframe)
```

An `sframe` instrument records the research design, not only the questions. This
vignette builds a slice of the published Thailand digital marketing study
(Sharafuddin, Madhavan, and Wangtueai 2024) with the constructors shown one at a
time, then adds the analysis plan and a measurement model. The full study is
assembled in the worked-study vignette.

## Choice sets

Choice sets define reusable response options. Items reference them through
`choice_set`.

```{r choices}
likert5 <- sf_choices(
  "likert5",
  values = 1:5,
  labels = c("Strongly disagree", "Disagree",
             "Neither agree nor disagree", "Agree", "Strongly agree")
)

visitor <- sf_choices("visitor", c("first_time", "repeat"),
                      c("First-time visitor", "Repeat visitor"))
```

## Items and item types

Items are response variables unless their type is `section_break` or
`text_block`. Required items carry `required = TRUE`. Item IDs should stay
stable, because response columns reuse them and the analysis plan refers to
them. These items measure perceived value (DMPV) and tourist satisfaction (TS).

```{r items}
intro <- sf_item(
  "intro", "About your trip",
  type          = "section_break",
  section_intro = "Please rate your experience of digital booking and your visit."
)

dmpv_1 <- sf_item("dmpv_1", "The destination's online content was trustworthy.",
  type = "likert", required = TRUE, choice_set = "likert5", scale_id = "DMPV")
dmpv_2 <- sf_item("dmpv_2", "The online information was consistent across platforms.",
  type = "likert", required = TRUE, choice_set = "likert5", scale_id = "DMPV")
dmpv_3 <- sf_item("dmpv_3", "The digital channels offered good value for money.",
  type = "likert", required = TRUE, choice_set = "likert5", scale_id = "DMPV")
dmpv_4 <- sf_item("dmpv_4", "I was aware of the destination through digital channels.",
  type = "likert", required = TRUE, choice_set = "likert5", scale_id = "DMPV")

ts_1 <- sf_item("ts_1", "The trip met my expectations.",
  type = "likert", required = TRUE, choice_set = "likert5", scale_id = "TS")
ts_2 <- sf_item("ts_2", "My overall travel experience was satisfying.",
  type = "likert", required = TRUE, choice_set = "likert5", scale_id = "TS")
ts_3 <- sf_item("ts_3", "I felt comfortable at the destination.",
  type = "likert", required = TRUE, choice_set = "likert5", scale_id = "TS")

visitor_type <- sf_item("visitor_type", "I am a",
  type = "single_choice", required = TRUE, choice_set = "visitor")

attention <- sf_item("attention", "For quality control, please select Agree.",
  type = "single_choice", required = TRUE, choice_set = "likert5")
```

## Scales, reverse coding, and minimum valid items

A scale records item membership, the scoring method, any reverse-coded items,
and the minimum number of valid items needed to compute a score.

```{r scales}
dmpv <- sf_scale("DMPV", "Digital marketing perceived value",
  items = c("dmpv_1", "dmpv_2", "dmpv_3", "dmpv_4"), method = "mean", min_valid = 3)

ts <- sf_scale("TS", "Tourist satisfaction",
  items = c("ts_1", "ts_2", "ts_3"), method = "mean", min_valid = 2)
```

## Attention checks

Checks keep the quality-control logic with the instrument.

```{r checks}
attention_check <- sf_check(
  id = "attention_agree", item_id = "attention", type = "attention",
  pass_values = 4, fail_action = "flag", label = "Instructional attention check"
)
```

## Branching

A branch shows or hides an item based on an earlier answer. This one is a
placeholder for a repeat-visitor follow-up.

```{r branch}
repeat_branch <- sf_branch(
  item_id    = "ts_3",
  depends_on = "visitor_type",
  operator   = "==",
  value      = "repeat",
  action     = "show"
)
```

## The analysis plan

Each block binds a research question to a technique and to the variables that
fill each role. A reliability check expects `items`. A group comparison expects
a `group` and an `outcome`.

```{r plan}
analysis_plan <- list(
  list(id = "M1", research_question = "Is perceived value internally consistent?",
       family = "measurement", method = "reliability_alpha",
       roles = list(items = c("dmpv_1", "dmpv_2", "dmpv_3", "dmpv_4"))),
  list(id = "RQ1", research_question = "Do repeat visitors report higher satisfaction?",
       family = "group_comparison", method = "mann_whitney",
       roles = list(group = "visitor_type", outcome = "TS"),
       options = list(alpha = 0.05))
)
```

## Assemble the instrument

`sf_instrument()` takes the questions through `components` and the research
design through `analysis_plan` and `models`. The model is added with
`add_model()`, which checks each indicator against the instrument items.

```{r assemble}
instr <- sf_instrument(
  title       = "Digital marketing study (teaching slice)",
  version     = "1.0.0",
  description = "A slice of the published Thailand study for teaching the constructors.",
  authors     = "Research team",
  languages   = "en",
  components  = list(
    likert5, visitor,
    intro, dmpv_1, dmpv_2, dmpv_3, dmpv_4, ts_1, ts_2, ts_3,
    visitor_type, attention, dmpv, ts, attention_check, repeat_branch
  ),
  analysis_plan = analysis_plan
)

ts_model <- sf_model(
  id    = "ts_cfa",
  label = "Satisfaction measurement model",
  type  = "cfa",
  constructs = list(
    sf_construct("DMPV", "Perceived value", c("dmpv_1", "dmpv_2", "dmpv_3", "dmpv_4")),
    sf_construct("TS",   "Tourist satisfaction", c("ts_1", "ts_2", "ts_3"))
  )
)

instr <- add_model(instr, ts_model)
```

## Validate the instrument

`validate_sframe()` returns a diagnostic. Print it to read the result, and use
the accessors to get at the parts.

```{r validate}
validate_sframe(instr, strict = FALSE)
```

```{r validate-explore}
validation <- validate_sframe(instr, strict = FALSE)
sf_is_valid(validation)
sf_problems(validation)
```

In production, strict validation stops with a structured error when anything
is wrong. When it passes, `as_sframe()` recovers the instrument with its
validated stamp set.

```{r strict}
instr <- as_sframe(validate_sframe(instr))
sf_meta(instr)$validated
```

## Write and read `.sframe` files

The `.sframe` file is the portable instrument file. It keeps the validated
metadata, the analysis plan, and the model in one place.

```{r roundtrip}
path <- tempfile(fileext = ".sframe")
write_sframe(instr, path, overwrite = TRUE)

loaded <- read_sframe(path)
inherits(loaded, "sframe")
sf_meta(loaded)$title
length(sf_plan(loaded))
length(loaded$models)
```

## GUI option

The same instrument, plan, and model can be authored in SurveyBuilder, which
saves a `.sframe` file for use in R.

```{r gui, eval = FALSE}
launch_builder()
launch_builder_demo(open = FALSE)
```
