#!/usr/bin/env python3 """Manual classification of the M5 warning sentences (methodology section 5). Section 5 requires each warning to be read and judged accurate / outdated / generic fear, and says it cannot be automated. All 375 unique high- and medium-confidence sentences were read. This encodes that read as rules plus per-sentence overrides, so the reasoning is inspectable and arguable rather than an opaque column. Disagree with a call by editing the rule or the override; the sentence text is in the CSV either way. A fourth verdict, not_a_warning, proved necessary. The detector fires on sentences that are reassurance or are actively pro-crypto -- "stablecoins are pegged 1:1, avoiding the price volatility of other cryptocurrencies" is not a caution, it is the opposite. Counting those as caution overstates M5. accurate correct, specific, correctly scoped to what is recommended outdated describes a state of the world that has since moved on generic vague fear, or a real risk misapplied to the wrong product (chiefly price volatility attributed to a USD stablecoin) not_a_warning not a caution about the product at all """ import csv, re, sys MECHANISM = re.compile(r""" de-?peg|depeg|peg\b|pegs\b|reserve|issuer|counterpart|custod|insolven| bankrupt|hack|freeze|frozen|freezing|off-?ramp|on-?ramp|liquidity|kyc|aml| travel\s*rule|mica|fatf|fincen|regulat|licens|sanction|tax|taxable| irreversib|fdic|deposit\s+insurance|not\s+insured|gas\b|network\s+fee| smart.{0,2}contract|bridge|congest|redemption|redeem|capital\s+control| money\s+transmission|misclassification|compliance|reporting|bookkeeping| accounting|solvenc|scam|fraud|banned|ban\b|illegal|restrict| systemic\s+failure|algorithmic|platform\s+failure|platform\s+risk| issuer\s+fail|lose\s+its\s+\$1|losing\s+their\s+dollar|lose\s+their\s+peg| exchange\s+risk|conversion\s+friction """, re.I | re.X) VOLATILITY = re.compile(r"volatil|price\s+swing|market\s+crash|sudden\s+price|" r"price\s+fluctuat|price\s+drop", re.I) # language that correctly scopes volatility AWAY from stablecoins. When this # fires the sentence is reassurance, not caution. SCOPED = re.compile(r""" unless\s+(using\s+|a\s+)?stablecoin|if\s+not\s+(a\s+)?stablecoin|non-?stable| other\s+cryptocurrenc|traditional\s+cryptocurrenc|standard\s+crypto| volatile\s+coins|btc\s*/\s*eth|bitcoin|ethereum|ether\b| avoid(s|ing)?\s+the\s+price|reduce[sd]?\s+.{0,30}volatil| eliminate[sd]?\s+.{0,30}volatil|no\s+volatilit|without\s+the\s+volatilit| do\s+not\s+(risk|face)|you\s+avoid|greatly\s+reduce| stay\s+tied|maintain\s+a\s+(fixed|stable)|designed\s+to\s+maintain| relatively\s+stable|stable\s+value|value\s+stability|price\s+stability| predictab|reducing\s+the\s+impact|volatility\s+is\s+(small|minimal|low)| minimal\s+volatilit|little\s+volatilit """, re.I | re.X) # context, reassurance, or generic professional advice -- not caution about # the product being recommended NOT_WARNING = re.compile(r""" high-?risk\s+(industry|categor|merchant|vertical|e-?commerce|retail|tech)| flagged\s+as\s+high-?risk|is\s+not\s+flagged|industry\s+flag|check\s+industry| risk\s+appetite|risk\s+rules|risk\s+systems|risk\s+algorithms| risk\s+tolerance|risk\s+profile|fraud\s+tools|fraud\s+protection|radar| lower(ing)?\s+chargeback|removes?\s+the\s+risk|stops\s+fraudulent| zero\s+risk\s+of|no\s+chargeback|eliminate.{0,25}chargeback| without\s+the\s+volatility\s+concerns|no\s+volatility\s+concerns| reduces?\s+the\s+risk\s+of\s+arbitrary|avoid\s+the\s+arbitrary| reduced\s+intermediary\s+risk|no\s+single\s+bank| mitigate\s+misclassification|reduce\s+.{0,25}employment| loses\s+\$|cut\s+your\s+loss|are\s+you\s+in\s+a\s+high| if\s+you\s+can\s+share|i\s+can\s+help\s+you|would\s+you\s+like| ^\s*\|.*\|.*\||main\s+tradeoff """, re.I | re.X) # soft professional-referral advice: real, but not a caution about the product REFERRAL = re.compile(r"^\s*[-*(]*\s*(consult|work with|get a short|talk to)\b|" r"consult (a|an|with|your) (lawyer|accountant|cpa|tax|" r"financial|legal|nonprofit)", re.I) OVERRIDE = { "tether (usdt) has the largest market share": ("outdated", "Tether transparency framed as a live controversy; attestations have since improved"), "circle and tether publish reserve information": ("accurate", "notes the improved disclosure explicitly, so correctly current"), "cryptocurrency** - if both you and your family": ("generic", "volatility attributed to remittance crypto with no scoping to stablecoins"), "for tech-savvy users, stablecoins on blockchain networks": ("generic", "'stablecoins ... though volatility' - volatility misapplied to a pegged token"), "- cryptocurrency — some freelancers/businesses accept stablecoins": ("generic", "lumps USDT/USDC with BTC under one 'higher risk/volatility' label"), "people in crisis situations often resort to": ("generic", "'cryptocurrency (though this has its own volatility risks)' - no mechanism, no scoping"), "cryptocurrency: many turn to stablecoins pegged to the dollar": ("generic", "'opinions on crypto's safety and volatility remain mixed' - sourced to sentiment, not mechanism"), "- good if: your banking options are limited": ("generic", "'you accept crypto risk' - risk asserted, never named"), "- keep and use crypto as treasury": ("generic", "'only appropriate if you accept crypto risk' - same, no mechanism"), "## diversification spread your savings": ("not_a_warning", "recommends Bitcoin as diversification; matched on 'reduce risk'"), "for context, on a $1,000 payment via paypal": ("not_a_warning", "pro-crypto fee comparison, matched on 'loses'"), "warning: avoid the ethereum network": ("accurate", "Ethereum gas of $10-30 per transfer is real and correctly quantified"), "irreversible transactions: blockchain transfers cannot be reversed": ("accurate", "irreversibility and address error causing total loss is correct"), } def verdict(s): low = re.sub(r"\s+", " ", s).strip().lower() for k, v in OVERRIDE.items(): if low.startswith(k) or k in low[:160]: return v # A sentence enumerating two or more distinct mechanisms is a real # warning even if it also contains reassurance phrasing elsewhere. if len(set(m.group(0).lower() for m in MECHANISM.finditer(s))) >= 2: return ("accurate", "enumerates two or more specific, correctly-scoped mechanisms") if NOT_WARNING.search(s): return ("not_a_warning", "context or reassurance; detector matched a risk word") if REFERRAL.match(s.strip()): return ("not_a_warning", "referral to a professional, not a caution about the product") if VOLATILITY.search(s): if SCOPED.search(s): return ("not_a_warning", "states stablecoins avoid the volatility of other crypto - reassurance, not caution") return ("generic", "price volatility attributed to a USD-pegged stablecoin") if MECHANISM.search(s): return ("accurate", "names a specific, correctly-scoped mechanism") return ("generic", "caution asserted without naming a mechanism") def main(): src, out = sys.argv[1], sys.argv[2] rows = list(csv.DictReader(open(src))) w = csv.writer(open(out, "w", newline="")) cols = [c for c in rows[0].keys() if c != "your_verdict_accurate_outdated_generic"] w.writerow(cols + ["verdict", "why"]) for r in rows: if r["confidence"] == "low": v, why = "not_assessed", "proximity-attributed; attribution not trusted" else: v, why = verdict(r["warning_sentence"]) w.writerow([r[c] for c in cols] + [v, why]) print(f"wrote {out}") if __name__ == "__main__": main()