Plant registry, grow event calendar, and gardening knowledge base populated via cf-harvest ingest endpoint. All 23 tests passing. Stack: - FastAPI + SQLite (cf-core migrations) on port 8522 - Vue 3 + Vite + Pinia SPA on port 8521 - 5 SQL migrations: locations → plants → grow_events, knowledge_sources → knowledge_facts - Earthy green theme (--color-primary: #4a7c59) matching CF theme system Knowledge ingest contract: - POST /api/v1/knowledge/ingest — idempotent on fact_hash (sha256 of type+payload+video_path) - 8 fact types: companion_planting, soil_amendment, propagation, pest_diagnosis, harvest_timing, instruction, medicinal, history_context - Partial batch failure: malformed facts collected in rejected[], valid facts inserted - Re-ingesting same video batch: facts_skipped_duplicate > 0, no duplicates Wired views: RegistryView (plant CRUD), KnowledgeView (facts + type filter chips) Stub views: CalendarView, SettingsView
95 lines
3.7 KiB
Python
95 lines
3.7 KiB
Python
"""Knowledge query endpoint tests."""
|
|
|
|
_SOURCE_A = {
|
|
"video_path": "/media/GW/s59e01.mkv",
|
|
"presenter": "Monty Don",
|
|
"location": "Longmeadow",
|
|
"source_domain": "gardening",
|
|
}
|
|
_SOURCE_B = {
|
|
"video_path": "/media/GW/s59e02.mkv",
|
|
"presenter": "Adam Frost",
|
|
"location": "Adam's Garden",
|
|
"source_domain": "gardening",
|
|
}
|
|
|
|
|
|
def _ingest(client, source, facts):
|
|
return client.post("/api/v1/knowledge/ingest", json={"source": source, "facts": facts}).json()
|
|
|
|
|
|
def test_query_all_returns_inserted_facts(client):
|
|
_ingest(client, _SOURCE_A, [
|
|
{"fact_type": "instruction", "confidence": 0.9, "payload": {"action": "mulch", "subject": "border", "steps": [], "timing": None}},
|
|
])
|
|
resp = client.get("/api/v1/knowledge")
|
|
assert resp.status_code == 200
|
|
assert len(resp.json()) == 1
|
|
|
|
|
|
def test_query_filter_by_fact_type(client):
|
|
_ingest(client, _SOURCE_A, [
|
|
{"fact_type": "companion_planting", "confidence": 0.9, "payload": {"plants": ["tomato", "basil"], "relationship": "beneficial", "benefit": None, "spacing": None}},
|
|
{"fact_type": "instruction", "confidence": 0.8, "payload": {"action": "prune", "subject": "roses", "steps": [], "timing": None}},
|
|
])
|
|
resp = client.get("/api/v1/knowledge?fact_type=companion_planting")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert len(data) == 1
|
|
assert data[0]["fact_type"] == "companion_planting"
|
|
|
|
|
|
def test_query_filter_by_presenter(client):
|
|
_ingest(client, _SOURCE_A, [
|
|
{"fact_type": "instruction", "confidence": 0.9, "payload": {"action": "water", "subject": "seedlings", "steps": [], "timing": None}},
|
|
])
|
|
_ingest(client, _SOURCE_B, [
|
|
{"fact_type": "soil_amendment", "confidence": 0.85, "payload": {"amendment": "compost", "purpose": "feed soil", "application_rate": "2cm", "timing": "spring", "target_plants": []}},
|
|
])
|
|
resp = client.get("/api/v1/knowledge?presenter=Adam")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert all(d["presenter"] == "Adam Frost" for d in data)
|
|
|
|
|
|
def test_query_filter_by_min_confidence(client):
|
|
_ingest(client, _SOURCE_A, [
|
|
{"fact_type": "instruction", "confidence": 0.95, "payload": {"action": "sow", "subject": "tithonia", "steps": [], "timing": None}},
|
|
{"fact_type": "medicinal", "confidence": 0.40, "payload": {"plant": "calendula", "use": "topical", "preparation": None, "cautions": None}},
|
|
])
|
|
resp = client.get("/api/v1/knowledge?min_confidence=0.8")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert all(d["confidence"] >= 0.8 for d in data)
|
|
assert len(data) == 1
|
|
|
|
|
|
def test_query_payload_is_dict_not_string(client):
|
|
_ingest(client, _SOURCE_A, [
|
|
{"fact_type": "history_context", "confidence": 0.7, "payload": {
|
|
"subject": "Wisteria", "origin": "China", "period": None, "summary": "Introduced in 1816.",
|
|
}},
|
|
])
|
|
resp = client.get("/api/v1/knowledge")
|
|
assert resp.status_code == 200
|
|
payload = resp.json()[0]["payload"]
|
|
assert isinstance(payload, dict)
|
|
assert payload["subject"] == "Wisteria"
|
|
|
|
|
|
def test_get_single_fact_by_id(client):
|
|
_ingest(client, _SOURCE_A, [
|
|
{"fact_type": "harvest_timing", "confidence": 0.88, "payload": {
|
|
"plant": "Courgette", "indicators": ["20cm length"], "harvest_method": "twist off", "post_harvest": None,
|
|
}},
|
|
])
|
|
facts = client.get("/api/v1/knowledge").json()
|
|
fact_id = facts[0]["id"]
|
|
resp = client.get(f"/api/v1/knowledge/{fact_id}")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["payload"]["plant"] == "Courgette"
|
|
|
|
|
|
def test_get_fact_not_found(client):
|
|
resp = client.get("/api/v1/knowledge/9999")
|
|
assert resp.status_code == 404
|