Skip to content

detect_signals() raises TypeError on every phased chart #141

Closed

Description

@cnicholas

What happens

detect_signals() raises on every chart run with phased=True: X or mR, the combined by=[] chart or a stratified one, called directly or after focus().

import processbehavior as pb

study = pb.formulate(pb.make_design(state=1, seed=42), response='y', time='time',
                     factors=['factor 1', 'factor 2'])
study.execute(chart='X', by=[], phased=True).detect_signals()
# TypeError: unsupported operand type(s) for -: 'float' and 'NoneType'

r = study.execute(chart='X', by=['factor 1'], phased=True)
r.focus(r.strata[0]).detect_signals()          # same TypeError

Why

  • A phased chart gives each phase its own centre line, so its statistics are {'N': None, 'center': None, 'lpl': None, 'upl': None, 'limits_vary': True} (the contract since 5c9929c). The per-phase centre and limits are on the rows.
  • SignalDetector.detect sends every limits_vary chart to _calculate_per_row_zones (processbehavior/signals/detector.py, ~337–341), which computes data['upl'] - stats['center']. That was written for Xbar/S with unequal subgroup sizes, where the limits move but the centre is one number.
  • Phased charts also set metadata['run_rules_applicable'] = False (70503be: "zones assume fixed center"). Only the renderer reads it; result_signals._detect_for_chart does not, so the detector tries to build zones for rules 2–8 at all.
  • No test runs signal detection on a phased chart.

Impact

  • The chart itself is right: the per-phase limits are correct, and points beyond them are flagged (beyond_limits, used by the plot, chart_table and get_signals()).
  • detect_signals() returns nothing. A caller that catches the error, as the processbehavior.com app does, can only report that signal detection failed.

Proposed change

  1. result_signals._detect_for_chart passes metadata['run_rules_applicable'] to SignalDetector.detect.
  2. When it is False and the rules are the chart-type default, the default set is Rule 1 only. Rules 2–8 are not applicable rather than skipped, so the result is not marked partial on their account. This is how Xbar/S are already Rule-1-only.
  3. An explicit rule list runs as given (the existing "explicit rules, no filtering" contract in SignalConfig.get_rules_for_chart). The per-row zones, the run rule and the violation records use each row's own centre when the scalar centre is None.

Related, not fixed here

Calling detect_signals() at the top level of any stratified result raises ValidationError: Missing required statistics: ['upl', 'lpl', 'center'], followed by the list of strata. result.focus(stratum).detect_signals() works. The message could say so.

Context

Found while scoping phased limits for Xbar/S.

Activity

  1. added a commit that references this issue on Oct 14, 2026
  2. cnicholas commented on Oct 14, 2026

    @cnicholas
    OwnerAuthor

    Fixed in #145, merged to main with the 0.4.0 stack (e982d32).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions