@furongjun1999/dsh-memory 0.4.11 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +552 -465
- package/codebuddy/CODEBUDDY.md +11 -3
- package/codebuddy/README.md +92 -90
- package/codebuddy/mcp.json +27 -27
- package/docs/GBrain/345/217/257/345/200/237/351/211/264/347/202/271_/347/201/265/346/236/242/350/220/275/347/202/271/344/272/244/346/216/245_20260919.md +169 -169
- package/docs/Pi/345/217/257/345/255/246/344/271/240/344/274/230/347/202/271_/347/201/265/346/236/242/345/244/247/350/204/221/346/224/271/350/277/233/344/272/244/346/216/245_20260915.md +144 -144
- package/docs/README.md +143 -111
- package/docs/discipline/harnesses.yaml +244 -226
- package/docs/discipline/templates/full.md.tmpl +61 -61
- package/docs/discipline/templates/rules.mdc.tmpl +68 -0
- package/docs/discipline/templates/skill.md.tmpl +23 -23
- package/docs/eval/AGI/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v1.0.md +299 -299
- package/docs/eval/AGI/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v2.0.md +239 -239
- package/docs/eval/bench_lingshu_self/bench_self.py +140 -0
- package/docs/eval/bench_lingshu_self/self_bench_result.json +404 -0
- package/docs/eval/bench_lingshu_self//347/201/265/346/236/242/350/207/252/345/272/223/347/253/257/345/210/260/347/253/257/346/243/200/347/264/242/345/256/236/346/265/213_v1.0.md +38 -0
- package/docs/eval//344/270/215/345/217/257/351/235/240/346/200/247/350/220/275/345/234/260_P0_v1.0.md +185 -0
- package/docs/eval//345/256/236/351/252/214/346/226/271/346/241/210_/345/255/246/344/271/240/351/227/255/347/216/257AB/344/270/216/346/250/252/350/257/204_v1.0.md +174 -174
- package/docs/eval//346/225/205/351/232/234/346/263/250/345/205/245/345/256/236/346/265/213_v1.0.md +422 -0
- package/docs/eval//346/250/252/350/257/204_/345/205/255/345/256/266100/351/242/230/344/270/255/350/213/261/345/217/214/346/237/245_v1.0.md +223 -223
- package/docs/eval//347/253/257/345/210/260/347/253/257LoCoMoQA/345/220/214/345/217/243/345/276/204/345/257/271/347/205/247_v1.0.md +100 -0
- package/docs/eval//347/253/257/345/210/260/347/253/257/345/271/262/346/211/260/346/261/240/350/257/204/346/265/213_/347/241/256/345/256/232/346/200/247/350/243/201/345/206/263vsLLM_judge_v1.1.md +197 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v1.md +156 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v10.md +210 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v11.md +227 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v12.md +203 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v13.md +233 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v14.md +191 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v15.md +213 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v16.md +214 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v17.md +199 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v2.md +156 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v3.md +152 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v4.md +128 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v5.md +114 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v6.md +192 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v7.md +187 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v8.md +207 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v9.md +203 -0
- package/docs/{mdcg → hive}//344/273/244/347/211/214/344/270/216/350/247/222/350/211/262/346/235/203/350/201/214/345/210/206/347/246/273_v0.1.md +165 -160
- package/docs/hive//344/273/244/347/211/214/350/257/255/344/271/211/344/277/256/346/255/243_/344/273/262/350/243/201/344/275/215_v0.1.md +76 -0
- package/docs/{mdcg → hive}//345/255/220/344/273/243/347/220/206/351/205/215/347/275/256/346/240/207/345/207/206_v0.5.md +236 -236
- package/docs/hive//345/256/211/345/205/250/345/256/241/350/256/241/345/256/236/351/224/232_v0.1.md +202 -0
- package/docs/hive//346/243/200/347/264/242/346/224/266/346/225/233/345/256/236/346/265/213/344/270/216S1b/350/256/276/350/256/241_v0.1.md +43 -43
- package/docs/hive//346/243/200/347/264/242/347/256/227/346/263/225/345/217/243/345/276/204/345/257/271/347/205/247_v0.1.md +85 -0
- package/docs/hive//346/243/200/347/264/242/350/267/257/345/276/204/344/270/216/350/256/244/347/237/245/347/273/223/346/236/204/345/245/221/347/272/246_v0.1.md +102 -102
- package/docs/hive//350/234/202/345/267/242M6_ingest/345/256/236/346/226/275/350/256/241/345/210/222_v0.1.md +47 -0
- package/docs/hive//350/234/202/345/267/242/345/217/214/345/256/236/344/276/213/344/272/222/351/252/214_/350/256/276/350/256/241/345/256/232/347/250/277.md +519 -503
- package/docs/hive//350/234/202/345/267/242/345/267/245/344/275/234/350/256/260/345/277/206_/351/241/271/347/233/256/350/256/241/345/210/222.md +85 -85
- package/docs/hive//350/234/202/345/267/242/350/256/276/350/256/241_/347/220/206/350/256/272/345/257/271/351/275/220_v0.1.md +191 -0
- package/docs/hive//350/234/202/345/267/242/350/277/255/344/273/243_/345/256/217/350/247/202/344/270/216/347/276/244/344/275/223/350/260/203/345/272/246_v0.1.md +90 -0
- package/docs/images/lingshu-moonlight-covenant-poster-preview.jpg +0 -0
- package/docs/images/lingshu-moonlight-covenant-poster.png +0 -0
- package/docs/mdcg/D_meta_/345/267/245/347/250/213/345/214/226/346/226/271/346/241/210_v0.2.md +216 -216
- package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +646 -646
- package/docs/mdcg/lingshu_tutorial.html +14449 -14449
- package/docs/mdcg/release_v0.4.11.md +49 -0
- package/docs/mdcg/release_v0.4.5.md +55 -55
- package/docs/mdcg/tool_table_v0.3.0.md +117 -117
- package/docs/mdcg//345/205/250/345/272/223/344/273/243/347/240/201/350/257/204/345/256/241/344/270/216/346/235/241/344/273/266/345/214/226/346/263/250/351/207/212_/350/256/241/345/210/222_v0.1.md +600 -600
- package/docs/mdcg//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +40 -40
- package/docs/mdcg//345/215/225/345/205/203/350/207/252/346/210/221/351/224/232/347/202/271_/347/263/273/347/273/237/346/217/220/347/244/272/350/257/215/346/240/207/345/207/206_v0.3.md +275 -275
- package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +54 -0
- package/docs/mdcg//346/272/220/347/240/201/347/272/247/346/236/266/346/236/204/345/256/241/350/256/241_GPT/346/211/271/350/257/204/345/257/271/347/205/247_v1.0.md +130 -130
- package/docs/mdcg//347/201/265/346/236/24282/345/267/245/345/205/267_/345/212/237/350/203/275/346/225/264/347/220/206/344/270/216/350/277/201/347/247/273/346/230/240/345/260/204_v0.1.md +278 -278
- package/docs/mdcg//347/201/265/346/236/242/350/256/260/345/277/206/345/212/250/350/257/215/345/215/217/350/256/256_v1.0-draft.md +172 -172
- package/docs/mdcg//347/274/272/345/217/243/345/215/225_P0/346/224/266/345/217/243_v0.1.md +270 -270
- package/docs/mdcg//350/256/244/347/237/245/345/233/276_G4-G8/347/274/272/345/217/243/350/243/201/345/256/232/345/215/225_v0.1.md +491 -491
- package/docs/mdcg//350/256/244/347/237/245/345/233/276_/346/235/241/344/273/266/347/251/272/351/227/264/345/220/210/346/210/220/344/270/216/347/224/237/346/225/210/346/235/241/344/273/266/345/217/243/345/276/204_v0.1.md +340 -340
- package/docs/mdcg//350/256/244/347/237/245/345/233/276_/347/264/242/345/274/225/344/270/216/345/267/245/347/250/213/350/247/204/350/214/203/345/214/226_/350/256/241/345/210/222_v0.1.md +588 -588
- package/docs/plans/GridWorld/346/234/200/345/260/217/351/227/255/347/216/257/350/247/204/346/240/274_v0.1.md +176 -176
- package/docs/plans//345/221/275/345/220/215/346/262/273/347/220/206_/351/241/271/347/233/256/350/256/241/345/210/222.md +81 -81
- package/docs/swarm//350/234/202/347/276/244/344/272/222/350/201/224_v0.1.md +704 -704
- package/docs/swarm//350/234/202/347/276/244/345/220/214/351/224/231/346/243/200/346/265/213/345/256/236/351/252/214/345/215/217/350/256/256_v0.1.md +242 -242
- package/docs/theory//344/270/215/345/217/257/351/235/240/345/256/232/347/220/206/344/270/216/345/244/261/346/225/210/344/274/230/345/205/210/346/241/206/346/236/266_v0.3.md +365 -0
- package/docs/theory//345/215/225/347/272/277/347/250/213/344/270/216/346/263/250/346/204/217/345/212/233/351/233/206/344/270/255_/346/227/240/344/272/211/350/256/256/347/220/206/350/256/272/346/226/207/346/241/243_v1.0.md +129 -129
- package/docs/theory//345/271/266/345/217/221/345/277/205/347/204/266/346/200/247/347/220/206/350/256/272_v0.2.md +134 -134
- package/docs/theory//346/231/272/350/203/275/350/256/2723.4.md +5260 -5260
- package/docs/theory//346/246/202/345/277/265/345/210/206/345/261/202/345/257/271/351/275/220/350/241/250_v0.1.md +145 -145
- package/docs/theory//347/220/206/350/256/272_/346/234/272/345/210/266_/344/273/243/347/240/201_/345/256/236/351/252/214_/347/274/272/345/217/243/347/237/251/351/230/265_v0.1.md +144 -144
- package/docs/theory//347/220/206/350/256/272/344/273/223/346/213/206/345/210/206/344/270/216/347/231/275/347/256/261/347/237/245/350/257/206/345/272/223/345/206/205/350/277/201_v0.1.md +198 -198
- package/docs/theory//350/256/244/347/237/245/344/273/243/347/220/206/344/270/216/346/224/266/346/225/233/347/273/223/346/236/204_/346/235/241/344/273/266/350/256/272/351/207/215/346/236/204_v0.1.md +221 -221
- package/docs/world_model//344/270/226/347/225/214/346/250/241/345/236/213_/347/245/236/347/273/217/347/275/221/347/273/234/345/272/225/345/261/202/346/236/266/346/236/204_v1.0.md +237 -237
- package/docs//344/270/215/345/217/257/351/235/240/346/200/247/347/220/206/350/256/272_v0.1.md +343 -0
- package/docs//345/217/221/345/270/203/344/273/266/345/233/236/346/272/257/350/257/264/346/230/216_v0.1.md +82 -82
- package/docs//345/267/245/344/275/234/347/272/252/345/276/213_/350/256/244/347/237/245/345/233/276/346/235/241/347/233/256_v1.1.json +28 -2
- package/dsh/README.md +82 -82
- package/dsh/cordis.yml.example +139 -139
- package/dsh/update-lingshu.bat +11 -11
- package/lib/bridge.d.ts +9 -0
- package/lib/bridge.js +35 -0
- package/lib/hooks.js +36 -2
- package/lib/index.js +7 -1
- package/lib/lib/roleplay_web.js +116 -29
- package/lib/lib/token_store.d.ts +7 -1
- package/lib/lib/token_store.js +12 -3
- package/md_cg/__init__.py +7 -7
- package/md_cg/audit.py +379 -368
- package/md_cg/autonomy.py +287 -287
- package/md_cg/backfill.py +1328 -1327
- package/md_cg/backfill_bigdomain.py +34 -34
- package/md_cg/backfill_bucket_zh.py +35 -0
- package/md_cg/bench6_arms.py +410 -410
- package/md_cg/bench6_common.py +230 -230
- package/md_cg/bench6_competitors.py +212 -212
- package/md_cg/bench_axis_domain.py +257 -257
- package/md_cg/bench_blind_comp.py +308 -308
- package/md_cg/bench_e2e_judge.py +532 -0
- package/md_cg/bench_e2e_locomo_qa.py +368 -0
- package/md_cg/bench_e2e_qa.py +256 -0
- package/md_cg/bench_en_atoms_public.py +230 -230
- package/md_cg/bench_governance.py +348 -348
- package/md_cg/bench_lme_zh.py +410 -410
- package/md_cg/bench_locomo.py +121 -121
- package/md_cg/bench_locomo_zh.py +450 -450
- package/md_cg/bench_locomo_zh_public.py +147 -147
- package/md_cg/bench_longmem.py +112 -112
- package/md_cg/bench_membench.py +632 -632
- package/md_cg/bench_p0.py +149 -149
- package/md_cg/bench_progressive.py +287 -287
- package/md_cg/bench_role_views.py +238 -238
- package/md_cg/bench_task_ab.py +243 -243
- package/md_cg/bench_task_ab_llm.py +408 -408
- package/md_cg/bench_unified_en.py +204 -204
- package/md_cg/bench_zh_mad.py +601 -601
- package/md_cg/blindspot_tickets.py +123 -123
- package/md_cg/branches.py +301 -285
- package/md_cg/build_postings.py +73 -73
- package/md_cg/ccgc.py +1006 -948
- package/md_cg/census.py +132 -132
- package/md_cg/chain.py +315 -300
- package/md_cg/codeindex.py +531 -531
- package/md_cg/coldverify.py +292 -292
- package/md_cg/comment_gate.py +337 -337
- package/md_cg/cond_compose.py +190 -190
- package/md_cg/cond_facts.py +154 -154
- package/md_cg/cond_template.json +106 -106
- package/md_cg/condition_anchor.py +142 -142
- package/md_cg/conformance.py +726 -726
- package/md_cg/consistency.py +717 -717
- package/md_cg/consolidate.py +1537 -1439
- package/md_cg/corpus.py +110 -110
- package/md_cg/crosscheck.py +1098 -1097
- package/md_cg/crypto.py +3 -1
- package/md_cg/d_meta.py +310 -310
- package/md_cg/datapath.py +78 -18
- package/md_cg/docindex.py +473 -473
- package/md_cg/eval_common.py +575 -575
- package/md_cg/evidence.py +4 -2
- package/md_cg/evolution.py +477 -477
- package/md_cg/export.py +222 -220
- package/md_cg/forgetting.py +581 -581
- package/md_cg/fsutil.py +377 -329
- package/md_cg/hotcache.py +48 -7
- package/md_cg/hyperedge.py +251 -251
- package/md_cg/identity.py +390 -390
- package/md_cg/insight.py +500 -500
- package/md_cg/interop.py +338 -0
- package/md_cg/judgment_manifest.py +177 -0
- package/md_cg/lexicon/build_cedict_en_zh.py +329 -329
- package/md_cg/lexicon/build_standard_en.py +171 -171
- package/md_cg/lexicon/expand_en_zh.py +211 -211
- package/md_cg/lifecycle.py +272 -272
- package/md_cg/linkref.py +280 -280
- package/md_cg/links.py +140 -107
- package/md_cg/mcp_server.py +403 -55
- package/md_cg/md_whitebox.py +345 -345
- package/md_cg/mdcg.py +783 -233
- package/md_cg/mdcos.py +500 -70
- package/md_cg/metacognition.py +591 -591
- package/md_cg/migrate.py +119 -119
- package/md_cg/migrate_aeis.py +221 -221
- package/md_cg/migrate_roleplay.py +293 -293
- package/md_cg/migrate_wisdom_graph.py +360 -360
- package/md_cg/mreview/__init__.py +25 -25
- package/md_cg/mreview/__main__.py +110 -110
- package/md_cg/mreview/bundle.py +178 -178
- package/md_cg/mreview/candidates.py +262 -262
- package/md_cg/mreview/govern.py +694 -693
- package/md_cg/mreview/locate.py +939 -939
- package/md_cg/mreview/pipeline.py +728 -728
- package/md_cg/mreview/rules/duplication.json +21 -21
- package/md_cg/mreview/rules/field_coverage.json +54 -54
- package/md_cg/mreview/rules/source_license.json +21 -21
- package/md_cg/mreview/rules/template_flow.json +21 -21
- package/md_cg/mreview/ruleset.py +252 -252
- package/md_cg/nodefile.py +575 -575
- package/md_cg/pooling.py +484 -472
- package/md_cg/postings.py +300 -298
- package/md_cg/predict.py +1100 -1100
- package/md_cg/progressive.py +123 -123
- package/md_cg/protect.py +272 -272
- package/md_cg/protocol/md_cg_gate.proto +33 -33
- package/md_cg/protocol.py +372 -372
- package/md_cg/provenance.py +582 -582
- package/md_cg/reach.py +453 -453
- package/md_cg/readcache.py +143 -0
- package/md_cg/reconcile.py +228 -0
- package/md_cg/refindex.py +833 -833
- package/md_cg/refine.py +604 -604
- package/md_cg/review_cli.py +170 -0
- package/md_cg/roleviews.py +89 -89
- package/md_cg/routing.py +393 -365
- package/md_cg/run_tests.py +211 -0
- package/md_cg/scrub.py +13 -3
- package/md_cg/security.py +128 -18
- package/md_cg/self_state.py +1029 -1029
- package/md_cg/selfreport.py +152 -151
- package/md_cg/semantic/__init__.py +10 -10
- package/md_cg/semantic/canonical.py +122 -122
- package/md_cg/semantic/en_normalizer.py +364 -364
- package/md_cg/semantic/en_zh_map.json +28694 -0
- package/md_cg/semantic/export_en_zh_map.py +64 -0
- package/md_cg/semantic/unify.py +45 -0
- package/md_cg/semantic/zh_en_atoms.py +139 -139
- package/md_cg/signer.py +7 -4
- package/md_cg/sources.py +816 -582
- package/md_cg/statushdr.py +179 -179
- package/md_cg/stg.py +54 -37
- package/md_cg/subgraph.py +729 -729
- package/md_cg/sustain.py +35 -5
- package/md_cg/tasks.py +470 -470
- package/md_cg/test_access_hints.py +147 -0
- package/md_cg/test_action_derive.py +203 -203
- package/md_cg/test_audit_rotate.py +270 -270
- package/md_cg/test_autonomy.py +143 -143
- package/md_cg/test_bench_governance.py +102 -102
- package/md_cg/test_blindspot_tickets.py +166 -166
- package/md_cg/test_branch_discard_tombstone.py +136 -0
- package/md_cg/test_branches.py +13 -3
- package/md_cg/test_ccg_perturb.py +184 -184
- package/md_cg/test_ccgc.py +433 -433
- package/md_cg/test_census_prune.py +81 -81
- package/md_cg/test_chain_read_isolate.py +168 -0
- package/md_cg/test_cond_compose_anchors.py +76 -76
- package/md_cg/test_cond_match.py +165 -165
- package/md_cg/test_condition_anchor.py +81 -81
- package/md_cg/test_d_meta.py +412 -412
- package/md_cg/test_datapath_device_name.py +203 -0
- package/md_cg/test_datapath_root.py +199 -199
- package/md_cg/test_emit_negtail_cache.py +156 -0
- package/md_cg/test_en_pipeline.py +22 -2
- package/md_cg/test_gain_gate.py +212 -212
- package/md_cg/test_govern_directread.py +421 -0
- package/md_cg/test_health_scale.py +173 -173
- package/md_cg/test_hive_ingest.py +285 -0
- package/md_cg/test_hot_cold.py +215 -215
- package/md_cg/test_hyperedge.py +245 -245
- package/md_cg/test_i26_empty_first_write.py +116 -0
- package/md_cg/test_i27_e041_identity.py +128 -0
- package/md_cg/test_i28_hotcache_prodpath.py +122 -0
- package/md_cg/test_i32_hotcache_env_key.py +218 -0
- package/md_cg/test_identity_attribution.py +96 -15
- package/md_cg/test_index_durability.py +17 -3
- package/md_cg/test_interop.py +95 -0
- package/md_cg/test_interop_judgment.py +228 -0
- package/md_cg/test_issue39_utf8_stdio.py +273 -0
- package/md_cg/test_lifecycle.py +309 -309
- package/md_cg/test_linkref.py +306 -306
- package/md_cg/test_links_concurrent_write.py +188 -0
- package/md_cg/test_lock.py +43 -43
- package/md_cg/test_md_access_parity.py +255 -255
- package/md_cg/test_md_writepath.py +345 -345
- package/md_cg/test_mdstore_search_parity.py +160 -0
- package/md_cg/test_merge_upsert.py +168 -0
- package/md_cg/test_mr_m2.py +587 -587
- package/md_cg/test_mr_m3.py +710 -710
- package/md_cg/test_mr_m4.py +485 -485
- package/md_cg/test_n123_derive_expiry_chain.py +205 -0
- package/md_cg/test_n130_verify_falsified_protect.py +185 -0
- package/md_cg/test_n131_merge_gate.py +205 -0
- package/md_cg/test_p0.py +250 -250
- package/md_cg/test_p1.py +316 -316
- package/md_cg/test_p10_identity.py +173 -173
- package/md_cg/test_p11_consistency.py +233 -233
- package/md_cg/test_p12_metacognition.py +212 -212
- package/md_cg/test_p13_encryption.py +241 -241
- package/md_cg/test_p14_sustain.py +249 -249
- package/md_cg/test_p15_scrub.py +280 -280
- package/md_cg/test_p16_self_state.py +301 -301
- package/md_cg/test_p17_predict.py +354 -354
- package/md_cg/test_p18_whitebox.py +171 -171
- package/md_cg/test_p19_migrate_roleplay.py +149 -149
- package/md_cg/test_p1x_ref_root.py +160 -0
- package/md_cg/test_p20_evolution.py +315 -315
- package/md_cg/test_p21_tokens.py +293 -270
- package/md_cg/test_p22_theory.py +175 -175
- package/md_cg/test_p23_links.py +311 -311
- package/md_cg/test_p24_evidence.py +227 -227
- package/md_cg/test_p25_weights.py +156 -156
- package/md_cg/test_p26_refindex.py +416 -416
- package/md_cg/test_p27_docindex.py +16 -7
- package/md_cg/test_p28_refcheck.py +305 -305
- package/md_cg/test_p29_session_ingest_export.py +354 -333
- package/md_cg/test_p2_mcp.py +3 -0
- package/md_cg/test_p3.py +11 -2
- package/md_cg/test_p30_maintain.py +330 -330
- package/md_cg/test_p31_insight.py +534 -534
- package/md_cg/test_p32_backfill.py +7 -1
- package/md_cg/test_p33_ccg_wiring.py +293 -293
- package/md_cg/test_p34_crosscheck.py +331 -331
- package/md_cg/test_p35_conditioned_claim.py +252 -252
- package/md_cg/test_p36_kp_align.py +230 -230
- package/md_cg/test_p37_condition_space.py +248 -248
- package/md_cg/test_p38_concurrent_flush.py +102 -0
- package/md_cg/test_p38_contextualize.py +273 -273
- package/md_cg/test_p39_verify_flow.py +153 -0
- package/md_cg/test_p39_vision_evidence.py +369 -369
- package/md_cg/test_p40_refine_worklist.py +241 -241
- package/md_cg/test_p41_evolve_patrol.py +224 -224
- package/md_cg/test_p42_provenance.py +269 -269
- package/md_cg/test_p43_pooling.py +412 -398
- package/md_cg/test_p44_md_whitebox.py +231 -231
- package/md_cg/test_p45_session_identity.py +219 -219
- package/md_cg/test_p46_unit_scope.py +272 -272
- package/md_cg/test_p47_session_view.py +316 -0
- package/md_cg/test_p4_fuzzy.py +223 -223
- package/md_cg/test_p5_semantic.py +226 -226
- package/md_cg/test_p6_consolidate.py +440 -387
- package/md_cg/test_p7_goals_recent.py +202 -202
- package/md_cg/test_p8_subgraph_chain.py +200 -200
- package/md_cg/test_p9_forget_protect.py +231 -231
- package/md_cg/test_predict_beta.py +135 -135
- package/md_cg/test_preflight_failclosed.py +100 -100
- package/md_cg/test_progressive.py +146 -146
- package/md_cg/test_propose_tail_index.py +157 -0
- package/md_cg/test_protocol.py +243 -243
- package/md_cg/test_reach.py +378 -378
- package/md_cg/test_reach_keys.py +201 -201
- package/md_cg/test_read_clip.py +141 -141
- package/md_cg/test_read_scope_b27.py +277 -0
- package/md_cg/test_readcache_default_on.py +168 -0
- package/md_cg/test_readcache_precise_inval.py +270 -0
- package/md_cg/test_readcache_prodpath.py +203 -0
- package/md_cg/test_reconcile_v0.py +294 -0
- package/md_cg/test_retr_gates_prodpath.py +140 -0
- package/md_cg/test_retr_s1.py +344 -340
- package/md_cg/test_retr_s1b.py +276 -209
- package/md_cg/test_retr_s3.py +194 -194
- package/md_cg/test_retr_s4.py +163 -163
- package/md_cg/test_retr_s5.py +200 -200
- package/md_cg/test_retr_s6.py +157 -157
- package/md_cg/test_retr_s7.py +392 -384
- package/md_cg/test_retr_s8_time.py +369 -316
- package/md_cg/test_retr_s9_edges.py +286 -286
- package/md_cg/test_retr_s9_entity_ctx.py +9 -3
- package/md_cg/test_retr_score_once.py +208 -0
- package/md_cg/test_review_conformance.py +367 -367
- package/md_cg/test_review_onepass.py +170 -0
- package/md_cg/test_role_views.py +354 -354
- package/md_cg/test_rrf_graph_seed_cache.py +154 -0
- package/md_cg/test_security_audit.py +155 -0
- package/md_cg/test_security_audit_b26.py +161 -0
- package/md_cg/test_security_audit_v21.py +250 -0
- package/md_cg/test_sem_noise.py +242 -242
- package/md_cg/test_semantic_canonical.py +16 -2
- package/md_cg/test_session_isolation.py +168 -0
- package/md_cg/test_snapshot_autoclose.py +187 -0
- package/md_cg/test_subproc_encoding.py +192 -192
- package/md_cg/test_sustain_mutual.py +153 -153
- package/md_cg/test_tail_watermark_race.py +208 -0
- package/md_cg/test_tasks.py +409 -409
- package/md_cg/test_tenant_env_override_warn.py +139 -0
- package/md_cg/test_tenant_registry_corrupt_warn.py +151 -0
- package/md_cg/test_tool_face.py +189 -189
- package/md_cg/test_transfer.py +180 -180
- package/md_cg/test_trust.py +361 -361
- package/md_cg/test_twophase.py +286 -286
- package/md_cg/test_v14_fixes.py +38 -20
- package/md_cg/test_validity_filter.py +280 -280
- package/md_cg/test_verify_answer.py +138 -138
- package/md_cg/test_verify_dirty_reconcile.py +157 -0
- package/md_cg/test_wisdom_md_store.py +292 -292
- package/md_cg/test_writelimit.py +197 -197
- package/md_cg/test_writepipe.py +214 -214
- package/md_cg/theory.py +6 -3
- package/md_cg/tokens.py +85 -14
- package/md_cg/tool_face.py +260 -260
- package/md_cg/trust.py +986 -950
- package/md_cg/twophase.py +231 -231
- package/md_cg/units.py +668 -667
- package/md_cg/vision_evidence.py +667 -666
- package/md_cg/weights.py +624 -624
- package/md_cg/whitebox.py +527 -527
- package/md_cg/whitebox_kb/__init__.py +37 -37
- package/md_cg/whitebox_kb/aeis_core/__init__.py +42 -42
- package/md_cg/whitebox_kb/aeis_core/semantic.py +280 -280
- package/md_cg/whitebox_kb/aeis_core/textutil.py +13 -13
- package/md_cg/whitebox_kb/engine.py +310 -310
- package/md_cg/whitebox_kb/seed_knowledge//346/231/272/350/203/275/350/256/2723.4.md +5260 -5260
- package/md_cg/whitebox_kb/wisdom/browser_units.py +2631 -2631
- package/md_cg/whitebox_kb/wisdom/causal_discover.py +432 -432
- package/md_cg/whitebox_kb/wisdom/chat_engine.py +1506 -1506
- package/md_cg/whitebox_kb/wisdom/compiler_code_units.py +3033 -3033
- package/md_cg/whitebox_kb/wisdom/condition_algebra.py +112 -112
- package/md_cg/whitebox_kb/wisdom/condition_frame.py +315 -315
- package/md_cg/whitebox_kb/wisdom/condition_kb.py +108 -108
- package/md_cg/whitebox_kb/wisdom/conflict_map.json +4445 -4445
- package/md_cg/whitebox_kb/wisdom/core/lexer.py +512 -512
- package/md_cg/whitebox_kb/wisdom/core/name_checker.py +1023 -1023
- package/md_cg/whitebox_kb/wisdom/cspmn.py +258 -258
- package/md_cg/whitebox_kb/wisdom/csre.py +264 -264
- package/md_cg/whitebox_kb/wisdom/danmaku_audit.py +252 -252
- package/md_cg/whitebox_kb/wisdom/distilled_condition_units.json +6417 -6417
- package/md_cg/whitebox_kb/wisdom/docs/WB-EVAL-20260902b.json +1950 -1950
- package/md_cg/whitebox_kb/wisdom/docs/WB-EVAL-20260902c.json +1296 -1296
- package/md_cg/whitebox_kb/wisdom/docs/whitebox_capability_graph_demo.json +59 -59
- package/md_cg/whitebox_kb/wisdom/graph_db_units.py +3089 -3089
- package/md_cg/whitebox_kb/wisdom/knowledge_points.py +318 -318
- package/md_cg/whitebox_kb/wisdom/md_access.py +470 -470
- package/md_cg/whitebox_kb/wisdom/md_store.py +276 -251
- package/md_cg/whitebox_kb/wisdom/migrate_wisdom.py +476 -476
- package/md_cg/whitebox_kb/wisdom/multilang_ir.py +128 -124
- package/md_cg/whitebox_kb/wisdom/navigate.py +248 -248
- package/md_cg/whitebox_kb/wisdom/neural_retrieve.py +224 -224
- package/md_cg/whitebox_kb/wisdom/os_units.py +2735 -2735
- package/md_cg/whitebox_kb/wisdom/pattern_separation.py +376 -376
- package/md_cg/whitebox_kb/wisdom/prereq_map.json +364 -364
- package/md_cg/whitebox_kb/wisdom/python_code_units.py +2766 -2766
- package/md_cg/whitebox_kb/wisdom/role_solidified.json +7 -7
- package/md_cg/whitebox_kb/wisdom/route_memory.py +244 -244
- package/md_cg/whitebox_kb/wisdom/scene_reconstruction.py +148 -148
- package/md_cg/whitebox_kb/wisdom/snr_report.json +40 -40
- package/md_cg/whitebox_kb/wisdom/test_code_compose_domains.py +7541 -7541
- package/md_cg/whitebox_kb/wisdom/test_compiler_self_bootstrap.py +354 -354
- package/md_cg/whitebox_kb/wisdom/test_ecosystem_assembly.py +158 -158
- package/md_cg/whitebox_kb/wisdom/test_ecosystem_demos.py +193 -193
- package/md_cg/whitebox_kb/wisdom/test_graph_db.py +292 -292
- package/md_cg/whitebox_kb/wisdom/test_python_self_bootstrap.py +160 -160
- package/md_cg/whitebox_kb/wisdom/trigger_words_index.json +4366 -4366
- package/md_cg/whitebox_kb/wisdom/verifier.py +1297 -1297
- package/md_cg/writelimit.py +356 -356
- package/md_cg/writepipe.py +20 -8
- package/package.json +101 -96
- package/skills/plugin.json +54 -54
- package/skills/skills/designer-perspective/SKILL.md +158 -158
- package/skills/skills/designer-perspective/references/01-observation-position.md +66 -66
- package/skills/skills/designer-perspective/references/02-structure-recognition.md +62 -62
- package/skills/skills/designer-perspective/references/03-direction-judgment.md +55 -55
- package/skills/skills/designer-perspective/references/04-qualification-verdict.md +72 -72
- package/skills/skills/designer-perspective/references/05-condition-attribution.md +74 -74
- package/skills/skills/designer-perspective/scripts/designer.py +545 -545
- package/skills/skills/designer-perspective/tests/cases.jsonl +17 -17
- package/skills/skills/designer-perspective/tests/selftest.py +61 -61
- package/skills/skills/lingshu-browser/SKILL.md +60 -60
- package/skills/skills/lingshu-compiler/SKILL.md +56 -56
- package/skills/skills/lingshu-compiler/units/analyze-type-infer/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/check-name-real/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-assign/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-expr-tree/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-full-pipeline/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-func-def/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-if-then/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-logic-expr/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-recursive/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-scope/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-type-check/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compile-while/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0010c4bf/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0355bffb/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0361708a/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-054a0414/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-05a1691a/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-05eeed1e/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0622a1f6/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-08b54217/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0a62b70c/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0ab24d00/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0e093688/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0e82b966/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0ee9b9b9/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-0f3787e6/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-1028685f/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-11897630/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-16661b9b/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-1f722303/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-25be1262/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-2a76ba07/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-2dbea54a/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-2df52f16/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-2ec2c9d2/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-2f8c8f39/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-38378ac7/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-39457d2e/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-39fb5926/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-47f4fbfa/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-4a8cd1f1/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-4b230b6b/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-4cbbda95/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-4d5a68ff/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-56dc9bdd/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-57e76ebe/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-61ff016b/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-63eae588/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-64c4224b/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-66377955/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-674ab4f6/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-6af76fd6/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-6d979db2/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-6de326c0/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-6f1c0eea/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-724d6c9b/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-7690177f/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-7b229a7d/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-7f471b3a/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-815cad08/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-83c61634/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-8c798c81/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-8dd747ac/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-90324d0a/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-94b8d72d/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-94f12231/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-95937c16/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-98a5625b/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-98b4c42f/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-98e3894a/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-9bdfe4b8/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-9d9b4e83/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-9f7be5ad/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-a6daf076/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-a7995a0c/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-a8399248/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-aabbd099/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-afe169d8/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-b0678bda/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-b09bd196/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-b1396e23/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-b9b31ce0/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-c10264a7/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-cb1e8e4b/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-ccafd438/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-cda9c262/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-ce648068/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-cf5776a4/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-d974e5d3/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-e3979fd3/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-eb1cf2b5/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-ecb30d5b/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-f8c8b24b/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-f99fedbe/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-fa8ff5f7/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/compiler-fe1b058d/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/lex-chinese-program/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/lex-dao-de-jing/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/lex-nine-chapters/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-arithmetic/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-array-ops/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-closure-call/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-closure-create/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-compare/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-cond-jump/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-cond-space/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-exception/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-func-call/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-loop-run/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-profiling/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-refcount/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-run-loop/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-short-circuit/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-stack-guard/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-stack-ops/SKILL.md +45 -45
- package/skills/skills/lingshu-compiler/units/vm-trust-accum/SKILL.md +45 -45
- package/skills/skills/lingshu-graph/SKILL.md +63 -63
- package/skills/skills/lingshu-net/SKILL.md +48 -48
- package/skills/skills/lingshu-os/SKILL.md +64 -64
- package/skills/skills/lingshu-pylang/SKILL.md +71 -71
- package/src/bridge.ts +33 -0
- package/src/hooks.ts +38 -2
- package/src/index.ts +526 -518
- package/src/lib/datapath.ts +326 -326
- package/src/lib/mdcg_client.ts +413 -413
- package/src/lib/mutual.ts +428 -428
- package/src/lib/prompt_safety.ts +62 -62
- package/src/lib/python_path.ts +71 -71
- package/src/lib/roleplay_web.ts +116 -29
- package/src/lib/token_store.ts +13 -3
- package/src/tools.ts +212 -212
- package/zcode/AGENTS.md +11 -3
- package/zcode/README.md +41 -41
- /package/docs/{mdcg → hive}//344/270/273/344/273/243/347/220/206/345/255/220/344/273/243/347/220/206/350/256/260/345/277/206/346/236/266/346/236/204/350/256/276/350/256/241.md" +0 -0
package/md_cg/refine.py
CHANGED
|
@@ -1,605 +1,605 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""G6:node_ 结构提炼 · 小样本抽检工单与扩批闸门(只读预演,纯标准库)。
|
|
3
|
-
|
|
4
|
-
缺口(G6):`node_*`(P37 双子管线派生记忆)自带 `ccg_exempt=true`,正文只有
|
|
5
|
-
「功能名 / 生效条件」两行,`子功能 / 执行 / 不适用条件` 均未声明(实测 1324 条)。
|
|
6
|
-
若直接对全量跑归纳提炼(`consolidate.induce`),一旦**提炼口径**失准,就会把
|
|
7
|
-
模板化词面当成「共性条件」固化成概念节点,污染单一真相源。
|
|
8
|
-
|
|
9
|
-
裁定单条件 A:**先抽 20 条做提炼预演,人工核对提炼口径,绝不盲跑全量**。
|
|
10
|
-
|
|
11
|
-
本模块只做三件事(零 LLM、零第三方依赖,且**不改写任何节点**):
|
|
12
|
-
1. `plan` —— 确定性分层抽样出 20 条工单(原文摘录 / 字段缺口 / 口径声明);
|
|
13
|
-
2. `preview` —— 在抽检样本上跑与 `consolidate.induce` **同源的**聚类口径,
|
|
14
|
-
输出候选概念 + 每条共性条件的可追溯来源 + **样本内文档频率**
|
|
15
|
-
(df 高 = 模板/通用水位线,不得当作区分性共性);
|
|
16
|
-
3. `apply`/`gate` —— 把抽检批次与人工裁决落 `_refine.jsonl` 留痕,并据此
|
|
17
|
-
计算「是否允许扩大批次」。
|
|
18
|
-
|
|
19
|
-
纪律与诚实边界:
|
|
20
|
-
· 提炼执行仍由 `consolidate.induce` 承担(产出恒 `inferred`);本模块不实现
|
|
21
|
-
第二套提炼器,避免口径分叉——`preview` 复用同一套 `_jaccard/_common_terms`。
|
|
22
|
-
· 抽检若取「20 条节点」,在库内实测**可能产不出候选**(`min_cluster=3` 时
|
|
23
|
-
20 条随机样本 0 簇);此时如实报 `candidates=0` 并**拒绝放行扩批**,
|
|
24
|
-
由 `sample_adequacy` 提示「按候选为抽检单位」或上调样本量。
|
|
25
|
-
· 人工裁决必须由库外人类给出;本模块只做算术与留痕,**不代替裁决**。
|
|
26
|
-
· 共性条件的去模板判据只用**词面统计**,不做语义猜测(宁可漏判,不可误判)。
|
|
27
|
-
"""
|
|
28
|
-
from __future__ import annotations
|
|
29
|
-
|
|
30
|
-
import hashlib
|
|
31
|
-
import math
|
|
32
|
-
import os
|
|
33
|
-
import time
|
|
34
|
-
|
|
35
|
-
from . import consolidate, nodefile
|
|
36
|
-
from .fsutil import append_jsonl, read_jsonl
|
|
37
|
-
from .mdcos import MdCGOS
|
|
38
|
-
|
|
39
|
-
REFINE_LOG = "_refine.jsonl"
|
|
40
|
-
|
|
41
|
-
#: 抽检对象 id 前缀(P37 派生记忆)
|
|
42
|
-
PREFIX_DEFAULT = "node_"
|
|
43
|
-
#: 裁定单条件 A 规定的抽检条数
|
|
44
|
-
SAMPLE_N = 20
|
|
45
|
-
#: 抽样种子:同 seed ⇒ 同样本(可复算、可复核)
|
|
46
|
-
SAMPLE_SEED = "g6-sample-v1"
|
|
47
|
-
|
|
48
|
-
MIN_JACCARD = consolidate.INDUCE_MIN_JACCARD
|
|
49
|
-
MIN_CLUSTER = consolidate.INDUCE_MIN_CLUSTER
|
|
50
|
-
MAX_TERMS = consolidate.INDUCE_MAX_TERMS
|
|
51
|
-
|
|
52
|
-
#: 共性条件在**抽检样本**内的文档频率 ≥ 该值 → 判为模板/通用水位线,不具区分性
|
|
53
|
-
GENERIC_DF = 0.80
|
|
54
|
-
#: 工单正文摘录上限(超出即标 truncated;完整原文请 `cg read`)
|
|
55
|
-
EXCERPT_MAX = 4000
|
|
56
|
-
#: 扩批通过率闸门:人工核对「忠实」比例下限
|
|
57
|
-
GATE_MIN_PASS_RATE = 0.90
|
|
58
|
-
|
|
59
|
-
#: 样本量校准:目标产出候选数 / 有界爬坡上限(样本量非唯一杠杆,故设上限)
|
|
60
|
-
CALIBRATE_TARGET = 20
|
|
61
|
-
CALIBRATE_CAP = 200
|
|
62
|
-
|
|
63
|
-
#: 人工核对裁决键(与工单 review_items 一一对应)
|
|
64
|
-
VERDICT_KEYS = ("faithful", "added_info")
|
|
65
|
-
|
|
66
|
-
#: 提炼口径声明——人工核对的就是这份口径,不是某一次的输出
|
|
67
|
-
SPEC = {
|
|
68
|
-
"method": "consolidate.induce 同源口径:bigram-jaccard 贪心聚类(确定性、零 LLM)",
|
|
69
|
-
"min_cluster": MIN_CLUSTER,
|
|
70
|
-
"min_jaccard": MIN_JACCARD,
|
|
71
|
-
"fields": ["功能名", "生效条件", "子功能", "执行", "不适用条件"],
|
|
72
|
-
"evidence": "inferred(未经验证,不得直接当事实使用)",
|
|
73
|
-
"common_condition_source": "成员 positive_body 的词面统计(不做语义推断)",
|
|
74
|
-
"review_items": [
|
|
75
|
-
{"key": "faithful",
|
|
76
|
-
"ask": "每条共性条件是否能在**全部**成员原文中原样找到"
|
|
77
|
-
"(grounding_gap 为空即无缺口)"},
|
|
78
|
-
{"key": "added_info",
|
|
79
|
-
"ask": "概念是否引入了成员原文之外的词(外部知识/编造)——命中即整批否决"},
|
|
80
|
-
{"key": "discriminating",
|
|
81
|
-
"ask": "共性条件是否为区分性特征;df≥%.2f 的模板词不得充当共性"
|
|
82
|
-
% GENERIC_DF},
|
|
83
|
-
{"key": "member_consistency",
|
|
84
|
-
"ask": "成员是否真属同一范畴(无强行拼团)"},
|
|
85
|
-
],
|
|
86
|
-
"gate": {"min_pass_rate": GATE_MIN_PASS_RATE,
|
|
87
|
-
"require_review_all": True,
|
|
88
|
-
"fail_on_added_info": True},
|
|
89
|
-
"honest_limits": [
|
|
90
|
-
"本模块不执行提炼写入;扩批需 `consolidate.induce` 另开批次执行并留痕",
|
|
91
|
-
"人工裁决由库外人类给出,本模块不代替裁决",
|
|
92
|
-
],
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
# ---- 通用工具 -------------------------------------------------------------
|
|
96
|
-
|
|
97
|
-
# 生效条件:x 为 str 时返回 MdCGOS(x),否则原样返回 x;
|
|
98
|
-
def _as_cg(x):
|
|
99
|
-
return MdCGOS(x) if isinstance(x, str) else x
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
# 生效条件:cg 具 "root" 属性时以该值为根、否则以 str(cg) 为根,与模块常量 REFINE_LOG 拼接返回;
|
|
103
|
-
def _log_path(cg) -> str:
|
|
104
|
-
root = cg.root if hasattr(cg, "root") else str(cg)
|
|
105
|
-
return os.path.join(root, REFINE_LOG)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
# 生效条件:read_jsonl(_log_path(cg)) 返回假值时按空列表处理,仅保留 action 字段等于 "refine" 的记录;
|
|
109
|
-
def _read_log(cg) -> list:
|
|
110
|
-
return [r for r in (read_jsonl(_log_path(cg)) or [])
|
|
111
|
-
if r.get("action") == "refine"]
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
# 生效条件:cg 无 index 或 index["nodes"] 为假值时以空字典查找;nodes.get(nid) 为假值时返回空字典 {};
|
|
115
|
-
def _entry(cg, nid):
|
|
116
|
-
nodes = (getattr(cg, "index", None) or {}).get("nodes") or {}
|
|
117
|
-
return nodes.get(nid) or {}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
# 生效条件:取 _entry(cg, nid) 的 path(假值回落空串)的父目录 basename;该 basename 为假值时返回 "(root)";
|
|
121
|
-
def _family(cg, nid):
|
|
122
|
-
"""家族 = 节点所在 `cond_*` 目录名(无则 `(root)`)。"""
|
|
123
|
-
p = str(_entry(cg, nid).get("path") or "").replace("\\", "/")
|
|
124
|
-
d = os.path.basename(os.path.dirname(p))
|
|
125
|
-
return d or "(root)"
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
# 生效条件:对传入的 parts 逐项 str 后以 "|" 连接并 UTF-8 编码,返回 sha1 的 hexdigest;
|
|
129
|
-
def _sha(*parts) -> str:
|
|
130
|
-
h = hashlib.sha1("|".join(str(p) for p in parts).encode("utf-8"))
|
|
131
|
-
return h.hexdigest()
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
# 生效条件:遍历 cg.index 的 nodes,仅收 str(nid) 以 prefix 开头且条目 protected 为假值的 nid 进 ids(排序后返回),protected 为真值的计入 protected 计数;
|
|
135
|
-
def _pool(cg, prefix) -> tuple:
|
|
136
|
-
"""抽检池:id 以 prefix 开头、非受保护节点(与 induce 的池口径一致)。"""
|
|
137
|
-
nodes = (getattr(cg, "index", None) or {}).get("nodes") or {}
|
|
138
|
-
ids, protected = [], 0
|
|
139
|
-
for nid, e in nodes.items():
|
|
140
|
-
if not str(nid).startswith(prefix):
|
|
141
|
-
continue
|
|
142
|
-
if (e or {}).get("protected"):
|
|
143
|
-
protected += 1
|
|
144
|
-
continue
|
|
145
|
-
ids.append(nid)
|
|
146
|
-
ids.sort()
|
|
147
|
-
return ids, protected
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
# 生效条件:cg 的 id 池经 prefix(假值回落 PREFIX_DEFAULT)过滤后,n 为 None 用 SAMPLE_N、否则 int(n),seed 假值回落 SAMPLE_SEED;pool 为空或 n<=0 时返回 ([], meta),否则按家族分层最大余数分配并在层内按 _sha(seed, id) 升序取前 k 个返回 (picked, meta);
|
|
151
|
-
def sample_ids(cg, n=None, seed=None, prefix=None) -> tuple:
|
|
152
|
-
"""确定性分层抽样(家族=层,家族内按 sha1(seed|id) 排序)。
|
|
153
|
-
|
|
154
|
-
分层 + 最大余数分配:主家族按规模拿到多数名额,稀有家族亦保留席位(n 足够时)。
|
|
155
|
-
返回 `(ids, meta)`;同 `seed` 必得同样本——抽检可复核、可复算。
|
|
156
|
-
"""
|
|
157
|
-
n = SAMPLE_N if n is None else int(n)
|
|
158
|
-
seed = seed or SAMPLE_SEED
|
|
159
|
-
prefix = prefix or PREFIX_DEFAULT
|
|
160
|
-
pool, protected = _pool(cg, prefix)
|
|
161
|
-
fam = {}
|
|
162
|
-
for nid in pool:
|
|
163
|
-
fam.setdefault(_family(cg, nid), []).append(nid)
|
|
164
|
-
meta = {"prefix": prefix, "seed": seed, "requested": int(n),
|
|
165
|
-
"pool": len(pool), "skipped_protected": protected,
|
|
166
|
-
"families": len(fam), "sampled": 0, "strata": {}}
|
|
167
|
-
if not pool or n <= 0:
|
|
168
|
-
return [], meta
|
|
169
|
-
n = min(int(n), len(pool))
|
|
170
|
-
names = sorted(fam)
|
|
171
|
-
raw = {f: n * len(fam[f]) / float(len(pool)) for f in names}
|
|
172
|
-
alloc = {f: int(math.floor(raw[f])) for f in names}
|
|
173
|
-
rest = n - sum(alloc.values())
|
|
174
|
-
for f in sorted(names, key=lambda x: (-(raw[x] - alloc[x]), x))[:rest]:
|
|
175
|
-
alloc[f] += 1
|
|
176
|
-
picked, strata = [], {}
|
|
177
|
-
for f in names:
|
|
178
|
-
k = int(alloc[f])
|
|
179
|
-
if k <= 0:
|
|
180
|
-
continue
|
|
181
|
-
rank = sorted(fam[f], key=lambda x: _sha(seed, x))
|
|
182
|
-
take = rank[:k]
|
|
183
|
-
picked.extend(take)
|
|
184
|
-
strata[f] = {"pool": len(fam[f]), "picked": len(take)}
|
|
185
|
-
picked.sort()
|
|
186
|
-
meta["sampled"] = len(picked)
|
|
187
|
-
meta["strata"] = strata
|
|
188
|
-
return picked, meta
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
# 生效条件:对 cg 中 nid 对应节点(cg.get(nid) 为假值时用空字典)生成字段;content 长度大于 EXCERPT_MAX 时 body 截断且 body_truncated 为 True,否则 body 为全 content;
|
|
192
|
-
def _item(cg, nid) -> dict:
|
|
193
|
-
node = cg.get(nid) or {}
|
|
194
|
-
fm = node.get("frontmatter") or {}
|
|
195
|
-
content = node.get("content") or ""
|
|
196
|
-
comp = nodefile.ccg_completeness(content)
|
|
197
|
-
e = _entry(cg, nid)
|
|
198
|
-
truncated = len(content) > EXCERPT_MAX
|
|
199
|
-
return {
|
|
200
|
-
"id": nid, "family": _family(cg, nid), "layer": e.get("layer"),
|
|
201
|
-
"tags": list(e.get("tags") or []), "importance": e.get("importance"),
|
|
202
|
-
"edges": len(fm.get("edges") or []),
|
|
203
|
-
"ccg_exempt": bool(fm.get("ccg_exempt")),
|
|
204
|
-
"ccg_present": comp["present"],
|
|
205
|
-
"ccg_missing": [m for m in nodefile.CCG_MARKS if m not in comp["present"]],
|
|
206
|
-
"comment": fm.get("comment"),
|
|
207
|
-
"body": content[:EXCERPT_MAX], "body_len": len(content),
|
|
208
|
-
"body_truncated": truncated,
|
|
209
|
-
"source_sha": _sha(content)[:16],
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
# ---- 预演:与 induce 同源的聚类口径 ---------------------------------------
|
|
214
|
-
|
|
215
|
-
# 生效条件:pool 中能取到 grams 的 nid 进入 cache;按 keys 顺序贪心,与当前 a 的 _jaccard >= float(min_jaccard) 且未分配的后续 b 并入组,组大小达到 int(min_cluster) 才成组,返回 (cache, keys, groups);
|
|
216
|
-
def _cluster(cg, pool, min_jaccard, min_cluster) -> tuple:
|
|
217
|
-
"""贪心聚类(与 `consolidate.induce_memories` 同源)。返回 (cache, keys, groups)。"""
|
|
218
|
-
from . import subgraph
|
|
219
|
-
cache = {}
|
|
220
|
-
for nid in pool:
|
|
221
|
-
got = subgraph._node_terms_and_grams(cg, nid)
|
|
222
|
-
if got and got.get("grams"):
|
|
223
|
-
cache[nid] = got
|
|
224
|
-
keys = sorted(cache)
|
|
225
|
-
assigned, groups = set(), []
|
|
226
|
-
for i, a in enumerate(keys):
|
|
227
|
-
if a in assigned:
|
|
228
|
-
continue
|
|
229
|
-
ga = cache[a]["grams"]
|
|
230
|
-
grp = [b for b in keys[i + 1:]
|
|
231
|
-
if b not in assigned
|
|
232
|
-
and consolidate._jaccard(ga, cache[b]["grams"]) >= float(min_jaccard)]
|
|
233
|
-
if len(grp) + 1 < int(min_cluster):
|
|
234
|
-
continue
|
|
235
|
-
members = [a] + grp
|
|
236
|
-
assigned.update(members)
|
|
237
|
-
groups.append(members)
|
|
238
|
-
return cache, keys, groups
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
# 生效条件:terms 为空时对每个 t 返回 df[t]=0;否则对 keys 中每个 k 的 cache[k]["pos"] 统计各 term 出现次数,返回 df;
|
|
242
|
-
def _term_df(cache, keys, terms) -> dict:
|
|
243
|
-
"""词面在抽检样本内的文档频率(不含语义推断)。"""
|
|
244
|
-
df = {t: 0 for t in terms}
|
|
245
|
-
if not terms:
|
|
246
|
-
return df
|
|
247
|
-
for k in keys:
|
|
248
|
-
pos = set(cache[k]["pos"])
|
|
249
|
-
for t in terms:
|
|
250
|
-
if t in pos:
|
|
251
|
-
df[t] += 1
|
|
252
|
-
return df
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
# 生效条件:x 转为 cg;min_jaccard 为 None 取 MIN_JACCARD、否则 float(min_jaccard),min_cluster 为 None 取 MIN_CLUSTER、否则 int(min_cluster);ids 为真值时 pool 为显式 ids 去重排序且 meta 标记 explicit_ids=True,否则 pool/meta 来自 sample_ids(cg, n=n, seed=seed, prefix=prefix);对 pool 聚类后,require_conditions 为真且某组 common 为空时跳过该组并累计 skipped_no_cond;返回含 candidates 与 sample_adequacy 的只读预演字典;
|
|
256
|
-
def preview(x, ids=None, n=None, seed=None, prefix=None,
|
|
257
|
-
min_jaccard=None, min_cluster=None, require_conditions=True) -> dict:
|
|
258
|
-
"""提炼预演(只读):抽样 → 同源聚类 → 共性条件 + 来源 + 泛化度标记。"""
|
|
259
|
-
cg = _as_cg(x)
|
|
260
|
-
min_j = MIN_JACCARD if min_jaccard is None else float(min_jaccard)
|
|
261
|
-
min_c = MIN_CLUSTER if min_cluster is None else int(min_cluster)
|
|
262
|
-
if ids:
|
|
263
|
-
pool = sorted({str(i) for i in ids})
|
|
264
|
-
meta = {"prefix": prefix or PREFIX_DEFAULT, "seed": seed or SAMPLE_SEED,
|
|
265
|
-
"requested": len(pool), "pool": len(pool),
|
|
266
|
-
"skipped_protected": 0, "families": 0, "sampled": len(pool),
|
|
267
|
-
"strata": {}, "explicit_ids": True}
|
|
268
|
-
else:
|
|
269
|
-
pool, meta = sample_ids(cg, n=n, seed=seed, prefix=prefix)
|
|
270
|
-
|
|
271
|
-
cache, keys, groups = _cluster(cg, pool, min_j, min_c)
|
|
272
|
-
base = max(1, len(keys))
|
|
273
|
-
cands, skipped_no_cond, all_terms = [], 0, set()
|
|
274
|
-
for members in groups:
|
|
275
|
-
common = consolidate._common_terms([cache[m]["pos"] for m in members])
|
|
276
|
-
if require_conditions and not common:
|
|
277
|
-
skipped_no_cond += 1
|
|
278
|
-
continue
|
|
279
|
-
neg = consolidate._union_terms([cache[m]["neg"] for m in members])
|
|
280
|
-
all_terms.update(common)
|
|
281
|
-
cands.append({"concept_id": consolidate._concept_id(members),
|
|
282
|
-
"members": members, "size": len(members),
|
|
283
|
-
"common_conditions": common,
|
|
284
|
-
"non_applicable": neg})
|
|
285
|
-
|
|
286
|
-
df = _term_df(cache, keys, sorted(all_terms))
|
|
287
|
-
for c in cands:
|
|
288
|
-
members = c["members"]
|
|
289
|
-
src = {t: [m for m in members if t in cache[m]["pos"]]
|
|
290
|
-
for t in c["common_conditions"]}
|
|
291
|
-
gap = [t for t, ms in src.items() if len(ms) < len(members)]
|
|
292
|
-
generic = [{"term": t, "df": df.get(t, 0),
|
|
293
|
-
"ratio": round(df.get(t, 0) / float(base), 4)}
|
|
294
|
-
for t in c["common_conditions"]
|
|
295
|
-
if df.get(t, 0) / float(base) >= GENERIC_DF]
|
|
296
|
-
c.update({
|
|
297
|
-
"condition_sources": src,
|
|
298
|
-
"grounding_gap": gap,
|
|
299
|
-
"generic_conditions": generic,
|
|
300
|
-
"discriminating": [t for t in c["common_conditions"]
|
|
301
|
-
if df.get(t, 0) / float(base) < GENERIC_DF],
|
|
302
|
-
"reason": ("%d 条记忆内容相近且共享条件「%s」→ 归纳为概念"
|
|
303
|
-
% (len(members),
|
|
304
|
-
"、".join(c["common_conditions"][:MAX_TERMS]) or "无")),
|
|
305
|
-
})
|
|
306
|
-
generic_only = sum(1 for c in cands if not c["discriminating"])
|
|
307
|
-
adequacy = "ok" if cands else "insufficient"
|
|
308
|
-
return {
|
|
309
|
-
"root": cg.root, "dry_run": True, "readonly": True,
|
|
310
|
-
"action": "refine_preview", "op": "maintain",
|
|
311
|
-
"prefix": meta["prefix"], "seed": meta["seed"],
|
|
312
|
-
"requested": meta["requested"], "pool": meta["pool"],
|
|
313
|
-
"skipped_protected": meta["skipped_protected"],
|
|
314
|
-
"families": meta["families"], "strata": meta["strata"],
|
|
315
|
-
"sampled": meta["sampled"], "sample": pool,
|
|
316
|
-
"indexed": len(keys), "min_cluster": min_c, "min_jaccard": min_j,
|
|
317
|
-
"require_conditions": bool(require_conditions),
|
|
318
|
-
"clusters": len(cands), "candidates": cands,
|
|
319
|
-
"skipped_no_condition": skipped_no_cond,
|
|
320
|
-
"candidates_generic_only": generic_only,
|
|
321
|
-
"df_basis": "sampled", "generic_df_threshold": GENERIC_DF,
|
|
322
|
-
"sample_adequacy": adequacy,
|
|
323
|
-
"note": ("预演:未写盘、未改任何节点;候选供人工核对提炼口径"
|
|
324
|
-
if cands else
|
|
325
|
-
"抽检样本不足以产出候选(样本量或抽检单位需校准),不得据此扩批"),
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
# ---- 工单 ---------------------------------------------------------------
|
|
330
|
-
|
|
331
|
-
# 生效条件:x 转为 cg;prefix 假值回落 PREFIX_DEFAULT,seed 假值回落 SAMPLE_SEED;ids 为真值时 sample 为显式 ids 去重排序且 real_pool=len(sample)、skipped=0、families=0,否则 sample/meta 来自 sample_ids(cg, n=n, seed=seed, prefix=prefix) 并取 meta["pool"]/meta["skipped_protected"]/meta["families"];items 为 sample 的 _item,preview 以 sample 为显式 ids 调用,返回只读工单;
|
|
332
|
-
def plan(x, ids=None, n=None, seed=None, prefix=None,
|
|
333
|
-
min_jaccard=None, min_cluster=None) -> dict:
|
|
334
|
-
"""抽检工单(只读):确定性样本 + 原文摘录 + 字段缺口 + 口径声明 + 预演。"""
|
|
335
|
-
cg = _as_cg(x)
|
|
336
|
-
prefix = prefix or PREFIX_DEFAULT
|
|
337
|
-
seed = seed or SAMPLE_SEED
|
|
338
|
-
if ids:
|
|
339
|
-
sample = sorted({str(i) for i in ids})
|
|
340
|
-
real_pool, skipped, families = len(sample), 0, 0
|
|
341
|
-
else:
|
|
342
|
-
sample, meta = sample_ids(cg, n=n, seed=seed, prefix=prefix)
|
|
343
|
-
real_pool, skipped, families = (meta["pool"], meta["skipped_protected"],
|
|
344
|
-
meta["families"])
|
|
345
|
-
items = [_item(cg, nid) for nid in sample]
|
|
346
|
-
pv = preview(cg, ids=sample, prefix=prefix,
|
|
347
|
-
min_jaccard=min_jaccard, min_cluster=min_cluster)
|
|
348
|
-
strata = {}
|
|
349
|
-
for it in items:
|
|
350
|
-
k = it["family"]
|
|
351
|
-
s = strata.setdefault(k, {"picked": 0, "missing_fields": {},
|
|
352
|
-
"ccg_exempt": 0})
|
|
353
|
-
s["picked"] += 1
|
|
354
|
-
if it["ccg_exempt"]:
|
|
355
|
-
s["ccg_exempt"] += 1
|
|
356
|
-
for m in it["ccg_missing"]:
|
|
357
|
-
s["missing_fields"][m] = s["missing_fields"].get(m, 0) + 1
|
|
358
|
-
return {
|
|
359
|
-
"root": cg.root, "dry_run": True, "readonly": True,
|
|
360
|
-
"action": "refine", "op": "maintain",
|
|
361
|
-
"prefix": prefix, "seed": seed,
|
|
362
|
-
"requested": n if n is not None else (len(sample) if ids else SAMPLE_N),
|
|
363
|
-
"pool": real_pool, "skipped_protected": skipped,
|
|
364
|
-
"sampled": len(items), "families": families,
|
|
365
|
-
"sample": sample, "sample_sha": _sha(*sample)[:16],
|
|
366
|
-
"strata": strata, "items": items,
|
|
367
|
-
"worklist": items, "spec": SPEC,
|
|
368
|
-
"preview": {"clusters": pv["clusters"], "candidates": pv["candidates"],
|
|
369
|
-
"min_cluster": pv["min_cluster"],
|
|
370
|
-
"min_jaccard": pv["min_jaccard"],
|
|
371
|
-
"skipped_no_condition": pv["skipped_no_condition"],
|
|
372
|
-
"candidates_generic_only": pv["candidates_generic_only"],
|
|
373
|
-
"sample_adequacy": pv["sample_adequacy"],
|
|
374
|
-
"df_basis": pv["df_basis"],
|
|
375
|
-
"generic_df_threshold": pv["generic_df_threshold"]},
|
|
376
|
-
"gate_rule": SPEC["gate"],
|
|
377
|
-
"note": ("抽检工单(只读):供人工核对提炼口径;核对通过前不得扩批。"
|
|
378
|
-
"行 `apply=true` 只落抽检留痕,不改任何节点。"),
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
# 生效条件:x 转为 cg;target 为 None 取 CALIBRATE_TARGET、否则 int(target),cap 为 None 取 CALIBRATE_CAP、否则 int(cap),初始 n=min(SAMPLE_N if n0 is None else int(n0), cap);循环以 n 调 preview(cg, n=n, seed=seed, prefix=prefix, min_jaccard=min_jaccard, min_cluster=min_cluster) 并记录 step,直到 pv["clusters"] >= target 则 chosen=step 并 break,或 n >= min(cap, pool or cap) 则 break;chosen 为 None 时 blocked=True 并按 steps 汇总 recommend,否则 recommend 基于 chosen;返回只读校准报告;
|
|
383
|
-
def calibrate(x, target=None, cap=None, n0=None, seed=None, prefix=None,
|
|
384
|
-
min_jaccard=None, min_cluster=None) -> dict:
|
|
385
|
-
"""样本量校准(只读):20 条起步有界爬坡,直到产出 `target` 个候选或触顶。
|
|
386
|
-
|
|
387
|
-
目的是回答「20 条工单为什么交不出候选」,并给出**有界**的替代样本量;
|
|
388
|
-
若爬坡到顶仍无候选、或候选共性条件全为高 df 模板词,则如实报
|
|
389
|
-
`blocked` 并指出真正的瓶颈是**提炼口径**而非样本量——不得据此扩批。
|
|
390
|
-
"""
|
|
391
|
-
cg = _as_cg(x)
|
|
392
|
-
target = CALIBRATE_TARGET if target is None else int(target)
|
|
393
|
-
cap = CALIBRATE_CAP if cap is None else int(cap)
|
|
394
|
-
n = min(SAMPLE_N if n0 is None else int(n0), cap)
|
|
395
|
-
steps, chosen, pool = [], None, None
|
|
396
|
-
while True:
|
|
397
|
-
pv = preview(cg, n=n, seed=seed, prefix=prefix,
|
|
398
|
-
min_jaccard=min_jaccard, min_cluster=min_cluster)
|
|
399
|
-
pool = pv["pool"]
|
|
400
|
-
step = {"n": n, "sampled": pv["sampled"], "clusters": pv["clusters"],
|
|
401
|
-
"candidates_generic_only": pv["candidates_generic_only"],
|
|
402
|
-
"discriminating_terms": sorted(
|
|
403
|
-
{t for c in pv["candidates"] for t in c["discriminating"]})[:12]}
|
|
404
|
-
steps.append(step)
|
|
405
|
-
if pv["clusters"] >= target:
|
|
406
|
-
chosen = step
|
|
407
|
-
break
|
|
408
|
-
top = min(cap, pool or cap)
|
|
409
|
-
if n >= top:
|
|
410
|
-
break
|
|
411
|
-
n = min(n * 2, top)
|
|
412
|
-
blocked = chosen is None
|
|
413
|
-
saw_clusters = any(s["clusters"] for s in steps)
|
|
414
|
-
total_c = sum(s["clusters"] for s in steps)
|
|
415
|
-
gen_only = sum(s["candidates_generic_only"] for s in steps)
|
|
416
|
-
if not blocked:
|
|
417
|
-
rec = ("抽检样本量校准为 n=%d(产出 %d 个候选);仍须人工核对提炼口径,"
|
|
418
|
-
"核对通过前不得扩批" % (chosen["n"], chosen["clusters"]))
|
|
419
|
-
else:
|
|
420
|
-
parts = ["爬坡至 cap=%d 仍只产出 %d 个候选(< target=%d),样本量不足以"
|
|
421
|
-
"直接支撑抽检" % (cap, steps[-1]["clusters"], target)]
|
|
422
|
-
if saw_clusters:
|
|
423
|
-
if gen_only >= total_c:
|
|
424
|
-
parts.append("且已产出候选的共性条件**全部**为高 df 模板词(无区分性)")
|
|
425
|
-
else:
|
|
426
|
-
parts.append("且候选中 %d/%d 的共性条件全为高 df 模板词,"
|
|
427
|
-
"其余『区分性』项形如整行条件文本(含时间戳),"
|
|
428
|
-
"属归一化缺失导致的伪区分性" % (gen_only, total_c))
|
|
429
|
-
parts.append("须先修正提炼口径(剔除模板词 + 归一化/裁剪整行文本)"
|
|
430
|
-
"或以「候选」为抽检单位重新校准;当前状态不得扩批")
|
|
431
|
-
rec = ";".join(parts)
|
|
432
|
-
return {"ok": True, "action": "refine_calibrate", "op": "maintain",
|
|
433
|
-
"root": cg.root, "dry_run": True, "readonly": True,
|
|
434
|
-
"target": target, "cap": cap, "pool": pool,
|
|
435
|
-
"steps": steps, "chosen": chosen, "blocked": blocked,
|
|
436
|
-
"sample_adequacy": "ok" if not blocked else "insufficient",
|
|
437
|
-
"recommend": rec, "note": "只读校准:未写盘、未改任何节点。"}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
# ---- 留痕与扩批闸门 ------------------------------------------------------
|
|
441
|
-
|
|
442
|
-
# 生效条件:candidates 中取 concept_id 为真值的 id 列表;verdicts 中为 dict 且 concept_id 非空且首次出现的条目计入 reviewed 并累加其中 faithful/added_info 的真值计数;ids 非空时按 reviewed < len(ids) → awaiting_human_review、added 非零 → added_info_detected、faithful_rate < GATE_MIN_PASS_RATE → faithful_rate_below_gate、否则 ok 且 expand_allowed=True;ids 为空时 reason="no_candidates" 且 expand_allowed=False;
|
|
443
|
-
def _verdict_stats(verdicts, candidates) -> dict:
|
|
444
|
-
ids = [c.get("concept_id") for c in candidates if c.get("concept_id")]
|
|
445
|
-
seen, faithful, added = set(), 0, 0
|
|
446
|
-
for v in verdicts or []:
|
|
447
|
-
if not isinstance(v, dict):
|
|
448
|
-
continue
|
|
449
|
-
cid = v.get("concept_id")
|
|
450
|
-
if not cid or cid in seen:
|
|
451
|
-
continue
|
|
452
|
-
seen.add(cid)
|
|
453
|
-
faithful += 1 if v.get("faithful") else 0
|
|
454
|
-
added += 1 if v.get("added_info") else 0
|
|
455
|
-
reviewed = len(seen)
|
|
456
|
-
rate = (faithful / float(reviewed)) if reviewed else 0.0
|
|
457
|
-
expand, reason = False, "no_candidates"
|
|
458
|
-
if ids:
|
|
459
|
-
if reviewed < len(ids):
|
|
460
|
-
reason = "awaiting_human_review"
|
|
461
|
-
elif added:
|
|
462
|
-
reason = "added_info_detected"
|
|
463
|
-
elif rate < GATE_MIN_PASS_RATE:
|
|
464
|
-
reason = "faithful_rate_below_gate"
|
|
465
|
-
else:
|
|
466
|
-
reason = "ok"
|
|
467
|
-
expand = True
|
|
468
|
-
return {"candidates": len(ids), "reviewed": reviewed,
|
|
469
|
-
"faithful": faithful, "added_info": added,
|
|
470
|
-
"faithful_rate": round(rate, 4),
|
|
471
|
-
"min_pass_rate": GATE_MIN_PASS_RATE,
|
|
472
|
-
"expand_allowed": expand, "reason": reason}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
# 生效条件:x 转为 cg;以 ids/n/seed/prefix/min_jaccard/min_cluster 调 plan 得到 p,batch 假值(含 None/空串)回落当前时间字符串;从 p["preview"]["candidates"] 与 verdicts 经 _verdict_stats 得 stats;把含 batch/actor/prefix/seed/sample/verdicts/gate/note 的 record 追加写入 _log_path(cg),返回 rep;
|
|
476
|
-
def apply(x, batch=None, actor=None, verdicts=None, note=None,
|
|
477
|
-
ids=None, n=None, seed=None, prefix=None,
|
|
478
|
-
min_jaccard=None, min_cluster=None) -> dict:
|
|
479
|
-
"""落抽检批次 + 人工裁决到 `_refine.jsonl`。**不改写任何节点**。"""
|
|
480
|
-
cg = _as_cg(x)
|
|
481
|
-
p = plan(cg, ids=ids, n=n, seed=seed, prefix=prefix,
|
|
482
|
-
min_jaccard=min_jaccard, min_cluster=min_cluster)
|
|
483
|
-
batch = batch or time.strftime("%Y%m%d-%H%M%S")
|
|
484
|
-
cands = p["preview"]["candidates"]
|
|
485
|
-
stats = _verdict_stats(verdicts, cands)
|
|
486
|
-
record = {
|
|
487
|
-
"t": time.time(), "action": "refine", "batch": batch, "actor": actor,
|
|
488
|
-
"prefix": p["prefix"], "seed": p["seed"], "requested": p["requested"],
|
|
489
|
-
"pool": p["pool"], "sampled": p["sampled"],
|
|
490
|
-
"sample": p["sample"], "sample_sha": p["sample_sha"],
|
|
491
|
-
"sample_adequacy": p["preview"]["sample_adequacy"],
|
|
492
|
-
"min_cluster": p["preview"].get("min_cluster"),
|
|
493
|
-
"candidates": [{"concept_id": c["concept_id"], "members": c["members"],
|
|
494
|
-
"common_conditions": c["common_conditions"],
|
|
495
|
-
"grounding_gap": c["grounding_gap"],
|
|
496
|
-
"discriminating": c["discriminating"]}
|
|
497
|
-
for c in cands],
|
|
498
|
-
"verdicts": list(verdicts or []), "gate": stats, "note": note,
|
|
499
|
-
}
|
|
500
|
-
append_jsonl(_log_path(cg), record)
|
|
501
|
-
rep = {"ok": True, "action": "refine", "op": "maintain", "batch": batch,
|
|
502
|
-
"root": cg.root, "sampled": record["sampled"],
|
|
503
|
-
"candidates": stats["candidates"], "reviewed": stats["reviewed"],
|
|
504
|
-
"gate": stats, "log": REFINE_LOG,
|
|
505
|
-
"note": ("抽检留痕已落盘(未改任何节点);"
|
|
506
|
-
+ ("闸门放行扩批(仍需另开 induce 批次并留痕)"
|
|
507
|
-
if stats["expand_allowed"] else
|
|
508
|
-
"闸门未放行:" + stats["reason"]))}
|
|
509
|
-
return rep
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
# 生效条件:x 转为 cg;batch 为真值时只保留 _read_log(cg) 中 batch 字段相等的记录,过滤后为空时返回 batches=0、expand_allowed=False、reason="no_batch";否则取最后一条记录,以其 verdicts 与 candidates(假值转空列表)经 _verdict_stats 复算并返回 expand_allowed/reason 等;
|
|
513
|
-
def gate(x, batch=None) -> dict:
|
|
514
|
-
"""扩批闸门:读留痕复算通过率(不写盘)。"""
|
|
515
|
-
cg = _as_cg(x)
|
|
516
|
-
recs = _read_log(cg)
|
|
517
|
-
if batch:
|
|
518
|
-
recs = [r for r in recs if r.get("batch") == batch]
|
|
519
|
-
if not recs:
|
|
520
|
-
return {"ok": True, "action": "refine_gate", "op": "maintain",
|
|
521
|
-
"root": cg.root, "batch": batch, "batches": 0,
|
|
522
|
-
"expand_allowed": False, "reason": "no_batch"}
|
|
523
|
-
rec = recs[-1]
|
|
524
|
-
stats = _verdict_stats(rec.get("verdicts"), rec.get("candidates") or [])
|
|
525
|
-
return {"ok": True, "action": "refine_gate", "op": "maintain",
|
|
526
|
-
"root": cg.root, "batch": rec.get("batch"),
|
|
527
|
-
"batches": len({r.get("batch") for r in recs}),
|
|
528
|
-
"sampled": rec.get("sampled"),
|
|
529
|
-
"sample_adequacy": rec.get("sample_adequacy"),
|
|
530
|
-
"gate": stats, "expand_allowed": stats["expand_allowed"],
|
|
531
|
-
"reason": stats["reason"],
|
|
532
|
-
"note": ("闸门放行:可另开 induce 批次扩大提炼规模并逐批留痕"
|
|
533
|
-
if stats["expand_allowed"] else
|
|
534
|
-
"闸门未放行,禁止扩大批次;不得盲跑全量。")}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
# 生效条件:batch 为真值时只保留该批次记录,total 先记为过滤后条数;limit 非 None 且 >=0 时以 recs[-int(limit):] 截尾(limit=0 因 [-0:] 等价 [0:] 仍返回全部记录),limit 为 None 或负数时不截断;
|
|
538
|
-
def history(x, limit=100, batch=None) -> dict:
|
|
539
|
-
cg = _as_cg(x)
|
|
540
|
-
recs = _read_log(cg)
|
|
541
|
-
if batch:
|
|
542
|
-
recs = [r for r in recs if r.get("batch") == batch]
|
|
543
|
-
total = len(recs)
|
|
544
|
-
if limit is not None and limit >= 0:
|
|
545
|
-
recs = recs[-int(limit):]
|
|
546
|
-
return {"root": cg.root, "total": total, "returned": len(recs),
|
|
547
|
-
"action": "refine", "records": recs}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
# ---- CLI(真实库抽检/预演用;MCP 侧走 maintain action) -------------------
|
|
551
|
-
|
|
552
|
-
# 生效条件:解析 argv(None 时由 argparse 取 sys.argv)得到 root/n/seed/prefix/min-jaccard/min-cluster/report 等参数;a.calibrate 为真时调 calibrate 并打印 target/cap/pool/blocked/sample_adequacy/recommend 及每步摘要;否则 a.preview 为真选 preview、为假选 plan,调用后打印 thin 摘要与候选并返回 0;
|
|
553
|
-
def _main(argv=None) -> int:
|
|
554
|
-
import argparse
|
|
555
|
-
import json
|
|
556
|
-
ap = argparse.ArgumentParser(description="G6 提炼抽检(默认只预演,不写盘)")
|
|
557
|
-
ap.add_argument("root", help="认知图库根")
|
|
558
|
-
ap.add_argument("--n", type=int, default=SAMPLE_N, help="抽检条数(默认 20)")
|
|
559
|
-
ap.add_argument("--seed", default=SAMPLE_SEED, help="抽样种子(同 seed 同样本)")
|
|
560
|
-
ap.add_argument("--prefix", default=PREFIX_DEFAULT, help="id 前缀(默认 node_)")
|
|
561
|
-
ap.add_argument("--min-jaccard", type=float, default=None)
|
|
562
|
-
ap.add_argument("--min-cluster", type=int, default=None)
|
|
563
|
-
ap.add_argument("--preview", action="store_true", help="只出预演(不出工单正文)")
|
|
564
|
-
ap.add_argument("--calibrate", action="store_true",
|
|
565
|
-
help="只做样本量校准(20 条起步有界爬坡)")
|
|
566
|
-
ap.add_argument("--report", default=None, help="把汇总 JSON 另存一份")
|
|
567
|
-
a = ap.parse_args(argv)
|
|
568
|
-
if a.calibrate:
|
|
569
|
-
rep = calibrate(a.root, seed=a.seed, prefix=a.prefix,
|
|
570
|
-
min_jaccard=a.min_jaccard, min_cluster=a.min_cluster)
|
|
571
|
-
if a.report:
|
|
572
|
-
with open(a.report, "w", encoding="utf-8") as f:
|
|
573
|
-
json.dump(rep, f, ensure_ascii=False, indent=2)
|
|
574
|
-
print(json.dumps({k: rep[k] for k in
|
|
575
|
-
("target", "cap", "pool", "blocked",
|
|
576
|
-
"sample_adequacy", "recommend")},
|
|
577
|
-
ensure_ascii=False, indent=2))
|
|
578
|
-
for s in rep["steps"]:
|
|
579
|
-
print(" - n=%4d clusters=%2d generic_only=%2d discriminating=%s"
|
|
580
|
-
% (s["n"], s["clusters"], s["candidates_generic_only"],
|
|
581
|
-
s["discriminating_terms"][:6]))
|
|
582
|
-
return 0
|
|
583
|
-
fn = preview if a.preview else plan
|
|
584
|
-
rep = fn(a.root, n=a.n, seed=a.seed, prefix=a.prefix,
|
|
585
|
-
min_jaccard=a.min_jaccard, min_cluster=a.min_cluster)
|
|
586
|
-
if a.report:
|
|
587
|
-
with open(a.report, "w", encoding="utf-8") as f:
|
|
588
|
-
json.dump(rep, f, ensure_ascii=False, indent=2)
|
|
589
|
-
pv = rep.get("preview") if "preview" in rep else rep
|
|
590
|
-
thin = {k: rep.get(k) for k in ("action", "prefix", "seed", "requested",
|
|
591
|
-
"pool", "sampled", "families")}
|
|
592
|
-
thin.update({k: pv.get(k) for k in ("clusters", "sample_adequacy",
|
|
593
|
-
"candidates_generic_only")})
|
|
594
|
-
print(json.dumps(thin, ensure_ascii=False, indent=2))
|
|
595
|
-
for c in (pv.get("candidates") or []):
|
|
596
|
-
print(" - %s size=%d common=%s generic=%s gap=%s"
|
|
597
|
-
% (c["concept_id"], c["size"], c["common_conditions"][:4],
|
|
598
|
-
[g["term"] for g in c.get("generic_conditions", [])],
|
|
599
|
-
c.get("grounding_gap")))
|
|
600
|
-
return 0
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
if __name__ == "__main__":
|
|
604
|
-
import sys
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""G6:node_ 结构提炼 · 小样本抽检工单与扩批闸门(只读预演,纯标准库)。
|
|
3
|
+
|
|
4
|
+
缺口(G6):`node_*`(P37 双子管线派生记忆)自带 `ccg_exempt=true`,正文只有
|
|
5
|
+
「功能名 / 生效条件」两行,`子功能 / 执行 / 不适用条件` 均未声明(实测 1324 条)。
|
|
6
|
+
若直接对全量跑归纳提炼(`consolidate.induce`),一旦**提炼口径**失准,就会把
|
|
7
|
+
模板化词面当成「共性条件」固化成概念节点,污染单一真相源。
|
|
8
|
+
|
|
9
|
+
裁定单条件 A:**先抽 20 条做提炼预演,人工核对提炼口径,绝不盲跑全量**。
|
|
10
|
+
|
|
11
|
+
本模块只做三件事(零 LLM、零第三方依赖,且**不改写任何节点**):
|
|
12
|
+
1. `plan` —— 确定性分层抽样出 20 条工单(原文摘录 / 字段缺口 / 口径声明);
|
|
13
|
+
2. `preview` —— 在抽检样本上跑与 `consolidate.induce` **同源的**聚类口径,
|
|
14
|
+
输出候选概念 + 每条共性条件的可追溯来源 + **样本内文档频率**
|
|
15
|
+
(df 高 = 模板/通用水位线,不得当作区分性共性);
|
|
16
|
+
3. `apply`/`gate` —— 把抽检批次与人工裁决落 `_refine.jsonl` 留痕,并据此
|
|
17
|
+
计算「是否允许扩大批次」。
|
|
18
|
+
|
|
19
|
+
纪律与诚实边界:
|
|
20
|
+
· 提炼执行仍由 `consolidate.induce` 承担(产出恒 `inferred`);本模块不实现
|
|
21
|
+
第二套提炼器,避免口径分叉——`preview` 复用同一套 `_jaccard/_common_terms`。
|
|
22
|
+
· 抽检若取「20 条节点」,在库内实测**可能产不出候选**(`min_cluster=3` 时
|
|
23
|
+
20 条随机样本 0 簇);此时如实报 `candidates=0` 并**拒绝放行扩批**,
|
|
24
|
+
由 `sample_adequacy` 提示「按候选为抽检单位」或上调样本量。
|
|
25
|
+
· 人工裁决必须由库外人类给出;本模块只做算术与留痕,**不代替裁决**。
|
|
26
|
+
· 共性条件的去模板判据只用**词面统计**,不做语义猜测(宁可漏判,不可误判)。
|
|
27
|
+
"""
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import hashlib
|
|
31
|
+
import math
|
|
32
|
+
import os
|
|
33
|
+
import time
|
|
34
|
+
|
|
35
|
+
from . import consolidate, nodefile
|
|
36
|
+
from .fsutil import append_jsonl, read_jsonl
|
|
37
|
+
from .mdcos import MdCGOS
|
|
38
|
+
|
|
39
|
+
REFINE_LOG = "_refine.jsonl"
|
|
40
|
+
|
|
41
|
+
#: 抽检对象 id 前缀(P37 派生记忆)
|
|
42
|
+
PREFIX_DEFAULT = "node_"
|
|
43
|
+
#: 裁定单条件 A 规定的抽检条数
|
|
44
|
+
SAMPLE_N = 20
|
|
45
|
+
#: 抽样种子:同 seed ⇒ 同样本(可复算、可复核)
|
|
46
|
+
SAMPLE_SEED = "g6-sample-v1"
|
|
47
|
+
|
|
48
|
+
MIN_JACCARD = consolidate.INDUCE_MIN_JACCARD
|
|
49
|
+
MIN_CLUSTER = consolidate.INDUCE_MIN_CLUSTER
|
|
50
|
+
MAX_TERMS = consolidate.INDUCE_MAX_TERMS
|
|
51
|
+
|
|
52
|
+
#: 共性条件在**抽检样本**内的文档频率 ≥ 该值 → 判为模板/通用水位线,不具区分性
|
|
53
|
+
GENERIC_DF = 0.80
|
|
54
|
+
#: 工单正文摘录上限(超出即标 truncated;完整原文请 `cg read`)
|
|
55
|
+
EXCERPT_MAX = 4000
|
|
56
|
+
#: 扩批通过率闸门:人工核对「忠实」比例下限
|
|
57
|
+
GATE_MIN_PASS_RATE = 0.90
|
|
58
|
+
|
|
59
|
+
#: 样本量校准:目标产出候选数 / 有界爬坡上限(样本量非唯一杠杆,故设上限)
|
|
60
|
+
CALIBRATE_TARGET = 20
|
|
61
|
+
CALIBRATE_CAP = 200
|
|
62
|
+
|
|
63
|
+
#: 人工核对裁决键(与工单 review_items 一一对应)
|
|
64
|
+
VERDICT_KEYS = ("faithful", "added_info")
|
|
65
|
+
|
|
66
|
+
#: 提炼口径声明——人工核对的就是这份口径,不是某一次的输出
|
|
67
|
+
SPEC = {
|
|
68
|
+
"method": "consolidate.induce 同源口径:bigram-jaccard 贪心聚类(确定性、零 LLM)",
|
|
69
|
+
"min_cluster": MIN_CLUSTER,
|
|
70
|
+
"min_jaccard": MIN_JACCARD,
|
|
71
|
+
"fields": ["功能名", "生效条件", "子功能", "执行", "不适用条件"],
|
|
72
|
+
"evidence": "inferred(未经验证,不得直接当事实使用)",
|
|
73
|
+
"common_condition_source": "成员 positive_body 的词面统计(不做语义推断)",
|
|
74
|
+
"review_items": [
|
|
75
|
+
{"key": "faithful",
|
|
76
|
+
"ask": "每条共性条件是否能在**全部**成员原文中原样找到"
|
|
77
|
+
"(grounding_gap 为空即无缺口)"},
|
|
78
|
+
{"key": "added_info",
|
|
79
|
+
"ask": "概念是否引入了成员原文之外的词(外部知识/编造)——命中即整批否决"},
|
|
80
|
+
{"key": "discriminating",
|
|
81
|
+
"ask": "共性条件是否为区分性特征;df≥%.2f 的模板词不得充当共性"
|
|
82
|
+
% GENERIC_DF},
|
|
83
|
+
{"key": "member_consistency",
|
|
84
|
+
"ask": "成员是否真属同一范畴(无强行拼团)"},
|
|
85
|
+
],
|
|
86
|
+
"gate": {"min_pass_rate": GATE_MIN_PASS_RATE,
|
|
87
|
+
"require_review_all": True,
|
|
88
|
+
"fail_on_added_info": True},
|
|
89
|
+
"honest_limits": [
|
|
90
|
+
"本模块不执行提炼写入;扩批需 `consolidate.induce` 另开批次执行并留痕",
|
|
91
|
+
"人工裁决由库外人类给出,本模块不代替裁决",
|
|
92
|
+
],
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# ---- 通用工具 -------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
# 生效条件:x 为 str 时返回 MdCGOS(x),否则原样返回 x;
|
|
98
|
+
def _as_cg(x):
|
|
99
|
+
return MdCGOS(x) if isinstance(x, str) else x
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# 生效条件:cg 具 "root" 属性时以该值为根、否则以 str(cg) 为根,与模块常量 REFINE_LOG 拼接返回;
|
|
103
|
+
def _log_path(cg) -> str:
|
|
104
|
+
root = cg.root if hasattr(cg, "root") else str(cg)
|
|
105
|
+
return os.path.join(root, REFINE_LOG)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# 生效条件:read_jsonl(_log_path(cg)) 返回假值时按空列表处理,仅保留 action 字段等于 "refine" 的记录;
|
|
109
|
+
def _read_log(cg) -> list:
|
|
110
|
+
return [r for r in (read_jsonl(_log_path(cg)) or [])
|
|
111
|
+
if r.get("action") == "refine"]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# 生效条件:cg 无 index 或 index["nodes"] 为假值时以空字典查找;nodes.get(nid) 为假值时返回空字典 {};
|
|
115
|
+
def _entry(cg, nid):
|
|
116
|
+
nodes = (getattr(cg, "index", None) or {}).get("nodes") or {}
|
|
117
|
+
return nodes.get(nid) or {}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# 生效条件:取 _entry(cg, nid) 的 path(假值回落空串)的父目录 basename;该 basename 为假值时返回 "(root)";
|
|
121
|
+
def _family(cg, nid):
|
|
122
|
+
"""家族 = 节点所在 `cond_*` 目录名(无则 `(root)`)。"""
|
|
123
|
+
p = str(_entry(cg, nid).get("path") or "").replace("\\", "/")
|
|
124
|
+
d = os.path.basename(os.path.dirname(p))
|
|
125
|
+
return d or "(root)"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# 生效条件:对传入的 parts 逐项 str 后以 "|" 连接并 UTF-8 编码,返回 sha1 的 hexdigest;
|
|
129
|
+
def _sha(*parts) -> str:
|
|
130
|
+
h = hashlib.sha1("|".join(str(p) for p in parts).encode("utf-8"))
|
|
131
|
+
return h.hexdigest()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# 生效条件:遍历 cg.index 的 nodes,仅收 str(nid) 以 prefix 开头且条目 protected 为假值的 nid 进 ids(排序后返回),protected 为真值的计入 protected 计数;
|
|
135
|
+
def _pool(cg, prefix) -> tuple:
|
|
136
|
+
"""抽检池:id 以 prefix 开头、非受保护节点(与 induce 的池口径一致)。"""
|
|
137
|
+
nodes = (getattr(cg, "index", None) or {}).get("nodes") or {}
|
|
138
|
+
ids, protected = [], 0
|
|
139
|
+
for nid, e in nodes.items():
|
|
140
|
+
if not str(nid).startswith(prefix):
|
|
141
|
+
continue
|
|
142
|
+
if (e or {}).get("protected"):
|
|
143
|
+
protected += 1
|
|
144
|
+
continue
|
|
145
|
+
ids.append(nid)
|
|
146
|
+
ids.sort()
|
|
147
|
+
return ids, protected
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# 生效条件:cg 的 id 池经 prefix(假值回落 PREFIX_DEFAULT)过滤后,n 为 None 用 SAMPLE_N、否则 int(n),seed 假值回落 SAMPLE_SEED;pool 为空或 n<=0 时返回 ([], meta),否则按家族分层最大余数分配并在层内按 _sha(seed, id) 升序取前 k 个返回 (picked, meta);
|
|
151
|
+
def sample_ids(cg, n=None, seed=None, prefix=None) -> tuple:
|
|
152
|
+
"""确定性分层抽样(家族=层,家族内按 sha1(seed|id) 排序)。
|
|
153
|
+
|
|
154
|
+
分层 + 最大余数分配:主家族按规模拿到多数名额,稀有家族亦保留席位(n 足够时)。
|
|
155
|
+
返回 `(ids, meta)`;同 `seed` 必得同样本——抽检可复核、可复算。
|
|
156
|
+
"""
|
|
157
|
+
n = SAMPLE_N if n is None else int(n)
|
|
158
|
+
seed = seed or SAMPLE_SEED
|
|
159
|
+
prefix = prefix or PREFIX_DEFAULT
|
|
160
|
+
pool, protected = _pool(cg, prefix)
|
|
161
|
+
fam = {}
|
|
162
|
+
for nid in pool:
|
|
163
|
+
fam.setdefault(_family(cg, nid), []).append(nid)
|
|
164
|
+
meta = {"prefix": prefix, "seed": seed, "requested": int(n),
|
|
165
|
+
"pool": len(pool), "skipped_protected": protected,
|
|
166
|
+
"families": len(fam), "sampled": 0, "strata": {}}
|
|
167
|
+
if not pool or n <= 0:
|
|
168
|
+
return [], meta
|
|
169
|
+
n = min(int(n), len(pool))
|
|
170
|
+
names = sorted(fam)
|
|
171
|
+
raw = {f: n * len(fam[f]) / float(len(pool)) for f in names}
|
|
172
|
+
alloc = {f: int(math.floor(raw[f])) for f in names}
|
|
173
|
+
rest = n - sum(alloc.values())
|
|
174
|
+
for f in sorted(names, key=lambda x: (-(raw[x] - alloc[x]), x))[:rest]:
|
|
175
|
+
alloc[f] += 1
|
|
176
|
+
picked, strata = [], {}
|
|
177
|
+
for f in names:
|
|
178
|
+
k = int(alloc[f])
|
|
179
|
+
if k <= 0:
|
|
180
|
+
continue
|
|
181
|
+
rank = sorted(fam[f], key=lambda x: _sha(seed, x))
|
|
182
|
+
take = rank[:k]
|
|
183
|
+
picked.extend(take)
|
|
184
|
+
strata[f] = {"pool": len(fam[f]), "picked": len(take)}
|
|
185
|
+
picked.sort()
|
|
186
|
+
meta["sampled"] = len(picked)
|
|
187
|
+
meta["strata"] = strata
|
|
188
|
+
return picked, meta
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# 生效条件:对 cg 中 nid 对应节点(cg.get(nid) 为假值时用空字典)生成字段;content 长度大于 EXCERPT_MAX 时 body 截断且 body_truncated 为 True,否则 body 为全 content;
|
|
192
|
+
def _item(cg, nid) -> dict:
|
|
193
|
+
node = cg.get(nid) or {}
|
|
194
|
+
fm = node.get("frontmatter") or {}
|
|
195
|
+
content = node.get("content") or ""
|
|
196
|
+
comp = nodefile.ccg_completeness(content)
|
|
197
|
+
e = _entry(cg, nid)
|
|
198
|
+
truncated = len(content) > EXCERPT_MAX
|
|
199
|
+
return {
|
|
200
|
+
"id": nid, "family": _family(cg, nid), "layer": e.get("layer"),
|
|
201
|
+
"tags": list(e.get("tags") or []), "importance": e.get("importance"),
|
|
202
|
+
"edges": len(fm.get("edges") or []),
|
|
203
|
+
"ccg_exempt": bool(fm.get("ccg_exempt")),
|
|
204
|
+
"ccg_present": comp["present"],
|
|
205
|
+
"ccg_missing": [m for m in nodefile.CCG_MARKS if m not in comp["present"]],
|
|
206
|
+
"comment": fm.get("comment"),
|
|
207
|
+
"body": content[:EXCERPT_MAX], "body_len": len(content),
|
|
208
|
+
"body_truncated": truncated,
|
|
209
|
+
"source_sha": _sha(content)[:16],
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# ---- 预演:与 induce 同源的聚类口径 ---------------------------------------
|
|
214
|
+
|
|
215
|
+
# 生效条件:pool 中能取到 grams 的 nid 进入 cache;按 keys 顺序贪心,与当前 a 的 _jaccard >= float(min_jaccard) 且未分配的后续 b 并入组,组大小达到 int(min_cluster) 才成组,返回 (cache, keys, groups);
|
|
216
|
+
def _cluster(cg, pool, min_jaccard, min_cluster) -> tuple:
|
|
217
|
+
"""贪心聚类(与 `consolidate.induce_memories` 同源)。返回 (cache, keys, groups)。"""
|
|
218
|
+
from . import subgraph
|
|
219
|
+
cache = {}
|
|
220
|
+
for nid in pool:
|
|
221
|
+
got = subgraph._node_terms_and_grams(cg, nid)
|
|
222
|
+
if got and got.get("grams"):
|
|
223
|
+
cache[nid] = got
|
|
224
|
+
keys = sorted(cache)
|
|
225
|
+
assigned, groups = set(), []
|
|
226
|
+
for i, a in enumerate(keys):
|
|
227
|
+
if a in assigned:
|
|
228
|
+
continue
|
|
229
|
+
ga = cache[a]["grams"]
|
|
230
|
+
grp = [b for b in keys[i + 1:]
|
|
231
|
+
if b not in assigned
|
|
232
|
+
and consolidate._jaccard(ga, cache[b]["grams"]) >= float(min_jaccard)]
|
|
233
|
+
if len(grp) + 1 < int(min_cluster):
|
|
234
|
+
continue
|
|
235
|
+
members = [a] + grp
|
|
236
|
+
assigned.update(members)
|
|
237
|
+
groups.append(members)
|
|
238
|
+
return cache, keys, groups
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# 生效条件:terms 为空时对每个 t 返回 df[t]=0;否则对 keys 中每个 k 的 cache[k]["pos"] 统计各 term 出现次数,返回 df;
|
|
242
|
+
def _term_df(cache, keys, terms) -> dict:
|
|
243
|
+
"""词面在抽检样本内的文档频率(不含语义推断)。"""
|
|
244
|
+
df = {t: 0 for t in terms}
|
|
245
|
+
if not terms:
|
|
246
|
+
return df
|
|
247
|
+
for k in keys:
|
|
248
|
+
pos = set(cache[k]["pos"])
|
|
249
|
+
for t in terms:
|
|
250
|
+
if t in pos:
|
|
251
|
+
df[t] += 1
|
|
252
|
+
return df
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# 生效条件:x 转为 cg;min_jaccard 为 None 取 MIN_JACCARD、否则 float(min_jaccard),min_cluster 为 None 取 MIN_CLUSTER、否则 int(min_cluster);ids 为真值时 pool 为显式 ids 去重排序且 meta 标记 explicit_ids=True,否则 pool/meta 来自 sample_ids(cg, n=n, seed=seed, prefix=prefix);对 pool 聚类后,require_conditions 为真且某组 common 为空时跳过该组并累计 skipped_no_cond;返回含 candidates 与 sample_adequacy 的只读预演字典;
|
|
256
|
+
def preview(x, ids=None, n=None, seed=None, prefix=None,
|
|
257
|
+
min_jaccard=None, min_cluster=None, require_conditions=True) -> dict:
|
|
258
|
+
"""提炼预演(只读):抽样 → 同源聚类 → 共性条件 + 来源 + 泛化度标记。"""
|
|
259
|
+
cg = _as_cg(x)
|
|
260
|
+
min_j = MIN_JACCARD if min_jaccard is None else float(min_jaccard)
|
|
261
|
+
min_c = MIN_CLUSTER if min_cluster is None else int(min_cluster)
|
|
262
|
+
if ids:
|
|
263
|
+
pool = sorted({str(i) for i in ids})
|
|
264
|
+
meta = {"prefix": prefix or PREFIX_DEFAULT, "seed": seed or SAMPLE_SEED,
|
|
265
|
+
"requested": len(pool), "pool": len(pool),
|
|
266
|
+
"skipped_protected": 0, "families": 0, "sampled": len(pool),
|
|
267
|
+
"strata": {}, "explicit_ids": True}
|
|
268
|
+
else:
|
|
269
|
+
pool, meta = sample_ids(cg, n=n, seed=seed, prefix=prefix)
|
|
270
|
+
|
|
271
|
+
cache, keys, groups = _cluster(cg, pool, min_j, min_c)
|
|
272
|
+
base = max(1, len(keys))
|
|
273
|
+
cands, skipped_no_cond, all_terms = [], 0, set()
|
|
274
|
+
for members in groups:
|
|
275
|
+
common = consolidate._common_terms([cache[m]["pos"] for m in members])
|
|
276
|
+
if require_conditions and not common:
|
|
277
|
+
skipped_no_cond += 1
|
|
278
|
+
continue
|
|
279
|
+
neg = consolidate._union_terms([cache[m]["neg"] for m in members])
|
|
280
|
+
all_terms.update(common)
|
|
281
|
+
cands.append({"concept_id": consolidate._concept_id(members),
|
|
282
|
+
"members": members, "size": len(members),
|
|
283
|
+
"common_conditions": common,
|
|
284
|
+
"non_applicable": neg})
|
|
285
|
+
|
|
286
|
+
df = _term_df(cache, keys, sorted(all_terms))
|
|
287
|
+
for c in cands:
|
|
288
|
+
members = c["members"]
|
|
289
|
+
src = {t: [m for m in members if t in cache[m]["pos"]]
|
|
290
|
+
for t in c["common_conditions"]}
|
|
291
|
+
gap = [t for t, ms in src.items() if len(ms) < len(members)]
|
|
292
|
+
generic = [{"term": t, "df": df.get(t, 0),
|
|
293
|
+
"ratio": round(df.get(t, 0) / float(base), 4)}
|
|
294
|
+
for t in c["common_conditions"]
|
|
295
|
+
if df.get(t, 0) / float(base) >= GENERIC_DF]
|
|
296
|
+
c.update({
|
|
297
|
+
"condition_sources": src,
|
|
298
|
+
"grounding_gap": gap,
|
|
299
|
+
"generic_conditions": generic,
|
|
300
|
+
"discriminating": [t for t in c["common_conditions"]
|
|
301
|
+
if df.get(t, 0) / float(base) < GENERIC_DF],
|
|
302
|
+
"reason": ("%d 条记忆内容相近且共享条件「%s」→ 归纳为概念"
|
|
303
|
+
% (len(members),
|
|
304
|
+
"、".join(c["common_conditions"][:MAX_TERMS]) or "无")),
|
|
305
|
+
})
|
|
306
|
+
generic_only = sum(1 for c in cands if not c["discriminating"])
|
|
307
|
+
adequacy = "ok" if cands else "insufficient"
|
|
308
|
+
return {
|
|
309
|
+
"root": cg.root, "dry_run": True, "readonly": True,
|
|
310
|
+
"action": "refine_preview", "op": "maintain",
|
|
311
|
+
"prefix": meta["prefix"], "seed": meta["seed"],
|
|
312
|
+
"requested": meta["requested"], "pool": meta["pool"],
|
|
313
|
+
"skipped_protected": meta["skipped_protected"],
|
|
314
|
+
"families": meta["families"], "strata": meta["strata"],
|
|
315
|
+
"sampled": meta["sampled"], "sample": pool,
|
|
316
|
+
"indexed": len(keys), "min_cluster": min_c, "min_jaccard": min_j,
|
|
317
|
+
"require_conditions": bool(require_conditions),
|
|
318
|
+
"clusters": len(cands), "candidates": cands,
|
|
319
|
+
"skipped_no_condition": skipped_no_cond,
|
|
320
|
+
"candidates_generic_only": generic_only,
|
|
321
|
+
"df_basis": "sampled", "generic_df_threshold": GENERIC_DF,
|
|
322
|
+
"sample_adequacy": adequacy,
|
|
323
|
+
"note": ("预演:未写盘、未改任何节点;候选供人工核对提炼口径"
|
|
324
|
+
if cands else
|
|
325
|
+
"抽检样本不足以产出候选(样本量或抽检单位需校准),不得据此扩批"),
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
# ---- 工单 ---------------------------------------------------------------
|
|
330
|
+
|
|
331
|
+
# 生效条件:x 转为 cg;prefix 假值回落 PREFIX_DEFAULT,seed 假值回落 SAMPLE_SEED;ids 为真值时 sample 为显式 ids 去重排序且 real_pool=len(sample)、skipped=0、families=0,否则 sample/meta 来自 sample_ids(cg, n=n, seed=seed, prefix=prefix) 并取 meta["pool"]/meta["skipped_protected"]/meta["families"];items 为 sample 的 _item,preview 以 sample 为显式 ids 调用,返回只读工单;
|
|
332
|
+
def plan(x, ids=None, n=None, seed=None, prefix=None,
|
|
333
|
+
min_jaccard=None, min_cluster=None) -> dict:
|
|
334
|
+
"""抽检工单(只读):确定性样本 + 原文摘录 + 字段缺口 + 口径声明 + 预演。"""
|
|
335
|
+
cg = _as_cg(x)
|
|
336
|
+
prefix = prefix or PREFIX_DEFAULT
|
|
337
|
+
seed = seed or SAMPLE_SEED
|
|
338
|
+
if ids:
|
|
339
|
+
sample = sorted({str(i) for i in ids})
|
|
340
|
+
real_pool, skipped, families = len(sample), 0, 0
|
|
341
|
+
else:
|
|
342
|
+
sample, meta = sample_ids(cg, n=n, seed=seed, prefix=prefix)
|
|
343
|
+
real_pool, skipped, families = (meta["pool"], meta["skipped_protected"],
|
|
344
|
+
meta["families"])
|
|
345
|
+
items = [_item(cg, nid) for nid in sample]
|
|
346
|
+
pv = preview(cg, ids=sample, prefix=prefix,
|
|
347
|
+
min_jaccard=min_jaccard, min_cluster=min_cluster)
|
|
348
|
+
strata = {}
|
|
349
|
+
for it in items:
|
|
350
|
+
k = it["family"]
|
|
351
|
+
s = strata.setdefault(k, {"picked": 0, "missing_fields": {},
|
|
352
|
+
"ccg_exempt": 0})
|
|
353
|
+
s["picked"] += 1
|
|
354
|
+
if it["ccg_exempt"]:
|
|
355
|
+
s["ccg_exempt"] += 1
|
|
356
|
+
for m in it["ccg_missing"]:
|
|
357
|
+
s["missing_fields"][m] = s["missing_fields"].get(m, 0) + 1
|
|
358
|
+
return {
|
|
359
|
+
"root": cg.root, "dry_run": True, "readonly": True,
|
|
360
|
+
"action": "refine", "op": "maintain",
|
|
361
|
+
"prefix": prefix, "seed": seed,
|
|
362
|
+
"requested": n if n is not None else (len(sample) if ids else SAMPLE_N),
|
|
363
|
+
"pool": real_pool, "skipped_protected": skipped,
|
|
364
|
+
"sampled": len(items), "families": families,
|
|
365
|
+
"sample": sample, "sample_sha": _sha(*sample)[:16],
|
|
366
|
+
"strata": strata, "items": items,
|
|
367
|
+
"worklist": items, "spec": SPEC,
|
|
368
|
+
"preview": {"clusters": pv["clusters"], "candidates": pv["candidates"],
|
|
369
|
+
"min_cluster": pv["min_cluster"],
|
|
370
|
+
"min_jaccard": pv["min_jaccard"],
|
|
371
|
+
"skipped_no_condition": pv["skipped_no_condition"],
|
|
372
|
+
"candidates_generic_only": pv["candidates_generic_only"],
|
|
373
|
+
"sample_adequacy": pv["sample_adequacy"],
|
|
374
|
+
"df_basis": pv["df_basis"],
|
|
375
|
+
"generic_df_threshold": pv["generic_df_threshold"]},
|
|
376
|
+
"gate_rule": SPEC["gate"],
|
|
377
|
+
"note": ("抽检工单(只读):供人工核对提炼口径;核对通过前不得扩批。"
|
|
378
|
+
"行 `apply=true` 只落抽检留痕,不改任何节点。"),
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
# 生效条件:x 转为 cg;target 为 None 取 CALIBRATE_TARGET、否则 int(target),cap 为 None 取 CALIBRATE_CAP、否则 int(cap),初始 n=min(SAMPLE_N if n0 is None else int(n0), cap);循环以 n 调 preview(cg, n=n, seed=seed, prefix=prefix, min_jaccard=min_jaccard, min_cluster=min_cluster) 并记录 step,直到 pv["clusters"] >= target 则 chosen=step 并 break,或 n >= min(cap, pool or cap) 则 break;chosen 为 None 时 blocked=True 并按 steps 汇总 recommend,否则 recommend 基于 chosen;返回只读校准报告;
|
|
383
|
+
def calibrate(x, target=None, cap=None, n0=None, seed=None, prefix=None,
|
|
384
|
+
min_jaccard=None, min_cluster=None) -> dict:
|
|
385
|
+
"""样本量校准(只读):20 条起步有界爬坡,直到产出 `target` 个候选或触顶。
|
|
386
|
+
|
|
387
|
+
目的是回答「20 条工单为什么交不出候选」,并给出**有界**的替代样本量;
|
|
388
|
+
若爬坡到顶仍无候选、或候选共性条件全为高 df 模板词,则如实报
|
|
389
|
+
`blocked` 并指出真正的瓶颈是**提炼口径**而非样本量——不得据此扩批。
|
|
390
|
+
"""
|
|
391
|
+
cg = _as_cg(x)
|
|
392
|
+
target = CALIBRATE_TARGET if target is None else int(target)
|
|
393
|
+
cap = CALIBRATE_CAP if cap is None else int(cap)
|
|
394
|
+
n = min(SAMPLE_N if n0 is None else int(n0), cap)
|
|
395
|
+
steps, chosen, pool = [], None, None
|
|
396
|
+
while True:
|
|
397
|
+
pv = preview(cg, n=n, seed=seed, prefix=prefix,
|
|
398
|
+
min_jaccard=min_jaccard, min_cluster=min_cluster)
|
|
399
|
+
pool = pv["pool"]
|
|
400
|
+
step = {"n": n, "sampled": pv["sampled"], "clusters": pv["clusters"],
|
|
401
|
+
"candidates_generic_only": pv["candidates_generic_only"],
|
|
402
|
+
"discriminating_terms": sorted(
|
|
403
|
+
{t for c in pv["candidates"] for t in c["discriminating"]})[:12]}
|
|
404
|
+
steps.append(step)
|
|
405
|
+
if pv["clusters"] >= target:
|
|
406
|
+
chosen = step
|
|
407
|
+
break
|
|
408
|
+
top = min(cap, pool or cap)
|
|
409
|
+
if n >= top:
|
|
410
|
+
break
|
|
411
|
+
n = min(n * 2, top)
|
|
412
|
+
blocked = chosen is None
|
|
413
|
+
saw_clusters = any(s["clusters"] for s in steps)
|
|
414
|
+
total_c = sum(s["clusters"] for s in steps)
|
|
415
|
+
gen_only = sum(s["candidates_generic_only"] for s in steps)
|
|
416
|
+
if not blocked:
|
|
417
|
+
rec = ("抽检样本量校准为 n=%d(产出 %d 个候选);仍须人工核对提炼口径,"
|
|
418
|
+
"核对通过前不得扩批" % (chosen["n"], chosen["clusters"]))
|
|
419
|
+
else:
|
|
420
|
+
parts = ["爬坡至 cap=%d 仍只产出 %d 个候选(< target=%d),样本量不足以"
|
|
421
|
+
"直接支撑抽检" % (cap, steps[-1]["clusters"], target)]
|
|
422
|
+
if saw_clusters:
|
|
423
|
+
if gen_only >= total_c:
|
|
424
|
+
parts.append("且已产出候选的共性条件**全部**为高 df 模板词(无区分性)")
|
|
425
|
+
else:
|
|
426
|
+
parts.append("且候选中 %d/%d 的共性条件全为高 df 模板词,"
|
|
427
|
+
"其余『区分性』项形如整行条件文本(含时间戳),"
|
|
428
|
+
"属归一化缺失导致的伪区分性" % (gen_only, total_c))
|
|
429
|
+
parts.append("须先修正提炼口径(剔除模板词 + 归一化/裁剪整行文本)"
|
|
430
|
+
"或以「候选」为抽检单位重新校准;当前状态不得扩批")
|
|
431
|
+
rec = ";".join(parts)
|
|
432
|
+
return {"ok": True, "action": "refine_calibrate", "op": "maintain",
|
|
433
|
+
"root": cg.root, "dry_run": True, "readonly": True,
|
|
434
|
+
"target": target, "cap": cap, "pool": pool,
|
|
435
|
+
"steps": steps, "chosen": chosen, "blocked": blocked,
|
|
436
|
+
"sample_adequacy": "ok" if not blocked else "insufficient",
|
|
437
|
+
"recommend": rec, "note": "只读校准:未写盘、未改任何节点。"}
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
# ---- 留痕与扩批闸门 ------------------------------------------------------
|
|
441
|
+
|
|
442
|
+
# 生效条件:candidates 中取 concept_id 为真值的 id 列表;verdicts 中为 dict 且 concept_id 非空且首次出现的条目计入 reviewed 并累加其中 faithful/added_info 的真值计数;ids 非空时按 reviewed < len(ids) → awaiting_human_review、added 非零 → added_info_detected、faithful_rate < GATE_MIN_PASS_RATE → faithful_rate_below_gate、否则 ok 且 expand_allowed=True;ids 为空时 reason="no_candidates" 且 expand_allowed=False;
|
|
443
|
+
def _verdict_stats(verdicts, candidates) -> dict:
|
|
444
|
+
ids = [c.get("concept_id") for c in candidates if c.get("concept_id")]
|
|
445
|
+
seen, faithful, added = set(), 0, 0
|
|
446
|
+
for v in verdicts or []:
|
|
447
|
+
if not isinstance(v, dict):
|
|
448
|
+
continue
|
|
449
|
+
cid = v.get("concept_id")
|
|
450
|
+
if not cid or cid in seen:
|
|
451
|
+
continue
|
|
452
|
+
seen.add(cid)
|
|
453
|
+
faithful += 1 if v.get("faithful") else 0
|
|
454
|
+
added += 1 if v.get("added_info") else 0
|
|
455
|
+
reviewed = len(seen)
|
|
456
|
+
rate = (faithful / float(reviewed)) if reviewed else 0.0
|
|
457
|
+
expand, reason = False, "no_candidates"
|
|
458
|
+
if ids:
|
|
459
|
+
if reviewed < len(ids):
|
|
460
|
+
reason = "awaiting_human_review"
|
|
461
|
+
elif added:
|
|
462
|
+
reason = "added_info_detected"
|
|
463
|
+
elif rate < GATE_MIN_PASS_RATE:
|
|
464
|
+
reason = "faithful_rate_below_gate"
|
|
465
|
+
else:
|
|
466
|
+
reason = "ok"
|
|
467
|
+
expand = True
|
|
468
|
+
return {"candidates": len(ids), "reviewed": reviewed,
|
|
469
|
+
"faithful": faithful, "added_info": added,
|
|
470
|
+
"faithful_rate": round(rate, 4),
|
|
471
|
+
"min_pass_rate": GATE_MIN_PASS_RATE,
|
|
472
|
+
"expand_allowed": expand, "reason": reason}
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
# 生效条件:x 转为 cg;以 ids/n/seed/prefix/min_jaccard/min_cluster 调 plan 得到 p,batch 假值(含 None/空串)回落当前时间字符串;从 p["preview"]["candidates"] 与 verdicts 经 _verdict_stats 得 stats;把含 batch/actor/prefix/seed/sample/verdicts/gate/note 的 record 追加写入 _log_path(cg),返回 rep;
|
|
476
|
+
def apply(x, batch=None, actor=None, verdicts=None, note=None,
|
|
477
|
+
ids=None, n=None, seed=None, prefix=None,
|
|
478
|
+
min_jaccard=None, min_cluster=None) -> dict:
|
|
479
|
+
"""落抽检批次 + 人工裁决到 `_refine.jsonl`。**不改写任何节点**。"""
|
|
480
|
+
cg = _as_cg(x)
|
|
481
|
+
p = plan(cg, ids=ids, n=n, seed=seed, prefix=prefix,
|
|
482
|
+
min_jaccard=min_jaccard, min_cluster=min_cluster)
|
|
483
|
+
batch = batch or time.strftime("%Y%m%d-%H%M%S")
|
|
484
|
+
cands = p["preview"]["candidates"]
|
|
485
|
+
stats = _verdict_stats(verdicts, cands)
|
|
486
|
+
record = {
|
|
487
|
+
"t": time.time(), "action": "refine", "batch": batch, "actor": actor,
|
|
488
|
+
"prefix": p["prefix"], "seed": p["seed"], "requested": p["requested"],
|
|
489
|
+
"pool": p["pool"], "sampled": p["sampled"],
|
|
490
|
+
"sample": p["sample"], "sample_sha": p["sample_sha"],
|
|
491
|
+
"sample_adequacy": p["preview"]["sample_adequacy"],
|
|
492
|
+
"min_cluster": p["preview"].get("min_cluster"),
|
|
493
|
+
"candidates": [{"concept_id": c["concept_id"], "members": c["members"],
|
|
494
|
+
"common_conditions": c["common_conditions"],
|
|
495
|
+
"grounding_gap": c["grounding_gap"],
|
|
496
|
+
"discriminating": c["discriminating"]}
|
|
497
|
+
for c in cands],
|
|
498
|
+
"verdicts": list(verdicts or []), "gate": stats, "note": note,
|
|
499
|
+
}
|
|
500
|
+
append_jsonl(_log_path(cg), record)
|
|
501
|
+
rep = {"ok": True, "action": "refine", "op": "maintain", "batch": batch,
|
|
502
|
+
"root": cg.root, "sampled": record["sampled"],
|
|
503
|
+
"candidates": stats["candidates"], "reviewed": stats["reviewed"],
|
|
504
|
+
"gate": stats, "log": REFINE_LOG,
|
|
505
|
+
"note": ("抽检留痕已落盘(未改任何节点);"
|
|
506
|
+
+ ("闸门放行扩批(仍需另开 induce 批次并留痕)"
|
|
507
|
+
if stats["expand_allowed"] else
|
|
508
|
+
"闸门未放行:" + stats["reason"]))}
|
|
509
|
+
return rep
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
# 生效条件:x 转为 cg;batch 为真值时只保留 _read_log(cg) 中 batch 字段相等的记录,过滤后为空时返回 batches=0、expand_allowed=False、reason="no_batch";否则取最后一条记录,以其 verdicts 与 candidates(假值转空列表)经 _verdict_stats 复算并返回 expand_allowed/reason 等;
|
|
513
|
+
def gate(x, batch=None) -> dict:
|
|
514
|
+
"""扩批闸门:读留痕复算通过率(不写盘)。"""
|
|
515
|
+
cg = _as_cg(x)
|
|
516
|
+
recs = _read_log(cg)
|
|
517
|
+
if batch:
|
|
518
|
+
recs = [r for r in recs if r.get("batch") == batch]
|
|
519
|
+
if not recs:
|
|
520
|
+
return {"ok": True, "action": "refine_gate", "op": "maintain",
|
|
521
|
+
"root": cg.root, "batch": batch, "batches": 0,
|
|
522
|
+
"expand_allowed": False, "reason": "no_batch"}
|
|
523
|
+
rec = recs[-1]
|
|
524
|
+
stats = _verdict_stats(rec.get("verdicts"), rec.get("candidates") or [])
|
|
525
|
+
return {"ok": True, "action": "refine_gate", "op": "maintain",
|
|
526
|
+
"root": cg.root, "batch": rec.get("batch"),
|
|
527
|
+
"batches": len({r.get("batch") for r in recs}),
|
|
528
|
+
"sampled": rec.get("sampled"),
|
|
529
|
+
"sample_adequacy": rec.get("sample_adequacy"),
|
|
530
|
+
"gate": stats, "expand_allowed": stats["expand_allowed"],
|
|
531
|
+
"reason": stats["reason"],
|
|
532
|
+
"note": ("闸门放行:可另开 induce 批次扩大提炼规模并逐批留痕"
|
|
533
|
+
if stats["expand_allowed"] else
|
|
534
|
+
"闸门未放行,禁止扩大批次;不得盲跑全量。")}
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
# 生效条件:batch 为真值时只保留该批次记录,total 先记为过滤后条数;limit 非 None 且 >=0 时以 recs[-int(limit):] 截尾(limit=0 因 [-0:] 等价 [0:] 仍返回全部记录),limit 为 None 或负数时不截断;
|
|
538
|
+
def history(x, limit=100, batch=None) -> dict:
|
|
539
|
+
cg = _as_cg(x)
|
|
540
|
+
recs = _read_log(cg)
|
|
541
|
+
if batch:
|
|
542
|
+
recs = [r for r in recs if r.get("batch") == batch]
|
|
543
|
+
total = len(recs)
|
|
544
|
+
if limit is not None and limit >= 0:
|
|
545
|
+
recs = recs[-int(limit):]
|
|
546
|
+
return {"root": cg.root, "total": total, "returned": len(recs),
|
|
547
|
+
"action": "refine", "records": recs}
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
# ---- CLI(真实库抽检/预演用;MCP 侧走 maintain action) -------------------
|
|
551
|
+
|
|
552
|
+
# 生效条件:解析 argv(None 时由 argparse 取 sys.argv)得到 root/n/seed/prefix/min-jaccard/min-cluster/report 等参数;a.calibrate 为真时调 calibrate 并打印 target/cap/pool/blocked/sample_adequacy/recommend 及每步摘要;否则 a.preview 为真选 preview、为假选 plan,调用后打印 thin 摘要与候选并返回 0;
|
|
553
|
+
def _main(argv=None) -> int:
|
|
554
|
+
import argparse
|
|
555
|
+
import json
|
|
556
|
+
ap = argparse.ArgumentParser(description="G6 提炼抽检(默认只预演,不写盘)")
|
|
557
|
+
ap.add_argument("root", help="认知图库根")
|
|
558
|
+
ap.add_argument("--n", type=int, default=SAMPLE_N, help="抽检条数(默认 20)")
|
|
559
|
+
ap.add_argument("--seed", default=SAMPLE_SEED, help="抽样种子(同 seed 同样本)")
|
|
560
|
+
ap.add_argument("--prefix", default=PREFIX_DEFAULT, help="id 前缀(默认 node_)")
|
|
561
|
+
ap.add_argument("--min-jaccard", type=float, default=None)
|
|
562
|
+
ap.add_argument("--min-cluster", type=int, default=None)
|
|
563
|
+
ap.add_argument("--preview", action="store_true", help="只出预演(不出工单正文)")
|
|
564
|
+
ap.add_argument("--calibrate", action="store_true",
|
|
565
|
+
help="只做样本量校准(20 条起步有界爬坡)")
|
|
566
|
+
ap.add_argument("--report", default=None, help="把汇总 JSON 另存一份")
|
|
567
|
+
a = ap.parse_args(argv)
|
|
568
|
+
if a.calibrate:
|
|
569
|
+
rep = calibrate(a.root, seed=a.seed, prefix=a.prefix,
|
|
570
|
+
min_jaccard=a.min_jaccard, min_cluster=a.min_cluster)
|
|
571
|
+
if a.report:
|
|
572
|
+
with open(a.report, "w", encoding="utf-8") as f:
|
|
573
|
+
json.dump(rep, f, ensure_ascii=False, indent=2)
|
|
574
|
+
print(json.dumps({k: rep[k] for k in
|
|
575
|
+
("target", "cap", "pool", "blocked",
|
|
576
|
+
"sample_adequacy", "recommend")},
|
|
577
|
+
ensure_ascii=False, indent=2))
|
|
578
|
+
for s in rep["steps"]:
|
|
579
|
+
print(" - n=%4d clusters=%2d generic_only=%2d discriminating=%s"
|
|
580
|
+
% (s["n"], s["clusters"], s["candidates_generic_only"],
|
|
581
|
+
s["discriminating_terms"][:6]))
|
|
582
|
+
return 0
|
|
583
|
+
fn = preview if a.preview else plan
|
|
584
|
+
rep = fn(a.root, n=a.n, seed=a.seed, prefix=a.prefix,
|
|
585
|
+
min_jaccard=a.min_jaccard, min_cluster=a.min_cluster)
|
|
586
|
+
if a.report:
|
|
587
|
+
with open(a.report, "w", encoding="utf-8") as f:
|
|
588
|
+
json.dump(rep, f, ensure_ascii=False, indent=2)
|
|
589
|
+
pv = rep.get("preview") if "preview" in rep else rep
|
|
590
|
+
thin = {k: rep.get(k) for k in ("action", "prefix", "seed", "requested",
|
|
591
|
+
"pool", "sampled", "families")}
|
|
592
|
+
thin.update({k: pv.get(k) for k in ("clusters", "sample_adequacy",
|
|
593
|
+
"candidates_generic_only")})
|
|
594
|
+
print(json.dumps(thin, ensure_ascii=False, indent=2))
|
|
595
|
+
for c in (pv.get("candidates") or []):
|
|
596
|
+
print(" - %s size=%d common=%s generic=%s gap=%s"
|
|
597
|
+
% (c["concept_id"], c["size"], c["common_conditions"][:4],
|
|
598
|
+
[g["term"] for g in c.get("generic_conditions", [])],
|
|
599
|
+
c.get("grounding_gap")))
|
|
600
|
+
return 0
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
if __name__ == "__main__":
|
|
604
|
+
import sys
|
|
605
605
|
sys.exit(_main())
|