| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- """Verify the supplied capture archive and prepare two fixed inputs; no network or DB access."""
- from pathlib import Path
- import collections
- import hashlib
- import json
- import zipfile
- ROOT = Path(__file__).resolve().parents[1]
- ARCHIVE = ROOT / 'work/current-product-archive-20261102.zip'
- CAPTURE = ROOT / 'work/current-product-archive-20261102'
- DEST = ROOT / 'work/current-product-observations-20261102'
- DOCS = Path(r'F:\文档\工作坊当前资料')
- EXPECTED_ARCHIVE = '0ec14dcccbce26bc0fc1a368bfaef4bbc32ea6f0b651ae31df5dd28a8bc0e7a5'
- TASK = 'KSF-商品页面观察-20261102-01'
- REPORTED_TIME = '2026-11-02 19:11–19:13 +08:00(采集者回报范围;无精确时间原始日志)'
- sha = lambda b: hashlib.sha256(b).hexdigest()
- encode = lambda obj: (json.dumps(obj, ensure_ascii=False, indent=2) + '\n').encode('utf-8')
- def retain(path, value):
- if path.exists():
- assert path.read_bytes() == value, f'Preserve different existing file: {path}'
- else:
- path.parent.mkdir(parents=True, exist_ok=True)
- with path.open('xb') as out:
- out.write(value)
- def main():
- archive = ARCHIVE.read_bytes()
- assert len(archive) == 614046 and sha(archive) == EXPECTED_ARCHIVE
- manifest = json.loads((CAPTURE / 'originals-manifest.json').read_text('utf-8'))
- assert len(manifest) == 4 and len({m['file'] for m in manifest}) == 4
- files = []
- for entry in manifest:
- assert Path(entry['file']).name == entry['file']
- blob = (CAPTURE / 'originals' / entry['file']).read_bytes()
- assert len(blob) == entry['size'] and sha(blob) == entry['sha256']
- files.append({'file': entry['file'], 'bytes': len(blob), 'sha256': sha(blob), 'matched': True})
- by_name = {entry['file']: entry for entry in files}
- card_blob = (CAPTURE / 'originals/suning-card.html').read_bytes()
- card_text = card_blob.decode('utf-8')
- card = json.loads(card_text)
- assert len(card) == 1 and set(card[0]) == {'html', 'text'}
- assert len(card_text) == 4341 and len(card_blob) == 4681
- widths = collections.Counter(len(c.encode('utf-8')) for c in card_text)
- assert widths == {1: 4170, 2: 2, 3: 169}
- assert len(card[0]['html']) == 4000 and card[0]['html'].endswith('aria-hidden="tru')
- suning = card[0]['text']
- huajie = (CAPTURE / 'originals/huajie-5904.txt').read_text('utf-8')
- quote_start = huajie.index('新品 康师傅 红烧牛肉拌面115g')
- quote_end = huajie.index('进入店铺', quote_start) + len('进入店铺')
- huajie_quote = huajie[quote_start:quote_end]
- assert huajie.count('$') == 23
- assert all(word not in huajie for word in ['USD', '美元', '人民币'])
- review = {
- 'ok': True, 'operation': 'current-product-archive-independently-reviewed',
- 'archiveSha256': EXPECTED_ARCHIVE, 'archiveBytes': len(archive), 'archiveEntries': 8,
- 'originalsVerified': files, 'screenshotsVisuallyReviewed': True,
- 'suningSize': {'bytes': len(card_blob), 'characters': len(card_text),
- 'utf8WidthCounts': dict(widths),
- 'explanation': '169 three-byte characters add 338 bytes; two two-byte yen signs add 2 bytes; total difference 340',
- 'historicalFirstToolOutputAvailable': False},
- 'suningFormat': {'container': 'JSON array with html and text', 'htmlCharacters': 4000,
- 'htmlFragmentTruncated': True, 'completePageHtml': False},
- 'huajieCurrency': 'dollar sign only; currency and market region not verified',
- 'missingSizeNote': {'presentInPackage': False, 'removalActorOrIntentVerified': False,
- 'originalsCurrentIntegrityBlocked': False},
- 'verifiedScope': 'archived page display observations; not current stock, fulfilled orders or verified SKU parameters',
- 'readOnly': True, 'networkRequests': 0, 'businessWrites': 0,
- }
- retain(ROOT / 'work/current-product-archive-review-20261102.json', encode(review))
- def task(key, url, platform, title, raw, quote, original, screenshot, limitations):
- begin = raw.index(quote)
- record = {
- 'itemKey': key + '-page-observation', 'className': 'KfEvidence',
- 'externalKey': TASK + ':' + key + ':page-observation:v1',
- 'data': {
- 'dataMode': 'live', 'quote': quote,
- 'evidenceKind': 'retailer_listing_observation',
- 'platform': platform, 'quoteForm': 'verbatim_excerpt',
- 'verificationState': 'archived_text_and_screenshot_checked',
- 'collectedAt': None,
- 'locator': {
- 'url': url, 'section': '已归档商品卡文字' if key == 'suning' else '已归档店铺商品页面文字',
- 'startCharacter': begin, 'endCharacter': begin + len(quote), 'unit': 'Unicode code point',
- 'observedAtReported': REPORTED_TIME, 'exactCaptureTimestampVerified': False,
- 'archiveSha256': EXPECTED_ARCHIVE,
- 'textOrigin': {'file': original, 'sha256': by_name[original]['sha256'],
- 'extraction': 'JSON[0].text' if key == 'suning' else 'UTF-8 text file'},
- 'screenshot': {'file': screenshot, 'sha256': by_name[screenshot]['sha256']},
- 'limitations': limitations,
- **({'productLink': 'https://product.suning.com/0000000000/12424158513.html',
- 'deliveryRegionVisible': '江西南昌'} if key == 'suning' else {}),
- },
- },
- }
- assert raw[begin:begin + len(quote)] == quote
- return {
- 'source': {'title': title, 'sourceKind': 'ecommerce', 'dataMode': 'live',
- 'sourceUrl': url, 'platform': platform, 'rawText': raw, 'collectedAt': None},
- 'records': [record],
- }
- inputs = {
- 'suning.input.json': task(
- 'suning', 'https://search.suning.com/%E5%BA%B7%E5%B8%88%E5%82%85%E6%8B%8C%E9%9D%A2/',
- 'suning', '苏宁搜索商品卡观察(2026-11-02;规格与库存未核实)', suning, suning,
- 'suning-card.html', 'kangshifu-dangqian-suning-list.png',
- ['页面展示不证明有货、可下单或当前在售。', '17.70元对应件数未知,不是已核实单碗价或成交价。',
- '领券200-10只是商品卡提示,适用范围和优惠门槛未独立核实。',
- '标题与主图不能补足净含量、组合件数、配料或营养参数;不创建或关联既有产品/SKU。',
- '100+评价只是页面计数提示,没有取得消费者逐字评价。',
- 'HTML片段已截断;source.rawText仅来自随包JSON导出的text字段。']),
- 'huajie.input.json': task(
- 'huajie', 'https://www.huajiemarket.com/goods/info/5904', 'huajiemarket',
- '华姐零食铺商品页面观察(2026-11-02;币种与市场地区未核实)', huajie, huajie_quote,
- 'huajie-5904.txt', 'kangshifu-dangqian-huajie-5904.png',
- ['4.59与5.99使用$符号;具体币种未知,不按美元或人民币换算。',
- '市场地区和配送地区未核实,不作为中国国内在售或价格对比证据。',
- '115g只是标题标示;碗形仅为主图图示,非规格参数已核实;无出口包装证据。',
- '有效期和加入购物车入口只记录页面文字,不证明实物批次、库存或完成交易。',
- '营销文案属于店铺描述,不是消费者反馈;不创建或关联既有产品/SKU。']),
- }
- for name, value in inputs.items():
- retain(DEST / name, encode(value))
- review_text = '''当前商品原件核对结果(2026年11月2日)
- 附件614,046字节,包SHA-256与运维回报一致;8个文件齐全,四份原件的大小与完整哈希4/4吻合。Codex已实际查看两张截图并读取文字。
- 苏宁截图及商品卡文字支持商品标题、17.70元页面报价、苏宁自营标记、券提示和江西南昌收货地区;不支持净含量、组合件数、单碗价、成交价或当前库存。
- 华姐页面支持标题中的115g、$4.59/$5.99、有效期文字与店铺名称;币种和市场地区未核实。商品主图呈碗形,仅保留为图片观察,不当作规格参数或出口包装证明。
- 大小差异已独立复现:suning-card.html为4,681字节、4,341个字符。补充说明的字符分类需纠正:实际为169个三字节字符和两个两字节的¥符号,多出169×2+2×1=340字节。可解释当前数字差异,但没有首次工具输出,不能反推全部历史。
- 文件格式也应准确记录:名为.html的文件实际是含html/text的JSON数组,其中html为4,000字符的截断片段,不是完整网页。现有商品文字仍可与截图互相核对,来源入库时只用实际取得的text字段。
- SIZE-DISCREPANCY-NOTE.md不在交付包中,Codex未操作远端目录,无法判断是谁删除、为何删除或内容是否合并;记录为未确认的过程备注。它不影响当前四份原件与清单的核对,不据此断言历史完整。
- 结论:归档原件核对通过,适合保存为两条独立的“商品页面观察”来源及证据。当前在售、SKU规格和可比较单价仍未核实。固定输入已准备并在本地验证后交付;实际线上入库、产品创建和报告更新均未执行。
- '''
- retain(DOCS / '11-当前商品原件核对结果.txt', review_text.encode('utf-8'))
- ops = '''运维001:准备两条商品页面观察入库。只落地固定输入与留存目录,不代跑业务。
- 包:ksf-page-observations-20261102.zip。核FILES.sha256全部项。
- 将inputs/suning.input.json、inputs/huajie.input.json放入/opt/data/ksf-executor-20261102/page-observations-01/,root:root 0644、hermes可读;保持内容和文件名不变。沿用现有ksf-secure.mjs,不装新执行器、不改Skill/Function、不重启。
- 归档原件继续留在原位置;包只包含已核对的固定输入和核对说明,不替换原件。输入仅涉及KfSource+KfEvidence,无KfCategory、KfProduct、会话或报告写入。
- 确认本任务键KSF-商品页面观察-20261102-01的历史与在途;如已存在相同记录,保留不删,回读后按实际created/unchanged计数。发现已有该任务执行回执或在途则停止交付执行,不换键重跑。
- 以hermes(10000)、HOME=/opt/data建立全新留存目录/opt/data/ksf-workshop-private/page-observations/KSF-page-observations-20261102-01(父目录与目标0700)。目标如已存在,不清空也不另换目录,先核查已有状态。
- 回报输入哈希与权限、历史/在途核查和留存目录状态。随后交王刚001在一条新的真实消息任务里执行一次;运维不代跑。无需新建身份或复制员工/会话配置。
- '''
- wang = '''王刚001:运维准备完成后,使用你本条消息已核实的现有agentProfileKey,通过现有secure入口导入两份固定页面观察输入。输入已经由Codex逐文件及截图核对,不需要你设计或改写。
- 固定任务键KSF-商品页面观察-20261102-01。先确认无该任务执行回执/在途。以hermes(10000)、HOME=/opt/data执行;本步授权是资料入库,不是当前在售、SKU或价格研究已完成。
- 第一份(苏宁):
- node /opt/data/skills/ksf-workshop-runtime/scripts/ksf-secure.mjs workshop-dedup --input /opt/data/ksf-executor-20261102/page-observations-01/suning.input.json --request-id KSF-商品页面观察-20261102-01:suning:preflight:v1
- 只读查重成功后执行一次:
- node /opt/data/skills/ksf-workshop-runtime/scripts/ksf-secure.mjs workshop-import --agent-profile <当前已核实agentProfileKey> --input /opt/data/ksf-executor-20261102/page-observations-01/suning.input.json --request-id KSF-商品页面观察-20261102-01:suning:import:v1
- 先确认外层ok和entryResult.ok均为true,取得entryResult.result里的sourceId、items[0].objectId和operation。只创建KfEvidence条目;不建产品或分类。输入的collectedAt=null是有意保留:原件只留采集者回报的观察时间范围,范围在locator.observedAtReported,不伪造精确时间。
- 用返回的sourceId和evidenceId分别写只读输入{"className":"KfSource","objectId":"<实际sourceId>"}及{"className":"KfEvidence","objectId":"<实际evidenceId>"}到留存目录(0600、独占创建),调用:
- node /opt/data/skills/ksf-workshop-runtime/scripts/ksf-secure.mjs workshop-read --input <上述绝对路径> --request-id KSF-商品页面观察-20261102-01:suning:read-source:v1
- 另一条evidence回读使用同格式命令及requestId KSF-商品页面观察-20261102-01:suning:read-evidence:v1。
- 确认来源rawText/sourceKind=ecommerce/dataMode=live/平台/URL与输入相同;证据quote/locator/evidenceKind/verificationState与输入逐项一致、sourceRef指回本次来源、productRef为空、collectedAt为空。
- 苏宁导入与两项回读均通过后,才处理第二份华姐页面:上述各命令的输入文件换为huajie.input.json,requestId里的suning统一换为huajie,其余流程相同。两个来源分开保存,不合并商品。
- 所有输入、stdout/stderr、退出码、两层调用回执独占留存到/opt/data/ksf-workshop-private/page-observations/KSF-page-observations-20261102-01,0600。任何一步失败即停,包括查重或回读失败,不自动重试/换键/删记录;回报已取得编号。错误不代表零写入。
- 成功回报每页sourceId、evidenceId、jobId、sourceOperation、created/unchanged、import两层invocationId及精确回读结果。相同资料unchanged属正常,不为增加数量换键。没有新产品、SKU、会话、报告或新版S1是预期;G8IrkkDG7X保持原样,不重跑旧任务。不得说“已核实当前在售”“已验证单碗价”“采集了消费者评价”或“美元价”。
- '''
- retain(DOCS / '12-发给运维001-商品页面观察准备.txt', ops.encode('utf-8'))
- retain(DOCS / '13-发给王刚001-商品页面观察入库.txt', wang.encode('utf-8'))
- package = {**{'inputs/' + name: encode(value) for name, value in inputs.items()},
- 'archive-review.json': encode(review), '核对结果.txt': review_text.encode('utf-8'),
- '给运维001.txt': ops.encode('utf-8'), '给王刚001.txt': wang.encode('utf-8')}
- package['FILES.sha256'] = ''.join(sha(blob) + ' ' + name + '\n' for name, blob in package.items()).encode()
- target = DOCS / 'ksf-page-observations-20261102.zip'
- assert not target.exists(), 'Preserve existing package'
- with zipfile.ZipFile(target, 'x', compression=zipfile.ZIP_DEFLATED) as z:
- for name, blob in package.items():
- z.writestr(name, blob)
- with zipfile.ZipFile(target) as z:
- assert z.testzip() is None
- for line in z.read('FILES.sha256').decode().splitlines():
- expected, name = line.split(' ', 1)
- assert sha(z.read(name)) == expected
- receipt = {'file': str(target), 'sha256': sha(target.read_bytes()), 'bytes': target.stat().st_size,
- 'manifestChecked': '6/6', 'taskKey': TASK, 'sourcesPlanned': 2, 'evidencePlanned': 2,
- 'productsPlanned': 0, 'reportsPlanned': 0, 'businessWrites': 0, 'targetExecuted': False,
- 'inputHashes': {name: sha(encode(value)) for name, value in inputs.items()}}
- retain(ROOT / 'work/current-product-observations-package-receipt.json', encode(receipt))
- print(json.dumps(receipt, ensure_ascii=True, indent=2))
- if __name__ == '__main__':
- main()
|