build-data-dictionaries.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. """Generate complete Skill dictionaries from maintained business definitions and SQLite DDL."""
  2. import hashlib
  3. import json
  4. from pathlib import Path
  5. import re
  6. import sqlite3
  7. import sys
  8. ROOT=Path(__file__).resolve().parents[1]
  9. sys.path.insert(0,str(ROOT/'server/voc-library'))
  10. import schema_catalog
  11. import retrieval
  12. from product_insights import ANNOTATION_DDL
  13. PURPOSES=dict(line.split('|',1) for line in '''_User|平台用户账号,员工身份不由昵称创建
  14. _Role|平台角色与授权关系
  15. KfProbe|部署验证记录
  16. KfSource|原文、文件与数据集来源
  17. KfSession|工作坊会话与可信员工/Agent归属
  18. KfInsight|洞察与需求归纳
  19. KfArtifact|阶段成果与报告
  20. KfProject|课堂项目与阶段
  21. KfOpportunity|机会卡片
  22. KfCategory|业务品类字典
  23. KfCategoryRelation|品类之间的结构关系
  24. KfProduct|结构化业务产品
  25. KfEvidence|逐字证据、出处及研究范围
  26. KfEvidenceLink|证据与业务对象关系
  27. KfMarketMetric|市场指标及统计口径
  28. KfAssessment|验证模型与评估结果
  29. KfConcept|新品方案版本
  30. KfStageEvent|项目阶段推进事件
  31. KfImportJob|入库任务及汇总回执
  32. KfImportItem|逐条入库结果及依赖
  33. KfReviewIssue|人工复核问题及状态
  34. KfSupplier|供应商资料
  35. KfMaterial|原料资料
  36. KfProcess|工艺资料
  37. KfStandard|标准与约束依据
  38. KfSupplyQuote|供应报价及时间口径
  39. KfFormulaItem|方案配料及计算基准
  40. KfVocRun|采集执行与用量回执,不执行采集
  41. KfRawXhsNote|小红书原始内容
  42. KfRawXhsComment|小红书原始评论
  43. KfRawDouyinVideo|抖音原始视频文字与指标
  44. KfRawDouyinComment|抖音原始评论
  45. KfRawTaobaoItem|淘宝接口族原始商品
  46. KfRawTaobaoReview|淘宝接口族原始评价
  47. KfRawJdItem|京东原始商品
  48. KfRawJdReview|京东原始评价
  49. KfRawBilibiliPost|哔哩哔哩原始内容
  50. KfRaw1688Offer|1688原始货源
  51. KfSku|业务SKU规格
  52. KfRetailPriceSnapshot|业务零售价格快照
  53. KfProcessStep|工艺步骤与约束
  54. KfQuestionnaireQuestion|问卷问题与字段定义
  55. KfQuestionnaireAnswer|真实问卷答复,保留原话
  56. KsfServiceCredential|服务凭据的授权与到期配置
  57. KsfOperation|幂等操作和回执
  58. KsfUploadSession|文件上传会话
  59. KsfReportPreview|报告预览及内容身份
  60. KsfConfirmationEvent|员工真实确认事件
  61. KsfRuntimeConfig|运行配置、称呼及分组等受控注册表
  62. KsfFileObject|已验证文件及字节'''.splitlines())
  63. EXTRA_LABELS=dict(line.split('|',1) for line in '''objectId|服务生成记录编号
  64. createdAt|服务创建时间
  65. updatedAt|服务更新时间
  66. label|配置显示标签
  67. tokenHash|服务端凭据指纹,非Agent业务数据
  68. scopes|授权作用范围
  69. projectIds|授权项目编号集合
  70. enabled|是否启用
  71. expiresAt|到期时间
  72. ownerId|服务或文件归属编号
  73. requestId|稳定请求追踪编号
  74. operation|操作类型
  75. payloadHash|请求载荷指纹
  76. receipt|操作实际回执
  77. fileName|上传文件名
  78. contentType|文件媒体类型
  79. sizeBytes|文件字节数
  80. sha256|完整文件字节指纹
  81. state|上传状态
  82. fileReceipt|服务验证的文件回执
  83. identity|报告版本与内容绑定身份
  84. file|实际文件引用
  85. reportType|报告类型代码
  86. dataMode|实际取得模式
  87. artifactSubtype|附属成果子类型
  88. employeeId|真实确认员工编号
  89. previewId|已生成预览编号
  90. action|员工确认操作
  91. confirmedAt|实际确认时间
  92. key|受控配置项键
  93. value|受控配置对象
  94. name|文件或业务名称
  95. bytesBase64|文件字节的Base64编码'''.splitlines())
  96. SPECIAL={
  97. '_User':'平台用户管理;不接受研究资料通用导入', '_Role':'平台角色管理;不接受研究资料通用导入',
  98. 'KfProbe':'运维部署探测,不接受研究资料导入', 'KfSource':'workshop-import内来源登记;文件走受控上传验证',
  99. 'KfSession':'workshop-start', 'KfArtifact':'workshop-save;正式报告report-preview/report-submit及真人确认',
  100. 'KfProject':'project-create/project-progress', 'KfOpportunity':'workshop-save机会卡',
  101. 'KfConcept':'concept-save', 'KfFormulaItem':'concept-save配料', 'KfStageEvent':'project-progress等流程自动生成',
  102. 'KfVocRun':'既有ingest采集回执登记', 'KfImportJob':'workshop-import/ingest自动生成',
  103. 'KfImportItem':'workshop-import/ingest自动生成', 'KfReviewIssue':'审核或噪声反馈服务生成',
  104. 'KfQuestionnaireAnswer':'ksfApplyReviewFeedback(action=record_answer);当前wrapper无直连命令,须按已部署入口使用,不能冒称可直写',
  105. 'KsfServiceCredential':'管理员配置;Agent不得写凭据值', 'KsfOperation':'服务幂等处理自动生成',
  106. 'KsfUploadSession':'受控文件上传流程生成', 'KsfReportPreview':'report-preview服务生成',
  107. 'KsfConfirmationEvent':'真实员工确认页生成,不代造确认',
  108. 'KsfRuntimeConfig':'employee-name/分组管理等专用服务,或管理员运行配置;不通用导入',
  109. 'KsfFileObject':'受控文件上传服务生成',
  110. }
  111. def write(path,data):
  112. path.parent.mkdir(parents=True,exist_ok=True)
  113. path.write_text(json.dumps(data,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
  114. def main():
  115. source=ROOT/'server/references/runtime-data.json'; data=json.loads(source.read_text(encoding='utf-8'))
  116. core=(ROOT/'server/src/core.mjs').read_text(encoding='utf-8')
  117. protected=set(re.findall(r"'([^']+)'",re.search(r'const serverFields=new Set\(\[(.*?)\]\)',core).group(1)))
  118. managed={'schemaVersion','externalKey','reviewState','recordState'}
  119. tables={}
  120. for table,defs in data['tables'].items():
  121. assert table in PURPOSES, 'UNDOCUMENTED_TABLE:'+table
  122. fields={}
  123. for name,definition in defs.items():
  124. meaning=definition.get('label') or EXTRA_LABELS.get(name)
  125. assert meaning, 'UNDOCUMENTED_FIELD:'+table+'.'+name
  126. item={**definition,'meaning':meaning,'requiredByDefinition':bool(definition.get('required')),
  127. 'allowedInGenericImport':table in data['importClasses'] and name not in protected and definition['type'] in ('String','Number','Boolean','Array','Object','Date','Pointer','File'),
  128. 'writeOwner':'service' if name in protected or table.startswith('Ksf') or table.startswith('_') else 'relation_service' if definition['type']=='Relation' else 'managed_metadata' if name in managed else 'agent_source_mapping',
  129. 'nullMeaning':'未取得/不适用,不用0、空姓名或当前日期代替;必填项缺失须补来源',
  130. 'unit':'g' if name in ('netWeightG','totalNetWeightG') else 'bytes' if name in ('sizeBytes','fileSize') else definition.get('unit')}
  131. if definition['type'] in ('Object','Array'):item['valueStructure']='开放JSON结构;实体字段已完整列出,内部键按操作合同/真实原载荷,不臆造固定嵌套字段'
  132. if name=='dataMode':item['allowedValues']=data['reportRules']['dataModes']
  133. if name=='reviewState':item['importAllowedValues']=['pending','draft']
  134. if name=='recordState':item['importAllowedValues']=['active','archived']
  135. fields[name]=item
  136. tables[table]={'purpose':PURPOSES[table],'readRoute':'workshop-search/workshop-read(受当前授权与noiseContext约束)' if table.startswith('Kf') and not table.startswith('Ksf') else '只提供schema元信息,不通过研究Agent读取内部行值',
  137. 'writeRoute':SPECIAL.get(table,'workshop-import/ingest(以实际allowedClasses为准)'),
  138. 'genericImportDeclared':table in data['importClasses'],'fields':fields}
  139. dictionary={'schema':'ksf.business-data-dictionary.v1','schemaVersion':data['schemaVersion'],
  140. 'contractRevision':data['contractRevision'],'sourceSha256':hashlib.sha256(source.read_bytes()).hexdigest(),
  141. 'tableCount':len(tables),'fieldCount':sum(len(t['fields']) for t in tables.values()),'tables':tables,
  142. 'liveSchemaCommand':'workshop-schema','authority':'本地维护定义;生产实际表列须workshop-schema核对,未部署不能宣称已读取生产全结构',
  143. 'importAuthority':'workshop-contract实际allowedClasses/字段;本字典不授予写权限','productionVerified':False}
  144. platform=json.loads((ROOT/'server/references/platform-field-dictionary.json').read_text(encoding='utf-8'))
  145. physical=json.loads((ROOT/'server/references/production-schema-20261106.json').read_text(encoding='utf-8'))
  146. dictionary['platformFields']=platform['tables']
  147. dictionary['platformCommonFields']=platform['commonFields']
  148. for table in physical['tables']:
  149. for field in table['fields']:
  150. definition=tables.get(table['name'],{}).get('fields',{}).get(field['logicalName']) or platform['tables'].get(table['name'],{}).get(field['name']) or platform['commonFields'].get(field['name'])
  151. assert definition and definition.get('meaning'), 'UNMAPPED_PHYSICAL_FIELD:'+table['name']+'.'+field['name']
  152. field['meaning']=definition['meaning']
  153. field['writeOwner']=definition['writeOwner']
  154. field['allowedInGenericImport']=definition.get('allowedInGenericImport',False)
  155. dictionary['physicalSchemaSnapshot']=physical
  156. dictionary['logicalSchemaSnapshot']=json.loads((ROOT/'server/references/production-logical-schema-20261106.json').read_text(encoding='utf-8'))
  157. dictionary['relationTables']={
  158. '_Join:'+name+':'+table:{'ownerClass':table,'field':name,'targetClass':definition['targetClass'],
  159. 'meaning':table+'.'+name+'关系存储;关联'+definition['targetClass']+',由专用业务服务/Parse维护,不直接SQL写入'}
  160. for table,values in data['tables'].items() for name,definition in values.items() if definition['type']=='Relation'}
  161. dictionary['physicalFieldCount']=physical['fieldCount']
  162. dictionary['physicalSnapshotAuthority']='已独立读取的生产元信息快照,仅含列与约束;运行时仍应workshop-schema核差异,不代表可读取内部行值'
  163. write(ROOT/'server/references/business-data-dictionary.json',dictionary)
  164. write(ROOT/'skills/ksf-workshop-runtime/references/business-data-dictionary.json',dictionary)
  165. c=sqlite3.connect(':memory:');c.executescript((ROOT/'server/voc-library/schema.sql').read_text(encoding='utf-8'))
  166. retrieval.create_index(c);c.executescript(ANNOTATION_DDL)
  167. c.executescript('CREATE TABLE source_origins(source_record_id TEXT NOT NULL REFERENCES source_records(id),archive_sha256 TEXT NOT NULL,PRIMARY KEY(source_record_id,archive_sha256)); CREATE TABLE record_versions(entity TEXT NOT NULL,record_id TEXT NOT NULL,batch_sha256 TEXT NOT NULL,record_json TEXT NOT NULL,PRIMARY KEY(entity,record_id,batch_sha256)); ANALYZE;')
  168. research=schema_catalog.catalog(c);assert research['complete'], 'INCOMPLETE_RESEARCH_SEMANTICS'
  169. research.update(authority='维护DDL生成的完整结构样本;安装状态与实际计数请query_voc.py schema读取',installed=False)
  170. for t in research['tables']:t['installed']=False;t['recordCount']=None
  171. write(ROOT/'skills/ksf-voc-library/references/research-data-dictionary.json',research)
  172. print(json.dumps({'businessTables':len(tables),'businessFields':dictionary['fieldCount'],
  173. 'researchTables':research['tableCount'],'researchFields':sum(len(t['fields']) for t in research['tables'])}))
  174. if __name__=='__main__':main()