#!/usr/bin/env python3
"""LiNK field catalogue — GENERATED, DO NOT HAND-EDIT.

Source of truth: link_build_manual_6_20260812.xlsx
Manual revision: LiNK REAL ESTATE MARKETPLACE — DRUPAL 11.4 BUILD GUIDE — v6 (2026-08-12)
Regenerate:      python3 make-field-map.py

Every machine name the Drupal build manual defines appears exactly once below.
The page generators render from this catalogue and verify-fields.py asserts
that each entry reaches the built HTML, so the prototype cannot quietly fall
behind a manual revision.

Record shape:

    entity       property_listing | bid | service_offering | profile_* | paragraph_*
    group        the manual's Step/Section column, verbatim
    label        the manual's field label, verbatim
    machine      Drupal field machine name — the identity used by verify-fields
    card         "1" or "N"
    type         the manual's Field Type column
    values       allowed list values parsed out of Notes ([] when not a list)
    vocab        vocabulary label for term references, else None
    required     manual says Required
    hidden       not on the form (computed / ECA-written)
    private      None | "seller" (hidden from buyers) | "admin"
    conditional  the manual's Conditional clause, else None
    money        render with a $ prefix
"""

REVISION = 'LiNK REAL ESTATE MARKETPLACE — DRUPAL 11.4 BUILD GUIDE — v6 (2026-08-12)'
SOURCE = 'link_build_manual_6_20260812.xlsx'

FIELDS = [
    {'entity': 'profile_buyer', 'group': 'Buyer', 'label': 'Profile photo', 'machine': 'field_profile_photo', 'card': '1', 'type': 'Media ref (Image). Shared across all profiles.', 'notes': 'Media ref (Image). Shared across all profiles.', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_buyer', 'group': 'Buyer', 'label': 'Phone', 'machine': 'field_phone', 'card': '1', 'type': 'Telephone. Shared field.', 'notes': 'Telephone. Shared field.', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_buyer', 'group': 'Buyer', 'label': 'Preferred contact method', 'machine': 'field_contact_method', 'card': 'N', 'type': 'List (text). Checkboxes: system_message | email | sms | phone', 'notes': 'List (text). Checkboxes: system_message | email | sms | phone', 'values': ['system_message', 'email', 'sms', 'phone'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_buyer', 'group': 'Buyer', 'label': 'Preferred property types', 'machine': 'field_pref_property_types', 'card': 'N', 'type': 'Term ref → Property Type. Checkboxes.', 'notes': 'Term ref → Property Type. Checkboxes.', 'values': [], 'vocab': 'Property Type', 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_buyer', 'group': 'Buyer', 'label': 'Preferred locations', 'machine': 'field_pref_locations', 'card': 'N', 'type': 'Term ref → Location. Autocomplete.', 'notes': 'Term ref → Location. Autocomplete.', 'values': [], 'vocab': 'Location', 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_buyer', 'group': 'Buyer', 'label': 'Budget minimum ($)', 'machine': 'field_budget_min', 'card': '1', 'type': 'Number (integer). Prefix: $', 'notes': 'Number (integer). Prefix: $', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': True},
    {'entity': 'profile_buyer', 'group': 'Buyer', 'label': 'Budget maximum ($)', 'machine': 'field_budget_max', 'card': '1', 'type': 'Number (integer). Prefix: $', 'notes': 'Number (integer). Prefix: $', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': True},
    {'entity': 'profile_buyer', 'group': 'Buyer', 'label': 'Preferred bedrooms (min)', 'machine': 'field_pref_beds', 'card': '1', 'type': 'Number (integer)', 'notes': 'Number (integer)', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_buyer', 'group': 'Buyer', 'label': 'Preferred bathrooms (min)', 'machine': 'field_pref_baths', 'card': '1', 'type': 'Number (decimal, 1 dp)', 'notes': 'Number (decimal, 1 dp)', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_buyer', 'group': 'Buyer', 'label': 'Email alerts opt-in', 'machine': 'field_optin_email', 'card': '1', 'type': 'Boolean. Default: checked', 'notes': 'Boolean. Default: checked', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_buyer', 'group': 'Buyer', 'label': 'SMS alerts opt-in', 'machine': 'field_optin_sms', 'card': '1', 'type': 'Boolean. Default: unchecked (SMS costs $$)', 'notes': 'Boolean. Default: unchecked (SMS costs $$)', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_seller', 'group': 'Seller', 'label': 'Profile photo', 'machine': 'field_profile_photo', 'card': '1', 'type': 'Media ref (Image). SHARED FIELD.', 'notes': 'Media ref (Image). SHARED FIELD.', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_seller', 'group': 'Seller', 'label': 'Phone', 'machine': 'field_phone', 'card': '1', 'type': 'Telephone. SHARED FIELD.', 'notes': 'Telephone. SHARED FIELD.', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_seller', 'group': 'Seller', 'label': 'Contact address', 'machine': 'field_contact_address', 'card': '1', 'type': 'Address', 'notes': 'Address', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_seller', 'group': 'Seller', 'label': 'Show contact info to buyers', 'machine': 'field_show_contact', 'card': '1', 'type': 'Boolean. Required. Controls Bid page display.', 'notes': 'Boolean. Required. Controls Bid page display.', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_seller', 'group': 'Seller', 'label': 'Allowed contact channels', 'machine': 'field_contact_method', 'card': 'N', 'type': 'List (text). SHARED FIELD. Checkboxes: system_message | email', 'notes': 'List (text). SHARED FIELD. Checkboxes: system_message | email', 'values': ['system_message', 'email'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_provider', 'group': 'Provider', 'label': 'Business name', 'machine': 'field_sp_business_name', 'card': '1', 'type': 'Text (plain). Required', 'notes': 'Text (plain). Required', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_provider', 'group': 'Provider', 'label': 'Business logo/photo', 'machine': 'field_profile_photo', 'card': '1', 'type': 'Media ref (Image). SHARED FIELD', 'notes': 'Media ref (Image). SHARED FIELD', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_provider', 'group': 'Provider', 'label': 'Phone', 'machine': 'field_phone', 'card': '1', 'type': 'Telephone. SHARED FIELD. Required', 'notes': 'Telephone. SHARED FIELD. Required', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_provider', 'group': 'Provider', 'label': 'Business address', 'machine': 'field_contact_address', 'card': '1', 'type': 'Address. SHARED FIELD. Required', 'notes': 'Address. SHARED FIELD. Required', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_provider', 'group': 'Provider', 'label': 'Years in business', 'machine': 'field_sp_years_experience', 'card': '1', 'type': 'Number (integer)', 'notes': 'Number (integer)', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_provider', 'group': 'Provider', 'label': 'Company history', 'machine': 'field_sp_history', 'card': '1', 'type': 'Text (long)', 'notes': 'Text (long)', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_provider', 'group': 'Provider', 'label': 'Certifications/licenses', 'machine': 'field_sp_credentials', 'card': 'N', 'type': 'Paragraph → credential', 'notes': 'Paragraph → credential', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_provider', 'group': 'Provider', 'label': 'License number', 'machine': 'field_sp_license_number', 'card': '1', 'type': 'Text (plain)', 'notes': 'Text (plain)', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_provider', 'group': 'Provider', 'label': 'Insured?', 'machine': 'field_sp_insured', 'card': '1', 'type': 'Boolean. Required', 'notes': 'Boolean. Required', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_provider', 'group': 'Provider', 'label': 'Insurance coverage ($)', 'machine': 'field_sp_insurance_amount', 'card': '1', 'type': 'Number (integer). Conditional: if insured=Yes. Prefix: $', 'notes': 'Number (integer). Conditional: if insured=Yes. Prefix: $', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': 'yes', 'money': True},
    {'entity': 'profile_provider', 'group': 'Provider', 'label': 'Verified by LiNK', 'machine': 'field_sp_verified', 'card': '1', 'type': 'Boolean. FIELD PERMISSIONS: Admin-only edit', 'notes': 'Boolean. FIELD PERMISSIONS: Admin-only edit', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': 'admin', 'conditional': None, 'money': False},
    {'entity': 'profile_provider', 'group': 'Provider', 'label': 'Communication methods', 'machine': 'field_sp_comm_methods', 'card': 'N', 'type': 'List (text). Required. Values: text | phone | email | app', 'notes': 'List (text). Required. Values: text | phone | email | app', 'values': ['text', 'phone', 'email', 'app'], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'profile_provider', 'group': 'Provider', 'label': 'Subscription status', 'machine': 'field_sp_subscription', 'card': '1', 'type': 'List (text). Values: none | active | past_due | canceled. Admin-only, Stripe-synced', 'notes': 'List (text). Values: none | active | past_due | canceled. Admin-only, Stripe-synced', 'values': ['none', 'active', 'past_due', 'canceled'], 'vocab': None, 'required': False, 'hidden': False, 'private': 'admin', 'conditional': None, 'money': False},
    {'entity': 'profile_provider', 'group': 'Provider', 'label': 'Advertising tier', 'machine': 'field_sp_ad_tier', 'card': '1', 'type': 'List (text). Values: none | promoted. Drives search boost.', 'notes': 'List (text). Values: none | promoted. Drives search boost.', 'values': ['none', 'promoted'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'STEP 1: SNAPSHOT', 'label': 'Property address', 'machine': 'field_prop_address', 'card': '1', 'type': 'Address (module address)', 'notes': 'Required, validated, geocoded to field_geolocation', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 1', 'label': 'Geolocation', 'machine': 'field_geolocation', 'card': '1', 'type': 'Geofield', 'notes': 'Hidden from form; auto-populated by Geocoder. Powers maps.', 'values': [], 'vocab': None, 'required': False, 'hidden': True, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 1', 'label': 'Property type', 'machine': 'field_prop_type', 'card': '1', 'type': 'Term ref → Property Type', 'notes': 'Required, Select dropdown', 'values': [], 'vocab': 'Property Type', 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 1', 'label': 'Square footage', 'machine': 'field_prop_sqft', 'card': '1', 'type': 'Number (integer)', 'notes': "Required, Suffix 'sq ft'", 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 1', 'label': 'Number of levels', 'machine': 'field_prop_levels', 'card': '1', 'type': 'Number (integer)', 'notes': 'Required', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 1', 'label': 'Lot size (acres)', 'machine': 'field_prop_lot_size', 'card': '1', 'type': 'Number (decimal, 2 dp)', 'notes': 'Required', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 1', 'label': 'Bedrooms', 'machine': 'field_prop_beds', 'card': '1', 'type': 'Number (integer)', 'notes': 'Required', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 1', 'label': 'Full bathrooms', 'machine': 'field_prop_baths_full', 'card': '1', 'type': 'Number (integer)', 'notes': 'Required', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 1', 'label': 'Half bathrooms', 'machine': 'field_prop_baths_half', 'card': '1', 'type': 'Number (integer)', 'notes': 'Required', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 1', 'label': 'Year built', 'machine': 'field_prop_year_built', 'card': '1', 'type': 'Number (integer)', 'notes': 'Required, min 1700, max current year', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 1', 'label': 'Property description', 'machine': 'field_prop_description', 'card': '1', 'type': 'Text (long)', 'notes': 'Required', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 1', 'label': 'List price ($)', 'machine': 'field_prop_price', 'card': '1', 'type': 'Number (integer)', 'notes': 'Required, Prefix $. THE search/index price.', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': True},
    {'entity': 'property_listing', 'group': 'Step 1', 'label': 'Listing strength score', 'machine': 'field_prop_strength_score', 'card': '1', 'type': 'Number (integer)', 'notes': 'Hidden from form, computed 0-100 by ECA', 'values': [], 'vocab': None, 'required': False, 'hidden': True, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'LISTING STATUS', 'label': 'Market status', 'machine': 'field_prop_market_status', 'card': '1', 'type': 'List (text)', 'notes': 'Required. Values: coming_soon | for_sale | under_contract | sold | off_market. Search API indexed; separate from editorial moderation.', 'values': ['coming_soon', 'for_sale', 'under_contract', 'sold', 'off_market'], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'STEP 2: LOCATION', 'label': 'Neighborhood', 'machine': 'field_prop_neighborhood', 'card': '1', 'type': 'Term ref → Location', 'notes': 'Autocomplete at neighborhood level', 'values': [], 'vocab': 'Location', 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 2', 'label': 'County/City', 'machine': 'field_prop_county', 'card': '1', 'type': 'Term ref → Location', 'notes': 'Required, Select at county level', 'values': [], 'vocab': 'Location', 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 2', 'label': 'School district', 'machine': 'field_prop_school_district', 'card': '1', 'type': 'Term ref → School District', 'notes': '', 'values': [], 'vocab': 'School District', 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 2', 'label': 'Land use code', 'machine': 'field_prop_land_use', 'card': '1', 'type': 'Text (plain)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 2', 'label': 'Commute information', 'machine': 'field_prop_commute', 'card': '1', 'type': 'Text (long)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 2', 'label': 'Surrounding area', 'machine': 'field_prop_surrounding', 'card': '1', 'type': 'Text (long)', 'notes': 'Parks, shopping, dining', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 2', 'label': 'Community features', 'machine': 'field_prop_community_feat', 'card': 'N', 'type': 'Term ref → Community Features', 'notes': 'Checkboxes', 'values': [], 'vocab': 'Community Features', 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 2', 'label': 'Internet providers', 'machine': 'field_prop_internet', 'card': 'N', 'type': 'Text (plain)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 2', 'label': 'Walk score', 'machine': 'field_prop_walk_score', 'card': '1', 'type': 'Number (integer)', 'notes': '0-100', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 2', 'label': 'STR (short-term rental) eligible', 'machine': 'field_prop_str_eligible', 'card': '1', 'type': 'Boolean', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 2', 'label': 'Climate features', 'machine': 'field_prop_climate_feat', 'card': 'N', 'type': 'Term ref → Climate Features', 'notes': '', 'values': [], 'vocab': 'Climate Features', 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 2', 'label': 'Flood zone', 'machine': 'field_prop_flood_zone', 'card': '1', 'type': 'List (text)', 'notes': 'Values: none | a_ae | v_ve | ah_ao | x_shaded | x_unshaded', 'values': ['none', 'a_ae', 'v_ve', 'ah_ao', 'x_shaded', 'x_unshaded'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'STEP 3: CONDITION', 'label': 'Roof age (years)', 'machine': 'field_prop_roof_age', 'card': '1', 'type': 'Number (integer)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 3', 'label': 'HVAC age (years)', 'machine': 'field_prop_hvac_age', 'card': '1', 'type': 'Number (integer)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 3', 'label': 'Water heater age (years)', 'machine': 'field_prop_wh_age', 'card': '1', 'type': 'Number (integer)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 3', 'label': 'Bathroom remodel year', 'machine': 'field_prop_bath_remodel_yr', 'card': '1', 'type': 'Number (integer)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 3', 'label': 'Bathroom permitted', 'machine': 'field_prop_bath_permit', 'card': '1', 'type': 'Boolean', 'notes': 'Conditional: if remodel year entered', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': 'yes', 'money': False},
    {'entity': 'property_listing', 'group': 'Step 3', 'label': 'Kitchen remodel year', 'machine': 'field_prop_kitchen_remodel_yr', 'card': '1', 'type': 'Number (integer)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 3', 'label': 'Kitchen permitted', 'machine': 'field_prop_kitchen_permit', 'card': '1', 'type': 'Boolean', 'notes': 'Conditional', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': 'yes', 'money': False},
    {'entity': 'property_listing', 'group': 'Step 3', 'label': 'Appliances included', 'machine': 'field_prop_appliances', 'card': 'N', 'type': 'Term ref → Appliances', 'notes': 'Checkboxes', 'values': [], 'vocab': 'Appliances', 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 3', 'label': 'Window types', 'machine': 'field_prop_windows', 'card': 'N', 'type': 'List (text)', 'notes': 'Values: double_hung | casement | awning | sliding | skylight | picture', 'values': ['double_hung', 'casement', 'awning', 'sliding', 'skylight', 'picture'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 3', 'label': 'Energy upgrades', 'machine': 'field_prop_energy_desc', 'card': '1', 'type': 'Text (long)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 3', 'label': 'Smart home features', 'machine': 'field_prop_smart_feat', 'card': 'N', 'type': 'Term ref → Smart Home Features', 'notes': 'Checkboxes', 'values': [], 'vocab': 'Smart Home Features', 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 3', 'label': 'Garage type', 'machine': 'field_prop_garage_type', 'card': '1', 'type': 'List (text)', 'notes': 'Values: none | attached | detached | carport', 'values': ['none', 'attached', 'detached', 'carport'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 3', 'label': 'Garage spaces', 'machine': 'field_prop_garage_spaces', 'card': '1', 'type': 'Number (integer)', 'notes': 'Conditional: if garage != none', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': 'yes', 'money': False},
    {'entity': 'property_listing', 'group': 'Step 3', 'label': 'Driveway material', 'machine': 'field_prop_driveway_material', 'card': '1', 'type': 'List (text)', 'notes': 'Values: gravel | concrete | asphalt | pavers', 'values': ['gravel', 'concrete', 'asphalt', 'pavers'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 3', 'label': 'Parking', 'machine': 'field_prop_parking', 'card': 'N', 'type': 'List (text)', 'notes': 'Values: street | driveway | garage | assigned', 'values': ['street', 'driveway', 'garage', 'assigned'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 3', 'label': 'Counter type', 'machine': 'field_prop_counter', 'card': '1', 'type': 'List (text)', 'notes': 'Values: granite | quartz | corian | laminate | marble | tile', 'values': ['granite', 'quartz', 'corian', 'laminate', 'marble', 'tile'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 3', 'label': 'Warranty provided', 'machine': 'field_prop_warranty', 'card': '1', 'type': 'Boolean', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 3', 'label': 'Warranty duration', 'machine': 'field_prop_warranty_length', 'card': '1', 'type': 'List (text)', 'notes': 'Conditional: 6_months | 1_year | 2_years', 'values': ['6_months', '1_year', '2_years'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': 'yes', 'money': False},
    {'entity': 'property_listing', 'group': 'Step 3', 'label': 'Radon mitigation system', 'machine': 'field_prop_radon', 'card': '1', 'type': 'Boolean', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 3', 'label': 'Insurance claims (5 yrs)', 'machine': 'field_prop_ins_claims', 'card': '1', 'type': 'Text (long)', 'notes': 'Conditional on boolean', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': 'boolean', 'money': False},
    {'entity': 'property_listing', 'group': 'PLUMBING', 'label': 'Supply line material', 'machine': 'field_prop_supply_line', 'card': 'N', 'type': 'List (text)', 'notes': 'Values: copper | pex | cpvc | pvc | galvanized | polybutylene', 'values': ['copper', 'pex', 'cpvc', 'pvc', 'galvanized', 'polybutylene'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Plumbing', 'label': 'Drain line material', 'machine': 'field_prop_drain_line', 'card': 'N', 'type': 'List (text)', 'notes': 'Same as supply line', 'values': ['copper', 'pex', 'cpvc', 'pvc', 'galvanized', 'polybutylene'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Plumbing', 'label': 'Sewer type', 'machine': 'field_prop_sewer', 'card': '1', 'type': 'List (text)', 'notes': 'Values: public | septic | grinder_pump', 'values': ['public', 'septic', 'grinder_pump'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Plumbing', 'label': 'Septic last pumped', 'machine': 'field_prop_septic_pumped', 'card': '1', 'type': 'Date', 'notes': 'Conditional: sewer = septic', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': 'yes', 'money': False},
    {'entity': 'property_listing', 'group': 'Plumbing', 'label': 'Plumbing year updated', 'machine': 'field_prop_plumbing_year', 'card': '1', 'type': 'Number (integer)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'ELECTRICAL', 'label': 'Wiring type', 'machine': 'field_prop_wiring', 'card': 'N', 'type': 'List (text)', 'notes': 'Values: copper | aluminum | knob_tube | cloth', 'values': ['copper', 'aluminum', 'knob_tube', 'cloth'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Electrical', 'label': 'Panel size', 'machine': 'field_prop_panel_size', 'card': 'N', 'type': 'List (text)', 'notes': 'Values: 60a | 100a | 150a | 200a | 400a', 'values': ['60a', '100a', '150a', '200a', '400a'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Electrical', 'label': 'Breaker or fuse', 'machine': 'field_prop_breaker_fuse', 'card': '1', 'type': 'List (text)', 'notes': 'Values: breaker | fuse', 'values': ['breaker', 'fuse'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Electrical', 'label': 'Panel brand', 'machine': 'field_prop_panel_brand', 'card': '1', 'type': 'List (text)', 'notes': 'Values: square_d | siemens | eaton | ge | other', 'values': ['square_d', 'siemens', 'eaton', 'ge', 'other'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Electrical', 'label': 'Electrical year updated', 'machine': 'field_prop_electrical_year', 'card': '1', 'type': 'Number (integer)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'FOUNDATION', 'label': 'Primary foundation type', 'machine': 'field_prop_foundation', 'card': 'N', 'type': 'List (text)', 'notes': 'Values: slab | crawl_space | basement | pier_beam | concrete_block | stone', 'values': ['slab', 'crawl_space', 'basement', 'pier_beam', 'concrete_block', 'stone'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Foundation', 'label': 'Basement finish', 'machine': 'field_prop_basement_finish', 'card': '1', 'type': 'List (text)', 'notes': 'Conditional: unfinished | partially_finished | fully_finished', 'values': ['unfinished', 'partially_finished', 'fully_finished'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': 'yes', 'money': False},
    {'entity': 'property_listing', 'group': 'Foundation', 'label': 'Foundation issues', 'machine': 'field_prop_foundation_issues', 'card': '1', 'type': 'Text (long)', 'notes': 'Conditional on boolean', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': 'boolean', 'money': False},
    {'entity': 'property_listing', 'group': 'Foundation', 'label': 'Structural construction', 'machine': 'field_prop_structure', 'card': 'N', 'type': 'List (text)', 'notes': 'Values: wood_frame | brick_veneer | stucco | concrete', 'values': ['wood_frame', 'brick_veneer', 'stucco', 'concrete'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'STEP 4: DETAILS & MEDIA', 'label': 'Rooms', 'machine': 'field_prop_rooms', 'card': 'N', 'type': 'Paragraph → room', 'notes': 'See Paragraphs sheet for room definition', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 4', 'label': 'Soil issues', 'machine': 'field_prop_soil', 'card': '1', 'type': 'Text (long)', 'notes': 'Land properties only', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 4', 'label': 'Listing photos', 'machine': 'field_prop_photos', 'card': 'N', 'type': 'Media ref (Image)', 'notes': 'Required (min 1). First = primary. Max 20MB each.', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 4', 'label': 'Virtual tour', 'machine': 'field_prop_virtual_tour', 'card': '1', 'type': 'Media ref (oEmbed video)', 'notes': 'Matterport/YouTube URL', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 4', 'label': 'Drone footage', 'machine': 'field_prop_drone', 'card': '1', 'type': 'Media ref (Video file)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'STEP 5: PRICING', 'label': 'Minimum acceptable price', 'machine': 'field_prop_price_min', 'card': '1', 'type': 'Number (integer)', 'notes': 'FIELD PERMISSIONS: Seller-only. Hidden from buyers.', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': 'seller', 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 5', 'label': 'Willing to negotiate', 'machine': 'field_prop_negotiable', 'card': '1', 'type': 'Boolean', 'notes': 'FIELD PERMISSIONS: Seller-only', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': 'seller', 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 5', 'label': 'Price flexibility (%)', 'machine': 'field_prop_price_flex', 'card': '1', 'type': 'Number (decimal)', 'notes': 'FIELD PERMISSIONS: Seller-only', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': 'seller', 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 5', 'label': 'Price confirmed', 'machine': 'field_prop_price_confirmed', 'card': '1', 'type': 'Boolean', 'notes': 'Required to publish. Equals field_prop_price.', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'STEP 6: FINANCIALS', 'label': 'Avg monthly electric ($)', 'machine': 'field_prop_util_electric', 'card': '1', 'type': 'Number (integer)', 'notes': 'Prefix $', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': True},
    {'entity': 'property_listing', 'group': 'Step 6', 'label': 'Avg monthly gas ($)', 'machine': 'field_prop_util_gas', 'card': '1', 'type': 'Number (integer)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': True},
    {'entity': 'property_listing', 'group': 'Step 6', 'label': 'Avg monthly water ($)', 'machine': 'field_prop_util_water', 'card': '1', 'type': 'Number (integer)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': True},
    {'entity': 'property_listing', 'group': 'Step 6', 'label': 'Avg monthly trash ($)', 'machine': 'field_prop_util_trash', 'card': '1', 'type': 'Number (integer)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': True},
    {'entity': 'property_listing', 'group': 'Step 6', 'label': 'Solar panels', 'machine': 'field_prop_solar', 'card': '1', 'type': 'List (text)', 'notes': 'Values: none | owned | leased', 'values': ['none', 'owned', 'leased'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 6', 'label': 'Solar monthly payment ($)', 'machine': 'field_prop_solar_payment', 'card': '1', 'type': 'Number (integer)', 'notes': 'Conditional: leased', 'values': ['leased'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': 'yes', 'money': True},
    {'entity': 'property_listing', 'group': 'Step 6', 'label': 'HOA required?', 'machine': 'field_prop_hoa_yn', 'card': '1', 'type': 'Boolean', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 6', 'label': 'HOA fee ($)', 'machine': 'field_prop_hoa_fee', 'card': '1', 'type': 'Number (integer)', 'notes': 'Conditional: hoa_yn=Yes', 'values': ['hoa_yn=Yes'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': 'yes', 'money': True},
    {'entity': 'property_listing', 'group': 'Step 6', 'label': 'HOA fee frequency', 'machine': 'field_prop_hoa_freq', 'card': '1', 'type': 'List (text)', 'notes': 'Values: monthly | quarterly | annually', 'values': ['monthly', 'quarterly', 'annually'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 6', 'label': 'Annual insurance premium ($)', 'machine': 'field_prop_insurance', 'card': '1', 'type': 'Number (integer)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': True},
    {'entity': 'property_listing', 'group': 'Step 6', 'label': 'Annual property taxes ($)', 'machine': 'field_prop_taxes', 'card': '1', 'type': 'Number (integer)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': True},
    {'entity': 'property_listing', 'group': 'Step 6', 'label': 'Assumable mortgage?', 'machine': 'field_prop_assumable', 'card': '1', 'type': 'Boolean', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 6', 'label': 'Assumable interest rate', 'machine': 'field_prop_assumable_rate', 'card': '1', 'type': 'Number (decimal, 3 dp)', 'notes': 'Conditional: assumable=Yes', 'values': ['assumable=Yes'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': 'yes', 'money': False},
    {'entity': 'property_listing', 'group': 'Step 6', 'label': 'Pending assessments?', 'machine': 'field_prop_assessments', 'card': '1', 'type': 'Boolean', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 6', 'label': 'Seller concessions', 'machine': 'field_prop_concessions', 'card': '1', 'type': 'Text (long)', 'notes': 'Conditional: assessments=Yes', 'values': ['assessments=Yes'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': 'yes', 'money': False},
    {'entity': 'property_listing', 'group': 'Step 6', 'label': 'Lease-back available?', 'machine': 'field_prop_leaseback', 'card': '1', 'type': 'Boolean', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'STEP 7: STORY', 'label': 'Unique selling points', 'machine': 'field_prop_usp', 'card': '1', 'type': 'Text (long)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 7', 'label': 'Improvements', 'machine': 'field_prop_improvements', 'card': 'N', 'type': 'Paragraph → improvement', 'notes': 'Year, cost, permitted status', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'PRIVATE (SELLER-ONLY)', 'label': 'Why selling', 'machine': 'field_prop_why_selling', 'card': '1', 'type': 'Text (long)', 'notes': 'FIELD PERMISSIONS: Seller-only. Hidden from buyers.', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': 'seller', 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Private', 'label': 'Timeline urgency', 'machine': 'field_prop_timeline', 'card': '1', 'type': 'List (text)', 'notes': 'Values: within_1_month | 1_3_months | 4_6_months | 6_12_months | 1_year_plus', 'values': ['within_1_month', '1_3_months', '4_6_months', '6_12_months', '1_year_plus'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Private', 'label': 'Already purchased?', 'machine': 'field_prop_next_home', 'card': '1', 'type': 'Boolean', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Private', 'label': 'Investment property?', 'machine': 'field_prop_investment', 'card': '1', 'type': 'Boolean', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Private', 'label': 'Best natural light', 'machine': 'field_prop_light', 'card': '1', 'type': 'List (text)', 'notes': 'Values: morning | midday | afternoon | evening', 'values': ['morning', 'midday', 'afternoon', 'evening'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Private', 'label': 'Outdoor features', 'machine': 'field_prop_outdoor_feat', 'card': 'N', 'type': 'Term ref → Outdoor Features', 'notes': '', 'values': [], 'vocab': 'Outdoor Features', 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Private', 'label': 'Storage features', 'machine': 'field_prop_storage_feat', 'card': 'N', 'type': 'Term ref → Storage Features', 'notes': '', 'values': [], 'vocab': 'Storage Features', 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Private', 'label': 'Noise levels', 'machine': 'field_prop_noise', 'card': 'N', 'type': 'List (text)', 'notes': 'Values: near_airport | near_train | near_highway | quiet', 'values': ['near_airport', 'near_train', 'near_highway', 'quiet'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Private', 'label': 'Pet-friendly features', 'machine': 'field_prop_pet_feat', 'card': '1', 'type': 'Text (long)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Private', 'label': 'Accessibility features', 'machine': 'field_prop_access_feat', 'card': 'N', 'type': 'Term ref → Accessibility Features', 'notes': '', 'values': [], 'vocab': 'Accessibility Features', 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'STEP 8: DOCUMENTS', 'label': 'Listing documents', 'machine': 'field_prop_documents', 'card': 'N', 'type': 'Paragraph → listing_document', 'notes': 'Type (taxonomy) + file in private://', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'STEP 9: CLOSING', 'label': 'Seller closing agent (LiNK)', 'machine': 'field_prop_closing_agent', 'card': '1', 'type': 'Entity ref → User (service_provider)', 'notes': 'Filtered to Closing & Legal category', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 9', 'label': 'Outside closing agent', 'machine': 'field_prop_outside_agent', 'card': '1', 'type': 'Paragraph → outside_agent', 'notes': 'Company, name, email, phone. Triggers ECA email.', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 9', 'label': 'Require pre-approval letter', 'machine': 'field_prop_req_preapproval', 'card': '1', 'type': 'Boolean', 'notes': 'Required', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 9', 'label': 'Require proof of funds', 'machine': 'field_prop_req_pof', 'card': '1', 'type': 'Boolean', 'notes': 'Required', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 9', 'label': 'Showing availability', 'machine': 'field_prop_showing_slots', 'card': 'N', 'type': 'Smart Date (recurring)', 'notes': "Seller's weekly available blocks", 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 9', 'label': 'Open house dates', 'machine': 'field_prop_open_houses', 'card': 'N', 'type': 'Smart Date', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 9', 'label': 'Yard sign purchased', 'machine': 'field_prop_yard_sign', 'card': '1', 'type': 'Boolean', 'notes': 'Set by Stripe webhook after $30 purchase', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 9', 'label': 'E-signature', 'machine': 'field_prop_signature', 'card': '1', 'type': 'Text (plain, 512)', 'notes': 'Signature Field. Required to publish.', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 9', 'label': 'Signature metadata', 'machine': 'field_prop_sig_meta', 'card': '1', 'type': 'Text (plain, 255)', 'notes': 'ECA writes timestamp + IP. Admin-only view.', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': 'admin', 'conditional': None, 'money': False},
    {'entity': 'property_listing', 'group': 'Step 9', 'label': 'HOA litigation info', 'machine': 'field_prop_hoa_litigation', 'card': '1', 'type': 'Text (long)', 'notes': 'Conditional on boolean', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': 'boolean', 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Listing', 'machine': 'field_bid_listing', 'card': '1', 'type': 'Entity ref → Node (property_listing)', 'notes': 'Required, locked on form', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Offer price ($)', 'machine': 'field_bid_price', 'card': '1', 'type': 'Number (integer)', 'notes': 'Required, Prefix $', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': True},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Offer expiration', 'machine': 'field_bid_expiration', 'card': '1', 'type': 'Date & time', 'notes': 'Required. ECA auto-expires status.', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Escalation clause?', 'machine': 'field_bid_escalation_yn', 'card': '1', 'type': 'Boolean', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Escalation increment ($)', 'machine': 'field_bid_esc_increment', 'card': '1', 'type': 'Number (integer)', 'notes': 'Conditional', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': 'yes', 'money': True},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Escalation ceiling ($)', 'machine': 'field_bid_esc_max', 'card': '1', 'type': 'Number (integer)', 'notes': 'Conditional', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': 'yes', 'money': True},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Additional terms', 'machine': 'field_bid_terms', 'card': '1', 'type': 'Text (long)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Financing method', 'machine': 'field_bid_financing', 'card': '1', 'type': 'List (text)', 'notes': 'Required. Values: cash | conventional | fha | va | other', 'values': ['cash', 'conventional', 'fha', 'va', 'other'], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Down payment %', 'machine': 'field_bid_down_pct', 'card': '1', 'type': 'Number (decimal, 2 dp)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Down payment ($)', 'machine': 'field_bid_down_amount', 'card': '1', 'type': 'Number (integer)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': True},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Lender name', 'machine': 'field_bid_lender', 'card': '1', 'type': 'Text (plain)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Proof of funds commitment', 'machine': 'field_bid_pof_commit', 'card': '1', 'type': 'Boolean', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Earnest money deposit?', 'machine': 'field_bid_emd_yn', 'card': '1', 'type': 'Boolean', 'notes': 'Required', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Earnest money amount ($)', 'machine': 'field_bid_emd_amount', 'card': '1', 'type': 'Number (integer)', 'notes': 'Conditional', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': 'yes', 'money': True},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Buyer closing agent (LiNK)', 'machine': 'field_bid_closing_agent', 'card': '1', 'type': 'Entity ref → User (service_provider)', 'notes': "VALIDATION: != seller's agent", 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Outside closing agent', 'machine': 'field_bid_outside_agent', 'card': '1', 'type': 'Paragraph → outside_agent', 'notes': 'Triggers invite email', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Contingencies', 'machine': 'field_bid_contingencies', 'card': 'N', 'type': 'List (text)', 'notes': 'Checkboxes: inspection | appraisal | financing | home_sale | warranty', 'values': ['inspection', 'appraisal', 'financing', 'home_sale', 'warranty'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Preferred closing date', 'machine': 'field_bid_closing_date', 'card': '1', 'type': 'Date', 'notes': 'Required. Blocks weekends.', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Possession date', 'machine': 'field_bid_possession', 'card': '1', 'type': 'Date', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Flexibility requests', 'machine': 'field_bid_flexibility', 'card': '1', 'type': 'Text (long)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Appliances included', 'machine': 'field_bid_appliances_inc', 'card': 'N', 'type': 'Term ref → Appliances', 'notes': 'Toggles', 'values': [], 'vocab': 'Appliances', 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Inclusions (other)', 'machine': 'field_bid_inclusions', 'card': '1', 'type': 'Text (long)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Exclusions', 'machine': 'field_bid_exclusions', 'card': '1', 'type': 'Text (long)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Pre-approval letter', 'machine': 'field_bid_preapproval', 'card': '1', 'type': 'Media ref (Document, private)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Proof of funds doc', 'machine': 'field_bid_pof', 'card': '1', 'type': 'Media ref (Document, private)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Offer letter', 'machine': 'field_bid_offer_letter', 'card': '1', 'type': 'Media ref (Document, private)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Digital signature', 'machine': 'field_bid_signature', 'card': '1', 'type': 'Text (plain, 512)', 'notes': 'Required', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Signature metadata', 'machine': 'field_bid_sig_meta', 'card': '1', 'type': 'Text (plain, 255)', 'notes': 'ECA sets timestamp + IP. Admin-only.', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': 'admin', 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Bid status', 'machine': 'field_bid_status', 'card': '1', 'type': 'List (text)', 'notes': 'Values: draft | submitted | viewed | countered | accepted | rejected | outbid | expired | withdrawn', 'values': ['draft', 'submitted', 'viewed', 'countered', 'accepted', 'rejected', 'outbid', 'expired', 'withdrawn'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Seller viewed timestamp', 'machine': 'field_bid_seller_viewed', 'card': '1', 'type': 'Date & time', 'notes': 'ECA sets on first render', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Counter-offer price ($)', 'machine': 'field_bid_counter_price', 'card': '1', 'type': 'Number (integer)', 'notes': 'Optional. New revision = counter.', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': True},
    {'entity': 'bid', 'group': 'Bid', 'label': 'Counter-offer terms', 'machine': 'field_bid_counter_terms', 'card': '1', 'type': 'Text (long)', 'notes': 'Optional', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Service category', 'machine': 'field_sp_category', 'card': '1', 'type': 'Term ref → Service Category', 'notes': 'Required. Drives tabs & filters.', 'values': [], 'vocab': 'Service Category', 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Service description', 'machine': 'field_sp_description', 'card': '1', 'type': 'Text (long)', 'notes': 'Required', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Pricing model', 'machine': 'field_sp_pricing_model', 'card': '1', 'type': 'List (text)', 'notes': 'Required. Values: request_quote | hourly | flat_fee | per_project', 'values': ['request_quote', 'hourly', 'flat_fee', 'per_project'], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Typical price range', 'machine': 'field_sp_price_range', 'card': '1', 'type': 'Text (plain)', 'notes': "E.g., '$300–$500'", 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Pricing menu', 'machine': 'field_sp_pricing_menu', 'card': '1', 'type': 'Media ref (Document)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Packages offered', 'machine': 'field_sp_packages', 'card': 'N', 'type': 'Paragraph → service_package', 'notes': 'Name, description, price', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Military discount?', 'machine': 'field_sp_mil_discount', 'card': '1', 'type': 'Boolean', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Other discounts', 'machine': 'field_sp_other_discounts', 'card': '1', 'type': 'Text (long)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Service FAQ', 'machine': 'field_sp_faq', 'card': 'N', 'type': 'Paragraph → faq_item', 'notes': 'Q&A pairs', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Portfolio (before/after)', 'machine': 'field_sp_portfolio', 'card': 'N', 'type': 'Media ref (Image)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Availability lead time', 'machine': 'field_sp_availability', 'card': '1', 'type': 'List (text)', 'notes': 'Required. Values: 1_day | 1_week | 2_weeks | 3_weeks | 1_month_plus', 'values': ['1_day', '1_week', '2_weeks', '3_weeks', '1_month_plus'], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Preferred job size', 'machine': 'field_sp_job_size', 'card': 'N', 'type': 'List (text)', 'notes': 'Values: small | medium | large', 'values': ['small', 'medium', 'large'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Coverage areas', 'machine': 'field_sp_coverage', 'card': 'N', 'type': 'Term ref → Location', 'notes': 'Required. County/city terms.', 'values': [], 'vocab': 'Location', 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Travel distance (miles)', 'machine': 'field_sp_travel_miles', 'card': '1', 'type': 'Number (integer)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Pulls permits?', 'machine': 'field_sp_permits', 'card': '1', 'type': 'Boolean', 'notes': 'Contractors only', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Estimate fee?', 'machine': 'field_sp_estimate_fee', 'card': '1', 'type': 'Boolean', 'notes': 'Conditional', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': 'yes', 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Quote process', 'machine': 'field_sp_quote_process', 'card': '1', 'type': 'Text (long)', 'notes': 'Conditional', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': 'yes', 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Appointment availability', 'machine': 'field_sp_appt_slots', 'card': 'N', 'type': 'Smart Date (recurring)', 'notes': "Provider's meeting windows", 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Accuracy attestation', 'machine': 'field_sp_attest', 'card': '1', 'type': 'Boolean', 'notes': "Required. 'Statements are accurate'", 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Average rating (cache)', 'machine': 'field_sp_rating_cache', 'card': '1', 'type': 'Number (decimal, 2 dp)', 'notes': 'Hidden. Voting API writes this.', 'values': [], 'vocab': None, 'required': False, 'hidden': True, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'service_offering', 'group': 'Service', 'label': 'Review count (cache)', 'machine': 'field_sp_review_count', 'card': '1', 'type': 'Number (integer)', 'notes': 'Hidden. Views hides stars if <5 reviews.', 'values': [], 'vocab': None, 'required': False, 'hidden': True, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_room', 'group': 'room', 'label': 'Room name', 'machine': 'field_room_name', 'card': '1', 'type': 'List (text)', 'notes': 'Values: living_room | family_room | kitchen | dining | primary_bed | bedroom | bathroom | office | bonus | laundry | other', 'values': ['living_room', 'family_room', 'kitchen', 'dining', 'primary_bed', 'bedroom', 'bathroom', 'office', 'bonus', 'laundry', 'other'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_room', 'group': 'room', 'label': 'Room length (ft)', 'machine': 'field_room_length', 'card': '1', 'type': 'Number (decimal)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_room', 'group': 'room', 'label': 'Room width (ft)', 'machine': 'field_room_width', 'card': '1', 'type': 'Number (decimal)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_room', 'group': 'room', 'label': 'Room square footage', 'machine': 'field_room_sqft', 'card': '1', 'type': 'Number (integer)', 'notes': 'Auto-calculated: L x W (editable)', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_room', 'group': 'room', 'label': 'Room flooring', 'machine': 'field_room_flooring', 'card': '1', 'type': 'Term ref → Flooring Type', 'notes': '', 'values': [], 'vocab': 'Flooring Type', 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_room', 'group': 'room', 'label': 'Ceiling fan?', 'machine': 'field_room_ceiling_fan', 'card': '1', 'type': 'Boolean', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_room', 'group': 'room', 'label': 'Overhead lighting?', 'machine': 'field_room_overhead_light', 'card': '1', 'type': 'Boolean', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_improvement', 'group': 'improvement', 'label': 'Description', 'machine': 'field_imp_description', 'card': '1', 'type': 'Text (plain)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_improvement', 'group': 'improvement', 'label': 'Year', 'machine': 'field_imp_year', 'card': '1', 'type': 'Number (integer)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_improvement', 'group': 'improvement', 'label': 'Cost ($)', 'machine': 'field_imp_cost', 'card': '1', 'type': 'Number (integer)', 'notes': 'Prefix $', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': True},
    {'entity': 'paragraph_improvement', 'group': 'improvement', 'label': 'Permitted?', 'machine': 'field_imp_permitted', 'card': '1', 'type': 'Boolean', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_listing_document', 'group': 'listing_document', 'label': 'Document type', 'machine': 'field_doc_type', 'card': '1', 'type': 'Term ref → Document Type', 'notes': 'Required', 'values': [], 'vocab': 'Document Type', 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_listing_document', 'group': 'listing_document', 'label': 'Document file', 'machine': 'field_doc_file', 'card': '1', 'type': 'File (pdf/jpg/png)', 'notes': 'PRIVATE file system, 25MB max', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_listing_document', 'group': 'listing_document', 'label': 'Document notes', 'machine': 'field_doc_notes', 'card': '1', 'type': 'Text (plain)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_credential', 'group': 'credential', 'label': 'Credential type', 'machine': 'field_cred_type', 'card': '1', 'type': 'List (text)', 'notes': 'Values: certification | license | award | insurance', 'values': ['certification', 'license', 'award', 'insurance'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_credential', 'group': 'credential', 'label': 'Credential name', 'machine': 'field_cred_name', 'card': '1', 'type': 'Text', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_credential', 'group': 'credential', 'label': 'Credential number', 'machine': 'field_cred_number', 'card': '1', 'type': 'Text', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_credential', 'group': 'credential', 'label': 'Credential file', 'machine': 'field_cred_file', 'card': '1', 'type': 'File (PRIVATE)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_outside_agent', 'group': 'outside_agent', 'label': 'Company name', 'machine': 'field_oa_company', 'card': '1', 'type': 'Text (plain)', 'notes': 'Required', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_outside_agent', 'group': 'outside_agent', 'label': 'Agent name', 'machine': 'field_oa_name', 'card': '1', 'type': 'Text (plain)', 'notes': 'Required', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_outside_agent', 'group': 'outside_agent', 'label': 'Agent email', 'machine': 'field_oa_email', 'card': '1', 'type': 'Email', 'notes': 'Required', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_outside_agent', 'group': 'outside_agent', 'label': 'Agent phone', 'machine': 'field_oa_phone', 'card': '1', 'type': 'Telephone', 'notes': 'Required', 'values': [], 'vocab': None, 'required': True, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_outside_agent', 'group': 'outside_agent', 'label': 'Invite sent?', 'machine': 'field_oa_invited', 'card': '1', 'type': 'Boolean', 'notes': 'ECA sets when invite sent', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_service_package', 'group': 'service_package', 'label': 'Package name', 'machine': 'field_pkg_name', 'card': '1', 'type': 'Text', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_service_package', 'group': 'service_package', 'label': 'Package description', 'machine': 'field_pkg_description', 'card': '1', 'type': 'Text (long)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_service_package', 'group': 'service_package', 'label': 'Package price ($)', 'machine': 'field_pkg_price', 'card': '1', 'type': 'Number (integer)', 'notes': 'Prefix $', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': True},
    {'entity': 'paragraph_service_package', 'group': 'service_package', 'label': 'Package file', 'machine': 'field_pkg_file', 'card': '1', 'type': 'File', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_faq_item', 'group': 'faq_item', 'label': 'Question', 'machine': 'field_faqi_question', 'card': '1', 'type': 'Text (plain)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_faq_item', 'group': 'faq_item', 'label': 'Answer', 'machine': 'field_faqi_answer', 'card': '1', 'type': 'Text (long)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_concierge_task', 'group': 'concierge_task', 'label': 'Task type', 'machine': 'field_ct_type', 'card': '1', 'type': 'List (text)', 'notes': 'Values: showing | repair | vendor_contact | research | pricing | documents', 'values': ['showing', 'repair', 'vendor_contact', 'research', 'pricing', 'documents'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_concierge_task', 'group': 'concierge_task', 'label': 'Task description', 'machine': 'field_ct_description', 'card': '1', 'type': 'Text (long)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_concierge_task', 'group': 'concierge_task', 'label': 'Task status', 'machine': 'field_ct_status', 'card': '1', 'type': 'List (text)', 'notes': 'Values: pending | active | done', 'values': ['pending', 'active', 'done'], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_concierge_task', 'group': 'concierge_task', 'label': 'Time spent (minutes)', 'machine': 'field_ct_minutes', 'card': '1', 'type': 'Number (integer)', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
    {'entity': 'paragraph_concierge_task', 'group': 'concierge_task', 'label': 'Completed date', 'machine': 'field_ct_completed', 'card': '1', 'type': 'Date', 'notes': '', 'values': [], 'vocab': None, 'required': False, 'hidden': False, 'private': None, 'conditional': None, 'money': False},
]

VOCABULARIES = {
    'Property Type': {'machine': 'property_type', 'hierarchical': False, 'terms': ['Single-family home', 'Condo', 'Townhouse', 'Land', 'Multi-family (2-4)', 'Manufactured', 'Modular']},
    'Location': {'machine': 'location', 'hierarchical': True, 'terms': ['Virginia → Richmond City', 'Henrico', 'Chesterfield', 'Hanover → The Fan', 'Church Hill', 'Short Pump']},
    'School District': {'machine': 'school_district', 'hierarchical': False, 'terms': ['Richmond Public Schools', 'Henrico County Public', 'Chesterfield County Public']},
    'Community Features': {'machine': 'community_feature', 'hierarchical': False, 'terms': ['Pool', 'Clubhouse', 'Trails', 'Playground', 'Golf course', 'Marina', 'Gated', 'Tennis courts', 'Fitness center']},
    'Outdoor Features': {'machine': 'outdoor_feature', 'hierarchical': False, 'terms': ['Fire pit', 'Screened porch', 'Deck', 'Patio', 'Irrigation', 'Fenced yard', 'Mature trees', 'Garden', 'Pool', 'Hot tub', 'Outdoor kitchen']},
    'Storage Features': {'machine': 'storage_feature', 'hierarchical': False, 'terms': ['Attic', 'Built-ins', 'Garage shelving', 'Shed', 'Basement storage', 'Walk-in closets']},
    'Accessibility Features': {'machine': 'accessibility_feature', 'hierarchical': False, 'terms': ['Main-level primary', 'Wheelchair access', 'No-step entry', 'Wide doorways', 'Grab bars', 'Stair lift', 'Roll-in shower']},
    'Smart Home Features': {'machine': 'smart_home_feature', 'hierarchical': False, 'terms': ['Video doorbell', 'Smart locks', 'Smart garage door', 'Window sensors', 'Smart thermostat', 'Smart lighting', 'Smart blinds', 'Smart leak detectors']},
    'Climate Features': {'machine': 'climate_feature', 'hierarchical': False, 'terms': ['Hurricane shutters', 'Sump pump', 'Storm shelter', 'Impact windows', 'Generator hookup', 'Whole-house generator']},
    'Appliances': {'machine': 'appliance', 'hierarchical': False, 'terms': ['Refrigerator', 'Range', 'Cooktop', 'Oven', 'Dishwasher', 'Microwave', 'Washer', 'Dryer', 'Freezer', 'Wine cooler', 'Garbage disposal']},
    'Service Category': {'machine': 'service_category', 'hierarchical': True, 'terms': ['Closing & Legal → Closing agent', 'Attorney', 'Title co.', 'Financing → Lender', 'Broker', 'Inspection → Inspector', 'Radon', 'Contractors → General', 'Roofer', 'HVAC', 'Plumber', 'Electrician', 'Marketing → Photographer', 'Videographer', 'Stager']},
    'Document Type': {'machine': 'document_type', 'hierarchical': False, 'terms': ['Property disclosure', 'Lead-paint disclosure', 'HOA docs', 'Utility statements', 'Survey/Plat', 'Tax history', 'Exclusions list', 'Home warranty', 'Repair receipts', 'Pre-listing inspection', 'Foundation repair records', 'Appraisal', 'Floor plan', 'Known defects', 'Unpermitted work', 'Pending liens', 'Easements', 'Rental history', 'FEMA claims', 'Wildfire risk', 'Hurricane exposure', 'Earthquake retrofitting', 'Warranties']},
    'FAQ Category': {'machine': 'faq_category', 'hierarchical': False, 'terms': ['Buying', 'Selling', 'Bidding & Offers', 'Service Providers', 'Concierge', 'Account & Billing']},
    'Flooring Type': {'machine': 'flooring_type', 'hierarchical': False, 'terms': ['Hardwood', 'Engineered wood', 'Laminate', 'Luxury vinyl', 'Tile', 'Carpet', 'Concrete', 'Bamboo', 'Cork', 'Stone']},
}

OTHER_TYPES = [
    {'label': 'Provider Review', 'machine': 'provider_review', 'key_fields': 'Service offering ref, Project ref, Satisfaction (Fivestar 1-5), Would use again (bool), Responsive/quality (Fivestar), Cost value ($-rating 1-5), Comments, Dispute status', 'notes': 'Moderate and publish each review individually. Voting API aggregates only published reviews. Hide the public aggregate rating until the offering has at least 5 published reviews.'},
    {'label': 'Project', 'machine': 'project', 'key_fields': 'Service offering ref, Client user ref, Related listing, Scope, Status (requested/quoted/accepted/active/complete/canceled), Amount, Scheduled date', 'notes': 'On status=complete, ECA queues review request to client.'},
    {'label': 'Showing Request', 'machine': 'showing_request', 'key_fields': "Listing ref, Requested slot (Smart Date from seller's availability), Type (scheduled/instant/open_house/appt), Status, Notes", 'notes': 'Author = requesting buyer. Seller confirms via Views.'},
    {'label': 'Transaction', 'machine': 'transaction', 'key_fields': 'Listing ref, Accepted bid ref, Status (pending/inspection/appraisal/financing/closing/closed/failed), Closing date, Notes', 'notes': 'Enable Revisions (audit trail). On status=closed, ECA sets listing sold, sends savings email.'},
    {'label': 'Payment Record', 'machine': 'payment_record', 'key_fields': 'User ref, Amount, Payment type (subscription/promotion/concierge/yard_sign), Stripe ID, Status (paid/refund_requested/refunded)', 'notes': 'Created by Stripe webhook. NEVER store card data. Ledger-only.'},
    {'label': 'Concierge Case', 'machine': 'concierge_case', 'key_fields': 'Client user ref, Related listing, Tier (tier_1/2/3), Status, Priority, Assigned staff, Tasks (Paragraph)', 'notes': 'Support cases.'},
    {'label': 'FAQ', 'machine': 'faq', 'key_fields': 'Title, Body, FAQ category (Term ref)', 'notes': 'Rendered with accordion Views.'},
    {'label': 'School', 'machine': 'school', 'key_fields': 'Title (name), field_school_geolocation (Geofield), field_school_type (list: elementary|middle|high|private|charter), field_school_district (Term ref → School District), field_school_address (Address)', 'notes': 'Populated by FREE VDOE/NCES import (~2,100 VA schools) via Feeds. Powers "Schools nearby" proximity block on listings. National = per-state NCES import; data, not schema. School RATINGS (GreatSchools) are licensed and NOT client-required — omit.'},
    {'label': 'Messaging thread (module entity)', 'machine': 'private_message_thread', 'key_fields': 'field_thread_listing: Entity ref -> property_listing (optional); subject prefilled with listing #', 'notes': 'NOT a node type - Private Message threads are fieldable entities. Listing ref powers per-listing conversation Views.'},
]

FLAGS = [
    {'label': 'Favorite', 'machine': 'favorite', 'applies_to': 'property_listing', 'usage': 'Per-user flag. Powers Favorites dashboard + alert triggers (price drop, new listing).'},
    {'label': 'Compare', 'machine': 'compare', 'applies_to': 'property_listing', 'usage': 'Per-user flag, LIMIT 4. Compare page = Views table of flagged nodes with sticky header, notes, share.'},
    {'label': 'Saved Provider', 'machine': 'saved_provider', 'applies_to': 'service_offering', 'usage': 'Optional convenience flag for provider discovery.'},
    {'label': 'Report Message', 'machine': 'report_message', 'applies_to': 'private_message', 'usage': 'Per-message abuse report; feeds admin review queue (DR-10). Compliance role reads REPORTED threads only, audit-logged (DR-9).'},
]

SDC_COMPONENTS = [
    {'name': 'listing-card', 'props': 'image, price, address, beds, baths, sqft, status, url, flag_buttons', 'source': 'field_prop_photos (card_400x300 style), field_prop_price, field_prop_address, field_prop_beds / baths_full / sqft, field_prop_market_status, favorite + compare flag links', 'used': 'Search results, favorites, dashboards. THE most reused component - build first.'},
    {'name': 'photo-gallery', 'props': 'images[], virtual_tour_url', 'source': 'field_prop_photos (gallery_1200, lazy load), field_prop_virtual_tour oEmbed', 'used': 'Listing page hero. Keyboard + swipe accessible.'},
    {'name': 'facet-sidebar', 'props': 'facets[] (label, count, active)', 'source': 'Facets region blocks (price, beds, baths, type, location, features)', 'used': 'Search + provider search. Collapses to drawer on mobile.'},
    {'name': 'map-panel', 'props': 'geojson pins, center, zoom, variant', 'source': 'Leaflet Views display of field_geolocation; variants: search (synced, bounding-box filter), mini (listing), schools layer', 'used': 'Map search page, listing mini-map, schools-nearby.'},
    {'name': 'compare-table', 'props': 'listings[] (full systems detail), notes', 'source': 'Compare flag items; all Step 1-3 fields; buyer note field', 'used': 'Compare page. Sticky first column + header; max 4.'},
    {'name': 'bid-panel', 'props': 'status, price, expiration_countdown, actions', 'source': 'bid node: field_bid_status, field_bid_price, field_bid_expiration; counter-offer revision data', 'used': 'Bid page + seller bid review. Status drives color via tokens.'},
    {'name': 'step-tabs', 'props': 'steps[] (label, state: done/active/todo)', 'source': 'link_listing_wizard form state + property_listing draft fields', 'used': 'Seller listing form. Nine resumable steps with save-and-continue, per-step validation, progress, preview, and final submission for moderation.'},
    {'name': 'rating-stars', 'props': 'avg, count, show (bool)', 'source': 'field_sp_rating_cache, field_sp_review_count (hidden if count < 5)', 'used': 'Provider cards, offering page, review lists.'},
    {'name': 'provider-card', 'props': 'logo, business_name, category, rating, coverage, promoted', 'source': 'Provider profile fields + field_sp_category, field_sp_ad_tier (promoted badge)', 'used': 'Provider search results. Promoted = subtle badge, not layout change.'},
    {'name': 'schools-nearby-list', 'props': 'schools[] (name, type, distance)', 'source': 'school nodes via Geofield proximity View', 'used': 'Listing page block, paired with map-panel schools layer.'},
    {'name': 'showing-calendar', 'props': 'events[], mode (seller/buyer/provider)', 'source': 'Smart Date fields + showing_request Views (calendar display)', 'used': 'All three calendar dashboards; one component, three data feeds.'},
    {'name': 'dashboard-stat-card', 'props': 'label, value, delta, link', 'source': 'Views aggregate counts (views, bids, favorites, projects)', 'used': 'All dashboards. Grid of 3-4 per overview screen.'},
    {'name': 'status-badge', 'props': 'state, size', 'source': 'field_prop_market_status + editorial moderation + bid/project/transaction status lists', 'used': 'Everywhere. Maintain one documented color map while keeping market status distinct from editorial moderation.'},
    {'name': 'doc-list', 'props': 'docs[] (type, label, date, url)', 'source': 'field_prop_documents / bid document fields (private files)', 'used': 'Listing docs tab, bid review. Type icon from document_type term.'},
    {'name': 'message-thread', 'props': 'messages[], participants', 'source': 'private_message module templates overridden in theme', 'used': 'Messages page. Style only - the module handles logic.'},
    {'name': 'alert-toast / empty-state', 'props': 'message, variant / icon, headline, cta', 'source': 'Drupal status messages; empty Views text', 'used': 'Global. Empty states matter: new users see them first.'},
]


# ------------------------------------------------------------------ helpers
BY_ENTITY = {}
for _f in FIELDS:
    BY_ENTITY.setdefault(_f["entity"], []).append(_f)

BY_MACHINE = {_f["machine"]: _f for _f in FIELDS}


def entity(name):
    """Fields for one entity, in manual order."""
    return BY_ENTITY.get(name, [])


def group(entity_name, *prefixes):
    """Fields whose manual Step/Section starts with any of `prefixes`.

    Matching is case-insensitive because the manual writes the first row of a
    section in caps ("STEP 3: CONDITION") and continues it in title case
    ("Step 3"). Both belong to the same wizard step.
    """
    lowered = tuple(p.lower() for p in prefixes)
    return [f for f in entity(entity_name)
            if f["group"].lower().startswith(lowered)]


def terms(vocab_label):
    v = VOCABULARIES.get(vocab_label)
    return v["terms"] if v else []
