<!-- canonical: https://efficientnewlanguage.org/eml-p/projects/P159-risk-flags/ | updated: 2026-10-18 -->

# P159 Risk flags

Accounts flagged from the signals against them, the flag stored bare and with its evidence: a withdrawn signal leaves the bare flag standing, and the evidence store recomputes just the flags that rested on it.

EML-P project `projects/risk-flags` in the EML language repo: 2 module(s), entry `main.eml`, terminal UI. There, `eml project run projects/risk-flags` runs it and `eml project verify projects/risk-flags` replays every session under CPython (two hash seeds) and in the interpreter; the site build replays every session in the interpreter again and publishes a session only if its screen matches.

Built on verified corpus cases: flag-outlives-its-evidence (https://efficientnewlanguage.org/cases/297-flag-outlives-its-evidence/).

## Sessions

### bad-input - interpreter: byte-equal to the golden

Input:

```text
0
x
2

2
s-99
2
s-1
2
s-1
3

3
6
3
2

3
2
4
3
2
x
3
5
1
5
5
4
6
```

Screen:

```text
== Risk flags ==
Accounts flagged from the signals against them: a bare flag, and a flag that keeps its evidence.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 0
Pick a number from 1 to 6.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> x
Pick a number from 1 to 6.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 2
signal to withdraw (e.g. s-1)> 
Cancelled.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 2
signal to withdraw (e.g. s-1)> s-99
There is no signal s-99 on file.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 2
signal to withdraw (e.g. s-1)> s-1
Signal s-1 (chargeback on a-1) withdrawn.
  flag only: a-1 stays flagged; nothing records that the flag came from s-1.
  with its evidence: a-1's flag rested on s-1; recomputed, it clears.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 2
signal to withdraw (e.g. s-1)> s-1
There is no signal s-1 on file.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 3
account (1 to 5)> 
Cancelled.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 3
account (1 to 5)> 6
Type an account from 1 to 5.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 3
account (1 to 5)> 2
severity (1 to 3)> 
Cancelled.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 3
account (1 to 5)> 2
severity (1 to 3)> 4
Type a severity from 1 to 3.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 3
account (1 to 5)> 2
severity (1 to 3)> x
Type a severity from 1 to 3.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 3
account (1 to 5)> 5
severity (1 to 3)> 1
Signal s-8 of severity 1 added on a-5; both stores recompute a-5: flagged.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 5
The nightly job recomputes all 5 flags from the signals on file; 1 changes.
It clears what a withdrawal left behind, a day late, and only because it recomputes every flag; the evidence store recomputed only the flags that rested on what was withdrawn.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 5
The nightly job recomputes all 5 flags from the signals on file; none changes.
Nothing had gone stale since the last recompute.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 4
Each stored flag against the flag the rule gives on the signals on file now:
  flag only:              should have cleared none; should have been raised none
  flag with its evidence: should have cleared none; should have been raised none

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 6
Bye.
```

### basic - interpreter: byte-equal to the golden

Input:

```text
1
4
2
s-1
2
s-3
1
4
3
3
3
1
5
4
6
```

Screen:

```text
== Risk flags ==
Accounts flagged from the signals against them: a bare flag, and a flag that keeps its evidence.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 1
  account  signals               now      flag only  flag with its evidence
  a-1      s-1:3                 flagged  flagged    flagged (s-1)
  a-2      s-2:1, s-3:1          flagged  flagged    flagged (s-2, s-3)
  a-3      s-4:2                 -        -          -
  a-4      s-5:1, s-6:2          flagged  flagged    flagged (s-5, s-6)
  a-5      s-7:1                 -        -          -
  signal:severity; the rule: two or more signals, or any one of severity 3

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 4
Each stored flag against the flag the rule gives on the signals on file now:
  flag only:              should have cleared none; should have been raised none
  flag with its evidence: should have cleared none; should have been raised none

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 2
signal to withdraw (e.g. s-1)> s-1
Signal s-1 (chargeback on a-1) withdrawn.
  flag only: a-1 stays flagged; nothing records that the flag came from s-1.
  with its evidence: a-1's flag rested on s-1; recomputed, it clears.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 2
signal to withdraw (e.g. s-1)> s-3
Signal s-3 (new device on a-2) withdrawn.
  flag only: a-2 stays flagged; nothing records that the flag came from s-3.
  with its evidence: a-2's flag rested on s-3; recomputed, it clears.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 1
  account  signals               now      flag only  flag with its evidence
  a-1      none                  -        flagged    -
  a-2      s-2:1                 -        flagged    -
  a-3      s-4:2                 -        -          -
  a-4      s-5:1, s-6:2          flagged  flagged    flagged (s-5, s-6)
  a-5      s-7:1                 -        -          -
  signal:severity; the rule: two or more signals, or any one of severity 3

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 4
Each stored flag against the flag the rule gives on the signals on file now:
  flag only:              should have cleared a-1, a-2; should have been raised none
  flag with its evidence: should have cleared none; should have been raised none
A withdrawn signal leaves the bare flag standing: the answer survived, and nothing says what it was the answer to.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 3
account (1 to 5)> 3
severity (1 to 3)> 3
Signal s-8 of severity 3 added on a-3; both stores recompute a-3: flagged.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 1
  account  signals               now      flag only  flag with its evidence
  a-1      none                  -        flagged    -
  a-2      s-2:1                 -        flagged    -
  a-3      s-4:2, s-8:3          flagged  flagged    flagged (s-4, s-8)
  a-4      s-5:1, s-6:2          flagged  flagged    flagged (s-5, s-6)
  a-5      s-7:1                 -        -          -
  signal:severity; the rule: two or more signals, or any one of severity 3

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 5
The nightly job recomputes all 5 flags from the signals on file; 2 change.
It clears what a withdrawal left behind, a day late, and only because it recomputes every flag; the evidence store recomputed only the flags that rested on what was withdrawn.

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 4
Each stored flag against the flag the rule gives on the signals on file now:
  flag only:              should have cleared none; should have been raised none
  flag with its evidence: should have cleared none; should have been raised none

1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit
choice> 6
Bye.
```

## Modules

### main.eml

```eml
# P159 risk flags: the accounts with their signals and both stored flags,
# withdrawing a signal, adding one, the nightly recompute, and the checks
# against the flags re-derived now.
import flags

def plain(s):
    0 => i
    len(s) => j
    while i < j and s[i] == " ":
        i + 1 => i
    while j > i and s[j - 1] == " ":
        j - 1 => j
    return s[i:j]

def pad(s, n):
    s => out
    while len(out) < n:
        out + " " => out
    return out

def yes(b):
    if b:
        return "flagged"
    return "-"

def listed(xs):
    "" => out
    for x in xs:
        if out == "":
            x => out
        else:
            out + ", " + x => out
    if out == "":
        return "none"
    return out

def show(signals, pl, pr):
    ("  " + pad("account", 9) + pad("signals", 22) + pad("now", 9) + pad("flag only", 11) + "flag with its evidence") ^0
    for a in flags.accounts():
        [] => ids
        for s in flags.against(signals, a):
            ids + [s[0] + ":" + str(s[2])] => ids
        if len(pr[a]) > 0:
            "flagged (" + listed(pr[a]) + ")" => ev
        else:
            "-" => ev
        ("  " + pad(a, 9) + pad(listed(ids), 22) + pad(yes(flags.derive(signals, a)), 9) + pad(yes(pl[a]), 11) + ev) ^0
    "  signal:severity; the rule: two or more signals, or any one of severity 3" ^0

def withdraw(signals, pl, pr):
    plain(input("signal to withdraw (e.g. s-1)> ")) => id
    if id == "":
        "Cancelled." ^0
        return [signals, pl, pr]
    flags.find(signals, id) => k
    if k < 0:
        ("There is no signal " + id + " on file.") ^0
        return [signals, pl, pr]
    signals[k] => s
    flags.without(signals, id) => after
    ("Signal " + id + " (" + s[3] + " on " + s[1] + ") withdrawn.") ^0
    if pl[s[1]]:
        ("  flag only: " + s[1] + " stays flagged; nothing records that the flag came from " + id + ".") ^0
    else:
        ("  flag only: " + s[1] + " was not flagged, and nothing changes.") ^0
    {} => npr
    [] => touched
    for a in flags.accounts():
        False => rested
        for e in pr[a]:
            if e == id:
                True => rested
        if rested:
            flags.evidence(after, a) => npr[a]
            touched + [a] => touched
        else:
            pr[a] => npr[a]
    if len(touched) == 0:
        "  with its evidence: no flag rested on it, and nothing is recomputed." ^0
    for a in touched:
        if len(npr[a]) == 0:
            ("  with its evidence: " + a + "'s flag rested on " + id + "; recomputed, it clears.") ^0
        else:
            ("  with its evidence: " + a + "'s flag rested on " + id + "; recomputed, it stands on " + listed(npr[a]) + ".") ^0
    return [after, pl, npr]

def add(signals, pl, pr, n):
    plain(input("account (1 to 5)> ")) => t
    if t == "":
        "Cancelled." ^0
        return [signals, pl, pr, n]
    if t != "1" and t != "2" and t != "3" and t != "4" and t != "5":
        "Type an account from 1 to 5." ^0
        return [signals, pl, pr, n]
    flags.accounts()[int(t) - 1] => a
    plain(input("severity (1 to 3)> ")) => v
    if v == "":
        "Cancelled." ^0
        return [signals, pl, pr, n]
    if v != "1" and v != "2" and v != "3":
        "Type a severity from 1 to 3." ^0
        return [signals, pl, pr, n]
    "s-" + str(n) => id
    signals + [[id, a, int(v), "new signal"]] => after
    {} => npl
    {} => npr
    for x in flags.accounts():
        pl[x] => npl[x]
        pr[x] => npr[x]
    flags.derive(after, a) => npl[a]
    flags.evidence(after, a) => npr[a]
    ("Signal " + id + " of severity " + v + " added on " + a + "; both stores recompute " + a + ": " + yes(npl[a]) + ".") ^0
    return [after, npl, npr, n + 1]

def checks(signals, pl, pr):
    [] => stale1
    [] => missing1
    [] => stale2
    [] => missing2
    for a in flags.accounts():
        flags.derive(signals, a) => now
        if pl[a] and not now:
            stale1 + [a] => stale1
        if not pl[a] and now:
            missing1 + [a] => missing1
        if len(pr[a]) > 0 and not now:
            stale2 + [a] => stale2
        if len(pr[a]) == 0 and now:
            missing2 + [a] => missing2
    "Each stored flag against the flag the rule gives on the signals on file now:" ^0
    ("  flag only:              should have cleared " + listed(stale1) + "; should have been raised " + listed(missing1)) ^0
    ("  flag with its evidence: should have cleared " + listed(stale2) + "; should have been raised " + listed(missing2)) ^0
    if len(stale1) > 0:
        "A withdrawn signal leaves the bare flag standing: the answer survived, and nothing says what it was the answer to." ^0

def nightly(signals, pl):
    flags.plain_store(signals) => npl
    0 => changed
    for a in flags.accounts():
        if npl[a] != pl[a]:
            changed + 1 => changed
    if changed == 0:
        "none changes" => said
    elif changed == 1:
        "1 changes" => said
    else:
        str(changed) + " change" => said
    ("The nightly job recomputes all " + str(len(flags.accounts())) + " flags from the signals on file; " + said + ".") ^0
    if changed > 0:
        "It clears what a withdrawal left behind, a day late, and only because it recomputes every flag; the evidence store recomputed only the flags that rested on what was withdrawn." ^0
    else:
        "Nothing had gone stale since the last recompute." ^0
    return npl

flags.opening() => signals
flags.plain_store(signals) => pl
flags.proven_store(signals) => pr
8 => next_id

"== Risk flags ==" ^0
"Accounts flagged from the signals against them: a bare flag, and a flag that keeps its evidence." ^0
True => running
while running:
    "" ^0
    "1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit" ^0
    plain(input("choice> ")) => choice
    if choice == "1":
        show(signals, pl, pr)
    elif choice == "2":
        withdraw(signals, pl, pr) => r
        r[0] => signals
        r[1] => pl
        r[2] => pr
    elif choice == "3":
        add(signals, pl, pr, next_id) => r
        r[0] => signals
        r[1] => pl
        r[2] => pr
        r[3] => next_id
    elif choice == "4":
        checks(signals, pl, pr)
    elif choice == "5":
        nightly(signals, pl) => pl
    elif choice == "6":
        False => running
    else:
        "Pick a number from 1 to 6." ^0
"Bye." ^0
```

Python projection of main.eml:

```python
import flags

def plain(s):
    i = 0
    j = len(s)
    while i < j and s[i] == " ":
        i = i + 1
    while j > i and s[j - 1] == " ":
        j = j - 1
    return s[i:j]

def pad(s, n):
    out = s
    while len(out) < n:
        out = out + " "
    return out

def yes(b):
    if b:
        return "flagged"
    return "-"

def listed(xs):
    out = ""
    for x in xs:
        if out == "":
            out = x
        else:
            out = out + ", " + x
    if out == "":
        return "none"
    return out

def show(signals, pl, pr):
    print("  " + pad("account", 9) + pad("signals", 22) + pad("now", 9) + pad("flag only", 11) + "flag with its evidence")
    for a in flags.accounts():
        ids = []
        for s in flags.against(signals, a):
            ids = ids + [s[0] + ":" + str(s[2])]
        if len(pr[a]) > 0:
            ev = "flagged (" + listed(pr[a]) + ")"
        else:
            ev = "-"
        print("  " + pad(a, 9) + pad(listed(ids), 22) + pad(yes(flags.derive(signals, a)), 9) + pad(yes(pl[a]), 11) + ev)
    print("  signal:severity; the rule: two or more signals, or any one of severity 3")

def withdraw(signals, pl, pr):
    id = plain(input("signal to withdraw (e.g. s-1)> "))
    if id == "":
        print("Cancelled.")
        return [signals, pl, pr]
    k = flags.find(signals, id)
    if k < 0:
        print("There is no signal " + id + " on file.")
        return [signals, pl, pr]
    s = signals[k]
    after = flags.without(signals, id)
    print("Signal " + id + " (" + s[3] + " on " + s[1] + ") withdrawn.")
    if pl[s[1]]:
        print("  flag only: " + s[1] + " stays flagged; nothing records that the flag came from " + id + ".")
    else:
        print("  flag only: " + s[1] + " was not flagged, and nothing changes.")
    npr = {}
    touched = []
    for a in flags.accounts():
        rested = False
        for e in pr[a]:
            if e == id:
                rested = True
        if rested:
            npr[a] = flags.evidence(after, a)
            touched = touched + [a]
        else:
            npr[a] = pr[a]
    if len(touched) == 0:
        print("  with its evidence: no flag rested on it, and nothing is recomputed.")
    for a in touched:
        if len(npr[a]) == 0:
            print("  with its evidence: " + a + "'s flag rested on " + id + "; recomputed, it clears.")
        else:
            print("  with its evidence: " + a + "'s flag rested on " + id + "; recomputed, it stands on " + listed(npr[a]) + ".")
    return [after, pl, npr]

def add(signals, pl, pr, n):
    t = plain(input("account (1 to 5)> "))
    if t == "":
        print("Cancelled.")
        return [signals, pl, pr, n]
    if t != "1" and t != "2" and t != "3" and t != "4" and t != "5":
        print("Type an account from 1 to 5.")
        return [signals, pl, pr, n]
    a = flags.accounts()[int(t) - 1]
    v = plain(input("severity (1 to 3)> "))
    if v == "":
        print("Cancelled.")
        return [signals, pl, pr, n]
    if v != "1" and v != "2" and v != "3":
        print("Type a severity from 1 to 3.")
        return [signals, pl, pr, n]
    id = "s-" + str(n)
    after = signals + [[id, a, int(v), "new signal"]]
    npl = {}
    npr = {}
    for x in flags.accounts():
        npl[x] = pl[x]
        npr[x] = pr[x]
    npl[a] = flags.derive(after, a)
    npr[a] = flags.evidence(after, a)
    print("Signal " + id + " of severity " + v + " added on " + a + "; both stores recompute " + a + ": " + yes(npl[a]) + ".")
    return [after, npl, npr, n + 1]

def checks(signals, pl, pr):
    stale1 = []
    missing1 = []
    stale2 = []
    missing2 = []
    for a in flags.accounts():
        now = flags.derive(signals, a)
        if pl[a] and not now:
            stale1 = stale1 + [a]
        if not pl[a] and now:
            missing1 = missing1 + [a]
        if len(pr[a]) > 0 and not now:
            stale2 = stale2 + [a]
        if len(pr[a]) == 0 and now:
            missing2 = missing2 + [a]
    print("Each stored flag against the flag the rule gives on the signals on file now:")
    print("  flag only:              should have cleared " + listed(stale1) + "; should have been raised " + listed(missing1))
    print("  flag with its evidence: should have cleared " + listed(stale2) + "; should have been raised " + listed(missing2))
    if len(stale1) > 0:
        print("A withdrawn signal leaves the bare flag standing: the answer survived, and nothing says what it was the answer to.")

def nightly(signals, pl):
    npl = flags.plain_store(signals)
    changed = 0
    for a in flags.accounts():
        if npl[a] != pl[a]:
            changed = changed + 1
    if changed == 0:
        said = "none changes"
    elif changed == 1:
        said = "1 changes"
    else:
        said = str(changed) + " change"
    print("The nightly job recomputes all " + str(len(flags.accounts())) + " flags from the signals on file; " + said + ".")
    if changed > 0:
        print("It clears what a withdrawal left behind, a day late, and only because it recomputes every flag; the evidence store recomputed only the flags that rested on what was withdrawn.")
    else:
        print("Nothing had gone stale since the last recompute.")
    return npl

signals = flags.opening()
pl = flags.plain_store(signals)
pr = flags.proven_store(signals)
next_id = 8
print("== Risk flags ==")
print("Accounts flagged from the signals against them: a bare flag, and a flag that keeps its evidence.")
running = True
while running:
    print("")
    print("1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit")
    choice = plain(input("choice> "))
    if choice == "1":
        show(signals, pl, pr)
    elif choice == "2":
        r = withdraw(signals, pl, pr)
        signals = r[0]
        pl = r[1]
        pr = r[2]
    elif choice == "3":
        r = add(signals, pl, pr, next_id)
        signals = r[0]
        pl = r[1]
        pr = r[2]
        next_id = r[3]
    elif choice == "4":
        checks(signals, pl, pr)
    elif choice == "5":
        pl = nightly(signals, pl)
    elif choice == "6":
        running = False
    else:
        print("Pick a number from 1 to 6.")
print("Bye.")
```

### flags.eml

```eml
# P159 risk flags - accounts flagged from the signals against them, the flag
# stored two ways. As in the corpus case flag-outlives-its-evidence, a flag
# is computed once from what the system knew and stored as a yes or no; the
# signals it came from can be withdrawn - a chargeback reversed, a report
# retracted - and with no record of which signals a flag came from, nothing
# knows to clear it. The second store keeps each flag's evidence beside it,
# so a withdrawn signal reaches exactly the flags that rested on it. A
# signal is [id, account, severity, what]; the rule: two or more signals,
# or any one of severity 3.

def accounts():
    return ["a-1", "a-2", "a-3", "a-4", "a-5"]

def opening():
    return [["s-1", "a-1", 3, "chargeback"], ["s-2", "a-2", 1, "address change"], ["s-3", "a-2", 1, "new device"], ["s-4", "a-3", 2, "velocity"], ["s-5", "a-4", 1, "new device"], ["s-6", "a-4", 2, "report"], ["s-7", "a-5", 1, "address change"]]

def against(signals, account):
    [] => out
    for s in signals:
        if s[1] == account:
            out + [s] => out
    return out

def derive(signals, account):
    # the rule, as it lives in the code
    against(signals, account) => mine
    if len(mine) >= 2:
        return True
    for s in mine:
        if s[2] >= 3:
            return True
    return False

def evidence(signals, account):
    # the ids a flag rests on: the account's signals when the rule raises it
    [] => ids
    if derive(signals, account):
        for s in against(signals, account):
            ids + [s[0]] => ids
    return ids

def plain_store(signals):
    # account -> flag, computed from the signals as they are now
    {} => st
    for a in accounts():
        derive(signals, a) => st[a]
    return st

def proven_store(signals):
    # account -> the evidence ids its flag rests on ([] for no flag)
    {} => st
    for a in accounts():
        evidence(signals, a) => st[a]
    return st

def find(signals, id):
    0 => i
    for s in signals:
        if s[0] == id:
            return i
        i + 1 => i
    return 0 - 1

def without(signals, id):
    [] => out
    for s in signals:
        if s[0] != id:
            out + [s] => out
    return out
```

Python projection of flags.eml:

```python
def accounts():
    return ["a-1", "a-2", "a-3", "a-4", "a-5"]

def opening():
    return [["s-1", "a-1", 3, "chargeback"], ["s-2", "a-2", 1, "address change"], ["s-3", "a-2", 1, "new device"], ["s-4", "a-3", 2, "velocity"], ["s-5", "a-4", 1, "new device"], ["s-6", "a-4", 2, "report"], ["s-7", "a-5", 1, "address change"]]

def against(signals, account):
    out = []
    for s in signals:
        if s[1] == account:
            out = out + [s]
    return out

def derive(signals, account):
    mine = against(signals, account)
    if len(mine) >= 2:
        return True
    for s in mine:
        if s[2] >= 3:
            return True
    return False

def evidence(signals, account):
    ids = []
    if derive(signals, account):
        for s in against(signals, account):
            ids = ids + [s[0]]
    return ids

def plain_store(signals):
    st = {}
    for a in accounts():
        st[a] = derive(signals, a)
    return st

def proven_store(signals):
    st = {}
    for a in accounts():
        st[a] = evidence(signals, a)
    return st

def find(signals, id):
    i = 0
    for s in signals:
        if s[0] == id:
            return i
        i = i + 1
    return 0 - 1

def without(signals, id):
    out = []
    for s in signals:
        if s[0] != id:
            out = out + [s]
    return out
```

## README

# P159 - Risk flags

Accounts flagged from the signals against them, the flag stored two ways:
as a bare yes or no, and with the evidence it rests on.

- `main.eml` - the menu, the accounts with their signals and both stored
  flags, withdrawing a signal, adding one, the checks, and the nightly
  recompute
- `flags.eml` - the accounts and their signals, the rule, the evidence a
  flag rests on, both stores built from the signals, and finding and
  removing a signal

How each part works:

- The rule: two or more signals against an account, or any one of severity
  3. Five accounts start with seven signals - a chargeback on a-1, two
  small signals on a-2, a report and a new device on a-4 - and a-1, a-2 and
  a-4 are flagged.
- As in the corpus case `flag-outlives-its-evidence`, a flag is computed
  from what the system knew and stored. Withdraw the chargeback on a-1 and
  the bare flag stays: nothing records that it came from that signal, so
  nothing knows to clear it. The flag kept with its evidence rested on
  that signal, so it is recomputed, and clears.
- Withdrawing only ever makes a flag stale; a new signal recomputes its
  account in both stores, so neither ever misses a flag. The checks compare
  both stores with the rule on the signals on file, split by direction.
- The nightly recompute rights the bare store, a day late and only by
  recomputing every flag; the evidence store recomputes just the flags that
  rested on what was withdrawn.

What is checked: menu choices 1 to 6; a signal on file to withdraw; an
account from 1 to 5 and a severity from 1 to 3 for a new signal. An empty
answer cancels.

Sessions: `sessions/basic.in` gives:
- the accounts and the checks; s-1 and s-3 withdrawn; the accounts and the
  checks again;
- a severity-3 signal added on a-3; the accounts, the nightly recompute and
  the checks.

`sessions/bad-input.in` gives:
- menu choices 0 and x;
- withdrawing: a cancel, s-99, then s-1 twice;
- adding: a cancel and 6 for the account, then a-2 with a cancel, 4 and x
  for the severity; a signal on a-5;
- the nightly recompute twice, and the checks.

Built on the verified corpus case `flag-outlives-its-evidence` (a stored
flag with no link back to the evidence it was derived from, re-derived and
compared by direction).
