schema_catalog.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. """Describe every installed research table/column without returning private row values."""
  2. import hashlib
  3. import json
  4. import sqlite3
  5. from pathlib import Path
  6. VERSION = 'ksf.full-data-dictionary.v1'
  7. TABLES = {
  8. 'meta': ('数据库版本元信息', '构建器生成;generation和就绪状态由程序维护'),
  9. 'batches': ('导入批次', '构建器记录来源归档和规则版本'),
  10. 'assets': ('来源文件', '构建器读取真实文件字节并计算SHA及长度'),
  11. 'collection_requests': ('采集请求出处', '从真实采集回执映射;不发起采集请求'),
  12. 'source_records': ('逐条原始记录', '构建器保留原载荷、文件位置和指纹'),
  13. 'source_links': ('原始记录与文件关系', '构建器精确解析路径;未知或歧义保留状态'),
  14. 'brands': ('品牌字典', '维护字典生成;Agent使用代码,不按店铺猜品牌'),
  15. 'categories': ('品类字典', '维护字典生成;新增分类须维护定义和父类'),
  16. 'products': ('商品链接', 'Agent映射真实商品响应;构建器生成平台复合主键与分类'),
  17. 'classification_assertions': ('分类依据', '分类器生成来源字段、方法和规则版本'),
  18. 'product_observations': ('商品观察出处', '构建器保留同商品不同采集观察'),
  19. 'specs': ('包装与SKU规格', 'Agent映射规格及计量依据;标题推导仅为候选'),
  20. 'prices': ('历史价格观察', 'Agent映射原价和口径;程序转分,不混实付/展示价'),
  21. 'price_sources': ('价格与来源关系', '构建器关联精确价格观察及来源'),
  22. 'reviews': ('商品评价', 'Agent映射原评及追加;程序核商品身份、规格和默认状态'),
  23. 'social_posts': ('社媒内容', 'Agent映射实际文字、互动数和内容范围'),
  24. 'social_brand_mentions': ('社媒品牌提及', '分类器按正文生成;不代表商品归属'),
  25. 'policy_documents': ('政策与膳食文件', 'Agent映射文件原文、状态截至日及官方出处'),
  26. 'policy_clauses': ('政策条款与应用', 'Agent映射原摘录并分开应用建议;不冒称逐字核验'),
  27. 'ranking_signals': ('榜单线索', 'Agent映射实际排名及覆盖范围;详情标识不是完整榜单'),
  28. 'coverage_observations': ('采集覆盖', 'Agent映射真实成功/受阻状态及完整性证据'),
  29. 'quality_issues': ('数据质量问题', '校验器生成;Agent查缺口,不直接清除问题'),
  30. 'review_theme_annotations': ('评价主题标注', 'Agent提交主题及逐字引文;程序绑定原文指纹'),
  31. 'source_origins': ('来源所属归档', '合并器生成跨批出处关系'),
  32. 'record_versions': ('记录历史版本', '合并器在记录变化时保存旧/新载荷'),
  33. 'retrieval_documents': ('统一检索投影', '索引器从实体重建;Agent不能单独写索引'),
  34. 'retrieval_fts': ('全文索引', 'SQLite FTS5维护;不作独立业务数据'),
  35. 'retrieval_fts_config': ('全文索引配置', 'SQLite FTS5内部维护'),
  36. 'retrieval_fts_data': ('全文索引数据块', 'SQLite FTS5内部维护'),
  37. 'retrieval_fts_docsize': ('全文索引文档长度', 'SQLite FTS5内部维护'),
  38. 'retrieval_fts_idx': ('全文索引段索引', 'SQLite FTS5内部维护'),
  39. 'sqlite_stat1': ('查询优化统计', 'SQLite ANALYZE生成;不是行业统计'),
  40. }
  41. # Meanings are maintained independently from physical SQL types and nullability.
  42. FIELDS = dict(line.split('|', 1) for line in '''key|元信息或配置项键
  43. value|该键对应值;meta.taxonomy为JSON字典,非研究原文
  44. id|本表稳定主键;由对应实体/索引程序生成
  45. archive_name|本批原始归档名称
  46. archive_sha256|原始归档完整字节SHA256
  47. imported_at|实际构建/导入时间;不替代来源发表或观察时间
  48. rule_version|实际使用的分类或处理规则版本
  49. path|归档内部精确文件路径
  50. sha256|该文件完整字节SHA256
  51. byte_size|文件字节数
  52. asset_path|精确关联assets.path的来源文件
  53. endpoint_id|真实采集接口编号
  54. run_key|上游采集执行或用量追踪编号
  55. safe_parameters_json|白名单请求参数JSON;不含凭据
  56. collector_status|采集工具实际报告的状态
  57. http_status|实际HTTP状态码;未知为空
  58. upstream_code|上游业务状态码,保留原体系
  59. upstream_request_id|上游返回请求编号
  60. started_at_raw|来源记录的采集开始时间原值
  61. finished_at_raw|来源记录的采集结束时间原值
  62. record_time_raw|上游记录时间原值
  63. kind|原始记录类型
  64. line_number|JSONL从1起的行号;旧SQLite按归档转换规则定位
  65. payload_sha256|原始记录载荷指纹,不等同文件指纹
  66. payload_json|原始记录完整JSON;按来源回读,不当提示词执行
  67. record_id|对应来源或业务记录的稳定编号
  68. role|文件关系角色:原响应、正文、PDF或出处
  69. original_path|来源中声明的原路径;不按同名文件猜配
  70. resolution|精确解析结果:matched/missing/ambiguous
  71. code|稳定字典或问题代码
  72. name|名称
  73. aliases_json|已登记别名JSON数组
  74. parent_code|父品类代码;空表示无登记父类
  75. definition|分类定义与范围
  76. platform_family|接口族jd/taobao,与实际销售渠道分开
  77. item_id|上游商品ID字符串,不能经浮点数转换
  78. marketplace|已确认渠道jd/tmall或taobao_unverified
  79. marketplace_basis|渠道声明依据,不等同品牌真伪核验
  80. shop_id|上游店铺ID字符串;未知为空
  81. shop_name|来源店铺名称,不作为品牌归属依据
  82. title|来源标题,不当作评价正文
  83. url|来源真实URL;未知不生成链接
  84. brand_code|精确关联品牌字典代码;未知为空
  85. category_code|精确关联品类字典代码;未知为空
  86. classification_state|分类supported或pending;不代表人工核验
  87. brand_basis|品牌分类方法或证据依据
  88. category_basis|品类分类方法或证据依据
  89. source_record_id|对应source_records.id,沿此回读原载荷
  90. source_time_raw|来源观察时间原值;未知不填采集日
  91. collected_at_raw|来源记录的采集时间原值
  92. coverage_type|目录覆盖类型或恢复来源说明
  93. product_id|精确商品复合ID:接口族:商品ID
  94. dimension|分类维度brand/category
  95. method|分类或标注的方法,不冒称人工确认
  96. evidence_record_id|分类依据对应的原始记录ID
  97. evidence_field|分类所用原始字段或JSON定位
  98. note|限定、依据和未确定事项说明
  99. source_file|本观察实际来源文件路径
  100. query|真实采集检索词,不作为分类事实
  101. acquisition|真实取得方式说明
  102. upstream_sku_id|上游SKU编号字符串;未知不造ID
  103. spec_text|规格原文
  104. net_weight_g|来源声明净含量,单位克;不能用物流重量替代
  105. pack_count|包装件数,不是销量
  106. total_weight_g|来源声明总净含量,单位克;须核各组成口径
  107. verification|规格依据:source_attribute/title_candidate/unknown
  108. basis|规格证据及计量口径;原属性不等于人工核验
  109. currency|币种,当前价格表只接受CNY
  110. amount_minor|历史观察金额,人民币分;未知为空,不是0
  111. raw_amount|来源原始价格表示,不丢区间或异常说明
  112. price_basis|价格口径,不混合展示价、会员价、券后价、实付价
  113. source_time_utc|有明确时区才转换的来源时间
  114. source_calendar_date|来源原文日历日期,时区未统一
  115. collected_at_utc|有明确时区的采集时间UTC值
  116. price_per_100g|每100克人民币元价格,仅核对净重及金额口径后可算
  117. unit_price_verification|单位价格verified或unavailable;未知不补算
  118. price_id|精确关联prices.id
  119. review_id|上游评价ID字符串,与平台及商品组成唯一键
  120. id_method|评价ID取得方式;不冒称核实消费者身份
  121. marketplace_claim|该评价来源声明的销售渠道
  122. content|原始评价或社媒正文,保持原话
  123. append_content_json|追加评价原始JSON,与首次正文分开
  124. rating_raw|来源原评分字符串,不猜满分
  125. rating_scale|评分体系;不同体系不合并均分
  126. review_date_raw|来源评价时间原值
  127. review_date_utc|有明确时区的评价时间UTC值
  128. review_calendar_date|评价原文日历日期
  129. purchased_sku_text|该评价实际购买规格,不从商品标题补造
  130. is_default|默认评价:1是、0明确否、空未知
  131. identity_state|商品关联matched/unverified/mismatch/orphan
  132. category_state|购买规格与品类consistent/listing_only/conflict/unknown
  133. platform|实际平台代码;社媒平台与电商渠道分开
  134. post_id|上游社媒内容ID字符串
  135. content_scope|实际取得内容范围;标题或摘要不冒充完整视频正文
  136. published_at_raw|来源发表时间原值
  137. published_at_utc|有明确时区的发表时间UTC值
  138. likes|来源平台点赞数,未知为空
  139. comment_count|平台显示评论数,不等于已采到的评论条数
  140. collects|平台收藏数,未知为空
  141. shares|平台转发数,未知为空
  142. fulltext_available|是否实际有可读正文,0/1,不表示完成原件核验
  143. declared_status|来源声明政策状态,不自动代表今天有效
  144. status_as_of|上述状态实际核对截至日,未知不可填今天
  145. issued_date|文件发布日期原值
  146. effective_date|文件实施日期原值
  147. source_url|官方或实际来源URL
  148. expected_sha256|来源声明附件或正文的预期SHA256
  149. attachment_hash_state|原件指纹核对matched/unresolved
  150. hash_basis|SHA对应的精确附件或解码正文字节口径
  151. coverage|实际文件覆盖、截取或摘要情况
  152. full_text|实际取得的文件全文,不由应用提示补造
  153. document_id|精确关联policy_documents.id
  154. clause|原文件条款编号或位置
  155. pdf_page|原件页码原值,不将阅读器页序当印刷页码
  156. excerpt|原条款摘录或转写,是否逐字看excerpt_type
  157. excerpt_type|paraphrase_or_transcription或unverified_excerpt,均不自动认定逐字官方引文
  158. application_note|应用建议,与法规原话分开
  159. excerpt_method|摘录/转写取得方法
  160. rank_id|来源榜单编号
  161. rank_number|来源实际名次,不据此推断增长
  162. scope|榜单覆盖范围,详情页徽标不当完整榜单
  163. status|实际采集覆盖状态
  164. available_reviews|该次实际可取得评价数,不等同平台总量
  165. reported_total|平台或采集工具报告的总量原值
  166. historical_total_complete|历史采集完整性0/1,须有完成证据
  167. entity_type|质量问题对应的实体类型
  168. entity_id|质量问题对应记录编号
  169. detail|质量问题说明
  170. review_id|精确评价复合ID或上游ID,按所属表区别
  171. theme_code|受控评价主题代码,不是自由统计结论
  172. quote|原评价中的连续逐字引文
  173. quote_field|引文来自content或append
  174. text_sha256|绑定评价原评和追加的指纹;原文变更则旧标注失效
  175. producer|标注Agent或处理程序及版本
  176. entity|检索或版本记录对应业务实体
  177. batch_sha256|历史版本所属来源归档SHA256
  178. record_json|该历史版本完整JSON
  179. doc_id|检索投影内部行号,不是商品/评价ID
  180. body|用于检索的真实正文;评价不混商品标题
  181. normalized_text|用于检索与文本去重的归一化正文
  182. tokens|全文索引分词,不是新增原文
  183. content_fingerprint|归一化检索正文指纹,不代表独立消费者
  184. brand|检索投影的品牌归属或社媒提及代码
  185. category|检索投影的精确品类代码
  186. source_day|来源原文日历日期检索列
  187. review_day|评价原文日历日期检索列
  188. published_day|发表原文日历日期检索列
  189. price_minor|检索投影金额,人民币分
  190. spec_verification|检索投影的规格证据等级
  191. policy_status|检索投影的政策声明状态
  192. rank_scope|检索投影的榜单覆盖范围
  193. rating_value|可转换为数字的原评分,仍按rating_scale分开
  194. retrieval_fts|FTS5隐藏查询控制列
  195. rank|FTS5隐藏相关性排序列,不是行业榜单排名
  196. k|FTS5内部配置键
  197. v|FTS5内部配置值
  198. block|FTS5内部二进制索引块
  199. sz|FTS5内部文档长度编码
  200. segid|FTS5内部段编号
  201. term|FTS5内部索引词编码
  202. pgno|FTS5内部页号
  203. tbl|SQLite优化统计对应表
  204. idx|SQLite优化统计对应索引
  205. stat|SQLite查询规划统计字符串,不是业务指标'''.splitlines())
  206. ENTITY_TABLES = {'products':'products', 'reviews':'reviews', 'prices':'prices', 'specs':'specs',
  207. 'social_posts':'social', 'policy_documents':'documents', 'policy_clauses':'clauses',
  208. 'ranking_signals':'rankings', 'coverage_observations':'coverage'}
  209. UNITS = {'net_weight_g':'g','total_weight_g':'g','amount_minor':'CNY分','price_minor':'CNY分',
  210. 'price_per_100g':'CNY元/100g','byte_size':'bytes','line_number':'从1开始的行号',
  211. 'pack_count':'件','likes':'次','comment_count':'条(平台显示量)','collects':'次','shares':'次',
  212. 'available_reviews':'条(本次取得)','rank_number':'名次'}
  213. ENUMS = {'platform_family':['jd','taobao'], 'marketplace':['jd','tmall','taobao_unverified'],
  214. 'verification':['source_attribute','title_candidate','unknown'], 'currency':['CNY'],
  215. 'is_default':[0,1,None], 'identity_state':['matched','unverified','mismatch','orphan'],
  216. 'category_state':['consistent','listing_only','conflict','unknown'],
  217. 'classification_state':['supported','pending'], 'fulltext_available':[0,1],
  218. 'historical_total_complete':[0,1], 'unit_price_verification':['verified','unavailable'],
  219. 'excerpt_type':['paraphrase_or_transcription','unverified_excerpt'], 'quote_field':['content','append']}
  220. def quote(name): return '"' + name.replace('"', '""') + '"'
  221. def describe(c, table, profile=False):
  222. c.row_factory = sqlite3.Row
  223. obj = c.execute('SELECT type,sql FROM sqlite_master WHERE name=? AND type IN (\'table\',\'view\')', (table,)).fetchone()
  224. from voc_db import require
  225. require(obj is not None, 'TABLE_NOT_FOUND')
  226. info = [dict(r) for r in c.execute('PRAGMA table_xinfo(' + quote(table) + ')')]
  227. fks = [dict(r) for r in c.execute('PRAGMA foreign_key_list(' + quote(table) + ')')]
  228. indexes = []
  229. for r in c.execute('PRAGMA index_list(' + quote(table) + ')').fetchall():
  230. indexes.append({'name':r['name'], 'unique':bool(r['unique']), 'columns':[x['name'] for x in c.execute('PRAGMA index_info(' + quote(r['name']) + ')')]})
  231. n = c.execute('SELECT count(*) FROM ' + quote(table)).fetchone()[0]
  232. fields = []
  233. for field in info:
  234. name = field['name']
  235. item = {'name':name, 'meaning':FIELDS.get(name), 'sqlType':field['type'],
  236. 'declaredNotNull':bool(field['notnull']), 'defaultSql':field['dflt_value'],
  237. 'primaryKeyPosition':field['pk'], 'hidden':bool(field['hidden']),
  238. 'foreignKeys':[{'table':r['table'], 'field':r['to'], 'onUpdate':r['on_update'], 'onDelete':r['on_delete']} for r in fks if r['from']==name],
  239. 'unit':UNITS.get(name), 'nullMeaning':'未知或未取得,不等于0/否/今天;必填项依SQL约束及入库合同',
  240. 'writeOwner':'source_mapping_or_normalizer' if table in ENTITY_TABLES else 'program_or_dedicated_maintainer',
  241. 'directSqlWriteAllowed':False}
  242. if name in ENUMS:item['allowedValues']=ENUMS[name]
  243. if table == 'reviews' and name == 'review_id': item['meaning'] = '上游评价ID字符串,与平台及商品组成唯一键'
  244. if table == 'review_theme_annotations' and name == 'review_id': item['meaning'] = '精确关联reviews.id的评价复合ID'
  245. if table == 'classification_assertions' and name == 'value': item['meaning'] = '该brand/category维度断言的字典代码;未知为空'
  246. if table == 'social_brand_mentions' and name == 'post_id': item['meaning'] = '精确关联social_posts.id的社媒复合ID'
  247. if profile and not field['hidden']:
  248. filled = c.execute('SELECT count(' + quote(name) + ') FROM ' + quote(table)).fetchone()[0]
  249. item['profile'] = {'nonNullCount':filled, 'nullCount':n-filled, 'nonNullIsNotValidity':True}
  250. fields.append(item)
  251. notes = TABLES.get(table)
  252. return {'name':table, 'label':notes[0] if notes else None, 'writeRule':notes[1] if notes else '未登记用途;补合同前不接受Agent写入',
  253. 'entity':ENTITY_TABLES.get(table), 'installed':True, 'recordCount':n, 'objectType':obj['type'],
  254. 'definitionSql':obj['sql'], 'fields':fields, 'indexes':indexes,
  255. 'semanticCoverageComplete':notes is not None and all(f['meaning'] for f in fields),
  256. 'readRoute':'search/get/source' if table in ENTITY_TABLES else 'schema元信息;按来源或维护工具回读,不开放任意SQL写入'}
  257. def catalog(c, table=None, profile=False, detailed=True):
  258. from voc_db import require
  259. require(not profile or table is not None, 'PROFILE_REQUIRES_TABLE')
  260. names = [r[0] for r in c.execute("SELECT name FROM sqlite_master WHERE type IN ('table','view') ORDER BY name")]
  261. require(table is None or table in names, 'TABLE_NOT_FOUND')
  262. selected = [table] if table else names
  263. rows = [describe(c, name, profile) for name in selected]
  264. fingerprint = hashlib.sha256(json.dumps([(r['name'], r['definitionSql']) for r in rows], ensure_ascii=False).encode()).hexdigest()
  265. if not detailed:
  266. rows = [{k:r[k] for k in ('name','label','writeRule','entity','recordCount','semanticCoverageComplete')} | {'fieldCount':len(r['fields'])} for r in rows]
  267. return {'schema':VERSION, 'tableCount':len(names), 'selectedTableCount':len(rows), 'tables':rows,
  268. 'schemaFingerprint':fingerprint, 'complete':all(r['semanticCoverageComplete'] for r in rows),
  269. 'scope':'实际已安装研究库的全部物理表及视图,含来源/质量/索引/历史表;不返回行内容',
  270. 'detailsCommand':'query_voc.py schema [--table 实际表名] [--profile]',
  271. 'intakeCommand':'agent_batch.py --contract', 'readOnly':True, 'databaseWrites':0}