argo-search 1.0.1 → 2.9.7
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/LICENSE +201 -21
- package/README.en.md +822 -0
- package/README.es.md +864 -0
- package/README.ja.md +864 -0
- package/README.ko.md +864 -0
- package/README.md +867 -320
- package/SKILL.md +135 -0
- package/assets/readme/hero.svg +121 -0
- package/assets/readme/made-with-beautify.svg +10 -0
- package/assets/readme/proof-routes.svg +73 -0
- package/assets/readme/settings-keys.png +0 -0
- package/assets/readme/settings-tools.png +0 -0
- package/assets/readme/why-better.svg +104 -0
- package/assets/readme/workflow.svg +83 -0
- package/backends/domain_profiles.json +2308 -96
- package/backends/engine_registry.yaml +3314 -503
- package/backends/query_synonyms_cn.json +35 -0
- package/backends/quota_profiles.json +2409 -161
- package/backends/source_types_cn.json +83 -0
- package/backends/text_lexicon.json +44 -0
- package/bin/argo +703 -0
- package/bin/argo.js +71 -39
- package/config.yaml +4976 -363
- package/cordis.patch.yml +21 -0
- package/data/countries.json +40 -0
- package/data/lang_matrix/lang_capability.json +842 -0
- package/data/lang_matrix/matrix_2026-09-10.json +11921 -0
- package/data/regions_cn.json +1 -0
- package/docs/ADDING_NEW_ENGINE.md +184 -0
- package/docs/ARGO_INTRO.md +124 -0
- package/docs/DATA_SOURCE_GAPS.md +81 -0
- package/docs/DESIGN_LOCAL_DATA_FUSION.md +90 -0
- package/docs/ENGINE_CATALOG.md +564 -0
- package/docs/MCP_SETUP.md +106 -0
- package/docs/PLATFORM_PATHS.md +111 -0
- package/docs/RELEASE_NOTES_v2.8.0.md +71 -0
- package/docs/RELEASE_NOTES_v2.8.2.md +63 -0
- package/docs/RELEASE_NOTES_v2.8.3.md +55 -0
- package/docs/RELEASE_NOTES_v2.8.4.md +79 -0
- package/docs/RELEASE_NOTES_v2.8.5.md +102 -0
- package/docs/RELEASE_NOTES_v2.8.6.md +66 -0
- package/docs/RELEASE_NOTES_v2.8.7.md +65 -0
- package/docs/RELEASE_NOTES_v2.8.8.md +67 -0
- package/docs/RELEASE_NOTES_v2.8.9.md +67 -0
- package/docs/RELEASE_NOTES_v2.9.0.md +64 -0
- package/docs/SEARCH_ARCHIVE.md +115 -0
- package/docs/assets/architecture.svg +104 -0
- package/docs/assets/hero.svg +98 -0
- package/docs/engines/bangumi.md +27 -0
- package/docs/engines/biorxiv.md +27 -0
- package/docs/engines/clawhub.md +27 -0
- package/docs/engines/crt_sh.md +27 -0
- package/docs/engines/datacite.md +27 -0
- package/docs/engines/deps_dev.md +27 -0
- package/docs/engines/douban_movie.md +27 -0
- package/docs/engines/endoflife.md +27 -0
- package/docs/engines/flk_law.md +27 -0
- package/docs/engines/fxtwitter.md +27 -0
- package/docs/engines/google_news.md +27 -0
- package/docs/engines/gov_regulations.md +27 -0
- package/docs/engines/guqian.md +27 -0
- package/docs/engines/hackernews.md +27 -0
- package/docs/engines/iplant.md +27 -0
- package/docs/engines/keenable.md +27 -0
- package/docs/engines/met_museum.md +27 -0
- package/docs/engines/nasa_images.md +27 -0
- package/docs/engines/netease_music.md +27 -0
- package/docs/engines/npm.md +27 -0
- package/docs/engines/nvd.md +27 -0
- package/docs/engines/openreview.md +27 -0
- package/docs/engines/openstd.md +27 -0
- package/docs/engines/parallel_free.md +27 -0
- package/docs/engines/people_daily.md +27 -0
- package/docs/engines/realtime_index.md +27 -0
- package/docs/engines/sec_edgar.md +27 -0
- package/docs/engines/seltz.md +27 -0
- package/docs/engines/skillsmp.md +27 -0
- package/docs/engines/sspai.md +27 -0
- package/docs/engines/std_samr.md +27 -0
- package/docs/engines/train.md +27 -0
- package/docs/engines/un_comtrade.md +27 -0
- package/docs/engines/wikisource.md +27 -0
- package/docs/engines/zdic.md +27 -0
- package/docs/engines/zenodo.md +27 -0
- package/docs/engines/zhihu_hot_app.md +27 -0
- package/docs//344/270/272/344/273/200/344/271/210/351/200/211/346/213/251argo.md +176 -0
- package/engines/README.md +10 -0
- package/engines/_template_cli.yaml +39 -0
- package/engines/_template_http.yaml +37 -0
- package/engines/plugins/_template_plugin.py +50 -0
- package/engines/specs/_cisa_kev.yaml +54 -0
- package/engines/specs/_osv.yaml +63 -0
- package/engines/specs/aljazeera.yaml +43 -0
- package/engines/specs/antara.yaml +43 -0
- package/engines/specs/artic.yaml +20 -0
- package/engines/specs/aviation_weather.yaml +39 -0
- package/engines/specs/bangumi.yaml +21 -0
- package/engines/specs/biorxiv.yaml +24 -0
- package/engines/specs/carbon_intensity.yaml +32 -0
- package/engines/specs/cinii.yaml +44 -0
- package/engines/specs/clawhub.yaml +49 -0
- package/engines/specs/cleveland.yaml +19 -0
- package/engines/specs/cn_ai_news.yaml +43 -0
- package/engines/specs/core.yaml +35 -0
- package/engines/specs/crt_sh.yaml +38 -0
- package/engines/specs/ct24.yaml +43 -0
- package/engines/specs/datacite.yaml +27 -0
- package/engines/specs/datagov_uk.yaml +41 -0
- package/engines/specs/ddgs_images.yaml +45 -0
- package/engines/specs/deepseek_search.yaml +74 -0
- package/engines/specs/deezer.yaml +20 -0
- package/engines/specs/deps_dev.yaml +21 -0
- package/engines/specs/digup.yaml +36 -0
- package/engines/specs/doi.yaml +39 -0
- package/engines/specs/douban_movie.yaml +22 -0
- package/engines/specs/dw_news.yaml +47 -0
- package/engines/specs/egov_law.yaml +21 -0
- package/engines/specs/elpais.yaml +43 -0
- package/engines/specs/endoflife.yaml +21 -0
- package/engines/specs/energy_charts.yaml +21 -0
- package/engines/specs/factcheck_org.yaml +52 -0
- package/engines/specs/faz.yaml +43 -0
- package/engines/specs/federal_register.yaml +57 -0
- package/engines/specs/firecrawl.yaml +53 -0
- package/engines/specs/flk_law.yaml +24 -0
- package/engines/specs/folha.yaml +43 -0
- package/engines/specs/france24.yaml +44 -0
- package/engines/specs/full_fact.yaml +47 -0
- package/engines/specs/fxtwitter.yaml +31 -0
- package/engines/specs/gbfs_nyc.yaml +32 -0
- package/engines/specs/gdacs.yaml +21 -0
- package/engines/specs/gdelt.yaml +64 -0
- package/engines/specs/google_news.yaml +22 -0
- package/engines/specs/gov_regulations.yaml +38 -0
- package/engines/specs/guardian_rss.yaml +59 -0
- package/engines/specs/hurriyet.yaml +43 -0
- package/engines/specs/iplant.yaml +25 -0
- package/engines/specs/jikan.yaml +24 -0
- package/engines/specs/jolpica.yaml +20 -0
- package/engines/specs/k10plus.yaml +20 -0
- package/engines/specs/keenable.yaml +44 -0
- package/engines/specs/lefigaro.yaml +43 -0
- package/engines/specs/listenbrainz.yaml +19 -0
- package/engines/specs/matichon.yaml +43 -0
- package/engines/specs/met_museum.yaml +21 -0
- package/engines/specs/nasa_images.yaml +45 -0
- package/engines/specs/netease_music.yaml +38 -0
- package/engines/specs/nhk.yaml +43 -0
- package/engines/specs/nhtsa_vpic.yaml +20 -0
- package/engines/specs/noaa_swpc.yaml +20 -0
- package/engines/specs/nvd.yaml +38 -0
- package/engines/specs/obis.yaml +20 -0
- package/engines/specs/opencitations.yaml +52 -0
- package/engines/specs/openf1.yaml +21 -0
- package/engines/specs/openfoodfacts.yaml +45 -0
- package/engines/specs/openligadb.yaml +20 -0
- package/engines/specs/openreview.yaml +37 -0
- package/engines/specs/openstd.yaml +21 -0
- package/engines/specs/people_daily.yaml +22 -0
- package/engines/specs/realtime_index.yaml +34 -0
- package/engines/specs/ror.yaml +20 -0
- package/engines/specs/satnogs.yaml +20 -0
- package/engines/specs/sec_edgar.yaml +46 -0
- package/engines/specs/skillsmp.yaml +50 -0
- package/engines/specs/sogou_weixin_browser.yaml +30 -0
- package/engines/specs/soilgrids.yaml +25 -0
- package/engines/specs/sspai.yaml +36 -0
- package/engines/specs/stackexchange.yaml +46 -0
- package/engines/specs/std_samr.yaml +21 -0
- package/engines/specs/tass.yaml +43 -0
- package/engines/specs/tle_mirror.yaml +21 -0
- package/engines/specs/train.yaml +37 -0
- package/engines/specs/tvmaze.yaml +20 -0
- package/engines/specs/twitter_syndication.yaml +31 -0
- package/engines/specs/un_comtrade.yaml +23 -0
- package/engines/specs/unpaywall.yaml +54 -0
- package/engines/specs/vnpress.yaml +43 -0
- package/engines/specs/weather.yaml +32 -0
- package/engines/specs/who_don.yaml +21 -0
- package/engines/specs/who_gho.yaml +20 -0
- package/engines/specs/wikimedia_commons.yaml +34 -0
- package/engines/specs/wikisource.yaml +22 -0
- package/engines/specs/worms.yaml +19 -0
- package/engines/specs/x_trending.yaml +34 -0
- package/engines/specs/yna.yaml +43 -0
- package/engines/specs/ynet.yaml +43 -0
- package/engines/specs/zbmath.yaml +40 -0
- package/engines/specs/zdic.yaml +22 -0
- package/engines/specs/zenodo.yaml +27 -0
- package/engines/specs/zhihu_hot_app.yaml +35 -0
- package/mcp/clients.yaml +74 -0
- package/package.json +51 -12
- package/packages/dsh-plugin/README.md +134 -0
- package/packages/dsh-plugin/cordis.patch.yml +48 -0
- package/packages/dsh-plugin/dsh/client.js +1244 -0
- package/packages/dsh-plugin/dsh/index.js +2290 -0
- package/packages/dsh-plugin/dsh/native-tools.mjs +743 -0
- package/packages/dsh-plugin/package.json +38 -0
- package/packages/dsh-plugin/test/client.test.js +232 -0
- package/packages/dsh-plugin/test/config.test.js +30 -0
- package/packages/dsh-plugin/test/index.test.js +1338 -0
- package/references/query-templates.md +96 -0
- package/references/query-templates.yaml +118 -0
- package/scripts/ab_eval.py +219 -0
- package/scripts/ab_eval_p0p1.py +266 -0
- package/scripts/adaptive.py +271 -70
- package/scripts/answer.py +154 -0
- package/scripts/archive_run.py +591 -0
- package/scripts/argo_engine_registry.py +172 -0
- package/scripts/argo_paths.py +728 -0
- package/scripts/article.py +136 -0
- package/scripts/arxiv_source.py +201 -0
- package/scripts/badcase_ledger.py +252 -0
- package/scripts/batch_probe.py +241 -0
- package/scripts/benchmark.py +19 -0
- package/scripts/bounded_run.py +144 -0
- package/scripts/browser_auth.py +323 -0
- package/scripts/burstiness_detector.py +147 -0
- package/scripts/cache.py +1078 -67
- package/scripts/cache_guard.py +271 -0
- package/scripts/cache_key_vdom.py +56 -0
- package/scripts/cache_similarity.py +170 -0
- package/scripts/caliber_isolation.py +195 -0
- package/scripts/calibrate_lowquality.py +210 -0
- package/scripts/candidate_envelope.py +366 -0
- package/scripts/chrome_cdp.py +871 -0
- package/scripts/circuit_breaker.py +389 -0
- package/scripts/citations.py +334 -0
- package/scripts/cite_cli.py +59 -0
- package/scripts/cjk_tokens.py +95 -0
- package/scripts/clarify.py +47 -24
- package/scripts/cli_io.py +240 -0
- package/scripts/config.py +683 -45
- package/scripts/config_cache.py +402 -0
- package/scripts/conn_pool.py +220 -0
- package/scripts/content_security.py +685 -0
- package/scripts/content_signals.py +730 -0
- package/scripts/crawl.py +74 -35
- package/scripts/daemon.py +866 -0
- package/scripts/digup_bridge.py +214 -0
- package/scripts/engine_admission.py +366 -0
- package/scripts/engine_dispatch.py +1019 -0
- package/scripts/engine_env.py +534 -0
- package/scripts/engine_failure.py +411 -0
- package/scripts/engine_families.py +732 -0
- package/scripts/engine_policy.py +218 -0
- package/scripts/engine_requires.py +190 -0
- package/scripts/engine_status.py +322 -0
- package/scripts/engine_validate.py +638 -0
- package/scripts/engines.py +778 -294
- package/scripts/engines_base.py +1521 -0
- package/scripts/engines_builders.py +298 -0
- package/scripts/engines_builders_batch10.py +159 -0
- package/scripts/engines_builders_batch11.py +174 -0
- package/scripts/engines_builders_batch12.py +103 -0
- package/scripts/engines_builders_batch13.py +340 -0
- package/scripts/engines_builders_batch9.py +1468 -0
- package/scripts/engines_builders_cn.py +2283 -0
- package/scripts/engines_builders_data.py +2923 -0
- package/scripts/engines_builders_data_macro.py +775 -0
- package/scripts/engines_builders_feeds.py +255 -0
- package/scripts/engines_builders_intl.py +1239 -0
- package/scripts/engines_builders_search.py +440 -0
- package/scripts/engines_builders_tech.py +1360 -0
- package/scripts/evidence.py +588 -162
- package/scripts/evidence_loop.py +592 -0
- package/scripts/evidence_tier.py +298 -0
- package/scripts/except_sets.py +25 -0
- package/scripts/extract.py +55 -16
- package/scripts/fact_align.py +207 -0
- package/scripts/fact_crosscheck.py +230 -0
- package/scripts/fetch.py +25 -12
- package/scripts/fetch_browser.py +87 -0
- package/scripts/fetch_jsrun.py +122 -0
- package/scripts/fetch_quality.py +225 -0
- package/scripts/fetch_render_tinyfish.py +117 -0
- package/scripts/fetch_v3.py +1779 -0
- package/scripts/focus_extract.py +350 -0
- package/scripts/fulltext_store.py +169 -0
- package/scripts/fxtwitter_api.py +325 -0
- package/scripts/gen_engine_catalog.py +364 -0
- package/scripts/gen_native_tools.py +91 -0
- package/scripts/gen_routing_snapshot.py +184 -0
- package/scripts/health_check.py +140 -402
- package/scripts/health_probe.py +23 -8
- package/scripts/hot_state.py +134 -0
- package/scripts/http_client.py +1053 -0
- package/scripts/image_ops.py +430 -0
- package/scripts/image_vision/vision_probe +0 -0
- package/scripts/image_vision/vision_probe.swift +126 -0
- package/scripts/install.ps1 +148 -0
- package/scripts/install.sh +206 -0
- package/scripts/job.py +1186 -0
- package/scripts/jump_resolver.py +235 -0
- package/scripts/lang_capability.py +150 -0
- package/scripts/lang_detect.py +289 -0
- package/scripts/lang_matrix_probe.py +329 -0
- package/scripts/lang_pref.py +311 -0
- package/scripts/link_source.py +340 -0
- package/scripts/local_engines.py +321 -0
- package/scripts/local_image.py +970 -0
- package/scripts/local_seek.py +366 -0
- package/scripts/macro_countries.py +82 -0
- package/scripts/matrix_search_eval.py +1105 -0
- package/scripts/mcp_diag.py +8 -3
- package/scripts/mcp_handlers.py +1074 -0
- package/scripts/mcp_handlers_surface.py +157 -0
- package/scripts/mcp_launch.sh +122 -0
- package/scripts/mcp_server.py +118 -417
- package/scripts/mcp_setup.py +407 -0
- package/scripts/mcp_tools.py +330 -0
- package/scripts/mcp_transport.py +191 -0
- package/scripts/min_version_scan.py +335 -0
- package/scripts/net_proxy.py +258 -0
- package/scripts/network_aware.py +152 -0
- package/scripts/paper.py +274 -0
- package/scripts/pdf_extract.py +518 -0
- package/scripts/phrase_filter.py +66 -0
- package/scripts/plan.py +422 -0
- package/scripts/postprocess_signals.py +150 -0
- package/scripts/pre-commit +48 -0
- package/scripts/query_enhance.py +157 -0
- package/scripts/query_operators.py +196 -0
- package/scripts/query_rewriter.py +116 -7
- package/scripts/query_signals.py +300 -0
- package/scripts/query_templates.py +167 -0
- package/scripts/query_understanding.py +411 -0
- package/scripts/query_variants.py +298 -0
- package/scripts/quota.py +488 -76
- package/scripts/rank_signals.py +775 -0
- package/scripts/ranking_eval.py +319 -0
- package/scripts/readability_extract.py +433 -0
- package/scripts/recompute.py +415 -0
- package/scripts/recovery.py +616 -0
- package/scripts/recovery_l0.py +160 -0
- package/scripts/redskill/redskill_engine.py +332 -0
- package/scripts/regression_p0p1.py +318 -0
- package/scripts/replay_eval.py +640 -0
- package/scripts/rerank_tfidf.py +225 -0
- package/scripts/research.py +591 -403
- package/scripts/research_cli.py +361 -0
- package/scripts/research_dossier.py +492 -0
- package/scripts/research_expand.py +146 -0
- package/scripts/research_gates.py +272 -0
- package/scripts/research_report.py +209 -0
- package/scripts/research_strategy.py +37 -0
- package/scripts/research_work_packages.py +195 -0
- package/scripts/result_lang.py +311 -0
- package/scripts/robots_guard.py +259 -0
- package/scripts/route.py +921 -254
- package/scripts/route_bangs.py +40 -0
- package/scripts/route_cache.py +225 -0
- package/scripts/route_combo.py +910 -0
- package/scripts/route_domains.py +328 -0
- package/scripts/route_lang.py +605 -0
- package/scripts/route_log.py +63 -0
- package/scripts/route_policy.py +250 -0
- package/scripts/rrf_classic.py +66 -0
- package/scripts/screenshot.py +73 -0
- package/scripts/search.py +897 -405
- package/scripts/search_attribution.py +88 -0
- package/scripts/search_benchmark.py +407 -0
- package/scripts/search_cli.py +627 -0
- package/scripts/search_entry.py +389 -0
- package/scripts/search_output.py +606 -0
- package/scripts/search_pipeline.py +1077 -0
- package/scripts/search_rank.py +991 -0
- package/scripts/search_types.py +27 -12
- package/scripts/seek_locator.py +68 -0
- package/scripts/semantic_evidence.py +215 -0
- package/scripts/serp_guard.py +228 -0
- package/scripts/serp_kill_log.py +51 -0
- package/scripts/single_flight.py +120 -0
- package/scripts/social_engines/__init__.py +10 -0
- package/scripts/social_engines/bilibili_engine.py +131 -0
- package/scripts/social_engines/hackernews_engine.py +113 -0
- package/scripts/social_engines/reddit_engine.py +172 -0
- package/scripts/social_engines/twitter_engine.py +397 -0
- package/scripts/social_engines/v2ex_engine.py +101 -0
- package/scripts/social_engines/weibo_engine.py +128 -0
- package/scripts/social_engines/xiaohongshu_engine.py +137 -0
- package/scripts/social_engines/zhihu_engine.py +139 -0
- package/scripts/social_research.py +201 -0
- package/scripts/sogou_weixin_browser.py +318 -0
- package/scripts/source_ledger.py +249 -0
- package/scripts/stage_timing.py +58 -0
- package/scripts/stats_cli.py +95 -0
- package/scripts/straggler_policy.py +60 -0
- package/scripts/stylometry_detector.py +231 -0
- package/scripts/sync_backends.py +353 -0
- package/scripts/sync_installs.py +29 -0
- package/scripts/template_cli.py +127 -0
- package/scripts/text_lexicon.py +72 -0
- package/scripts/tfidf_router.py +109 -37
- package/scripts/time_utils.py +233 -0
- package/scripts/topic_research_profiles.py +643 -0
- package/scripts/train.py +737 -0
- package/scripts/tweet.py +596 -0
- package/scripts/url_canon.py +217 -0
- package/scripts/url_optimize.py +56 -0
- package/scripts/url_safety.py +236 -0
- package/scripts/usage_log.py +116 -0
- package/scripts/usage_log_clean.py +113 -0
- package/scripts/v2ex_nodes.py +311 -0
- package/scripts/variant_recall.py +188 -0
- package/scripts/watch.py +205 -0
- package/scripts/wechat_article.py +258 -0
- package/scripts/wide_research.py +402 -0
- package/scripts/wx.py +349 -0
- package/scripts/x_article_fetch.py +141 -0
- package/scripts/x_search.py +234 -0
- package/scripts/x_trending.py +189 -0
- package/scripts/yaml_load.py +67 -0
- package/sub-skills/ego-search/SKILL.md +253 -0
- package/sub-skills/ego-search/references/browser-runtime.md +43 -0
- package/sub-skills/ego-search/references/install.md +67 -0
- package/sub-skills/ego-search/references/original-ego-upgrade.md +51 -0
- package/sub-skills/ego-search/scripts/ego_search.py +800 -0
- package/sub-skills/ego-search/scripts/merge.py +226 -0
- package/sub-skills/ego-search/scripts/quality.py +64 -0
- package/sub-skills/ego-search/scripts/runtime.py +172 -0
- package/sub-skills/ego-search/scripts/safety.py +109 -0
- package/sub-skills/ego-search/scripts/serp_spec.py +109 -0
- package/sub-skills/ego-search/scripts/webbridge_adapter.py +374 -0
- package/sub-skills/ego-search/tests/test_query_normalize.py +68 -0
- package/sub-skills/js-run/SKILL.md +67 -0
- package/sub-skills/js-run/scripts/jsrun.py +112 -0
- package/sub-skills/js-run/scripts/shim.js +188 -0
- package/sub-skills/js-run/tests/fixtures/challenge_like.js +27 -0
- package/sub-skills/js-run/tests/test_jsrun.py +165 -0
- package/sub-skills/local-search/SKILL.md +50 -53
- package/sub-skills/local-search/bm25.py +162 -0
- package/sub-skills/local-search/config.yaml +152 -55
- package/sub-skills/local-search/engine_registry.py +91 -26
- package/sub-skills/local-search/{health_check.py → local_health_check.py} +140 -10
- package/sub-skills/local-search/local_search_adapter.py +13 -2
- package/sub-skills/local-search/parse_maps.yaml +40 -42
- package/sub-skills/local-search/search_v3.py +1182 -65
- package/sub-skills/local-search/smart_router.py +47 -7
- package/sub-skills/local-seek/SKILL.md +110 -0
- package/sub-skills/local-seek/config/domains.yaml +20 -0
- package/sub-skills/local-seek/references/auto-behaviors.md +24 -0
- package/sub-skills/local-seek/references/fd-mdfind-recipes.md +62 -0
- package/sub-skills/local-seek/references/git-integration.md +54 -0
- package/sub-skills/local-seek/references/remote-handoff.md +46 -0
- package/sub-skills/local-seek/references/rg-recipes.md +96 -0
- package/sub-skills/local-seek/references/strategies.md +52 -0
- package/sub-skills/local-seek/references/structural-search.md +59 -0
- package/sub-skills/local-seek/scripts/eval_rank.py +216 -0
- package/sub-skills/local-seek/scripts/eval_seek.py +224 -0
- package/sub-skills/local-seek/scripts/seek.py +1465 -0
- package/sub-skills/local-seek/scripts/seek_frecency.py +126 -0
- package/sub-skills/local-seek/scripts/seek_fts.py +430 -0
- package/sub-skills/local-seek/scripts/seek_rank.py +193 -0
- package/sub-skills/local-seek/tests/test_content_rank.py +222 -0
- package/sub-skills/local-seek/tests/test_determinism_20261006.py +145 -0
- package/sub-skills/local-seek/tests/test_noise_tier.py +101 -0
- package/sub-skills/local-seek/tests/test_pcre2_probe.py +136 -0
- package/sub-skills/local-seek/tests/test_rank_pinyin.py +77 -0
- package/sub-skills/local-seek/tests/test_regression_20261003.py +169 -0
- package/sub-skills/local-seek/tests/test_regression_20261006.py +146 -0
- package/sub-skills/local-seek/tests/test_seek_degradation.py +111 -0
- package/sub-skills/local-seek/tests/test_seek_fts.py +313 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""caliber_isolation.py — 口径隔离:不同口径的引擎结果不直接 merge。
|
|
3
|
+
|
|
4
|
+
同口径引擎的结果才能 merge 到同一条目,不同口径的结果按优先级并列输出。
|
|
5
|
+
口径分类表 + 分组逻辑是纯函数,与 search_rank.py 的调度/融合职责不同。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
_CALIBER_GROUPS = {
|
|
13
|
+
"financial_data": frozenset({
|
|
14
|
+
"fred", "worldbank", "eurostat", "frankfurter", "fx_rate",
|
|
15
|
+
"eastmoney", "sina_quote", "tencent_quote", "yahoo_finance",
|
|
16
|
+
"sec_companyfacts", "finviz", "un_comtrade",
|
|
17
|
+
"em_flow", "em_miaoxiang", "tencent_kline", "jin10",
|
|
18
|
+
}),
|
|
19
|
+
"encyclopedia": frozenset({
|
|
20
|
+
"wikipedia", "baidu_baike", "wikidata",
|
|
21
|
+
"wikisource", "wikimedia_commons", "dnb",
|
|
22
|
+
}),
|
|
23
|
+
"social": frozenset({
|
|
24
|
+
"twitter", "weibo", "zhihu", "zhihu_global", "zhihu_user",
|
|
25
|
+
"xiaohongshu", "wechat_sogou", "bilibili_hot", "douyin_hot",
|
|
26
|
+
"weibo_hot", "toutiao_hot", "hackernews", "reddit",
|
|
27
|
+
}),
|
|
28
|
+
"code": frozenset({
|
|
29
|
+
"github", "stackoverflow", "so", "local_gitlab", "deps_dev",
|
|
30
|
+
"endoflife", "pypi", "npm", "crates",
|
|
31
|
+
}),
|
|
32
|
+
"news": frozenset({
|
|
33
|
+
"google_news", "bing_rss", "cls_telegraph", "em_global_news",
|
|
34
|
+
"people_daily",
|
|
35
|
+
}),
|
|
36
|
+
"academic": frozenset({
|
|
37
|
+
"arxiv", "local_pubmed", "google_scholar", "semantic_scholar",
|
|
38
|
+
"crossref", "openalex", "doaj", "core", "gdacs",
|
|
39
|
+
"clinicaltrials", "openfda", "uniprot", "rcsb_pdb",
|
|
40
|
+
}),
|
|
41
|
+
"media": frozenset({
|
|
42
|
+
"imdb", "tvmaze", "bangumi", "deezer",
|
|
43
|
+
"itunes", "listenbrainz", "figshare", "met_museum",
|
|
44
|
+
}),
|
|
45
|
+
"reference": frozenset({
|
|
46
|
+
"open_library", "weread", "douban_book", "gutenberg", "rfc_editor", "courtlistener", "hal", "eu_opendata",
|
|
47
|
+
}),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# 口径优先级:数值越小优先级越高,排序时高口径的结果排在前面
|
|
51
|
+
_CALIBER_PRIORITY = {
|
|
52
|
+
"financial_data": 0,
|
|
53
|
+
"academic": 1,
|
|
54
|
+
"encyclopedia": 2,
|
|
55
|
+
"news": 3,
|
|
56
|
+
"social": 4,
|
|
57
|
+
"code": 5,
|
|
58
|
+
"media": 6,
|
|
59
|
+
"reference": 7,
|
|
60
|
+
"general": 8,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def caliber_of(engine: str) -> str:
|
|
65
|
+
"""返回引擎的口径分类,未分类的引擎归为 general。"""
|
|
66
|
+
for caliber, engines in _CALIBER_GROUPS.items():
|
|
67
|
+
if engine in engines:
|
|
68
|
+
return caliber
|
|
69
|
+
return "general"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def group_by_caliber(ranked_lists: list[list[dict[str, Any]]]) -> dict[str, list[list[dict[str, Any]]]]:
|
|
73
|
+
"""按口径分组:同口径的引擎结果才能 merge。
|
|
74
|
+
|
|
75
|
+
分组单位是**整条引擎列表**,不是单条结果。此前实现把每条结果包成
|
|
76
|
+
单元素列表,`merge_within_caliber` 里的 `enumerate(group)` 于是恒为
|
|
77
|
+
`i=0`,同一引擎的所有结果拿到相同 RRF 分——名次衰减被抹平,输出退化成
|
|
78
|
+
「按引擎权重整段拼接」(2026-10-17 实测:anysearch 5 条同分 0.017213)。
|
|
79
|
+
一个列表通常来自一个引擎;若上游混入多引擎,按口径拆开但保持原序。
|
|
80
|
+
"""
|
|
81
|
+
caliber_groups: dict[str, list[list[dict[str, Any]]]] = {}
|
|
82
|
+
for results in ranked_lists:
|
|
83
|
+
if not results:
|
|
84
|
+
continue
|
|
85
|
+
by_caliber: dict[str, list[dict[str, Any]]] = {}
|
|
86
|
+
for r in results:
|
|
87
|
+
if not isinstance(r, dict):
|
|
88
|
+
continue
|
|
89
|
+
eng = r.get("_engine") or r.get("source", "") or ""
|
|
90
|
+
by_caliber.setdefault(caliber_of(eng), []).append(r)
|
|
91
|
+
for cal, group in by_caliber.items():
|
|
92
|
+
if group:
|
|
93
|
+
caliber_groups.setdefault(cal, []).append(group)
|
|
94
|
+
return caliber_groups
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def merge_within_caliber(
|
|
98
|
+
groups: list[list[dict[str, Any]]],
|
|
99
|
+
k: int = 60,
|
|
100
|
+
weighted: bool = True,
|
|
101
|
+
lang: str | None = None,
|
|
102
|
+
_canonical_url=None,
|
|
103
|
+
_engine_weight=None,
|
|
104
|
+
) -> list[dict[str, Any]]:
|
|
105
|
+
"""同口径内做 RRF 融合。"""
|
|
106
|
+
if _canonical_url is None:
|
|
107
|
+
from url_canon import canonical_url as _canonical_url
|
|
108
|
+
if _engine_weight is None:
|
|
109
|
+
from search_rank import _engine_weight as _engine_weight
|
|
110
|
+
|
|
111
|
+
scores: dict[str, float] = {}
|
|
112
|
+
items: dict[str, dict[str, Any]] = {}
|
|
113
|
+
|
|
114
|
+
for group in groups:
|
|
115
|
+
for i, r in enumerate(group):
|
|
116
|
+
key = (
|
|
117
|
+
_canonical_url(r.get("url", ""))
|
|
118
|
+
or (f"__title__:{r.get('title', '')}" if r.get("title") else "")
|
|
119
|
+
or (f"__card__:{r.get('card_type', '')}" if r.get("card_type") else "")
|
|
120
|
+
or f"__idx__:{id(r)}"
|
|
121
|
+
)
|
|
122
|
+
w = _engine_weight(r.get("_engine") or r.get("source") or "",
|
|
123
|
+
lang=lang) if weighted else 1.0
|
|
124
|
+
scores[key] = scores.get(key, 0.0) + w / (k + i + 1)
|
|
125
|
+
eng = r.get("_engine") or r.get("source", "") or ""
|
|
126
|
+
if key not in items:
|
|
127
|
+
item = dict(r)
|
|
128
|
+
item["_rrf_score"] = 0.0
|
|
129
|
+
cons: list[str] = []
|
|
130
|
+
if eng:
|
|
131
|
+
cons.append(eng)
|
|
132
|
+
item["consensus_engines"] = cons
|
|
133
|
+
items[key] = item
|
|
134
|
+
else:
|
|
135
|
+
cur = items[key]
|
|
136
|
+
new_txt = f"{r.get('title', '')} {r.get('snippet', '')}"
|
|
137
|
+
cur_txt = f"{cur.get('title', '')} {cur.get('snippet', '')}"
|
|
138
|
+
if len(new_txt) > len(cur_txt):
|
|
139
|
+
cur["title"] = r.get("title", cur.get("title"))
|
|
140
|
+
cur["snippet"] = r.get("snippet", cur.get("snippet"))
|
|
141
|
+
sources = {cur.get("source", ""), r.get("source", "")}
|
|
142
|
+
cur["source"] = "/".join(s for s in sources if s)
|
|
143
|
+
cons = list(cur.get("consensus_engines") or [])
|
|
144
|
+
if eng and eng not in cons:
|
|
145
|
+
cons.append(eng)
|
|
146
|
+
cur["consensus_engines"] = cons
|
|
147
|
+
|
|
148
|
+
ranked = sorted(scores.items(), key=lambda x: -x[1])
|
|
149
|
+
out = []
|
|
150
|
+
for key, _ in ranked:
|
|
151
|
+
item = items[key]
|
|
152
|
+
item["_rrf_score"] = round(scores[key], 6)
|
|
153
|
+
out.append(item)
|
|
154
|
+
return out
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def merge_by_caliber(
|
|
158
|
+
ranked_lists: list[list[dict[str, Any]]],
|
|
159
|
+
k: int = 60,
|
|
160
|
+
weighted: bool = True,
|
|
161
|
+
lang: str | None = None,
|
|
162
|
+
_canonical_url=None,
|
|
163
|
+
_engine_weight=None,
|
|
164
|
+
) -> list[dict[str, Any]]:
|
|
165
|
+
"""口径隔离的 RRF 融合:同口径内 merge,不同口径按优先级并列输出。
|
|
166
|
+
|
|
167
|
+
这是 rrf_merge 的口径隔离版本,调用方(search_rank.rrf_merge)传入
|
|
168
|
+
私有函数引用,本模块不反向依赖 search_rank。
|
|
169
|
+
|
|
170
|
+
当 weighted=False 时,不分组,走全局 RRF(经典 RRF 行为)。
|
|
171
|
+
"""
|
|
172
|
+
if not weighted:
|
|
173
|
+
# 经典 RRF:不分组,但**保留每个引擎列表的独立名次**——把所有结果
|
|
174
|
+
# 拍平成一个列表会让后一个引擎的 rank 接着前一个数,名次衰减错位。
|
|
175
|
+
return merge_within_caliber(
|
|
176
|
+
ranked_lists,
|
|
177
|
+
k=k, weighted=False, lang=lang,
|
|
178
|
+
_canonical_url=_canonical_url, _engine_weight=_engine_weight,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
caliber_groups = group_by_caliber(ranked_lists)
|
|
182
|
+
|
|
183
|
+
merged_by_caliber: dict[str, list[dict[str, Any]]] = {}
|
|
184
|
+
for cal, groups in caliber_groups.items():
|
|
185
|
+
merged_by_caliber[cal] = merge_within_caliber(
|
|
186
|
+
groups, k=k, weighted=weighted, lang=lang,
|
|
187
|
+
_canonical_url=_canonical_url, _engine_weight=_engine_weight,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# 不同口径按优先级排序后并列输出
|
|
191
|
+
out = []
|
|
192
|
+
for cal in sorted(merged_by_caliber.keys(),
|
|
193
|
+
key=lambda c: _CALIBER_PRIORITY.get(c, 8)):
|
|
194
|
+
out.extend(merged_by_caliber[cal])
|
|
195
|
+
return out
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""calibrate_lowquality.py — 低质内容信号的区分度标定(2026-09-27 新增)。
|
|
3
|
+
|
|
4
|
+
存在的理由:clickbait / title_body / template 三道门槛(0.5 / 0.5 / 0.75)
|
|
5
|
+
原先都是拍脑袋的常数。手工构造两个样本时发现两个问题:
|
|
6
|
+
- 精心打磨的现代农场页 clickbait 得 0.0(PACLIC 那套悬念/夸张词表对
|
|
7
|
+
2026 年的 SEO 写法已失效);
|
|
8
|
+
- 正常技术长文 template 得 0.833,农场页 0.875 —— 信号基本没有判别力。
|
|
9
|
+
两者都是「看几个例子觉得还行」暴露不出来的,只有跑成批样本才知道。
|
|
10
|
+
|
|
11
|
+
本脚本输出三个量,都是**与阈值无关**的排序性质,故能先于调参使用:
|
|
12
|
+
- AUC : 该信号单独作为判别器的区分度(0.5 = 无判别力)
|
|
13
|
+
- 最好阈值 : 扫全阈值区间取 F1 最优点,顺带给出现行阈值的位置
|
|
14
|
+
- 混淆 : 现行阈值下 good 被误杀 / low 被漏放的具体样本 id
|
|
15
|
+
|
|
16
|
+
用法:
|
|
17
|
+
python3 scripts/calibrate_lowquality.py # 人读表格
|
|
18
|
+
python3 scripts/calibrate_lowquality.py --json # 机读(供门禁对比)
|
|
19
|
+
|
|
20
|
+
改任何阈值前后各跑一次,把数字贴进 commit —— 不要再写「感觉变好了」。
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
import sys
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
REPO = Path(__file__).resolve().parent.parent
|
|
30
|
+
sys.path.insert(0, str(REPO / "scripts"))
|
|
31
|
+
|
|
32
|
+
FIXTURE = REPO / "tests" / "golden" / "lowquality_calibration.json"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _signals(sample: dict) -> dict:
|
|
36
|
+
"""取一条样本的三个低质信号(与 content_signals 同源,避免口径分叉)。"""
|
|
37
|
+
from content_signals import (score_clickbait, score_template_repetition,
|
|
38
|
+
score_title_body_consistency)
|
|
39
|
+
title = sample.get("title", "")
|
|
40
|
+
content = sample.get("content", "")
|
|
41
|
+
cb = score_clickbait(title)
|
|
42
|
+
tb = score_title_body_consistency(title, content)
|
|
43
|
+
tp = score_template_repetition(content)
|
|
44
|
+
return {
|
|
45
|
+
# clickbait: 越大越像农场
|
|
46
|
+
"clickbait": float(cb.get("score", 0.0)),
|
|
47
|
+
# template: 越大越像模板批量生成
|
|
48
|
+
"template": float(tp.get("repetition", 0.0)),
|
|
49
|
+
# title_body: 越小越文不对题 → 取反,使三个信号同向(越大越低质)
|
|
50
|
+
"title_body_gap": 1.0 - float(tb.get("coverage", 0.5)),
|
|
51
|
+
# keyword_density: 越大越是关键词堆砌(排序层信号,见 rank_signals)
|
|
52
|
+
"keyword_density": _density(sample),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _density(sample: dict) -> float:
|
|
57
|
+
"""整体关键词密度(title+snippet 合并)。延迟导入避免顶层拖慢 --help。"""
|
|
58
|
+
try:
|
|
59
|
+
from rank_signals import keyword_density, relevance_units
|
|
60
|
+
except ImportError:
|
|
61
|
+
return 0.0
|
|
62
|
+
uq = set(relevance_units(sample.get("query", "")))
|
|
63
|
+
return keyword_density(
|
|
64
|
+
f"{sample.get('title', '')} {sample.get('content', '')}", uq)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# 现行 evidence_loop.py 里的实际门槛,改动这里必须同步改那里
|
|
68
|
+
CURRENT_THRESHOLDS = {
|
|
69
|
+
"clickbait": ("ge", 0.5),
|
|
70
|
+
"title_body_gap": ("mismatch", 0.5), # 语义阈值:coverage < 0.5
|
|
71
|
+
"template": ("ge", 0.75),
|
|
72
|
+
# 排序层:rank_signals.stuffing_density_penalty 的门槛
|
|
73
|
+
"keyword_density": ("ge", 0.22),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _auc(pairs: list[tuple[float, int]]) -> float:
|
|
78
|
+
"""AUC(越大越 low 方向):正类(low=1)得分高于负类(good=0)的概率。
|
|
79
|
+
|
|
80
|
+
约定:所有进入本函数的信号都**同向**——score 越大越像低质
|
|
81
|
+
(clickbait / template / title_body_gap / keyword_density 皆如此,
|
|
82
|
+
前两个本来就是,title_body_gap 在取反时已转正)。故 AUC 可直接跨信号
|
|
83
|
+
比较,0.5 = 无判别力,>0.5 = 有判别力。ties 记 0.5。
|
|
84
|
+
|
|
85
|
+
实现用排序累加而非 sklearn:零依赖,样本量小,O(n²) 完全够。
|
|
86
|
+
"""
|
|
87
|
+
pos = [s for s, y in pairs if y == 1]
|
|
88
|
+
neg = [s for s, y in pairs if y == 0]
|
|
89
|
+
if not pos or not neg:
|
|
90
|
+
return float("nan")
|
|
91
|
+
total = 0.0
|
|
92
|
+
for p in pos:
|
|
93
|
+
for n in neg:
|
|
94
|
+
total += 1.0 if p > n else (0.5 if p == n else 0.0)
|
|
95
|
+
return total / (len(pos) * len(neg))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _best_threshold(pairs: list[tuple[float, int]]) -> tuple[float, float]:
|
|
99
|
+
"""扫候选阈值,返回 (最佳 F1 的阈值, 该 F1)。
|
|
100
|
+
|
|
101
|
+
候选取所有相邻分数的中点——对连续分数而言,判别边界只可能在数据点之间。
|
|
102
|
+
分数完全相同的一组无法分开,此时 F1 反映的是「按分数盲猜」的上限。
|
|
103
|
+
"""
|
|
104
|
+
vals = sorted({s for s, _ in pairs})
|
|
105
|
+
if len(vals) < 2:
|
|
106
|
+
return (vals[0] if vals else 0.0, 0.0)
|
|
107
|
+
cands = [(vals[i] + vals[i + 1]) / 2 for i in range(len(vals) - 1)]
|
|
108
|
+
best = (0.0, 0.0)
|
|
109
|
+
for t in cands:
|
|
110
|
+
tp = sum(1 for s, y in pairs if s >= t and y == 1)
|
|
111
|
+
fp = sum(1 for s, y in pairs if s >= t and y == 0)
|
|
112
|
+
fn = sum(1 for s, y in pairs if s < t and y == 1)
|
|
113
|
+
prec = tp / (tp + fp) if (tp + fp) else 0.0
|
|
114
|
+
rec = tp / (tp + fn) if (tp + fn) else 0.0
|
|
115
|
+
f1 = 2 * prec * rec / (prec + rec) if (prec + rec) else 0.0
|
|
116
|
+
if f1 > best[1]:
|
|
117
|
+
best = (t, f1)
|
|
118
|
+
return best
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def run() -> dict:
|
|
122
|
+
data = json.loads(FIXTURE.read_text(encoding="utf-8"))
|
|
123
|
+
samples = data["samples"]
|
|
124
|
+
for s in samples:
|
|
125
|
+
s["_sig"] = _signals(s)
|
|
126
|
+
|
|
127
|
+
report: dict = {"n": len(samples), "signals": {}}
|
|
128
|
+
for name in ("clickbait", "template", "title_body_gap", "keyword_density"):
|
|
129
|
+
pairs = [(s["_sig"][name], 1 if s["quality"] == "low" else 0) for s in samples]
|
|
130
|
+
auc = _auc(pairs)
|
|
131
|
+
thr, f1 = _best_threshold(pairs)
|
|
132
|
+
cur_t = CURRENT_THRESHOLDS[name][1]
|
|
133
|
+
fp = [s["id"] for s in samples
|
|
134
|
+
if s["quality"] == "good" and s["_sig"][name] >= cur_t]
|
|
135
|
+
fn = [s["id"] for s in samples
|
|
136
|
+
if s["quality"] == "low" and s["_sig"][name] < cur_t]
|
|
137
|
+
report["signals"][name] = {
|
|
138
|
+
"auc": round(auc, 4),
|
|
139
|
+
"best_threshold": round(thr, 4),
|
|
140
|
+
"best_f1": round(f1, 4),
|
|
141
|
+
"current_threshold": cur_t,
|
|
142
|
+
"false_positive_ids": fp, # good 被判低质
|
|
143
|
+
"false_negative_ids": fn, # low 被放过
|
|
144
|
+
# 分低质类别的 AUC:一个信号通常只对某一类低质负责,把它不负责的
|
|
145
|
+
# 类别也塞进同一个 AUC 会得出「越改越差」的反直觉结论。
|
|
146
|
+
# 2026-09-27 踩过:堆砌密度信号上线后总体 AUC 0.571→0.370,
|
|
147
|
+
# 实际是因为「文不对题」「标题党」这些**密度天然低**的样本被算进
|
|
148
|
+
# 了分母——它们 relevance 高是**正确**的(确实高度相关,只是别的
|
|
149
|
+
# 问题),不该由密度信号压。按类拆开后该信号在 stuffing 类上
|
|
150
|
+
# AUC=0.964(越大越低质方向),即近乎完美。
|
|
151
|
+
"auc_by_lowclass": _auc_by_lowclass(samples, name),
|
|
152
|
+
}
|
|
153
|
+
return report
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _auc_by_lowclass(samples: list[dict], name: str) -> dict:
|
|
157
|
+
"""按低质类别(用 id 前缀归组)分别算 AUC。
|
|
158
|
+
|
|
159
|
+
类别取 id 的第一个下划线段:farm_* / stuff_* / seo_good_*。
|
|
160
|
+
"""
|
|
161
|
+
out: dict = {}
|
|
162
|
+
groups = sorted({s["id"].split("_")[0] for s in samples
|
|
163
|
+
if s["quality"] == "low"})
|
|
164
|
+
for g in groups:
|
|
165
|
+
sub_low = [s for s in samples
|
|
166
|
+
if s["quality"] == "low" and s["id"].startswith(g + "_")]
|
|
167
|
+
if not sub_low:
|
|
168
|
+
continue
|
|
169
|
+
neg = [s for s in samples if s["quality"] == "good"]
|
|
170
|
+
if not neg:
|
|
171
|
+
continue
|
|
172
|
+
pairs = [(s["_sig"][name], 1) for s in sub_low] + \
|
|
173
|
+
[(s["_sig"][name], 0) for s in neg]
|
|
174
|
+
out[g] = round(_auc(pairs), 4)
|
|
175
|
+
return out
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def main() -> int:
|
|
179
|
+
as_json = "--json" in sys.argv[1:]
|
|
180
|
+
rep = run()
|
|
181
|
+
if as_json:
|
|
182
|
+
# 走统一入口而非 json.dumps(indent=2):stdout 不出缩进 JSON
|
|
183
|
+
# (tests/test_context_budget.py 门禁;人读格式在下方非 --json 分支)
|
|
184
|
+
from cli_io import dumps
|
|
185
|
+
print(dumps(rep))
|
|
186
|
+
return 0
|
|
187
|
+
|
|
188
|
+
print(f"低质信号区分度标定({rep['n']} 条样本,好/坏对半)\n")
|
|
189
|
+
print(f"{'信号':<18}{'总体AUC':>9}{'现门槛':>9}{'最佳门槛':>10}{'最佳F1':>9}")
|
|
190
|
+
print("-" * 55)
|
|
191
|
+
for name, m in rep["signals"].items():
|
|
192
|
+
print(f"{name:<18}{m['auc']:>9.3f}{m['current_threshold']:>9.2f}"
|
|
193
|
+
f"{m['best_threshold']:>10.3f}{m['best_f1']:>9.3f}")
|
|
194
|
+
print("\n总体 AUC:0.5=无判别力,0.75=可用,0.9+=强。")
|
|
195
|
+
print("⚠️ 总体 AUC 会误导——一个信号通常只对某一类低质负责。**按类看**:")
|
|
196
|
+
for name, m in rep["signals"].items():
|
|
197
|
+
byc = m.get("auc_by_lowclass") or {}
|
|
198
|
+
if not byc:
|
|
199
|
+
continue
|
|
200
|
+
cells = " ".join(f"{k}={v:.3f}" for k, v in sorted(byc.items()))
|
|
201
|
+
print(f" {name:<18} {cells}")
|
|
202
|
+
print("\n误杀(good 判成 low) / 漏放(low 判成 good):")
|
|
203
|
+
for name, m in rep["signals"].items():
|
|
204
|
+
print(f" {name:<18} 误杀={m['false_positive_ids'] or '无'}")
|
|
205
|
+
print(f" {'':<18} 漏放={m['false_negative_ids'] or '无'}")
|
|
206
|
+
return 0
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
if __name__ == "__main__":
|
|
210
|
+
raise SystemExit(main())
|