Skip to content

Editing databases

Most of what VoLCA reads is background data: Agribalyse, ecoinvent, BAFU. Those are shared by everything running on the engine, so nothing writes to them. What you author goes into a database of your own - one you uploaded, or a copy you made - and references the background through the usual cross-database links.

DatabaseWriting, editing an inventoryDeleting activitiesSaved
Configured in the engine’s TOMLrefusedin memory only-
Uploaded, whatever its formatyesyesto a journal beside its files
A copy of eitheryesyesto a journal of its own

“Copy it first” is therefore about the background data the engine is configured with, which every caller shares. A database you uploaded is already yours: it takes an edit directly, and keeps it. If you want a draft rather than a change, copy it because you want a draft - nothing refuses the edit otherwise.

An edit never rewrites the files you uploaded. It is recorded in a journal beside them, and loading the database replays the journal over what the files say. That is what makes every format editable on the same terms: EcoSpold 1, SimaPro CSV, ILCD and Excel derive each process identity from names, categories and the position of a dataset in its file, so rewriting them would have moved every identity at the next read. A file that is never rewritten parses to the same identifiers forever.

A copy shares the source’s value without duplicating its files: it keeps its own identity and its own journal, and reads the files of the database it was copied from. For that reason the source cannot be deleted while a copy of it exists.

Exporting is unaffected: an export is a snapshot that leaves the original in place, and every writer accepts every database.

An instance can be configured read-only, and then it answers every question and changes nothing: writing an activity comes back as a 403, as loading, uploading and deleting already do. That is what a shared instance is - one engine several accounts read at once, where any one caller writing would write for all of them. The free tier is exactly that, so authoring starts on a tier with an instance of its own.

A copy is a database of your own, so it spends the same budget an upload does: one slot of what the plan can store, and one of what it can hold in memory. If a copy is refused, the message says which of the two ran out.

You never supply a process_id. The engine derives it from four things you do supply - activity name, location, product name, product unit - so writing the same description twice addresses the same row.

That is what makes correcting an activity a correction rather than a duplicate. It also means changing any of those four describes a different activity: to rename one, write the new one and delete the old.

An activity that came out of a file cannot be re-described. Its identity was minted by the parser that read it, from that file’s names and categories, so a description written by hand addresses a different row - and a description carries less than the file did anyway: classification, synonyms, parameters, pedigree, allocation and coproducts have nowhere to go in it.

So an imported activity is changed by naming only the lines that move:

Terminal window
POST /api/v1/db/my-copy/activity/{process_id}/exchanges
{"remove": [{"kind": "biosphere", "flow": "{flow-id}"}],
"setAmounts": [{"select": {"kind": "input", "provider": "{process-id}"},
"amount": 2.5}],
"addInputs": [{"provider": "{process-id}", "amount": 1.2}]}

Everything the request does not name stays exactly as it was. Only the inventory side is addressable - an input by its supplier, a waste output by its treatment, a flow of nature by its flow - because the reference product and the coproducts are what the activity is, not what it exchanges.

A selector that matches nothing is refused rather than quietly doing nothing, and one that matches several lines changes all of them and says how many. The answer counts what happened per selector: {"removed": [2], "amountsSet": [1], "added": 1, "transient": false, "warnings": []}.

The same edit is a call in Python (client.edit_exchanges(...)), a command (volca database edit-exchanges), a tool an assistant can use (edit_exchanges), and, in the web interface, the Edit exchanges button on each of an activity’s four exchange tabs: rows gain an amount and a remove control, and the tab’s own add button opens a picker for the kind of line that tab lists.

Importing a 20 000-dataset file is tolerant on purpose: a row whose supplier does not resolve is warned about and dropped, because refusing the whole file over one line helps nobody. Authoring is the opposite situation - you are here, the batch is small, and every defect is fixable on the spot. So:

  • a supplier that does not resolve is an error, not a dropped line;
  • an amount that is not a finite non-zero number is an error;
  • a unit that cannot be converted into the supplier’s is an error;
  • a biosphere exchange must be stated in its flow’s own unit, because the biosphere matrix carries amounts through unconverted.

Everything wrong with a batch comes back at once, each complaint naming the activity and the exchange it belongs to, and nothing is written until it all passes.

A biosphere flow you introduce comes back as a warning rather than an error: no characterization factor matches a brand-new flow by identity, so it scores as zero in every method until one is mapped to it.

Starting from a row the database already holds is the common case, and reading one back gives you less than writing one takes. An input hands back the process that supplies it, which is exactly what a written input names. A biosphere exchange hands back its flow’s name and compartment, while a written one names the flow by identifier - so an inventory read cannot be restated whole without matching flows by name, which is a guess.

Until reading hands the identifier back, derive on the technosphere inputs and say what was left behind. And compare like with like: a derivative missing the source’s emissions scores lower for that reason alone, so the honest baseline is the same inventory written the same way, without the substitution.

from volca import ActivityInput, Client, TechInput
client = Client(db="my-database")
client.copy_database("my-database-draft")
written = client.create_activities(
ActivityInput(
name="yogurt, at dairy",
location="FR",
product_name="yogurt",
product_amount=1.0,
product_unit="kg",
inputs=[TechInput(provider="5b0f…_c16c…", amount=1.2)],
),
db_name="my-database-draft",
)
print(written["written"]) # the process ids you can now ask about

replace_activity(process_id, activity) rewrites one that is already there. Both return {"written", "transient", "warnings"}; transient is true when the edit lives in memory only.

The same JSON document works over either transport:

Terminal window
volca database create-activities my-database-draft --from activities.json
volca database replace-activity my-database-draft --process-id 5b0f…_c16c… --from one.json

volca/examples/derive_activity/ copies a database, writes an existing activity into the copy twice - as it stands, and with a single supplier substituted - scores the two, and prints the per-category change.