{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://www.bidbro.com/data/scopespec/scopespec-0.1.schema.json",
  "title": "ScopeSpec 0.1 — comparable residential project brief",
  "description": "ScopeSpec is an open, versioned field list for a comparable residential project brief. It defines what a homeowner must state about a job, and what a contractor's quote must return, so that two quotes for the same project can be compared line for line instead of by headline price alone.",
  "type": "object",
  "properties": {
    "spec": {
      "type": "object",
      "title": "Document identity",
      "description": "Says which version of the standard a reader is holding, so a brief written today is still interpretable when 0.2 exists.",
      "properties": {
        "specVersion": {
          "description": "The ScopeSpec version this brief conforms to. `0.1` for this version.",
          "type": "string",
          "minLength": 1
        },
        "briefId": {
          "description": "An identifier unique within whoever issued the brief. Opaque; carries no personal information.",
          "type": "string",
          "minLength": 1
        },
        "created": {
          "description": "ISO 8601 date the brief was first issued.",
          "type": "string",
          "format": "date",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "revision": {
          "description": "Increments every time the brief changes after it has been sent to anyone. Bidders quote a revision, not a brief.",
          "type": "integer"
        },
        "locale": {
          "description": "BCP 47 language tag for the prose fields.",
          "type": "string",
          "minLength": 1
        }
      },
      "required": [
        "specVersion",
        "briefId",
        "created",
        "revision"
      ],
      "additionalProperties": false
    },
    "project": {
      "type": "object",
      "title": "The project",
      "description": "What is being asked for, in the words of the person asking, plus the timing constraints that change a price.",
      "properties": {
        "tradeProfile": {
          "description": "The trade profile whose additional required fields apply. `other` is valid and imposes no extra fields.",
          "type": "string",
          "enum": [
            "roofing-replacement",
            "bathroom-remodel",
            "kitchen-remodel",
            "hvac-replacement",
            "exterior-painting",
            "other"
          ]
        },
        "summary": {
          "description": "One to three sentences describing the work in plain language. Not a specification — the fields below are the specification.",
          "type": "string",
          "minLength": 1
        },
        "motivation": {
          "description": "Why the work is being done. Changes what a good contractor proposes: an active leak and a planned sale lead to different recommendations.",
          "type": "string",
          "minLength": 1
        },
        "desiredStart": {
          "description": "Earliest acceptable start date, if there is one.",
          "type": "string",
          "format": "date",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "schedulingFlexibility": {
          "description": "How much the start date can move. A hard deadline is a price input and must not be discovered at contract stage.",
          "type": "string",
          "enum": [
            "urgent",
            "firm-date",
            "flexible",
            "seasonal"
          ]
        },
        "budgetStated": {
          "description": "Whether the brief states a budget at all. Stating `false` is a legitimate and common answer.",
          "type": "boolean"
        }
      },
      "required": [
        "tradeProfile",
        "summary",
        "schedulingFlexibility",
        "budgetStated"
      ],
      "additionalProperties": false
    },
    "property": {
      "type": "object",
      "title": "The property",
      "description": "The physical facts that change the work, at a granularity that cannot identify the household.",
      "properties": {
        "propertyType": {
          "description": "The kind of dwelling.",
          "type": "string",
          "enum": [
            "single-family-detached",
            "townhouse",
            "condominium",
            "duplex",
            "manufactured",
            "other"
          ]
        },
        "postalCode": {
          "description": "Postal code only. Never a street address — see conformance rule C6.",
          "type": "string",
          "minLength": 1
        },
        "locality": {
          "description": "City or town name.",
          "type": "string",
          "minLength": 1
        },
        "region": {
          "description": "State or province code.",
          "type": "string",
          "minLength": 1
        },
        "yearBuilt": {
          "description": "Year of original construction, if known. Drives code, material and hazard assumptions.",
          "type": "integer"
        },
        "stories": {
          "description": "Number of storeys above grade. An access and labour input on every exterior trade.",
          "type": "integer"
        },
        "occupiedDuringWork": {
          "description": "Whether the household will be living in the property while the work runs. Changes sequencing, dust control and working hours.",
          "type": "boolean"
        },
        "petsOnSite": {
          "description": "Whether animals will be present. A gate-and-containment question, not a courtesy field.",
          "type": "boolean"
        }
      },
      "required": [
        "propertyType",
        "postalCode",
        "locality",
        "region",
        "occupiedDuringWork"
      ],
      "additionalProperties": false
    },
    "scope": {
      "type": "object",
      "title": "Scope — inclusions, exclusions, allowances",
      "description": "The heart of the standard. Comparability fails here more often than anywhere else, and it fails silently.",
      "properties": {
        "inclusions": {
          "description": "Every item of work the homeowner intends to be part of the price. One item per entry, written as a deliverable rather than a task.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "exclusions": {
          "description": "Work explicitly NOT in the price. Required even when empty, because an empty exclusions list is itself a statement — see conformance rule C3.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "allowances": {
          "description": "Budgets for selections not yet made. Required even when empty.",
          "type": "array",
          "items": {
            "$ref": "#/definitions/allowance"
          }
        },
        "siteConditions": {
          "description": "Known conditions a bidder would otherwise discover on site: soft decking, prior repairs, drainage, slope, known asbestos or lead, tight side yards.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "accessConstraints": {
          "description": "Anything limiting how crews, materials or a dumpster reach the work. Required even when empty.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "debrisHandling": {
          "description": "Who removes and disposes of debris. The most commonly assumed-both-ways line item in residential work.",
          "type": "string",
          "enum": [
            "homeowner",
            "contractor",
            "shared",
            "undecided"
          ]
        },
        "cleanupStandard": {
          "description": "The condition the site is returned in.",
          "type": "string",
          "enum": [
            "broom-clean",
            "vacuumed",
            "as-found",
            "unstated"
          ]
        }
      },
      "required": [
        "inclusions",
        "exclusions",
        "allowances",
        "siteConditions",
        "accessConstraints",
        "debrisHandling"
      ],
      "additionalProperties": false
    },
    "measurements": {
      "type": "object",
      "title": "Measurements",
      "description": "Quantities with units and provenance. A quantity whose origin is unstated cannot be checked, and an unchecked quantity is the usual reason two prices differ by a third.",
      "properties": {
        "quantities": {
          "description": "Each measurement the trade profile requires, plus any others that matter. Every entry carries name, value, unit, measurementMethod and confidence.",
          "type": "array",
          "items": {
            "$ref": "#/definitions/quantity"
          }
        },
        "quantitiesVerifiedBy": {
          "description": "Who is responsible for confirming the quantities before the price becomes binding.",
          "type": "string",
          "enum": [
            "homeowner",
            "contractor",
            "shared",
            "undecided"
          ]
        }
      },
      "required": [
        "quantities",
        "quantitiesVerifiedBy"
      ],
      "additionalProperties": false
    },
    "materials": {
      "type": "object",
      "title": "Materials and selections",
      "description": "Whether the selections are made, who buys them, and what remains an allowance.",
      "properties": {
        "selectionsComplete": {
          "description": "Whether every material selection has been made. `false` is normal and must be paired with allowances.",
          "type": "boolean"
        },
        "specifiedItems": {
          "description": "Items already chosen, named specifically enough to price. Required even when empty.",
          "type": "array",
          "items": {
            "$ref": "#/definitions/specifiedItem"
          }
        },
        "ownerSuppliedItems": {
          "description": "Anything the homeowner will supply. Required even when empty — an unstated owner-supplied item is a schedule risk nobody priced.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        }
      },
      "required": [
        "selectionsComplete",
        "specifiedItems",
        "ownerSuppliedItems"
      ],
      "additionalProperties": false
    },
    "permits": {
      "type": "object",
      "title": "Permits and approvals",
      "description": "Permit assumptions stated up front, because a permit assumption discovered late moves both price and schedule.",
      "properties": {
        "permitAssumedRequired": {
          "description": "Whether the brief assumes a permit is required. `unknown` is an honest and common answer, and is treated as a question for the bidder rather than a defect.",
          "type": "string",
          "enum": [
            "yes",
            "no",
            "unknown"
          ]
        },
        "permitPulledBy": {
          "description": "Who applies for and pays for the permit under this brief's assumptions.",
          "type": "string",
          "enum": [
            "homeowner",
            "contractor",
            "shared",
            "undecided"
          ]
        },
        "jurisdiction": {
          "description": "The permitting authority, where the homeowner knows it.",
          "type": "string",
          "minLength": 1
        },
        "hoaApprovalRequired": {
          "description": "Whether an HOA or architectural review approval is needed.",
          "type": "string",
          "enum": [
            "yes",
            "no",
            "unknown"
          ]
        },
        "inspectionsExpected": {
          "description": "Inspections the homeowner expects, where known.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        }
      },
      "required": [
        "permitAssumedRequired",
        "permitPulledBy",
        "hoaApprovalRequired"
      ],
      "additionalProperties": false
    },
    "documents": {
      "type": "object",
      "title": "Photos and documents",
      "description": "What evidence accompanies the brief. A brief with no photographs is quoted more cautiously, and the caution is priced.",
      "properties": {
        "photos": {
          "description": "Photographs supplied. Each entry states what the photo shows; a URL is optional and may be omitted for a brief that travels outside the issuing platform.",
          "type": "array",
          "items": {
            "$ref": "#/definitions/documentRef"
          }
        },
        "drawings": {
          "description": "Plans, sketches or measured drawings. Required even when empty.",
          "type": "array",
          "items": {
            "$ref": "#/definitions/documentRef"
          }
        },
        "reports": {
          "description": "Inspection reports, engineer's letters, prior estimates being replaced. Required even when empty.",
          "type": "array",
          "items": {
            "$ref": "#/definitions/documentRef"
          }
        }
      },
      "required": [
        "photos",
        "drawings",
        "reports"
      ],
      "additionalProperties": false
    },
    "assumptions": {
      "type": "object",
      "title": "Stated assumptions",
      "description": "The list every bidder must accept or restate. This is the mechanism that converts a difference in reading into a visible difference on the page.",
      "properties": {
        "assumptions": {
          "description": "One assumption per entry, written so it can be accepted or contradicted without ambiguity.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        }
      },
      "required": [
        "assumptions"
      ],
      "additionalProperties": false
    },
    "x-extensions": {
      "type": "object",
      "description": "Implementation-specific additions. Nothing here is part of the standard, and a consumer may ignore all of it without becoming non-conforming."
    }
  },
  "required": [
    "spec",
    "project",
    "property",
    "scope",
    "measurements",
    "materials",
    "permits",
    "documents",
    "assumptions"
  ],
  "additionalProperties": false,
  "definitions": {
    "allowance": {
      "type": "object",
      "title": "Allowance",
      "description": "A stated budget for a selection that has not been made. Conformance rule C4: an allowance without an amount is not an allowance.",
      "properties": {
        "item": {
          "type": "string",
          "minLength": 1,
          "description": "What the allowance is for."
        },
        "amount": {
          "type": "number",
          "minimum": 0,
          "description": "The budgeted amount."
        },
        "currency": {
          "type": "string",
          "pattern": "^[A-Z]{3}$",
          "description": "ISO 4217 currency code."
        },
        "covers": {
          "type": "string",
          "minLength": 1,
          "description": "What the amount covers — material only, material and labour, and so on."
        }
      },
      "required": [
        "item",
        "amount",
        "currency",
        "covers"
      ],
      "additionalProperties": false
    },
    "quantity": {
      "type": "object",
      "title": "Quantity",
      "description": "A measurement with its unit and its provenance. Conformance rule C2: a number without a unit and a measurementMethod is not comparable.",
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1,
          "description": "What is being measured."
        },
        "value": {
          "type": "number",
          "description": "The measured value."
        },
        "unit": {
          "type": "string",
          "minLength": 1,
          "description": "The unit, stated explicitly."
        },
        "measurementMethod": {
          "type": "string",
          "enum": [
            "tape-measured",
            "laser-measured",
            "aerial-report",
            "from-drawings",
            "estimated",
            "unknown"
          ]
        },
        "confidence": {
          "type": "string",
          "enum": [
            "measured",
            "approximate",
            "rough"
          ]
        }
      },
      "required": [
        "name",
        "value",
        "unit",
        "measurementMethod",
        "confidence"
      ],
      "additionalProperties": false
    },
    "specifiedItem": {
      "type": "object",
      "title": "Specified item",
      "description": "A material selection already made, named specifically enough to price.",
      "properties": {
        "item": {
          "type": "string",
          "minLength": 1
        },
        "specification": {
          "type": "string",
          "minLength": 1
        },
        "suppliedBy": {
          "type": "string",
          "enum": [
            "homeowner",
            "contractor",
            "shared",
            "undecided"
          ]
        }
      },
      "required": [
        "item",
        "specification",
        "suppliedBy"
      ],
      "additionalProperties": false
    },
    "documentRef": {
      "type": "object",
      "title": "Document reference",
      "description": "A photograph, drawing or report accompanying the brief. `url` is optional so a brief can travel outside the platform that issued it.",
      "properties": {
        "documentType": {
          "type": "string",
          "enum": [
            "photo",
            "drawing",
            "report",
            "prior-estimate",
            "other"
          ]
        },
        "describes": {
          "type": "string",
          "minLength": 1
        },
        "url": {
          "type": "string",
          "minLength": 1
        }
      },
      "required": [
        "documentType",
        "describes"
      ],
      "additionalProperties": false
    },
    "scopeSpecQuote": {
      "type": "object",
      "title": "ScopeSpec quote",
      "description": "The response half of the standard: what a quote must return for two quotes against the same brief to be comparable. Reference as scopespec-0.1.schema.json#/definitions/scopeSpecQuote.",
      "properties": {
        "priceBasis": {
          "description": "How the price is constructed. Two prices on different bases are not comparable and must not be presented as if they were.",
          "type": "string",
          "enum": [
            "fixed-price",
            "cost-plus",
            "time-and-materials",
            "not-to-exceed",
            "allowance-based"
          ]
        },
        "priceTotal": {
          "description": "The total for the scope as quoted, in the stated currency, exclusive of allowances listed separately.",
          "type": "number"
        },
        "currency": {
          "description": "ISO 4217 currency code.",
          "type": "string",
          "minLength": 1
        },
        "allowancesCarried": {
          "description": "The brief's allowances as carried in this quote, so identical allowances sit on identical base prices.",
          "type": "array",
          "items": {
            "$ref": "#/definitions/allowance"
          }
        },
        "assumptionsAccepted": {
          "description": "The brief's assumptions this quote accepts, verbatim.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "assumptionsRestated": {
          "description": "Assumptions the bidder replaced, with the replacement text. Required even when empty — this is where two apparently identical prices stop being identical.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "exclusionsAdded": {
          "description": "Anything the bidder excludes beyond the brief's own exclusions. Required even when empty.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "validUntil": {
          "description": "The date the price expires. A quote without an expiry cannot be compared against one with a short expiry on equal terms.",
          "type": "string",
          "format": "date",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "startWindow": {
          "description": "When work could begin, as a stated window rather than a promise of a date.",
          "type": "string",
          "minLength": 1
        },
        "durationEstimate": {
          "description": "Expected working duration, with the unit stated.",
          "type": "string",
          "minLength": 1
        },
        "paymentSchedule": {
          "description": "Each payment as a stated trigger and amount or percentage.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "warranty": {
          "description": "Workmanship warranty in plain words, stated separately from any manufacturer warranty.",
          "type": "string",
          "minLength": 1
        },
        "changeOrderPolicy": {
          "description": "How a change is priced and authorised. Unstated at quote stage, it is negotiated at the worst possible moment.",
          "type": "string",
          "minLength": 1
        },
        "licenseNumber": {
          "description": "The contractor's licence number as issued by the relevant authority. Optional in the standard because licensing regimes differ; verification is the reader's responsibility, not the standard's.",
          "type": "string",
          "minLength": 1
        },
        "insuranceOnFile": {
          "description": "Whether the bidder asserts current liability and workers' compensation cover. An assertion, not a verification — the standard never implies BidBro or anyone else has checked it.",
          "type": "boolean"
        }
      },
      "required": [
        "priceBasis",
        "priceTotal",
        "currency",
        "allowancesCarried",
        "assumptionsAccepted",
        "assumptionsRestated",
        "exclusionsAdded",
        "validUntil",
        "startWindow",
        "durationEstimate",
        "paymentSchedule",
        "warranty",
        "changeOrderPolicy"
      ],
      "additionalProperties": false
    }
  }
}
