Repository navigation
Survey Sheet
EDLD records surface mining deposits locally, in
<data>/mining.db. That store is yours and stays on your machine. This
directory is for the optional other half: pushing those deposits to a Google
Sheet so a squadron, a wing, or a Discord full of strangers can read them.
Everything here is off until you turn it on.
| File | What it is |
|---|---|
Loader.gs |
The one file you paste into the sheet. Installs and upgrades everything else from the sheet's own menu. |
Mining_Dashboard.xlsx |
Optional starting spreadsheet: a themed, filterable, sortable dashboard over Surface Sites, and an empty Ring Hotspots tab. |
Code.gs |
Source: the receiver, which accepts deposits and ring hotspots from EDLD and writes the Surface Sites and Ring Hotspots tabs. |
Dashboard.gs |
Source: the dashboard's theme, click-to-sort and formula repair. |
Template.gs |
Source: builds the Dashboard, Settings, Queries and Themes tabs a sheet does not have. |
Upgrade.gs |
Source: the sheet's upgrade steps, run by Upgrade after new code is installed. |
edld_sheet.js |
Generated: the three sources bundled, which is what the loader installs. Never edit. |
release.json |
Generated: the version, the bundle's file name and SHA-256, and what it needs. |
build_bundle.py |
Regenerates the bundle and the manifest; --check says whether they are stale. Standard library only. |
build_dashboard.py |
Regenerates the template, and the bundle and manifest with it. |
A small script that lives inside your spreadsheet and accepts writes over HTTP. EDLD posts batches of deposits to it; it decides what is new, what has changed, and what to ignore.
No Google Cloud project. No OAuth. No API key — API keys authenticate reads of public data and cannot write to a sheet at all, which is why this is not built that way.
You end up with two strings: a URL and a token. Those are what you hand to anyone you want writing to the sheet, and the token is what you change when you want them to stop.
1. Make or open the spreadsheet. Any Google Sheet — a new, blank one is
the usual start. Installing builds everything: the Dashboard, Settings,
Surface Sites and Ring Hotspots tabs, and the hidden Queries and Themes tabs behind them, and removes
Google's empty Sheet1. An existing sheet keeps its tabs and gains only the
ones it is missing. See The dashboard below.
Importing Mining_Dashboard.xlsx (Drive → New → File upload, then File → Save
as Google Sheets) gives the same starting point, if you would rather begin from
a file; you still paste the loader into it afterwards.
2. Paste the loader. Extensions → Apps Script. Replace everything in
Code.gs with the contents of Loader.gs from this directory (the file's name
in the editor does not matter), and save. It is the only file the project
needs, and the only one you will ever paste: everything else is installed and
upgraded from the sheet's menu. Why it works that way is in Upgrading a
sheet.
3. Install. Reload the spreadsheet. An ED Dashboard menu appears; run Install / Upgrade…. Google asks you to authorize the script the first time — it needs to fetch the release from GitHub and to change this spreadsheet. It will also warn that the app is unverified: that is the normal notice for a script you added yourself and have not submitted to Google for review. Advanced → Go to (project name).
Upgrade shows the version it found and asks before installing anything. It then asks for a token: leave the box blank and it generates one and shows it to you. The token is the only thing standing between your sheet and anyone who learns the URL, and it is kept in the script's properties rather than in any file, so upgrades never touch it. ED Dashboard → Set sheet token… changes it later. Until one is set, the script refuses every write, so a half-finished setup fails closed rather than open.
4. Deploy. Deploy → New deployment → gear icon → Web app.
| Field | Value |
|---|---|
| Description | anything |
| Execute as | Me |
| Who has access | Anyone with the link |
"Anyone with the link" sounds worse than it is. It means the endpoint is reachable without a Google account, which it has to be for EDLD to post to it. Every request still has to carry the token, and the sheet itself is not shared by this — its own sharing settings are unchanged and still decide who can read it.
Google will ask you to authorize the script the first time. It will also warn that the app is unverified: that is the normal notice for a script you wrote yourself and have not submitted to Google for review. Advanced → Go to (project name).
5. Copy the web app URL. It looks like
https://script.google.com/macros/s/AKfy.../exec. Keep the /exec on the end.
6. Point EDLD at it. In config.toml:
[SurfaceSurvey]
Enabled = true
WebAppURL = "https://script.google.com/macros/s/AKfy.../exec"
Token = "the token you generated"
ReporterName = "CMDR YOUR NAME" # blank to publish anonymously
IncludeTest = false
BatchSize = 200ReporterName fills the reported_by column. Leave it empty and the column
stays blank — the deposits still publish.
7. Test it. The Test button in Options round-trips a ping and tells you what came back. If it fails it says why: a bad token, an unreachable host and an unconfigured script are three different messages.
Give them the URL and the token. That is the whole of it — they paste both into
their own config.toml and their finds start landing in your sheet.
Who can read the sheet is a separate question, settled the way it always was: the sheet's own Share button. EDLD does not touch it.
To revoke someone, run ED Dashboard → Set sheet token… and give the new token to everyone but the person you are removing. It takes effect on the next request — no redeploy — and everyone keeps the same URL.
Layout v4 renames the Deposits tab to Surface Sites and adds Ring
Hotspots. The rename keeps every row and column where it was, and the
dashboard's formulas follow it. Until Upgrade has run, a sheet keeps taking
deposits as before; EDLD holds its hotspots back, logs "the sheet has no Ring
Hotspots tab — run ED Dashboard > Upgrade", and Test connection says the same.
They are sent at the first write after the upgrade.
Ring Hotspots columns, in order: hotspot_id, system, system_address,
body, ring, ring_type, reserve_level, commodity, overlap, note,
count, reported_by, is_test, first_seen, last_confirmed,
overlap_updated. A hotspot is matched on hotspot_id, worked out from the
system, ring and commodity. A later report fills blank ring_type,
reserve_level, count and system_address; overlap and note change
together, only for a newer overlap_updated.
The script matches on deposit_id, the first column. EDLD works out which
sightings are the same deposit before sending — same system, same body, same
commodity, within 200 meters — and gives each one a stable twelve-character id
that never changes once assigned. The script compares those strings.
This is why the check is on the sheet's side rather than in EDLD. Several commanders write to one sheet and none of them can see the others' local stores, so a client-side check would still let two people who found the same rock on the same evening both append it, each of them right about what they had seen. The script holds a document lock while it writes, so the second one updates the first one's row instead.
What a later report may change depends on what the field is:
-
Observations — rigs, refine count, the body's facts,
last_confirmed— are replaced only by a report with a newerlast_confirmed, so somebody replaying an old session cannot walk back a fresher reading. -
Assessments — amount and both densities — are what the HUD says the site
holds when full. A later report only fills them where they are blank. They
change when a commander corrects one, which stamps
assessment_updated, and a newer stamp is the only thing that replaces them. Otherwise anyone who merely drove onto a site would re-send whatever they imported weeks ago and undo the correction. -
Depletion is a date and nothing else — never an amount. The latest date
wins, since sites refill and are worked out again. It is written as text, so
Sheets does not parse it into a date in its own timezone and hand back a
different day. A sheet from before this that has
Depletedin the amount column has it cleared the next time the row is written, and never serves it. -
Notes are a commander's writing, stamped
notes_updated. The newest note wins outright, a blank one included, since emptying the box is how a note is withdrawn; an older copy coming back round is ignored.
Blank cells are always filled in. Any text Sheets would run as a formula —
starting with =, +, - or @ — is stored as text, so a note cannot
execute in anyone's copy of the sheet.
After upgrading EDLD, open the sheet and run ED Dashboard → Upgrade…. It
reads the release on the branch the sheet follows — main unless you have
picked another — tells you what it will change, and on your say-so:
- fetches the sheet code from beside that release's manifest and checks its SHA-256 against the manifest — a mismatch installs nothing;
- copies
Surface Sitesto a hidden tab namedSurface Sites backup <date>(Deposits backup <date>on a sheet from before layout v4); - installs the code and runs the sheet's upgrade steps — new columns, the dashboard's formulas and layout, tidying data an older version wrote — recording each as it completes, so an interrupted upgrade resumes where it stopped;
- re-applies the theme.
Surface Sites and Ring Hotspots are only ever added to, your Settings values are left as they are, and the URL and token do not change. A dashboard tab the sheet does not have is built; one it has is never rewritten. The release line in Settings!D1 follows each upgrade unless you have replaced it with your own text. EDLD's own writes wait while it runs. Running it again on an up-to-date sheet says so and changes nothing. "Up to date" is decided by the code's hash, not its version, so a branch whose version file has not moved still installs each new build. ED Dashboard → About shows what is installed, its hash, and where updates come from.
Nothing about a sheet's updates involves tags or GitHub Releases: a sheet reads
sheets/release.json from a branch and the bundle from beside it. To try what
is on dev before it is merged or tagged:
- Push
dev, withsheets/edld_sheet.jsandsheets/release.jsonrebuilt (python3 sheets/build_bundle.py; the release workflow andscripts/build_local.shboth refuse a stale pair). - In a copy of your sheet, run ED Dashboard → Update from branch… and
enter
dev. - Run ED Dashboard → Upgrade….
Each later push to dev is another Upgrade away. Enter a blank branch to go
back to main; Upgrade then offers main's code, and says so when the sheet's
layout is newer than that code expects — nothing in the sheet is undone, and the
older code leaves the newer columns alone. Any branch name works, including ones
with a slash, such as exp/server-mode.
GitHub serves raw files from a cache for a few minutes after a push, so the manifest and the bundle can briefly disagree. Upgrade then refuses the download and says to wait five minutes; nothing is changed.
A branch with no sheets/release.json — main, until the loader is merged —
is reported as having no sheet release rather than as an error.
Test connection in EDLD's Options names the release the sheet runs, and fails with "run ED Dashboard > Upgrade" when the sheet is too old to store everything this EDLD sends. An out-of-date sheet still accepts writes; it drops the fields it has no column for, which is why the test says so rather than reporting success.
Sheets set up by pasting Code.gs (and Dashboard.gs) have no Upgrade menu,
so they need this once:
- Open Extensions → Apps Script and copy your token — the text between the
quotes in
var TOKEN = '…'near the top ofCode.gs. The loader asks for it; giving it the same one means nobody's EDLD needs changing. - Delete every file in the project, then paste
Loader.gsas its only file and save. - Reload the spreadsheet and run ED Dashboard → Install / Upgrade…. Paste the token when asked.
- Deploy → Manage deployments → pencil → Version: New version → Deploy. The web app keeps its URL. This is the last redeploy the sheet needs: from now on the deployment runs the loader, and the loader runs whatever Upgrade last installed.
Until step 4 the web app still runs the old script, so writes keep working throughout.
An Apps Script project can rewrite its own files only through the Apps Script
API, and that API is off in the hidden Cloud project every sheet script gets.
Turning it on means a Cloud console visit for every sheet owner, which is
exactly what this setup promises you never need. So Loader.gs never changes:
it keeps the entry points Google calls — the web app, the menu, the edit
triggers — and runs the real code, which Upgrade stores in the script's
properties and the loader re-checks against its SHA-256 every time it loads it.
The check guards against a damaged download or stored copy. It does not make
the code more trustworthy than this repository, which is the same trust as
pasting it by hand: code comes only from raw.githubusercontent.com, only from
beside the manifest that names it — a manifest can name a file, not an address
— and never without you confirming the version. A release that needs a newer
loader says so and stops; that is the one case where a sheet needs another
paste.
That has happened once. Loader 2 fetches from indevlin/EDLD, where the
repository moved from indevlin/EDLD, and every release from then on needs
it, so a sheet running loader 1 is told to replace Loader.gs the next time
Upgrade runs. Paste the new Loader.gs over the old one — the script
properties, the token and the deployment all stay — save, reload, and run
Upgrade again.
A fork changes one line of Loader.gs, EDLD_REPO_RAW. To point one sheet at
a manifest somewhere else on raw.githubusercontent.com without editing the
loader, set the script property EDLD_RELEASE_URL (Project Settings → Script
properties) to its full address; it takes precedence over the branch.
Deposits publish when you leave a body, not as you mine. Sheets allows around sixty writes a minute; a single real mining session generated 608 refine events. Batching at the body boundary keeps one rig run from becoming a rate limit.
Nothing is marked published until the script says so. A network failure, an error page, a bad token or a partial write all leave the rows pending, and the next flush retries them. The failure mode this avoids is the one where EDLD reports success because the HTTP request completed.
IncludeTest is off by default. Deposits flagged as test data stay local.
Useful while you are setting this up and would rather not seed a squadron sheet
with whatever you were poking at.
The URL is half a credential. EDLD only ever logs its hostname, and Token
is redacted from the debug header by name. If you paste a log somewhere, check
it anyway.
The dashboard is a read side for people who open the sheet rather than run
EDLD. Installing builds it (Template.gs), and Mining_Dashboard.xlsx is the
same thing as a file; both come from the constants in build_dashboard.py, and
tests/test_sheets_template.py checks the two say the same thing cell for
cell. Either way it is a template, not a copy of anybody's data: Surface Sites and Ring Hotspots hold only their header rows, and Settings holds
placeholders.
Settings. Put your squadron's name in B1 and the maintaining commander in B2; the dashboard title and subtitle come from those. Leave B1 blank and the title falls back to "CMDR name's Mining Data". D1 carries the template version.
Filtering. Pick a System, a Commodity, or both in A5:B5. The table appears
at row 7 once either is set, and is empty until then. Both lists are built from
whatever is in Surface Sites, so they fill themselves as commanders publish.
Deposits flagged is_test are left out, as they are from EDLD's own reads.
Sorting. Sort By and Order (E5:F5) sort the table; the header of the sorted column carries ▲ or ▼. With EDLD installed in the sheet, clicking a header does the same: once to sort by it, again to reverse. Amount and Density sort by rank — Low, Medium, High, the levels EDLD records — rather than alphabetically. The sort is shared, since it lives in two cells: whoever clicks last decides it for everyone viewing.
Sorting is done inside the table's formula, not with Data → Sort range. The
table is one FILTER expression, and Sheets cannot reorder a formula's output
in place; a manual sort over it either fails or is undone on the next
recalculation.
Readability. Rows alternate between the theme's Panel and Panel Alt colors. High amounts and densities are highlighted, Low ones dimmed, and a deposit with a depletion date is struck through. Notes sit in the last column, wrapped, and the rest of the row aligns with their first line.
Theme. Settings B5 picks one of five presets — Classic HUD, Federation,
Empire, Alliance, Thargoid — or Custom, which uses the hex codes in B10:B21.
B6:B7 pick the title and body fonts. The template arrives in Classic HUD;
changing any of this needs EDLD installed in the sheet, because Sheets cannot color a cell
from a formula and something has to apply the codes. It re-applies on every
edit to Settings, or from the ED Dashboard menu. More presets go on the
hidden Themes tab: add a column before Custom and it joins the list.
Applying the theme replaces every conditional formatting rule on the Dashboard
tab. Rules of your own belong in styleDashboard_ in Dashboard.gs.
If the table shows nothing at all — no header when a filter is set — a formula has been lost. ED Dashboard → Repair dashboard formulas rewrites them, and rebuilds any dashboard tab that has been deleted. The template stores its Sheets-only formulas in the wrapper Google uses for its own xlsx export, which Sheets unwraps on import; the repair is there for the case where it does not.
Edit build_dashboard.py and run it from the repository root:
python3 sheets/build_dashboard.py # writes sheets/Mining_Dashboard.xlsx
python3 sheets/build_dashboard.py --version 20261001--version defaults to the version file, so rebuild after bumping it for a
release and the template's Settings!D1 matches it. It needs openpyxl,
which is in requirements-dev.txt. The dashboard's Sheets-only formulas are defined once, in
FORMULAS in Dashboard.gs; the build reads them from there and checks that
each one survives the export wrapper intact. Change a formula in Dashboard.gs
and rebuild, and the template and the repair command stay in agreement. The
Classic HUD palette appears in both files (THEMES and ED.FALLBACK).
tests/test_sheets_dashboard.py holds the pieces together: the committed
template must match a fresh build, the FILTER's column letters must name the
right fields in Code.gs COLUMNS, the sort rank must match the levels in
core/mining_db.py, and the two copies of Classic HUD must agree. Changing
COLUMNS — appending, as it always should be — leaves the letters valid; the
test is there for the day something is inserted instead.
build_bundle.py — run on its own, or by build_dashboard.py — writes
edld_sheet.js, which is Code.gs, Dashboard.gs and Upgrade.gs joined into
the body the loader runs, and release.json, which names the bundle by file
name with its SHA-256, the sheet layout version it brings a sheet to, and the
oldest loader it runs under. Both are committed, because sheets install them
straight from the branch.
For sheets, merging to main is the release. Sheets follow main by
default and read whatever release.json is there; tags and GitHub Releases
play no part. So rebuild after bumping version, before committing. Four
things refuse a stale pair: build_bundle.py --check, the release workflow's
verify job, scripts/build_local.sh, and the tests.
A change that needs the sheet itself changed — a column, a tab, a layout —
is a new entry at the end of SHEET_MIGRATIONS in Upgrade.gs, with the next
to: number. The rules for a step are at the top of that file; the short
version is that it must be safe to run twice and may only add to Surface Sites
and Ring Hotspots.
tests/test_sheets_loader.py runs the loader, the bundle and an old sheet
through a full upgrade under Node, with tests/gas_fake.js standing in for
Google's services.
Raise LOADER_VERSION in Loader.gs only when the loader itself must change,
and MIN_LOADER in Upgrade.gs only when a bundle cannot run under an older
one: every raise is another paste for every sheet owner.
Do not round-trip the template through Excel or LibreOffice. Neither can evaluate the dashboard's formulas, and saving from either replaces them with errors.
Positional, and shared between Code.gs and core/sheets_publish.py. If you
add one, add it to the end of COLUMNS in both. Reordering breaks every
existing row.
deposit_id · system · system_address · body · body_id ·
planet_class · gravity · body_radius_m · atmosphere · volcanism ·
signal_no · commodity · latitude · longitude · density_claimed ·
density_observed · amount · rigs · refine_count · first_seen ·
last_confirmed · reported_by · is_test · depleted_on · notes ·
notes_updated · assessment_updated
density_claimed is what the body's own signals advertised. density_observed
is what was actually found. They are separate columns because a site can be
rich and empty at the same time.
Getting Started
Guides
Reference
- Automation
- Building
- Configuration
- Dossier
- Edcars
- Exploration Exobiology Plan
- Licensing
- Mission Bootstrap
- Privacy
- Roadmap
- Server
- Signing
- Startup
- Streamer Stats Overlay
- Surface Survey
- Terminal Output
- Theming
Releases