207 lines
20 KiB
Python
207 lines
20 KiB
Python
"""Construct the preregistered question set from raw Markdown, not an index.
|
||
|
||
All line ranges/requirements below were reviewed against the original files.
|
||
This is evaluation-only code; it is never imported by production answering.
|
||
"""
|
||
import hashlib,json,re
|
||
from pathlib import Path
|
||
import frontmatter
|
||
ROOT=Path(__file__).resolve().parents[2]
|
||
WIKI=ROOT.parent/'zpwiki'
|
||
OUT=Path(__file__).parent
|
||
# slug, work, topic line, assignment start/end, latest complete meeting and source range
|
||
CONFIG=[
|
||
('adrian_ondra','Bakalársky projekt 2027',15,20,22,'2026-11-05',24,39),
|
||
('samuel_stucka','Bakalársky projekt 2027',15,28,33,'2026-09-29',35,45),
|
||
('sebastian_kiss','Bakalársky projekt 2027',15,21,23,'2026-10-23',26,36),
|
||
('yehor_piliavin','Bakalársky projekt 2027',15,25,27,'2026-10-15',36,49),
|
||
('kristian_sokolsky','Bakalársky projekt 2027',15,19,21,'2026-10-23',23,36),
|
||
('ronald_zalacko','Bakalárska práca 2026',22,29,33,'2025-12-08',35,55),
|
||
('simona_bobrovcanova','Bakalárska práca 2026',18,22,25,'2026-02-27',33,48),
|
||
('denis_le_thanh','Bakalárska práca 2026',24,29,32,'2025-10-03',35,52),
|
||
('oleksandr_dorybohov','Bakalárska práca 2026',20,29,32,'2026-03-23',40,54),
|
||
('rostyslav_rodzhuk','Bakalárska práca 2026',20,24,27,'2026-02-06',64,78),
|
||
]
|
||
# Denis's assignment is one explicit goal, not the subsequent meeting instructions.
|
||
CONFIG[7]=('denis_le_thanh','Bakalárska práca 2026',24,29,29,'2025-10-03',44,58)
|
||
|
||
def profile(slug):
|
||
p=next((WIKI/'pages/students').glob('*/'+slug+'/README.md'))
|
||
raw=p.read_text();page=frontmatter.loads(raw)
|
||
assert page.get('published') is not False
|
||
return {'name':page['title'],'path':str(p.relative_to(WIKI)),'lines':raw.splitlines(),
|
||
'sha256':hashlib.sha256(raw.encode()).hexdigest(),'tags':page.get('taxonomy',{}).get('tag',[])}
|
||
|
||
def evidence(p,start,end,requirements,terms):
|
||
assert 1<=start<=end<=len(p['lines'])
|
||
return {'path':p['path'],'sha256':p['sha256'],'url':'https://zp.kemt.fei.tuke.sk/'+p['path'].removeprefix('pages/').removesuffix('/README.md'),
|
||
'start':start,'end':end,'quote':'\n'.join(p['lines'][start-1:end]),
|
||
'requirements':requirements if isinstance(requirements,list) else [requirements],
|
||
'retrieval_terms':[[t] if isinstance(t,str) else t for t in terms]}
|
||
|
||
cases=[]
|
||
def add(category,q,refs,status='ok',requirements=(),max_words=None):
|
||
c={'id':f'T{len(cases)+1:03d}','category':category,'question':q,'expected_status':status,'evidence':refs,
|
||
'response_requirements':list(requirements)}
|
||
if max_words:c['max_words']=max_words
|
||
cases.append(c)
|
||
|
||
P=[profile(c[0]) for c in CONFIG]
|
||
# 20 metadata questions: values come directly from heading/topic lines.
|
||
for i,(p,c) in enumerate(zip(P,CONFIG)):
|
||
slug,label,title,start,end,date,mstart,mend=c
|
||
topic=p['lines'][title-1]
|
||
ref=evidence(p,min(title,13),max(title,15),
|
||
f"For {p['name']}, identify {label} with topic/title exactly '{topic}' and work year {label[-4:]}. Do not substitute study start.",[topic,label[-4:]])
|
||
add('title_year',f'Pri osobe {p["name"]} over názov záznamu "{label}" a pripoj evidovaný rok práce.',[ref])
|
||
refs=[]
|
||
for line_no,line in enumerate(p['lines'],1):
|
||
if re.match(r'^#+\s+(?:Bakalársk|Diplomov|Vedeck|Výskumn|Tímov)',line):
|
||
heading=line.lstrip('# ').strip()
|
||
refs.append(evidence(p,line_no,min(line_no+5,len(p['lines'])),
|
||
f"Include the distinct academic record '{heading}' of {p['name']}; title only if explicit, do not invent it.",[heading]))
|
||
add('work_inventory',f'Zostav prehľad evidovaných prác a projektov osoby {p["name"]}; pri záznamoch rozlíš typ, rok a doložený názov.',refs)
|
||
# 20 goals and planned actions. These do not establish completed work.
|
||
GOALS=[]
|
||
for p,c in zip(P,CONFIG):
|
||
slug,label,title,start,end,date,mstart,mend=c
|
||
goals=[line.strip() for line in p['lines'][start-1:end] if line.strip()]
|
||
refs=[evidence(p,n,n,f"{p['name']}, {label}: present this source goal as intended work, not a completed result: {g}",[re.sub(r'^\s*(?:[-*]|\d+[.)])\s*','',g)]) for n,g in enumerate(p['lines'],1) if start<=n<=end and g.strip()]
|
||
GOALS.append(refs)
|
||
add('goals',f'Aké sú ciele záznamu "{label}" osoby {p["name"]}? Zhrň všetky doložené body vlastnými slovami a nezamieňaj ich za hotové výsledky.',refs)
|
||
# All follow-up tasks of the selected meeting, without giving their answers in the question.
|
||
task_header=next(n for n,line in enumerate(p['lines'],1) if mstart<=n<=mend and line.strip().startswith('Úlohy'))
|
||
task_lines=[(n,line) for n,line in enumerate(p['lines'],1) if task_header<n<=mend and line.strip().startswith('- ')]
|
||
refs=[evidence(p,n,n,f"{p['name']}: this is an assigned follow-up task, not a delivered result: {line}",[line.strip('- ')]) for n,line in task_lines]
|
||
assert refs,(slug,'no tasks')
|
||
year,month,day=date.split('-')
|
||
add('planned_task',f'Aké úlohy dostal {p["name"]} podľa záznamu zo stretnutia {int(day)}.{int(month)}.{year}? Odlíš budúce úlohy od toho, čo už bolo urobené.',refs)
|
||
# 12 specific recorded results, methods and problems; independently located lines.
|
||
ACTIVITIES=[
|
||
('adrian_ondra',29,30,'Stanza/dependency parsing and spaCy are mentioned; tried SlovakBERT-NER, no proof of building a new model.', ['stanza','SlovakBERT-NER'],'metódy'),
|
||
('kristian_sokolsky',27,28,'Downloaded/parsed several MK pages and attempted fine-tuning; an attempt is not proven successful training.', ['Stiahnutie','pokus'],'pokrok'),
|
||
('ronald_zalacko',38,39,'React frontend; Flask backend; Postgres, SQLAlchemy, Vite JS, Websocket/socket.io, JWT auth; application works.', ['React','Flask','Postgres'],'metódy'),
|
||
('simona_bobrovcanova',37,38,'Small-model context limit and tested hierarchical summarization (chunks then summary of summaries).',['kontext','hierarchická'],'problémy'),
|
||
('simona_bobrovcanova',55,58,'Prepared court data and LoRA script, evaluated with ROUGE/BLEU and fine-tuned/evaluated the explicitly named models.', ['ROUGE','BLEU'],'výsledky'),
|
||
('oleksandr_dorybohov',74,78,'Local demo works with local model; searches court decisions/proceedings; OLLAMA gpt-oss:20B; written text compilation problem.', ['lokálne','OLLAMA'],'stav'),
|
||
('oleksandr_dorybohov',61,62,'Dockerfile/compose; httpx replaces requests, streamlit changes to chainlit; improved system prompt.', ['httpx','chainlit'],'metódy'),
|
||
('rostyslav_rodzhuk',65,68,'Internet fallback and option to add own documents are recorded; text has no progress.', ['fallback','dokumenty'],'pokrok'),
|
||
('yevhenii_medushivskyi',102,102,'Crawler based on Scrapy uses redis, Mongo, Trafilatura, Playwright and Celery.', ['Scrapy','Trafilatura'],'metódy'),
|
||
('yevhenii_medushivskyi',44,52,'Approximately 450 MB collected text. Plan: mine domains, continue collection to at least 5GB, improve deduplication/boilerplate removal e.g. justext. Do not claim 5GB achieved.', ['450MB','5GB'],'výsledky'),
|
||
('samuel_dzurina',57,58,'First chatbot searches PDF and internet via DUCKDUCK, streamlit UI; local RAG me5large indexing two CMR versions through langchain.', ['DUCKDUCK','langchain'],'metódy'),
|
||
('denis_le_thanh',48,58,'Studied LLM, transformers, anaconda, Okapi, open instruct and ollama. Planned PEFT-QLORA, Slovak Mistral, TRL, unsloth, LLaMA-Factory and GPU/Colab/server sequence are not proven deployment.', ['Naštudované','ollama','PEFT-QLORA'],'pokrok'),
|
||
]
|
||
ACTIVITY_QUESTIONS=[
|
||
'Aký model a ďalšie nástroje sú doložené pri práci Adriána Ondru na bakalárskom projekte 2027? Neoznačuj vyskúšanie za vytvorenie nového modelu.',
|
||
'Ako pokročil Kristián Sokolský s bakalárskym projektom 2027? Rozlišuj vykonaný zber dát od pokusu o dotrénovanie.',
|
||
'Aké technológie má Ronald Zalacko na frontende, backende a v databáze a čo je doložené o funkčnosti aplikácie?',
|
||
'S akým problémom pri dlhých textoch sa stretla Simona Bobrovčanová a aký prístup už vyskúšala na jeho riešenie?',
|
||
'Ktoré modely a vyhodnocovacie metriky sú zaznamenané pri bakalárskej práci Simony Bobrovčanovej podľa záznamu z 5.12.2025? Odlíš vykonané experimenty od plánov.',
|
||
'Čo už vie právny agent Oleksandra Dorybohova, ktorý model využíva a aký technický problém sa týkal písomnej časti?',
|
||
'Čo sa zmenilo v nasadení a používateľskom rozhraní Oleksandra Dorybohova podľa záznamu z 19.12.2025?',
|
||
'Aké možnosti internetového vyhľadávania a práce s vlastnými dokumentmi mal agent Rostyslava Rodzhuka podľa záznamu zo 6.2.2026?',
|
||
'Na akých technológiách je postavený webový crawler Yevheniiho Medushivského? Uveď doložené použité nástroje.',
|
||
'Koľko textu mal Yevhenii Medushivskyi získaného podľa záznamu z 13.3.2026 a čo mal ešte urobiť? Cieľ nezamieňaj za už dosiahnutý objem.',
|
||
'Aké vyhľadávanie, rozhranie, model a lokálny RAG mal Samuel Džurina podľa záznamu zo 17.12.2025? Čo sa indexovalo?',
|
||
'Čo mal Denis Le Thanh naštudované a čo mal ešte vyskúšať podľa záznamu z 3.10.2025? Štúdium nie je dôkaz hotového nasadenia.'
|
||
]
|
||
for qi,(slug,start,end,requirement,terms,facet) in enumerate(ACTIVITIES):
|
||
p=profile(slug);line=p['lines'][start-1]
|
||
# Fail authoring rather than silently select the wrong lines after a source edit.
|
||
assert all(any(t.lower() in part.lower() for part in p['lines'][start-1:end]) for t in terms),(slug,start,p['lines'][start-1:end])
|
||
label=next((c[1] for c in CONFIG if c[0]==slug),'Bakalárska práca 2026')
|
||
ref=evidence(p,start,end,f'{p["name"]}, {label}: {requirement}',terms)
|
||
add('recorded_'+facet,ACTIVITY_QUESTIONS[qi],[ref])
|
||
# 8 dates: ask for complete recorded dates, not inferred execution dates.
|
||
for p,c in list(zip(P,CONFIG))[:7]+[(P[8],CONFIG[8])]:
|
||
slug,label,title,start,end,date,mstart,mend=c
|
||
ref=evidence(p,mstart,mend,
|
||
f"Latest meeting with an explicitly complete date is {date}; explain its source-recorded state/tasks for {p['name']}. Record date is not the exact date of execution. Undated records do not acquire its year.", [date[:4]] )
|
||
add('latest_record',f'Kedy je posledné stretnutie osoby {p["name"]} s úplným dátumom a čo sa pri ňom riešilo? Dátum záznamu odlíš od vykonania práce.',[ref])
|
||
# 8 comparisons of requested goals; per-side requirements remain separate.
|
||
for a,b in [(0,1),(2,3),(3,4),(5,6),(6,8),(8,9),(0,2),(5,9)]:
|
||
add('comparison',f'Porovnaj zadanie osoby {P[a]["name"]} pri "{CONFIG[a][1]}" so zadaním osoby {P[b]["name"]} pri "{CONFIG[b][1]}". Vysvetli rozdiel v cieľoch a uveď oba zdroje.',[*GOALS[a],*GOALS[b]])
|
||
# 6 relations/joins, directly checked against YAML and explicit work headings.
|
||
for a,b in [(3,4),(6,7),(8,9)]:
|
||
common=sorted(set(P[a]['tags'])&set(P[b]['tags']))
|
||
refs=[evidence(P[x],1,8,f"Tags of {P[x]['name']} are {P[x]['tags']}; shared tags of this pair are exactly {common}.",common) for x in [a,b]]
|
||
add('shared_tags',f'Ktoré tagy spájajú osoby {P[a]["name"]} a {P[b]["name"]}? Uveď iba spoločný prienik a zdroje.',refs)
|
||
for a,b in [(0,1),(2,4),(5,6)]:
|
||
year=CONFIG[a][1][-4:];assert year==CONFIG[b][1][-4:]
|
||
refs=[evidence(P[x],13,24,f"{P[x]['name']} has {CONFIG[x][1]}. Common recorded work year of the pair is {year}, not study-start year.",[year]) for x in [a,b]]
|
||
add('shared_years',f'Ktorý rok práce je spoločný pre osoby {P[a]["name"]} a {P[b]["name"]}? Najprv uveď spoločný rok, potom príslušné záznamy.',refs)
|
||
# 6 robustness/absence checks. Unknown-world claims are never the expected result.
|
||
for i,variant in [(2,'Sebastián Kiš'),(5,'Ronald Zalackoo')]:
|
||
c=CONFIG[i];p=P[i];line=c[2]
|
||
ref=evidence(p,1,max(24,line),f"Resolve typo to {p['name']}, report the recognized name and the source title/work year, without inventing a new person.",[c[1][-4:]])
|
||
add('name_typo',f'Zisti názov práce alebo projektu osoby {variant} a evidovaný rok. Pri oprave mena povedz, koho si rozpoznal.',[ref])
|
||
for i in [0,8]:
|
||
p=P[i]
|
||
assert not any('1991' in line for line in p['lines'])
|
||
ref=evidence(p,1,24,f"No 1991 academic record occurs in this profile; report absent evidence for this person/year, never substitute {CONFIG[i][1][-4:]}.",['1991'])
|
||
add('missing_year',f'Čo je doložené o práci osoby {p["name"]} z roku 1991? Nepouži záznam iného roka.',[ref],'no_answer')
|
||
add('unknown_person','Na čom pracoval Quirin Nenájdený a aké výsledky dosiahol?',[],status='no_answer',requirements=['No matching source person; ask for a real full name without global unrelated results or world-level nonexistence.'])
|
||
sam=[profile('samuel_stucka'),profile('samuel_dzurina'),profile('samuel_horani'),profile('samuel_vasil'),profile('samuel_sirotnik')]
|
||
add('ambiguous_person','Čo sa podarilo Samuelovi pri jeho práci?',[evidence(p,1,8,'Several actual people named Samuel exist; ask which full name is meant, never guess one.',[]) for p in sam],status='ambiguous')
|
||
# Open-set discovery/similarity: reference coverage is not exhaustive peer recall.
|
||
def ref_line(slug,needle,req,terms):
|
||
p=profile(slug);n=next(i for i,line in enumerate(p['lines'],1) if needle in line)
|
||
return evidence(p,n,n,req,terms)
|
||
for slug,needle in [('adrian_ondra','Vyskúšaný model SlovakBERT-NER'),
|
||
('simona_bobrovcanova','Vyskúšaná hierarchická sumarizácia'),
|
||
('samuel_dzurina','Urobené lokálne RAG'),
|
||
('yevhenii_medushivskyi','Vytvorený crawler na báze Scrapy')]:
|
||
p=profile(slug)
|
||
ref=ref_line(slug,needle,f"Reference person {p['name']} has this documented topic/activity. Return at least one other actually sourced person with a substantive related topic; distinguish assigned, planned and actual work. No collaboration inference.",[])
|
||
add('similarity_open_set',f'Kto sa zaoberal obsahovo podobnou témou ako {p["name"]}? Vysvetli konkrétne prepojenie a dolož obe osoby.',[ref],requirements=['At least one peer supported by its own original source. Generic Python/LLM study alone is not sufficient.'])
|
||
cases[-1]['retrieval_reference_mode']='anchor_coverage_not_exhaustive_peer_recall'
|
||
DISCOVERY=[
|
||
('simona_bobrovcanova','Vyskúšaná hierarchická sumarizácia','Kto už vyskúšal hierarchickú sumarizáciu na problém s dlhým kontextom? Stačí jeden doložený príklad.', 'One actual tested hierarchical summarization example, not an unexecuted task.'),
|
||
('yevhenii_medushivskyi','Vytvorený crawler na báze Scrapy','U koho je doložené vytvorenie webového crawlera pomocou Scrapy? Uveď konkrétny príklad a použitý technologický základ.', 'One actual built Scrapy crawler with a correctly bound person and stack.'),
|
||
('rostyslav_rodzhuk','Inteligentný agent pre podporu práce špeciálneho pedagóga','Ktorý študent sa venoval agentovi pre podporu špeciálneho pedagóga? Priblíž zameranie, bez domýšľania výsledkov.', 'One person/work focused on a special educator assistant; topic/assignment is not implementation proof.'),
|
||
('yehor_piliavin','Vyvinúť metódu na vytváranie riadiacich vektorov','Kto sa venoval riadeniu generovania veľkého jazykového modelu pomocou steering vectors? Rozlíš doložené ciele od výsledkov.', 'A documented steering-vectors project; planned method development is not completed evaluation.')]
|
||
for slug,needle,q,req in DISCOVERY:
|
||
ref=ref_line(slug,needle,req+' This reference is a verified example; another independently sourced correct example is acceptable.',[])
|
||
add('discovery_open_set',q,[ref]);cases[-1]['retrieval_reference_mode']='example_coverage_not_exhaustive_discovery_recall'
|
||
for a,b,start,end in [(0,4,'1.9.2026','6.11.2026'),(6,8,'1.1.2026','30.4.2026'),
|
||
(5,None,'1.9.2025','31.12.2025'),(9,7,'1.9.2025','28.2.2026')]:
|
||
other=profile('yevhenii_medushivskyi') if b is None else P[b]
|
||
ca=CONFIG[a];refs=[evidence(P[a],ca[6],ca[7],f"For {P[a]['name']}, provide a documented topic/activity in a record within {start}–{end}. Work year is not record/event date; an undated note cannot acquire a year.",[])]
|
||
if b is None:refs.append(ref_line('yevhenii_medushivskyi','Vytvorený crawler na báze Scrapy',f"For {other['name']}, give one actually recorded activity in {start}–{end}; source example is the 7.11.2025 crawler record.",[]))
|
||
else:refs.append(evidence(other,CONFIG[b][6],CONFIG[b][7],f"For {other['name']}, give one documented record/activity within {start}–{end}; exact execution time is not known from a meeting date.",[]))
|
||
add('record_period',f'Aké témy alebo činnosti sú zaznamenané pri osobách {P[a]["name"]} a {other["name"]} od {start} do {end}? Uveď pre každého jeden doložený príklad. Rozlišuj rok práce od dátumu záznamu.',refs)
|
||
add('supervisor_role','Kto vedie bakalársku prácu Ronalda Zalacka a kto je pri profile uvedený ako autor dokumentu? Tieto roly odlíš.',
|
||
[evidence(P[5],1,22,'Supervisor explicitly Matúš Pleva; document author Daniel Hladek. Do not infer that Hladek supervises this thesis.',['Matúš Pleva','Daniel Hladek'])])
|
||
add('advisor_roles','Kto je vedúci a kto konzultuje bakalársku prácu Denisa Le Thanha? Uveď doložené roly, nie odhad z autora stránky.',
|
||
[evidence(P[7],1,29,'Supervisor Tomáš Kormaník; consultations Kristián Sopkovič. Document author Daniel Hladek is not supervision evidence.',['Tomáš Kormaník','Kristián Sopkovič'])])
|
||
add('study_status','Ukončil Rostyslav Rodzhuk štúdium úspešne, alebo máme iba záznam o dokončených výstupoch práce? Vysvetli, čo je naozaj doložené.',
|
||
[ref_line('rostyslav_rodzhuk','Dokončené texty aj program.','Texts and program finished, under an undated-year record 11.5.; this does NOT prove successful thesis defense or graduation.',['Dokončené texty'])],status='no_answer',requirements=['Do not infer failed graduation or that the study is still ongoing either.'])
|
||
add('time_roles','Pri Yehorovi Piliavinovi odlíš rok začiatku štúdia, rok bakalárskeho projektu a najnovší úplný dátum stretnutia. Znamená rok projektu dátum vykonania činnosti?',
|
||
[evidence(P[3],1,40,'Study start 2024; bachelor project 2027; most recent fully dated record 2026-10-15. Project year does not date actual execution.',['2024','2027','2026'])])
|
||
# Stronger closed-set tests replace redundant single-record inventories/tasks.
|
||
def replace_case(index,category,q,refs,status='ok'):
|
||
cases[index]={'id':cases[index]['id'],'category':category,'question':q,'expected_status':status,
|
||
'evidence':refs,'response_requirements':[]}
|
||
for index,person_index,count in [(3,1,1),(19,9,2)]:
|
||
p=P[person_index]
|
||
refs=[evidence(p,n,n,f"Exactly {count} distinct explicit work/project headings are recorded in this profile; count records, not meetings or study-start years. This heading is one of the records.",[line.lstrip('# ').strip()])
|
||
for n,line in enumerate(p['lines'],1) if re.match(r'^#+\s+(?:Bakalársk|Diplomov|Vedeck|Výskumn|Tímov)',line)]
|
||
assert len(refs)==count
|
||
replace_case(index,'record_count',f'Koľko rôznych prác a projektov má evidovaných {p["name"]}? Spočítaj akademické záznamy, nie stretnutia ani podúlohy.',refs)
|
||
shared=[i for i,c in enumerate(cases) if c['category']=='shared_years']
|
||
replace_case(shared[0],'no_shared_year',f'Majú {P[0]["name"]} a {P[5]["name"]} spoločný rok evidovanej práce alebo projektu? Ak nie, nezamieňaj ich odlišné roky.',
|
||
[evidence(P[x],13,24,f"{P[x]['name']} has only work year {CONFIG[x][1][-4:]}; the two profiles have no common academic work year.",[CONFIG[x][1][-4:]]) for x in [0,5]],'no_answer')
|
||
replace_case(shared[2],'shared_years',f'V ktorom roku majú {P[5]["name"]} a {P[9]["name"]} obaja evidovanú prácu? Najprv uveď prienik rokov, potom príslušné záznamy.',
|
||
[evidence(P[x],13,24,'Common work year is 2026. The 2025 scientific project belongs only to Rostyslav and must not be returned as shared.',['2026']) for x in [5,9]])
|
||
for slot,tag,indices in [(21,'alignment',[3,4]),(27,'nlp',[0,1])]:
|
||
refs=[evidence(P[x],1,27,f"This person has tag {tag} AND an explicit Bachelor project 2027. The complete matching set is {[P[i]['name'] for i in indices]}. Preserve person-level join, work type and year; include the documented topic if available.",[tag,'2027']) for x in indices]
|
||
replace_case(slot,'tag_work_year_join',f'Ktorí študenti majú tag {tag} a zároveň bakalársky projekt z roku 2027? Pri každom uveď meno a doložený názov alebo tému projektu.',refs)
|
||
|
||
assert len(cases)==96,len(cases)
|
||
value={'schema':1,'split':'TEST','authoring':'AI-authored and source-checked BEFORE execution; no human validation claimed.',
|
||
'sampling':'Predeclared user-task strata, same existing public corpus; exact questions excluded from historical question files. These are not unseen-document tests.',
|
||
'target':.94,'cases':cases}
|
||
if __name__=='__main__':
|
||
(OUT/'test.json').write_text(json.dumps(value,ensure_ascii=False,indent=2)+'\n')
|
||
print('Wrote',len(cases),'cases')
|