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

run.key

the keyword set, shown read-only

run.tre

the tree list as written

FVSOut.db

the output database: the primary surface

run.sum

the fixed-width summary, as FVS wrote it

summary.csv

the parsed summary, for the trajectory chart

provenance.json

the PyFVS commit, the FVS source commit, the variant list

variants.json

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_Metric is the live stand, one row per tree per period.

  • FVS_CutList_Metric marks 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 by TreeIndex. 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_SitePrep is not among them: its site-preparation allocation differs, for a reason that is not this package’s. FVS_Error is 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/skins

the shipped manifests

GET /api/oracle

how many exposed tables have an independent check, against the dump and against a standalone

GET /api/runs

run directories under the roots, with state

GET /api/runs/{id}/summary

summary.csv rows

GET /api/runs/{id}/tables

exposed 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}/keywords

run.key as text

GET /api/runs/{id}/provenance

the pins and the variant list

POST /api/runs

start a run by invoking the CLI

GET /api/runs/{id}/log

the log the service captured, which is where a failed run’s traceback is

GET /api/runs/{id}/events

an 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.