{
"id": "ink.igav.whatmealtodo.defs",
"defs": {
"head": {
"type": "token",
"description": "Matched on the head noun only — pantry 'flour' against recipe 'all-purpose flour'. Weaker than exact, and shown as such."
},
"exact": {
"type": "token",
"description": "The recipe's canonical ingredient equals a canonical name in the pantry."
},
"staple": {
"type": "token",
"description": "Something the kitchen always holds rather than happens to hold. As a pantryItem `kind`, that is what its owner asserted about it; as a match verdict, it is why the ingredient counted. Said out loud in both places rather than folded silently into the coverage, because 'you keep this around' is a weaker claim than 'you have this right now' and a reader is entitled to tell them apart."
},
"missing": {
"type": "token",
"description": "Neither held nor kept on hand. This is what a shopping list is made of."
},
"proposed": {
"type": "token",
"description": "The name was produced by the normalizer and accepted as offered."
},
"quantity": {
"type": "object",
"required": [
"value"
],
"properties": {
"unit": {
"type": "string",
"maxLength": 32,
"description": "Plain lowercase strings rather than tokens, and deliberately open. A person types what they have, and a vocabulary that refused 'punnet' would push the real value into the free text where nothing can read it. The known values are the ones the normalizer emits.",
"knownValues": [
"g",
"kg",
"ml",
"l",
"tsp",
"tbsp",
"cup",
"oz",
"lb",
"clove",
"can",
"jar",
"package",
"piece",
"slice",
"stick",
"pinch",
"dash",
"basket",
"bunch",
"handful",
"sprig",
"stalk",
"head",
"bag",
"box",
"bottle",
"packet",
"sachet",
"envelope",
"tin",
"tub"
]
},
"value": {
"type": "number",
"minimum": 0
}
},
"description": "How much of something there is. Optional wherever it appears: 'some flour' is a legitimate pantry entry, and a recipe that says 'a handful of parsley' has no number to give."
},
"unparsed": {
"type": "token",
"description": "The normalizer could not reduce the ingredient to a name, so no claim is made about it either way. Distinct from `missing`: one is an answer, the other is a failure to have one."
},
"corrected": {
"type": "token",
"description": "A person overrode the normalizer's proposal. Every one of these is a case the rules got wrong, which is why it is recorded rather than discarded."
},
"ingredient": {
"type": "token",
"description": "A pantry item the kitchen happens to hold — a stock level, and the kind that runs out. The default reading of a pantryItem with no `kind`."
},
"recipeView": {
"type": "object",
"required": [
"uri",
"cid",
"author",
"name",
"indexedAt"
],
"properties": {
"cid": {
"type": "string",
"format": "cid"
},
"uri": {
"type": "string",
"format": "at-uri"
},
"name": {
"type": "string",
"maxLength": 255
},
"text": {
"type": "string",
"maxLength": 3000
},
"author": {
"type": "string",
"format": "did"
},
"indexedAt": {
"type": "string",
"format": "datetime",
"description": "When this AppView indexed it. Says nothing about when it was written — that is the record's own createdAt."
},
"totalTime": {
"type": "string",
"format": "duration"
},
"recipeCuisine": {
"type": "string"
},
"recipeCategory": {
"type": "string"
},
"suitableForDiet": {
"type": "array",
"items": {
"type": "string"
}
}
},
"description": "A recipe as this AppView holds it. A projection of exchange.recipe.recipe, not a copy: only the fields a suggestion needs."
},
"suggestion": {
"type": "object",
"required": [
"recipe",
"ingredients",
"coverage",
"missing"
],
"properties": {
"recipe": {
"ref": "#recipeView",
"type": "ref"
},
"missing": {
"type": "array",
"items": {
"type": "string",
"maxLength": 100
},
"description": "The canonical names with verdict `missing`. Empty means cook now."
},
"coverage": {
"type": "number",
"maximum": 1,
"minimum": 0,
"description": "Matched ingredients over total, items kept on hand counted as matched, unparsed ingredients counted against — an ingredient we could not read is not an ingredient we have."
},
"unreadable": {
"type": "integer",
"minimum": 0,
"description": "How many ingredients came back unparsed. Surfaced next to the result rather than hidden, so a suspiciously poor match is legible as a normalizer failure instead of a missing ingredient."
},
"ingredients": {
"type": "array",
"items": {
"ref": "#ingredientMatch",
"type": "ref"
}
}
},
"description": "One recipe measured against one pantry."
},
"ingredientMatch": {
"type": "object",
"required": [
"raw",
"verdict"
],
"properties": {
"raw": {
"type": "string",
"maxLength": 500,
"description": "The ingredient exactly as the recipe wrote it."
},
"verdict": {
"type": "string",
"knownValues": [
"ink.igav.whatmealtodo.defs#exact",
"ink.igav.whatmealtodo.defs#head",
"ink.igav.whatmealtodo.defs#staple",
"ink.igav.whatmealtodo.defs#missing",
"ink.igav.whatmealtodo.defs#unparsed"
]
},
"canonical": {
"type": "string",
"maxLength": 100,
"description": "What the normalizer reduced it to. Absent when the verdict is unparsed."
}
},
"description": "One recipe ingredient, and what became of it. Carried per ingredient rather than summarised, because a coverage number with no account of how it was reached is indistinguishable from a bug."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}