跳到正文
DEV Community· StarKnight·· 4 小時前AI 評分53

作者分享將 TabPFN 用於 Cashcast 收款工具的實測心得

What I learned putting TabPFN into a small business tool during Global Hack Week

AI 導讀

作者將 TabPFN 用於 Cashcast 收款工具,在按月回測的 1,075 次預測中取得 0.882 AUC,略低於邏輯回歸的 0.890。TabPFN 的分位數預測可按發票未付款時間估算條件逾期風險,其 80% 付款日期區間覆蓋率為 79.1%,高於梯度提升樹的 57.5%。作者亦指出,CPU 預設不允許使用超過 1,000 筆樣本,因此將資料限於最近 1,000 筆已付款發票。

正文

This is a submission for the MLH x DEV Writing Challenge

What I Built

I built Cashcast, a receivables tool for freelancers, small agencies and bookkeepers. You give it your invoice history as a CSV, and for every unpaid invoice it estimates the chance of late payment, a likely payment date with an 80% range, and the cash you can expect each week for the next eight weeks. The model runs on your own laptop, so a client list never has to leave it.

I built it during Global Hack Week: Hacktoberfest, starting on October 12, 2026. It is also my entry for the DEV Hacktoberfest Week 2 challenge ("Side Hustle"), where I wrote up the product and the full backtest: Cashcast on DEV. This post is about something different: what I learned about the model underneath it, and the parts of the week that did not go to plan.

The short version of the result: TabPFN, a tabular foundation model, predicted late invoices well (AUC 0.882 over 1,075 predictions in a month-by-month backtest), but a plain logistic regression did just as well (0.890). Finding that out, and working out what TabPFN still adds, was the most useful thing I learned all week.

Demo

Live demo: https://starknightt.github.io/cashcast/

Cashcast demo: pick a date, see the riskiest invoices, reveal what happened, then the backtest

The page replays IBM's public accounts receivable sample. Pick a date in 2013, see what Cashcast would have said that morning, then tick "Reveal what happened" to compare with the real payment dates.

Code: https://github.com/StarKnightt/cashcast (MIT)

Partner Technologies

TabPFN (Prior Labs, a Hacktoberfest 2026 partner)

TabPFN is a transformer pre-trained on millions of synthetic tables. You do not train it on your data in the usual sense. fit() stores your rows as context, and predict() runs one forward pass that looks at the training rows and the new rows together. Five things I learned from using it in a real tool:

1. Version choice is a licensing decision. The tabpfn package (9.2.0 at the time) ships loaders for v2, v2.5, v2.6, v3, v3.5 and v3.5-fast. Only v2 downloads directly from Hugging Face without an account. The newer versions ask you to accept a license through a Prior Labs login first, and Prior Labs' recommended path for v3.5 is its API under a free non-commercial license. For a product someone might pay for, I picked v2, whose Prior Labs License 1.1 is Apache 2.0 plus an attribution clause (hence the "Built with PriorLabs-TabPFN" line in the footer). Selecting it explicitly is one line:

from tabpfn import TabPFNClassifier, TabPFNRegressor
from tabpfn.constants import ModelVersion

clf = TabPFNClassifier.create_default_for_version(ModelVersion.V2, device="cpu")
reg = TabPFNRegressor.create_default_for_version(ModelVersion.V2, device="cpu")

2. The CPU limit is real, and it shaped the design. My first test with 1,050 training rows stopped with "Running on CPU with more than 1000 samples is not allowed by default due to slow performance." You can override it with an environment variable, but I kept the limit and trained on the 1,000 most recent paid invoices instead. For a payment-behavior model, recent history is arguably the right history anyway. A classifier plus a regressor on 1,000 rows took 39 to 45 seconds per run on my laptop CPU, which is fine for a daily forecast and too slow for anything interactive.

3. Ask the regressor for quantiles, not a number. predict(X, output_type="quantiles", quantiles=[...]) returns a whole distribution per row. I ask for 49 levels (0.02 to 0.98). That turned out to be the most useful feature in the project, because a distribution can be conditioned. If an invoice is already 20 days old and unpaid, the question changes from "when will it be paid?" to "when will it be paid, given that it was not paid in the first 20 days?" With the quantile curve as a CDF, that is a couple of lines:

s_age = 1 - np.interp(age, curve, LEVELS)      # P(days_to_pay > age)
s_due = 1 - np.interp(terms, curve, LEVELS)    # P(days_to_pay > terms)
p_late_now = s_due / s_age                     # P(late | still unpaid today)

A classifier cannot answer that without a separate model for every age.

4. Missing values can go straight in. A new client has no payment history, so features such as "average days to pay" are empty. TabPFN accepted NaN directly. The scikit-learn baselines needed an imputer in front.

5. Test it against something boring. I compared TabPFN with four baselines on exactly the same rows. Logistic regression tied it on accuracy, and gradient-boosted trees did worse, especially on uncertainty: their 80% ranges caught the real date only 57.5% of the time against TabPFN's 79.1%. Where TabPFN pulled ahead was range shape. For clients with steady payment habits its ranges were narrower than the linear model's fixed band (16.0 against 18.4 days), and for erratic clients they were wider and caught more (73.3% against 69.7%). Modest, but measurable, and it is the reason the product uses TabPFN rather than the simpler model.

GitHub Pages

The demo is a static page with precomputed replay data, deployed by splitting the web/ folder into a gh-pages branch (git subtree split --prefix web -b gh-pages). Keeping the live demo static means it costs nothing to host and cannot leak anyone's data, because the only data on it is a public sample. The same page switches into local mode when it is served by the Python process on localhost.

Hackathon Experience

Global Hack Week: Hacktoberfest ran online from October 9 to October 15, 2026, alongside the weekly DEV Hacktoberfest challenges. I worked on this project solo.

What I will remember most is the moment the backtest came back. I had built the whole project around TabPFN and expected it to beat everything clearly. Instead, a logistic regression matched it. The tempting move was to tune until TabPFN won. I kept the result, published it on the demo page, and went looking for what the model genuinely adds, which led to the per-client range test above. A hackathon week is short enough that you are tempted to ship the first good-looking number; it was also long enough to check it.

Things I would do differently next time:

  • Test the model's limits (row caps, licensing, download size) in the first hour, before designing around it.
  • Treat the weekly cash forecast as its own problem. It is the weakest part of Cashcast: the actual amount fell inside its 80% range in only 67.5% of 80 weeks, because my simulation treats invoices as independent.

來源:DEV Community · dev.to