Skip to content

feat(psychometric): expose grand-mean-centered event-time lag - #332

Draft
seonghobae wants to merge 4 commits into
agent/psychometric-cwc-irregular-event-lag-probefrom
agent/psychometric-grand-mean-event-lag-probe
Draft

feat(psychometric): expose grand-mean-centered event-time lag#332
seonghobae wants to merge 4 commits into
agent/psychometric-cwc-irregular-event-lag-probefrom
agent/psychometric-grand-mean-event-lag-probe

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Stacked on #327 (cab5e58). One atomic public grand-mean-centered event-time lag on that CWC head. Not CWC. Not RI-CLPM. Not DSEM. Not a second invariance crate. Does not add another Driver p.16 std restoration.

Maps (distinct from #327 CWC).

  1. center_grand_mean_event_lags subtracts the sample grand mean, then emits consecutive residuals as LaggedWithinResidual pairs. Consecutive times are ordered with f64::total_cmp. Singleton clusters are skipped.
  2. recover_grand_mean_centered_irregular_residual_log_rate is the pairwise mean of Voelkle, Oud, Davidov, and Schmidt (2012, Eq. 7) on nonzero same-sign residuals. When |later| / |earlier| is finite the rate is ln(|later| / |earlier|) / Δt; overflowed or underflowed ratios use (ln|later| − ln|earlier|) / Δt. The pairwise mean is incremental so two finite rates whose raw sum overflows stay representable.
  3. refuse_grand_mean_centered_log_rate_as_within_person_lag always fails closed.

Why this is not a within-person lag. Hamaker, Kuiper, and Grasman (2015, p. 104; UvA PDF opened 2026-08-30T20:58Z from https://pure.uva.nl/ws/files/2688454/168970_Hamaker_Kuiper_Grasman_2015_A_Critique_of_Cross_Lagged_Panel_Model.pdf) show that lagged relations formed from deviations around shared (grand) means implicitly assume there are no trait-like between-person differences. Those differences contaminate coefficients supposed to represent within-person change. The RI-CLPM lagged relations use within-person deviations from stable means rather than grand means. This helper is not RI-CLPM.

T=2 distinction from CWC. T=2 CWC is always r, −r (empty admissible, InvalidNumericInput). T=2 CGM can keep same-sign pairs when clusters sit on opposite sides of the grand mean. When cluster means coincide, CGM equals CWC.

Fail closed. Non-event clock, empty/singleton-only series, fewer than two clusters, non-positive interval, overflowing grand mean, finite grand mean whose residual overflows, tiny Δt with a huge log-ratio, a non-finite event time, and empty admissible set after skipping zero/opposite-sign pairs. Treating the CGM log-rate as a within-person lag always fails closed.

Local verify (147c844). cargo fmt on touched Rust; clippy -p psychometric_core --all-targets --all-features -- -D warnings clean; cargo test -p psychometric_core: lib 212, scientific-claim 53, multilevel 109.

Do not self-approve. Independent non-author APPROVE is required before squash-merge, together with exact-head required-check success. Queued/pending/skipped/cancelled/stale/predecessor-head is non-passing.

@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copy link
Copy Markdown
Contributor Author

Local verify at dbb8108 (stacked on #327 aeead97):

  • cargo fmt --all
  • cargo clippy -p psychometric_core --all-targets -- -D warnings clean
  • cargo test -p psychometric_core --lib 209 passed
  • scientific_claim_boundary_contract 53 passed
  • multilevel_event_time_recovery_contract 108 passed

Hamaker, Kuiper, and Grasman (2015, p. 104) is the claim boundary: CGM lagged relations confound stable between-person differences with within-person change. T=2 CWC is empty (r, −r); T=2 CGM can keep same-sign pairs. Not RI-CLPM.

Do not self-approve. Independent non-author APPROVE is still missing. naru-osg invitation 331032327 remains pending (read, 2026-08-30T20:53:43Z). Only collaborator is the author, so review cannot be requested from a collaborator.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 3 potential issues.

Devin Review

Comment thread crates/psychometric_core/src/event_time.rs Outdated
Comment on lines +6904 to +6923
let mut groups: BTreeMap<u64, Vec<ClusteredEventScore>> = BTreeMap::new();
let mut score_sum = 0.0_f64;
for &row in rows {
if !row.event_time.is_finite() || !row.score.is_finite() {
return Err(PsychometricError::InvalidNumericInput);
}
score_sum += row.score;
groups.entry(row.cluster_key).or_default().push(row);
}
if groups.len() < 2 {
return Err(PsychometricError::InsufficientClusters);
}
let grand_mean = score_sum / (rows.len() as f64);
if !grand_mean.is_finite() {
return Err(PsychometricError::InvalidNumericInput);
}
let mut pairs = Vec::new();
for occasions in groups.values_mut() {
if occasions.len() < 2 {
continue;

@devin-ai-integration devin-ai-integration Bot Aug 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Singletons still affect centering

center_grand_mean_event_lags includes singleton observations in the sample grand mean, then omits them only from pair construction. This matches observation-level grand-mean centering.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +6970 to +6978
for pair in lagged {
if !same_sign_nonzero(pair.earlier_residual, pair.later_residual) {
continue;
}
sum += voelkle_same_sign_log_rate(
pair.earlier_residual,
pair.later_residual,
pair.event_delta,
)?;

@devin-ai-integration devin-ai-integration Bot Aug 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Extreme residual ratios remain admissible

voelkle_same_sign_log_rate subtracts absolute logarithms after sign validation. Finite same-sign pairs survive direct-ratio overflow or underflow without admitting zero or opposite-sign pairs.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

#327 head is now e0177eb (incremental CWC pairwise mean). This PR is still stacked on predecessor aeead97 and is CONFLICTING/DIRTY. Do not merge. Independent non-author APPROVE still required. Not rebasing this branch from the CWC slice in this cycle; rebase onto e0177eb is a separate write.

@seonghobae
seonghobae force-pushed the agent/psychometric-grand-mean-event-lag-probe branch from dbb8108 to 4db3d9b Compare August 30, 2026 21:41

Copy link
Copy Markdown
Contributor Author

Rebased onto #327 79262df (was dirty on stale aeead97). Head is now 4db3d9b.

Kept the Hamaker et al. (2015, p. 104) CGM estimand. Wired recover_grand_mean_centered_irregular_residual_log_rate through the same Voelkle Eq. 7 ratio-ln plus overflow-safe incremental mean as CWC so a raw overflowing sum cannot drop a representable pairwise mean. Still not CWC, not RI-CLPM, not DSEM.

Local after rebase:

  • cargo fmt --all
  • cargo clippy -p psychometric_core --all-targets --all-features -- -D warnings
  • cargo test -p psychometric_core: lib 212, crate_contract 2, esem 5, multilevel 109, plausible-value 4, rubin/mean-gate 6, scientific-claim 53

Unpaywall 2026-08-30T21:36Z: Meredith (1993) and Mislevy (1991) remain is_oa: false. Still unread. Do not self-approve. Do not merge without independent non-author APPROVE and exact-head required-check success. #327 remains first in the executable queue.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Author COMMENT on 4db3d9b, not approval. Rebased onto #327 79262df. Independent non-author APPROVE is still required. Do not self-approve.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 2 new potential issues.

Devin Review

Comment on lines +320 to +321
/// Grand-mean-center consecutive event-time lags (not a within-person lag).
pub use event_time::center_grand_mean_event_lags;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Public estimand lacks PRD update

center_grand_mean_event_lags adds a public psychometric estimand without changing the approved PRD. Clarify its target status or version the PRD.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up, not a PRD version bump. center_grand_mean_event_lags is a refuse-closed sibling estimand: Hamaker, Kuiper, and Grasman (2015, p. 104) show lagged relations from grand-mean deviations are not within-person change. refuse_grand_mean_centered_log_rate_as_within_person_lag always fails closed. ADR 0005 already records the distinction. The approved product/measurement target is unchanged, so AGENTS.md item 14 does not require a PRD version change for this slice.

Comment thread docs/research/multilevel-event-time-recovery.md Outdated
@seonghobae
seonghobae force-pushed the agent/psychometric-grand-mean-event-lag-probe branch from 4db3d9b to 147c844 Compare August 30, 2026 21:52

Copy link
Copy Markdown
Contributor Author

Author note on 147c844, not approval. Rebased onto #327 cab5e58.

Nightly unique-site branch coverage on predecessor 4db3d9b was 3949/3960. Remaining holes after the CWC && fix:

  • production center_grand_mean_event_lags never took !event_time.is_finite() (score-NaN was covered; event-time NaN was not)
  • overflow/underflow reconstructions used dead continue / && arms on fixtures that are all same-sign extreme pairs

Also split the merged grand-mean and already-centered research bullets.

Local verify on 147c844: clippy -D warnings clean; lib 212; scientific-claim 53; multilevel 109.

Independent non-author APPROVE is still required. Do not self-approve. Do not merge without that APPROVE and exact-head required-check success.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Author COMMENT on 147c844, not approval. Rebased onto #327 cab5e58. Independent non-author APPROVE still required. Do not self-approve.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

Devin Review

Comment on lines +6962 to +6966
pairs.push(LaggedWithinResidual {
earlier_residual,
later_residual,
event_delta,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Grand-mean deviations bypass within-person refusal

center_grand_mean_event_lags returns LaggedWithinResidual, which recover_irregular_centered_residual_log_rate accepts as within-person input. Their direct composition succeeds, bypassing the mandatory refusal.

Prompt for agents
The new center_grand_mean_event_lags function constructs LaggedWithinResidual values, but that public type represents already-centered within-person residuals and is accepted by recover_irregular_centered_residual_log_rate. The PR's own tests demonstrate that composing these APIs succeeds. Introduce a distinct type or provenance-aware API for grand-mean deviations so they cannot enter the within-person recovery path. Keep the grand-mean-specific recovery available, and make any attempted reinterpretation as a within-person lag fail closed as required by CLAUDE.md.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed on d703946. center_grand_mean_event_lags now returns LaggedGrandMeanResidual. That type cannot enter recover_irregular_centered_residual_log_rate (Hamaker, Kuiper, & Grasman, 2015, p. 104).

Comment on lines +6936 to +6937
score_sum += row.score;
groups.entry(row.cluster_key).or_default().push(row);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Row ordering changes valid centering

When finite scores cancel mathematically, score_sum += row.score can overflow for one row order but not another. Identical observations then alternate between valid pairs and InvalidNumericInput.

Prompt for agents
center_grand_mean_event_lags computes score_sum with naive sequential addition before grouping and sorting. A dataset containing +f64::MAX and -f64::MAX can succeed in an alternating order but overflow in an order that places the positive values first, despite having the same finite mathematical grand mean and valid residuals. Compute the grand mean with an overflow-resistant, permutation-stable f64 strategy, and add a permutation test using identical rows in both orders.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed on d703946. Finite raw sums still use sum/n so coinciding cluster means match CWC. When that sum overflows, positives and negatives are averaged separately with the overflow-safe incremental mean and combined by count, so cancelling ±MAX scores keep a representable zero grand mean in either row order.

@seonghobae
seonghobae marked this pull request as draft August 30, 2026 22:03

Copy link
Copy Markdown
Contributor Author

Author COMMENT on d703946, not approval.

Devin on 147c844:

  • CGM pairs typed as LaggedWithinResidual could enter recover_irregular_centered_residual_log_rate
  • score_sum += row.score overflowed for cancelling ±MAX in one row order

center_grand_mean_event_lags now returns LaggedGrandMeanResidual. That type cannot enter the already-centered within-person helper (Hamaker, Kuiper, & Grasman, 2015, p. 104). Finite raw sums still use sum/n so coinciding cluster means match CWC. Overflowed sums sign-split the overflow-safe incremental mean and combine by count, so ±MAX cancel in either row order. An all-MAX series has a finite grand mean of MAX. Still not CWC, not RI-CLPM, not DSEM.

Local verify on d703946:

  • cargo fmt --all -- --check
  • cargo clippy -p psychometric_core --all-targets --all-features -- -D warnings
  • cargo test -p psychometric_core --lib 215 passed
  • scientific_claim_boundary_contract 53 passed
  • multilevel_event_time_recovery_contract 109 passed

Independent non-author APPROVE is still required. Do not self-approve. Do not merge without that APPROVE and exact-head required-check success. naru-osg invitation 331032327 remains pending (read). Only collaborator is the author, so review cannot be requested from a collaborator.

Public CGM pipeline is a distinct estimand from CWC. Hamaker, Kuiper,
and Grasman (2015, p. 104) show that lagged relations from grand-mean
deviations confound stable between-person differences with within-person
change. T=2 CWC is always r, -r; T=2 CGM can keep same-sign pairs.
Not CWC, not RI-CLPM, not DSEM.
…h items

Nightly branch coverage on 4db3d9b was 3949/3960. center_grand_mean_event_lags
never took !event_time.is_finite(); overflow/underflow reconstructions used
dead && and continue arms. Split the merged grand-mean and already-centered
research bullets.

Hamaker, Kuiper, and Grasman (2015) still names this not a within-person lag.
Not DSEM. Meredith (1993) / Mislevy (1991) still unread.
Devin on 147c844: LaggedWithinResidual from CGM bypassed
recover_irregular_centered_residual_log_rate. Naive score_sum of
±MAX overflowed in one row order. Hamaker, Kuiper, and Grasman
(2015, p. 104): grand-mean lagged relations are not within-person.

LaggedGrandMeanResidual cannot enter the within-person helper.
Finite raw sums still use sum/n so coinciding cluster means match
CWC. Overflowed sums sign-split the overflow-safe incremental mean.

Not CWC, not RI-CLPM, not DSEM. Do not self-approve.
@seonghobae
seonghobae force-pushed the agent/psychometric-grand-mean-event-lag-probe branch from d703946 to e90f3aa Compare August 31, 2026 00:03

Copy link
Copy Markdown
Contributor Author

Author COMMENT on e90f3aa, not approval. Rebased onto #327 8103ddc (rustfmt of predecessor cab5e58).

Scientific content is unchanged from d703946: LaggedGrandMeanResidual cannot enter the within-person helper; finite raw sums use sum/n; overflowed sums sign-split the overflow-safe incremental mean (Hamaker, Kuiper, & Grasman, 2015, p. 104). Not CWC, not RI-CLPM, not DSEM.

Independent non-author APPROVE is still required. Do not self-approve. Do not merge without that APPROVE and exact-head required-check success.

Public center_grand_mean_event_lags rustdoc linked crate-private
overflow_safe_running_mean. cargo doc -Dwarnings failed the
warning-free documentation job on e90f3aa. Name the overflow-safe
incremental mean in prose instead. Not CWC, not RI-CLPM, not DSEM.
Do not self-approve.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Author COMMENT on dabb769, not approval.

cargo doc -Dwarnings failed on predecessor e90f3aa because public center_grand_mean_event_lags rustdoc linked crate-private overflow_safe_running_mean. The public docs now name the overflow-safe incremental mean in prose (Hamaker, Kuiper, & Grasman, 2015, p. 104). Local: rustdoc -Dwarnings clean; clippy -D warnings clean; lib 215, scientific-claim 53, multilevel 109.

Independent non-author APPROVE is still required. Do not self-approve. Do not merge without that APPROVE and exact-head required-check success.

Copy link
Copy Markdown
Contributor Author

Author COMMENT on dabb769, not approval.

Warning-free rustdoc failed on e90f3aa (private-intra-doc-links to crate-private overflow_safe_running_mean). Head is now dabb769. Scientific content is unchanged: LaggedGrandMeanResidual cannot enter the within-person helper; finite raw sums use sum/n; overflowed sums sign-split the overflow-safe incremental mean (Hamaker, Kuiper, & Grasman, 2015, p. 104). Not CWC, not RI-CLPM, not DSEM.

Independent non-author APPROVE is still required. Do not self-approve.

seonghobae added a commit that referenced this pull request Sep 2, 2026
Form p_it = x_it − μ_t on aligned waves and map consecutive residuals
through Voelkle et al. (2012, Eq. 7). Those residuals still contain
trait-like between-person deviations, so the recovered rate is refused
as a within-person lag. This is not RI-CLPM, not CWC, not the #332
sample grand mean, and not DSEM.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request priority: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant