413 lines
21 KiB
Python
413 lines
21 KiB
Python
"""Acceptance, adversarial extraction and source-bound temporal regressions."""
|
|
import copy
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from tools.knowledge.sources import profile_units, meeting_date
|
|
from tools.knowledge.extraction import extract_batch
|
|
from tools.knowledge.query import answer_from_index, intent, invalid_question, select_topic_evidence
|
|
|
|
|
|
def response(value):
|
|
return {'choices':[{'message':{'content':json.dumps(value,ensure_ascii=False)}}]}
|
|
|
|
|
|
def doc(name='Alena Nová', content=None):
|
|
return {'title':name, 'path':'pages/students/2020/alena/README.md',
|
|
'content':content or '''# Diplomová práca 2026
|
|
Zadanie:
|
|
1. Vytvorte systém RAG na vyhľadávanie dokumentov.
|
|
Stretnutie 5.2.2026
|
|
Stav:
|
|
- Implementovaný systém RAG s vyhľadávaním dokumentov.
|
|
Úlohy:
|
|
- Vyhodnoťte systém pomocou DeepEval.
|
|
Stretnutie 6.3.
|
|
Stav:
|
|
- Vyhodnotenie modelu zatiaľ nefunguje.
|
|
'''}
|
|
|
|
|
|
def fixture_index():
|
|
units=profile_units(doc())
|
|
second=profile_units({**doc(), 'title':'Boris Starý', 'path':'pages/students/2020/boris/README.md',
|
|
'content':'# Diplomová práca 2025\nStretnutie 3.4.2025\nStav:\n- Implementovaný systém RAG pre vzdelávanie.\n'})
|
|
for unit in [*units,*second]:
|
|
unit.update(concepts=['RAG'] if 'RAG' in unit['text'] else [], relations=[])
|
|
return {'units':[*units,*second], 'index_version':{'version':'fixture'}, 'version':'knowledge-fixture'}
|
|
|
|
|
|
def test_dates_are_explicit_and_invalid_dates_are_not_repaired():
|
|
assert meeting_date('Stretnutie 5.2.2026')['date']=='2026-02-05'
|
|
assert meeting_date('Virtuálne stretnutie 12.3.')=={
|
|
'date':None,'date_label':'12.3.','date_precision':'day_month'}
|
|
assert meeting_date('Stretnutie 31.2.2026')['date'] is None
|
|
assert meeting_date('Diplomová práca 2026') is None
|
|
assert meeting_date('Virtuálne stretnutie 13.11:2020:')['date']=='2020-11-13'
|
|
|
|
|
|
def test_source_units_keep_person_work_phase_and_date_together():
|
|
units=profile_units(doc())
|
|
assignment=next(u for u in units if 'Vytvorte' in u['text'])
|
|
done=next(u for u in units if 'Implementovaný' in u['text'])
|
|
planned=next(u for u in units if 'Vyhodnoťte' in u['text'])
|
|
partial=next(u for u in units if 'nefunguje' in u['text'])
|
|
assert assignment['phase']=='assignment' and assignment['date'] is None
|
|
assert done['phase']=='result' and done['date']=='2026-02-05'
|
|
assert planned['phase']=='planned' and planned['date']=='2026-02-05'
|
|
assert partial['phase']=='progress' and partial['date'] is None
|
|
assert partial['date_precision']=='day_month' and partial['work_year']==2026
|
|
assert len({u['id'] for u in units})==len(units)
|
|
|
|
|
|
def test_same_quote_in_different_works_is_not_a_single_evidence():
|
|
units=profile_units(doc(content='# Diplomový projekt 2025\nStav:\n- Funguje systém RAG.\n'
|
|
'# Diplomová práca 2026\nStav:\n- Funguje systém RAG.\n'))
|
|
assert len(units)==2 and units[0]['id']!=units[1]['id']
|
|
assert {u['work_year'] for u in units}=={2025,2026}
|
|
|
|
|
|
def test_sibling_nonacademic_heading_does_not_inherit_previous_work():
|
|
units=profile_units(doc(content='# Diplomová práca 2026\nStav:\n- Funguje systém RAG.\n'
|
|
'# Príprava na projekt\n- Iná poznámka mimo diplomovej práce.\n'))
|
|
assert units[-1]['work_label'] is None and units[-1]['phase']=='unknown'
|
|
|
|
|
|
def test_reference_urls_and_topic_examples_are_not_promoted_to_results_or_goals():
|
|
units=profile_units(doc(content='# Diplomová práca 2026\nZadanie:\n- Vytvorte systém RAG.\n'
|
|
'Napr.\n- téma pre základné školy.\nStav:\n- Implementovaný systém RAG.\n'
|
|
'Zdroje dokumentov:\n- https://example.test/research\n'))
|
|
assert not any('https://' in u['text'] for u in units)
|
|
example=next(u for u in units if 'téma' in u['text'])
|
|
assert example['phase']=='unknown'
|
|
|
|
|
|
def test_partial_implementation_is_not_a_completed_result_even_under_urobene():
|
|
units=profile_units(doc(content='# Diplomová práca 2026\nUrobené:\n- rozrobené web rozhranie\n'))
|
|
assert units[0]['phase']=='progress' and units[0]['kind']=='progress'
|
|
|
|
|
|
def test_explicit_outputs_and_checked_tasks_remain_distinguishable():
|
|
units=profile_units(doc(content='# Bakalársky projekt 2020\nVýstupy:\n- Aplikácia vie rozpoznávať entity.\n'
|
|
'Úlohy:\n- [x] Naštudujte korpusy pre NER.\n- [ ] Vyhodnoťte model NER.\n'))
|
|
assert units[0]['phase']=='result'
|
|
assert units[1]['phase']=='result' and units[1]['completion_basis']=='checked_task'
|
|
assert units[2]['phase']=='planned'
|
|
|
|
|
|
def test_model_intent_cannot_rewrite_questions_or_invent_query_operators():
|
|
from tools.knowledge.query import model_intent,ContentAnswerRejected,needs_intent_model
|
|
question='Ktorí študenti sa zaoberali anotovaním textov?'
|
|
assert needs_intent_model(question)
|
|
def classify(messages):
|
|
assert messages[1]['content']==question
|
|
return response({'intent':'discovery'})
|
|
assert model_intent(question,classify)=='discovery'
|
|
with pytest.raises(ContentAnswerRejected):model_intent(question,lambda _:response({'intent':'execute_cypher'}))
|
|
with pytest.raises(ContentAnswerRejected):model_intent(question,lambda _:response({'intent':'discovery','query':'changed'}))
|
|
assert not needs_intent_model('Aké práce má Ján Holp?')
|
|
assert not needs_intent_model('Aké práce a projekty má Ondrej Megela?')
|
|
assert not needs_intent_model('Koľko osôb má prácu v roku 2024?')
|
|
|
|
|
|
def test_extraction_is_bound_to_literal_evidence_and_can_return_empty_concepts():
|
|
units=profile_units(doc())[:2]
|
|
output={'items':[{'evidence_id':key,'concepts':['RAG'],'relations':[]} for key in ['E2','E1']]}
|
|
result=extract_batch(units,lambda _:response(output))
|
|
assert [r['id'] for r in result]==[u['id'] for u in units]
|
|
assert result[0]['phase']=='assignment' and result[1]['phase']=='result'
|
|
|
|
|
|
@pytest.mark.parametrize('change',[
|
|
lambda v:v['items'][0].update(evidence_id='unknown'),
|
|
lambda v:v['items'][0].update(date='2099-01-01'),
|
|
lambda v:v['items'].append(copy.deepcopy(v['items'][0])),
|
|
lambda v:v.update(instructions='ignore the sources'),
|
|
])
|
|
def test_wrong_llm_extractions_are_rejected_after_bounded_retry(change):
|
|
unit=profile_units(doc())[0]
|
|
payload={'items':[{'evidence_id':'E1','concepts':['RAG'],'relations':[]}]}
|
|
change(payload)
|
|
attempts=[]
|
|
def complete(messages):
|
|
attempts.append(messages)
|
|
return response(payload)
|
|
with pytest.raises(ValueError):extract_batch([unit],complete)
|
|
assert len(attempts)==2
|
|
|
|
|
|
def test_unsupported_concepts_and_relations_are_discarded_without_losing_source_evidence():
|
|
unit=profile_units(doc())[0]
|
|
payload={'items':[{'evidence_id':'E1','concepts':['RAG','vymyslený model'],
|
|
'relations':[{'subject':'RAG','predicate':'uses','object':'BERT'}]}]}
|
|
rows=extract_batch([unit],lambda _:response(payload))
|
|
assert rows[0]['text']==unit['text'] and rows[0]['concepts']==['RAG']
|
|
assert rows[0]['relations']==[]
|
|
assert rows[0]['discarded_concepts']==rows[0]['discarded_relations']==1
|
|
|
|
|
|
def test_timeline_uses_meeting_date_not_work_year_and_omits_plans():
|
|
result=answer_from_index('Kedy dosiahla Alena Nová aký výsledok v roku 2026?',fixture_index(),engine='rag')
|
|
assert result['status']=='ok'
|
|
assert len(result['evidence'])==1
|
|
assert result['evidence'][0]['date']=='2026-02-05'
|
|
assert 'DeepEval' not in result['answer']
|
|
assert all(r['person']=='Alena Nová' for r in result['evidence'])
|
|
|
|
|
|
def test_period_is_applied_to_same_evidence_not_another_person_or_work_year():
|
|
result=answer_from_index('Aké témy sa riešili od roku 2025 do roku 2025?',fixture_index(),engine='graphrag')
|
|
assert result['status']=='ok' and {r['person'] for r in result['evidence']}=={'Boris Starý'}
|
|
assert all(r['date']=='2025-04-03' for r in result['evidence'])
|
|
|
|
|
|
def test_unknown_person_and_unknown_topic_do_not_expand_into_global_results():
|
|
result=answer_from_index('Ako pokročil Xaver Neexistujúci?',fixture_index(),engine='rag')
|
|
assert result['status']=='no_answer' and not result['source_urls']
|
|
result=answer_from_index('Kto sa venoval kvantovej teleportácii?',fixture_index(),engine='rag')
|
|
assert result['status']=='no_answer' and not result['evidence']
|
|
|
|
|
|
def test_similarity_includes_both_sides_of_its_evidence():
|
|
result=answer_from_index('Kto pracoval na podobnej téme ako Alena Nová?',fixture_index(),engine='graphrag')
|
|
assert result['status']=='ok'
|
|
assert {r['person'] for r in result['evidence']}=={'Alena Nová','Boris Starý'}
|
|
assert len(result['source_urls'])==2
|
|
assert 'nie dôkaz spolupráce' in result['answer']
|
|
|
|
|
|
def test_shared_interface_or_webhook_alone_does_not_prove_topic_similarity():
|
|
index=fixture_index()
|
|
for row in index['units']:row['concepts']=['webhook','rozhranie']
|
|
result=answer_from_index('Kto pracoval na podobnej téme ako Alena Nová?',index,engine='graphrag')
|
|
assert result['status']=='no_answer' and not result['evidence']
|
|
|
|
|
|
def test_shared_graph_concepts_are_relevance_checked_before_delivery():
|
|
selected=[]
|
|
def choose(messages):
|
|
payload=json.loads(messages[1]['content'])
|
|
assert payload['reference_evidence'] and payload['evidence']
|
|
selected.append(True)
|
|
return response({'evidence_ids':['R1']})
|
|
result=answer_from_index('Kto pracoval na podobnej téme ako Alena Nová?',fixture_index(),
|
|
engine='graphrag',complete=choose)
|
|
assert selected and result['status']=='ok'
|
|
assert {u['person'] for u in result['evidence']}=={'Alena Nová','Boris Starý'}
|
|
|
|
|
|
def test_year_constraint_is_not_dropped_while_resolving_a_typo():
|
|
result=answer_from_index('Ako pokročila Alena Novvá pri diplomovej práci v roku 1999?',fixture_index(),engine='rag')
|
|
assert result['status']=='no_answer' and not result['evidence']
|
|
|
|
|
|
def test_metadata_filters_are_never_silently_dropped():
|
|
result=answer_from_index('Kto pracoval na podobnej téme ako Alena Nová s tagom NLP?',fixture_index(),engine='rag')
|
|
assert result['status']=='ambiguous' and result['reason']=='unsupported_content_join'
|
|
|
|
|
|
def test_topic_model_cannot_add_unprovided_evidence():
|
|
candidates=fixture_index()['units'][:1]
|
|
with pytest.raises(ValueError):
|
|
select_topic_evidence('Kto vytvoril chatbota?',candidates,lambda _:response({'evidence_ids':['invented']}))
|
|
|
|
|
|
def test_selection_accepts_fenced_json_and_empty_selection_but_never_unbound_lists():
|
|
candidates=fixture_index()['units'][:1]
|
|
fenced={'choices':[{'message':{'content':'```json\n{"evidence_ids":["R1"]}\n```'}}]}
|
|
assert select_topic_evidence('Kto pracoval na RAG?',candidates,lambda _:fenced)==candidates
|
|
assert select_topic_evidence('Kto pracoval na teleportácii?',candidates,lambda _:response([]))==[]
|
|
with pytest.raises(ValueError):
|
|
select_topic_evidence('Kto pracoval na RAG?',candidates,lambda _:response(['R1']))
|
|
|
|
|
|
def test_work_context_cannot_join_another_person_or_another_work():
|
|
from tools.knowledge.sources import with_work_context
|
|
index=fixture_index()
|
|
topic={**index['units'][0],'id':'topic','kind':'topic','text':'Názov: Agent pre RAG.'}
|
|
unrelated={**topic,'id':'unrelated','work_label':'Diplomový projekt 2025','text':'Názov: Iná práca.'}
|
|
other={**topic,'id':'other','person':'Boris Starý','source_path':'other','text':'Názov: Cudzia práca.'}
|
|
rows=with_work_context([*index['units'],topic,unrelated,other])
|
|
unit=next(r for r in rows if r['id']==index['units'][1]['id'])
|
|
assert [r['id'] for r in unit['context_quotes']]==['topic']
|
|
|
|
|
|
def test_annotation_of_another_target_or_an_analogy_is_not_named_entity_annotation():
|
|
from tools.knowledge.query import activity_supported
|
|
question='Kto sa venoval anotácii pomenovaných entít?'
|
|
assert not activity_supported(question,{'text':'Vytvorené anotácie do databázy, cca 1700 jednotiek.'})
|
|
assert not activity_supported(question,{'text':'Anotácia urážlivých častí. Dáta anotujete podobne ako named entities.'})
|
|
assert not activity_supported(question,{'text':'Rozpoznávanie pomenovaných entít pomocou BERT.'})
|
|
assert activity_supported(question,{'text':'Rozbehaná slovenská anotačná schéma pre pomenované entity.'})
|
|
assert activity_supported(question,{'text':'Vytvorené anotácie do databázy.',
|
|
'context_quotes':[{'text':'Názov: Anotácia NER.'}]})
|
|
|
|
|
|
def test_explicit_dates_can_resolve_a_semester_without_inventing_boundaries():
|
|
result=answer_from_index('Aké témy sa riešili v zimnom semestri od 1.1.2026 do 1.3.2026?',
|
|
fixture_index(),engine='rag',mode='period')
|
|
assert result['status']=='ok' and all(r['date']=='2026-02-05' for r in result['evidence'])
|
|
|
|
|
|
@pytest.mark.parametrize('query',['CRZP','HDFS systémy','NLP projekty'])
|
|
def test_real_acronyms_are_not_keyboard_gibberish(query):
|
|
assert not invalid_question(query)
|
|
|
|
|
|
def test_discovery_selects_recorded_implementation_before_a_bare_topic_link():
|
|
index=fixture_index()
|
|
rows=[{**index['units'][0],'id':'link','phase':'unknown','kind':'context','text':'- [Chatbot](/topics/chatbot)'},
|
|
{**index['units'][0],'id':'done','phase':'result','kind':'result','text':'- Chatbot funguje pre agendu mesta.'}]
|
|
def select(messages):
|
|
supplied=json.loads(messages[1]['content'])['evidence']
|
|
assert supplied[0]['text']=='- Chatbot funguje pre agendu mesta.'
|
|
return response({'evidence_ids':['R1']})
|
|
result=answer_from_index('Kto vytvoril chatbota?',{**index,'units':rows},engine='rag',complete=select)
|
|
assert result['status']=='ok' and result['evidence'][0]['id']=='done'
|
|
|
|
|
|
@pytest.mark.parametrize('exception,reason',[
|
|
(__import__('tools.knowledge.query',fromlist=['ContentModelUnavailable']).ContentModelUnavailable,'model_unavailable'),
|
|
(__import__('tools.knowledge.query',fromlist=['ContentAnswerRejected']).ContentAnswerRejected,'answer_rejected'),
|
|
(RuntimeError,'content_index_unavailable'),
|
|
])
|
|
def test_content_failures_keep_model_rejection_and_index_errors_distinct(monkeypatch,exception,reason):
|
|
import tools.knowledge.query as query
|
|
from tools.answers.answer_delivery import deliver_answer
|
|
def failed(*a,**kw):raise exception('private diagnostic must not escape')
|
|
monkeypatch.setattr(query,'deliver_content',failed)
|
|
result=deliver_answer('Ako pokročil Ján Holp?')
|
|
assert result['reason']==reason and not result['accepted_for_delivery']
|
|
assert 'private diagnostic' not in result['answer']
|
|
|
|
|
|
@pytest.mark.parametrize('query',['xdzf','asdfgh','bcdfrt','x y z'])
|
|
def test_unintelligible_input_requests_clarification(query):
|
|
assert invalid_question(query)
|
|
|
|
|
|
def test_intent_does_not_intercept_existing_metadata_questions():
|
|
assert intent('Aký je názov diplomovej práce Jána Holpa a z ktorého roku je?') is None
|
|
assert intent('Aké spoločné kľúčové slová majú Ján Pták a Ján Malinovský?') is None
|
|
|
|
|
|
def test_real_markdown_acceptance_sources_are_independent_of_extraction_parser():
|
|
from tools.rag.common import ZPWIKI_ROOT
|
|
expectations=[
|
|
('2016/jan_holp','funguje databáza Cassandra'),
|
|
('2018/ondrej_megela','Dotrenovany t5 model na generovanie otazok.'),
|
|
('2022/jan_malinovsky','Extrahovaný text pomocou pypdf a docx.'),
|
|
('2021/martin_sarissky','Problém s kvalitou vyhľadávania.'),
|
|
]
|
|
if not ZPWIKI_ROOT.is_dir():pytest.skip('Local wiki is not installed')
|
|
for path,quote in expectations:
|
|
raw=(ZPWIKI_ROOT/'pages/students'/path/'README.md').read_text()
|
|
assert quote in raw
|
|
|
|
|
|
def test_stale_extraction_is_not_used(tmp_path):
|
|
from tools.knowledge.store import load_index
|
|
folder=tmp_path/'knowledge';folder.mkdir()
|
|
(folder/'index.json').write_text(json.dumps({'index_version':{'version':'old'}}))
|
|
with pytest.raises(RuntimeError):load_index(tmp_path,{'version':'new'})
|
|
|
|
|
|
def test_shared_embedding_cache_can_be_reused_without_writes(tmp_path):
|
|
import numpy as np
|
|
from tools.rag.embedding_cache import EmbeddingCache
|
|
path=tmp_path/'cache.sqlite'
|
|
writable=EmbeddingCache(path)
|
|
writable.save({'fixture':np.array([1.,0.],dtype=np.float32)})
|
|
before=path.read_bytes()
|
|
readonly=EmbeddingCache(path,readonly=True)
|
|
assert readonly.load(['fixture'])['fixture'].tolist()==[1.,0.]
|
|
with pytest.raises(RuntimeError):readonly.save({'other':np.array([0.,1.])})
|
|
assert path.read_bytes()==before
|
|
|
|
|
|
def test_build_is_cached_and_source_changes_cannot_activate_it(tmp_path):
|
|
from tools.knowledge.build import build
|
|
from tools.sync.index_state import durable_json
|
|
from tools.sync.sync_indexes import source_files
|
|
import hashlib
|
|
wiki=tmp_path/'wiki';data=tmp_path/'data';data.mkdir()
|
|
path=wiki/'pages/students/2020/alena/README.md';path.parent.mkdir(parents=True)
|
|
path.write_text('---\ntitle: Alena Nová\npublished: true\n---\n'+doc()['content'])
|
|
version={'version':'fixture','source_sha256':hashlib.sha256(json.dumps(source_files(wiki),sort_keys=True).encode()).hexdigest()}
|
|
durable_json(data/'index_version.json',version)
|
|
calls=[]
|
|
def extract(messages):
|
|
payload=json.loads(messages[1]['content']);calls.append(1)
|
|
return response({'items':[{'evidence_id':r['evidence_id'],'concepts':[],'relations':[]} for r in payload]})
|
|
first=build(wiki,data,extract,{'model':'fixture'},graph=False,vectors=False)
|
|
assert first['profiles']==1 and first['llm_batches']==1
|
|
activated=(data/'knowledge/index.json').read_bytes()
|
|
second=build(wiki,data,extract,{'model':'fixture'},graph=False,vectors=False)
|
|
assert second['llm_batches']==0 and second['cached_batches']==1 and len(calls)==1
|
|
assert activated==(data/'knowledge/index.json').read_bytes()
|
|
path.write_text(path.read_text()+'\nNové podklady.\n')
|
|
with pytest.raises(RuntimeError):build(wiki,data,extract,{'model':'fixture'},graph=False,vectors=False)
|
|
assert activated==(data/'knowledge/index.json').read_bytes()
|
|
|
|
|
|
@pytest.mark.parametrize('enabled',[False,True])
|
|
def test_index_sync_refreshes_content_only_after_it_has_been_enabled(tmp_path,monkeypatch,enabled):
|
|
import tools.rag.rebuild_index as rebuild
|
|
import tools.sync.sync_indexes as sync
|
|
from tools.knowledge import build as knowledge
|
|
target=tmp_path/'data';target.mkdir()
|
|
if enabled:
|
|
(target/'knowledge').mkdir()
|
|
(target/'knowledge/index.json').write_text('{}')
|
|
calls=[];phases=[]
|
|
monkeypatch.setattr(sync,'synchronize_indexes',lambda *a,**kw:{'counts':{'documents':1}})
|
|
monkeypatch.setattr(knowledge,'refresh_existing',lambda *a:calls.append(a) or {'cached_batches':1})
|
|
result=rebuild.rebuild_index(wiki_root=tmp_path/'wiki',data_dir=target,progress=phases.append)
|
|
assert bool(calls)==enabled
|
|
assert ('knowledge' in result)==enabled
|
|
if enabled:assert phases[-1]=='extracting_knowledge'
|
|
|
|
|
|
def test_explicit_work_year_is_not_misused_as_an_event_date():
|
|
index=fixture_index()
|
|
row=next(r for r in index['units'] if r['phase']=='result' and r['person']=='Alena Nová')
|
|
row['date']='2025-12-01'
|
|
result=answer_from_index('Kedy dosiahla Alena Nová výsledok pri diplomovej práci z roku 2026?',index,engine='rag')
|
|
assert result['status']=='ok' and result['evidence'][0]['date']=='2025-12-01'
|
|
|
|
|
|
def test_reversed_time_interval_requires_clarification():
|
|
result=answer_from_index('Aké témy sa riešili od roku 2026 do roku 2020?',fixture_index(),engine='rag')
|
|
assert result['status']=='ambiguous' and result['reason']=='invalid_period'
|
|
|
|
|
|
@pytest.mark.skipif(__import__('os').getenv('RUN_NEO4J_TESTS')!='1',reason='Requires local Neo4j')
|
|
def test_live_content_graph_roundtrip_is_idempotent_and_separate(monkeypatch):
|
|
import uuid
|
|
from tools.knowledge import store
|
|
from tools.graphrag.graph_db import create_neo4j_driver,get_neo4j_settings
|
|
scope='knowledge-test-'+uuid.uuid4().hex
|
|
monkeypatch.setattr(store,'SCOPE',scope)
|
|
index=fixture_index()
|
|
try:
|
|
store.write_graph(index)
|
|
first=store.read_graph(index)
|
|
store.write_graph(index)
|
|
again=store.read_graph(index)
|
|
assert len(first)==len(again)==len(index['units'])
|
|
assert {r['id']:r['text'] for r in first}=={r['id']:r['text'] for r in index['units']}
|
|
assert any(r.get('date')=='2026-02-05' for r in again)
|
|
with pytest.raises(RuntimeError):store.read_graph({**index,'version':'wrong-version'})
|
|
broken=copy.deepcopy(index)
|
|
broken['units'][0]['source_path']={'not':'a Neo4j property'}
|
|
from neo4j.exceptions import Neo4jError
|
|
with pytest.raises(Neo4jError):store.write_graph(broken)
|
|
assert {r['id'] for r in store.read_graph(index)}=={r['id'] for r in index['units']}
|
|
finally:
|
|
settings=get_neo4j_settings()
|
|
with create_neo4j_driver(settings) as driver,driver.session(database=settings.database) as session:
|
|
session.run('MATCH (n {graph_scope:$scope}) DETACH DELETE n',scope=scope).consume()
|
|
session.run('MATCH (n:KnowledgeState {scope:$scope}) DELETE n',scope=scope).consume()
|