A study by Majd Jamal

I searched and found gold.

— An anomaly detection study

My model scanned 2.4 million public invoices from Örebro kommun for unusual patterns. One of the most unusual cases involved a million-kronor order from a goldsmith. I requested the original documents and found 169 watches, diamond rings and 18‑carat gold chains bought for staff, along with a billing error.

Scroll to read the study

Should tax money pay for gold chains?

I ran anomaly detection on public invoices. Here is what my algorithm found…

Data from

Örebro kommun, a municipality in Sweden, publishes a list of the supplier invoices it has paid.

Updated every month, the data shows who was paid, how much, on which date and by which municipal department. It is free for anyone to use, but it does not say what was bought.

In Sweden, anyone can request documents from a public authority without giving their name or explaining why. I used that right to obtain the three documents behind this story.

2,446,856 invoices,

worth $4 billion,

paid from Jan 2018 to Jul 2026.

Source: Örebro kommun, supplier invoices, open data (24 Excel files, 7,294,483 postings). Amounts excluding VAT; the dollar figure is rounded from 38.9 billion kronor. Data downloaded 9 Sep 2026 from 38.9 billion kronor. Data downloaded 9 Sep 2026.

Scanning fiscal year 2023 invoicesScanning transactions for fiscal year 2023

I trained an autoencoder on the fiscal year of 2023. It learns what a normal invoice looks like, then highlights invoices that deviate from the normal. Across several settings and training runs, one case from 2023 was particularly eye‑catching.

The autoencoder’s map of all 291,292 invoices paid in fiscal year 2023. Similar invoices lie close to each other; darker dots deviate more from the normal. Gold: the credit note from the gold store, ranked 5th.

Following the outlier

I traced the credit note through the payment records and found three documents for the same order.

The three documents of the order, by fiscal year of payment. Amounts in kronor, excluding VAT.
Fiscal yearDocumentNumberDatedPaidAmount (SEK)
2023Invoice 24849251Ranked 37,652nd of 291,292Ranked 37,652nd of 291,2922484925115 Nov 202315 Dec 2023882,960
2023Credit note 24862111Flagged by the scan · ranked 5th of 291,2922486211130 Nov 202315 Dec 2023−882,960
2024New invoice 24867828248678281 Dec 20232 Jan 20241,014,000≈ 1 million131,040 kr more than the first bill

Three documents, one order. The credit note, in gold, is the document the scan flagged: it cancels the first invoice in full, and the new invoice takes its place. The scan ranked the first invoice 37,652nd: it flagged the correction, not the bill. The scan ranked the first invoice 37,652nd: it flagged the correction, not the bill. In the open data the store has 13 invoices from 2018 to 2025, and this is its only credit note.

Source: Örebro kommun, supplier invoices, open data (payment dates, amounts excl. VAT excl. VAT); dates of issue from the three documents, received from Örebro kommun on 30 Sep 2026. Fiscal year = calendar year of payment.

So I emailed them about it

I asked for the three documents, why the first invoice was credited, and whether the gifts were for 2023 or 2024. The registrar sent all three and answered both.

Majd Jamalto Örebro kommun

Copies of three supplier invoices

Hi!

My name is Majd Jamal and I’m analysing the municipality’s open data on supplier invoices. Thank you for publishing it!

I would like copies of three invoices from Jarl Sandin Guld och Silver AB (org. no. 556285-0700), account 71110 Staff recognition and gifts. Invoice numbers according to the open data:

  • 24849251, paid 2023-12-15,
    882,960 kr
  • 24862111, paid 2023-12-15,
    −882,960 kr (credit note)
  • 24867828, paid 2024-01-02,
    1,014,000 kr
  • 24849251, paid 2023-12-15, 882,960 kr
  • 24862111, paid 2023-12-15, −882,960 kr (credit note)
  • 24867828, paid 2024-01-02, 1,014,000 kr

Personal data may of course be masked.

If possible, I would also like to know why the first invoice was credited, and whether the gifts were for 2023 or 2024.

Thanks in advance!

Kind regards
Majd Jamal

Örebro kommun · Registratorto Majd Jamal

Re: Copies of three supplier invoices

Hi!

Attached are the requested documents. The first invoice concerned the 24-year gifts for 2023, and it was credited because the invoice had the wrong amount on two lines.

Kind regards,
Registrar
Information Management and City Archives, Örebro kommun

  • 24849251.pdf
  • 24862111.pdf (the credit note the model flagged)
  • 24867828.pdf

Source: emails between Majd Jamal and Örebro kommun, 27 and 30 September 2026, translated from Swedish. Email addresses are left out.

Sent

Copies of three supplier invoices

Majd Jamalto Örebro kommun

Hi!

My name is Majd Jamal and I’m analysing the municipality’s open data on supplier invoices. Thank you for publishing it!

I would like copies of three invoices from Jarl Sandin Guld och Silver AB (org. no. 556285-0700), account 71110 Staff recognition and gifts. Invoice numbers according to the open data:

  • 24849251, paid 2023-12-15,
    882,960 kr
  • 24862111, paid 2023-12-15,
    −882,960 kr (credit note)
  • 24867828, paid 2024-01-02,
    1,014,000 kr
  • 24849251, paid 2023-12-15, 882,960 kr
  • 24862111, paid 2023-12-15, −882,960 kr (credit note)
  • 24867828, paid 2024-01-02, 1,014,000 kr

Personal data may of course be masked.

If possible, I would also like to know why the first invoice was credited, and whether the gifts were for 2023 or 2024.

Thanks in advance!

Kind regards
Majd Jamal

English translation of the Swedish original. Email addresses are left out.

Kopior av tre leverantörsfakturor

Majd Jamaltill Örebro kommun

Hej!

Jag heter Majd Jamal och analyserar kommunens öppna data över leverantörsfakturor. Tack för att ni publicerar den!

Jag skulle vilja få kopior av tre fakturor från Jarl Sandin Guld och Silver AB (org.nr 556285-0700), konto 71110 Personaluppvakt o gåvor. Fakturanummer enligt öppna data:

  • 24849251, betald 2023-12-15,
    882 960 kr
  • 24862111, betald 2023-12-15,
    –882 960 kr (kreditfaktura)
  • 24867828, betald 2024-01-02,
    1 014 000 kr
  • 24849251, betald 2023-12-15, 882 960 kr
  • 24862111, betald 2023-12-15, –882 960 kr (kreditfaktura)
  • 24867828, betald 2024-01-02, 1 014 000 kr

Personuppgifter kan gärna maskas.

Om det går skulle jag också vilja veta varför den första fakturan krediterades, och om gåvorna avsåg 2023 eller 2024.

Tack på förhand!

Med vänliga hälsningar
Majd Jamal

The Swedish original, as sent. Email addresses are left out.

Reply

Re: Copies of three supplier invoices

Örebro kommun · Registratorto Majd Jamal

Hi!

Attached are the requested documents. The first invoice concerned the 24-year gifts for 2023, and it was credited because the invoice had the wrong amount on two lines.

Kind regards,
Registrar
Information Management and City Archives, Örebro kommun

  • 24849251.pdf
  • 24862111.pdf (the credit note the model flagged)
  • 24867828.pdf

English translation of the Swedish original. Email addresses are left out.

Sv: Kopior av tre leverantörsfakturor

Örebro kommun · Registratortill Majd Jamal

Hej!

Bifogar efterfrågade handlingar. Den första fakturan avsåg 24 årsgåvor 2023 och den krediterades för att fakturan hade fel belopp på två poster.

Med vänliga hälsningar,
Registrator
Informationshantering och Stadsarkiv, Kommunledningsförvaltningen

  • 24849251.pdf
  • 24862111.pdf
  • 24867828.pdf

The Swedish original, as received. Email addresses are left out.

What was inside the invoice?

The documents show what was bought, how the billing changed and when the replacement invoice was paid.

What

169 gifts for employees with long service

Everything on the new bill: 1,014,000 kr excl. VAT. One dot is one gift; the 42 gold chains are in gold.

  • 42 gold chains,
    18 carat
  • 38 watches
  • 34 rings
  • 29 designer
    jewellery sets
  • 19 diamond pendants19 diamond
    pendants
  • 6 pairs of
    diamond earrings
    6 pairs of
    diamond
    earrings
  • 1 diamond bracelet1 diamond
    bracelet

When

Billed in 2023, paid in 2024

The original invoice and its full credit share the same payment date, 15 December 2023, and net to zero. The replacement invoice was paid on 2 January 2024.

Did this correction move the expense out of 2023?

Why the amount changed

The same order, a higher bill

The original invoice listed the 42 gold chains at 2,880 kr each. The replacement listed them at 6,000 kr each, adding 131,040 kr excluding VAT. The municipality said the original amounts were wrong.

Why did the same chain on the receipt increase in price?

Source: Örebro kommun, supplier invoices, open data (payment dates, amounts excl. VAT); dates of issue, the gifts and their prices from the three documents and the registrar’s reply, received on 30 Sep 2026.

Disclaimer

This study demonstrates anomaly detection, not an accusation against Örebro kommun. The model helped me choose a record to investigate. The documents explained a billing error that had already been corrected. A flag is a reason to look closer, not proof of wrongdoing.

Method and limitations

From the open data to the map, in six small steps.

The method as a flow: collect the open data, combine and clean it, count the invoices per year, turn each invoice into 8 numbers, squeeze them through an autoencoder to 2 numbers and back, draw the 2 numbers as the map and rank the invoices by highest reconstruction error.

Collect data

orebro.se · 24 files
2,446,856 invoices
7,294,483 lines

Combine and clean

two column layouts
→ one clean table
nothing dropped, credits kept

Invoices per year

= 1,000 invoices

By the year each invoice was first paid. 2026 runs January to July.

Feature engineering

8 numbers per invoicehow rare · how big ·
which sign · how many lines

Autoencoder

8 → 2 → 8 · no labels

8 in

rebuilt

latent space

Visualize

the 2 latent numbers of each 2023 invoice, drawn as the map

The map of the 291,292 invoices paid in 2023, the credit note marked by a gold dot

Rank anomalies

by highest reconstruction error
top 5 of 291,292 · 2023

  1. 1Premises rent290.85
  2. 2Flat rent70.05
  3. 3Agency nurses32.43
  4. 4Vehicle inspection21.75
  5. 5Credit note, jeweller18.73
The method as a flow: collect the open data, combine and clean it, count the invoices per year, turn each invoice into 8 numbers, squeeze them through an autoencoder to 2 numbers and back, draw the 2 numbers as the map and rank the invoices by highest reconstruction error.

Collect data

orebro.se · 24 files
2,446,856 invoices · 7,294,483 lines

Combine and clean

two layouts → one clean table
nothing dropped, credits kept

Invoices per year

= 1,000 invoices

By the year first paid. 2026: January to July.

Feature engineering

8 numbersper invoice

Autoencoder

8 → 2 → 8 · no labels

8 in

rebuilt

latent space

Visualize

each 2023 invoice’s
2 numbers as the map

The map of the 291,292 invoices paid in 2023, the credit note marked by a gold dot

Rank anomalies

by highest
reconstruction error
top 5 · 2023

  1. 1Premises rent
  2. 2Flat rent
  3. 3Agency nurses
  4. 4Vehicle inspection
  5. 5Credit note

Evaluation of the method.

The model uses reconstruction error to rank unusual invoices, but this does not establish whether they contain mistakes. Without labelled outcomes, I cannot report F1 or ROC AUC. Synthetic tests could check specific behaviours, though optimising for invented cases could favour patterns I already expect.

I reviewed the top 20 invoices from each year and followed up the jeweller’s credit note. The documents explained a billing error that had already been corrected. This shows how a flag can guide investigation. Overall detection performance remains unmeasured.

Receipts.

The open data does not show what was bought. I have to request the original invoices, which slows down investigations. Working with the municipality could make reviews faster and help improve the model.

Each invoice becomes 8 numbers, like P(account | committee). The autoencoder squeezes them into 2, a dot on the map, and rebuilds all 8; the worse the rebuild, the darker the dot.

  1. 7,294,483postings in the ledger
  2. cleanone row per invoice
  3. 8 featuresnumbers per invoice
  4. X 291,292 × 8every invoice of 2023
  5. 8 → 2 → 8the autoencoder; the 2 is the map

P(account | committee): how likely is it that this committee pays for this kind of thing?

“This system is built on unsupervised learning, without any labels, it’s difficult to measure exact performance. And with injected anomalies, it would optimize on something we know we are looking for.”

On evaluation

Data: Örebro kommun’s supplier invoices, January 2018 – July 2026: 7,294,483 postings, 2,446,856 invoices, about $4 billion. Invoice details (CSV) · The emails (TXT)

Appendix

Every formula and setting behind the features, the X and the autoencoder.

Feature engineering

8 per invoice

Each posting line of 2023 is scored against the lines like it. An invoice keeps its lines’ most extreme scores and its number of lines: eight numbers per invoice.

Which account
  • 1 Account given committee

    p_konto_namnd_max

    High when at least one line on the invoice is booked to an account this committee rarely uses.

    x_{v,1} = \max_{i \in v} \left[ -\ln \hat{P}(k_i \mid c_i, e_i) \right]
    \hat{P}(k \mid c, e) = \frac{n(e, c, k)}{n(e, c)}
  • 2 Account given supplier

    p_konto_leverantor_max

    High when at least one line is booked to an account this supplier is rarely booked against.

    x_{v,2} = \max_{i \in v} \left[ -\ln \hat{P}(k_i \mid s_i, e_i) \right]
    \hat{P}(k \mid s, e) = \frac{n(e, s, k)}{n(e, s)}
How large
  • 3 Amount given account (max)

    p_amount_account_max

    High when at least one line is far larger (sign ignored) than is typical for its account: r is a robust z‑score, in spreads from the median.

    x_{v,3} = \max_{i \in v} r_i^{(e,k)}
    r_i^{(e,k)} = \frac{a_i - m_{(e_i,k_i)}}{\hat{\sigma}_{(e_i,k_i)}}
  • 4 Amount given account (min)

    p_amount_account_min

    Strongly negative when at least one line is far smaller (sign ignored) than is typical for its account; high only when every line is above typical.

    x_{v,4} = \min_{i \in v} r_i^{(e,k)}
  • 5 Amount given supplier (max)

    p_amount_reskontra_max

    High when at least one line is far larger (sign ignored) than this supplier’s typical line.

    x_{v,5} = \max_{i \in v} r_i^{(s)}
    r_i^{(s)} = \frac{a_i - m_{s_i}}{\hat{\sigma}_{s_i}}
  • 6 Amount given supplier (min)

    p_amount_reskontra_min

    Strongly negative when at least one line is far smaller (sign ignored) than this supplier’s typical line.

    x_{v,6} = \min_{i \in v} r_i^{(s)}
Which sign
  • 7 Sign given account

    s_sign_account_max

    High when a line has an unusual sign for its account, e.g. a credit on an account that is almost always debited (b = 0 for credits and zero amounts).

    x_{v,7} = \max_{i \in v} \left[ -\ln \hat{P}(b_i \mid k_i, e_i) \right]
    b_i = \mathbf{1}\{y_i > 0\}
How many lines
  • 8 Number of lines

    n_lines

    High when the invoice is split over many posting lines (in 2023 the median is 1 and the maximum 266).

    x_{v,8} = |v| = \#\{\, i : i \in v \,\}
Notation and shared formulas
i
a posting line
v
an invoice, v = 1, \dots, N, and the set of its lines, i \in v
e_i
era: the account plan, identified by code length (5 or 4 digits)
c_i
committee
s_i
supplier (organisation number)
k_i
account code
y_i
amount, kronor excl. VAT
  • Share and rarity

    n(\cdot) counts the 2023 posting lines with those values: g a group (a committee, a supplier, an account, each within its era), u a value inside it. The rarity is 0 when every line in the group has the value, and grows the rarer it is.

    \hat{P}(u \mid g) = \frac{n(g,\, u)}{n(g)}
    -\ln \hat{P}(u_i \mid g_i)
  • Log amount

    Size on a log scale, sign ignored: the sign is feature 7.

    a_i = \ln\!\left(1 + |y_i|\right)
  • Typical amount

    The median log amount in the line’s group g: its account within its era, (e_i, k_i), for features 3 and 4; its supplier, s_i, for 5 and 6.

    m_g = \operatorname*{median}_{j \in g} a_j
  • Robust spread

    The median absolute deviation M_g, scaled (×1.4826) to match a normal \sigma; when M_g is 0, the mean absolute deviation A_g, scaled (×1.2533) to match a normal \sigma too; never below 0.05.

    M_g = \operatorname*{median}_{j \in g} \left| a_j - m_g \right|
    A_g = \operatorname*{mean}_{j \in g} \left| a_j - m_g \right|
    \tilde{\sigma}_g = \begin{cases} 1.4826\, M_g & M_g > 0 \\ 1.2533\, A_g & M_g = 0 \end{cases}
    \hat{\sigma}_g = \max\!\left(0.05,\; \tilde{\sigma}_g\right)

Make an X

StandardScaler

One row per invoice, one column per feature. Missing or infinite values are set to 0 first (2023 has none). Each column is then centred on its mean and divided by its standard deviation, so all eight count on the same scale.

X = \begin{bmatrix} x_{1,1} & \cdots & x_{1,8} \\ \vdots & \ddots & \vdots \\ x_{N,1} & \cdots & x_{N,8} \end{bmatrix} \in \mathbb{R}^{N \times 8}
N = 291\,292
\tilde{x}_{v,j} = \frac{x_{v,j} - \mu_j}{\sigma_j}
\mu_j = \frac{1}{N}\sum_{v} x_{v,j}
\sigma_j = \sqrt{\frac{1}{N}\sum_{v} \left(x_{v,j} - \mu_j\right)^2}

Autoencoder

PyTorch

A small neural network squeezes each invoice’s eight numbers into two, then rebuilds all eight from those two. Trained on every invoice of 2023, it learns what ordinary invoices look like: an invoice it rebuilds badly is unusual.

The drawing: the invoice’s 8 standardised numbers enter an encoder of 128, 64 and 16 units and come out as z, 2 numbers; a decoder of 16, 64 and 128 units rebuilds 8 numbers from z. One rebuilt number misses its original, marked in gold.
x̂ — reconstruction, used for the anomaly score
\varepsilon(v) = \frac{1}{8} \sum_{j=1}^{8} \left( \hat{x}_{v,j} - \tilde{x}_{v,j} \right)^2

How badly invoice v is rebuilt, averaged over its eight numbers. The gold store’s credit note ranked 5th of 291,292.

z — latent space, used for visualisation

Each invoice’s two numbers place it on the map. Similar invoices land close together; the score is the shade, not the position.

  • Encoder

    ReLU after each hidden layer; the two-number layer z is linear.

    z_v = E(\tilde{\mathbf{x}}_v) \in \mathbb{R}^{2}
    E:\; 8 \to 128 \to 64 \to 16 \to 2
    h_0 = \tilde{\mathbf{x}}
    h_\ell = \mathrm{ReLU}(W_\ell\, h_{\ell-1} + b_\ell),\; \ell \le 3
    E(\tilde{\mathbf{x}}) = W_4\, h_3 + b_4
  • Decoder

    The same form in reverse, with its own weights; the eight outputs are linear.

    \hat{\mathbf{x}}_v = D(z_v) \in \mathbb{R}^{8}
    D:\; 2 \to 16 \to 64 \to 128 \to 8
  • Training

    The mean of \varepsilon over each batch B, minimised for 20 epochs. The published run trained with AE.train_stream, which shuffles within windows of four batches read in file order; AE.train (used by examples/run_analysis.py) shuffles all invoices each epoch, so its map differs in detail.

    \mathcal{L}(B) = \frac{1}{|B|} \sum_{v \in B} \varepsilon(v)
  • optimiser Adam
  • learning rate 10⁻³
  • epochs 20
  • batch 4,096
  • seed 0

Access and review the code on GitHub