8.0 KiB
AGENTS.md
Guidance for agents working in this repository (Brandize · BI Dashboard).
What this project is
A flat, server-free browser dashboard for the Brandize BI webhook. It renders gross revenue (Brutto inkl. USt) per departure country plus a VAT summary, with KPI cards, bar charts and detail tables. There is no build step, no framework, no package manager — just static files opened directly in a browser.
Supporting tools: a bash dev-server launcher (start) and a Python CLI (send) that
uploads CSV rows to an n8n webhook. SQL scripts (sql/) define the upstream queries
that feed the webhook (Odoo 17 / PostgreSQL).
Layout
| Path | Purpose |
|---|---|
index.html |
Dashboard markup: sidebar nav, shared filter bar, one <main class="view"> per report, auth overlay |
styles.css |
All styling (CSS custom properties in :root, BEM-ish class names) |
app.js |
Everything: data loading, parsing, rendering, CSV export, auth, navigation |
README.md |
User-facing docs (German/English mixed). Update it when behavior changes |
start |
Bash: installs live-server if missing and serves the folder on :8080 |
send |
Python CLI: batches CSV rows to the .../webhook/VAT n8n webhook |
sql/*.sql |
Upstream Odoo/PostgreSQL queries (see below) |
data/ |
Git-ignored. Empty in the repo; holds local CSV payloads for send |
Running / validating
There is no test suite and no linter. Validation is manual:
- Open
index.htmldirectly (double-click) — the intended primary usage, works overfile://. - Or run
./startand browsehttp://localhost:8080for live reload. ./send data/<file>.csvrequires Python 3 +requestsand network access to the webhook.
Because fetch hits a remote webhook protected by HTTP Basic Auth, you generally
cannot fully exercise the UI without valid credentials and network access. Don't
claim validation passed unless you actually loaded the page and saw it work; otherwise
state clearly what you could not run.
Architecture notes (app.js)
Single file, loaded as a plain <script> (not a module), "use strict" at the top.
No bundler — keep everything in global scope as the file already does.
- Config constants at the top:
API_URL,MONTHS(German abbreviations),PALETTE. els— a single object caching allgetElementById/querySelectorlookups. Add new DOM handles here rather than querying inline.- State:
currentView,lastData({ abgangsland, vat }),auth. - Sections are delimited by
/* ---------- Name ---------- */banners: Auth, Period selector, Formatting, Data, Rendering (per report), CSV export, Navigation, Init. - Views: the
VIEWSmap defines title/subtitle per view id.showView(name)toggles.hiddenon the<main class="view">elements and re-renders. A new report needs: markup section inindex.html, an entry inVIEWS, a render function, and aviewEls()branch. - Data flow:
load()→POST API_URLwith{ query, month }→extractArray(payload, key)→normalizeAbgangsland/normalizeVat→renderActiveView().load()queries only the active dashboard's dataset; it is triggered fromshowView()(when a dashboard is displayed) and from the refresh/period controls — never prefetched.
Webhook / data contract
- The webhook is called with POST and a JSON body
{ "query": <key>, "month": "YYYY-MMM" }(quarter dashboards send{ "query": <key>, "quarter": "YYYY-QN" }instead).queryselects the dataset. The datasets are large, so exactly one dataset is requested per call, for the dashboard currently shown (no prefetching). - Query keys:
"Summe je Abgangsland"(Umsatz je Abgangsland),"VAT Summary","ODOO Rechnungen pro Monat","Alle Rechnungen pro Monat","Quartal Schweiz","FIFO Lager Bewertung"and"Produkt Materialkosten". Each view'squerylives in theVIEWSmap, which maps the active dashboard to itsnormalizefunction;quarter: truemarks the dashboards that use a quarter period. The FIFO and Produkt-Materialkosten responses are[{ id, query, row_count, data: [...] }](unwrapped innormalizeFifo/normalizeMaterialkosten). In Produkt Materialkosten,remaining_qtyis the remaining quantity of that cost bracket (the last cost bracket, not FIFO), not the product total. extractArrayaccepts the dataset either as a bare array or wrapped under the query key (case-insensitive). If the shape changes upstream, adjust the normalizers.- The webhook returns German-formatted strings, e.g.
"90.717,93 €"and"1.234". Parsing (parseEuro,parseQuantity) strips symbols and swaps./,— do not useparseFloatdirectly on the raw values. monthusesYYYY-MMM(e.g.2026-SEP), matching%(month)sinsql/*.sql;quarterusesYYYY-QN(e.g.2026-Q3), matching%(quarter)sinsql/Quartal_Schweiz.sql.
Auth
HTTP Basic. The first request goes out without credentials; a 401 triggers the
login overlay. Credentials live in sessionStorage (best-effort — unavailable on
file://, where they stay in memory only) and are sent as Authorization: Basic ....
SQL scripts (sql/)
Odoo 17 / PostgreSQL against table brandize_datacenter_amazon_vat_transaction.
Each file has a header block documenting the model, parameters and invocation.
- Parameter is named
%(month)sfor psycopg2; inpsqluse:'month'instead (documented in the headers — respect this distinction). - Money is formatted in SQL to German notation via
translate(to_char(...), ',.', '.,') || ' €'so the frontend receives display-ready strings. Keep formatting consistent if you add columns. Delete_Activity_Period.sqlis destructive — note the "ACHTUNG" warning and the transaction-bracket guidance. Be cautious editing it.
Conventions
- Language: UI text is German, code identifiers and comments are English (comments occasionally German). Keep new user-facing strings German.
- Number/date formatting: always
de-DEviatoLocaleStringin JS and Germanto_charformatting in SQL. Dates asYYYY-MMMwith theMONTHSabbreviations. - JS style:
"use strict", double quotes, semicolons, 2-space indent,const/let(novar), namedfunctiondeclarations,camelCase. Nullish-safe (??, optional guards). DOM built imperatively withdocument.createElement(charts are hand-rolled bars/lists, no chart library). - CSS: use the existing
:rootcustom properties for colors/spacing/radius; reuse existing class patterns rather than inventing new naming. - CSV export: semicolon-delimited, UTF-8 with BOM (for Excel umlauts), CRLF line
endings, includes a
Gesamttotal row, filename*_YYYY-MMM.csv. Follow this pattern for any new exportable view. - Keep changes minimal and consistent with the existing file structure — this is a small, intentionally dependency-free static app. Prefer no new dependencies.
Gotchas
data/is git-ignored; never commit contents from it.sendexpects CSVs with a header row (csv.DictReader) and posts JSON arrays in batches of 50.- The
sendscript targets a different webhook (/webhook/VAT) than the dashboard (/webhook/bi-dashboard). Don't conflate them. fetchfromfile://sendsOrigin: null; the webhook reflects origins to allow CORS. Changing how requests are made can break the "just open the file" workflow.- README documents behavior (navigation, auth, CSV, filter). If you change any of these,
update
README.mdin the same change. - The invoice dashboards (ODOO Rechnungen pro Monat, Alle Rechnungen pro Monat
and Quartal Schweiz) are pivot experiments: they load jQuery, jQuery UI and
PivotTable.js from CDNs (so they need internet), reuse
normalizeRechnungen, and render viarenderRechnungenPivot(no detail table). Layouts come fromPIVOT_PRESETS(keyed per view; the Vorlage dropdown) / the pivotUI panel; the renderer is fixed to Table. The pivot container is recreated on each render so a preset actually applies.