from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from core.deployment import DeploymentPolicy
from datasets.field_notes import FieldNote, FieldNoteStore
from plant.app import build_app, load_config
from plant.plant_loader import (
FieldNotesPlantExporter,
LocalFolderLoader,
SpeciesIndexBuilder,
demo_species,
)
from plant.plant_service import DemoPlantVisionService, extract_json_object, parse_plant_response
from plant.plant_tab import (
identify_plant_callback,
plant_training_plan,
render_result_card,
save_field_note_callback,
species_table,
)
from plant.plant_tools import dataset_stats, search_species, set_services, training_plan
from plant.training import build_plant_training_plan, write_llamafactory_dataset_info
class PlantReferenceAppTest(unittest.TestCase):
def test_plant_config_loads_domain_and_model(self) -> None:
config = load_config("plant/models.yaml")
self.assertEqual(config["domain"]["name"], "plant_discovery")
self.assertEqual(config["models"]["plant_vlm"]["hf_id"], "openbmb/MiniCPM-V-4.6")
def test_no_model_app_builds_gradio_blocks(self) -> None:
demo = build_app(no_model=True)
self.assertEqual(type(demo).__name__, "Blocks")
def test_space_policy_rejects_no_model_mode(self) -> None:
import plant.app as plant_app
original = plant_app.current_policy
plant_app.current_policy = lambda: DeploymentPolicy("space") # type: ignore[assignment]
try:
with self.assertRaises(ValueError):
build_app(no_model=True)
finally:
plant_app.current_policy = original # type: ignore[assignment]
def test_default_app_builds_openbmb_service_without_loading_weights(self) -> None:
demo = build_app()
self.assertEqual(type(demo).__name__, "Blocks")
def test_demo_service_returns_structured_plant_result(self) -> None:
result = DemoPlantVisionService().identify(object(), force_thinking=True)
self.assertEqual(result.latin_name, "Bellis perennis")
self.assertGreater(result.confidence, 0.8)
self.assertEqual(result.to_dict()["family"], "Asteraceae")
def test_demo_service_reports_no_llm_usage(self) -> None:
status = DemoPlantVisionService().service_status()
self.assertFalse(status["uses_llm"])
self.assertEqual(status["mode"], "demo")
def test_extract_json_repairs_common_model_wrapping(self) -> None:
parsed = extract_json_object('```json\n{"latin_name":"Rosa canina",}\n```')
self.assertEqual(parsed["latin_name"], "Rosa canina")
def test_parse_plant_response_builds_schema(self) -> None:
result = parse_plant_response(
'{"common_name":"Oak","latin_name":"Quercus robur","confidence":0.91}',
model_used="demo",
)
self.assertEqual(result.genus, "Quercus")
self.assertEqual(result.model_used, "demo")
def test_local_folder_loader_uses_species_folder_names(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
species_dir = Path(tmp) / "Acer_palmatum"
species_dir.mkdir()
(species_dir / "leaf.jpg").write_bytes(b"not a real image but good metadata")
loader = LocalFolderLoader(tmp)
self.assertEqual(loader.species_list(), ["Acer palmatum"])
self.assertEqual(loader.iter_records()[0].latin_name, "Acer palmatum")
def test_species_index_falls_back_to_demo_species(self) -> None:
index = SpeciesIndexBuilder(root="missing-plant-root").build({"datasets": {}})
self.assertIn("Bellis perennis", index)
def test_species_table_filters_by_query_and_family(self) -> None:
rows = species_table(demo_species(), "rose", "Rosaceae")
self.assertEqual(rows[0][0], "Rosa canina")
def test_render_result_card_escapes_model_text(self) -> None:
html = render_result_card(
{
"common_name": "",
"latin_name": "Bellis perennis",
"confidence": 0.5,
}
)
self.assertIn("<script>", html)
self.assertNotIn("