bootstrap-industry-details.py 4.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. """Reuse archived details whose interface version is proved; never guess a version."""
  2. import argparse
  3. import json
  4. from pathlib import Path
  5. import sys
  6. ROOT = Path(__file__).resolve().parents[1]
  7. sys.path.insert(0, str(ROOT / 'server/voc-library'))
  8. from monitoring_details import import_detail
  9. from monitoring_store import need, sha
  10. def run(store, generation, out):
  11. base = ROOT / 'work/voc-shop-chain-audit-20261106-r1/received-r2'
  12. manifest = {}
  13. for line in (base / 'FILES.sha256').read_text('utf-8-sig').splitlines():
  14. digest, name = line.split(' ', 1)
  15. manifest[name] = digest
  16. coverage_raw = (base / 'pages/coverage.json').read_bytes()
  17. need(sha(coverage_raw) == manifest['pages/coverage.json'], 'ARCHIVED_COVERAGE_SHA_MISMATCH')
  18. td2 = json.loads(coverage_raw)['items'][4]
  19. need(td2['api'] == 'taobao/get-item-detail/v6', 'ARCHIVED_ENDPOINT_UNPROVEN')
  20. need(td2['sha256'] == manifest['originals/td2.json'], 'ARCHIVED_ENDPOINT_RAW_MISMATCH')
  21. sources = [(base / 'originals' / name, manifest['originals/' + name], family, '', endpoint)
  22. for name, family, endpoint in [('d1.json', 'jd', 'jd.get_item_detail_v1'),
  23. ('td2.json', 'taobao', 'taobao.get_item_detail_v6')]]
  24. sources.append((ROOT / 'work/voc-product-restoration-20261106-r1/original-brand-detail.json',
  25. 'e65742376ef5bd66eb1d14f907adcf6a1a88779cd472f5e8d80dc744e095c470', 'jd', '/result', 'jd.get_item_detail_v1'))
  26. need(not out.exists(), 'DETAIL_BOOTSTRAP_RECEIPT_ALREADY_EXISTS')
  27. results = []
  28. for path, expected, family, envelope, endpoint in sources:
  29. raw = path.read_bytes()
  30. need(sha(raw) == expected, 'ARCHIVED_DETAIL_SHA_MISMATCH')
  31. doc = json.loads(raw)
  32. response = doc['result'] if envelope else doc
  33. body = response['data']
  34. root = envelope + '/data/data/'
  35. item = root + ('product' if family == 'jd' else 'item')
  36. product = body['requestParams']['query']['itemId']
  37. fields = ([{'kind': 'selling_points', 'pointer': item + '/sellPoint', 'format': 'text'},
  38. {'kind': 'description', 'pointer': item + '/description', 'format': 'html'}] if family == 'jd' else
  39. [{'kind': 'description', 'pointer': item + '/desc', 'format': 'html'},
  40. {'kind': 'attributes', 'pointer': item + '/attribute', 'format': 'array'},
  41. {'kind': 'detail_images', 'pointer': item + '/detailImages', 'format': 'array'}])
  42. source = {'schema': 'ksf.detail-evidence.v1', 'generation': generation, 'platformFamily': family,
  43. 'productId': family + ':' + product, 'endpointId': endpoint,
  44. 'requestId': body['requestId'], 'rawFile': path.name, 'rawSha256': expected,
  45. 'mapping': {'successChecks': [{'pointer': envelope + '/code', 'equals': 200},
  46. {'pointer': envelope + '/data/code', 'equals': 0}],
  47. 'requestProductPointer': envelope + '/data/requestParams/query/itemId',
  48. 'returnedProductPointer': item + ('/productId' if family == 'jd' else '/itemId'),
  49. 'requestIdPointer': envelope + '/data/requestId',
  50. 'titlePointer': item + ('/skuName' if family == 'jd' else '/title'),
  51. 'observedAtPointer': envelope + '/data/recordTime',
  52. 'shopIdPointer': item + '/shopId' if family == 'jd' else root + 'seller/shopId',
  53. 'fields': fields}}
  54. results.append(import_detail(store, source, path.parent, generation))
  55. receipt = {'ok': True, 'generation': generation, 'observations': len(results),
  56. 'newImports': sum(not r['alreadyRegistered'] for r in results), 'results': results,
  57. 'paidCalls': 0, 'researchWrites': 0, 'productionDeployed': False,
  58. 'skipped': [{'file': 'td1.json', 'reason': 'ENDPOINT_VERSION_NOT_PROVEN', 'rawRetained': True}],
  59. 'note': '京东2件卖点字段为空;淘宝td2为图片。td1接口版本未证实,不新导入。旧原件与旧记录保留,均无详情文字覆盖。'}
  60. out.parent.mkdir(parents=True, exist_ok=True)
  61. out.write_text(json.dumps(receipt, ensure_ascii=False, indent=2) + '\n', 'utf-8')
  62. return receipt
  63. if __name__ == '__main__':
  64. p = argparse.ArgumentParser(description=__doc__)
  65. p.add_argument('--store', type=Path, required=True)
  66. p.add_argument('--generation', required=True)
  67. p.add_argument('--out', type=Path, required=True)
  68. args = p.parse_args()
  69. print(json.dumps(run(args.store, args.generation, args.out), ensure_ascii=False))