Skip to content
C.W.K.
Lesson 02 of 06 · published

Siblings Across Cuisines

~14 min · siblings, polymorphism, evidence, inference

Level 0Mise en Place
0 XP0/43 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

What a sibling is

The reading rubric defines it in one line: siblings are dishes, often from other cuisines, that inherit the same principle. For each dish on the plate, Pippa may list them, and each one carries five fields: name_original, name_ko, cuisine, the principle it shares (named exactly as she named it above), and why_same, a sentence on how it inherits that principle.

The rubric asks for three to five siblings across different cuisines when the principle supports them, and says fewer is fine and none is fine. The code caps the list at five (dish.siblings[:5]). This is the part of Sizzle that derives new dishes. Dad photographs one plate, and the tree grows by the dishes that plate implies.

Polymorphism you can eat

The design's worked example shows the shape. The root class is a technique: move aroma into fat first, then cook in that fat. Its interface is a set of roles, mainly an aromatic, a fat and something that cooks in the fat. Each sibling binds those roles with different members:

SiblingAromaticFatWhat cooks in it
Gambas al ajillo (Spain)Garlic, chiliOlive oilPrawns
Aglio e olio (Italy)Garlic, chiliOlive oilPasta, tossed through
Mushroom ajillo (Spain)GarlicOlive oilMushrooms
Scallion-oil fried rice (Korea)ScallionNeutral oilRice
爆香 stir-fry (China)Garlic, ginger, scallionVery hot oilThe rest of the wok
Sofrito for paellaOnion, garlic, pepperOlive oilThe rice and its stock

Different kitchens, one principle. A cook who holds the class can stand at the stove with whatever fills those roles tonight. The oo-quest teaches this reading in Track 4, under "Polymorphism Across Cuisines", and it ends that section with a warning worth carrying here: a shared structural role is a hypothesis to taste and revise, not an identity. Its table reads cuisines along flavor axes. Sizzle's principles are techniques with a role interface, which is the decomposition that can be stored, matched against a pantry and derived from a photo. And Sizzle stores the warning too, as a field.

What the code does with each sibling

The second half of integrate walks the list. For each sibling it:

  1. skips it if it has neither an original nor a Korean name;
  2. looks for an existing dish or variant by original name, then by Korean name, using the same folded matching as every other lookup;
  3. picks a parent: the principle the sibling names, if that name matches one this reading resolved, else the dish's first resolved principle, or none at all when the reading resolved no principle for this dish;
  4. proposes a new dish if nothing matched, with origin: "sibling", the Korean name as its gloss, the cuisine, and why_same as its summary;
  5. if a dish already existed, refreshes its words only when it was itself a sibling proposal, and adds the parent edge unless it is the very dish on this plate.

Three consequences are easy to miss. A sibling class has no role bindings. Nothing on the plate showed how it fills the roles, so its page carries only why it belongs. The principle match in step 3 uses a light key (spacing and case only), so a sibling whose principle Pippa spelled a little differently still lands under the dish's first principle instead of floating free. That fallback needs a principle to fall back to. When the reading resolved none for the dish, a new sibling is proposed with no parents and waits under "Not yet under a principle" on the Tree page. And step 5 means a dish already in the tree can gain a new parent from a sibling mention. The two-principle cap limits what one reading may claim about one dish. It does not cap how many principles a dish can inherit over time.

Inferred until met

A sibling arrives with no plate linked to it, and evidence in Sizzle is computed from links, never stored. So a fresh sibling reads as inferred: "Pippa's inference" on the web, a sparkles icon on the phone. The engine's own test checks exactly this. After one gambas plate, the dish on the plate is encountered, aglio e olio is inferred, and the Korean-named sibling exists in the tree.

Nothing has to be edited for that to change. When Dad later eats aglio e olio and photographs it, the new reading names it as the dish on the plate. _resolve_dish finds the existing sibling class by name and links the new plate to it. The same row now reads "Eaten". That is the web's label for encountered, and encountered means only that at least one plate is linked and none of them was cooked at home. A plate Dad marked as seen at a market would make the row read "Eaten" just the same, so the label says more than the status knows. A plate marked as cooked at home makes it "Cooked". Its origin still says sibling, and its status is still proposed. A linked plate proves he met the dish. Adopting it is a separate act that only Dad performs.

Principle: Store an inference as a real record and compute its trust from evidence. The record never has to change for the trust to change.

Why the badge is not decoration

Sizzle's invariant 5 says evidence status is always shown, and the domain model says it bluntly: an inferred sibling never looks like a tasted one. A tree of fifty dishes where forty are Pippa's guesses is useful only if you can see which forty. Without the badge, the tree would slowly convince Dad that he knows dishes he has never tasted.

Code

cwkSizzle, backend/reading.py: integrate, second half: inferred siblings (verbatim, lines 224-239)·python
        for sibling in dish.siblings[:5]:
            name = sibling.name_original or sibling.name_ko
            if not name.strip():
                continue
            existing = sizzle.find_dish(sibling.name_original, sibling.name_ko)
            parent = principle_by_name.get(_key(sibling.principle)) or (principle_ids[0] if principle_ids else None)
            if existing is None:
                sizzle.propose_class(kind="dish", name=name, gloss=sibling.name_ko, cuisine=sibling.cuisine,
                                     summary=sibling.why_same, origin="sibling",
                                     parents=[parent] if parent else [], source_encounter=encounter_id)
                continue
            if existing["origin"] == "sibling":
                _refresh(sizzle, existing, {"gloss": sibling.name_ko, "cuisine": sibling.cuisine,
                                            "summary": sibling.why_same})
            if parent and existing["id"] != dish_id:
                sizzle.ensure_parent(existing["id"], parent)
cwkSizzle, backend/tests/test_tree.py: one plate, one eaten dish, inferred siblings (verbatim, lines 11-23)·python
def test_a_reading_grows_the_tree_with_evidence(make_client, fake_pippa):
    client = make_client()
    encounter_id = capture(client, "req-tree-001")["encounter"]["id"]
    body = _read(client, encounter_id)
    assert body["view"]["unsure"], "doubts are carried, not hidden"
    tree = client.get("/api/tree").json()
    assert len(tree["principles"]) == 1
    principle = tree["principles"][0]
    assert principle["status"] == "proposed"
    by_name = {d["name"]: d for d in principle["dishes"]}
    assert by_name["Gambas al ajillo"]["evidence"]["status"] == "encountered"
    assert by_name["Aglio e olio"]["evidence"]["status"] == "inferred"
    assert "파기름 볶음밥" in by_name

External links

Exercise

Add a model-inferred suggestion to software you are building: related articles for a post, likely duplicates for a bug report, similar products for an item. Store each suggestion as a real entity with an origin field, and compute its evidence status from links to real records instead of storing a flag. Write a test that asserts a new suggestion reads as inferred, then link a real record to it and assert it now reads as confirmed without any update to the suggestion's own row.
Hint
Keep evidence out of the table entirely. Sizzle's evidence() runs a query over current links every time a class is read, so there is no flag to forget to update. Cap how many suggestions one model call may add, the way the sibling loop takes at most five.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.