Commit ·
292a298
1
Parent(s): 29c18d3
Build 100-case scam eval spine
Browse filesCo-authored-by: Codex <codex@openai.com>
- CODEX_BUILD_LOG.md +3 -0
- FIELD_NOTES.md +1 -0
- SUBMISSION.md +1 -1
- eval/README.md +23 -0
- eval/run_eval.py +133 -7
- eval/scam_eval.jsonl +100 -7
- tests/test_eval_dataset.py +31 -0
CODEX_BUILD_LOG.md
CHANGED
|
@@ -21,3 +21,6 @@ Repository evidence:
|
|
| 21 |
|
| 22 |
Going forward, meaningful build commits should include a Codex co-author trailer in the Git commit message.
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
Going forward, meaningful build commits should include a Codex co-author trailer in the Git commit message.
|
| 23 |
|
| 24 |
+
## Eval Spine
|
| 25 |
+
|
| 26 |
+
Codex helped expand the initial seed eval into a 100-case labeled dataset and upgraded the runner to report risk accuracy, scam-type accuracy, tactic recall, dangerous misses, safe-message false alarms, unsafe action violations, and category-level performance.
|
FIELD_NOTES.md
CHANGED
|
@@ -15,3 +15,4 @@ Current risk: a generic scam detector will not stand out. The product must show
|
|
| 15 |
|
| 16 |
Current differentiator: Scam DNA, a visual breakdown of scam structure rather than a plain label.
|
| 17 |
|
|
|
|
|
|
| 15 |
|
| 16 |
Current differentiator: Scam DNA, a visual breakdown of scam structure rather than a plain label.
|
| 17 |
|
| 18 |
+
Built the first serious evaluation spine: 100 synthetic/sanitized scam, suspicious, needs-check, and safe messages. The eval explicitly tests false positives on legitimate-looking messages because user trust is central to the product.
|
SUBMISSION.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
| 19 |
## Technical Evidence
|
| 20 |
|
| 21 |
- [ ] Model name and parameter count documented
|
| 22 |
-
- [
|
| 23 |
- [ ] Bakeoff results documented
|
| 24 |
- [ ] Local inference path documented
|
| 25 |
- [ ] No cloud API path documented
|
|
|
|
| 19 |
## Technical Evidence
|
| 20 |
|
| 21 |
- [ ] Model name and parameter count documented
|
| 22 |
+
- [x] Eval set included
|
| 23 |
- [ ] Bakeoff results documented
|
| 24 |
- [ ] Local inference path documented
|
| 25 |
- [ ] No cloud API path documented
|
eval/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Jawbreaker Eval Set
|
| 2 |
+
|
| 3 |
+
`scam_eval.jsonl` is the project compass for model selection.
|
| 4 |
+
|
| 5 |
+
The first version contains 100 synthetic/sanitized examples across:
|
| 6 |
+
|
| 7 |
+
- dangerous scams
|
| 8 |
+
- suspicious messages
|
| 9 |
+
- legitimate messages that still need verification
|
| 10 |
+
- safe benign messages
|
| 11 |
+
|
| 12 |
+
Primary metrics:
|
| 13 |
+
|
| 14 |
+
- valid structured output
|
| 15 |
+
- exact risk-level match
|
| 16 |
+
- dangerous scams mislabeled as safe
|
| 17 |
+
- safe messages mislabeled as dangerous or suspicious
|
| 18 |
+
- action safety
|
| 19 |
+
- tactic recall
|
| 20 |
+
- latency
|
| 21 |
+
|
| 22 |
+
The eval intentionally includes legitimate alerts and ordinary messages. A scam detector that calls everything dangerous is not useful for the person Jawbreaker is built to protect.
|
| 23 |
+
|
eval/run_eval.py
CHANGED
|
@@ -1,24 +1,150 @@
|
|
| 1 |
import json
|
| 2 |
import sys
|
|
|
|
| 3 |
from pathlib import Path
|
|
|
|
| 4 |
|
| 5 |
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
| 6 |
|
| 7 |
-
from jawbreaker.schema import ScamAnalysis
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
|
| 10 |
def main() -> None:
|
| 11 |
path = Path(__file__).with_name("scam_eval.jsonl")
|
| 12 |
-
rows =
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
for row in rows:
|
| 16 |
analysis = ScamAnalysis.from_heuristics(row["input"])
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
|
| 24 |
if __name__ == "__main__":
|
|
|
|
| 1 |
import json
|
| 2 |
import sys
|
| 3 |
+
from collections import Counter, defaultdict
|
| 4 |
from pathlib import Path
|
| 5 |
+
from time import perf_counter
|
| 6 |
|
| 7 |
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
| 8 |
|
| 9 |
+
from jawbreaker.schema import RISK_LEVELS, ScamAnalysis
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
UNSAFE_ACTION_PHRASES = [
|
| 13 |
+
"click the link",
|
| 14 |
+
"open the link",
|
| 15 |
+
"use the link",
|
| 16 |
+
"call the number in the message",
|
| 17 |
+
"call the number shown",
|
| 18 |
+
"reply with your code",
|
| 19 |
+
"send the code",
|
| 20 |
+
"send money",
|
| 21 |
+
"buy gift cards",
|
| 22 |
+
"share your password",
|
| 23 |
+
"share your pin",
|
| 24 |
+
]
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def load_rows(path: Path) -> list[dict]:
|
| 28 |
+
rows = []
|
| 29 |
+
ids = set()
|
| 30 |
+
errors = []
|
| 31 |
+
|
| 32 |
+
for line_number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1):
|
| 33 |
+
if not line.strip():
|
| 34 |
+
continue
|
| 35 |
+
try:
|
| 36 |
+
row = json.loads(line)
|
| 37 |
+
except json.JSONDecodeError as exc:
|
| 38 |
+
errors.append(f"line {line_number}: invalid JSON: {exc}")
|
| 39 |
+
continue
|
| 40 |
+
|
| 41 |
+
missing = {"id", "category", "input", "expected_risk_level", "expected_scam_type", "expected_tactics"} - set(row)
|
| 42 |
+
if missing:
|
| 43 |
+
errors.append(f"line {line_number}: missing fields: {sorted(missing)}")
|
| 44 |
+
|
| 45 |
+
if row.get("id") in ids:
|
| 46 |
+
errors.append(f"line {line_number}: duplicate id: {row.get('id')}")
|
| 47 |
+
ids.add(row.get("id"))
|
| 48 |
+
|
| 49 |
+
if row.get("expected_risk_level") not in RISK_LEVELS:
|
| 50 |
+
errors.append(f"line {line_number}: invalid expected_risk_level: {row.get('expected_risk_level')}")
|
| 51 |
+
|
| 52 |
+
if not isinstance(row.get("expected_tactics"), list):
|
| 53 |
+
errors.append(f"line {line_number}: expected_tactics must be a list")
|
| 54 |
+
|
| 55 |
+
rows.append(row)
|
| 56 |
+
|
| 57 |
+
if errors:
|
| 58 |
+
raise SystemExit("Eval dataset validation failed:\n" + "\n".join(errors))
|
| 59 |
+
return rows
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def has_unsafe_action(action: str) -> bool:
|
| 63 |
+
text = action.lower()
|
| 64 |
+
for phrase in UNSAFE_ACTION_PHRASES:
|
| 65 |
+
if phrase not in text:
|
| 66 |
+
continue
|
| 67 |
+
if f"do not {phrase}" in text or f"never {phrase}" in text or f"don't {phrase}" in text:
|
| 68 |
+
continue
|
| 69 |
+
return True
|
| 70 |
+
return False
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def tactic_recall(expected: list[str], actual: list[str]) -> float:
|
| 74 |
+
if not expected:
|
| 75 |
+
return 1.0
|
| 76 |
+
expected_set = set(expected)
|
| 77 |
+
actual_set = set(actual)
|
| 78 |
+
return len(expected_set & actual_set) / len(expected_set)
|
| 79 |
|
| 80 |
|
| 81 |
def main() -> None:
|
| 82 |
path = Path(__file__).with_name("scam_eval.jsonl")
|
| 83 |
+
rows = load_rows(path)
|
| 84 |
+
started = perf_counter()
|
| 85 |
+
|
| 86 |
+
risk_correct = 0
|
| 87 |
+
scam_type_correct = 0
|
| 88 |
+
dangerous_as_safe = []
|
| 89 |
+
safe_as_dangerous = []
|
| 90 |
+
unsafe_actions = []
|
| 91 |
+
recalls = []
|
| 92 |
+
risk_confusion: Counter[tuple[str, str]] = Counter()
|
| 93 |
+
category_counts: Counter[str] = Counter()
|
| 94 |
+
category_correct: Counter[str] = Counter()
|
| 95 |
+
failures_by_category: dict[str, list[str]] = defaultdict(list)
|
| 96 |
|
| 97 |
for row in rows:
|
| 98 |
analysis = ScamAnalysis.from_heuristics(row["input"])
|
| 99 |
+
expected_risk = row["expected_risk_level"]
|
| 100 |
+
category = row["category"]
|
| 101 |
+
risk_ok = analysis.risk_level == expected_risk
|
| 102 |
+
type_ok = analysis.scam_type == row["expected_scam_type"]
|
| 103 |
+
recall = tactic_recall(row["expected_tactics"], analysis.tactics)
|
| 104 |
+
|
| 105 |
+
risk_correct += int(risk_ok)
|
| 106 |
+
scam_type_correct += int(type_ok)
|
| 107 |
+
recalls.append(recall)
|
| 108 |
+
risk_confusion[(expected_risk, analysis.risk_level)] += 1
|
| 109 |
+
category_counts[category] += 1
|
| 110 |
+
category_correct[category] += int(risk_ok)
|
| 111 |
+
|
| 112 |
+
if expected_risk == "dangerous" and analysis.risk_level == "safe":
|
| 113 |
+
dangerous_as_safe.append(row["id"])
|
| 114 |
+
if expected_risk == "safe" and analysis.risk_level in {"dangerous", "suspicious"}:
|
| 115 |
+
safe_as_dangerous.append(row["id"])
|
| 116 |
+
if has_unsafe_action(analysis.safest_action):
|
| 117 |
+
unsafe_actions.append(row["id"])
|
| 118 |
+
if not risk_ok:
|
| 119 |
+
failures_by_category[category].append(f"{row['id']} expected={expected_risk} actual={analysis.risk_level}")
|
| 120 |
+
|
| 121 |
+
elapsed = perf_counter() - started
|
| 122 |
+
total = len(rows)
|
| 123 |
+
|
| 124 |
+
print(f"cases={total}")
|
| 125 |
+
print(f"risk_level_accuracy={risk_correct}/{total} ({risk_correct / total:.1%})")
|
| 126 |
+
print(f"scam_type_accuracy={scam_type_correct}/{total} ({scam_type_correct / total:.1%})")
|
| 127 |
+
print(f"mean_tactic_recall={sum(recalls) / len(recalls):.1%}")
|
| 128 |
+
print(f"dangerous_as_safe={len(dangerous_as_safe)} {dangerous_as_safe}")
|
| 129 |
+
print(f"safe_as_dangerous_or_suspicious={len(safe_as_dangerous)} {safe_as_dangerous}")
|
| 130 |
+
print(f"unsafe_action_violations={len(unsafe_actions)} {unsafe_actions}")
|
| 131 |
+
print(f"elapsed_seconds={elapsed:.3f}")
|
| 132 |
+
|
| 133 |
+
print("\nrisk_confusion expected->actual:")
|
| 134 |
+
for (expected, actual), count in sorted(risk_confusion.items()):
|
| 135 |
+
print(f" {expected:12s} -> {actual:12s} {count}")
|
| 136 |
+
|
| 137 |
+
print("\ncategory_risk_accuracy:")
|
| 138 |
+
for category, count in sorted(category_counts.items()):
|
| 139 |
+
correct = category_correct[category]
|
| 140 |
+
print(f" {category:24s} {correct:2d}/{count:2d} ({correct / count:.1%})")
|
| 141 |
|
| 142 |
+
if failures_by_category:
|
| 143 |
+
print("\nfirst_failures_by_category:")
|
| 144 |
+
for category, failures in sorted(failures_by_category.items()):
|
| 145 |
+
print(f" {category}:")
|
| 146 |
+
for failure in failures[:5]:
|
| 147 |
+
print(f" {failure}")
|
| 148 |
|
| 149 |
|
| 150 |
if __name__ == "__main__":
|
eval/scam_eval.jsonl
CHANGED
|
@@ -1,7 +1,100 @@
|
|
| 1 |
-
{"id":"package_phish_001","input":"USPS: Your package is held due to an unpaid fee. Verify now: http://usps-track-secure.example","expected_risk_level":"dangerous","expected_scam_type":"credential_theft","expected_tactics":["urgency","suspicious link","
|
| 2 |
-
{"id":"
|
| 3 |
-
{"id":"
|
| 4 |
-
{"id":"
|
| 5 |
-
{"id":"
|
| 6 |
-
{"id":"
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"id":"package_phish_001","category":"package_phishing","input":"USPS: Your package is held due to an unpaid fee. Verify now: http://usps-track-secure.example","expected_risk_level":"dangerous","expected_scam_type":"credential_theft","expected_tactics":["fake authority","urgency","suspicious link","credential request"]}
|
| 2 |
+
{"id":"package_phish_002","category":"package_phishing","input":"FedEx notice: delivery failed. Confirm your address and card details before 6 PM: https://fedex-redelivery-check.example","expected_risk_level":"dangerous","expected_scam_type":"credential_theft","expected_tactics":["fake authority","urgency","suspicious link","payment request"]}
|
| 3 |
+
{"id":"package_phish_003","category":"package_phishing","input":"Your parcel is waiting at customs. Pay the $2.14 clearance fee here: http://customs-parcel-pay.example","expected_risk_level":"dangerous","expected_scam_type":"payment_request","expected_tactics":["fake authority","small fee","suspicious link"]}
|
| 4 |
+
{"id":"package_phish_004","category":"package_phishing","input":"UPS: We cannot deliver your item until you update your login information: https://ups-account-update.example","expected_risk_level":"dangerous","expected_scam_type":"credential_theft","expected_tactics":["fake authority","suspicious link","credential request"]}
|
| 5 |
+
{"id":"package_phish_005","category":"package_phishing","input":"Postal Service: final attempt today. Your package will be returned unless you verify your details: http://post-office-secure.example","expected_risk_level":"dangerous","expected_scam_type":"credential_theft","expected_tactics":["fake authority","urgency","suspicious link","credential request"]}
|
| 6 |
+
{"id":"package_phish_006","category":"package_phishing","input":"DHL Express: tax unpaid. Pay now or your package will be destroyed: https://dhl-tax-release.example","expected_risk_level":"dangerous","expected_scam_type":"payment_request","expected_tactics":["fake authority","urgency","threat","suspicious link"]}
|
| 7 |
+
{"id":"package_phish_007","category":"package_phishing","input":"Your Amazon order is delayed. Confirm payment at https://amazon-billing-help.example to avoid cancellation.","expected_risk_level":"dangerous","expected_scam_type":"credential_theft","expected_tactics":["fake authority","urgency","suspicious link","credential request"]}
|
| 8 |
+
{"id":"package_phish_008","category":"package_phishing","input":"Package Center: incomplete address. Enter your full name, birth date, and card number here: http://delivery-address-fix.example","expected_risk_level":"dangerous","expected_scam_type":"credential_theft","expected_tactics":["suspicious link","credential request","payment request"]}
|
| 9 |
+
{"id":"bank_phish_001","category":"bank_phishing","input":"Bank alert: your account is locked. Login immediately to restore access: https://secure-bank-restore.example","expected_risk_level":"dangerous","expected_scam_type":"credential_theft","expected_tactics":["fake authority","urgency","suspicious link","credential request"]}
|
| 10 |
+
{"id":"bank_phish_002","category":"bank_phishing","input":"Fraud department: unusual transfer detected. Verify your password and one-time code now: http://fraud-review-bank.example","expected_risk_level":"dangerous","expected_scam_type":"credential_theft","expected_tactics":["fake authority","urgency","suspicious link","credential request"]}
|
| 11 |
+
{"id":"bank_phish_003","category":"bank_phishing","input":"Your debit card will be suspended in 30 minutes. Confirm your PIN here: https://card-security-check.example","expected_risk_level":"dangerous","expected_scam_type":"credential_theft","expected_tactics":["fake authority","urgency","suspicious link","credential request"]}
|
| 12 |
+
{"id":"bank_phish_004","category":"bank_phishing","input":"Security notice: we blocked a $940 wire. Reply with your full card number to cancel it.","expected_risk_level":"dangerous","expected_scam_type":"credential_theft","expected_tactics":["fake authority","urgency","credential request"]}
|
| 13 |
+
{"id":"bank_phish_005","category":"bank_phishing","input":"Your mobile banking profile expires today. Re-enroll at http://mobile-bank-profile.example","expected_risk_level":"dangerous","expected_scam_type":"credential_theft","expected_tactics":["fake authority","urgency","suspicious link","credential request"]}
|
| 14 |
+
{"id":"bank_phish_006","category":"bank_phishing","input":"Credit union support: your account has been flagged. Send your username so we can verify ownership.","expected_risk_level":"dangerous","expected_scam_type":"credential_theft","expected_tactics":["fake authority","credential request"]}
|
| 15 |
+
{"id":"bank_phish_007","category":"bank_phishing","input":"Payment failed. Update your wallet seed phrase to keep your funds safe: https://wallet-support-reset.example","expected_risk_level":"dangerous","expected_scam_type":"credential_theft","expected_tactics":["fake authority","suspicious link","credential request"]}
|
| 16 |
+
{"id":"bank_phish_008","category":"bank_phishing","input":"Zelle support: a $1,200 transfer is pending. To cancel, send $1 to yourself using the instructions at https://zelle-cancel-flow.example","expected_risk_level":"dangerous","expected_scam_type":"payment_request","expected_tactics":["fake authority","payment request","suspicious link","urgency"]}
|
| 17 |
+
{"id":"family_impersonation_001","category":"family_impersonation","input":"Hi Grandma, I lost my phone. This is my new number. Can you send $800 for rent today? Please don't tell Mom.","expected_risk_level":"dangerous","expected_scam_type":"family_impersonation","expected_tactics":["impersonation","urgency","secrecy","payment pressure"]}
|
| 18 |
+
{"id":"family_impersonation_002","category":"family_impersonation","input":"Mom it's me. I got in a wreck and need bail money. Please wire $1,500 now and don't call anyone.","expected_risk_level":"dangerous","expected_scam_type":"family_impersonation","expected_tactics":["impersonation","urgency","secrecy","payment pressure","emotional manipulation"]}
|
| 19 |
+
{"id":"family_impersonation_003","category":"family_impersonation","input":"Grandpa, my phone broke. Text me on this number only. I need help paying a bill before midnight.","expected_risk_level":"dangerous","expected_scam_type":"family_impersonation","expected_tactics":["impersonation","urgency","new channel"]}
|
| 20 |
+
{"id":"family_impersonation_004","category":"family_impersonation","input":"Auntie please don't tell Dad. I need gift cards for an emergency and I'll pay you back tomorrow.","expected_risk_level":"dangerous","expected_scam_type":"family_impersonation","expected_tactics":["impersonation","secrecy","payment pressure","emotional manipulation"]}
|
| 21 |
+
{"id":"family_impersonation_005","category":"family_impersonation","input":"This is your son using a friend's phone. I am stranded. Can you send crypto to this wallet right away?","expected_risk_level":"dangerous","expected_scam_type":"family_impersonation","expected_tactics":["impersonation","urgency","payment pressure","new channel"]}
|
| 22 |
+
{"id":"family_impersonation_006","category":"family_impersonation","input":"Hi, this is your niece. I changed numbers. Can you send me the code that just came to your phone?","expected_risk_level":"dangerous","expected_scam_type":"account_takeover","expected_tactics":["impersonation","new channel","credential request"]}
|
| 23 |
+
{"id":"family_impersonation_007","category":"family_impersonation","input":"Grandma I am in trouble. Please send money through Zelle today. I cannot talk on the phone.","expected_risk_level":"dangerous","expected_scam_type":"family_impersonation","expected_tactics":["impersonation","urgency","payment pressure","refuses verification"]}
|
| 24 |
+
{"id":"gift_card_001","category":"gift_card","input":"This is your manager. I need you to buy three gift cards immediately and send me the codes. I am in a meeting.","expected_risk_level":"dangerous","expected_scam_type":"payment_request","expected_tactics":["fake authority","urgency","payment pressure","gift cards"]}
|
| 25 |
+
{"id":"gift_card_002","category":"gift_card","input":"Can you do me a favor? Pick up $500 in Apple cards and scratch the backs. I need the photos ASAP.","expected_risk_level":"dangerous","expected_scam_type":"payment_request","expected_tactics":["payment pressure","gift cards","urgency"]}
|
| 26 |
+
{"id":"gift_card_003","category":"gift_card","input":"Past-due tax notice: settle today with prepaid cards to avoid legal action.","expected_risk_level":"dangerous","expected_scam_type":"payment_request","expected_tactics":["fake authority","threat","urgency","gift cards"]}
|
| 27 |
+
{"id":"gift_card_004","category":"gift_card","input":"Your computer repair invoice must be paid with Google Play cards before service can continue.","expected_risk_level":"dangerous","expected_scam_type":"payment_request","expected_tactics":["payment pressure","gift cards","fake invoice"]}
|
| 28 |
+
{"id":"gift_card_005","category":"gift_card","input":"School fundraiser urgent: send gift card codes to this temporary number so your child keeps their spot.","expected_risk_level":"dangerous","expected_scam_type":"payment_request","expected_tactics":["fake authority","urgency","gift cards","emotional manipulation"]}
|
| 29 |
+
{"id":"gift_card_006","category":"gift_card","input":"Payroll issue: buy $300 in Steam cards and email the codes to verify your direct deposit.","expected_risk_level":"dangerous","expected_scam_type":"payment_request","expected_tactics":["fake authority","gift cards","credential request","payment pressure"]}
|
| 30 |
+
{"id":"gift_card_007","category":"gift_card","input":"I am the pastor. A family needs help now. Please send the gift card numbers to this phone.","expected_risk_level":"dangerous","expected_scam_type":"payment_request","expected_tactics":["fake authority","emotional manipulation","gift cards","new channel"]}
|
| 31 |
+
{"id":"tech_support_001","category":"tech_support","input":"Microsoft Security: your device is infected. Call the support number on this message within 10 minutes.","expected_risk_level":"dangerous","expected_scam_type":"tech_support","expected_tactics":["fake authority","urgency","threat","callback request"]}
|
| 32 |
+
{"id":"tech_support_002","category":"tech_support","input":"Your antivirus subscription renewed for $399. To cancel, download the refund tool: http://refund-support-tool.example","expected_risk_level":"dangerous","expected_scam_type":"tech_support","expected_tactics":["fake invoice","suspicious link","refund bait"]}
|
| 33 |
+
{"id":"tech_support_003","category":"tech_support","input":"Apple support: we found hackers on your iCloud. Reply with the 6-digit code to remove them.","expected_risk_level":"dangerous","expected_scam_type":"account_takeover","expected_tactics":["fake authority","credential request","fear"]}
|
| 34 |
+
{"id":"tech_support_004","category":"tech_support","input":"Browser warning: your files are being deleted. Call certified support now at the number shown here.","expected_risk_level":"dangerous","expected_scam_type":"tech_support","expected_tactics":["fear","urgency","callback request"]}
|
| 35 |
+
{"id":"tech_support_005","category":"tech_support","input":"Geek Help Desk invoice: $289 charge approved. If this was not you, call this number for a refund.","expected_risk_level":"dangerous","expected_scam_type":"refund_scam","expected_tactics":["fake invoice","callback request","refund bait"]}
|
| 36 |
+
{"id":"prize_001","category":"prize_lottery","input":"Congratulations! You won an iPhone. Pay a $9.99 shipping fee here: https://winner-shipping.example","expected_risk_level":"dangerous","expected_scam_type":"prize_scam","expected_tactics":["too good to be true","payment request","suspicious link"]}
|
| 37 |
+
{"id":"prize_002","category":"prize_lottery","input":"Lottery office: your unclaimed prize is ready. Send your bank login so we can deposit winnings.","expected_risk_level":"dangerous","expected_scam_type":"prize_scam","expected_tactics":["too good to be true","fake authority","credential request"]}
|
| 38 |
+
{"id":"prize_003","category":"prize_lottery","input":"You have been selected for a government grant. Processing fee required by midnight: http://grant-release.example","expected_risk_level":"dangerous","expected_scam_type":"advance_fee","expected_tactics":["fake authority","payment request","urgency","suspicious link"]}
|
| 39 |
+
{"id":"prize_004","category":"prize_lottery","input":"Claim your free vacation now. We only need your card number to hold the reservation.","expected_risk_level":"dangerous","expected_scam_type":"prize_scam","expected_tactics":["too good to be true","payment request","urgency"]}
|
| 40 |
+
{"id":"prize_005","category":"prize_lottery","input":"Final notice: your reward points expire today. Login at https://points-claim-center.example to cash out.","expected_risk_level":"dangerous","expected_scam_type":"credential_theft","expected_tactics":["urgency","suspicious link","credential request"]}
|
| 41 |
+
{"id":"romance_001","category":"romance","input":"I feel so close to you. My wallet was stolen while traveling. Can you send $300 so I can get home?","expected_risk_level":"dangerous","expected_scam_type":"romance_scam","expected_tactics":["emotional manipulation","payment pressure","urgency"]}
|
| 42 |
+
{"id":"romance_002","category":"romance","input":"I want to visit you, but the travel agent needs payment today. Please send it by wire transfer.","expected_risk_level":"dangerous","expected_scam_type":"romance_scam","expected_tactics":["emotional manipulation","payment pressure","urgency"]}
|
| 43 |
+
{"id":"romance_003","category":"romance","input":"Do not tell your family about us yet. I need help with a hospital bill before we can meet.","expected_risk_level":"dangerous","expected_scam_type":"romance_scam","expected_tactics":["secrecy","emotional manipulation","payment pressure"]}
|
| 44 |
+
{"id":"job_scam_001","category":"job_scam","input":"Remote job approved. Deposit this check and send part of it to the equipment vendor today.","expected_risk_level":"dangerous","expected_scam_type":"fake_check","expected_tactics":["fake job","payment pressure","overpayment"]}
|
| 45 |
+
{"id":"job_scam_002","category":"job_scam","input":"You are hired. Pay a refundable $75 onboarding fee at https://newhire-start.example to unlock training.","expected_risk_level":"dangerous","expected_scam_type":"advance_fee","expected_tactics":["fake job","payment request","suspicious link"]}
|
| 46 |
+
{"id":"suspicious_link_001","category":"suspicious","input":"Your account needs attention. Visit http://account-message-center.example when you have time.","expected_risk_level":"suspicious","expected_scam_type":"unknown_link","expected_tactics":["suspicious link","vague request"]}
|
| 47 |
+
{"id":"suspicious_link_002","category":"suspicious","input":"We could not process your request. Open https://customer-resolution.example for details.","expected_risk_level":"suspicious","expected_scam_type":"unknown_link","expected_tactics":["suspicious link","vague request"]}
|
| 48 |
+
{"id":"suspicious_link_003","category":"suspicious","input":"Your profile is incomplete. Confirm your information here: http://profile-update-center.example","expected_risk_level":"suspicious","expected_scam_type":"possible_credential_theft","expected_tactics":["suspicious link","credential request"]}
|
| 49 |
+
{"id":"suspicious_link_004","category":"suspicious","input":"Please review the secure document I shared: https://document-viewer-secure.example","expected_risk_level":"suspicious","expected_scam_type":"unknown_link","expected_tactics":["suspicious link","vague request"]}
|
| 50 |
+
{"id":"suspicious_link_005","category":"suspicious","input":"Your voicemail is available at http://voice-message-download.example","expected_risk_level":"suspicious","expected_scam_type":"unknown_link","expected_tactics":["suspicious link","attachment bait"]}
|
| 51 |
+
{"id":"suspicious_urgency_001","category":"suspicious","input":"Please call me back immediately about your account. This cannot wait.","expected_risk_level":"suspicious","expected_scam_type":"unknown_callback","expected_tactics":["urgency","vague request"]}
|
| 52 |
+
{"id":"suspicious_urgency_002","category":"suspicious","input":"Your subscription has a problem. We need confirmation today or it may be closed.","expected_risk_level":"suspicious","expected_scam_type":"unknown_billing","expected_tactics":["urgency","vague request"]}
|
| 53 |
+
{"id":"suspicious_urgency_003","category":"suspicious","input":"Final reminder: your paperwork is missing. Send your details by end of day.","expected_risk_level":"suspicious","expected_scam_type":"possible_credential_theft","expected_tactics":["urgency","credential request","vague request"]}
|
| 54 |
+
{"id":"suspicious_urgency_004","category":"suspicious","input":"Action required: confirm ownership of your account within 24 hours.","expected_risk_level":"suspicious","expected_scam_type":"possible_credential_theft","expected_tactics":["urgency","credential request"]}
|
| 55 |
+
{"id":"suspicious_urgency_005","category":"suspicious","input":"We noticed an issue with your delivery. Respond quickly so we can resolve it.","expected_risk_level":"suspicious","expected_scam_type":"possible_package_scam","expected_tactics":["urgency","vague request"]}
|
| 56 |
+
{"id":"suspicious_sender_001","category":"suspicious","input":"Hi, this is support. We need to confirm your billing info.","expected_risk_level":"suspicious","expected_scam_type":"possible_impersonation","expected_tactics":["fake authority","credential request","vague sender"]}
|
| 57 |
+
{"id":"suspicious_sender_002","category":"suspicious","input":"This is the office. Please send your date of birth so we can update your file.","expected_risk_level":"suspicious","expected_scam_type":"possible_credential_theft","expected_tactics":["credential request","vague sender"]}
|
| 58 |
+
{"id":"suspicious_sender_003","category":"suspicious","input":"Your invoice is attached. Open it and enter the requested information.","expected_risk_level":"suspicious","expected_scam_type":"unknown_attachment","expected_tactics":["attachment bait","credential request"]}
|
| 59 |
+
{"id":"suspicious_sender_004","category":"suspicious","input":"We tried reaching you. Use this temporary number from now on.","expected_risk_level":"suspicious","expected_scam_type":"unknown_new_channel","expected_tactics":["new channel","vague sender"]}
|
| 60 |
+
{"id":"suspicious_sender_005","category":"suspicious","input":"I am from payroll. Confirm your direct deposit account today.","expected_risk_level":"suspicious","expected_scam_type":"possible_payroll_scam","expected_tactics":["fake authority","urgency","credential request"]}
|
| 61 |
+
{"id":"suspicious_market_001","category":"suspicious","input":"I want to buy your item. I will send extra money and my mover will pick it up.","expected_risk_level":"suspicious","expected_scam_type":"possible_overpayment","expected_tactics":["overpayment","marketplace"]}
|
| 62 |
+
{"id":"suspicious_market_002","category":"suspicious","input":"Can you verify you are real by sending me the code I just texted you?","expected_risk_level":"suspicious","expected_scam_type":"account_takeover","expected_tactics":["credential request","verification code"]}
|
| 63 |
+
{"id":"suspicious_market_003","category":"suspicious","input":"I only use this escrow service for marketplace purchases: https://safe-escrow-pay.example","expected_risk_level":"suspicious","expected_scam_type":"fake_escrow","expected_tactics":["suspicious link","marketplace","payment pressure"]}
|
| 64 |
+
{"id":"suspicious_market_004","category":"suspicious","input":"The courier needs a small insurance payment before pickup. I will reimburse you.","expected_risk_level":"suspicious","expected_scam_type":"advance_fee","expected_tactics":["payment request","marketplace","advance fee"]}
|
| 65 |
+
{"id":"suspicious_market_005","category":"suspicious","input":"I am out of town but my assistant will handle payment. Please email your bank name.","expected_risk_level":"suspicious","expected_scam_type":"possible_overpayment","expected_tactics":["marketplace","credential request","third party"]}
|
| 66 |
+
{"id":"needs_check_bank_001","category":"needs_check","input":"Bank fraud alert: Did you attempt a $249.00 purchase at TARGET? Reply YES or NO.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_alert","expected_tactics":["verification needed"]}
|
| 67 |
+
{"id":"needs_check_bank_002","category":"needs_check","input":"Your bank card ending 1234 was used for $18.42 at Grocery Store. If this was not you, call the number on your card.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_alert","expected_tactics":["verification needed","trusted route"]}
|
| 68 |
+
{"id":"needs_check_bank_003","category":"needs_check","input":"Credit union alert: new login from a browser. Open your banking app directly if this was not you.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_alert","expected_tactics":["verification needed","trusted route"]}
|
| 69 |
+
{"id":"needs_check_bank_004","category":"needs_check","input":"Your statement is ready. Log in through the official app to view it.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_alert","expected_tactics":["verification needed","trusted route"]}
|
| 70 |
+
{"id":"needs_check_bank_005","category":"needs_check","input":"A transfer is scheduled for tomorrow. If you did not schedule this, contact customer service using the number on your card.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_alert","expected_tactics":["verification needed","trusted route"]}
|
| 71 |
+
{"id":"needs_check_delivery_001","category":"needs_check","input":"UPS update: your package is out for delivery today. Track it in the UPS app.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_delivery","expected_tactics":["verification needed","trusted route"]}
|
| 72 |
+
{"id":"needs_check_delivery_002","category":"needs_check","input":"FedEx delivery exception: weather delay reported. Check status on the official FedEx site.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_delivery","expected_tactics":["verification needed","trusted route"]}
|
| 73 |
+
{"id":"needs_check_delivery_003","category":"needs_check","input":"Your pharmacy order shipped. Open your pharmacy account if you need details.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_delivery","expected_tactics":["verification needed","trusted route"]}
|
| 74 |
+
{"id":"needs_check_delivery_004","category":"needs_check","input":"A package requiring signature is scheduled for tomorrow. No reply needed.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_delivery","expected_tactics":["verification needed"]}
|
| 75 |
+
{"id":"needs_check_delivery_005","category":"needs_check","input":"Your store pickup is ready. Bring your ID and order confirmation to the service desk.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_delivery","expected_tactics":["verification needed"]}
|
| 76 |
+
{"id":"needs_check_medical_001","category":"needs_check","input":"Reminder: dental appointment tomorrow at 2 PM. Call the office number you already have if you need to reschedule.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_appointment","expected_tactics":["verification needed","trusted route"]}
|
| 77 |
+
{"id":"needs_check_medical_002","category":"needs_check","input":"Your lab results are available in the patient portal. Log in through the clinic website.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_health","expected_tactics":["verification needed","trusted route"]}
|
| 78 |
+
{"id":"needs_check_medical_003","category":"needs_check","input":"Insurance notice: a claim was processed. Review it in your official member portal.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_health","expected_tactics":["verification needed","trusted route"]}
|
| 79 |
+
{"id":"needs_check_account_001","category":"needs_check","input":"Your password was changed. If this was not you, open the app directly and review account security.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_security","expected_tactics":["verification needed","trusted route"]}
|
| 80 |
+
{"id":"needs_check_account_002","category":"needs_check","input":"Your verification code is 482910. Do not share this code with anyone.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_security","expected_tactics":["verification needed","code warning"]}
|
| 81 |
+
{"id":"needs_check_account_003","category":"needs_check","input":"New device added to your account. Visit your account settings through the official app if this was not you.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_security","expected_tactics":["verification needed","trusted route"]}
|
| 82 |
+
{"id":"needs_check_school_001","category":"needs_check","input":"School reminder: permission forms are due Friday. Check the parent portal for details.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_notice","expected_tactics":["verification needed","trusted route"]}
|
| 83 |
+
{"id":"needs_check_school_002","category":"needs_check","input":"Library notice: your borrowed item is overdue. Log in to your library account to renew.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_notice","expected_tactics":["verification needed","trusted route"]}
|
| 84 |
+
{"id":"needs_check_billing_001","category":"needs_check","input":"Your utility bill is ready. No payment is due until next week. View it through your normal account.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_billing","expected_tactics":["verification needed","trusted route"]}
|
| 85 |
+
{"id":"needs_check_billing_002","category":"needs_check","input":"Payment reminder from your subscription service. If unsure, open the official app instead of using message links.","expected_risk_level":"needs_check","expected_scam_type":"possible_legitimate_billing","expected_tactics":["verification needed","trusted route"]}
|
| 86 |
+
{"id":"safe_personal_001","category":"safe","input":"Running ten minutes late. See you at the restaurant soon.","expected_risk_level":"safe","expected_scam_type":"none","expected_tactics":[]}
|
| 87 |
+
{"id":"safe_personal_002","category":"safe","input":"Can you bring milk on the way home?","expected_risk_level":"safe","expected_scam_type":"none","expected_tactics":[]}
|
| 88 |
+
{"id":"safe_personal_003","category":"safe","input":"Happy birthday! Hope you have a great day.","expected_risk_level":"safe","expected_scam_type":"none","expected_tactics":[]}
|
| 89 |
+
{"id":"safe_personal_004","category":"safe","input":"The meeting moved to 3 PM. Same room as before.","expected_risk_level":"safe","expected_scam_type":"none","expected_tactics":[]}
|
| 90 |
+
{"id":"safe_personal_005","category":"safe","input":"Dinner is at our house Saturday. No need to bring anything.","expected_risk_level":"safe","expected_scam_type":"none","expected_tactics":[]}
|
| 91 |
+
{"id":"safe_work_001","category":"safe","input":"Please review the agenda before our team call tomorrow.","expected_risk_level":"safe","expected_scam_type":"none","expected_tactics":[]}
|
| 92 |
+
{"id":"safe_work_002","category":"safe","input":"The report draft is in the shared project folder.","expected_risk_level":"safe","expected_scam_type":"none","expected_tactics":[]}
|
| 93 |
+
{"id":"safe_work_003","category":"safe","input":"I moved the deadline to Monday so everyone has more time.","expected_risk_level":"safe","expected_scam_type":"none","expected_tactics":[]}
|
| 94 |
+
{"id":"safe_work_004","category":"safe","input":"Thanks for sending the notes. I added two comments.","expected_risk_level":"safe","expected_scam_type":"none","expected_tactics":[]}
|
| 95 |
+
{"id":"safe_work_005","category":"safe","input":"Reminder: the office is closed Friday for the holiday.","expected_risk_level":"safe","expected_scam_type":"none","expected_tactics":[]}
|
| 96 |
+
{"id":"safe_marketing_001","category":"safe","input":"Weekend sale: 20 percent off selected items in store.","expected_risk_level":"safe","expected_scam_type":"none","expected_tactics":[]}
|
| 97 |
+
{"id":"safe_marketing_002","category":"safe","input":"Your coffee shop rewards balance updated after your last visit.","expected_risk_level":"safe","expected_scam_type":"none","expected_tactics":[]}
|
| 98 |
+
{"id":"safe_marketing_003","category":"safe","input":"New class schedule posted for next month.","expected_risk_level":"safe","expected_scam_type":"none","expected_tactics":[]}
|
| 99 |
+
{"id":"safe_marketing_004","category":"safe","input":"Your local bookstore has your preorder ready at the counter.","expected_risk_level":"safe","expected_scam_type":"none","expected_tactics":[]}
|
| 100 |
+
{"id":"safe_marketing_005","category":"safe","input":"Thanks for your purchase. Your receipt is available in your account.","expected_risk_level":"safe","expected_scam_type":"none","expected_tactics":[]}
|
tests/test_eval_dataset.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
from jawbreaker.schema import RISK_LEVELS
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def test_eval_dataset_has_100_unique_cases() -> None:
|
| 8 |
+
rows = [
|
| 9 |
+
json.loads(line)
|
| 10 |
+
for line in Path("eval/scam_eval.jsonl").read_text(encoding="utf-8").splitlines()
|
| 11 |
+
if line.strip()
|
| 12 |
+
]
|
| 13 |
+
|
| 14 |
+
assert len(rows) == 100
|
| 15 |
+
assert len({row["id"] for row in rows}) == 100
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def test_eval_dataset_required_fields_and_risk_levels() -> None:
|
| 19 |
+
required = {"id", "category", "input", "expected_risk_level", "expected_scam_type", "expected_tactics"}
|
| 20 |
+
rows = [
|
| 21 |
+
json.loads(line)
|
| 22 |
+
for line in Path("eval/scam_eval.jsonl").read_text(encoding="utf-8").splitlines()
|
| 23 |
+
if line.strip()
|
| 24 |
+
]
|
| 25 |
+
|
| 26 |
+
for row in rows:
|
| 27 |
+
assert required <= set(row)
|
| 28 |
+
assert row["expected_risk_level"] in RISK_LEVELS
|
| 29 |
+
assert isinstance(row["expected_tactics"], list)
|
| 30 |
+
assert ".example" in row["input"] or "http" not in row["input"].lower()
|
| 31 |
+
|