Acoustic calculator
Audience: developers & AI agents · Scope: the public acoustic/reverberation calculator · Last reviewed: 2026-07-25
TL;DR — A public tool where a visitor describes a room and gets an acoustic result back. The site owns the form, the validation and the reference data; the calculation itself runs on an external Google Cloud endpoint that the site calls with a Google identity token. The reference data (materials, products, room targets) is not in the database — it is three JSON files uploaded through Custom Settings.
Entry point: index.php?option=com_configbox&view=customview&viewname=bcacousticcalculator — linked from the
Resources menu and from the configurator page.
What it is & why it exists
Specifiers want to know whether a space will sound right, and which Beta-Calco acoustic products get it there. Rather than embed acoustics maths in the site, the model is delegated to a separately-deployed service; the site is the front door, the validator, and the owner of the product/material catalogue the model works from.
How it fits together
visitor fills the form (room size, space function, materials, products)
│
▼
ConfigboxControllerBcacousticcalculator
├─ callWarmup() → wakes the endpoint (cold starts are expected)
└─ getCalculationResponse()
│
▼
ConfigboxModelBcacousticcalculator
├─ getDataFromRequest() – marshal the form
├─ getValidationMessages() – server-side validation
├─ getAccessToken() – Google ID token for the endpoint
└─ getCalculationResponse() – POST JSON → the Cloud endpoint
│
getMaterials() / getProducts() / getRoomTargets() ← three uploaded JSON files
| File | Role |
|---|---|
data/customization/views/bcacousticcalculator/ | the public page and its form (default.php also holds the YouTube video-guide thumbnail and the agent-only chatbot include) |
data/customization/controllers/bcacousticcalculator.php | callWarmup() and getCalculationResponse() — the two AJAX tasks |
data/customization/models/bcacousticcalculator.php | reference-data loading, validation, auth, and the outbound call |
data/customization/templates/configuratorpage/complete_page.php | the per-product ACOUSTIC CALCULATOR button, shown when acoustic_calculator_link is set |
Admin settings
All in Custom Settings → Acoustic Calculator.
| Setting | Label | Type | Blank / off |
|---|---|---|---|
acoustics_endpoint_url | Acoustic Calculator Endpoint URL | string | No calculation is possible — the call has nowhere to go. It doubles as the token's audience, so it must be the exact endpoint. It is also the chatbot's endpoint — see the warning below. |
acoustics_materials_json | Materials JSON File | file | No materials to choose from |
acoustics_products_json | Products JSON File | file | No products to choose from |
acoustics_room_targets_json | Room Targets JSON File | file | No target curves to compare against |
The three JSON files are stored in the private data store under
private/custom_media/acoustics_calculator/{materials,products,room_targets}/. Each upload is capped at
50 KB, restricted to .json / application/json, and carries FILENAME_TO_RECORD_ID SAVE_FILENAME NODELETEFILE — so the stored file is named after the settings row (1.json live, 3.json dev) and the form
offers Replace file but no delete. Live's products file is already ~40 KB, so the cap is within reach.
Per product, not in this group: acoustic_calculator_link ("Enable Link to Acoustic Calculator", a boolean
in the product form's Acoustic Calculator group, stored in #__configbox_external_product_appends) renders
the ACOUSTIC CALCULATOR button on that product's configurator page. It links to the plain calculator route
— no product is pre-selected. 28 products have it set (counted 2026-07-25 in the DDEV copy of live data).
Credentials are not configured here. The calculator authenticates with the site-wide Google service account
(reps_file) — see Google Sheets → credentials. That single key
also backs Sheets, Drive, BigQuery and the chatbot.
Control / data flow
- The page loads the materials, products and room targets from the uploaded JSON files. This is why updating the catalogue is a file upload, not a data migration.
- On submit, the model marshals the request and runs server-side validation (
getValidationMessages()) — e.g. "Space function is required". Validation is not delegated to the endpoint. getAccessToken()builds a Google client from the service-account key, sets the scope to the endpoint URL, callsfetchAccessTokenWithAssertion()and takes theid_token(not the access token) — the standard way to call a private Cloud Run/Functions service, where the token's audience must equal the service URL.callWarmUp()posts{"warm_up": true}first, then the real payload is POSTed as JSON.- The endpoint's response is returned to the page.
Data model
None on the site side. Nothing is persisted: no request log, no result table. The reference data lives in files, the answer is computed remotely and rendered. A submitted calculation leaves no trace beyond the logs and metrics below.
Deployment runbook (manual steps)
- Deploy/obtain the acoustics endpoint and note its exact URL.
- Grant the site's Google service account permission to invoke it (for Cloud Run, the Invoker role).
- Custom Settings → Acoustic Calculator → set Acoustic Calculator Endpoint URL.
- Upload the three JSON files: materials, products, room targets.
- Smoke test: open the calculator, submit a room, confirm a result. A first call after idle may be slow — that is the cold start the warm-up exists for.
Turning it off: clear the endpoint URL (the tool then cannot calculate) or unpublish the menu items linking
to it — there are two published entries, resources/acoustic-calculator in mainmenu and an SEO alias. Neither
removes the per-product ACOUSTIC CALCULATOR buttons, which are driven by acoustic_calculator_link. There
is no dedicated on/off switch.
Monitoring
| Signal | Where |
|---|---|
| Log type | custom_acoustic_calc |
| CloudWatch namespace | Acoustic-Calculator — see monitoring.md |
Gotchas & caveats
- The endpoint URL is also the token audience. Changing the URL without re-issuing the token, or a trailing slash mismatch, produces an auth failure rather than a routing error — misleading if you're debugging.
- The chatbot posts to the same endpoint, distinguished only by an
ai_request: trueflag in the body. Changing this setting moves both features, and nothing in the settings UI says so. Re-test the chatbot after any change here. - Cold starts are real. The explicit warm-up call exists because the endpoint is serverless; if the warm-up is skipped or fails, the first real call may time out.
- Reference data updates are file uploads. Nobody edits materials or products in the admin UI — a stale catalogue means a stale JSON file. There is no validation that the JSON matches what the endpoint expects.
- A missing or unparseable file degrades silently — it does not error.
getMaterials()and friends wrap everything intry/catchand rethrow, but with a missing file the chain isfile_get_contents→false→json_decode→null→ array-offset/foreachwarnings, not throwables, so an empty array comes back. Verified on DDEV by renamingmaterials/3.jsonaway and by truncating it: the page still returned HTTP 200 with the full form, and the six surface dropdowns rendered with only Select — no error anywhere on the page or for the visitor. An empty dropdown is the only symptom, and it points at exactly one of the three files. - Only a material's first category is honoured. The model sets
$item['category'] = $item['categories'][0]and the view matches that single value againstCeilings/Floors/Walls, so a material listed for several surfaces appears in one dropdown only. The live catalogue works around this with duplicate entries (e.g. separate drywall rows for walls and ceilings); the 40 materials split 25 / 10 / 5. show: falsein the products file hides a product from the series dropdown without removing it — check that before concluding the file is wrong.- Product thumbnails are absolute
https://betacalco.com/images/acoustic-calculator/…URLs inside the products file, so dev and staging load them from live, and a new product needs its image uploaded to live's image folder separately. - It shares the site-wide Google credential. Rotating
reps_fileaffects this tool along with Sheets, BigQuery and the chatbot. - Nothing is stored, so there is no way to reproduce a visitor's reported result after the fact — ask for their inputs.
- The result panel's "Send Feedback" link is a hard-coded Google Form URL in
tmpl/result.php. Visitor feedback lands there, not anywhere in the site — worth knowing when someone asks where the responses go.
Testing
No dedicated spec. The practical check is submitting a calculation on the page after any change to the endpoint, the credential, or the JSON files.
Possible follow-ups
- Validate the uploaded JSON against a schema at upload time, so a bad file fails at the point of change rather than in front of a visitor.
- Log submissions (even anonymously) so reported results can be reproduced.
Related docs
- Google Sheets → credentials — the shared service account
- Chatbot — same endpoint, and it renders only on this page (agents only)
- Monitoring & metrics
For operators → admin-guide/products-configurator/update-the-acoustic-calculator.md.