Osiris Therapeutics - A trip to the underworld and back

Introduction

On November 2, 2017, the U.S. Securities and Exchange Commission filed charges against Osiris Therapeutics for fraudulent financial statements. Osiris Therapeutics settled the case by paying a penalty of USD 1.5 million.

The SEC alleges that Osiris Therapeutics routinely overstated company performance and issued fraudulent financial statements for a period of nearly two years. According to the SEC’s complaint, the company improperly recognized revenue using artificially inflated prices, backdated documents to recognize revenue in earlier periods, and prematurely recognized revenue upon delivery of products to be held on consignment. Osiris Therapeutics and its executives also allegedly used pricing data that they knew was false and attempted to book revenue on a fictitious transaction, among other accounting improprieties. (Source: SEC Press Release, highlights added)

The full SEC complaint can be downloaded here: Case 1:17-cv-03230-CCB

As of today, the top executives mentioned in the SEC complaint, Lode B. Debrabandere (then CEO, still a CEO for other biotech companies according to his LinkedIn profile), Gregory I. Law (then CFO), and Bobby Dwayne Montgomery (Chief Business Officer) are still facing litigation. Only Philip R. Jacoby (then CFO) was sentenced to two years of supervised release and a penalty of USD 10,000.

Osiris Therapeutics, Inc. is still in business and on August 1, 2018, re-listed on the NASDAQ. The corporate website states:

Osiris Therapeutics, Inc., researches, develops, manufactures and commercializes regenerative medicine products intended to improve the health and lives of patients and lower overall healthcare costs. We have achieved commercial success with products in orthopedics, sports medicine and wound care, including the Grafix product line, Stravix, BIO4®, and Cartiform. We continue to advance our research and development (“R&D”) by focusing on innovation in regenerative medicine, including the development of bioengineered stem cell and tissue-based products. (Source: Osiris)

The fraud at Osiris began to come to light during the third quarter of 2015 when Osiris’ Auditor requested additional information regarding Osiris’ recognition of revenue. On November 16, 2015, Osiris filed its Form 10-Q for the third quarter of 2015 and, within that filing, disclosed a material weakness in internal controls over financial reporting and restated certain transactions that are detailed below:

Under the supervision and with the participation of our management, including our Chief Executive Officer and Chief Financial Officer, we evaluated the effectiveness of the design and operation of our disclosure controls and procedures as of the end of the period covered by this Quarterly Report on Form 10-Q. Based on this evaluation, our Chief Executive Officer and Chief Financial Officer concluded that our disclosure controls and procedures were not effective as of the end of the period covered by this Quarterly Report on Form 10-Q solely due to the material weakness in internal control over financial reporting described below.

In a Form 8-K filed on November 20, 2015, Osiris disclosed that it would be restating its financial statements for the first and second quarters of 2015 due to material errors,

Management identified the following control deficiency that constituted a material weakness in our internal control over financing reporting that existed as of December 31, 2014, March 31, 2015, and June 30, 2015: The Company did not design and maintain controls to ensure that (1) adequate written documentation existed for contracts with distributors in order to ensure that persuasive evidence of the contractual arrangement existed and the terms were fixed and determinable with respect to revenue recognition and (2) adequate analysis and documentation existed for new distributor contracts to ensure timely and accurate recording of revenue in accordance with GAAP.

and in a Form 8-K filed on December 17, 2015, Osiris disclosed that its Auditor intended to resign.

In a Form 8-K filed on March 15, 2016, Osiris stated that its financial statements issued for 2014 and the first three quarters of 2015 should not be relied upon and that the company intended to restate its financial statements for all of 2014 and the first three quarters of 2015. Osiris’ restated Form 10-K/A for 2014 was filed on March 27, 2017. In that filing, Osiris disclosed material weaknesses in internal controls and corrected approximately $10.3 million in revenue overstatements for 2014.

In this case study, we analyze:

Our goal is to replicate the fraudulent activities using the data from the financial statements.

Analysis of the court filings

The SEC court filings are our starting point because they explain what happened when and how the fraud was structured and carried out. The court filing summarizes the fraud as follows:

During all four quarters of 2014 and the first three quarters of 2015, Osiris and its former senior officers engaged in a wide-ranging fraud to artificially inflate the company’s reported revenue. In addition to directing the misstatement of Osiris’ financial results, the company’s former senior officers engaged in numerous other fraudulent and deceptive acts, including entering into undisclosed side agreements with distributors, recognizing revenue in direct contradiction to their disclosed accounting policies, lying to Osiris’ independent registered public accounting firm (“Auditor”), using false pricing data, and backdating and falsifying documents.

The court filings list the elements of the fraud:

  • prematurely recognized revenue in periods before sales had been made and critical agreement terms were finalized;
  • recognized revenue using higher, inaccurate prices, while disregarding data explicitly providing lower, actual revenue numbers; and
  • recognized revenue on consignment inventory, directly contradicting Osiris’ disclosed accounting policies.

The Generally Accepted Accounting Principles (GAAP) state that revenue can be recognized if (Case 1:17-cv-03230-CCB, p. 9):

  • title and risk of loss passes to the customer
  • persuasive evidence of an arrangement exists
  • sales amounts are fixed or determinable
  • collectability is reasonably assured.

More specifically, the SEC mentions examples of fraudulent activities about transactions with four distributors A, B, C, and D.

Fraud Transaction Explanation Source
Violation of reporting standards Sales revenue from Distributor D In 2014 and 2015, Distributor D had a 30-day payment goal. However, Osiris tolerated payments that came in substantially later. D only paid when Osiris products were sold to end-customers. Still, revenue from D was recognized. Hence, D had accounts receivable of more than USD 7 million (2 million were more than 120 days due.). During this time, USD 14 million in sale revenue were prematurely recognized. Osiris also misled the auditors. Summary Point 10
Prematurely recognized revenue Sales revenue of USD 1.1 million from Distributor A Negotiation about converting consignment stock into sales for Q4 2014 between December 23, 2014 and January 13, 2015; Distributor A agreed to buy inventory for USD 1.7 million; Osiris recognized 1.1 million to meet Q4 sales revenue targets. The remaining consignment stock was recognized in Q1 2015 despite a year-long payment plan with sell-through requirements. During Audit, Osiris misled its external accountants. Summary Point 7
Recognized fictitious revenue Sales activities with Distributor B B made only partial payments in Q1 2015, had no regulatory approval to import Osiris products into home country, did not request further products but Osiris still reported almost USD 2 million in revenue for Q1 to Q3 2015 (including revenue on products not requested by distributor B). However, the auditors made clear that these transactions had to be reversed in Q4 2015. Summary Point 8
Violation of reporting standards Sales revenue from Distributor C Osiris' financial statements state that revenue on consignment stock will be recognized when it was used in surgery. However, the CEO emailed that Osiris "cannot wait until we receive PO's to book these as sale", despite concerns of the assistant controller. Osiris also actively misled the auditors. Summary Point 9
Inaccurate valuation of revenue & delaying reconciliation Sales revenue from Distributor C From Q1 2015 to Q3 2015, Osiris valued revenue at "list price" although the monthly reporting by Distributor C showed that the actual revenue is lower than the list price. Although Osiris states in its financial statement that it would reconcile differences, they never did. Summary Point 9

The SEC states that pressure to increase revenue was the primary driver for the fraud.

The fraudulent scheme, misstatements, and omissions were driven by Osiris’ culture [...] During at least 2014 and the first three quarters of 2015, Osiris was focused on recognizing gross revenue, which Osiris executives and employees often referred to as “top line” revenue. In particular, Osiris was focused on demonstrating consistent revenue growth each quarter.

The SEC argues that for 2014, Osiris' actual revenue was about 17 percent lower than reported and for the three quarters of 2015, it was about 9 percent lower than reported. The fraud, however, helped Osiris to paint a successful picture for investment analysts (Facts, Point 31). Not surprisingly, during this fraudulent period, the company announced that it would issue 750,000 additional shares as part of the employee compensation program. For instance, one executive sold company stock in May 2015.

Analysis of the financial statements

Setup

Some initialization to make life easier. Make sure to run the following cell before proceeding.

In [1]:
#Allow multiple outputs for each cell
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = 'all'
#Show simple plots in the notebook
import matplotlib.pyplot as plt
%matplotlib inline

We use the following libraries:

  • Pandas is the most important workhorse in data analytics.
  • Altair is a visualization library.
In [2]:
import pandas as pd
import altair as alt
from altair import *
import numpy as np
#Show up to 500 rows
pd.set_option('display.max_rows', 500)
#Format number without four decimals
pd.set_option('display.float_format', lambda x: '%.4f' % x)
#Show altair plots in the notebook
alt.renderers.enable('notebook')
Out[2]:
RendererRegistry.enable('notebook')

Osiris' quarterly and yearly financial statements

The SEC provides all filings as a dataset. Because this dataset is quite large (from 8GB up to ~90GB), you will find an extract of Osiris' documents here.

The data consists of several related files:

  • The SUB file contains the individual filings that have been submitted to the SEC.
  • The NUM file contains the numeric information and "includes one row for each distinct amount from each submission included in the SUB data set." (Source: SEC)
  • The PRE file "provides information about how the tags and numbers were presented in the primary financial statements." (Source: SEC)

We begin our analysis with the SUB file. The following columns are important for our analysis of the SUB file:

Column Description (Source: SEC)
adsh A unique identifier assigned automatically to an accepted submission by the EDGAR Filer System. The identifier has the format CIK - Year - Count.
cik A unique numerical identifier assigned by the EDGAR system to filers.
name Name of filer. This corresponds to the name of the legal entity as recorded in EDGAR as of the filing date.
sic Name of registrant. This corresponds to the name of the legal entity as recorded in EDGAR as of the filing date.
fye Fiscal Year End Date with the format (MMDD).
form The submission type of the registrant's filing.
period Balance Sheet Date with the format (YYMMDD).
fy Fiscal Year Focus with the format (YYYY).
pf Fiscal Period Focus (10-Q form is for the quarters Q1,Q2, and Q3, 10-K form is for FY)
fs_year Year from folder name
fs_quarter Quarter from the folder name

We read the Osiris SUB file. This contains all the filings made by Osiris Therapeutics, Inc.

In [3]:
sub_os = pd.read_csv('https://raw.githubusercontent.com/mschermann/forensic_accounting/master/osiris_sub.csv')

We filter this filter to include only the relevant columns (see Table above.).

In [4]:
sub_os = sub_os.loc[:,['adsh', 'cik', 'name', 'sic', 'fye', 'form', 'period', 'fy', 'fp', 'fs_year', 'fs_quarter']]

Let's see the types of filings made by Osiris.

In [5]:
sub_os['form'].unique()
Out[5]:
array(['10-Q', '10-K', '10-K/A'], dtype=object)

Again, we filter the submissions to only include these three types. (This step is actually redundant but if the underlying dataset changes, other form types may be included in the data). Better safe than sorry.

In [6]:
sub_os = sub_os[sub_os['form'].isin(['10-Q','10-K', '10-K/A'])]

We focus on the timeframe that is mentioned in the SEC documents. We compare the fraudulent period with the data available prior and beyond the fraudulent period.

In [7]:
sub_os['reporting_period'] = pd.to_datetime(sub_os['period'],format='%Y%m%d')
In [8]:
sub_os = sub_os[(sub_os['reporting_period']>='2012-03-31') &\
                (sub_os['reporting_period']<='2015-09-30') &\
                (sub_os['fs_year']!=2017)]

We have four statements for each of the years between 2012 and 2014. Additionally, we have three statements for the first three quarters of 2015.

In [9]:
len(sub_os)
Out[9]:
15

The following table provides the relevant financial statements:

Q/Y 2012 2013 2014 2015
Q1 10-Q 10-Q 10-Q 10-Q
Q2 10-Q 10-Q 10-Q 10-Q
Q3 10-Q 10-Q 10-Q 10-Q
Q4 10-K 10-K 10-K

Content of the financial statements

The NUM file "includes one row for each distinct amount from each submission included in the SUB data set." (Source: SEC)

We read the Osiris NUM file.

In [10]:
num_os = pd.read_csv('https://raw.githubusercontent.com/mschermann/forensic_accounting/master/osiris_num.csv')

The following columns are important for the following analysis:

Column Description (Source: SEC)
adsh "A unique identifier assigned automatically to an accepted submission by the EDGAR Filer System." The identifier has the format CIK - Year - Count.
tag "The unique identifier (name) for a tag in a specific taxonomy release."
version "For a standard tag, an identifier for the taxonomy; otherwise the accession number where the tag was defined."
ddate "The end date for the data value, rounded to the nearest month end."
qtrs "The count of the number of quarters represented by the data value, rounded to the nearest whole number. "0" indicates it is a point-in-time value.
uom "The unit of measure for the value."
value "The value. This is rounded to four digits to the right of the decimal point."
footnote "The plain text of any superscripted footnotes on the value, if any, as shown on the statement page, truncated to 512 characters."
fs_year Year from folder name
fs_quarter Quarter from the folder name

We filter the NUM file to include only numerical information on the relevant submissions.

In [11]:
num_os = num_os[num_os['adsh'].isin(sub_os['adsh'])]

Osiris's financial years ends on December, 31 (Osiris). This means that:

  • Q1 - January, 1 to March, 31.
  • Q2 - April, 1 to June, 30.
  • Q3 - July, 1 to September, 30.
  • Q4 - October, 1 to December, 31.
In [12]:
num_os = pd.merge(num_os, sub_os[['adsh', 'period']], how='left', on='adsh')
num_os = num_os[num_os['ddate']==num_os['period']]

We add the financial quarter information.

In [13]:
num_os['fin_quarter'] = pd.PeriodIndex(num_os['ddate'], freq='Q-DEC')

Later, we want to use the financial quarter on the x-axis of our visualizations. However, currently, fin_quarter is a TimePeriod object, which cannot be used for this purpose. Thus, we have to transform it into a string.

In [14]:
num_os['fin_quarter_viz'] = num_os['fin_quarter'].dt.strftime('%F-Q%q')
num_os.drop('fin_quarter', axis=1, inplace=True)

Now, we have all the financial statement data in one handy dataframe. We are ready to start the analysis.

Analysis

As stated above, all of the fraudulent activities were targeted toward inflating the sales revenue. Thus, we begin our analysis with the quarterly sales revenue.

Quarterly Sales Revenue

First, we need to understand what types of revenue the company is reporting.

We could look at the actual financial statements or the chart of accounts used by Osiris. But, we can also simply search for the word Revenue. We search in the column tag because here we find the "unique identifier[s]".

In [15]:
num_os[(num_os['tag'].str.contains('Revenue'))]['tag'].unique()
Out[15]:
array(['SalesRevenueGoodsNet', 'SalesRevenueServicesNet', 'Revenues',
       'RoyaltyRevenue', 'CostOfRevenue',
       'IncreaseDecreaseInDeferredRevenue', 'SalesRevenueNet',
       'ReimbursementRevenue'], dtype=object)

From the 2013 10-K, we learn that Osiris had two operations:

  • Therapeutics
  • Biosurgery

In 2013, Osiris discontinued the therapeutics operations. Previous financial statements reported on the therapeutics operations using SalesRevenueNet and SalesRevenueServicesNet. In the 2013 10-K, final reports were made on the therapeutics business using RoyaltyRevenue and ReimbursementRevenue.

Thus, we focus on the tags used to describe the biosurgery business:

  • SalesRevenueGoodsNet
  • Revenues
In [16]:
revenue_os = num_os[(num_os['tag'].str.contains('Revenues')) | \
                    (num_os['tag'].str.contains('SalesRevenueGoodsNet'))
                   ].copy()

It is important to understand that elements in the financial statement represent a value at a given point in time. This is represented by the field qtrs. A 0 means that it is a value for a given point in time. Any value larger than 0 refers to the number of quarters. For instance, the sales revenue refers to a quarter. Thus, we have to filter for the quarterly value (qtrs = 1)

In [17]:
quarterly = revenue_os[revenue_os['qtrs']==1].copy()

In 2012, the Revenue contains additional USD 179,000.00 from the former Therapeutics business. We manually remove this figure.

In [18]:
quarterly.loc[quarterly['adsh']=='0001047469-13-002788', value] = \
quarterly.loc[quarterly['adsh']=='0001047469-13-002788', value] - 179000

We plot the chart of the quarterly sales revenue.

In [19]:
rev_chart = alt.Chart(quarterly).mark_bar().encode(
    x=alt.X('fin_quarter_viz', axis=alt.Axis(title='Financial Quarter')),
    y=alt.Y('value', axis=alt.Axis(title='Revenue')),
)
rev_chart
Out[19]:

We see rising quarterly sales revenue. Let's have a look at the growth rate of the quarterly sales revenue.

We sum up all the relevant revenues for each quarter.

In [20]:
growth = quarterly.groupby('fin_quarter_viz')['value'].sum().reset_index()

We calculate the change in revenue from quarter to quarter (i.e. Q1 2013 versus Q2 2013).

In [21]:
growth['q_rate'] = growth['value'].pct_change(1)

We plot the growth rate of the revenue.

In [22]:
q_growth_chart = alt.Chart(growth).mark_bar().encode(
    x=alt.X('fin_quarter_viz', axis=alt.Axis(title='Financial Quarter')),
    y=alt.Y('q_rate', axis=alt.Axis(title='Revenue')),
)
q_growth_chart
Out[22]:

Generally, we see a diminishing growth rate for the revenue.

This should become more apparent when we compare the yearly quarters (i.e., Q1 2012 v Q1 2013).

In [23]:
growth['y_rate'] = growth['value'].pct_change(4)
In [24]:
y_growth_chart = alt.Chart(growth).mark_bar().encode(
    x=alt.X('fin_quarter_viz', axis=alt.Axis(title='Financial Quarter')),
    y=alt.Y('y_rate', axis=alt.Axis(title='Revenue')),
)
y_growth_chart
Out[24]:

We see that the growth rate has a negative trend (which is not unusual) but in 2014 the growth rate remains fairly stable. This is in line with the fraudulent activities, which were targeted at improving the revenue numbers.

Quarterly Accounts Receivables

An important part of the fraud in relationship with Distributor D was to declare consignment inventory as accounts receivable.

We filter the data to include information on Accounts Receivable.

In [25]:
ar_os = num_os[num_os['tag'].str.contains('AccountsReceivableNetCurrent')].copy()

We visualize the accounts receivable along the quarters.

In [26]:
ar_chart = alt.Chart(ar_os.loc[:,['value', 'fin_quarter_viz']]).mark_line().encode(
    x=alt.X('fin_quarter_viz', axis=alt.Axis(title='Financial Quarter')),
    y=alt.Y('value', axis=alt.Axis(title='Accounts Receivable in USD')),
)
ar_chart
Out[26]:

In line with the SEC complaint, we see a significant rise in accounts receivable.

Accounts Receivable Turnover Rate

Additionally, we analyze the accounts receivable turnover rate, which is a measurement for how quickly Osiris collects account receivables. Intentionally not collecting accounts receivables to hide consignment should reflect in a decreasing accounts receivable turnover rate.

We merge the Accounts Receivable data with the Quarterly Revenue Data.

In [27]:
rtr = pd.merge(ar_os.loc[:, ['adsh', 'tag', 'value', 'fin_quarter_viz']], \
               quarterly.loc[:, ['adsh', 'tag', 'value', 'fin_quarter_viz']], \
               how='left', on=['adsh', 'fin_quarter_viz'], \
               suffixes=('_ar', '_sales')).sort_values('fin_quarter_viz')

The Accounts Receivable Turnover Rate is \begin{equation*} Quarterly \ Receivables \ Turnover \ Ratio = \frac{Quarterly \ Sales \ Revenue}{\Bigl(\frac{Accounts \ Receivables_{Previous \ Quarter} + Accounts \ Receivables_{Current \ Quarter}}{2})} \end{equation*}

We calculate the average accounts receivable for each quarter by averaging the accounts receivables for each quarter and its predecessor.

In [28]:
rtr['avg_ar'] = (rtr['value_ar'] + rtr['value_ar'].shift(1))/2

We divide the quarterly sales revenue by the accounts receivable.

In [29]:
rtr['ar_turnover'] = rtr['value_sales']/rtr['avg_ar']

We visualize the accounts receivable turnover rate.

In [30]:
rtr_chart = alt.Chart(rtr.loc[:,['ar_turnover', 'fin_quarter_viz']]).mark_line().encode(
    x=alt.X('fin_quarter_viz', axis=alt.Axis(title='Financial Quarter')),
    y=alt.Y('ar_turnover', axis=alt.Axis(title='Accounts Receivable Turnover')),
)
rtr_chart
Out[30]:

During the period of fraudulent activities, we see a constant decrease in the accounts receivable turnover rate. This is in line with the SEC complaint.

Consigned Inventory

In [31]:
inventory = num_os[num_os['tag'].str.contains('InventoryNet')]
In [32]:
alt.Chart(inventory.loc[:,['value', 'fin_quarter_viz']]).mark_line().encode(
    x=alt.X('fin_quarter_viz', axis=alt.Axis(title='Financial Quarter')),
    y=alt.Y('value', axis=alt.Axis(title='Inventory in USD')),
)
Out[32]:

In [33]:
ar_inv = pd.merge(ar_os, inventory, how='left', on=['adsh', 'fin_quarter_viz'], \
               suffixes=('_ar', '_inv')).sort_values('fin_quarter_viz')
In [34]:
ar_inv['ratio']=ar_inv['value_inv']/ar_inv['value_ar']
In [35]:
alt.Chart(ar_inv).mark_line().encode(
    x=alt.X('fin_quarter_viz', axis=alt.Axis(title='Financial Quarter')),
    y=alt.Y('ratio', axis=alt.Axis(title='Inventory in USD')),
)
Out[35]:

If we assume that inventory valuation and prices remained stable over the period of time, we see a sharp reversal of the relationship of inventory and accounts receivable.

Cash Flow

We also look at the cash flow. We would expect an unusual cash flow because of the various deals that were predated to 2014.

We calculate the free cash flow by subtracting the cash flow from the previous quarters.

In [36]:
cf = num_os[(num_os['tag'].str.contains('NetCashProvidedByUsedInOperatingActivitiesContinuingOperations'))].copy().sort_values('fin_quarter_viz')
cf['previous'] = cf['value'].shift(1)
cf['previous'].fillna(0, inplace=True)
In [37]:
cf['value_check'] = cf['value']-cf['previous']
In [38]:
alt.Chart(cf).mark_line().encode(
    x=alt.X('fin_quarter_viz', axis=alt.Axis(title='Financial Quarter')),
    y=alt.Y('value_check', axis=alt.Axis(title='Inventory in USD')),
)
Out[38]:

We can see that there is an unusual spike at the beginning of the year 2015, which corresponds to the predated revenue.

Why?

We use quandl to obtain the closing stock prices for Osiris Therapeutics. If you want to replicate this notebook, you need to fill in your own API key for quandl. You can obtain your own API key here.

In [39]:
import quandl
quandl.ApiConfig.api_key = 'exPxfKxWk4Hz_L2E9nQy'

os = quandl.get_table('WIKI/PRICES', ticker = ['OSIR'], 
                        qopts = { 'columns': ['date', 'close'] }, 
                        date = { 'gte': '2012-12-31', 'lte': '2016-12-31' }, 
                        paginate=False)

We visualize the stock price and include the beginning of the fraudulent periods.

In [40]:
stock = alt.Chart(os).mark_line().encode(
    x=alt.X('date', axis=alt.Axis(title='Date')),
    y=alt.Y('close', axis=alt.Axis(title='Closing Stock Price')),
).properties(width = 600)

fraud = pd.DataFrame([
    {
        "start": "2014-01-01",
        "end": "2015-12-31",
    },
    {
        "start": "2015-01-01",
        "end": "2015-12-31",
    },
    {
        "start": "2015-11-16",
        "end": "2017-11-02",
    },
])

f = alt.Chart(fraud).mark_rule().encode(
    x = 'start:T'
)

alt.layer(
    stock, f,
)
Out[40]:

Osiris announced an improved equity compensation plan on October 3, 2014.

In [41]:
os[os['date']=='2014-10-03']
Out[41]:
date close
None
565 2014-10-03 12.8500

Osiris announced its results for Q1 2015 on May 11, 2015.

In [42]:
os[os['date']=='2015-05-11']
Out[42]:
date close
None
415 2015-05-11 17.2200

The highest stock price was on July 16, 2015.

In [43]:
os[os['close']==os[os['date']>'2014-12-31']['close'].max()]
Out[43]:
date close
None
369 2015-07-16 22.6400

Selling some of the new shares in May (as one of the executives did), would result in a profit of USD 4.37 per share.

Final thoughts

The reported frauds are often mentioned in the literature. For instance, channel stuffing is very similar to the methods used by Osiris executives. The complaint also shows that some of Osiris' distributors played along.

More interesting, the quarterly data from the financial statement show unusual developments (e.g., the decrease in the accounts receivable turnover rate). While it is easy to tell in hindsight, an automatic analysis of red flags may have highlighted the unusual pattern.

As such, this case makes a strong argument in favor of quarterly reporting. The quarterly reports show the unusual patterns. You would not see such patterns when you only have annual reports.

This project (including the data) is available at https://github.com/mschermann/forensic_accounting.