433 lines
21 KiB
Python
433 lines
21 KiB
Python
import json
|
|
import pytest
|
|
from tools.knowledge.sources import profile_units
|
|
from tools.knowledge.discovery import expand_and_select
|
|
from tools.knowledge.query import ContentAnswerRejected, ContentModelUnavailable
|
|
|
|
Q='Kto označoval dáta? Na čo mali dáta slúžiť?'
|
|
|
|
def rows():
|
|
return profile_units({'title':'Eva Nová','path':'pages/students/2020/eva/README.md',
|
|
'content':'# Bakalárska práca 2025\nStav:\n- Ručne označené dáta.\nÚlohy:\n- Dáta majú slúžiť na učenie klasifikátora.\n# Diplomová práca 2027\nStav:\n- Hotový prekladač.'},include_records=True)
|
|
|
|
def run(mutation=None):
|
|
pool=rows();seed=next(r for r in pool if 'označené' in r['text'])
|
|
def complete(messages):
|
|
p=json.loads(messages[-1]['content']);sc=p['scopes'][0]
|
|
assert p['activity_question']=='Kto označoval dáta'
|
|
assert p['question']==Q
|
|
assert all('prekladač' not in r['text'] for r in sc['evidence'])
|
|
purpose=next(r['id'] for r in sc['evidence'] if 'klasifikátora' in r['text'])
|
|
assert p['follow_up_parts']==[{'part_id':'P1','text':'Na čo mali dáta slúžiť'}]
|
|
part={'part_id':'P1','evidence_ids':[purpose]}
|
|
choice={'scope_id':sc['scope_id'],'matches_activity':True,'primary_ids':sc['seed_ids'],'parts':[part]}
|
|
if mutation:mutation(choice)
|
|
return {'choices':[{'message':{'content':json.dumps({'scopes':[choice]})}}]}
|
|
return expand_and_select(Q,[seed],pool,complete)
|
|
|
|
def test_context_from_same_work_answers_purpose_without_changing_source():
|
|
selected,notes,_=run()
|
|
assert len(selected)==2 and not notes
|
|
assert all(r['work_year']==2025 for r in selected)
|
|
original={r['id']:r for r in rows()}
|
|
assert all(r==original[r['id']] for r in selected)
|
|
assert 'planned' in {r['phase'] for r in selected}
|
|
|
|
@pytest.mark.parametrize('mutation',[
|
|
lambda c:c['parts'][0].update(evidence_ids=['invented']),
|
|
lambda c:c.update(primary_ids=[]),
|
|
lambda c:c.update(matches_activity=False),
|
|
])
|
|
def test_invalid_ids_unproven_activity_and_changed_question_are_rejected(mutation):
|
|
with pytest.raises(ContentAnswerRejected):run(mutation)
|
|
|
|
def test_missing_detail_is_named_without_inventing_purpose():
|
|
selected,notes,_=run(lambda c:c['parts'][0].update(evidence_ids=[]))
|
|
assert len(selected)==1 and 'Na čo mali dáta slúžiť' in notes[0]
|
|
|
|
|
|
def test_negative_scope_with_empty_part_decisions_adds_no_evidence():
|
|
def reject(choice):
|
|
choice.update(matches_activity=False, primary_ids=[])
|
|
choice['parts'][0]['evidence_ids']=[]
|
|
selected,notes,_=run(reject)
|
|
assert selected==[] and notes==[]
|
|
|
|
def test_primary_activity_can_include_verified_same_work_expansion():
|
|
def add_detail(choice):
|
|
choice['primary_ids'] += choice['parts'][0]['evidence_ids']
|
|
selected, notes, _ = run(add_detail)
|
|
assert len(selected) == 2 and not notes
|
|
|
|
def test_discovery_seed_query_does_not_include_secondary_requirements():
|
|
from tools.answers.question_intent import discovery_topic
|
|
question = 'Kto označoval dáta v roku 2025? Ako ich vyhodnotil a na čo slúžili?'
|
|
assert discovery_topic(question) == 'Kto označoval dáta v roku 2025'
|
|
assert discovery_topic('Kto označoval dáta a ako ich vyhodnotil') == 'Kto označoval dáta a ako ich vyhodnotil'
|
|
|
|
def test_model_outage_does_not_claim_empty_evidence():
|
|
pool=rows();seed=next(r for r in pool if 'označené' in r['text'])
|
|
def fail(_):raise RuntimeError('PRIVATE')
|
|
with pytest.raises(ContentModelUnavailable,match='unavailable'):
|
|
expand_and_select(Q,[seed],pool,fail)
|
|
|
|
|
|
def _students(names):
|
|
pool=[]
|
|
for name in names:
|
|
key=name.split()[0].lower()
|
|
pool.extend(profile_units({'title':name,'path':f'pages/students/2020/{key}/README.md',
|
|
'content':'# Diplomová práca 2027\nStav:\n- Ručne označené dáta.'},include_records=True))
|
|
return pool,[r for r in pool if 'označené' in r['text']]
|
|
|
|
|
|
SEVEN=['Eva Nová','Adam Nový','Dana Malá','Ivan Biely','Jana Sivá','Karol Hnedý','Lea Zelená']
|
|
|
|
|
|
def _accept(payload):
|
|
return {'choices':[{'message':{'content':json.dumps({'scopes':[{'scope_id':s['scope_id'],'matches_activity':True,
|
|
'primary_ids':s['seed_ids'],'parts':[]} for s in payload['scopes']]})}}]}
|
|
|
|
|
|
def test_each_scope_gets_an_explicit_decision_in_balanced_batches():
|
|
pool,seeds=_students(SEVEN)
|
|
batches=[]
|
|
def select(messages):
|
|
payload=json.loads(messages[-1]['content']);batches.append(payload['scopes'])
|
|
return _accept(payload)
|
|
selected,_,_=expand_and_select(Q,seeds,pool,select)
|
|
# Two balanced calls use both slots of the shared endpoint in one round.
|
|
assert sorted(len(b) for b in batches)==[3,4]
|
|
assert {r['id'] for r in selected}=={r['id'] for r in seeds}
|
|
|
|
|
|
def _three_students():
|
|
# Four students: two balanced calls decide every scope explicitly.
|
|
return _students(['Eva Nová','Adam Nový','Dana Malá','Ivan Biely'])
|
|
|
|
|
|
def test_one_malformed_scope_decision_drops_only_that_student():
|
|
pool,seeds=_three_students()
|
|
def select(messages):
|
|
payload=json.loads(messages[-1]['content'])
|
|
value={'scopes':[{'scope_id':s['scope_id'],'matches_activity':True,
|
|
'primary_ids':['invented'] if s['person']=='Adam Nový' else s['seed_ids'],'parts':[]}
|
|
for s in payload['scopes']]}
|
|
return {'choices':[{'message':{'content':json.dumps(value)}}]}
|
|
selected,_,bounded=expand_and_select(Q,seeds,pool,select)
|
|
assert {r['person'] for r in selected}=={'Eva Nová','Dana Malá','Ivan Biely'}
|
|
assert bounded # The answer must disclose that the selection is limited.
|
|
|
|
|
|
def test_malformed_batch_is_retried_once_with_a_repair_hint():
|
|
pool,seeds=_three_students()
|
|
calls=[]
|
|
def select(messages):
|
|
payload=json.loads(messages[-1]['content']);calls.append(payload)
|
|
if len(calls)==1:
|
|
return {'choices':[{'message':{'content':'not JSON'}}]}
|
|
return _accept(payload)
|
|
selected,_,bounded=expand_and_select(Q,seeds,pool,select)
|
|
# Four students use both endpoint slots: two batches, one of them retried.
|
|
assert len({r['person'] for r in selected})==4 and not bounded and len(calls)==3
|
|
# An identical request would make a deterministic model repeat its output.
|
|
repaired=[c for c in calls if 'format_repair' in c]
|
|
assert len(repaired)==1 and 'format_repair' not in calls[0]
|
|
assert [s['scope_id'] for s in repaired[0]['scopes']]==[s['scope_id'] for s in calls[0]['scopes']]
|
|
|
|
|
|
def test_timeout_of_one_concurrent_batch_keeps_the_other_students():
|
|
pool,seeds=_students(SEVEN)
|
|
failed=[]
|
|
def select(messages):
|
|
payload=json.loads(messages[-1]['content'])
|
|
if any(s['person']=='Ivan Biely' for s in payload['scopes']):
|
|
failed.extend(s['person'] for s in payload['scopes'])
|
|
raise TimeoutError('slow batch')
|
|
return _accept(payload)
|
|
selected,_,bounded=expand_and_select(Q,seeds,pool,select)
|
|
# Students of the slow call keep their first-selection units (verbatim);
|
|
# nothing else of theirs is added and the answer is marked bounded.
|
|
assert {r['person'] for r in selected}==set(SEVEN) and failed and bounded
|
|
seed_ids={r['id'] for r in seeds}
|
|
assert all(r['id'] in seed_ids for r in selected if r['person'] in failed)
|
|
|
|
|
|
def test_rechecked_student_of_a_slow_call_is_not_added():
|
|
pool,seeds=_students(['Eva Nová','Adam Nový'])
|
|
chosen=[r for r in seeds if r['person']=='Eva Nová']
|
|
def select(messages):
|
|
payload=json.loads(messages[-1]['content'])
|
|
if any(s['person']=='Adam Nový' for s in payload['scopes']):
|
|
raise TimeoutError('slow batch')
|
|
return _accept(payload)
|
|
selected,_,bounded=expand_and_select(Q,chosen,pool,select,candidates=seeds)
|
|
assert {r['person'] for r in selected}<={'Eva Nová'} and bounded
|
|
|
|
|
|
def test_invented_question_part_and_its_evidence_are_ignored():
|
|
selected,notes,_=run(lambda c:c['parts'][0].update(part_id='P9'))
|
|
# Only the supplied follow-up exists; it stays named as unanswered.
|
|
assert len(selected)==1 and 'klasifikátora' not in selected[0]['text']
|
|
assert len(notes)==1 and 'Na čo mali dáta slúžiť' in notes[0]
|
|
|
|
|
|
def test_unsupplied_free_text_part_is_dropped_without_rejecting_the_scope():
|
|
selected,_,_=run(lambda c:c['parts'].append({'question_part':'Vymyslená požiadavka','evidence_ids':['E9']}))
|
|
assert len(selected)==2
|
|
|
|
|
|
def test_follow_up_parts_are_exact_substrings_chosen_by_code():
|
|
from tools.knowledge.discovery import follow_up_parts
|
|
assert follow_up_parts('Kto pracoval na chatbotovi?')==[]
|
|
assert follow_up_parts('Ktorí študenti robili s LLM a čo im fungovalo?')==['čo im fungovalo']
|
|
q='Ktorí študenti pripravovali alebo ručne označovali dáta? Čo označovali a na čo mali tieto dáta slúžiť?'
|
|
assert follow_up_parts(q)==['Čo označovali','na čo mali tieto dáta slúžiť']
|
|
# A relative clause after a comma is part of the main request.
|
|
assert follow_up_parts('Daj mi mená študentov, čo robili chatbota.')==[]
|
|
|
|
|
|
def test_positive_decision_without_seed_keeps_the_seed_evidence():
|
|
pool=rows();seed=next(r for r in pool if 'označené' in r['text'])
|
|
def select(messages):
|
|
p=json.loads(messages[-1]['content']);sc=p['scopes'][0]
|
|
other=next(e['id'] for e in sc['evidence'] if e['id'] not in sc['seed_ids'])
|
|
return {'choices':[{'message':{'content':json.dumps({'scopes':[{'scope_id':sc['scope_id'],
|
|
'matches_activity':True,'primary_ids':[other],'parts':[]}]})}}]}
|
|
selected,_,_=expand_and_select(Q,[seed],pool,select)
|
|
assert seed['id'] in {r['id'] for r in selected}
|
|
|
|
|
|
class _Plan(__import__('pydantic').BaseModel):
|
|
question: str = Q
|
|
intent: str = 'discovery'
|
|
presentation: str = 'overview'
|
|
meeting: object = None
|
|
|
|
|
|
def _with_summary(sentences, pool=None, seed=None, question=Q):
|
|
"""Decisions accept the seed; the wording call answers with `sentences`."""
|
|
if pool is None:
|
|
pool=rows();seed=next(r for r in pool if 'označené' in r['text'])
|
|
seen=[]
|
|
def select(messages):
|
|
p=json.loads(messages[-1]['content'])
|
|
if 'units' in p:
|
|
seen.append(p['units'])
|
|
return {'choices':[{'message':{'content':json.dumps({'sentences':[
|
|
{'id':p['units'][0]['id'] if e=='unit' else e,'text':t} for t,e in sentences]})}}]}
|
|
sc=p['scopes'][0]
|
|
assert p['write_summary'] is False # decisions never word
|
|
return {'choices':[{'message':{'content':json.dumps({'scopes':[{'scope_id':sc['scope_id'],
|
|
'matches_activity':True,'primary_ids':sc['seed_ids'],'parts':[]}]})}}]}
|
|
result=expand_and_select(question,[seed],pool,select,plan=_Plan(),known_people={'Eva Nová','Adam Nový'},with_prose=True)
|
|
return seed,result,seen
|
|
|
|
|
|
def test_checked_summary_sentence_is_returned_for_its_one_unit():
|
|
seed,(selected,_,_,prose),seen=_with_summary([('Ručne označila dáta.','unit')])
|
|
assert prose=={seed['id']:['Ručne označila dáta.']}
|
|
# The wording call sees each unit alone: its own text, nothing else.
|
|
assert [u['text'] for u in seen[0]]==[seed['text']]
|
|
|
|
|
|
@pytest.mark.parametrize('text,evidence',[
|
|
('Ručne označila dáta v roku 2025.','unit'), # dates are rendered by code
|
|
('Adam Nový ručne označil dáta.','unit'), # another person's name
|
|
('Ručne označila dáta.','U99'), # not a supplied unit
|
|
])
|
|
def test_unsafe_summary_sentence_is_dropped_but_selection_stays(text,evidence):
|
|
seed,(selected,_,_,prose),_=_with_summary([(text,evidence)])
|
|
assert prose=={} and seed['id'] in {r['id'] for r in selected}
|
|
|
|
|
|
def test_planned_unit_worded_as_achievement_is_dropped():
|
|
pool=profile_units({'title':'Eva Nová','path':'pages/students/2020/eva/README.md',
|
|
'content':'# Bakalárska práca 2025\nÚlohy:\n- Pripravte dáta na učenie klasifikátora.'},include_records=True)
|
|
seed=next(r for r in pool if 'Pripravte' in r['text'])
|
|
assert seed['phase'] in {'planned','assignment'}
|
|
def run_with(text):
|
|
return _with_summary([(text,'unit')],pool=pool,seed=seed,question='Kto pripravoval dáta?')[1][3]
|
|
assert run_with('Dáta boli pripravené na učenie klasifikátora.')=={}
|
|
assert run_with('Má pripraviť dáta na učenie klasifikátora.')=={seed['id']:['Má pripraviť dáta na učenie klasifikátora.']}
|
|
|
|
|
|
def test_wording_is_skipped_while_another_answer_uses_the_endpoint(monkeypatch):
|
|
from tools.answers import model_client
|
|
monkeypatch.setattr(model_client,'model_busy',lambda:True)
|
|
_,(selected,_,_,prose),seen=_with_summary([('Ručne označila dáta.','unit')])
|
|
assert selected and prose=={} and seen==[]
|
|
|
|
|
|
def test_student_gender_hint_is_supplied_for_wording():
|
|
from tools.knowledge.discovery import grammatical_gender
|
|
assert grammatical_gender('Simona Bobrovčanová')=='feminine'
|
|
assert grammatical_gender('Tetiana Mohorian')=='feminine'
|
|
assert grammatical_gender('Jakub Maruniak')=='masculine'
|
|
assert grammatical_gender('Eduard Matovka')=='masculine' # male surname ending in -a
|
|
assert grammatical_gender('Nikita Bodnar')=='masculine'
|
|
|
|
|
|
def _profile(name, content):
|
|
key=name.split()[0].lower()
|
|
return profile_units({'title':name,'path':f'pages/students/2020/{key}/README.md',
|
|
'content':content},include_records=True)
|
|
|
|
|
|
def test_recorded_result_is_offered_before_plans_repeating_the_topic():
|
|
# Twelve plans repeat the question's words more often than the one result.
|
|
plans='\n'.join(f'- Dotrénujte jazykový model variant {i} a vyhodnoťte model.' for i in range(14))
|
|
pool=_profile('Eva Nová','# Diplomová práca 2027\nÚlohy:\n'+plans+'\nStav:\n- funguje dotrénovanie Slovak Mistral.\n- po dotrénovaní je model ručne vyskúšaný.')
|
|
seed=next(r for r in pool if 'ručne vyskúšaný' in r['text'])
|
|
offered=[]
|
|
def select(messages):
|
|
p=json.loads(messages[-1]['content']);offered.extend(e['text'] for s in p['scopes'] for e in s['evidence'])
|
|
return _accept(p)
|
|
expand_and_select('Kto dotrénoval jazykový model?',[seed],pool,select)
|
|
assert any('funguje dotrénovanie Slovak Mistral' in t for t in offered)
|
|
|
|
|
|
def test_student_missed_by_first_selection_is_rechecked_and_needs_a_positive_decision():
|
|
pool,seeds=_students(['Eva Nová','Adam Nový'])
|
|
chosen=[r for r in seeds if r['person']=='Eva Nová']
|
|
def run_with(accept_adam):
|
|
seen=[]
|
|
def select(messages):
|
|
p=json.loads(messages[-1]['content']);seen.extend(s['person'] for s in p['scopes'])
|
|
return {'choices':[{'message':{'content':json.dumps({'scopes':[{'scope_id':s['scope_id'],
|
|
'matches_activity':accept_adam or s['person']!='Adam Nový',
|
|
'primary_ids':s['seed_ids'] if accept_adam or s['person']!='Adam Nový' else [],'parts':[]}
|
|
for s in p['scopes']]})}}]}
|
|
selected,_,_=expand_and_select(Q,chosen,pool,select,candidates=seeds)
|
|
return seen,{r['person'] for r in selected}
|
|
seen,people=run_with(True)
|
|
assert 'Adam Nový' in seen and people=={'Eva Nová','Adam Nový'}
|
|
seen,people=run_with(False)
|
|
assert people=={'Eva Nová'} # Only this step's positive decision adds a student.
|
|
|
|
|
|
def test_planned_candidate_is_not_rechecked():
|
|
pool=_profile('Eva Nová','# Diplomová práca 2027\nStav:\n- Ručne označené dáta.')
|
|
plan=_profile('Adam Nový','# Diplomová práca 2027\nÚlohy:\n- Označte dáta.')
|
|
seed=next(r for r in pool if 'označené' in r['text'])
|
|
seen=[]
|
|
def select(messages):
|
|
p=json.loads(messages[-1]['content']);seen.extend(s['person'] for s in p['scopes'])
|
|
return _accept(p)
|
|
expand_and_select(Q,[seed],pool+plan,select,candidates=[seed,*plan])
|
|
assert seen==['Eva Nová']
|
|
|
|
|
|
def test_scopes_beyond_the_detail_limit_get_a_light_decision_and_a_notice():
|
|
from tools.knowledge import discovery
|
|
names=[f'Meno{i} Priezvisko' for i in range(discovery.MAX_SCOPES+3)]
|
|
pool,seeds=_students(names)
|
|
scopes=[]
|
|
def select(messages):
|
|
p=json.loads(messages[-1]['content']);scopes.extend(p['scopes'])
|
|
assert len(p['scopes'])<=10
|
|
return _accept(p)
|
|
selected,notes,bounded=expand_and_select(Q,seeds,pool,select)
|
|
assert len({r['person'] for r in selected})==len(names)
|
|
assert sum(s['detail'] for s in scopes)==discovery.MAX_SCOPES
|
|
light=[s for s in scopes if not s['detail']]
|
|
assert len(light)==3 and all(len(s['evidence'])<=4 for s in light)
|
|
# The follow-up was not looked up for them: the answer is marked as a
|
|
# limited selection, without a per-person "missing" claim.
|
|
assert bounded and not any(s['person'] in n for s in light for n in notes)
|
|
|
|
|
|
def _fixed_calibration(monkeypatch):
|
|
from tools.knowledge import discovery
|
|
# Fixed calibration; the deployed values are measured and configurable.
|
|
monkeypatch.setattr(discovery,'TOKENS',{'light':25,'detail':70,'prose':60})
|
|
monkeypatch.setattr(discovery,'STREAM_RATE',60.0);monkeypatch.setattr(discovery,'SHARED_RATE',40.0)
|
|
monkeypatch.setattr(discovery,'TARGET_SECONDS',22.0);monkeypatch.setattr(discovery,'MAX_SCOPES',8)
|
|
return discovery
|
|
|
|
|
|
def test_work_per_scope_follows_the_time_left_and_the_endpoint_load(monkeypatch):
|
|
d=_fixed_calibration(monkeypatch)
|
|
# One user, enough time: every scope decided, the first ones in detail
|
|
# and the first of those with readable wording.
|
|
levels,worded=d.plan_levels(17,prose=True,seconds=35,busy=False)
|
|
assert len(levels)==17 and levels[:8]==['detail']*8 and set(levels[8:])=={'light'} and 4<=worded<=17
|
|
# Less time: still every student, fewer details, less wording.
|
|
short,few=d.plan_levels(17,prose=True,seconds=14,busy=False)
|
|
assert len(short)==17 and short.count('detail')<8 and few<worded
|
|
# Another answer shares the endpoint: one slot, about 40 tokens/s.
|
|
busy,busy_worded=d.plan_levels(17,prose=True,seconds=35,busy=True)
|
|
assert busy.count('detail')<=8 and busy_worded<worded
|
|
# Not even the decisions fit: fewer students, the answer is bounded.
|
|
tiny,_=d.plan_levels(17,prose=True,seconds=6,busy=False)
|
|
assert len(tiny)<17 and set(tiny)=={'light'}
|
|
assert d.plan_levels(3,prose=False,seconds=None,busy=False)[1]==0
|
|
|
|
|
|
def test_worded_and_plain_scopes_are_separate_calls(monkeypatch):
|
|
d=_fixed_calibration(monkeypatch)
|
|
costs=[130]*6+[25]*9
|
|
chunks,seconds=d._layout(costs,6,busy=False)
|
|
assert all(all(i<6 for i in c) or all(i>=6 for i in c) for c in chunks)
|
|
assert sorted(i for c in chunks for i in c)==list(range(15)) and seconds<22
|
|
# Sharing the endpoint, this answer's calls run one after another.
|
|
assert d._layout(costs,6,busy=True)[1]>seconds
|
|
|
|
|
|
def test_calls_never_exceed_the_input_limit(monkeypatch):
|
|
d=_fixed_calibration(monkeypatch)
|
|
sizes=[3000]*8+[700]*12
|
|
chunks,_=d._layout([70]*8+[25]*12,0,busy=False,sizes=sizes)
|
|
assert all(sum(sizes[i] for i in c)<=d.BATCH_INPUT_TOKENS and len(c)<=10 for c in chunks)
|
|
assert sorted(i for c in chunks for i in c)==list(range(20))
|
|
|
|
|
|
def test_summary_is_requested_per_call_and_ignored_when_not_requested():
|
|
pool,seeds=_students(['Eva Nová'])
|
|
def select(messages):
|
|
p=json.loads(messages[-1]['content'])
|
|
assert p['write_summary'] is False
|
|
sc=p['scopes'][0]
|
|
return {'choices':[{'message':{'content':json.dumps({'scopes':[{'scope_id':sc['scope_id'],
|
|
'matches_activity':True,'primary_ids':sc['seed_ids'],'parts':[],
|
|
'summary':[{'text':'Ručne označila dáta.','evidence_id':sc['seed_ids'][0]}]}]})}}]}
|
|
selected,_,_,prose=expand_and_select(Q,seeds,pool,select,plan=None,with_prose=True)
|
|
assert selected and prose=={}
|
|
|
|
|
|
def test_deadline_before_any_decision_falls_back_to_first_selection():
|
|
from tools.answers.model_client import DeadlineExceeded
|
|
pool,seeds=_students(['Eva Nová','Adam Nový'])
|
|
def late(_):raise DeadlineExceeded('budget')
|
|
selected,notes,bounded=expand_and_select(Q,seeds,pool,late)
|
|
assert {r['id'] for r in selected}=={r['id'] for r in seeds} and bounded and notes==[]
|
|
|
|
|
|
def test_rechecked_student_is_decided_on_the_whole_scope():
|
|
from tools.knowledge import discovery
|
|
names=[f'Meno{i} Priezvisko' for i in range(discovery.MAX_SCOPES)]
|
|
pool,seeds=_students(names)
|
|
extra=_profile('Adam Nový','# Diplomová práca 2027\nStav:\n'+'\n'.join(f'- Ručne označené dáta časť {i}.' for i in range(8)))
|
|
offered={}
|
|
def select(messages):
|
|
p=json.loads(messages[-1]['content'])
|
|
for sc in p['scopes']:offered[sc['person']]=sc
|
|
return _accept(p)
|
|
expand_and_select(Q,seeds,pool+extra,select,candidates=[*seeds,next(r for r in extra if 'označené' in r['text'])])
|
|
adam=offered['Adam Nový']
|
|
assert adam['detail'] is False and len(adam['evidence'])>3
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('source,text,kept',[
|
|
('- Prezretý video kanál s RASA tutoriálom.','Vytvoril prezretý video kanál s RASA tutoriálom.',False),
|
|
('- Vypracované experimenty pre obnovu interpunkcie.','Vytvoril vypracované experimenty pre obnovu interpunkcie.',False),
|
|
('- Vypracované experimenty pre obnovu interpunkcie.','Vypracoval experimenty pre obnovu interpunkcie.',True),
|
|
])
|
|
def test_wording_never_claims_work_the_unit_does_not_state(source,text,kept):
|
|
pool=_profile('Eva Nová','# Diplomová práca 2027\nStav:\n'+source)
|
|
seed=next(r for r in pool if r['text']==source)
|
|
_,(_,_,_,prose),_=_with_summary([(text,'unit')],pool=pool,seed=seed,question='Kto robil experimenty?')
|
|
assert (prose=={seed['id']:[text]})==kept
|