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.