Implementation of AnalysisBank: An Expert Analysis Pattern Library for Financial Report Generation (EMNLP 2026).
AnalysisBank distils expert reports into a library of Analyses — each pairing a data signal, an analytical move, and the expert span it came from. At inference time input signals are matched to library entries and the retrieved moves are applied to compose a report.
Requires Python 3.10+ and an LLM API key (OpenRouter by default; Azure also supported).
pip install -r requirements.txt
cp .env.example .env # add OPENROUTER_API_KEYCheck it works — this needs no data and no bank, it runs on a built-in sample library and prints a generated report:
cd analysisbank_pipeline && python demo_generation.py --stages 1,2,3,4requirements-optional.txt is needed only for local inference
(--provider huggingface), local embeddings, or --retrieval-method bm25.
| Generation inputs | Distillation corpus | Analysis bank | |
|---|---|---|---|
| DataTales | original DataTales release | same | download — 180 MB, 1,422 Analyses |
| Earnings | original Earnings2Insights release | Seeking Alpha — not redistributable | not released |
| SciGen | UKPLab/SciGen | same | download — 291 MB, 2,427 Analyses |
Earnings is partially reproducible. The generation inputs — earnings-call transcripts — come from the original Earnings2Insights release. The bank, however, is distilled from Seeking Alpha analyst articles, which we cannot redistribute; neither the bank nor that corpus is included here. The same articles are the expert reference for the head-to-head WIN evaluation, so that metric is likewise not reproducible for earnings. The earnings code path ships in full and runs against a bank distilled from any corpus you are licensed to use (§5).
Place banks under analysisbank/ and corpora under data/:
analysisbank/analyses_datatales.db
analysisbank/analyses_scigen.db
data/datatales/{datatales_train,datatales_test,datatales_test_reduced}.csv
data/datatales/<market>/ # DataTales OHLCV inputs
data/earnings2insights/with_extension/ # Earnings transcripts (generation only)
data/scigen/dataset/ # SciGen splits
Add --limit N to try a few inputs first, and --workers N to parallelise.
DataTales
python analysisbank_pipeline/run_generation_datatales.py \
--analyses analysisbank/analyses_datatales.db \
--data-dir data/datatales \
--output results/datatales \
--model qwen/qwen3-8b --k 10 --stage2-mode per_type --workers 16Earnings (transcripts from Earnings2Insights; needs a bank you distilled yourself)
python analysisbank_pipeline/run_generation.py \
--analyses analysisbank/analyses_earnings.db \
--input-dir data/earnings2insights/with_extension \
--output results/earnings \
--model qwen/qwen3-8b --k 10 \
--stage1-mode hierarchical --stage2-mode per_type --stage4-mode signals --workers 4SciGen
python analysisbank_pipeline/run_generation_scigen.py \
--analyses analysisbank/analyses_scigen.db \
--split test-CL \
--output results/scigen \
--model qwen/qwen3-8b --k 10 --stage2-mode per_typeThe flags above are the configuration used for the paper. Output lands in an
auto-suffixed subdirectory holding one report.json per input plus a
run_summary.json.
| Flag | Default | Notes |
|---|---|---|
--retrieval-method |
llm |
Or bm25, sentence_transformer |
--k |
5 | Analyses retrieved per input; the paper uses 10 |
--stage1-mode |
hierarchical |
Earnings only — single uses one extraction call, hierarchical one per signal type |
--stage2-mode |
per_type |
cosine retrieves globally, per_type per signal type |
--stage3-mode |
pattern |
pattern_with_refs / refs_only add expert spans to the prompt |
--stage4-mode |
signals |
Earnings only — transcript composes from the raw transcript instead of the signals |
--ablation |
– | Ablation variants reported in the paper |
python evaluation_llm/eval/run_eval.py --result-dirs results/datatales/<run-subdir>Reports reasoning depth, insight rate and analysis rate.
| Flag | Purpose |
|---|---|
--result-dirs A B |
Score several runs together |
--no-win / --no-unified |
Run only one half of the evaluation |
--themes-dir |
Enables theme coverage + factuality scoring |
--input-dir |
Enables direct factuality against source inputs |
--limit N |
Score only the first N inputs |
Set OPENROUTER_EVAL_MODEL to choose the judge (the paper uses
google/gemini-3-flash-preview). Output goes to evaluation_llm/eval_sessions/
plus summary and detail CSVs.
One run per benchmark ships in this repo as a worked example, and can be re-scored directly:
python evaluation_llm/eval/run_eval.py \
--result-dirs results_final_datatales/M14_deepseek-deepseek-v4-flash_stage2_per_type_top10_reasoning_highresults_final_datatales/— DeepSeek-V4-Flash, 460 reportsresults_final_scigen/— Qwen3-8B ontest-CL, all 492 entries
Each report.json holds the generated report, the extracted signals, the
retrieved analysis_slate, the per-Analysis analysis_results, and
validation_results. The remaining runs behind the paper's tables — the other
DataTales and SciGen models, and all earnings outputs — are available on
request.
python analysisbank_pipeline/run_extraction.py \
--dataset datatales \
--output analysisbank/analyses_datatales.db \
--limit 100--dataset accepts earnings, datatales or scigen and sets the default
input path; override with --csv (or --scigen-split / --scigen-path). This
is the four-pass extraction of Appendix A: Induce → Generalize → Deduplicate →
Quality-filter. --no-embed skips embedding; --branch ap runs the alternative
design ablation.
To distil from your own expert reports, pass a CSV with these columns:
sector, symbol, company, title, author, date, url, cleaned_text
cleaned_text is the report body; the rest becomes the Analysis provenance.
Each bank is a SQLite database:
analyses(id, data_signal, analytical_move, reference_texts, provenance, created_at)
embeddings(analysis_id, model, vector)
reference_embeddings(analysis_id, model, vector)
analysisbank_pipeline/
run_extraction.py Distil a corpus into a bank
run_generation_datatales.py DataTales pipeline
run_generation.py Earnings pipeline (paper App. C)
run_generation_scigen.py SciGen pipeline
extraction_pipeline.py Four-pass extraction (paper App. A)
generation_pipeline.py Stages 1–4
scigen_pipeline.py, scigen_data.py
analysisbank_db.py SQLite persistence
demo_extraction.py, demo_generation.py
llm_client.py, schemas.py, utils.py
evaluation_llm/eval/
run_eval.py Entry point
eval_unified.py Insight / analysis rate, depth, factuality
eval_win.py Head-to-head win rate vs expert reference
extract_themes.py Theme extraction (coverage anchor)
scigen_loader.py SciGen splits + table rendering
eval_common.py, prompts/
@inproceedings{yang2026analysisbank,
title = {{AnalysisBank}: An Expert Analysis Pattern Library for Financial Report Generation},
author = {Yang, Yajing and Ma, Yunshan and Koa, Kelvin J. L. and Kan, Min-Yen},
booktitle = {Proceedings of the 2026 Conference on Empirical Methods in Natural Language Processing},
year = {2026}
}Code is released under the MIT License. No benchmark corpus is redistributed here — obtain DataTales, SciGen and Earnings2Insights from their original releases and use them under their own terms. The Seeking Alpha analyst articles used to distil the earnings bank, and the bank itself, are withheld for copyright reasons.