The web surface
fresh-fvsbc web serves a browser surface over the artefacts a run already wrote. It is a reader: it holds no model state and computes no value the CLI cannot reproduce. The design, including what was rejected and why, is notes/design/fresh-fvsbc-web-frontend.md.
It is an optional extra. The core install pulls pandas and nothing else.
pip install 'fresh-fvsbc[web]'
fresh-fvsbc web --runs path/to/runs
The bind is 127.0.0.1 by default, and a non-loopback address is refused unless --allow-remote is
passed, because there is no authentication. --root-path makes the app work under a notebook proxy
prefix such as /user/<name>/proxy/8765/.
Deploying in a dev container
The FRESH dev container is a pre-authenticated environment: JupyterHub authenticates the browser and
proxies a local port to a generated path, so a service on port 9999 is reached at
https://fresh01.01101.dev/jupyterhub32/user/<name>/codeserver/proxy/9999/. The service therefore has to
listen off loopback, and it has to answer under the prefix:
fresh-fvsbc web --runs runs/ \
--host 0.0.0.0 --allow-remote \
--root-path /jupyterhub32/user/<name>/codeserver/proxy/9999 \
--port 9999
--allow-remote is required because the proxy connects from outside the loopback interface. The proxy
is what authenticates: the service has none of its own, and this is the deployment that supplies it. Do
not expose the port directly.
--root-path is what makes the prefix work. The service answers both at /api/runs and under the
prefix, and the front end asks for relative URLs, so one build serves either way.
What it reads
A run directory is the unit. It holds the files the CLI already writes:
File |
What the surface uses it for |
|---|---|
|
the keyword set, shown read-only |
|
the tree list as written |
|
the output database: the primary surface |
|
the fixed-width summary, as FVS wrote it |
|
the parsed summary, for the trajectory chart |
|
the PyFVS commit, the FVS source commit, the variant list |
|
the build’s variant list |
A run is complete once summary.csv exists and running until then. The service never writes into
a run directory, with one exception: starting a run creates a new one through the CLI.
The stand visuals
The visuals panel draws the stand from two tables in FVSOut.db:
FVS_TreeList_Metricis the live stand, one row per tree per period.FVS_CutList_Metricmarks the trees a treatment removed. It is optional: a run that cut nothing does not write it, and the view then shows no cut trees rather than inventing them.
The 2D map and the 3D viewer share one payload from GET /api/runs/{id}/visuals. The map is a
canvas; the 3D view is three.js with orbit and zoom. Both are read-only. Neither is a map in the
geographic sense:
FVS writes no x/y coordinates. The layout is schematic: each plot (
PtIndex) gets a square cell, and its trees are spaced on a stable golden-angle spiral ordered byTreeIndex. The same rows always produce the same picture.The 3D ground is flat. The per-tree tables carry no terrain, so the viewer draws a ground plane and a grid rather than a surface it cannot support.
Every displayed value comes from the run: species, DBH, height, TPH, crown ratio, the year, and whether a tree is in the cut list. Only the arrangement is computed.
The three.js runtime is vendored under static/vendor/three/ with its MIT licence, and loaded
only when a caller chooses the 3D view. The 2D map needs no WebGL, and the main shell stays light.
The oracle flag
This is the reason the surface exists. Two independent sources check the exposed tables, and the broader one is not the obvious one:
Four are checked against FVS’s own database dump — the tables the official case ships.
Twenty-eight are checked against a standalone ``FVSbc`` built from the same source, and agree value for value. That covers every table either build writes, not only the four the dump carries.
FVS_Regen_SitePrepis not among them: its site-preparation allocation differs, for a reason that is not this package’s.FVS_Erroris not either: FVS writes it only when it logs something, so the two builds cannot be made to agree on its presence.
GET /api/oracle reports both counts and both table lists, every table record carries oracle — which
means checked against a reference, either one — and the front end marks an unverified table in the table
list and above its rows. The badge leads with the standalone count, because reporting the dump’s four
alone would understate the surface by a factor of seven.
A number whose provenance is unchecked must not look like one whose provenance is checked. Output contract says the same thing in prose; this surface says it on screen.
Skins
A skin is a JSON manifest listing the panels and actions a role gets. Four ship: analyst, carbon,
reviewer and tables. The loader validates every panel and action against a registry and refuses an
unknown name rather than dropping it, because a manifest that silently loses a panel lies about what a
role can do. read_only removes every mutating action. The analyst and reviewer skins carry
the visuals panel; the reviewer sees the same stand read-only.
Endpoints
GET /api/skinsthe shipped manifests
GET /api/oraclehow many exposed tables have an independent check, against the dump and against a standalone
GET /api/runsrun directories under the roots, with state
GET /api/runs/{id}/summarysummary.csvrowsGET /api/runs/{id}/tablesexposed tables written, with counts and the flag
GET /api/runs/{id}/tables/{name}one table, paged, with its columns
GET /api/runs/{id}/visuals?year=the schematic stand payload: layout, trees, cut trees, species and the year’s summary row
GET /api/runs/{id}/keywordsrun.keyas textGET /api/runs/{id}/provenancethe pins and the variant list
POST /api/runsstart a run by invoking the CLI
GET /api/runs/{id}/logthe log the service captured, which is where a failed run’s traceback is
GET /api/runs/{id}/eventsan SSE stream, or 204 when the run is done
A table the reader does not expose is refused with its name, and so is a table the run did not write. An absent table is never rendered as an empty one.
Starting a run
POST /api/runs takes population, species, tree_age, an optional id, and keywords: an
ordered list in which an entry carrying | is a block (NAME|LINE|LINE) and anything else is a
keyword (NAME=FIELD,...).
Order is part of the contract. A DATABASE block must precede the stand setup, and FMIN, MISTOE,
RDIN and ESTAB follow it — so the list is passed through in the order given rather than grouped.
Without a DATABASE block a run writes FVS_Cases and FVS_Error and nothing else, which is the
first surprise a caller meets.
A run whose child died is reported as failed, and its traceback is at
GET /api/runs/{id}/log. A run the service did not start has no log and reports an empty body.
Not verified
No authentication, and no plan for one in this surface. That is why a remote bind is refused.
No live progress. The event stream reports what the run directory holds — running, then complete — and does not invent a cycle count the child did not write.
No geographic map. The 2D and 3D views are a schematic stand view over the per-tree tables. They carry no coordinates, no block polygon, no road, and no land base. The ws4 and fhops front ends have a map because their artefacts are spatial; FVS’s per-tree tables are not.
No 3D editing. The viewer orbits and zooms; it does not move a tree, paint terrain, or write a project. That is the Scenario Studio’s job, over its own artefacts.