The FoodOn ontology

FoodOn is the backbone FoodScholar projects onto — a ~39k-term ontology of food products with a real is-a hierarchy. fs.ontology is the lookup API used by the linker, the Layer A projection, and Layer C prompts.

Lazy loading & caching

The ontology loads on first access to fs.ontology: it parses config.ontology.foodon_path (OWL, via pronto) and caches the result to a Parquet file beside it, so subsequent runs skip the parse.

ontology:
  foodon_path: data/foodon.owl
  cache_path: data/foodon_cache.parquet
  prefix_filter: ["FOODON:"]    # restrict to a prefix, or null to load all

Lookup API

fs.ontology.name_to_id("olive oil")                  # "FOODON:..." | None
fs.ontology.id_to_label("FOODON:03309927")
fs.ontology.id_to_synonyms("FOODON:03309927", include_related=True)
fs.ontology.id_to_ancestors("FOODON:03309927")       # closed transitive set
fs.ontology.id_to_descendants("FOODON:00001002")
fs.ontology.search("olive", limit=25)

id_to_ancestors is what Layer A walks to build lifted support — a chunk mentioning cow milk counts as evidence for every class above it:

        flowchart TD
    A[cow milk] --> B[mammalian milk product]
    B --> C[milk or milk based food product]
    C --> D[dairy food product]
    D --> E[vertebrate food product]
    E --> F[animal food product]
    F --> G[Foods]
    
fs.ontology.id_to_ancestors("FOODON:03310029")
# {"FOODON:03315150",   # mammalian milk product
#  "FOODON:00001257",   # milk or milk based food product
#  "FOODON:00004242",   # animal food product
#  "FOODON:00001002", ...}   # ... up to Foods (the closed transitive set)

name_to_id and the synonym lookups are exact-match conveniences; the production NEL linker does not use them — it resolves mentions by dense kNN over term embeddings (see Annotation).

One ontology, mostly one facet

FoodOn is a food ontology. That’s why the foods facet is rich (a few hundred shelves) while health, nutrients, dietary_patterns, allergies, and sustainability are scaffolded but essentially unpopulated in the current corpus — there’s no food-class backbone to project them onto. prefix_filter: ["FOODON:"] keeps the loaded ontology to FOODON ids (real .owl files embed CHEBI/NCBITaxon/BFO terms inline), which the food linker wants — but it also means non-food facets have no ontology to draw from here. Treat foods as the production facet today; the multi-facet design is in place for ontologies that would back the others.

Tip

For tests and notebooks you can skip the loader entirely and attach an in-memory API built from a handful of terms:

from foodscholar.ontology import FoodOnAPI
fs.attach_ontology(FoodOnAPI(terms))

That’s how the unit tests exercise Layer A against a tiny fixture ontology with no OWL file at all.