Patchworks Export Contract
==========================
``femic export patchworks`` writes two artifacts:
- ``forestmodel.xml``
- ``fragments/fragments.shp`` (plus shapefile sidecars)
Terminology:
- A fragment shapefile row is one stand-fragment record.
- In this exporter, ``BLOCK`` is one-to-one with fragment rows (one fragment per
block id).
Patchworks semantics guardrail
------------------------------
Before reading the rest of this page, keep this separation fixed:
- ``managed`` / ``unmanaged`` means Patchworks treatment eligibility;
- ``natural`` / ``treated`` (or equivalent origin labels) means curve
provenance; and
- retention is an orthogonal area-reallocation surface.
Do not use curve-family availability, ``hasfg``, or any first-growth versus
plantation shortcut as a proxy for IFM. If a model needs both IFM and origin,
it should publish both explicitly. The compact repo-wide statement of this
contract lives at :doc:`contracts/patchworks-model-semantics`.
ForestModel XML requirements
----------------------------
The exporter now enforces these required structure elements before writing XML:
- Root tag: ``ForestModel``
- Root attributes: ``horizon``, ``year``, ``match``
- `` `` node with attributes: ``block``, ``area``, ``age``
- ```` node
- ````
- ````
- ````
- ````
- ````
- A ``unity`` curve with at least one point
- At least one ````
- Every ```` must reference an existing ````
Species-wise yield curves
-------------------------
For each AU/IFM species proportion curve, FEMIC now also emits a derived
species-yield curve:
- unmanaged: ``feature.Yield.unmanaged.``
- managed: ``feature.Yield.managed.`` and ``product.Yield.managed.``
For CC treatment consequences, managed product attributes now also include:
- total harvested volume: ``product.HarvestedVolume.managed.Total.CC``
- species harvested volume: ``product.HarvestedVolume.managed..CC``
Derived species-yield points are computed as:
``TotalVolume(age) * SpeciesProportion(age)``
where species proportions are evaluated at each total-curve age using constant or
piecewise-linear interpolation of the source species-proportion curve.
To reduce XML size/noise, serializer output trims redundant far-left and far-right
points when a curve starts/ends with repeated y-values; Patchworks extends
terminal points horizontally by default.
Curve IDs are emitted as readable tokens (for example
``managed_total__``, ``managed_prop___``,
``au__managed_yield_``) while remaining unique within the XML
file.
In these readable surfaces, ```` means the deterministic
Patchworks-safe AU token derived from ``stratum_code`` + ``si_level`` (for
example ``CWHvm_HW_FDC_H``). Operator characters such as ``+`` and ``-`` are
sanitized because Patchworks parses account labels as expressions rather than
free-text strings. When the same readable AU token would otherwise collide
across FMU/code targets, FEMIC prefixes the case code to keep the label
unique. The underlying compatibility field/normalizer still uses legacy
``tsa`` naming in parts of the runtime.
CC treatment minimum age is now resolved per AU as:
``CMAI(managed_total_curve) - 20``
where CMAI is the age with maximum mean annual increment
(``managed_volume(age) / age``) on the managed total-yield curve. The result is
clamped to ``[0, --cc-max-age]``.
Seral-stage attributes (optional)
---------------------------------
When ``--seral-stage-config`` is provided, the exporter emits per-AU binary
seral curves and binds these attributes:
- ``feature.Seral.regenerating``
- ``feature.Seral.young``
- ``feature.Seral.immature``
- ``feature.Seral.mature``
- ``feature.Seral.overmature``
- ``feature.Seral..regenerating``
- ``feature.Seral..young``
- ``feature.Seral..immature``
- ``feature.Seral..mature``
- ``feature.Seral..overmature``
- CC-treatment consequence area accounts by stage/AU:
``product.Seral.area...CC``
The global ``feature.Seral.`` labels remain in place for compatibility
and summary surfaces. The AU-specific ``feature.Seral..``
labels are the per-AU inventory-state surface.
Default boundaries are derived per AU from managed total-yield CMAI and peak
yield age:
- regenerating: ``0-5``
- young: ``6-25``
- immature: ``26-CMAI`` (CMAI floor of 25 applied for ordering stability)
- mature: ``CMAI+1`` to ``min(peak_yield_age, 200)``
- overmature: ``mature_upper+1`` and older
YAML supports optional per-AU stage overrides:
.. code-block:: yaml
default:
mature:
max_age: min_peak_or_200
au_overrides:
"985501000":
mature:
max_age: 170
overmature:
min_age: 171
Recognized token values for ``min_age``/``max_age`` are:
``cmai``, ``cmai_plus_1``, ``peak_yield_age``, ``min_peak_or_200``,
``mature_plus_1``.
Retention modulator (optional per fragment)
--------------------------------------------
The exporter now supports a per-fragment retention scalar:
- ``RETENTION = 0.0``: no retained area
- ``RETENTION = 1.0``: fully retained fragment area
- intermediate values: partial retained area
Current export behavior:
- fragments carry a numeric ``RETENTION`` field
- managed select statements emit:
````
- the retained share is reassigned to ``IFM='unmanaged'``
- ``ORIGIN`` is left unchanged on the retained portion
This keeps retention orthogonal to both:
- ``IFM`` as the managed/unmanaged regime field, and
- ``ORIGIN`` as the natural/planted composition field
This is intentional. Retention may change treatment eligibility without
changing curve provenance.
When ``RETENTION = 0.0`` everywhere, retention wiring is present but behavior is
unchanged relative to the pre-retention model.
Point formatting policy:
- ``x``: integer age strings when integral (default case)
- ``y`` for volume-yield curves (``managed_total_*``, ``unmanaged_total_*``,
``au_*_..._yield_*``): rounded to 1 decimal place
- ``y`` for normalized/proportion curves: rounded to at most 5 decimals
Fragments shapefile requirements
--------------------------------
The exporter validates these required fields before writing:
- ``BLOCK``: integer block ID (non-negative). A block may have one row
(one stand-fragment per block)
- ``AREA_HA``: numeric area in hectares (strictly positive)
- ``F_AGE``: numeric forest age (non-negative)
- ``AU``: numeric analysis-unit ID (non-negative)
- ``IFM``: management mode, one of ``managed`` or ``unmanaged``
- ``ORIGIN``: stand origin state, one of ``natural`` or ``planted``
- ``RETENTION``: retention factor in ``[0.0, 1.0]``
- ``TSA``: legacy case/FMU code label field retained for compatibility
- ``geometry``: non-null, non-empty geometry
Managed/unmanaged assignment:
- Exporter now supports two managed/unmanaged assignment modes:
- ``proportional`` (default):
interprets the THLB signal as a continuous managed-area share and carries
the complementary unmanaged share through ``RETENTION``;
- ``legacy_binary``:
preserves the older threshold/share-based stand snap.
- In ``proportional`` mode, exporter prefers continuous THLB sources in this
order:
``thlb_fact``, then ``thlb_raw``, then ``thlb_area``, then ``thlb``.
- In ``legacy_binary`` mode, exporter preserves the historical priority order:
``thlb`` (0/1), then ``thlb_fact`` (>0), then ``thlb_area`` (>0), then
``thlb_raw`` (>0).
- If no THLB signal is present, exporter defaults to ``managed``.
- In proportional mode, percent-style THLB signals greater than ``1.0`` are
interpreted as ``0..100`` percentages and normalized to ``0..1``.
- You can override the source column with ``--ifm-source-col``.
- ``--ifm-mode proportional`` is now the default and keeps continuous THLB
share rather than snapping stands immediately to ``{0,1}``.
- ``--ifm-threshold `` marks stands as managed when source value exceeds
the threshold in ``legacy_binary`` mode.
- ``--ifm-target-managed-share `` marks top-N stands as managed to hit
the requested stand-count share in ``legacy_binary`` mode.
- ``--ifm-threshold`` and ``--ifm-target-managed-share`` are mutually
exclusive, and both are only valid when ``--ifm-mode legacy_binary`` is in
effect.
Important semantic boundary:
- IFM assignment chooses the managed versus unmanaged lane only.
- It does not, by itself, choose the natural versus treated curve family.
- Natural/treated origin should come from the case's reviewed origin contract,
not from THLB or other IFM signals.
The fragments dataset must also carry a CRS.
CLI usage
---------
Basic usage:
.. code-block:: bash
PYTHONPATH=src python -m femic export patchworks --tsa k3z
Useful overrides:
- ``--bundle-dir``: alternate bundle source (``au_table.csv``, ``curve_table.csv``, ``curve_points_table.csv``)
- ``--checkpoint``: alternate stand checkpoint feather (must include geometry,
legacy ``TSA`` case code field, AU, and age)
- ``--output-dir``: export destination
- ``--start-year``, ``--horizon-years``, ``--cc-min-age``, ``--cc-max-age``,
``--cc-transition-ifm``, ``--fragments-crs``, ``--seral-stage-config``
- ``--ifm-mode``, ``--ifm-source-col``, ``--ifm-threshold``,
``--ifm-target-managed-share``
Transition note:
- By default, CC tracks do not write an IFM transition assignment.
- If ``--cc-transition-ifm unmanaged`` is provided, CC treatment writes
`` ``.
- ``--cc-transition-ifm managed`` is accepted but omitted from XML because it is
redundant within managed-only select statements.