Inspiration
For centuries, knowledge about plants and how they were prepared was written down in manuscripts and old books. Today most of it is hard to use: the language and spelling have changed (a medieval recipe says sauge, not sage), the texts are spread across collections, and nobody can search them by plant. Local plant names, including the Albanian names used in Kosovo and Albania, are at risk of being forgotten. We wanted a tool that makes this knowledge searchable without losing track of where each fact came from.
What it does
EthnoLens AI turns historical botanical and medical texts into a structured, searchable archive.
- Scan & Discover: upload a page (text, image or PDF). EthnoLens runs OCR when needed, extracts every plant mention (historical name, plant part, preparation, described use, other ingredients) and marks the name in gold inside the original passage.
- Archive: search by historical spelling, modern name, scientific name or Albanian name, and filter by plant part, preparation, source and language. Each plant opens as a specimen sheet next to its passage.
- EthnoMap: Albanian local plant names from a published field study (Mustafa et al. 2012, Albanian Alps, Kosovo), shown only where a source documents the region.
- Ask: answers questions only from archive passages, with citations you can open. If no passage supports an answer, it says the archive has no data. It refuses requests for medical advice, doses or treatments, in English and Albanian.
Every record keeps its source, folio, passage, an extraction confidence and a mapping confidence, and whether the name is stated in the text or only inferred. Example: “For þe coughe . tak **sauge* and rue and comyn…”* (MS Add. 9308, f. 5v, c. 1400) becomes sauge → sage → Salvia officinalis (GBIF: high) → Albanian name Sherbela.
How we built it
- Data: 100 recipes from MS Add. 9308 (Middle English, c. 1400) from the Curious Cures transcriptions at Cambridge Digital Library; 25 printed pages from the Biodiversity Heritage Library (FineBooks BHL IMPACT ground truth) to test OCR; 100 Albanian names for 98 species from Mustafa et al. (2012).
- Extraction: an LLM (
gpt-oss-20bon Groq, temperature 0) returns JSON, which is validated against our shared record schema with Pydantic. The application checks that each name really appears in the passage and stores its exact character position. - Taxonomy: the LLM proposes a Latin name for each normalised plant name, and the GBIF species-match API checks it. Only an exact species-level match is high; spelling variants are medium; genus-only matches and near-miss synonyms stay candidate and are never shown as identifications. Accepted names link records to the Albanian names.
- Stack: FastAPI + SQLite (every record must belong to a stored passage), Chroma for ranking passages, Tesseract OCR, and Streamlit + Folium for the interface.
- Reproducible: one command (
python -m pipeline.rebuild) downloads the sources and rebuilds the whole database from zero. Texts with an unclear licence are downloaded from the source, not redistributed.
Evaluation
We annotated 50 recipes ourselves as a test set (33 by hand, 17 with AI assistance, marked and reported separately). A match is the same recipe, historical name and plant part:
$$P = \frac{TP}{TP+FP} = 80.9\%, \qquad R = \frac{TP}{TP+FN} = 60.8\%, \qquad F_1 = \frac{2PR}{P+R} = 69.4\%$$
- Traceability: 100% of the 200 extracted records link to their source passage, and every stored span is valid.
- OCR: 2.7% mean character error rate on 25 printed BHL pages.
- Safety: 15/15 medical questions refused; questions with no supporting passage cite nothing.
- Albanian names: 31 of the 200 records are linked to an Albanian local name.
Challenges we ran into
- Historical spelling. Users ask about “sage”, but the manuscript says “sauge”. Ask links modern and Albanian names (Sherbela) to the historical spellings found in the records.
- Confident AI mistakes. The model read sal gemme (rock salt) as sage, and smalache (smallage, wild celery) as chamomile. A review caught both: we rejected or corrected these records, and our tests now check that every “sage” citation really contains a historical form of sage.
- Vector search always answers. Chroma returned its nearest passages even for questions about bananas. A passage is now cited only if it shares a content word with the question.
- Taxonomic traps. A misspelled name can land on a synonym of a different species, so those stay candidates.
- Limits and licences. The free LLM tier allows about 200,000 tokens a day, so we split the extraction across team members and merged the results. The Curious Cures transcriptions have no stated licence, so we never redistribute them.
Accomplishments that we're proud of
A pipeline that goes from a medieval manuscript line to a searchable record, where every claim can be checked against its source, and where the system says “candidate” or “no data” instead of guessing.
What we learned
Historical text breaks the usual NLP assumptions: spelling varies and names change meaning over time. A model can be confidently wrong, so evidence, review and honest uncertainty mattered more than adding features. We also learned to measure everything against our own annotated data rather than trusting the model.
What's next
More sources from Kosovo, Albania and the Balkans, handwritten text recognition for uploaded manuscripts, and review tools so researchers can verify or reject records.
Not medical advice: described uses are historical records, not recommendations.
Built With
- chromadb
- fastapi
- folium
- gbif-api
- groq
- leaflet.js
- openstreetmap
- pydantic
- python
- sqlite
- streamlit
- tesseract
- uvicorn