Fredgar AI ships two front ends over the same warehouse:
Interactive app (frontend/) |
Static mirror (this doc) | |
|---|---|---|
| Audience | Account-based users (token/session auth; reads public, writes/sync staff-only) | Anyone — no account, no backend |
| Stack | Vite/React SPA → Django/DRF API | Plain HTML/CSS/JS generated at build time |
| Freshness | Live (on-demand SEC/FRED syncs) | Rebuilt on a schedule (weekly + manual) |
| Hosting | Docker / nginx (see docker-compose.yml) |
GitHub Pages |
Both are rendered from the same services (build_company_context reuses the profile/statements/analytics code), so the mirror shows exactly what the API does. The proposed extract store sits behind both as a shared cache: Pages still builds offline from the Release warehouse; only the interactive backend (and refresh-data.yml) may pull new EDGAR/FRED bytes.
One page per company (infobox, headline snapshot, financial statements with per-line SEC source links, derived metrics, leadership, stakeholder index, filings, documents, XBRL fact preview) plus:
index.html — the landing/hero page: headline, a live client-side “search a real company”
demo widget (built from data already computed per company during the publish loop — no extra queries, no client fetches), a feature tour of what’s mirrored here vs. app-only, an editions comparison, a disclosure section, and the full searchable company table (#browse, unchanged behavior from before) below the marketing contentabout.html — data sources, methodology, and the two-front-ends storymacro/ — the FRED macro section (when series data is warehoused): macro/index.html
lists the curated theme bundles; macro/<slug>/index.html renders each bundle’s series with latest value, 1M/1Y deltas, a server-rendered SVG sparkline (no JS required), curation notes, industry-relevance tags, and a link back to the series on fred.stlouisfed.org — plus per-bundle observations.csv (full synced history, long format) and bundle.json. Includes the required FRED® API attribution. Omitted entirely when nothing is synced — no empty shell pages, and the “Macro” nav link only appears when the section exists.company.json, facts.csv, metrics.csv, statements.csv, filings.csv,
leadership.csv; site-wide companies.json / companies.csv.nojekyll (always) and sitemap.xml / robots.txt (when a base URL is configured)generate_site() collects a small “featured companies” list while it’s already looping over every company to render their pages — zero additional DB queries. Featured companies are preferred by whether they have any of the three demo-shown concepts (Revenue, Net Income, Total Assets) and then by richest data (counts.facts), capped at FEATURED_LIMIT (8). Their identity, headline figures, and fact/filing counts are rendered as ordinary server-side HTML (.example-card elements) — visible and fully readable with JavaScript disabled. A small vanilla-JS enhancement (the block script template block in index.html) adds a .js class to <html>, which is what actually collapses the cards to one-at-a-time with pill-switching and enables the search-filter box; without JS, every example simply renders stacked. Company data with no matching headline concept still appears in the demo with a graceful fallback message instead of blank figures.
publish_static_site → warehouse/services/static_site_publish.py:
Company, sync submissions + XBRL facts from SEC
EDGAR (DB-first payload cache, paced with --delay), compute derived metrics, and best-effort sync leadership (Forms 3/4/5). A failing ticker is reported and skipped — never aborts the publish.FRED_API_KEY is present (and --skip-macro isn’t passed), refresh the FRED series
bundles (refresh_series_bundles) so the macro section publishes with fresh data. No key → the sync is skipped and the macro section renders only from already-warehoused data (or is omitted). A failing FRED sync never blocks the company publish.generate_site
(warehouse/services/static_site.py); macro context comes from public_data/services/static_export.py.cd src
# Full build (SEC network) — default curated cohort:
python manage.py publish_static_site --output ../site
# Custom cohort, with sitemap/robots for a known public URL:
python manage.py publish_static_site --tickers AAPL,MSFT,NVDA \
--base-url https://bamr87.github.io/fredgar-ai --output ../site
# Offline re-render from already-warehoused data (no SEC calls):
python manage.py publish_static_site --skip-sync --output ../site
# Render EVERYTHING in the warehouse, offline (the dataset defines the site):
python manage.py publish_static_site --all-warehouse --output ../site
# Sync the warehouse without rendering (the dataset-refresh path):
python manage.py publish_static_site --sync-only --force-refresh
generate_static_site still exists for rendering arbitrary warehouse companies (--ticker/--cik/--all) without any syncing.
The extracted data lives in one compressed SQLite file — fredgar-warehouse.sqlite3.zst, attached to the rolling dataset GitHub Release. That file is the warehouse: companies, filings, XBRL facts, derived metrics, leadership, the raw SEC JSON payload cache (EdgarSecPayload), and FRED series observations.
Two workflows with strictly separated responsibilities:
refresh-data.yml — the only thing that
talks to SEC EDGAR or FRED. Weekly cron + manual dispatch (optional tickers input to refresh or add companies). It downloads the current dataset, runs publish_static_site --sync-only --force-refresh (force-refresh matters: the DB-first payload cache would otherwise serve stale SEC JSON forever from a persistent DB), then re-uploads the compressed DB to the release and dispatches a publish. The first run bootstraps the dataset from an empty warehouse.pages.yml — render + deploy only. Downloads the
dataset asset and runs publish_static_site --skip-sync / --all-warehouse — zero network calls to EDGAR/FRED, fast, deterministic, immune to upstream outages and rate limits. If the dataset release doesn’t exist yet it falls back to a live SEC sync (bootstrap) and tells you to run the refresh workflow.The published site itself remains the presentation-layer dataset (per-company company.json/facts.csv, site-wide companies.json, per-bundle observations.csv / bundle.json) — the release asset is the build input, not the display format.
.github/workflows/pages.yml builds and deploys on:
tickers input to publish a subset),main (self-heals if anything else deploys over the site).One-time repository setup:
USER_AGENT_EMAIL repository variable or secret — SEC requires a contact
email in the User-Agent; the refresh workflow (and the publish’s bootstrap fallback) fails fast without it.dataset release. All publishes are offline from then on.STATIC_SITE_APP_URL repository variable with the public URL of the
interactive app; every static page then shows a “Live app ↗” cross-link.FRED_API_KEY repository secret
(free key) so the refresh workflow also syncs the macro section. Without it the site simply omits the macro pages.The published URL is https://<owner>.github.io/<repo>/ (the workflow derives it automatically for the sitemap). All intra-site links are relative, so the site works at any base path — project pages, a custom domain, or file:// on disk.
--app-url flag or STATIC_SITE_APP_URL env (see
src/.env.example); rendered in the header/footer/about page.VITE_STATIC_SITE_URL when building the frontend; the sidebar shows a
“Public mirror” link (see frontend/README.md).| Env var | Used by | Purpose |
|---|---|---|
STATIC_SITE_BASE_URL |
generate_site |
Canonical public URL → sitemap.xml + robots.txt |
STATIC_SITE_APP_URL |
templates | “Live app” cross-link on the mirror |
STATIC_SITE_SOURCE_URL |
templates | “Source” link (defaults to this repo) |
USER_AGENT_EMAIL |
SEC client | Required contact email for SEC fair access |
FRED_API_KEY |
FRED provider | Optional — enables the macro section’s FRED sync at build time |
CLI flags (--base-url, --app-url) override the env vars per run.