verify-page-observations-readback.mjs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. // Independently inspect existing records. Only GETs and ksfGetRecord are allowed here.
  2. import fs from 'node:fs';
  3. import crypto from 'node:crypto';
  4. import assert from 'node:assert/strict';
  5. import {invoke,classRows} from '../server/scripts/platform-client.mjs';
  6. const taskKey='KSF-商品页面观察-20261102-01';
  7. const agentKey='agent-account:7c9f85363ca4c5a529a5b363308b065998924e2394e846145a5c62c904506d2e';
  8. const inputsDirectory='work/current-product-observations-20261102/';
  9. const archiveFile='work/current-product-archive-20261102.zip';
  10. const expectedArchive='0ec14dcccbce26bc0fc1a368bfaef4bbc32ea6f0b651ae31df5dd28a8bc0e7a5';
  11. const imported=[
  12. {key:'suning',sourceId:'pBQEl6tlfY',evidenceId:'cTo8j9FdIJ',jobId:'QF1TFQG7bY'},
  13. {key:'huajie',sourceId:'yo2mA_FByU',evidenceId:'dU0wGRdC0D',jobId:'RQoaUYKU8l'},
  14. ];
  15. const ptr=(className,objectId)=>({__type:'Pointer',className,objectId});
  16. const sha=bytes=>crypto.createHash('sha256').update(bytes).digest('hex');
  17. const read=async(className,objectId)=>{
  18. const r=await invoke('ksfGetRecord',{className,objectId});
  19. assert.equal(r.http,200);assert.equal(r.body?.result?.ok,true);
  20. return r.body.result.data.record;
  21. };
  22. let check='local archive';
  23. try{
  24. const archive=fs.readFileSync(archiveFile);
  25. assert.equal(archive.length,614046);assert.equal(sha(archive),expectedArchive);
  26. const packageReceipt=JSON.parse(fs.readFileSync('work/current-product-observations-package-receipt.json','utf8'));
  27. const reports=[];
  28. for(const ids of imported){
  29. check=ids.key+' records';
  30. const inputBytes=fs.readFileSync(inputsDirectory+ids.key+'.input.json');
  31. assert.equal(sha(inputBytes),packageReceipt.inputHashes[ids.key+'.input.json']);
  32. const input=JSON.parse(inputBytes),row=input.records[0];
  33. const [source,evidence,job]=await Promise.all([
  34. read('KfSource',ids.sourceId),read('KfEvidence',ids.evidenceId),read('KfImportJob',ids.jobId),
  35. ]);
  36. for(const [field,value]of Object.entries(input.source))assert.deepEqual(source[field]??null,value,ids.key+' source '+field);
  37. assert.equal(source.contentHash,sha(input.source.rawText));
  38. assert.equal(source.recordState,'active');
  39. for(const [field,value]of Object.entries(row.data))assert.deepEqual(evidence[field]??null,value,ids.key+' evidence '+field);
  40. assert.deepEqual(evidence.sourceRef,ptr('KfSource',ids.sourceId));
  41. assert.equal(evidence.productRef??null,null);assert.equal(evidence.artifactRef??null,null);
  42. assert.equal(evidence.recordState,'active');
  43. assert.equal(evidence.evidenceKind,'retailer_listing_observation');
  44. assert.equal(evidence.verificationState,'archived_text_and_screenshot_checked');
  45. assert.equal(evidence.collectedAt??null,null);assert.equal(source.collectedAt??null,null);
  46. assert.equal(evidence.locator.archiveSha256,sha(archive));
  47. for(const file of [evidence.locator.textOrigin,evidence.locator.screenshot]){
  48. assert.equal(sha(fs.readFileSync('work/current-product-archive-20261102/originals/'+file.file)),file.sha256);
  49. }
  50. const locator=evidence.locator;
  51. assert.equal([...source.rawText].slice(locator.startCharacter,locator.endCharacter).join(''),evidence.quote);
  52. assert.equal(locator.exactCaptureTimestampVerified,false);
  53. assert.equal(job.status,'succeeded');assert.equal(job.expectedCount,1);assert.equal(job.successCount,1);assert.equal(job.failureCount,0);
  54. assert.deepEqual(job.inputSourceRef,ptr('KfSource',ids.sourceId));
  55. assert.equal(job.producer.agent.key,agentKey);assert.equal(job.producer.agent.name,'王刚001');
  56. check=ids.key+' uniqueness and scope';
  57. const [sources,evidences,jobs,items,products,artifacts]=await Promise.all([
  58. classRows('KfSource',{contentHash:source.contentHash,sourceUrl:source.sourceUrl,platform:source.platform,dataMode:'live',recordState:'active'}),
  59. classRows('KfEvidence',{sourceRef:ptr('KfSource',ids.sourceId)}),
  60. classRows('KfImportJob',{inputSourceRef:ptr('KfSource',ids.sourceId)}),
  61. classRows('KfImportItem',{jobRef:ptr('KfImportJob',ids.jobId)}),
  62. classRows('KfProduct',{sourceRef:ptr('KfSource',ids.sourceId)}),
  63. classRows('KfArtifact',{sourceRef:ptr('KfSource',ids.sourceId)}),
  64. ]);
  65. assert.deepEqual(sources.map(x=>x.objectId),[ids.sourceId]);
  66. assert.deepEqual(evidences.map(x=>x.objectId),[ids.evidenceId]);
  67. assert.deepEqual(jobs.map(x=>x.objectId),[ids.jobId]);
  68. assert.equal(items.length,1);assert.equal(items[0].targetClass,'KfEvidence');
  69. assert.equal(items[0].targetId,ids.evidenceId);assert.equal(items[0].itemKey,row.itemKey);
  70. assert.equal(items[0].businessKey,row.externalKey);assert.equal(items[0].status,'succeeded');assert.equal(items[0].operation,'created');
  71. assert.equal(products.length,0);assert.equal(artifacts.length,0);
  72. reports.push({...ids,sourceOperation:'created',created:1,unchanged:0,sourceMatchesFixedInput:true,evidenceMatchesFixedInput:true,
  73. quoteAndUnicodeLocatorMatched:true,archiveHashAndOriginalHashesVerified:true,exactCaptureTimePreservedUnknown:true,
  74. evidenceKind:evidence.evidenceKind,verificationState:evidence.verificationState,locatorFieldCount:Object.keys(locator).length,
  75. duplicateSourceCount:sources.length,sourceProductCount:products.length,sourceArtifactCount:artifacts.length,sameAgent:true});
  76. }
  77. check='original S1';
  78. const [artifact,versions]=await Promise.all([
  79. read('KfArtifact','G8IrkkDG7X'),classRows('KfArtifact',{reportSeriesKey:'KSF-真实资料-20261102-01:S1'}),
  80. ]);
  81. const template=JSON.parse(fs.readFileSync('work/first-live-S1-v2-template.json','utf8'));
  82. for(const field of ['rawText','version','stageCode','title'])assert.deepEqual(artifact[field],template[field]);
  83. assert.deepEqual(artifact.report.evidence,template.report.evidence);
  84. assert.deepEqual(artifact.meta.workshop.sourceIds,template.sourceIds);
  85. assert.deepEqual(versions.map(x=>[x.objectId,x.version]).sort((a,b)=>a[1]-b[1]),[['cOm6c8gg7z',1],['G8IrkkDG7X',2]]);
  86. const result={ok:true,operation:'page-observations-independently-verified',at:new Date().toISOString(),taskKey,readOnly:true,businessWrites:0,
  87. dataMode:'live',sourceCount:2,evidenceCount:2,newProductsFromTheseSources:0,newArtifactsFromTheseSources:0,imports:reports,
  88. archive:{sha256:expectedArchive,bytes:archive.length,verifiedLocalPath:'F:/kangshifu-board/'+archiveFile,remoteArchivePackageAvailability:'not independently verified'},
  89. originalS1Preserved:true,originalS1ArtifactId:'G8IrkkDG7X',formalReportPublished:false,
  90. currentStockVerified:false,unitPriceVerified:false,consumerReviewsCollected:false,huajieCurrencyVerified:false};
  91. fs.writeFileSync('work/page-observations-readback-20261102.json',JSON.stringify(result,null,2)+'\n');
  92. console.log(JSON.stringify(result,null,2));
  93. }catch(e){
  94. const result={ok:false,operation:'page-observations-independent-readback-stopped',check,errorCode:'PAGE_OBSERVATIONS_READBACK_FAILED',
  95. checkLine:e.stack?.match(/verify-page-observations-readback\.mjs:(\d+):/)?.[1]??null,readOnly:true,businessWrites:0};
  96. fs.writeFileSync('work/page-observations-readback-20261102.json',JSON.stringify(result,null,2)+'\n');
  97. console.error(JSON.stringify(result,null,2));process.exitCode=1;
  98. }