Uses SEED_N=2000 typed documents (~4000 rows with OCR variants). Regenerates when the largest existing corpus is smaller than SEED_N.
If OPENROUTER_API_KEY is set, Stage A/B may call OpenRouter. With the setup cell above, this notebook prefers free OpenRouter models (openrouter/free and :free fallbacks) so generation still works when paid credits are unavailable; otherwise it falls back to templates.
# Target typed-document count (noisy OCR variants are generated 1:1 → ~2× rows).SEED_N =48SEED =42# Regenerates when the largest existing corpus is smaller than SEED_N.corpus_paths = ensure_seed_corpus(n=SEED_N, seed=SEED, log_wandb=False)display(pd.Series(corpus_paths, name="path").to_frame())print(f"requested SEED_N={SEED_N:,} | corpus n={corpus_paths.get('n', '?')} | generated={corpus_paths.get('generated')}")
path
documents
/Users/morningstar/Desktop/Cold_Storage/smol-d...
noisy
/Users/morningstar/Desktop/Cold_Storage/smol-d...
generated
false
n
240
requested SEED_N=48 | corpus n=240 | generated=false
record_id: CLM-2026-763588::application_personal::0
document_type: application_personal
--- TYPED ---
PERSONAL LINES APPLICATION
Claim Number: CLM-2026-763588
ACORD Form: 90
APPLICANT
Named Insured: Jordan Lewis
Policy Number: MN-455784-Z
State: MN
Coverage Type: commercial_general_liability
Effective Date: 2025-07-14
VEHICLE / PROPERTY
Policy Number: MN-455784-Z
Named Insured: Jordan Lewis
DRIVERS / OCCUPANTS
Policy Number: MN-455784-Z
Named Insured: Jordan Lewis
PRIOR INSURANCE
Policy Number: MN-455784-Z
Named Insured: Jordan Lewis
Applicant Signature
Prepared for claim CLM-2026-763588
--- HANDWRITING / OCR ---
P~RSO~A¤ LINES APPLICATION
Claim Number: CLM-2026-763588
ACORD Form: 90
APPLICANT
Named Insued: Jordan Lewis
Policy Number: MN-455784-Z
5tte: MN
Coverage Type: commercial_gener~l_liability
Effective Date: 2025-07-14
VEHICLE / PROPERTY
olicy Number: MN-45784-Z
Named Insured: Jordan LewiV
DRIVMRS / OCCUPANTS
Policy Numbr: MN-455784-Z
Named Insred: Jordan Lewis
PRI0R INSURANCE
Policy Number: MN-455784-Z
Named Insured: Jordan Lewis
Applicant Signature
Prepared for claim CLM-2026-763588
4. Train Random Forest — document type classification
Features: TF-IDF unigrams + bigrams over the document text (typed and OCR surfaces together).
train_df = frame[frame["split"] =="train"].reset_index(drop=True)val_df = frame[frame["split"] =="val"].reset_index(drop=True)test_df = frame[frame["split"] =="test"].reset_index(drop=True)# Fit on train + val (classical baseline); hold out test for final predictionfit_df = pd.concat([train_df, val_df], ignore_index=True)doc_clf = build_document_type_pipeline( n_estimators=300, max_features=20000, ngram_range=(1, 2), random_state=42,)doc_clf.fit(fit_df["text"], fit_df["document_type"])print(f"fitted on {len(fit_df):,} rows | classes: {list(doc_clf.classes_)}")print(f"TF-IDF vocabulary size: {len(doc_clf.named_steps['tfidf'].vocabulary_):,}")
doc_metrics = evaluate_classifier( doc_clf, as_str_list(test_df["text"]), as_str_list(test_df["document_type"]), labels=list(doc_clf.classes_),)print(f"Test accuracy : {doc_metrics['accuracy']:.4f}")print(f"Macro F1 : {doc_metrics['macro_f1']:.4f}")print(f"Weighted F1 : {doc_metrics['weighted_f1']:.4f}")print(f"N test rows : {doc_metrics['n']}")report_df = pd.DataFrame(doc_metrics["classification_report"]).Tdisplay(report_df.round(3))
Test accuracy : 1.0000
Macro F1 : 1.0000
Weighted F1 : 1.0000
N test rows : 72
precision
recall
f1-score
support
application_commercial
1.0
1.0
1.0
8.0
application_personal
1.0
1.0
1.0
8.0
certificate_evidence
1.0
1.0
1.0
8.0
claims_correspondence
1.0
1.0
1.0
10.0
loss_notice
1.0
1.0
1.0
12.0
policy_change_endorsement
1.0
1.0
1.0
6.0
repair_estimate
1.0
1.0
1.0
10.0
supporting_evidence
1.0
1.0
1.0
10.0
accuracy
1.0
1.0
1.0
1.0
macro avg
1.0
1.0
1.0
72.0
weighted avg
1.0
1.0
1.0
72.0
fig, ax = plt.subplots(figsize=(9, 7))ConfusionMatrixDisplay( confusion_matrix=np.asarray(doc_metrics["confusion_matrix"]), display_labels=doc_metrics["labels"],).plot(ax=ax, colorbar=True, cmap="Blues")# sklearn stubs type xticks_rotation as str only; set degrees on the axes instead.ax.tick_params(axis="x", labelrotation=45)ax.set_title("Document-type Random Forest — test confusion matrix")plt.tight_layout()plt.show()
Accuracy by surface (typed vs handwriting/OCR)
surface_rows = []for surface, group in test_df.groupby("surface"): m = evaluate_classifier( doc_clf, as_str_list(group["text"]), as_str_list(group["document_type"]), labels=list(doc_clf.classes_), ) surface_rows.append( {"surface": surface,"n": m["n"],"accuracy": m["accuracy"],"macro_f1": m["macro_f1"], } )surface_metrics = pd.DataFrame(surface_rows).sort_values("surface")display(surface_metrics.round(4))fig, ax = plt.subplots(figsize=(6, 3.5))sns.barplot(data=surface_metrics, x="surface", y="accuracy", hue="surface", ax=ax, legend=False)ax.set_ylim(0, 1.05)ax.set_title("Document-type accuracy by text surface")ax.set_ylabel("accuracy")plt.tight_layout()plt.show()
surface
n
accuracy
macro_f1
0
handwriting_ocr
36
1.0
1.0
1
typed
36
1.0
1.0
6. Predictions
Generate class predictions + confidence for every test document.
fi = top_tfidf_feature_importances(doc_clf, top_k=25)display(fi)fig, ax = plt.subplots(figsize=(8, 7))sns.barplot(data=fi, y="feature", x="importance", color="#2a6f97", ax=ax)ax.set_title("Top TF-IDF features by Random Forest importance")ax.set_xlabel("mean decrease in impurity")plt.tight_layout()plt.show()
feature
importance
0
is
0.015496
1
status
0.012904
2
named
0.012874
3
reserve
0.012052
4
policy
0.011587
5
change
0.011478
6
labor
0.011367
7
amount
0.011040
8
loss
0.010895
9
report
0.010878
10
insured
0.010435
11
of
0.009724
12
made
0.009580
13
date of
0.009522
14
name
0.009320
15
total
0.008838
16
00 reserve
0.008746
17
damage
0.008676
18
claim status
0.008485
19
reported
0.008446
20
review
0.008443
21
date
0.008354
22
estimate
0.008314
23
as
0.008310
24
coverage
0.008213
8. Secondary model — surface style (typed vs handwriting/OCR)
A second Random Forest predicts whether the text looks typed/clean or OCR/handwriting-noisy.
multilayer artifacts -> /Users/morningstar/Desktop/Cold_Storage/smol-doc-analyzer/models/random_forest_classifier
WandB: enabled=True mode=online project=smol-doc-analyzer
sweep_results.json: ok
layer_diagnostics.json: ok
eval_metrics.json: ok
train_meta.json: ok
10. Quick inference helper
Predict document type for an arbitrary snippet (typed or OCR-like text).
def predict_document(text: str, model=doc_clf) ->dict: label = model.predict([text])[0] proba = model.predict_proba([text])[0] conf =float(proba.max()) ranking =sorted(zip(model.classes_, proba), key=lambda x: x[1], reverse=True, )return {"predicted_label": label,"confidence": conf,"top3": [(c, float(p)) for c, p in ranking[:3]], }demo ="""PROPERTY LOSS NOTICEClaim Number: CLM-2026-111222Date of Loss: 2025-11-03Loss Type: waterDescription of Loss: Pipe burst in upstairs bathroom; ceiling damage reported.Reported By: Jordan Lee"""print(predict_document(demo))