Skip to main content

WhyTrend

CI License Python

Open Source Framework for Explainable Time Series Analysis

WhyTrend detects anomalies, change points, and trend shifts in time series — then automatically explains why they happened using external context and LLMs.

If this project is useful to you, consider giving it a star on GitHub — it helps others discover the project. Forks and pull requests are welcome.

Features

  • Fluent Pipeline API for source → detection → collection → ranking → explanation
  • Pluggable sources, detectors, collectors, rankers, and LLM providers
  • Plugin registry via Python entry points (whytrend.collectors, …)
  • Structured Report output (JSON, Markdown, HTML, and PDF)
  • Async collectors and LLM calls
  • Works offline with MockLLMProvider and local models via OllamaExplainer
  • whytrend command-line interface over the same plugin registry as Pipeline

Installation

git clone https://github.com/AlexProvatorov/WhyTrend.git
cd WhyTrend
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

Optional extras

pip install -e ".[openai]"      # OpenAI API
pip install -e ".[anthropic]"   # Anthropic Claude
pip install -e ".[deepseek]"    # DeepSeek (OpenAI-compatible client)
pip install -e ".[azure]"       # Azure OpenAI
pip install -e ".[openrouter]"  # OpenRouter
pip install -e ".[gemini]"      # Gemini marker (uses core httpx; no extra package)
pip install -e ".[trends]"      # Google Trends
pip install -e ".[prophet]"       # Prophet detector
pip install -e ".[ruptures]"      # change-point detector
pip install -e ".[river]"         # online / streaming detector
pip install -e ".[ranking]"       # Embedding ranker
pip install -e ".[pdf]"           # PDF reports (xhtml2pdf)
pip install -e ".[reports]"       # report extras (currently PDF)
pip install -e ".[all]"           # everything

Quickstart CLI

After install, analysis is one command. The mock explainer does not call the network when collectors are omitted:

whytrend analyze \
  --csv tests/fixtures/python_interest.csv \
  --keyword Python \
  --detector zscore \
  --set detector.threshold=1.0 \
  --explainer mock \
  --out report.md

Omit --out to print the report. --format is markdown, json, html, or pdf. PDF writes bytes and requires --out (pip install 'whytrend[pdf]'). --parquet reads a Parquet file instead of CSV. --trends KEYWORD loads Google Trends (pip install 'whytrend[trends]').

whytrend --help and whytrend analyze --help list the plugins installed in this environment. A plugin that needs constructor arguments stays in that list and is created when every missing value is passed with --set. llm takes --provider instead. Collectors call external sources, so the offline path is the command above with no --collectors. hn is an alias of hacker_news:

whytrend analyze \
  --csv tests/fixtures/python_interest.csv \
  --keyword Python \
  --collectors hn,wikipedia \
  --ranker bm25 \
  --format json \
  --out report.json

Repeat --set as detector.<param>, explainer.<param>, ranker.<param>, collector.<name>.<param>, source.<param>, or provider.<param>. none clears an optional value (ranker.top_k=none). Lists are comma-separated (collector.github_releases.repos=owner/name) and dicts are JSON (detector.prophet_kwargs={"seasonality_mode":"multiplicative"}).

Cloud explainers read the same environment variables as the Python API below (OPENAI_API_KEY, ANTHROPIC_API_KEY, and the rest). Ollama needs a local server instead of a key.

RSS, an LLM provider, and an HTML report:

whytrend analyze \
  --csv tests/fixtures/python_interest.csv \
  --keyword Python \
  --collectors rss \
  --set collector.rss.feed_urls=https://blog.python.org/feeds/posts/default \
  --explainer llm \
  --provider mock \
  --format html \
  --out report.html

Azure OpenAI needs its deployment name: --explainer azure_openai --set explainer.deployment=my-deploy (AZURE_OPENAI_API_KEY and AZURE_OPENAI_ENDPOINT). In-memory dataframes and client objects stay in the Python API.

Quickstart (CSV, no API keys)

from whytrend import (
    BM25Ranker,
    CSVSource,
    LLMExplainer,
    MockLLMProvider,
    Pipeline,
    ZScoreDetector,
)

pipeline = (
    Pipeline(window_days=3)
    .add_source(CSVSource("tests/fixtures/python_interest.csv", keyword="Python"))
    .add_detector(ZScoreDetector(threshold=1.0))
    .add_ranker(BM25Ranker(top_k=5))
    .add_explainer(LLMExplainer(MockLLMProvider()))
)

report = pipeline.run()
print(report.executive_summary)
print(report.to_markdown())
print(report.to_html())

Export a shareable HTML page or PDF attachment (whytrend[pdf] for PDF):

from pathlib import Path

Path("report.html").write_text(report.to_html(), encoding="utf-8")
Path("report.pdf").write_bytes(report.to_pdf())  # pip install 'whytrend[pdf]'

Run the full demo:

pip install -e ".[dev]"
python examples/mvp_demo.py

Production-style example

from whytrend import (
    GoogleTrends,
    OpenAIExplainer,
    Pipeline,
    ProphetDetector,
)
from whytrend.collectors import (
    GitHubReleasesCollector,
    GoogleNewsCollector,
    HackerNewsCollector,
    RSSFeedCollector,
    RedditCollector,
    StackOverflowCollector,
    WikipediaCollector,
)
from whytrend.rankers import BM25Ranker

pipeline = (
    Pipeline()
    .add_source(GoogleTrends("Python"))
    .add_detector(ProphetDetector())
    .add_collector(GoogleNewsCollector())
    .add_collector(HackerNewsCollector())
    .add_collector(RedditCollector())  # REDDIT_CLIENT_ID + REDDIT_CLIENT_SECRET
    .add_collector(GitHubReleasesCollector())  # optional GITHUB_TOKEN
    .add_collector(StackOverflowCollector())  # optional STACKEXCHANGE_KEY
    .add_collector(
        RSSFeedCollector(
            [
                "https://blog.python.org/feeds/posts/default",
                "https://pyfound.blogspot.com/feeds/posts/default",
            ]
        )
    )
    .add_collector(WikipediaCollector())
    .add_ranker(BM25Ranker())
    .add_explainer(OpenAIExplainer())  # OPENAI_API_KEY env var or api_key="..."
)

report = pipeline.run()
print(report.executive_summary)

Use OllamaExplainer(model="llama3.2") for a local LLM instead of OpenAI.

Other cloud providers (same explainability pipeline, different backends):

from whytrend import (
    AnthropicExplainer,
    AzureOpenAIExplainer,
    DeepSeekExplainer,
    GeminiExplainer,
    OpenRouterExplainer,
)

# ANTHROPIC_API_KEY — default model: claude-sonnet-4-20250514
AnthropicExplainer()

# GEMINI_API_KEY or GOOGLE_API_KEY — default model: gemini-2.0-flash
GeminiExplainer()

# DEEPSEEK_API_KEY — default model: deepseek-chat
DeepSeekExplainer()

# AZURE_OPENAI_API_KEY + AZURE_OPENAI_ENDPOINT (or pass api_key= / azure_endpoint=)
AzureOpenAIExplainer(deployment="gpt-4o-mini")

# OPENROUTER_API_KEY — default model: openai/gpt-4o-mini
# Optional site_url= / app_title= set OpenRouter ranking headers
OpenRouterExplainer(model="anthropic/claude-sonnet-4")

Pass api_key="..." to any of these constructors if you prefer not to use env vars. Swap them into .add_explainer(...) the same way as OpenAIExplainer.

OpenAIExplainer(base_url="https://openrouter.ai/api/v1", api_key=...) also works for OpenRouter; the dedicated classes improve DX (env vars, Azure deployment/endpoint, OpenRouter headers).

Reddit credentials (create at https://www.reddit.com/prefs/apps):

export REDDIT_CLIENT_ID="..."
export REDDIT_CLIENT_SECRET="..."

Or pass them explicitly:

RedditCollector(client_id="...", client_secret="...", subreddits=["Python", "MachineLearning"])

GitHub Releases works without a token for light use. For higher rate limits:

export GITHUB_TOKEN="ghp_..."
GitHubReleasesCollector(token="ghp_...", repos=["python/cpython"])

Stack Overflow works without a key for light use. For a higher daily quota (register at https://stackapps.com/):

export STACKEXCHANGE_KEY="..."
StackOverflowCollector(api_key="...", site="stackoverflow")

Custom RSS/Atom feeds (keyword + time-window filtered):

RSSFeedCollector(
    [
        "https://blog.python.org/feeds/posts/default",
        "https://hnrss.org/frontpage",
    ]
)

Architecture

Source → Detector → Event Builder → Collectors → Ranker → Explainer → Report

Choosing a detector

Detector Best for Notes
ZScoreDetector Sudden spikes/drops vs the series mean Fast, no extra deps
ProphetDetector Points outside a forecast band (trend + seasonality) Needs whytrend[prophet]
RupturesDetector Structural breaks / regime changes Needs whytrend[ruptures]; emits changepoint
RiverDetector Online / streaming point-by-point scores Needs whytrend[river]; batch + update()
from whytrend import RiverDetector, RupturesDetector

RupturesDetector(algorithm="pelt", model="rbf", penalty=10.0)
RiverDetector(model="gaussian", threshold=0.95, min_points=10)

Writing a WhyTrend plugin

Third-party packages can register components without forking WhyTrend. Direct imports (from whytrend import ZScoreDetector) remain the supported API.

Declare an entry point in your package:

[project.entry-points."whytrend.collectors"]
my_news = "mypkg.collectors:MyNewsCollector"

Groups:

  • whytrend.sources
  • whytrend.detectors
  • whytrend.collectors
  • whytrend.rankers
  • whytrend.explainers

Discover and load plugins at runtime:

from whytrend import list_plugins, load_plugin

detectors = list_plugins("whytrend.detectors")
Ruptures = load_plugin("whytrend.detectors", "ruptures")
MyNews = load_plugin("whytrend.collectors", "my_news")

load_plugin raises UnknownPluginError if the name is missing. Built-in classes are also registered in WhyTrend's own pyproject.toml, so names like zscore and hacker_news resolve the same way.

Roadmap

Core MVP is in place. Next focus: integrations and ecosystem.

v0.2 — More collectors

  • Google News
  • Reddit
  • GitHub Releases
  • RSS / Stack Overflow

v0.3 — More detectors

  • Ruptures (change-point)
  • River / streaming detectors

v0.4 — More LLM providers

  • Anthropic, Gemini, DeepSeek
  • Azure OpenAI, OpenRouter

v0.5 — Reports and DX

  • HTML / PDF reports
  • CLI (whytrend analyze ...)
  • Plugin registry (entry points)

v0.6 — Ranking quality

  • Hybrid ranker (BM25 + embeddings merged with RRF)
  • Post-ranker evidence filter (relevance thresholds, stronger rerank, explicit verdicts like evidence_insufficient / correlation_only / likely_cause)

Track progress in GitHub Issues.

Development

make install   # install with detected tool (uv / poetry / pip)
make check     # ruff + format check + mypy + pytest

Supports the three common workflows:

Tool Install Run checks
uv (default if installed) make install make check
poetry make install TOOL=poetry make check TOOL=poetry
pip / venv make install TOOL=pip make check TOOL=pip

Auto-detect order: uv → poetry → pip. Override anytime with TOOL=....

Useful targets:

make lint          # ruff check
make format        # ruff format + autofix
make format-check  # ruff format --check
make typecheck     # mypy
make test          # pytest
make help          # list all targets

Contributing

See CONTRIBUTING.md. Please follow the Code of Conduct. Security reports: SECURITY.md.

Author

Alexander Provatorov — GitHub @AlexProvatorov

License

Licensed under the Apache License, Version 2.0.

Metadata

Release files for whytrend 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for whytrend 0.5.0
File Size Uploaded Provenance Actions
whytrend-0.5.0.tar.gz 70.8 kB With provenance Details for whytrend-0.5.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for whytrend 0.5.0
File Interpreter ABI Platform Actions
whytrend-0.5.0-py3-none-any.whl Python 3 none any Details for whytrend-0.5.0-py3-none-any.whl

Total release size: 153.3 kB

Release files / whytrend-0.5.0.tar.gz

Download URL whytrend-0.5.0.tar.gz
Size 70.8 kB
Tags Source
SHA-256 checksum
How to use checksums
6dbbc2021f9e557a528bbfdfd4cb7230d83c00425e55104c8bf33212832dd1b4
BLAKE2b-256 checksum
How to use checksums
f7cf1b811f97b96d3e8fcbd55a6547113fcf64738ad221cbfcf8d0e34fabdcb1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

Release files / whytrend-0.5.0-py3-none-any.whl

Download URL whytrend-0.5.0-py3-none-any.whl
Size 82.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
52976b80749f738f421fc5812be14337af9186cfeb01f1ed1fe1c7f68e7268d2
BLAKE2b-256 checksum
How to use checksums
62b9f960a8d0047d9e4f6f4d3d3b59a8b37686fbcbf53f84ee1dc77baf392dca
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

Security for whytrend 0.5.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for whytrend-0.5.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page