Fraud alerts
A detector right 99% of the time over a million payments a month: accuracy that stays put while precision follows the fraud rate, the rate at which alerts are right as often as not, and a second check on the alerts.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
A detector right 99% of the time, run over a million card payments a month, and how often one of its alerts is fraud.
main.eml- the menu, a month through the detector, the fraud rate and the detector set by hand, every fraud rate side by side, and the alerts checked a second timefraud.eml- the month, the four counts, percentages in tenths rounded half up, precision and accuracy, the fraud rate at which alerts are right as often as not, and a second check
How each part works:
- The detector catches 99.0% of frauds and flags 1.0% of honest payments. With 200 frauds in a million payments it is right about 99.0% of them, and of its 10196 alerts 198 are fraud: precision 1.9%.
- As in the corpus case
base-rate-and-precision, accuracy is about the detector and precision is about the world: at every fraud rate from 1 to 100000 a month accuracy stays 99.0%, while precision runs from 0.0% to 91.7%. Alerts are fraud as often as not only from 10000 frauds a month - found from the formula and then checked past the rounding. - The alerts are a population of their own, with a fraud rate equal to the first precision. Checked again by an independent detector just as good, 196 of the 295 left are fraud: 66.4%.
- With the detector set by hand: catching nothing makes no alert right at any rate; flagging nothing honest makes every alert right from the first caught fraud.
What is checked: menu choices 1 to 6; frauds a month from 0 to 1000000; the catch rate and the false alarm rate from 0 to 1000 tenths of a percent. An empty answer cancels. Every count is rounded down.
Sessions: sessions/basic.in gives: - a month, every fraud rate, and the alerts checked again; - 20000 frauds a month, a month and the alerts checked again; - the detector at 99.9% and 0.1%, a month and every fraud rate.
sessions/bad-input.in gives: - menu choices 0 and x; - fraud rates: a cancel, 1000001 and x; - detectors: a cancel and 1001, then 990 with a cancel and x; a detector that catches nothing, every rate and a month; one that catches 0.5% and flags nothing honest, every rate and the alerts checked again.
Built on the verified corpus case base-rate-and-precision (a fixed test swept across base rates, precision against accuracy, and the rate at which a positive is more likely right than wrong).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal== Fraud alerts ==
A detector right 99% of the time, a million payments a month, and how often an alert is fraud.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 0
Pick a number from 1 to 6.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> x
Pick a number from 1 to 6.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 2
frauds in a month of 1000000 payments>
Cancelled.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 2
frauds in a month of 1000000 payments> 1000001
Type a whole number from 0 to 1000000.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 2
frauds in a month of 1000000 payments> x
Type a whole number from 0 to 1000000.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 3
frauds caught, in tenths of a percent (0 to 1000)>
Cancelled.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 3
frauds caught, in tenths of a percent (0 to 1000)> 1001
Type a whole number from 0 to 1000.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 3
frauds caught, in tenths of a percent (0 to 1000)> 990
honest payments flagged, in tenths of a percent (0 to 1000)>
Cancelled.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 3
frauds caught, in tenths of a percent (0 to 1000)> 990
honest payments flagged, in tenths of a percent (0 to 1000)> x
Type a whole number from 0 to 1000.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 3
frauds caught, in tenths of a percent (0 to 1000)> 0
honest payments flagged, in tenths of a percent (0 to 1000)> 10
The detector now catches 0.0% of frauds and flags 1.0% of honest payments.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 4
The same detector, 0.0% caught and 1.0% false alarms, at every fraud rate:
frauds a month accuracy alerts precision
1 99.0% 9999 0.0%
10 99.0% 9999 0.0%
100 99.0% 9999 0.0%
1000 98.9% 9990 0.0%
10000 98.0% 9900 0.0%
100000 89.1% 9000 0.0%
Accuracy runs from 89.1% to 99.0%; precision is 0.0% wherever there are alerts.
A detector that catches nothing makes no alert right, at any fraud rate.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 1
A month of 1000000 payments, 200 of them fraud; the detector catches 0.0% of frauds and flags 1.0% of honest payments:
flagged passed
fraud 0 200
honest 9998 989802
Accuracy 99.0%: the detector is right about that share of payments.
Of the 9998 alerts, 0 are fraud: precision 0.0%. Act on an alert and you are wrong 100.0% of the time.
A detector that catches nothing makes no alert right, at any fraud rate.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 3
frauds caught, in tenths of a percent (0 to 1000)> 5
honest payments flagged, in tenths of a percent (0 to 1000)> 0
The detector now catches 0.5% of frauds and flags 0.0% of honest payments.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 4
The same detector, 0.5% caught and 0.0% false alarms, at every fraud rate:
frauds a month accuracy alerts precision
1 100.0% 0 -
10 100.0% 0 -
100 100.0% 0 -
1000 99.9% 5 100.0%
10000 99.0% 50 100.0%
100000 90.1% 500 100.0%
Accuracy runs from 90.1% to 100.0%; precision is 100.0% wherever there are alerts.
An alert is fraud at least as often as not only from 200 frauds a month.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 5
The 1 alert checked again by an independent detector just as good:
first check: 1 fraud of 1, precision 100.0%
second check: no alert left
frauds missed by the two together: 200
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 6
Bye.
What was typed (29 lines)
0
x
2
2
1000001
2
x
3
3
1001
3
990
3
990
x
3
0
10
4
1
3
5
0
4
5
6
basic
interpreter: byte-equal== Fraud alerts ==
A detector right 99% of the time, a million payments a month, and how often an alert is fraud.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 1
A month of 1000000 payments, 200 of them fraud; the detector catches 99.0% of frauds and flags 1.0% of honest payments:
flagged passed
fraud 198 2
honest 9998 989802
Accuracy 99.0%: the detector is right about that share of payments.
Of the 10196 alerts, 198 are fraud: precision 1.9%. Act on an alert and you are wrong 98.1% of the time.
An alert is fraud at least as often as not only from 10000 frauds a month.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 4
The same detector, 99.0% caught and 1.0% false alarms, at every fraud rate:
frauds a month accuracy alerts precision
1 99.0% 9999 0.0%
10 99.0% 10008 0.1%
100 99.0% 10098 1.0%
1000 99.0% 10980 9.0%
10000 99.0% 19800 50.0%
100000 99.0% 108000 91.7%
Accuracy is 99.0% at every rate; precision runs from 0.0% to 91.7%, following the fraud rate, which the detector does not know.
An alert is fraud at least as often as not only from 10000 frauds a month.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 5
The 10196 alerts checked again by an independent detector just as good:
first check: 198 fraud of 10196, precision 1.9%
second check: 196 fraud of 295, precision 66.4%
The alerts are a population of their own, with a fraud rate equal to the first precision; the second check starts from there.
frauds missed by the two together: 4
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 2
frauds in a month of 1000000 payments> 20000
Fraud set to 20000 a month.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 1
A month of 1000000 payments, 20000 of them fraud; the detector catches 99.0% of frauds and flags 1.0% of honest payments:
flagged passed
fraud 19800 200
honest 9800 970200
Accuracy 99.0%: the detector is right about that share of payments.
Of the 29600 alerts, 19800 are fraud: precision 66.9%. Act on an alert and you are wrong 33.1% of the time.
An alert is fraud at least as often as not only from 10000 frauds a month.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 5
The 29600 alerts checked again by an independent detector just as good:
first check: 19800 fraud of 29600, precision 66.9%
second check: 19602 fraud of 19700, precision 99.5%
The alerts are a population of their own, with a fraud rate equal to the first precision; the second check starts from there.
frauds missed by the two together: 398
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 3
frauds caught, in tenths of a percent (0 to 1000)> 999
honest payments flagged, in tenths of a percent (0 to 1000)> 1
The detector now catches 99.9% of frauds and flags 0.1% of honest payments.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 1
A month of 1000000 payments, 20000 of them fraud; the detector catches 99.9% of frauds and flags 0.1% of honest payments:
flagged passed
fraud 19980 20
honest 980 979020
Accuracy 99.9%: the detector is right about that share of payments.
Of the 20960 alerts, 19980 are fraud: precision 95.3%. Act on an alert and you are wrong 4.7% of the time.
An alert is fraud at least as often as not only from 1000 frauds a month.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 4
The same detector, 99.9% caught and 0.1% false alarms, at every fraud rate:
frauds a month accuracy alerts precision
1 99.9% 999 0.0%
10 99.9% 1008 0.9%
100 99.9% 1098 9.0%
1000 99.9% 1998 50.0%
10000 99.9% 10980 91.0%
100000 99.9% 100800 99.1%
Accuracy is 99.9% at every rate; precision runs from 0.0% to 99.1%, following the fraud rate, which the detector does not know.
An alert is fraud at least as often as not only from 1000 frauds a month.
1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit
choice> 6
Bye.
What was typed (13 lines)
1
4
5
2
20000
1
5
3
999
1
1
4
6
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P157 fraud alerts: a month of payments through the detector, the fraud
# rate and the detector set by hand, every fraud rate side by side, and the
# alerts checked a second time.
import fraud
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 lpad(s, n):
s => out
while len(out) < n:
" " + out => out
return out
def pct(t):
t % 10 => d
return str(int((t - d) / 10)) + "." + str(d) + "%"
def number(prompt, high):
# a whole number from 0 to high, -1 for a cancel, -2 when not valid
plain(input(prompt)) => t
if t == "":
return 0 - 1
if len(t) > 7:
return 0 - 2
for c in t:
if c < "0" or c > "9":
return 0 - 2
if int(t) > high:
return 0 - 2
return int(t)
def even_line(catch, alarm):
fraud.even(catch, alarm) => b
if b < 0:
"A detector that catches nothing makes no alert right, at any fraud rate." ^0
else:
if b == 1:
"An alert is fraud at least as often as not from the first fraud of the month." ^0
else:
("An alert is fraud at least as often as not only from " + str(b) + " frauds a month.") ^0
def show_month(f, catch, alarm):
fraud.outcomes(f, catch, alarm) => r
("A month of " + str(fraud.month()) + " payments, " + str(f) + " of them fraud; the detector catches " + pct(catch) + " of frauds and flags " + pct(alarm) + " of honest payments:") ^0
(" " + pad("", 9) + lpad("flagged", 10) + lpad("passed", 10)) ^0
(" " + pad("fraud", 9) + lpad(str(r[0]), 10) + lpad(str(r[1]), 10)) ^0
(" " + pad("honest", 9) + lpad(str(r[2]), 10) + lpad(str(r[3]), 10)) ^0
("Accuracy " + pct(fraud.accuracy(r)) + ": the detector is right about that share of payments.") ^0
r[0] + r[2] => alerts
if alerts == 0:
"No alerts this month." ^0
else:
if alerts == 1:
"Of the 1 alert, " + str(r[0]) + " is fraud" => head
else:
"Of the " + str(alerts) + " alerts, " + str(r[0]) + " are fraud" => head
(head + ": precision " + pct(fraud.precision(r)) + ". Act on an alert and you are wrong " + pct(1000 - fraud.precision(r)) + " of the time.") ^0
even_line(catch, alarm)
def sweep(catch, alarm):
("The same detector, " + pct(catch) + " caught and " + pct(alarm) + " false alarms, at every fraud rate:") ^0
(" " + pad("frauds a month", 16) + pad("accuracy", 10) + pad("alerts", 9) + "precision") ^0
1001 => alo
0 - 1 => ahi
1001 => plo
0 - 1 => phi
for f in [1, 10, 100, 1000, 10000, 100000]:
fraud.outcomes(f, catch, alarm) => r
fraud.accuracy(r) => a
fraud.precision(r) => p
if r[0] + r[2] == 0:
(" " + pad(str(f), 16) + pad(pct(a), 10) + pad("0", 9) + "-") ^0
else:
(" " + pad(str(f), 16) + pad(pct(a), 10) + pad(str(r[0] + r[2]), 9) + pct(p)) ^0
if p < plo:
p => plo
if p > phi:
p => phi
if a < alo:
a => alo
if a > ahi:
a => ahi
if alo == ahi:
"Accuracy is " + pct(alo) + " at every rate" => acc
else:
"Accuracy runs from " + pct(alo) + " to " + pct(ahi) => acc
if phi < 0:
(acc + "; at none of these rates is there an alert.") ^0
elif plo == phi:
(acc + "; precision is " + pct(plo) + " wherever there are alerts.") ^0
else:
(acc + "; precision runs from " + pct(plo) + " to " + pct(phi) + ", following the fraud rate, which the detector does not know.") ^0
even_line(catch, alarm)
def again(f, catch, alarm):
fraud.outcomes(f, catch, alarm) => r
fraud.second(r, catch, alarm) => s
r[0] + r[2] => alerts
if alerts == 0:
"No alerts this month, so nothing to check again." ^0
return
if alerts == 1:
"The 1 alert checked again by an independent detector just as good:" ^0
else:
("The " + str(alerts) + " alerts checked again by an independent detector just as good:") ^0
(" first check: " + str(r[0]) + " fraud of " + str(alerts) + ", precision " + pct(fraud.precision(r))) ^0
if s[0] + s[1] == 0:
" second check: no alert left" ^0
else:
(" second check: " + str(s[0]) + " fraud of " + str(s[0] + s[1]) + ", precision " + pct(fraud.tenths(s[0], s[0] + s[1]))) ^0
"The alerts are a population of their own, with a fraud rate equal to the first precision; the second check starts from there." ^0
(" frauds missed by the two together: " + str(f - s[0])) ^0
200 => f
990 => catch
10 => alarm
"== Fraud alerts ==" ^0
"A detector right 99% of the time, a million payments a month, and how often an alert is fraud." ^0
True => running
while running:
"" ^0
"1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit" ^0
plain(input("choice> ")) => choice
if choice == "1":
show_month(f, catch, alarm)
elif choice == "2":
number("frauds in a month of 1000000 payments> ", fraud.month()) => n
if n == 0 - 1:
"Cancelled." ^0
elif n < 0:
"Type a whole number from 0 to 1000000." ^0
else:
n => f
("Fraud set to " + str(f) + " a month.") ^0
elif choice == "3":
number("frauds caught, in tenths of a percent (0 to 1000)> ", 1000) => a
if a == 0 - 1:
"Cancelled." ^0
elif a < 0:
"Type a whole number from 0 to 1000." ^0
else:
number("honest payments flagged, in tenths of a percent (0 to 1000)> ", 1000) => b
if b == 0 - 1:
"Cancelled." ^0
elif b < 0:
"Type a whole number from 0 to 1000." ^0
else:
a => catch
b => alarm
("The detector now catches " + pct(catch) + " of frauds and flags " + pct(alarm) + " of honest payments.") ^0
elif choice == "4":
sweep(catch, alarm)
elif choice == "5":
again(f, catch, alarm)
elif choice == "6":
False => running
else:
"Pick a number from 1 to 6." ^0
"Bye." ^0
Python projection (main.py)
import fraud
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 lpad(s, n):
out = s
while len(out) < n:
out = " " + out
return out
def pct(t):
d = t % 10
return str(int((t - d) / 10)) + "." + str(d) + "%"
def number(prompt, high):
t = plain(input(prompt))
if t == "":
return 0 - 1
if len(t) > 7:
return 0 - 2
for c in t:
if c < "0" or c > "9":
return 0 - 2
if int(t) > high:
return 0 - 2
return int(t)
def even_line(catch, alarm):
b = fraud.even(catch, alarm)
if b < 0:
print("A detector that catches nothing makes no alert right, at any fraud rate.")
elif b == 1:
print("An alert is fraud at least as often as not from the first fraud of the month.")
else:
print("An alert is fraud at least as often as not only from " + str(b) + " frauds a month.")
def show_month(f, catch, alarm):
r = fraud.outcomes(f, catch, alarm)
print("A month of " + str(fraud.month()) + " payments, " + str(f) + " of them fraud; the detector catches " + pct(catch) + " of frauds and flags " + pct(alarm) + " of honest payments:")
print(" " + pad("", 9) + lpad("flagged", 10) + lpad("passed", 10))
print(" " + pad("fraud", 9) + lpad(str(r[0]), 10) + lpad(str(r[1]), 10))
print(" " + pad("honest", 9) + lpad(str(r[2]), 10) + lpad(str(r[3]), 10))
print("Accuracy " + pct(fraud.accuracy(r)) + ": the detector is right about that share of payments.")
alerts = r[0] + r[2]
if alerts == 0:
print("No alerts this month.")
else:
if alerts == 1:
head = "Of the 1 alert, " + str(r[0]) + " is fraud"
else:
head = "Of the " + str(alerts) + " alerts, " + str(r[0]) + " are fraud"
print(head + ": precision " + pct(fraud.precision(r)) + ". Act on an alert and you are wrong " + pct(1000 - fraud.precision(r)) + " of the time.")
even_line(catch, alarm)
def sweep(catch, alarm):
print("The same detector, " + pct(catch) + " caught and " + pct(alarm) + " false alarms, at every fraud rate:")
print(" " + pad("frauds a month", 16) + pad("accuracy", 10) + pad("alerts", 9) + "precision")
alo = 1001
ahi = 0 - 1
plo = 1001
phi = 0 - 1
for f in [1, 10, 100, 1000, 10000, 100000]:
r = fraud.outcomes(f, catch, alarm)
a = fraud.accuracy(r)
p = fraud.precision(r)
if r[0] + r[2] == 0:
print(" " + pad(str(f), 16) + pad(pct(a), 10) + pad("0", 9) + "-")
else:
print(" " + pad(str(f), 16) + pad(pct(a), 10) + pad(str(r[0] + r[2]), 9) + pct(p))
if p < plo:
plo = p
if p > phi:
phi = p
if a < alo:
alo = a
if a > ahi:
ahi = a
if alo == ahi:
acc = "Accuracy is " + pct(alo) + " at every rate"
else:
acc = "Accuracy runs from " + pct(alo) + " to " + pct(ahi)
if phi < 0:
print(acc + "; at none of these rates is there an alert.")
elif plo == phi:
print(acc + "; precision is " + pct(plo) + " wherever there are alerts.")
else:
print(acc + "; precision runs from " + pct(plo) + " to " + pct(phi) + ", following the fraud rate, which the detector does not know.")
even_line(catch, alarm)
def again(f, catch, alarm):
r = fraud.outcomes(f, catch, alarm)
s = fraud.second(r, catch, alarm)
alerts = r[0] + r[2]
if alerts == 0:
print("No alerts this month, so nothing to check again.")
return
if alerts == 1:
print("The 1 alert checked again by an independent detector just as good:")
else:
print("The " + str(alerts) + " alerts checked again by an independent detector just as good:")
print(" first check: " + str(r[0]) + " fraud of " + str(alerts) + ", precision " + pct(fraud.precision(r)))
if s[0] + s[1] == 0:
print(" second check: no alert left")
else:
print(" second check: " + str(s[0]) + " fraud of " + str(s[0] + s[1]) + ", precision " + pct(fraud.tenths(s[0], s[0] + s[1])))
print("The alerts are a population of their own, with a fraud rate equal to the first precision; the second check starts from there.")
print(" frauds missed by the two together: " + str(f - s[0]))
f = 200
catch = 990
alarm = 10
print("== Fraud alerts ==")
print("A detector right 99% of the time, a million payments a month, and how often an alert is fraud.")
running = True
while running:
print("")
print("1) a month 2) the fraud rate 3) the detector 4) every fraud rate 5) check the alerts again 6) quit")
choice = plain(input("choice> "))
if choice == "1":
show_month(f, catch, alarm)
elif choice == "2":
n = number("frauds in a month of 1000000 payments> ", fraud.month())
if n == 0 - 1:
print("Cancelled.")
elif n < 0:
print("Type a whole number from 0 to 1000000.")
else:
f = n
print("Fraud set to " + str(f) + " a month.")
elif choice == "3":
a = number("frauds caught, in tenths of a percent (0 to 1000)> ", 1000)
if a == 0 - 1:
print("Cancelled.")
elif a < 0:
print("Type a whole number from 0 to 1000.")
else:
b = number("honest payments flagged, in tenths of a percent (0 to 1000)> ", 1000)
if b == 0 - 1:
print("Cancelled.")
elif b < 0:
print("Type a whole number from 0 to 1000.")
else:
catch = a
alarm = b
print("The detector now catches " + pct(catch) + " of frauds and flags " + pct(alarm) + " of honest payments.")
elif choice == "4":
sweep(catch, alarm)
elif choice == "5":
again(f, catch, alarm)
elif choice == "6":
running = False
else:
print("Pick a number from 1 to 6.")
print("Bye.")
fraud.eml
eml# P157 fraud alerts - a detector that is right 99% of the time, run over a
# million card payments a month. As in the corpus case
# base-rate-and-precision, accuracy is about the detector and precision -
# how often an alert is fraud - is about the world: when fraud is rare, 1%
# of the honest payments outnumbers 99% of the frauds, and most alerts are
# wrong. Counts are whole payments over a fixed month; a rate is in tenths
# of a percent.
def month():
return 1000000
def outcomes(fraud, catch, alarm):
# [frauds caught, frauds missed, honest flagged, honest passed] for a
# month with fraud frauds, catching catch and falsely flagging alarm
# tenths of a percent, each count rounded down
int(fraud * catch / 1000) => tp
month() - fraud => honest
int(honest * alarm / 1000) => fp
return [tp, fraud - tp, fp, honest - fp]
def tenths(n, d):
# n / d as a percentage in tenths, rounded half up; 0 when d is 0
if d == 0:
return 0
int((2000 * n + d) / (2 * d)) => q
while q * 2 * d > 2000 * n + d:
q - 1 => q
while (q + 1) * 2 * d <= 2000 * n + d:
q + 1 => q
return q
def precision(r):
return tenths(r[0], r[0] + r[2])
def accuracy(r):
return tenths(r[0] + r[3], month())
def right_half(b, catch, alarm):
# some fraud is caught, and the alerts are fraud at least as often as not
outcomes(b, catch, alarm) => r
return r[0] > 0 and r[0] >= r[2]
def even(catch, alarm):
# the fewest frauds a month at which an alert is fraud at least as
# often as not; the formula's answer, then nudged past the rounding
if catch == 0:
return 0 - 1
int(month() * alarm / (catch + alarm)) => b
if b < 1:
1 => b
while b > 1 and right_half(b - 1, catch, alarm):
b - 1 => b
while not right_half(b, catch, alarm):
b + 1 => b
return b
def second(r, catch, alarm):
# the alerts checked again by an independent detector just as good:
# [frauds still flagged, honest still flagged]
return [int(r[0] * catch / 1000), int(r[2] * alarm / 1000)]
Python projection (fraud.py)
def month():
return 1000000
def outcomes(fraud, catch, alarm):
tp = int(fraud * catch / 1000)
honest = month() - fraud
fp = int(honest * alarm / 1000)
return [tp, fraud - tp, fp, honest - fp]
def tenths(n, d):
if d == 0:
return 0
q = int((2000 * n + d) / (2 * d))
while q * 2 * d > 2000 * n + d:
q = q - 1
while (q + 1) * 2 * d <= 2000 * n + d:
q = q + 1
return q
def precision(r):
return tenths(r[0], r[0] + r[2])
def accuracy(r):
return tenths(r[0] + r[3], month())
def right_half(b, catch, alarm):
r = outcomes(b, catch, alarm)
return r[0] > 0 and r[0] >= r[2]
def even(catch, alarm):
if catch == 0:
return 0 - 1
b = int(month() * alarm / (catch + alarm))
if b < 1:
b = 1
while b > 1 and right_half(b - 1, catch, alarm):
b = b - 1
while not right_half(b, catch, alarm):
b = b + 1
return b
def second(r, catch, alarm):
return [int(r[0] * catch / 1000), int(r[2] * alarm / 1000)]