audit-enrichment-coverage.py 4.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. """Distinguish pending, rejected and published enrichment per original product ID."""
  2. from collections import Counter
  3. from datetime import datetime, timezone
  4. import importlib.util
  5. import json
  6. from pathlib import Path
  7. spec = importlib.util.spec_from_file_location('enrichment', Path(__file__).with_name('enrich-research-library.py'))
  8. e = importlib.util.module_from_spec(spec)
  9. spec.loader.exec_module(e)
  10. def audit():
  11. queue = e.read(e.WORK / 'product-queue.json')
  12. published = {}
  13. for directory in sorted((e.WORK / 'batches').iterdir()):
  14. file = directory / 'apply-result.json'
  15. if not file.exists(): continue
  16. result = e.read(file)['body']['result']
  17. if not (result.get('ok') and result.get('applied') and result.get('allRowsVerified')): continue
  18. receipt = e.read(directory / 'receipt.json')
  19. for row in receipt['products']:
  20. published[row['id']] = {**row, 'batch': directory.name, 'generation': result['generation'], 'allRowsVerified': True}
  21. states, rows = Counter(), []
  22. for product in queue:
  23. pid = product['productId']
  24. family, item = pid.split(':', 1)
  25. endpoints = []
  26. if product['missingVerifiedSpec'] or product['missingPrice']:
  27. endpoints.append(('detail', 'jd.get_item_detail_v1' if family == 'jd' else 'taobao.get_item_detail_v6'))
  28. if product['missingReviews']:
  29. endpoints.append(('reviews', 'jd.get_item_comments_v1' if family == 'jd' else 'taobao.get_item_comment_v3'))
  30. fields = {}
  31. for kind, endpoint in endpoints:
  32. prefix = e.WORK / 'collected' / (e.filename(pid) + '--' + e.filename(endpoint))
  33. meta_file = Path(str(prefix) + '.meta.json')
  34. if not meta_file.exists():
  35. fields[kind] = {'state': 'PENDING_COLLECTION'}
  36. continue
  37. meta = e.read(meta_file)
  38. if not meta.get('ok'):
  39. fields[kind] = {'state': 'REQUEST_FAILED', 'httpStatus': meta.get('httpStatus'), 'reason': meta.get('reason'), 'meta': meta_file.name}
  40. continue
  41. raw_file = prefix.parent / meta['responseFile']
  42. raw = raw_file.read_bytes()
  43. if e.sha(raw) != meta['sha256']: raise ValueError('ORIGINAL_RESPONSE_HASH_MISMATCH')
  44. field = {'state': 'VALID_RESPONSE', 'responseFile': meta['responseFile'], 'sha256': meta['sha256']}
  45. try:
  46. if kind == 'reviews':
  47. accepted, excluded = e.reviews(json.loads(raw), family, item)
  48. field.update(accepted=len(accepted), defaultReviews=sum(r['isDefault'] is True for r in accepted), excluded=dict(excluded))
  49. if not accepted:
  50. field['state'] = 'ALL_RETURNED_REVIEWS_REJECTED' if excluded else 'REQUESTED_PAGE_RETURNED_NO_REVIEWS'
  51. else:
  52. specs, price = e.detail(json.loads(raw), family, item)
  53. field.update(verifiedSpecs=len(specs), numericPrice=price is not None)
  54. if not specs and price is None: field['state'] = 'NO_USABLE_SOURCE_ATTRIBUTES_OR_PRICE'
  55. except ValueError as error:
  56. field['state'] = str(error)
  57. fields[kind] = field
  58. publication = published.get(pid)
  59. if publication: state = 'PUBLISHED_BATCH_WITH_VERIFIED_ROWS'
  60. elif any(f['state'] == 'PENDING_COLLECTION' for f in fields.values()): state = 'PENDING_COLLECTION'
  61. elif any(f['state'] == 'REQUEST_FAILED' for f in fields.values()): state = 'REQUEST_FAILED'
  62. elif any(f.get('accepted', 0) or f.get('verifiedSpecs', 0) or f.get('numericPrice') for f in fields.values()): state = 'COLLECTED_AWAITING_PUBLICATION'
  63. else: state = 'NO_USABLE_ROWS_IN_RESPONSES'
  64. states[state] += 1
  65. rows.append({'id': pid, 'state': state, 'publication': publication, 'fields': fields})
  66. summary = {'queuedProducts': len(queue), 'states': dict(states), 'productionWrites': 0,
  67. 'meaning': 'published is a batch state, not a claim that every product field or all platform reviews are complete'}
  68. result = {'schema': 'ksf.enrichment.coverage-audit.v1', 'checkedAt': datetime.now(timezone.utc).isoformat(), 'summary': summary, 'products': rows}
  69. e.save(e.WORK / 'coverage-audit.json', result)
  70. print(json.dumps(summary, ensure_ascii=False))
  71. return result
  72. if __name__ == '__main__': audit()