{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/medianfi-com/json-schema/medianfi-com-practice-packet-v1-schema.json",
  "title": "PracticePacketV1",
  "description": "Practice packet format v1 (#1805). Aggregates only: money is integer cents in `currency`, counts are nonnegative integers, dates are YYYY-MM-DD and months YYYY-MM. Labels are reviewed display labels and carry the email and phone tripwires; no other free text is accepted. Every month has its practice totals, every channel its spend row and every therapist its service and retention rows; acquisition rows may be sparse (an absent therapist and channel pair is zero) because each month's rows must add up to the month's practice new clients.\n",
  "x-generated": "2026-10-30",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/medianfi-com-openapi.json#/components/schemas/PracticePacketV1",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "formatVersion",
    "companyId",
    "currency",
    "timezone",
    "reportWindow",
    "history",
    "definitions",
    "sources",
    "approval",
    "reconciliation",
    "therapists",
    "channels",
    "acquisition",
    "practiceAcquisition",
    "spend",
    "serviceValue",
    "practiceServiceValue",
    "acquiredCohorts",
    "retention",
    "practiceRetention"
  ],
  "properties": {
    "formatVersion": {
      "type": "string",
      "enum": [
        "practice-packet/v1"
      ]
    },
    "companyId": {
      "type": "string",
      "format": "uuid"
    },
    "currency": {
      "type": "string",
      "pattern": "^[A-Z]{3}$",
      "description": "A supported two-decimal ISO 4217 code"
    },
    "timezone": {
      "type": "string",
      "description": "An IANA Area/Location time zone"
    },
    "reportWindow": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "start",
        "end"
      ],
      "description": "Inclusive whole months; `end` is the cutoff.",
      "properties": {
        "start": {
          "type": "string",
          "format": "date"
        },
        "end": {
          "type": "string",
          "format": "date"
        }
      }
    },
    "history": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "start",
        "complete"
      ],
      "description": "The service-value window runs from `start` to the cutoff and must not start after the report window; `complete` claims full practice history, so no client may be active at an opening snapshot before `start`.",
      "properties": {
        "start": {
          "type": "string",
          "format": "date"
        },
        "complete": {
          "type": "boolean"
        }
      }
    },
    "definitions": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "definitionVersion",
        "attributionPolicyVersion",
        "costPolicyVersion",
        "channelMappingVersion",
        "spendAllocationRule",
        "retentionWindowDays"
      ],
      "properties": {
        "definitionVersion": {
          "type": "string"
        },
        "attributionPolicyVersion": {
          "type": "string"
        },
        "costPolicyVersion": {
          "type": "string"
        },
        "channelMappingVersion": {
          "type": "string"
        },
        "spendAllocationRule": {
          "type": "string",
          "enum": [
            "acquired_client_share_v1"
          ],
          "description": "The allocation the API applies. Therapist-specific spend first; each channel-month's remaining spend is split by that channel's new clients per therapist, floors first, the leftover cents to the largest remainders, ties by therapist key."
        },
        "retentionWindowDays": {
          "type": "integer",
          "enum": [
            90
          ]
        }
      }
    },
    "sources": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "key",
          "label",
          "feeds",
          "dateBasis",
          "coverageStart",
          "coverageEnd",
          "extractedAt"
        ],
        "properties": {
          "key": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "feeds": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "acquisition",
                "attribution",
                "spend",
                "serviceValue",
                "retention"
              ]
            },
            "description": "acquisition decides first-ever clients (visits) and serviceValue collections and costs, both from history.start; attribution (channels), spend and retention from the report window start"
          },
          "dateBasis": {
            "type": "string",
            "enum": [
              "appointment_date",
              "service_date",
              "payment_date",
              "pay_date",
              "posting_date"
            ],
            "description": "The date that assigns this source's rows to a month"
          },
          "coverageStart": {
            "type": "string",
            "format": "date"
          },
          "coverageEnd": {
            "type": "string",
            "format": "date"
          },
          "extractedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the export was pulled. It must fall after coverageEnd has ended in the packet's timezone, because an export pulled on that day misses the rest of it."
          }
        }
      }
    },
    "approval": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "approvedBy",
        "approvedAt",
        "privacyReviewed"
      ],
      "properties": {
        "approvedBy": {
          "type": "string"
        },
        "approvedAt": {
          "type": "string",
          "format": "date-time"
        },
        "privacyReviewed": {
          "type": "boolean",
          "enum": [
            true
          ]
        }
      }
    },
    "reconciliation": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "status",
        "railOverlapRemovedCents",
        "pendingExcludedCount",
        "failedExcludedCount"
      ],
      "properties": {
        "status": {
          "type": "string",
          "enum": [
            "reconciled"
          ],
          "description": "The approver's attestation that the sources reconcile"
        },
        "railOverlapRemovedCents": {
          "type": "integer",
          "minimum": 0
        },
        "pendingExcludedCount": {
          "type": "integer",
          "minimum": 0
        },
        "failedExcludedCount": {
          "type": "integer",
          "minimum": 0
        }
      }
    },
    "therapists": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "key",
          "label",
          "role"
        ],
        "properties": {
          "key": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "clinician",
              "owner",
              "unassigned"
            ]
          }
        }
      }
    },
    "channels": {
      "type": "array",
      "minItems": 1,
      "description": "At least one channel; a channel with no spend still sends its explicit zero spend row.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "key",
          "label"
        ],
        "properties": {
          "key": {
            "type": "string",
            "description": "snake_case; `unattributed` is reserved"
          },
          "label": {
            "type": "string"
          }
        }
      }
    },
    "acquisition": {
      "type": "array",
      "description": "Actual new clients by month, first therapist and channel (`unattributed` when no channel is documented).",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "month",
          "therapist",
          "channel",
          "newClients"
        ],
        "properties": {
          "month": {
            "type": "string"
          },
          "therapist": {
            "type": "string"
          },
          "channel": {
            "type": "string"
          },
          "newClients": {
            "type": "integer",
            "minimum": 0
          }
        }
      }
    },
    "practiceAcquisition": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "month",
          "newClients"
        ],
        "properties": {
          "month": {
            "type": "string"
          },
          "newClients": {
            "type": "integer",
            "minimum": 0
          }
        }
      }
    },
    "spend": {
      "type": "array",
      "description": "Approved channel spend per month (explicit 0 for none); `direct` is therapist-specific spend. The rest is split by acquired-client share.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "month",
          "channel",
          "sourceSpendCents",
          "direct"
        ],
        "properties": {
          "month": {
            "type": "string"
          },
          "channel": {
            "type": "string"
          },
          "sourceSpendCents": {
            "type": "integer",
            "minimum": 0
          },
          "direct": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "therapist",
                "cents"
              ],
              "properties": {
                "therapist": {
                  "type": "string"
                },
                "cents": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            }
          }
        }
      }
    },
    "serviceValue": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "therapist",
          "grossCollectionsCents",
          "refundsCents",
          "directCostCents",
          "directCostUnknownReason",
          "uniqueClientsServed"
        ],
        "properties": {
          "therapist": {
            "type": "string"
          },
          "grossCollectionsCents": {
            "type": "integer",
            "minimum": 0
          },
          "refundsCents": {
            "type": "integer",
            "minimum": 0
          },
          "directCostCents": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0
          },
          "directCostUnknownReason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "owner_policy_pending",
              "not_supplied",
              null
            ]
          },
          "uniqueClientsServed": {
            "type": "integer",
            "minimum": 0,
            "description": "Distinct clients served, including those with zero collections"
          }
        }
      }
    },
    "practiceServiceValue": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "grossCollectionsCents",
        "refundsCents",
        "uniqueClientsServed"
      ],
      "properties": {
        "grossCollectionsCents": {
          "type": "integer",
          "minimum": 0
        },
        "refundsCents": {
          "type": "integer",
          "minimum": 0
        },
        "uniqueClientsServed": {
          "type": "integer",
          "minimum": 0,
          "description": "Deduplicated across therapists independently"
        }
      }
    },
    "acquiredCohorts": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "month",
          "therapist",
          "channel",
          "newClients",
          "netCollectionsCents"
        ],
        "properties": {
          "month": {
            "type": "string"
          },
          "therapist": {
            "type": "string"
          },
          "channel": {
            "type": "string"
          },
          "newClients": {
            "type": "integer",
            "minimum": 0
          },
          "netCollectionsCents": {
            "type": "integer"
          }
        }
      }
    },
    "retention": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "month",
          "therapist",
          "openingActive",
          "retainedOpening",
          "closingActive",
          "newClients",
          "reactivated",
          "transfersIn",
          "transfersOut"
        ],
        "properties": {
          "month": {
            "type": "string"
          },
          "therapist": {
            "type": "string"
          },
          "openingActive": {
            "type": "integer",
            "minimum": 0
          },
          "retainedOpening": {
            "type": "integer",
            "minimum": 0
          },
          "closingActive": {
            "type": "integer",
            "minimum": 0
          },
          "newClients": {
            "type": "integer",
            "minimum": 0
          },
          "reactivated": {
            "type": "integer",
            "minimum": 0
          },
          "transfersIn": {
            "type": "integer",
            "minimum": 0
          },
          "transfersOut": {
            "type": "integer",
            "minimum": 0
          }
        }
      }
    },
    "practiceRetention": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "month",
          "lookbackComplete",
          "openingActive",
          "retainedOpening",
          "closingActive",
          "newClients",
          "reactivated"
        ],
        "properties": {
          "month": {
            "type": "string"
          },
          "lookbackComplete": {
            "type": "boolean"
          },
          "openingActive": {
            "type": "integer",
            "minimum": 0
          },
          "retainedOpening": {
            "type": "integer",
            "minimum": 0
          },
          "closingActive": {
            "type": "integer",
            "minimum": 0
          },
          "newClients": {
            "type": "integer",
            "minimum": 0
          },
          "reactivated": {
            "type": "integer",
            "minimum": 0
          }
        }
      }
    }
  }
}