Skip to content

Metadata mappings

Metadata mappings let you populate controlled metadata properties directly from the uncontrolled metadata already attached to a document — deterministically, without calling a language model. You define a list of rules inline in your workflow; where a rule's condition is met, it writes a predefined value into the document's results.

This is handled by the MetadataMapper processing unit (processors.extractors.MetadataMapper). Rules are evaluated at runtime against DocumentVersion.metadata().

When to use this

Documents from source systems often already carry governed information in their native fields — for example a ProjectWise environment attribute env_LOCATOR_CODE = "L01". If a field reliably encodes a classification, mapping it with a rule is faster, free and fully auditable compared with asking a classifier to re-derive it.

Uncontrolled metadata is offered up as-is, with no guarantees on its veracity (see Documents). Mapping rules are the deterministic bridge from those raw fields to your governed standard.

Rule structure

A rule pairs a condition (tested against one or more source metadata fields) with one or more writes (the controlled properties to set when the condition is met).

{
    "condition": { <condition-group-or-leaf> },
    "writes": [ { "target_id": "<property-id>", "value": <payload> } ],
    "certainty": "high",
    "explanation": "Mapped from existing document metadata"
}
Attribute Required Type Description
condition Yes Object A single leaf condition, or a group of leaves combined with all/any. See Conditions.
writes Yes* Array One or more { target_id, value } writes applied when the condition matches. See Writes.
certainty String Default certainty for this rule's writes. Defaults to "high".
explanation String Default explanation stamped on this rule's writes. Defaults to "Mapped from existing document metadata".

Single-write shorthand

Instead of a writes array you may put target_id and value directly on the rule for the common one-property case. The two forms below are equivalent:

{ "condition": {...}, "target_id": "doc-type", "value": { "code": "CO", "description": "Correspondence" } }
{ "condition": {...}, "writes": [ { "target_id": "doc-type", "value": { "code": "CO", "description": "Correspondence" } } ] }

Writes

Each write names the property to set and the value to write.

Attribute Required Type Description
target_id Yes String The id of the controlled MetadataProperty to write.
value Yes Object or String The predefined value. For a classification property use { "code": ..., "description": ... }; for a freetext/text property use a string. String fields may contain regex capture references.
certainty String Overrides the rule-level certainty for this write.
explanation String Overrides the rule-level explanation for this write.

Conditions

A leaf condition binds one source metadata key (source_id) to one test (type). The available tests:

type Fields Matches when
regex pattern pattern is found anywhere in the (stringified) metadata value. Matching is case-insensitive. Capture groups can feed the written value.
numeric_range min, max, inclusive The value parses as a number and falls within the bound(s). min and max are each optional.
equals value The metadata value equals value (compared as strings).
one_of values The metadata value is one of values (compared as strings).
exists The source_id key is present at all (any value, including empty).
{ "source_id": "env_LOCATOR_CODE", "type": "regex",         "pattern": "L\\d\\d" }
{ "source_id": "env_REVISION",     "type": "numeric_range", "min": 0, "max": 100, "inclusive": "both" }
{ "source_id": "env_STATUS",       "type": "equals",        "value": "A" }
{ "source_id": "env_LOCATOR_CODE", "type": "one_of",        "values": ["L01", "L02"] }
{ "source_id": "env_LOCATOR_CODE", "type": "exists" }

A leaf whose source_id is not present in the document's metadata simply does not match. Regex matching is case-insensitive — L\d\d matches both L07 and l07.

Numeric bounds and inclusivity

For numeric_range, the inclusive field controls which bounds are closed:

inclusive Interval Meaning
"both" (default) [min, max] Both bounds inclusive.
"min" [min, max) Lower inclusive, upper exclusive.
"max" (min, max] Lower exclusive, upper inclusive.

There is deliberately no option for a fully open interval — at least one bound is always inclusive. Omit min or max for a one-sided range.

Combining conditions across fields

To test more than one source field, wrap leaves in a group with a match combinator:

Attribute Required Type Description
match String "all" (every leaf must match — AND) or "any" (at least one — OR). Defaults to "all".
conditions Yes Array The leaf conditions to combine.
{
    "match": "all",
    "conditions": [
        { "source_id": "env_LOCATOR_CODE", "type": "regex",  "pattern": "L\\d\\d" },
        { "source_id": "env_STATUS",       "type": "equals", "value": "A" }
    ]
}

Single condition

A rule's condition may be a lone leaf (no match/conditions wrapper) — it behaves as a group of one.

Capturing segments with regex

A regex condition can capture part of the source value and splice it into the written value. Reference captures with Python backreference syntax — numbered (\1, \2) or named (\g<name>):

{
    "condition": { "source_id": "env_LOCATOR_CODE", "type": "regex", "pattern": "L(?P<seq>\\d\\d)" },
    "writes": [
        { "target_id": "doc-type", "value": { "code": "CO-\\g<seq>", "description": "Correspondence \\g<seq>" } }
    ]
}

With env_LOCATOR_CODE = "L07" this writes { "code": "CO-07", "description": "Correspondence 07" }.

Use backslashes, not $, for capture references

Capture references use \1 / \g<name> (written as "\\1" / "\\g<name>" in JSON). Do not use $1 — the workflow runner resolves any $-prefixed string as a context variable before the rule is evaluated, so a $-style reference would be lost.

Multiple regex fields → use named groups

When a rule conditions on more than one regex field, numbered backreferences are ambiguous. Use named groups ((?P<name>...)\g<name>) so references resolve unambiguously across fields.

Ordering: last write wins

Rules are evaluated top to bottom. When more than one write targets the same property, the one that appears later in the list wins. Writes to different properties all apply. This lets you layer a specific override beneath a general default:

[
    { "condition": { "source_id": "env_LOCATOR_CODE", "type": "exists" },
      "target_id": "doc-type", "value": { "code": "UN", "description": "Unclassified" } },

    { "condition": { "source_id": "env_LOCATOR_CODE", "type": "regex", "pattern": "L\\d\\d" },
      "target_id": "doc-type", "value": { "code": "CO", "description": "Correspondence" } }
]

Here any document with a locator code starts as Unclassified, but one matching L\d\d ends up as Correspondence because that rule is lower in the list.

Respecting existing results

The mapper reuses the standard use_persisted_results flag, but for this unit it defaults to true:

use_persisted_results Behaviour
true (default) Respect existing. A write only lands on a property that has no value yet. Values from a previous run — or written by an earlier stage of this run — are left untouched.
false Overwrite. Writes always land, replacing any existing value for that property.

Set it via the step's instance_params.

Stage ordering matters

"Existing" means whatever is in the document's results when the mapper runs. Because steps within a stage run in parallel, place MetadataMapper in its own stage relative to any unit whose results it should respect or override.

Validation

If a target_id refers to a classification property, the written code is validated against that property's allowed values. An invalid code is still written — so it surfaces during QC — but flagged as not format-valid. Writes to freetext/text properties are not code-validated.

A target_id must resolve to a property your standard knows about (a governed property or a custom column). If it doesn't, that write is skipped with a warning rather than creating an ungoverned value — and if a rule has no valid targets left, the rule is skipped entirely without evaluating its condition.

Full example

A stage that applies two rules — a multi-field, multi-target rule and a simpler override:

{
    "name": "Map metadata",
    "steps": [
        {
            "name": "Apply metadata mapping rules",
            "class": "processors.extractors.MetadataMapper",
            "method": "run",
            "instance_params": { "use_persisted_results": "$use_persisted_results" },
            "call_params": {
                "document_version": "$connection.document_version",
                "session": "$connection.session",
                "rules": [
                    {
                        "condition": {
                            "match": "all",
                            "conditions": [
                                { "source_id": "env_LOCATOR_CODE", "type": "regex",  "pattern": "L\\d\\d" },
                                { "source_id": "env_STATUS",       "type": "equals", "value": "A" }
                            ]
                        },
                        "writes": [
                            { "target_id": "doc-type", "value": { "code": "CO",  "description": "Correspondence" } },
                            { "target_id": "status",   "value": { "code": "WIP", "description": "Work in progress" } }
                        ],
                        "certainty": "high",
                        "explanation": "Mapped from existing document metadata"
                    },
                    {
                        "condition": { "source_id": "env_LOCATOR_CODE", "type": "one_of", "values": ["D01", "D02"] },
                        "target_id": "doc-type",
                        "value": { "code": "DR", "description": "Drawing" }
                    }
                ]
            }
        }
    ]
}