{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://fullstackhvac.com/templates/v1/submission.schema.json",
  "title": "Full Stack HVAC Product Submission (v1)",
  "description": "Machine-readable contract for submitting structured product/tool/training data to Full Stack HVAC (fullstackhvac.com). A vendor's AI agent fills this envelope from the vendor's own knowledge and submits it for EDITORIAL VERIFICATION. Submitted data is treated as factual claims to be checked against authoritative sources. It is NOT published verbatim and NEVER affects FSH Scores, rankings, or editorial conclusions. One submission carries one vendor and one or more of that vendor's products. See x-fsh-instructions for how to fill and submit.",
  "type": "object",
  "additionalProperties": false,
  "x-fsh-instructions": {
    "audience": "An AI agent (Claude, Codex, etc.) acting on behalf of a product vendor, or a human operator.",
    "purpose": "Supply structured factual data so Full Stack HVAC can index a new product or verify/correct an existing one.",
    "how_to_fill": [
      "Start from starter.json (a blank valid skeleton) or the closest examples/*.example.json for your product_kind.",
      "Use only verifiable, public, accurate information. Cite a source URL wherever possible (evidence_url / source_urls).",
      "Leave a field OUT entirely when you do not know it. Do NOT guess and do NOT write 'unknown'/'N/A'. For features and booleans, an omitted value means 'unknown' and is treated differently from an explicit false.",
      "Pick classification.primary_index_key and category slugs from catalogs/index-keys.json and catalogs/categories.json. Pick feature_slug values from catalogs/canonical-features.json. If a feature has no matching slug, use the fallback shape (vendor_label + description) and editorial will map it.",
      "Asset fields (logos, screenshots) are OPTIONAL and must be URLs you control or are licensed to share. Do not paste binary data.",
      "Validate your completed file against this schema before submitting."
    ],
    "how_to_submit": "POST the completed JSON (Content-Type: application/json) to https://fullstackhvac.com/api/v1/product-submissions . You will receive a submission id. Alternatively paste it into the form at https://fullstackhvac.com/for-vendors/ .",
    "rules": [
      "Free-text fields (vendor.description, product.product_positioning_notes) are reference material for fact-checking only. FSH writes all published copy independently; your text will not appear verbatim.",
      "Classification (index/category) is a SUGGESTION. FSH editorial assignment is authoritative.",
      "Submissions cannot raise scores, change rankings, or remove competitors. Misrepresentation may result in the submission being rejected.",
      "rights_attestation fields must all be true or the submission is rejected."
    ],
    "policy": "https://fullstackhvac.com/vendor-relations/"
  },
  "required": ["schema_version", "submitter", "rights_attestation", "vendor", "products"],
  "properties": {
    "$schema": {
      "type": "string",
      "description": "Optional. Pointer back to this schema. Recommended value: https://fullstackhvac.com/templates/v1/submission.schema.json"
    },
    "schema_version": {
      "const": "1.0",
      "description": "Must equal the version this submission targets. The current version is published in manifest.json."
    },
    "submission_metadata": {
      "type": "object",
      "description": "Optional provenance about how this submission was prepared.",
      "additionalProperties": false,
      "properties": {
        "prepared_by": { "type": "string", "description": "Person or system that prepared the data, e.g. 'Acme marketing team' or 'Claude agent'." },
        "prepared_at": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp." },
        "agent": { "type": "string", "enum": ["claude", "codex", "other-ai", "human"], "description": "What produced this submission." },
        "source_notes": { "type": "string", "maxLength": 2000, "description": "Free notes on where the data came from (internal docs, spec sheets, etc.)." }
      }
    },
    "submitter": {
      "type": "object",
      "description": "Who is submitting. Used for follow-up and authority verification; not published.",
      "additionalProperties": false,
      "required": ["email", "relationship_to_vendor"],
      "properties": {
        "name": { "type": "string", "maxLength": 200 },
        "email": { "type": "string", "format": "email", "maxLength": 320, "description": "Reachable contact email. A vendor-domain email helps verify authority." },
        "role": { "type": "string", "maxLength": 200, "description": "e.g. 'Product Marketing Manager'." },
        "relationship_to_vendor": {
          "type": "string",
          "enum": ["employee", "agency", "reseller", "other"],
          "description": "Your relationship to the company that makes this product."
        }
      }
    },
    "rights_attestation": {
      "type": "object",
      "description": "Required attestations. All three must be true or the submission is rejected at intake.",
      "additionalProperties": false,
      "required": ["assets_licensed", "data_accurate", "understands_editorial_review"],
      "properties": {
        "assets_licensed": { "const": true, "description": "You confirm any asset URLs (logos/screenshots) are owned by the vendor or licensed for Full Stack HVAC to display." },
        "data_accurate": { "const": true, "description": "You confirm the submitted data is accurate to the best of your knowledge." },
        "understands_editorial_review": { "const": true, "description": "You understand this data is verified editorially, is not published verbatim, and does not affect scores or rankings." }
      }
    },
    "vendor": { "$ref": "#/$defs/vendor" },
    "products": {
      "type": "array",
      "description": "One or more products belonging to the vendor above.",
      "minItems": 1,
      "maxItems": 100,
      "items": { "$ref": "#/$defs/product" }
    }
  },
  "$defs": {
    "vendor": {
      "type": "object",
      "description": "The company/manufacturer that makes the product(s).",
      "additionalProperties": false,
      "required": ["name"],
      "properties": {
        "slug": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$", "maxLength": 120, "description": "Suggested URL slug, lowercase kebab-case, e.g. 'google-nest'. Editorial may adjust. If you omit it, one is derived from the name." },
        "name": { "type": "string", "maxLength": 255, "description": "Company display name, e.g. 'Google Nest'." },
        "website": { "type": "string", "format": "uri", "description": "Company homepage." },
        "logo_url": { "type": "string", "format": "uri", "description": "Optional. URL to the company logo (square or wordmark). Must be vendor-owned or licensed." },
        "description": { "type": "string", "maxLength": 2000, "description": "EDITORIAL-ONLY / not_for_direct_publication. A factual summary of the company for fact-checking; FSH writes its own copy." },
        "founded_year": { "type": "integer", "minimum": 1900, "maximum": 2100 },
        "headquarters": { "type": "string", "maxLength": 255, "description": "e.g. 'Austin, TX, USA'." },
        "employee_range": { "type": "string", "enum": ["1", "2-5", "6-10", "11-25", "26-50", "51-100", "101-500", "500+"] },
        "ownership_type": { "type": "string", "maxLength": 100, "description": "e.g. 'Private', 'Public', 'PE-backed', 'Subsidiary'." }
      }
    },
    "product": {
      "type": "object",
      "additionalProperties": false,
      "required": ["name", "product_kind", "classification"],
      "properties": {
        "name": { "type": "string", "maxLength": 255, "description": "Product display name, e.g. 'Nest Learning Thermostat'." },
        "slug": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$", "maxLength": 120, "description": "Suggested product slug (unique within the vendor). Editorial may adjust." },
        "product_kind": {
          "type": "string",
          "enum": ["software", "physical_tool", "training", "mentorship", "service"],
          "description": "What kind of thing this is. Drives which extension object is expected: physical_tool->smart_tool_details, training->training_details, mentorship->mentorship_details; software/service have no extension."
        },
        "website_url": { "type": "string", "format": "uri", "description": "Product page (may differ from the vendor homepage)." },
        "status": { "type": "string", "enum": ["active", "discontinued", "acquired", "delisted"], "default": "active" },
        "pricing_model": {
          "type": "string",
          "enum": ["free", "freemium", "subscription", "per_user", "per_job", "tiered", "custom", "quote_only"],
          "description": "Universal pricing model. For hardware that is sold at retail, use 'quote_only' or 'custom' here and give retail/MSRP detail in smart_tool_details.pricing_model."
        },
        "pricing_starting_at": { "type": "number", "minimum": 0, "description": "Lowest published price in USD, or omit if quote-only/unknown." },
        "pricing_notes": { "type": "string", "maxLength": 1000, "description": "Pricing clarifications, e.g. 'billed annually, per technician'." },
        "has_free_trial": { "type": "boolean" },
        "has_free_tier": { "type": "boolean" },
        "has_api": { "type": "boolean" },
        "has_mobile_app": { "type": "boolean" },
        "deployment_type": { "type": "string", "enum": ["cloud", "on_premise", "hybrid"], "description": "For software. For physical tools this typically describes the companion software." },
        "company_size_min": { "type": "integer", "minimum": 0, "description": "Smallest target customer size (employee count)." },
        "company_size_max": { "type": "integer", "minimum": 0, "description": "Largest target customer size; omit for no upper limit." },
        "product_positioning_notes": { "type": "string", "maxLength": 3000, "description": "EDITORIAL-ONLY / not_for_direct_publication. What the product does and who it is for, in plain factual terms. Reference material for fact-checking; FSH writes its own descriptions." },
        "classification": { "$ref": "#/$defs/classification" },
        "features": {
          "type": "array",
          "description": "Optional feature assertions. Tri-state: present=true (has it), present=false (explicitly lacks it), or omit the item (unknown).",
          "items": { "$ref": "#/$defs/feature" }
        },
        "media": { "$ref": "#/$defs/media" },
        "source_urls": {
          "type": "array",
          "description": "Public URLs that back up the facts in this product (spec sheets, pricing pages, app store listings).",
          "items": { "type": "string", "format": "uri" }
        },
        "smart_tool_details": { "$ref": "#/$defs/smartToolDetails" },
        "training_details": { "$ref": "#/$defs/trainingDetails" },
        "mentorship_details": { "$ref": "#/$defs/mentorshipDetails" }
      },
      "allOf": [
        {
          "if": { "properties": { "product_kind": { "const": "physical_tool" } } },
          "then": { "required": ["smart_tool_details"] }
        },
        {
          "if": { "properties": { "product_kind": { "const": "training" } } },
          "then": { "required": ["training_details"] }
        },
        {
          "if": { "properties": { "product_kind": { "const": "mentorship" } } },
          "then": { "required": ["mentorship_details"] }
        }
      ]
    },
    "classification": {
      "type": "object",
      "description": "Where this product belongs. SUGGESTION ONLY — editorial assignment is authoritative. Slugs are validated server-side against catalogs/.",
      "additionalProperties": false,
      "required": ["primary_index_key"],
      "properties": {
        "primary_index_key": {
          "type": "string",
          "enum": ["presence", "engagement", "operations", "design", "measurements", "training", "value-added-services"],
          "description": "The single best-fit index. See catalogs/index-keys.json."
        },
        "index_keys": {
          "type": "array",
          "description": "All indexes this product fits (include the primary).",
          "items": { "type": "string", "enum": ["presence", "engagement", "operations", "design", "measurements", "training", "value-added-services"] }
        },
        "primary_category_slug": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$", "description": "Best-fit subcategory slug from catalogs/categories.json, e.g. 'smart-thermostats'." },
        "secondary_category_slugs": { "type": "array", "items": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" } }
      }
    },
    "feature": {
      "type": "object",
      "description": "A single feature assertion. Use the canonical shape (feature_slug) when a slug exists in catalogs/canonical-features.json; otherwise use the fallback shape (vendor_label + description) and editorial will map it.",
      "oneOf": [
        {
          "additionalProperties": false,
          "required": ["feature_slug"],
          "properties": {
            "feature_slug": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$", "description": "Slug from catalogs/canonical-features.json, e.g. 'gps-tracking'." },
            "present": { "type": "boolean", "description": "true = has it; false = explicitly does not; OMIT = unknown." },
            "detail": { "type": "string", "maxLength": 500, "description": "Optional clarification, e.g. 'Two-way SMS' or 'add-on only'." },
            "evidence_url": { "type": "string", "format": "uri" }
          }
        },
        {
          "additionalProperties": false,
          "required": ["vendor_label"],
          "properties": {
            "vendor_label": { "type": "string", "maxLength": 200, "description": "Your name for a capability that has no canonical slug yet." },
            "description": { "type": "string", "maxLength": 500 },
            "evidence_url": { "type": "string", "format": "uri" }
          }
        }
      ]
    },
    "media": {
      "type": "object",
      "description": "Optional asset URLs. Must be vendor-owned or licensed (see rights_attestation). URLs only — no binary data.",
      "additionalProperties": false,
      "properties": {
        "logo_url": { "type": "string", "format": "uri" },
        "favicon_url": { "type": "string", "format": "uri" },
        "screenshots": {
          "type": "array",
          "maxItems": 12,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["url"],
            "properties": {
              "url": { "type": "string", "format": "uri" },
              "caption": { "type": "string", "maxLength": 300 }
            }
          }
        }
      }
    },
    "smartToolDetails": {
      "type": "object",
      "description": "Extension for product_kind=physical_tool (hardware: thermostats, gauges, sensors, IoT devices). All fields optional; omit unknowns.",
      "additionalProperties": false,
      "properties": {
        "pricing_model": { "type": "string", "enum": ["retail", "msrp", "quote_only", "subscription_required", "free"], "description": "Hardware-specific pricing model." },
        "pricing_starting_at": { "type": "number", "minimum": 0, "description": "Retail/MSRP starting price in USD." },
        "pricing_notes": { "type": "string", "maxLength": 1000 },
        "connectivity": { "type": "array", "items": { "type": "string", "enum": ["bluetooth", "wifi", "usb", "cloud", "zigbee", "zwave", "none"] }, "description": "All connectivity methods the device supports." },
        "a2l_compatible": { "type": "boolean", "description": "Rated for A2L (mildly flammable, low-GWP) refrigerants." },
        "refrigerants_supported": { "type": "integer", "minimum": 0, "description": "Count of refrigerant profiles supported (for gauges/analyzers)." },
        "sensor_type": { "type": "string", "enum": ["piezo", "strain_gauge", "thermocouple", "thermistor", "infrared", "semiconductor", "catalytic", "mems", "electrochemical", "other"] },
        "data_logging": { "type": "boolean", "description": "Can record measurements over time." },
        "ecosystem_platform": { "type": "string", "maxLength": 120, "description": "Vendor platform/ecosystem, e.g. 'Google Nest Ecosystem'." },
        "ruggedness_rating": { "type": "string", "enum": ["lab", "light_duty", "field", "industrial", "mil_spec"] },
        "battery_type": { "type": "string", "maxLength": 80, "description": "e.g. 'AA alkaline', 'Rechargeable lithium', 'Hardwired'." },
        "warranty_years": { "type": "integer", "minimum": 0, "maximum": 100 },
        "accuracy_class": { "type": "string", "enum": ["reference", "lab_grade", "field_grade", "screening", "indicative"] },
        "has_mobile_app": { "type": "boolean" },
        "companion_app": { "type": "string", "maxLength": 120, "description": "Name of the companion app, e.g. 'Google Home'." },
        "measurement_range": { "type": "string", "maxLength": 255, "description": "e.g. '-40°F to 140°F'." },
        "measurequick_compatible": { "type": "boolean", "description": "Integrates with the MeasureQuick diagnostic platform." }
      }
    },
    "trainingDetails": {
      "type": "object",
      "description": "Extension for product_kind=training (courses, certifications, continuing education). All fields optional; omit unknowns.",
      "additionalProperties": false,
      "properties": {
        "pricing_model": { "type": "string", "enum": ["free", "per_course", "subscription", "tuition", "employer_paid", "included_with_membership"] },
        "pricing_starting_at": { "type": "number", "minimum": 0 },
        "pricing_notes": { "type": "string", "maxLength": 1000 },
        "pricing_period": { "type": "string", "maxLength": 30, "description": "e.g. 'per course', 'per month', 'per year'." },
        "delivery_format": { "type": "string", "enum": ["in_person", "virtual_live", "self_paced_online", "vr_ar", "hybrid", "video"] },
        "credential_awarded": { "type": "string", "enum": ["certification", "license_prep", "ce_credits", "diploma", "degree", "badge", "none"] },
        "target_audience": { "type": "string", "enum": ["pre_apprentice", "apprentice", "journeyman", "master", "business_owner", "all_levels"] },
        "duration": { "type": "string", "maxLength": 120, "description": "e.g. '40 hours', '6 weeks', 'Self-paced'." },
        "hands_on_component": { "type": "boolean" },
        "accreditation": { "type": "string", "maxLength": 255, "description": "e.g. 'EPA Section 608 approved', 'NATE-approved'." },
        "a2l_refrigerant_coverage": { "type": "boolean", "description": "Covers A2L low-GWP refrigerants." },
        "ce_credits_awarded": { "type": "integer", "minimum": 0 },
        "gi_bill_va_eligible": { "type": "boolean" },
        "geographic_scope": { "type": "string", "enum": ["local", "regional", "state", "national", "virtual_nationwide", "international", "canada"] },
        "hvac_systems_covered": { "type": "array", "items": { "type": "string", "maxLength": 80 }, "description": "e.g. ['heat_pump', 'furnace', 'split_system']." },
        "spanish_language_available": { "type": "boolean" }
      }
    },
    "mentorshipDetails": {
      "type": "object",
      "description": "Extension for product_kind=mentorship (coaching programs, peer groups, advisory). All fields optional; omit unknowns.",
      "additionalProperties": false,
      "properties": {
        "pricing_model": { "type": "string", "enum": ["membership", "per_event", "subscription", "free", "custom", "franchise_fee"] },
        "pricing_starting_at": { "type": "number", "minimum": 0 },
        "pricing_notes": { "type": "string", "maxLength": 1000 },
        "pricing_period": { "type": "string", "maxLength": 30, "description": "e.g. 'per month', 'per year', 'per session'." },
        "delivery_format": { "type": "string", "enum": ["in_person", "virtual", "hybrid", "self_paced", "community"] },
        "target_revenue_range": { "type": "string", "enum": ["under_500k", "500k_1m", "1m_3m", "3m_10m", "10m_plus", "any"], "description": "Customer business revenue this program targets." },
        "meeting_frequency": { "type": "string", "enum": ["weekly", "biweekly", "monthly", "quarterly", "annual", "on_demand"] },
        "coaching_ratio": { "type": "string", "enum": ["group", "one_on_one", "hybrid_coaching"] },
        "focus_areas": { "type": "array", "items": { "type": "string", "maxLength": 80 }, "description": "e.g. ['business_growth', 'technical_skills', 'sales']." }
      }
    }
  }
}
