Spaces:
Sleeping
Sleeping
Download app.py from alrobles/ecointeraction-extractor: direct link, hf CLI and curl.
- Browser
- Download file 10.9 kB
-
https://huggingface.co/spaces/alrobles/ecointeraction-extractor/resolve/main/app.py
- Command line
-
hf download hf://spaces/alrobles/ecointeraction-extractor/app.py
-
curl -L -o app.py https://huggingface.co/spaces/alrobles/ecointeraction-extractor/resolve/main/app.py
10.9 kB
| import re | |
| import json | |
| import time | |
| from typing import Tuple, List, Dict, Any | |
| import gradio as gr | |
| from compact_resolver import CompactTaxonomicResolver, CompactTaxon | |
| # Initialize Compact Backbone Resolver (2.1 MB SQLite, sub-millisecond lookup) | |
| resolver = CompactTaxonomicResolver() | |
| INTERACTION_RO_MAP = { | |
| "pollinates": {"ro_id": "RO:0002455", "label": "pollinates", "valence": "mutualistic"}, | |
| "disperses_seeds_of": {"ro_id": "RO:0002456", "label": "disperses seeds of", "valence": "mutualistic"}, | |
| "symbiont_of": {"ro_id": "RO:0002440", "label": "symbiont of", "valence": "mutualistic"}, | |
| "mycorrhizal_partner_of": {"ro_id": "RO:0002442", "label": "mycorrhizal partner of", "valence": "mutualistic"}, | |
| "preys_on": {"ro_id": "RO:0002439", "label": "preys on", "valence": "antagonistic"}, | |
| "herbivore_of": {"ro_id": "RO:0002470", "label": "herbivore of", "valence": "antagonistic"}, | |
| "parasite_of": {"ro_id": "RO:0002444", "label": "parasite of", "valence": "antagonistic"}, | |
| "parasitoid_of": {"ro_id": "RO:0002208", "label": "parasitoid of", "valence": "antagonistic"}, | |
| "pathogen_of": {"ro_id": "RO:0002556", "label": "pathogen of", "valence": "antagonistic"}, | |
| "vector_of": {"ro_id": "RO:0002459", "label": "vector of", "valence": "antagonistic"}, | |
| "competes_with": {"ro_id": "RO:0002441", "label": "competes with", "valence": "antagonistic"}, | |
| "allelopathic_to": {"ro_id": "RO:0002555", "label": "allelopathic to", "valence": "antagonistic"}, | |
| "epibiont_on": {"ro_id": "RO:0002553", "label": "epibiont on", "valence": "commensal_facilitative"}, | |
| "phoretic_on": {"ro_id": "RO:0002437", "label": "phoretic on", "valence": "commensal_facilitative"}, | |
| "associated_with": {"ro_id": "RO:0002437", "label": "associated with", "valence": "unspecified"} | |
| } | |
| BINOMIAL_REGEX = re.compile(r"\b([A-Z][a-z]{2,20}\s+[a-z]{2,25}(?:\s+subsp\.\s+[a-z]{2,20}|\s+var\.\s+[a-z]{2,20})?)\b") | |
| def extract_candidate_taxa(text: str) -> List[str]: | |
| """Finds candidate Linnaean binomials in text.""" | |
| matches = BINOMIAL_REGEX.findall(text) | |
| # Deduplicate preserving order | |
| seen = set() | |
| result = [] | |
| for m in matches: | |
| clean = m.strip() | |
| if clean.lower() not in seen: | |
| seen.add(clean.lower()) | |
| result.append(clean) | |
| return result | |
| def extract_and_ground(passage: str, source_taxon: str = "", target_taxon: str = "") -> Tuple[str, str, str]: | |
| """Extracts ecological interaction and grounds entities against the compact backbone.""" | |
| t0 = time.perf_counter() | |
| p_lower = passage.lower() | |
| # Auto-detect taxa candidates if not specified | |
| candidates = extract_candidate_taxa(passage) | |
| if not source_taxon and len(candidates) >= 1: | |
| source_taxon = candidates[0] | |
| if not target_taxon and len(candidates) >= 2: | |
| target_taxon = candidates[1] | |
| # Resolve taxa via compact SQLite backbone (<50 µs each) | |
| src_res = resolver.resolve(source_taxon) if source_taxon else None | |
| tgt_res = resolver.resolve(target_taxon) if target_taxon else None | |
| # Interaction cue detection & RO semantic mapping | |
| detected_type = "associated_with" | |
| detected_valence = "unspecified" | |
| ro_id = "RO:0002437" | |
| if any(w in p_lower for w in ["pollinat", "floral visit", "pollen transfer", "nectar rob"]): | |
| detected_type = "pollinates" | |
| detected_valence = "mutualistic" | |
| ro_id = "RO:0002455" | |
| elif any(w in p_lower for w in ["seed dispers", "frugivor", "endozoochor", "dispersal of seed"]): | |
| detected_type = "disperses_seeds_of" | |
| detected_valence = "mutualistic" | |
| ro_id = "RO:0002456" | |
| elif any(w in p_lower for w in ["mycorrhiz", "arbuscular", "root nodul", "ectomycorrhiz"]): | |
| detected_type = "mycorrhizal_partner_of" | |
| detected_valence = "mutualistic" | |
| ro_id = "RO:0002442" | |
| elif any(w in p_lower for w in ["prey", "predat", "gut content", "kill rate", "diet of", "consumed"]): | |
| detected_type = "preys_on" | |
| detected_valence = "antagonistic" | |
| ro_id = "RO:0002439" | |
| elif any(w in p_lower for w in ["herbivor", "defoliat", "grazing on", "feeding on leaves", "caterpillar on"]): | |
| detected_type = "herbivore_of" | |
| detected_valence = "antagonistic" | |
| ro_id = "RO:0002470" | |
| elif any(w in p_lower for w in ["parasitoid", "host pupa", "host larva", "oviposition in"]): | |
| detected_type = "parasitoid_of" | |
| detected_valence = "antagonistic" | |
| ro_id = "RO:0002208" | |
| elif any(w in p_lower for w in ["parasit", "infest", "ectoparasit", "endoparasit", "tick on", "flea on"]): | |
| detected_type = "parasite_of" | |
| detected_valence = "antagonistic" | |
| ro_id = "RO:0002444" | |
| elif any(w in p_lower for w in ["vector", "transmitted by", "arbovirus vector"]): | |
| detected_type = "vector_of" | |
| detected_valence = "antagonistic" | |
| ro_id = "RO:0002459" | |
| elif any(w in p_lower for w in ["allelopath", "competiti", "growth inhibition"]): | |
| detected_type = "allelopathic_to" | |
| detected_valence = "antagonistic" | |
| ro_id = "RO:0002555" | |
| # Evidence span locator | |
| sentences = re.split(r"(?<=[.!?])\s+", passage) | |
| evidence_span = passage.strip()[:240] | |
| for s in sentences: | |
| s_low = s.lower() | |
| if source_taxon and source_taxon.lower() in s_low and target_taxon and target_taxon.lower() in s_low: | |
| evidence_span = s.strip() | |
| break | |
| elif any(cue in s_low for cue in ["prey", "pollinat", "parasit", "host", "dispers", "symbio", "feed"]): | |
| evidence_span = s.strip() | |
| break | |
| elapsed_ms = (time.perf_counter() - t0) * 1000.0 | |
| # Build output payload | |
| structured_edge = { | |
| "source_taxon": { | |
| "verbatim": source_taxon, | |
| "canonical": src_res.canonical_name if src_res else source_taxon, | |
| "gbif_key": src_res.gbif_key if src_res else None, | |
| "ncbi_taxid": src_res.ncbi_taxid if src_res else None, | |
| "kingdom": src_res.kingdom if src_res else "Unknown" | |
| }, | |
| "target_taxon": { | |
| "verbatim": target_taxon, | |
| "canonical": tgt_res.canonical_name if tgt_res else target_taxon, | |
| "gbif_key": tgt_res.gbif_key if tgt_res else None, | |
| "ncbi_taxid": tgt_res.ncbi_taxid if tgt_res else None, | |
| "kingdom": tgt_res.kingdom if tgt_res else "Unknown" | |
| }, | |
| "interaction": { | |
| "type": detected_type, | |
| "valence": detected_valence, | |
| "ro_id": ro_id, | |
| "standard": "OBO Relations Ontology / IS-1.0" | |
| }, | |
| "evidence": { | |
| "verbatim_span": evidence_span, | |
| "confidence": 0.95 if (detected_type != "associated_with" and src_res and tgt_res) else 0.70 | |
| }, | |
| "metrics": { | |
| "inference_time_ms": round(elapsed_ms, 2), | |
| "backbone_engine": "SQLite B-Tree (2.1 MB / 23.9k taxa)" | |
| } | |
| } | |
| # Format Markdown Table for Grounding | |
| def format_row(role: str, name: str, res: CompactTaxon) -> str: | |
| if res: | |
| gbif_link = f"[{res.gbif_key}](https://www.gbif.org/species/{res.gbif_key})" if res.gbif_key else "—" | |
| ncbi_link = f"[{res.ncbi_taxid}](https://www.ncbi.nlm.nih.gov/Taxonomy/Browser/wwwtax.cgi?id={res.ncbi_taxid})" if res.ncbi_taxid else "—" | |
| return f"| **{role}** | *{res.canonical_name}* | `{res.kingdom}` | {ncbi_link} | {gbif_link} | ✅ Grounded |" | |
| else: | |
| return f"| **{role}** | *{name or 'N/A'}* | `Unknown` | — | — | ⚠️ Unresolved |" | |
| grounding_table = ( | |
| "| Role | Canonical Taxon | Kingdom | NCBI TaxID | GBIF UsageKey | Status |\n" | |
| "| :--- | :--- | :--- | :--- | :--- | :--- |\n" | |
| f"{format_row('Source Taxon', source_taxon, src_res)}\n" | |
| f"{format_row('Target Taxon', target_taxon, tgt_res)}\n" | |
| ) | |
| metrics_md = f"⚡ **Latency:** `{elapsed_ms:.2f} ms` | 🎯 **Confidence:** `{structured_edge['evidence']['confidence']:.2f}` | 🏛️ **Standard:** `OBO {ro_id}`" | |
| return json.dumps(structured_edge, indent=2), grounding_table, metrics_md | |
| CUSTOM_CSS = """ | |
| .metric-card { | |
| background-color: #f8fafc; | |
| border-radius: 8px; | |
| padding: 12px 16px; | |
| margin-bottom: 12px; | |
| } | |
| """ | |
| with gr.Blocks(title="EcoInteraction Extractor", css=CUSTOM_CSS) as demo: | |
| gr.Markdown("# 🌿 EcoInteraction Extractor") | |
| gr.Markdown( | |
| "Sub-millisecond System 1 Biological Interaction Extractor with Compact Linnaean Backbone Grounding. " | |
| "Standardized to **IS-1.0** and **OBO Relations Ontology**." | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=5): | |
| passage_in = gr.Textbox( | |
| label="Scientific Literature Passage", | |
| value="In subalpine meadows, Bombus bimaculatus visits flowers of Pedicularis groenlandica to extract nectar and facilitate pollen transfer.", | |
| lines=5, | |
| placeholder="Paste an excerpt, abstract sentence, or observational note..." | |
| ) | |
| with gr.Row(): | |
| src_in = gr.Textbox(label="Source Taxon (Auto-detected if blank)", placeholder="e.g. Bombus bimaculatus") | |
| tgt_in = gr.Textbox(label="Target Taxon (Auto-detected if blank)", placeholder="e.g. Pedicularis groenlandica") | |
| btn = gr.Button("Extract & Ground Interaction", variant="primary") | |
| gr.Examples( | |
| examples=[ | |
| [ | |
| "Adult females of Ixodes ricinus were collected while feeding on Apodemus flavicollis in deciduous temperate woodlands.", | |
| "Ixodes ricinus", | |
| "Apodemus flavicollis" | |
| ], | |
| [ | |
| "In subalpine meadows, Bombus bimaculatus visits flowers of Pedicularis groenlandica to extract nectar and facilitate pollen transfer.", | |
| "Bombus bimaculatus", | |
| "Pedicularis groenlandica" | |
| ], | |
| [ | |
| "Falco peregrinus was observed repeatedly preying upon Columba livia within urban foraging territories.", | |
| "Falco peregrinus", | |
| "Columba livia" | |
| ] | |
| ], | |
| inputs=[passage_in, src_in, tgt_in] | |
| ) | |
| with gr.Column(scale=5): | |
| metrics_display = gr.Markdown("⚡ **Ready for extraction**") | |
| gr.Markdown("### 🧬 Taxonomic Grounding") | |
| table_out = gr.Markdown() | |
| gr.Markdown("### 📦 Structured IS-1.0 Interaction Edge") | |
| json_out = gr.Code(label="Normalized JSON Output", language="json") | |
| btn.click( | |
| extract_and_ground, | |
| inputs=[passage_in, src_in, tgt_in], | |
| outputs=[json_out, table_out, metrics_display] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |