reports <- read_reports()The business reports
contoso includes 1,127 fictional internal business reports about Contoso’s trading from May 2016 to the end of 2025. They are the documents a finance team reads alongside its sales data: weekly flashes, monthly reviews, quarterly country reports, committee minutes and plan memos. Each is written by a named person in a named business unit, published on a realistic date, and based on the “mega” dataset from create_contoso_duckdb("mega").
Types of report
| Business area | Report type | Authors (in post on the publication date) | How often | A full year | 2016-2025 | Typical length |
|---|---|---|---|---|---|---|
| Sales |
sales_flash: Weekly Sales Flash |
Sales Operations: Benjamin Castellanos to 2019, Priya Raman from 2020 | Weekly, every Monday | 52 (53 in 2020) | 501 | 430 words |
| Sales |
regional: Quarterly Business Review |
Country managers for the US, Germany, UK and Canada | Quarterly | 16 | 152 | 720 words |
| E-commerce and marketing |
ecommerce: E-commerce Performance Review |
Digital Commerce: Inès Duarte to 2021, Marcus Lindqvist from 2022 | Monthly | 12 | 115 | 640 words |
| Supply chain |
supply_chain: Vendor & Supply Review |
Supply Chain & Procurement: Victor Adeyemi to September 2020, Hannah Okafor from October 2020 | Monthly | 12 | 115 | 680 words |
| Manufacturing |
manufacturing: Contoso, Ltd Plant Report |
Tomasz Wisniewski, private-label plant | Monthly | 12 | 115 | 580 words |
| Pricing |
pricing: Pricing & Promotions Committee Minutes |
Chair: Robert Ashdown to 2022, Olivia Grant from 2023 | 5 meetings a year | 5 | 48 | 690 words |
| Planning and finance |
aop: Annual Operating Plan memo |
Corporate Planning: Margaret Holloway to June 2020, Daniel Okoye from July 2020 | Once, before each year from 2017 | 1 | 9 | 1,040 words |
| Operations |
alert: Supply alerts and operations notices |
Supply Chain, Retail Operations | When something happens | 2-5 | 34 | 310 words |
| External market |
industry: Sector Review |
Northbridge Retail Insights, an outside analyst | Quarterly | 4 | 38 | 690 words |
2016 is a partial year, because the data starts on 18 May 2016: weekly flashes run from week 21, monthly reports from June, quarterly reports cover Q3 and Q4 only, and there is no plan or budget.
reports |>
dplyr::count(report_type, unit, sort = TRUE)
#> # A tibble: 13 × 3
#> report_type unit n
#> <chr> <chr> <int>
#> 1 sales_flash Sales Operations 501
#> 2 ecommerce Digital Commerce 115
#> 3 manufacturing Contoso Manufacturing (Private Label) 115
#> 4 supply_chain Supply Chain & Procurement 115
#> 5 pricing Pricing & Promotions Committee 48
#> 6 industry Northbridge Retail Insights (external) 38
#> 7 regional Regional Sales - Canada 38
#> 8 regional Regional Sales - Germany 38
#> 9 regional Regional Sales - United Kingdom 38
#> 10 regional Regional Sales - United States 38
#> 11 alert Retail Operations 26
#> 12 aop Corporate Planning 9
#> 13 alert Supply Chain & Procurement 8Each type follows its own template:
| Report type | Sections |
|---|---|
sales_flash |
Headline, key numbers table, Channel, Country, Category, Watch list |
regional |
Executive summary, Performance vs last year and budget, Category highlights, Store operations, Customers and competitors, Priorities next quarter, Asks of HQ |
ecommerce |
Headline, Channel mix, Online performance vs last year and budget, Online category performance, Site and marketing notes, Priorities for next month |
supply_chain |
Summary, Vendor scorecard (red/amber/green by manufacturer), Issues and mitigations, Outlook |
manufacturing |
Summary, Sell-through by category and product line, Cost and yield, Capacity and launches, Risks |
pricing |
Attendees, Review of recent performance, Discussion, Decisions, Action items |
aop |
Summary of targets, Key assumptions, Phasing through the year, Risks to the plan, Accountabilities by unit |
alert |
What happened, Impact, Actions, Next update |
industry |
Key takeaways, Demand by category, Channels, Pricing and promotions, Outlook |
A sample report
The Weekly Sales Flash for week 20, in full:
sample <- dplyr::filter(reports, id == "sales-flash-2025-w20")
cat(sample$body)
#> # Weekly Sales Flash - Week 20 (12 May 2025 to 18 May 2025)
#> Priya Raman, Director of Sales Operations, Sales Operations | 19 May 2025
#>
#> **Headline**
#> - Revenue was $590M, flat on week 19 (-0.6%) and down about 9% on last year. That is a run-rate of roughly $2.4B a month.
#> - Stores were up 2.5% YoY. Online was down about 17% and is still the whole story on the gap.
#> - Margin held at 56% and discounts at 5.9%, within policy. No promo leakage.
#>
#> | Metric | Week 20 | WoW | YoY |
#> |---|---|---|---|
#> | Revenue | $590M | -0.6% | -9.3% |
#> | Online | $334M | -1.2% | -16.6% |
#> | Stores | $257M | +0.3% | +2.5% |
#> | Gross margin | 56.0% | | |
#> | Discount | 5.9% | | |
#> | Units | 2.39M | | |
#>
#> **Channel**
#> - **Online** is still carrying the marketplace exit. Migration to contoso.com is running well short of plan, and the paid-search cut isn't helping. The WoW slip of about 1% is small, but there is no sign of it turning.
#> - **Stores** are flat WoW and positive YoY for another week. Store Next is doing the lifting.
#>
#> **Country**
#> - **Wave 1 markets are strong YoY:** Germany +25%, Netherlands +27%, Italy +28% and France +11%. All were up or roughly flat WoW.
#> - **Wave 2:** Canada was +13% YoY. Australia was +9% YoY but -4.7% WoW, the weakest WoW of any market.
#> - **UK** was +3% YoY and +2.7% WoW as the Store Next rollout starts.
#> - **US** was down about 11% YoY at $111M. There is no Store Next until the July pilot.
#>
#> **Category**
#> - **Cameras** were +28% YoY. The A. Datum creator line and Fabrikam vlogging cameras in the experience zones are doing what they were meant to.
#> - **Audio** was +23% and **Games** +16%, both off a small base, which fits the floor-space shift.
#> - **TV and Video** was down about a third YoY (-36%) and -4% WoW. The range reset is biting far harder than the -8% plan assumption.
#> - **Computers** were down 10% YoY at $209M. Refresh demand isn't showing up yet, against a plan that weights the Lumen 10 lift to the first half.
#> - **Home Appliances** were down 17% YoY. The Litware safety notice may still be dampening the small-appliance categories.
#>
#> **Watch list**
#> - **Computers:** only about six weeks are left in H1 for the refresh to show. Escalate if it is still negative YoY by week 24.
#> - **TV and Video:** is the WoW decline stabilising, or is there more floor-space drag to come?
#> - **Australia:** check whether the WoW dip is a one-off or a post-conversion fade.
#> - **UK:** track the weekly lift as stores convert.
#> - **Online:** watch contoso.com conversion from former marketplace customers.Dimensions of a report
Metadata
Every report carries the same eight metadata fields, plus its text:
| Field | Type | Example |
|---|---|---|
id |
text | sales-flash-2025-w20 |
title |
text | Weekly Sales Flash - Week 20 (12 May 2025 to 18 May 2025) |
unit |
text | Sales Operations |
author |
text | Priya Raman, Director of Sales Operations |
report_type |
text | sales_flash |
period_start |
date | 2025-05-12 |
period_end |
date | 2025-05-18 |
published |
date | 2025-05-19 |
body |
Markdown text | the report itself |
period_start and period_end give the period a report covers, and published gives the date it was released. The gap between them varies by type: a weekly flash comes out the day after its week ends, a regional review about 12 days after its quarter. The plan memo is negative: each one is published in December of the year before the one it covers.
Business dimensions
The figures in each report are cut by the same dimensions as the contoso data. Which cuts a report uses depends on its type:
| Report type | Period | Breakdowns | Compared with |
|---|---|---|---|
sales_flash |
Week | Total, channel (online / stores), country, category | Prior week, same week last year |
regional |
Quarter | One country: total and category | Prior quarter, last year, budget |
ecommerce |
Month | Channel mix, online total, online by category | Prior month, last year, budget |
supply_chain |
Month | Total, manufacturer, category | Prior month, last year, budget |
manufacturing |
Month | Contoso, Ltd products: total, category, product line | Prior month, last year |
pricing |
The 8 weeks before the meeting | Total, category, channel | Prior 8 weeks, last year |
aop |
Full year, 2017-2025 | Budget by channel, country, category and month | The previous year’s actuals |
alert |
One event | Vendors, categories and regions affected | None |
industry |
Quarter | Market-level demand by category and channel (no Contoso figures) | Market last year |
The values those breakdowns take:
| Dimension | Values |
|---|---|
| Channel | Online, Stores |
| Country | United States, Canada, United Kingdom, Germany, France, Italy, Netherlands, Australia, plus Online |
| Category | Computers, Cell phones, Home Appliances, TV and Video, Cameras and camcorders, Audio, Games and Toys, Music, Movies and Audio Books |
| Manufacturer | 11, including Contoso, Ltd, the company’s own private label |
Measures
The numbers quoted in the reports are these measures:
| Measure | Meaning |
|---|---|
| Net revenue | Sales after discounts, in USD |
| Change vs prior period | Against the previous week, month or quarter |
| Change vs last year | Against the same period a year earlier |
| Variance to budget | Against that year’s Annual Operating Plan (from 2017) |
| Gross margin % | Gross margin as a share of net revenue |
| Discount % | Discounts as a share of gross revenue |
| Cost ratio | Cost of goods as a share of gross revenue |
| Units | Units sold |
The years in the reports
Reports run from the first weekly flash in May 2016 to the last reviews of 2025, published in January 2026:
reports |>
dplyr::count(year = format(published, "%Y"), name = "reports")
#> # A tibble: 11 × 2
#> year reports
#> <chr> <int>
#> 1 2016 60
#> 2 2017 117
#> 3 2018 119
#> 4 2019 117
#> 5 2020 119
#> 6 2021 118
#> 7 2022 117
#> 8 2023 117
#> 9 2024 118
#> 10 2025 117
#> 11 2026 8Together they tell one continuous story, and every event explains a movement in the data:
| Year | What happens |
|---|---|
| 2016 | A new sales system goes live on 18 May, which is why the data starts then |
| 2017 | A laptop and smartphone boom and the last big wave of new customers; smartphones replace compact cameras |
| 2018 | A price-list reset, four store openings and a one-off anniversary sale |
| 2019 | The best year so far, driven by a new loyalty programme that makes existing customers buy more often |
| 2020 | A rush to redeem loyalty points, then a component shortage that cuts stock across the range |
| 2021 | The shortage at its worst, then recovery |
| 2022 | Marketplaces, a TV and air-conditioning boom and a strong dollar |
| 2023 | A record year |
| 2024 | A difficult year, as customers buy less often and the best ones start to leave |
| 2025 | A marketplace exit, a TV range reset, a store refit programme (“Store Next”), a product safety notice and a PC refresh |
The stocktake, summer shutdown and holiday season recur every year. Each author only knows what their unit would know on the publication date, and people change roles over the years.
Glossary of terms
The terms the reports use, and the phrasings to search for, are defined in a glossary:
fp <- system.file("extdata", "reports", "glossary.yaml", package = "contoso")
glossary <- yaml12::read_yaml(fp)
glossary$win_back$definition
#> [1] "The marketing programme, run every year from 2018, that targets customers who have not bought for a year or more; also the customers who come back. Returners: about 104k (2018), 122k (2019), 47k (2020), 91k (2021), 104k (2022), 82k (2023, including 743 not seen since 2016), 28k (2024). Returners spend more than before they lapsed.\n"Some reports are deliberately wrong. A few authors misread the data or credit the wrong cause, as real business writing does. The package does not say which, so an FP&A assistant has to find them by checking the reports against the data.