Skip to content
 
 

Repository files navigation

AnalysisBank

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.


1. Setup

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_KEY

Check 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,4

requirements-optional.txt is needed only for local inference (--provider huggingface), local embeddings, or --retrieval-method bm25.


2. What you need per benchmark

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

3. Generate reports

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 16

Earnings (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 4

SciGen

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_type

The 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.

Key options

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

4. Evaluate

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.

Included model outputs

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_high
  • results_final_datatales/ — DeepSeek-V4-Flash, 460 reports
  • results_final_scigen/ — Qwen3-8B on test-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.


5. Build a bank from your own corpus

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)

Repository layout

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/

Citation

@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}
}

License

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.

About

Implementation of AnalysisBank: An Expert Analysis Pattern Library for Financial Report Generation (EMNLP 2026).

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages