| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364 |
- import fs from 'node:fs';
- import path from 'node:path';
- import assert from 'node:assert/strict';
- import {read, publicApi, save, load} from './lib/research-enrichment.mjs';
- const work=process.argv[2]||'work/five-topic-research-20261111';
- const review=load(path.join(work,'reviewed-selection.json'));
- const before=load(path.join(work,'contract-before.json'));
- const contract=await read('contract');
- assert.equal(contract.generation,before.generation,'BASELINE_CHANGED_REAUDIT_REQUIRED');
- const inventory=load('work/library-cleanup-20261111/round-02/inventory-after-result.json').body.result;
- const documents=load(path.join(work,'documents-before.json')).records;
- const normalizeUrl=u=>{try{const p=new URL(u);return p.hostname.replace(/^www\./,'')+p.pathname.replace(/\/$/,'');}catch{return '';}};
- const normalizeText=t=>String(t||'').replace(/[\s\p{P}\p{S}]/gu,'').toLowerCase();
- const existingSocial=inventory.social.map(r=>({...r,urlKey:normalizeUrl(r.url),normalized:normalizeText(r.content)}));
- // Include later verified batches that are newer than the inventory snapshot.
- for(const name of fs.readdirSync('work/database-enrichment-20261110/batches')){
- const directory=path.join('work/database-enrichment-20261110/batches',name);
- if(!fs.existsSync(path.join(directory,'apply-result.json')))continue;
- const file=path.join(directory,'reviewed-source-index.json');
- if(!fs.existsSync(file))continue;
- for(const row of load(file).filter(r=>r.entity==='social'))existingSocial.push({...row,urlKey:normalizeUrl(row.url),normalized:normalizeText(row.body)});
- }
- const remotelyFound=new Map();
- for(const entity of ['social','documents']){
- const ids=review.selected.filter(r=>r.entity===entity).map(r=>r.id);
- if(!ids.length)continue;
- const page=await publicApi({op:'browse',entity,quality:'audit',filters:{recordId:ids},limit:100,generation:contract.generation});
- save(path.join(work,'dedup-remote-'+entity+'.json'),page);
- for(const r of page.records)remotelyFound.set(entity+':'+r.record_id,r);
- }
- const rejected=[],accepted=[];
- for(const row of review.selected){
- let existing=remotelyFound.get(row.entity+':'+row.id),reason=existing?'existing_exact_id':null;
- if(row.entity==='social'&&!existing){
- existing=existingSocial.find(r=>r.urlKey===normalizeUrl(row.url));
- if(existing)reason='existing_original_url';
- else {
- const text=normalizeText(row.body);
- existing=existingSocial.find(r=>text.length>80&&r.normalized.length>80&&
- (r.normalized===text||r.normalized.includes(text)||(text.includes(r.normalized)&&r.normalized.length/text.length>0.85)));
- if(existing)reason='same_original_text';
- }
- }
- if(row.entity==='documents'&&!existing){
- // Full source URL is confirmed by get because document browse is an index.
- for(const doc of documents){
- if(normalizeText(doc.title)===normalizeText(row.title)){
- existing=await read('get',{entity:'documents',id:doc.record_id,generation:contract.generation});reason='existing_document_title_requires_reuse';break;
- }
- }
- }
- if(existing)rejected.push({id:row.id,url:row.url,reason,existingId:existing.id||existing.record_id||existing.record?.id});
- else {
- const prior=accepted.find(r=>normalizeUrl(r.url)===normalizeUrl(row.url)||normalizeText(r.body)===normalizeText(row.body));
- if(prior)rejected.push({id:row.id,url:row.url,reason:'duplicate_within_batch',existingId:prior.id});
- else accepted.push(row);
- }
- }
- assert.equal(new Set(accepted.map(r=>r.entity+':'+r.id)).size,accepted.length);
- const result={schema:'ksf.five-topic.dedup.v1',ok:true,generation:contract.generation,checkedAt:new Date().toISOString(),
- acceptedIds:accepted.map(r=>({entity:r.entity,id:r.id})),rejected,selected:accepted,existingRecords:rejected.length,
- countsByTopic:Object.fromEntries(['wellness','student','premium','stockpile','novelty'].map(t=>[t,accepted.filter(r=>r.topics.some(x=>x.topic===t)).length]))};
- save(path.join(work,'dedup-result.json'),result);
- console.log(JSON.stringify({ok:true,accepted:accepted.length,rejected,topics:result.countsByTopic}));
|