from pathlib import Path
import shutil
R=Path('/mnt/data/work4110/site');E=R/'доказательства/редакция-4.11.0';p=R/'ресурсы/сценарии/доказательный-контур.js'
shutil.copyfile(p,E/'кандидат-строки-не-принят.js.txt');shutil.copyfile(R/'доказательства/редакция-4.10.0/границы-4-10.json',E/'кандидат-строки-границы.json')
s=(E/'ядро-4.10.0-до-изменения.js.txt').read_text()
start=s.index('    const calls=new Map(), observationIds=new Map();');end=s.index('    const groups = new Map();',start)
s=s[:start]+'''    const calls=new Map(), observationIds=new Map();
    // Одиночная запись не требует отдельного массива на каждом индексе.
    // Список создаётся только при фактическом повторе; сами наблюдения не копируются.
    const indexCall=(map,key,item)=>{
      const old=map.get(key);
      if(!old)map.set(key,item);
      else if(Array.isArray(old))old.push(item);
      else map.set(key,[old,item]);
    };
    for(const source of valid)if(source.запись.серия){
      const series=source.запись.серия,placement=canonical([series.идентификатор,series.окно,series.условие]);
      for(const observation of series.наблюдения){
        if(++stepCount%250===0)yield {этап:"индекс вызовов",обработано:stepCount};
        const item={source,placement,call:canonical([observation.сеанс,Date.parse(observation.дата_время),observation.запрос])};
        indexCall(calls,item.call,item);indexCall(observationIds,observation.идентификатор,item);
      }
    }
    for(const list of calls.values())if(Array.isArray(list)&&new Set(list.map(x=>x.placement)).size>1)add('повтор вызова между группами','Один вызов внесён в разные серии, окна или условия. Он не становится независимым повтором; затронутые исходники исключены до рассмотрения.',list.map(x=>x.source.отпечаток_исходного_файла));
    for(const [id,list] of observationIds)if(Array.isArray(list)&&new Set(list.map(x=>x.call)).size>1)add('коллизия наблюдения',`Идентификатор наблюдения ${id} относится к разным вызовам; автоматического выбора нет.`,list.map(x=>x.source.отпечаток_исходного_файла));
    calls.clear();observationIds.clear();
''' + s[end:]
start=s.index('        const observations = new Map();');end=s.index('        for (const condition of first.условия_серии)',start)
s=s[:start]+'''        const observations = new Map();
        for (const s of wl) for (const r of s.запись.серия.наблюдения) {
          if(++stepCount%250===0)yield {этап:"наблюдения окна",обработано:stepCount};
          const key=r.идентификатор,condition=s.запись.серия.условие,previous=observations.get(key);
          if(previous){
            // Полную нормализованную проекцию строим только для сравнения
            // совпавшего ID. Все поля, включая доказательство вызова, сохраняются.
            const view=(record,c)=>({условие:c,...record,дата_время:new Date(record.дата_время).toISOString()});
            if(!same(view(previous.record,previous.condition),view(r,condition)))add('наблюдение',`Серия ${id}: разные данные наблюдения ${key}.`,[s.отпечаток_исходного_файла,previous.hash]);
          }else observations.set(key,{record:r,condition,hash:s.отпечаток_исходного_файла});
        }
        const invocations = new Map(); let duplicated = 0; let synthetic = 0;
        for (const entry of observations.values()) {
          const {record:r,condition,hash:sourceHash}=entry;
          if(++stepCount%250===0)yield {этап:"повторы окна",обработано:stepCount};
          const invocation=canonical([r.сеанс,Date.parse(r.дата_время),r.запрос]);
          const previous=invocations.get(invocation);
          if(previous) {
            duplicated++;
            if(!same([previous.condition,previous.record.ответ,previous.record.класс_доказательства],[condition,r.ответ,r.класс_доказательства]))
              add('совпадающий вызов',`Серия ${id}: один вызов имеет разные ответы, условия или классы.`,[previous.hash,sourceHash]);
            continue;
          }
          invocations.set(invocation,entry);
          if(!['НП','ДФ'].includes(r.класс_доказательства)){synthetic++;continue;}
          counters[condition]=(counters[condition]||0)+1;
        }
''' + s[end:];p.write_text(s)
print('patched indexes only; canonical unchanged')
