---
title: "Multimodal Process IRT: Responses, Time, Gaze, and Missingness"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Multimodal Process IRT: Responses, Time, Gaze, and Missingness}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

# Measurement channels, not feature dumping

The 0.7 architecture treats response-process observations as explicit
measurement channels. A process variable is not automatically useful merely
because it predicts an outcome. It should have a declared role, latent target,
family, provenance, and validation programme.

```{r}
spec <- irt_model_spec(
  id = "accuracy_time_gaze",
  latent = c("ability", "speed", "engagement"),
  channels = list(
    response = irt_response_channel("2pl"),
    rt       = irt_rt_channel("lognormal"),
    gaze     = irt_count_channel("negative_binomial")
  ),
  status = "experimental"
)
spec
```

Other channels include nominal choices, survival/event time, compositional AOI
measurements, process sequences, functional trajectories, and bounded
continuous process measures.

```{r}
irt_continuous_channel("censored_normal", value = "evidence_dwell_proportion")
irt_sequence_channel("scanpath", family = "hmm")
```

# Registry

```{r}
list_irt_models()
```

Models can be registered and later promoted only after their validation evidence
passes an explicit gate.

```{r, eval=FALSE}
register_irt_model(spec)
validate_irt_model("accuracy_time_gaze", validation_data)
promote_irt_model("accuracy_time_gaze", evidence = evidence_object)
```

# Response + response time + gaze

`fit_joint_gaze_rt_irt()` supports two roles:

* `engine = "reference"` gives a transparent crossed-effects decomposition for
  development and validation;
* `engine = "brms"` builds a multivariate Bayesian model with shared grouping
  identifiers, which is the preferred route when a full Bayesian joint model is
  scientifically required.

```{r, eval=FALSE}
fit <- fit_joint_gaze_rt_irt(
  data = trials,
  response = "correct",
  rt = "rt_ms",
  gaze = "fixation_count",
  person = "person_id",
  item = "item_id",
  gaze_family = "negative_binomial",
  engine = "brms"
)
plot(fit)
```

The function does not claim that a convenient reference engine is identical to
the published three-way Bayesian model. That distinction is kept in the fit
metadata.

# Graded responses

The same idea extends to ordinal/graded outcomes:

```{r, eval=FALSE}
fit_joint_graded_rt_process_irt(
  data = trials,
  response = "rating",
  rt = "rt_ms",
  process = "fixation_count",
  person = "person_id",
  item = "item_id",
  engine = "brms"
)
```

This is experimental until parameter recovery and external validation are
completed.

# Nominal distractors + option gaze

Binary correct/incorrect scoring discards which alternative was selected. A
nominal process model can retain both the selected option and visual
consideration of each option.

```{r, eval=FALSE}
fit <- fit_nominal_gaze_irt(
  data = option_trials,
  response_option = "choice",
  option_gaze = c("dwell_A", "dwell_B", "dwell_C", "dwell_D"),
  item = "item_id",
  person = "person_id"
)

option_process_information(fit)
distractor_process_map(fit)
audit_distractor_attention(fit)
plot(fit)
```

Interpretation should stay process-based: an option attracted or retained more
visual processing. This does not establish why.

# Missingness as a process

```{r, eval=FALSE}
missing <- classify_item_missingness(
  trials,
  response = "response",
  reached = "reached",
  inspected = "inspected_response_region",
  started = "started_response"
)

audit <- fit_omission_survival_irt(
  data = missing,
  response = "correct",
  response_time = "rt",
  omission_time = "elapsed",
  reached = "reached",
  person = "person_id",
  item = "item_id"
)
plot(audit)
```

The classification separates not reached, reached but not inspected, inspected
omission, and started-but-unanswered cases instead of converting them all to
`NA`.

# Device and algorithm facets

```{r, eval=FALSE}
facet_fit <- fit_manyfacet_process_irt(
  data = trials,
  response = "correct",
  process = "fixation_count",
  person = "person_id",
  item = "item_id",
  device = "device",
  session = "session",
  algorithm = "fixation_algorithm"
)

facet_effects(facet_fit)
audit_process_measurement_invariance(facet_fit)
plot(facet_fit)
```

A complementary `generalizability_process_study()` decomposes variance before a
full measurement model is attempted.

# Bounded gaze measures

AOI proportions and similar process quantities often have real mass at 0 and 1.
The conditional censored-normal calibration helper respects those bounds rather
than silently applying ordinary Gaussian regression.

```{r, eval=FALSE}
cn <- fit_censored_normal_process_irt(
  response_matrix = aoi_proportion_matrix,
  theta = calibration_theta,
  lower = 0,
  upper = 1
)
predict(cn, theta = seq(-2, 2, length.out = 9))
```

This is conditional calibration given supplied `theta`; it is not labelled as
the full marginal EM estimator from the 2026 CNRM paper.
