Detectlets: Compiling Forensic Accounting Research Into Computable Detection Modules

Source code → github.com/pon00050/jfia-forensic

Forensic accounting research has been published continuously since the 1980s. Beneish (1999) on M-Score. Dechow et al. (2011) on the F-Score. The CB/BW manipulation literature out of KOSDAQ in the late 2010s. Officer-network centrality work that grew out of the chaebol governance debates. Each paper proposes a detection rule: a signal, a threshold, a population it was tested on, an interpretation. Each rule is a candidate for screening real filings.

But the rules live in PDFs. The signals are described in prose. The thresholds are buried in tables. To run a detection screen against a real dataset you re-derive the rule each time, against the paper, in code. The next analyst does it again. The institutional knowledge of “what does the literature say about how to detect X” decays into “what did the last person who built this remember about the literature.”

A detectlet is what fixes that. It is a small structured artifact — a YAML file in this library’s case — that captures a paper’s detection rule in machine-readable form: the signals it uses, the thresholds it specifies, the variables it requires from the input data, the violation categories it maps to, and the citations to the source papers. Once you have detectlets, you have a registry of the literature’s detection content, queryable from code.

This library defines the schema and ships four reference detectlets grounded in JFIA literature.


What a Detectlet Looks Like

The Beneish M-Score detectlet, in its YAML form, captures everything you need to run the screen and trace it back to the source:

name: "Beneish M-Score"
version: "1.0.0"
scheme: "earnings_manipulation"
fss_violation_categories:
  - "revenue_fabrication"
  - "cost_distortion"
jfia_citations:
  - volume: 1
    issue: 1
    title: "Pre and Post-SOX Association between Audit Firm Tenure and Earnings Management Risk"
    authors: ["Santanu Mitra", "Donald R. Deis", "Mahmud Hossain"]
signals:
  - name: "DSRI"
    description: "Days Sales Receivable Index — receivables growing faster than revenue"
    threshold: 1.465
    direction: "above"
  # ... 7 more components
required_fields:
  - receivables
  - revenue
  - cogs
  - sga
  # ... 6 more
threshold: -1.78
korean_threshold: -2.45
output_fields:
  - m_score
  - flag
  - risk_tier
  # ...
interpretation: >
  M-Score above -1.78 (US threshold) or -2.45 (Korean bootstrap CI: [-3.50, -1.60])
  indicates possible earnings manipulation. Flags ~1,250 of 7,447 KOSDAQ company-years
  (2018-2023). TATA is negative for KOSDAQ (bootstrap-confirmed pattern).
  Labels sourced from 30 FSS sanctions (17 fraud=1, 13 fraud=0) + 20 auto-controls.

Three things are happening in that file. First, the rule is described in enough detail that another analyst can reproduce the screen. Second, the citations point back to the JFIA papers where the rule originates. Third, the empirical result on Korean data — flag rate, label source, threshold calibration — is captured alongside the rule, so the next reader knows not just what the paper said but how it has performed when actually run.

A DetectletRegistry loads all the YAML files in a directory and exposes them as Python objects:

from jfia_forensic import DetectletRegistry

registry = DetectletRegistry.from_yaml_dir("data/curated/detectlets/")
beneish = registry.get("Beneish M-Score")

print(beneish.threshold)              # -1.78
print(beneish.korean_threshold)       # -2.45
print(beneish.required_fields)        # ['receivables', 'revenue', ...]

That object is what a pipeline component reads when it wants to know what fields it needs to extract, what signals it needs to compute, and what threshold to apply.


The Four Reference Detectlets

The library ships four detectlets in data/curated/detectlets/, each grounded in published research and tested against Korean data.

Beneish M-Score — eight-variable earnings manipulation score, US and Korean thresholds, citations to the original Beneish (1999) paper and follow-up JFIA literature on audit-firm tenure interactions.

CB/BW Manipulation — at-issuance in-the-money detection for Korean convertible bonds and bonds with warrants. The signal is moneyness above 1 (stock price above conversion price at board-approval date). Maps to the FSS violation category disclosure_fraud and related_party (when affiliated bondholders are involved).

Officer Network Centrality — flags companies whose board officers occupy unusually central positions in the cross-company directorship graph. The signal is graph centrality above a threshold; the rationale traces to the JFIA literature on chaebol governance and independent-board failures.

Timing Anomaly — flags companies whose disclosure filings cluster suspiciously around price or volume spikes. The signal is a hypergeometric tail probability on the joint distribution of filing dates and abnormal trading days.

Each detectlet is a YAML file. Each file is independently versioned. Adding a fifth detectlet means writing one more YAML file, not modifying any existing code. The registry is the open-set extension point.


Where the Catalog Comes In

The detectlets reference JFIA papers by volume, issue, and index. To resolve those references — to actually pull the abstract, the keywords, the PDF URL — the library reads the jfia-catalog dataset:

from jfia_forensic import JFIACatalog

catalog = JFIACatalog.load("data/raw/jfia_catalog.json")
papers = catalog.search("earnings management", limit=5)
for p in papers:
    print(p.title)

The relationship between the two repositories is deliberate. jfia-catalog is the structured index — 469 articles across 16 years, 681 KB of JSON. It is useful on its own to any researcher doing literature review on forensic accounting. jfia-forensic is the layer that compiles selected papers into runnable detectlets and provides the search interface for the catalog. Splitting them keeps the catalog citable independently and lets the detectlet schema evolve without disturbing the underlying index.


What This Library Does Not Yet Do

The DetectletMatch class is exposed in the public API but does not yet implement matching logic. The intent is that a future version takes a company’s signal vector and reports which detectlets fire, with what scores and which citations. Today, the heavy lifting is on the consuming pipeline (kr-anomaly-scoring, krff-shell) — they read the detectlet YAML for thresholds and required fields, then run the screen themselves.

The Haiku-powered enrichment pipeline is implemented (it abstracts scheme and signal labels from JFIA paper abstracts) but has only been run on a subset of the catalog. Full enrichment is a 469-article batch job that has not been prioritized over user-facing work.

The signal vocabulary is closed-set, which is intentional. The library refuses to accept arbitrary signal names — every signal in a detectlet must appear in the canonical vocabulary defined in constants.py. This prevents the registry from drifting into per-paper signal name balkanization, but it means adding a genuinely new signal type requires a deliberate vocabulary update.


Why It Matters

A working forensic detection pipeline is, mechanically, a small number of detectlets running over a large body of filings. The detectlets define what to look for. The pipeline defines how to extract the inputs and produce the outputs. The catalog defines the literature backing the rules. Each layer is independently maintainable; the whole stack is an open, citable, queryable answer to “what does the forensic accounting literature say about how to detect X, and how does it perform on real data.”

The library is at github.com/pon00050/jfia-forensic. MIT license. 83 tests. Install with uv add git+https://github.com/pon00050/jfia-forensic.