{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/ayhire-com/json-schema/ayhire-com-role-funnel-schema.json",
  "title": "RoleFunnel",
  "x-generated": "2026-10-30",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/ayhire-com-openapi.yml#/components/schemas/RoleFunnel",
  "type": "object",
  "properties": {
    "role_id": {
      "type": "string",
      "format": "uuid"
    },
    "role_created_at": {
      "type": "string",
      "format": "date-time"
    },
    "found": {
      "type": "integer",
      "description": "People in the role as Home counts them, plus anyone with an outcome"
    },
    "tracked": {
      "type": "boolean",
      "description": "False until someone records an outcome in AyHire (decisions saved before outcomes existed don't count)"
    },
    "stages": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "stage": {
            "type": "string",
            "enum": [
              "reviewed",
              "shortlisted",
              "contacted",
              "replied",
              "interview",
              "offer",
              "hired"
            ]
          },
          "reached": {
            "type": "integer",
            "description": "Reached this stage or a later one"
          },
          "recorded": {
            "type": "integer",
            "description": "This stage written down"
          },
          "implied": {
            "type": "integer",
            "description": "reached - recorded: a later stage was recorded and this one wasn't"
          },
          "waiting": {
            "type": "integer",
            "description": "Reached it, nothing later recorded, not declined: no outcome recorded (not zero)"
          }
        },
        "required": [
          "stage",
          "reached",
          "recorded",
          "implied",
          "waiting"
        ]
      }
    },
    "conversions": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "enum": [
              "found",
              "reviewed",
              "shortlisted",
              "contacted",
              "replied",
              "interview",
              "offer",
              "hired"
            ]
          },
          "to": {
            "type": "string",
            "enum": [
              "reviewed",
              "shortlisted",
              "contacted",
              "replied",
              "interview",
              "offer",
              "hired"
            ]
          },
          "num": {
            "type": "integer"
          },
          "den": {
            "type": "integer"
          },
          "rate": {
            "type": [
              "number",
              "null"
            ],
            "description": "0-1, or null when there is nothing to divide by (never 0% of nobody)"
          }
        },
        "required": [
          "from",
          "to",
          "num",
          "den",
          "rate"
        ]
      }
    },
    "declined": {
      "type": "object",
      "properties": {
        "total": {
          "type": "integer"
        },
        "after": {
          "type": "object",
          "properties": {},
          "description": "By the furthest stage reached before declining (found: before a review)",
          "additionalProperties": {
            "type": "integer"
          }
        }
      },
      "required": [
        "total",
        "after"
      ]
    },
    "interview_conversion": {
      "type": "object",
      "properties": {
        "interviewed": {
          "type": "integer"
        },
        "offered": {
          "type": "integer"
        },
        "hired": {
          "type": "integer"
        },
        "offer_rate": {
          "type": [
            "number",
            "null"
          ],
          "description": "0-1, or null when there is nothing to divide by (never 0% of nobody)"
        },
        "hire_rate": {
          "type": [
            "number",
            "null"
          ],
          "description": "0-1, or null when there is nothing to divide by (never 0% of nobody)"
        }
      },
      "required": [
        "interviewed",
        "offered",
        "hired",
        "offer_rate",
        "hire_rate"
      ]
    },
    "time_to_first_shortlist": {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "seconds": {
              "type": "integer"
            },
            "at": {
              "type": "string",
              "format": "date-time"
            },
            "github_username": {
              "type": "string"
            },
            "source": {
              "type": [
                "string",
                "null"
              ]
            },
            "approximate": {
              "type": "boolean",
              "description": "From a decision saved before outcomes existed: an upper bound"
            }
          },
          "required": [
            "seconds",
            "at",
            "approximate"
          ]
        },
        {
          "type": "null"
        }
      ]
    },
    "by_source": {
      "type": "object",
      "properties": {
        "human": {
          "type": "integer"
        },
        "automated": {
          "type": "integer"
        },
        "unknown": {
          "type": "integer"
        }
      }
    },
    "corrections": {
      "type": "integer",
      "description": "Outcomes corrected or removed (never counted)"
    }
  },
  "required": [
    "role_id",
    "found",
    "tracked",
    "stages",
    "conversions",
    "declined",
    "interview_conversion",
    "time_to_first_shortlist"
  ]
}