@zhin.js/plugin-lottery 1.0.0

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.
Files changed (289) hide show
  1. package/README.md +114 -0
  2. package/agent/agent.ts +6 -0
  3. package/agent/instructions.md +2 -0
  4. package/agent/schedules/daily_pipeline.ts +34 -0
  5. package/agent/skills/lottery.md +36 -0
  6. package/agent/tools/compute_recommend.ts +11 -0
  7. package/agent/tools/get_model_state.ts +11 -0
  8. package/agent/tools/history.ts +22 -0
  9. package/agent/tools/list_pending.ts +11 -0
  10. package/agent/tools/save_prediction.ts +14 -0
  11. package/agent/tools/stats_snapshot.ts +11 -0
  12. package/agent/tools/sync.ts +18 -0
  13. package/evals/pipeline_smoke.eval.ts +8 -0
  14. package/lib/agent/agent.d.ts +3 -0
  15. package/lib/agent/agent.d.ts.map +1 -0
  16. package/lib/agent/agent.js +6 -0
  17. package/lib/agent/agent.js.map +1 -0
  18. package/lib/agent/prompts.d.ts +11 -0
  19. package/lib/agent/prompts.d.ts.map +1 -0
  20. package/lib/agent/prompts.js +11 -0
  21. package/lib/agent/prompts.js.map +1 -0
  22. package/lib/agent/read-skills.d.ts +3 -0
  23. package/lib/agent/read-skills.d.ts.map +1 -0
  24. package/lib/agent/read-skills.js +25 -0
  25. package/lib/agent/read-skills.js.map +1 -0
  26. package/lib/agent/run-turn.d.ts +14 -0
  27. package/lib/agent/run-turn.d.ts.map +1 -0
  28. package/lib/agent/run-turn.js +47 -0
  29. package/lib/agent/run-turn.js.map +1 -0
  30. package/lib/agent/schedules/daily_pipeline.d.ts +3 -0
  31. package/lib/agent/schedules/daily_pipeline.d.ts.map +1 -0
  32. package/lib/agent/schedules/daily_pipeline.js +36 -0
  33. package/lib/agent/schedules/daily_pipeline.js.map +1 -0
  34. package/lib/agent/tools/compute_recommend.d.ts +5 -0
  35. package/lib/agent/tools/compute_recommend.d.ts.map +1 -0
  36. package/lib/agent/tools/compute_recommend.js +11 -0
  37. package/lib/agent/tools/compute_recommend.js.map +1 -0
  38. package/lib/agent/tools/get_model_state.d.ts +5 -0
  39. package/lib/agent/tools/get_model_state.d.ts.map +1 -0
  40. package/lib/agent/tools/get_model_state.js +11 -0
  41. package/lib/agent/tools/get_model_state.js.map +1 -0
  42. package/lib/agent/tools/history.d.ts +6 -0
  43. package/lib/agent/tools/history.d.ts.map +1 -0
  44. package/lib/agent/tools/history.js +24 -0
  45. package/lib/agent/tools/history.js.map +1 -0
  46. package/lib/agent/tools/list_pending.d.ts +5 -0
  47. package/lib/agent/tools/list_pending.d.ts.map +1 -0
  48. package/lib/agent/tools/list_pending.js +11 -0
  49. package/lib/agent/tools/list_pending.js.map +1 -0
  50. package/lib/agent/tools/save_prediction.d.ts +6 -0
  51. package/lib/agent/tools/save_prediction.d.ts.map +1 -0
  52. package/lib/agent/tools/save_prediction.js +14 -0
  53. package/lib/agent/tools/save_prediction.js.map +1 -0
  54. package/lib/agent/tools/stats_snapshot.d.ts +5 -0
  55. package/lib/agent/tools/stats_snapshot.d.ts.map +1 -0
  56. package/lib/agent/tools/stats_snapshot.js +11 -0
  57. package/lib/agent/tools/stats_snapshot.js.map +1 -0
  58. package/lib/agent/tools/sync.d.ts +5 -0
  59. package/lib/agent/tools/sync.d.ts.map +1 -0
  60. package/lib/agent/tools/sync.js +19 -0
  61. package/lib/agent/tools/sync.js.map +1 -0
  62. package/lib/db.d.ts +33 -0
  63. package/lib/db.d.ts.map +1 -0
  64. package/lib/db.js +108 -0
  65. package/lib/db.js.map +1 -0
  66. package/lib/evals/pipeline_smoke.eval.d.ts +3 -0
  67. package/lib/evals/pipeline_smoke.eval.d.ts.map +1 -0
  68. package/lib/evals/pipeline_smoke.eval.js +8 -0
  69. package/lib/evals/pipeline_smoke.eval.js.map +1 -0
  70. package/lib/evaluate/adaptive-sim.d.ts +31 -0
  71. package/lib/evaluate/adaptive-sim.d.ts.map +1 -0
  72. package/lib/evaluate/adaptive-sim.js +155 -0
  73. package/lib/evaluate/adaptive-sim.js.map +1 -0
  74. package/lib/evaluate/backtest.d.ts +29 -0
  75. package/lib/evaluate/backtest.d.ts.map +1 -0
  76. package/lib/evaluate/backtest.js +87 -0
  77. package/lib/evaluate/backtest.js.map +1 -0
  78. package/lib/evaluate/hit-rate.d.ts +8 -0
  79. package/lib/evaluate/hit-rate.d.ts.map +1 -0
  80. package/lib/evaluate/hit-rate.js +120 -0
  81. package/lib/evaluate/hit-rate.js.map +1 -0
  82. package/lib/evaluate/review-pending.d.ts +13 -0
  83. package/lib/evaluate/review-pending.d.ts.map +1 -0
  84. package/lib/evaluate/review-pending.js +89 -0
  85. package/lib/evaluate/review-pending.js.map +1 -0
  86. package/lib/evaluate/tracker.d.ts +28 -0
  87. package/lib/evaluate/tracker.d.ts.map +1 -0
  88. package/lib/evaluate/tracker.js +265 -0
  89. package/lib/evaluate/tracker.js.map +1 -0
  90. package/lib/evaluate/train-weights.d.ts +28 -0
  91. package/lib/evaluate/train-weights.d.ts.map +1 -0
  92. package/lib/evaluate/train-weights.js +101 -0
  93. package/lib/evaluate/train-weights.js.map +1 -0
  94. package/lib/evaluate/weight-guard.d.ts +15 -0
  95. package/lib/evaluate/weight-guard.d.ts.map +1 -0
  96. package/lib/evaluate/weight-guard.js +46 -0
  97. package/lib/evaluate/weight-guard.js.map +1 -0
  98. package/lib/games/kl8-groups.d.ts +31 -0
  99. package/lib/games/kl8-groups.d.ts.map +1 -0
  100. package/lib/games/kl8-groups.js +62 -0
  101. package/lib/games/kl8-groups.js.map +1 -0
  102. package/lib/games/registry.d.ts +13 -0
  103. package/lib/games/registry.d.ts.map +1 -0
  104. package/lib/games/registry.js +45 -0
  105. package/lib/games/registry.js.map +1 -0
  106. package/lib/index.d.ts +3 -0
  107. package/lib/index.d.ts.map +1 -0
  108. package/lib/index.js +215 -0
  109. package/lib/index.js.map +1 -0
  110. package/lib/pipeline.d.ts +36 -0
  111. package/lib/pipeline.d.ts.map +1 -0
  112. package/lib/pipeline.js +130 -0
  113. package/lib/pipeline.js.map +1 -0
  114. package/lib/push.d.ts +3 -0
  115. package/lib/push.d.ts.map +1 -0
  116. package/lib/push.js +31 -0
  117. package/lib/push.js.map +1 -0
  118. package/lib/recommend/game-pick.d.ts +14 -0
  119. package/lib/recommend/game-pick.d.ts.map +1 -0
  120. package/lib/recommend/game-pick.js +169 -0
  121. package/lib/recommend/game-pick.js.map +1 -0
  122. package/lib/recommend/report.d.ts +22 -0
  123. package/lib/recommend/report.d.ts.map +1 -0
  124. package/lib/recommend/report.js +173 -0
  125. package/lib/recommend/report.js.map +1 -0
  126. package/lib/src/agent/prompts.d.ts +11 -0
  127. package/lib/src/agent/prompts.d.ts.map +1 -0
  128. package/lib/src/agent/prompts.js +11 -0
  129. package/lib/src/agent/prompts.js.map +1 -0
  130. package/lib/src/agent/read-skills.d.ts +3 -0
  131. package/lib/src/agent/read-skills.d.ts.map +1 -0
  132. package/lib/src/agent/read-skills.js +25 -0
  133. package/lib/src/agent/read-skills.js.map +1 -0
  134. package/lib/src/agent/run-turn.d.ts +14 -0
  135. package/lib/src/agent/run-turn.d.ts.map +1 -0
  136. package/lib/src/agent/run-turn.js +47 -0
  137. package/lib/src/agent/run-turn.js.map +1 -0
  138. package/lib/src/db.d.ts +33 -0
  139. package/lib/src/db.d.ts.map +1 -0
  140. package/lib/src/db.js +108 -0
  141. package/lib/src/db.js.map +1 -0
  142. package/lib/src/evaluate/adaptive-sim.d.ts +31 -0
  143. package/lib/src/evaluate/adaptive-sim.d.ts.map +1 -0
  144. package/lib/src/evaluate/adaptive-sim.js +159 -0
  145. package/lib/src/evaluate/adaptive-sim.js.map +1 -0
  146. package/lib/src/evaluate/backtest.d.ts +29 -0
  147. package/lib/src/evaluate/backtest.d.ts.map +1 -0
  148. package/lib/src/evaluate/backtest.js +87 -0
  149. package/lib/src/evaluate/backtest.js.map +1 -0
  150. package/lib/src/evaluate/hit-rate.d.ts +8 -0
  151. package/lib/src/evaluate/hit-rate.d.ts.map +1 -0
  152. package/lib/src/evaluate/hit-rate.js +118 -0
  153. package/lib/src/evaluate/hit-rate.js.map +1 -0
  154. package/lib/src/evaluate/review-pending.d.ts +13 -0
  155. package/lib/src/evaluate/review-pending.d.ts.map +1 -0
  156. package/lib/src/evaluate/review-pending.js +89 -0
  157. package/lib/src/evaluate/review-pending.js.map +1 -0
  158. package/lib/src/evaluate/tracker.d.ts +28 -0
  159. package/lib/src/evaluate/tracker.d.ts.map +1 -0
  160. package/lib/src/evaluate/tracker.js +269 -0
  161. package/lib/src/evaluate/tracker.js.map +1 -0
  162. package/lib/src/evaluate/train-weights.d.ts +28 -0
  163. package/lib/src/evaluate/train-weights.d.ts.map +1 -0
  164. package/lib/src/evaluate/train-weights.js +101 -0
  165. package/lib/src/evaluate/train-weights.js.map +1 -0
  166. package/lib/src/evaluate/weight-guard.d.ts +15 -0
  167. package/lib/src/evaluate/weight-guard.d.ts.map +1 -0
  168. package/lib/src/evaluate/weight-guard.js +46 -0
  169. package/lib/src/evaluate/weight-guard.js.map +1 -0
  170. package/lib/src/games/kl8-groups.d.ts +31 -0
  171. package/lib/src/games/kl8-groups.d.ts.map +1 -0
  172. package/lib/src/games/kl8-groups.js +62 -0
  173. package/lib/src/games/kl8-groups.js.map +1 -0
  174. package/lib/src/games/registry.d.ts +13 -0
  175. package/lib/src/games/registry.d.ts.map +1 -0
  176. package/lib/src/games/registry.js +45 -0
  177. package/lib/src/games/registry.js.map +1 -0
  178. package/lib/src/index.d.ts +3 -0
  179. package/lib/src/index.d.ts.map +1 -0
  180. package/lib/src/index.js +183 -0
  181. package/lib/src/index.js.map +1 -0
  182. package/lib/src/lottery-agent-deps.d.ts +24 -0
  183. package/lib/src/lottery-agent-deps.d.ts.map +1 -0
  184. package/lib/src/lottery-agent-deps.js +10 -0
  185. package/lib/src/lottery-agent-deps.js.map +1 -0
  186. package/lib/src/lottery-tool-handlers.d.ts +17 -0
  187. package/lib/src/lottery-tool-handlers.d.ts.map +1 -0
  188. package/lib/src/lottery-tool-handlers.js +155 -0
  189. package/lib/src/lottery-tool-handlers.js.map +1 -0
  190. package/lib/src/pipeline.d.ts +36 -0
  191. package/lib/src/pipeline.d.ts.map +1 -0
  192. package/lib/src/pipeline.js +130 -0
  193. package/lib/src/pipeline.js.map +1 -0
  194. package/lib/src/push.d.ts +3 -0
  195. package/lib/src/push.d.ts.map +1 -0
  196. package/lib/src/push.js +31 -0
  197. package/lib/src/push.js.map +1 -0
  198. package/lib/src/recommend/game-pick.d.ts +14 -0
  199. package/lib/src/recommend/game-pick.d.ts.map +1 -0
  200. package/lib/src/recommend/game-pick.js +169 -0
  201. package/lib/src/recommend/game-pick.js.map +1 -0
  202. package/lib/src/recommend/report.d.ts +22 -0
  203. package/lib/src/recommend/report.d.ts.map +1 -0
  204. package/lib/src/recommend/report.js +176 -0
  205. package/lib/src/recommend/report.js.map +1 -0
  206. package/lib/src/stats/engine.d.ts +18 -0
  207. package/lib/src/stats/engine.d.ts.map +1 -0
  208. package/lib/src/stats/engine.js +122 -0
  209. package/lib/src/stats/engine.js.map +1 -0
  210. package/lib/src/stats/pro-analysis.d.ts +7 -0
  211. package/lib/src/stats/pro-analysis.d.ts.map +1 -0
  212. package/lib/src/stats/pro-analysis.js +121 -0
  213. package/lib/src/stats/pro-analysis.js.map +1 -0
  214. package/lib/src/stats/weights.d.ts +8 -0
  215. package/lib/src/stats/weights.d.ts.map +1 -0
  216. package/lib/src/stats/weights.js +25 -0
  217. package/lib/src/stats/weights.js.map +1 -0
  218. package/lib/src/sync/fetch-official.d.ts +4 -0
  219. package/lib/src/sync/fetch-official.d.ts.map +1 -0
  220. package/lib/src/sync/fetch-official.js +160 -0
  221. package/lib/src/sync/fetch-official.js.map +1 -0
  222. package/lib/src/sync/run-sync.d.ts +8 -0
  223. package/lib/src/sync/run-sync.d.ts.map +1 -0
  224. package/lib/src/sync/run-sync.js +29 -0
  225. package/lib/src/sync/run-sync.js.map +1 -0
  226. package/lib/src/tools/agent-tools.d.ts +15 -0
  227. package/lib/src/tools/agent-tools.d.ts.map +1 -0
  228. package/lib/src/tools/agent-tools.js +211 -0
  229. package/lib/src/tools/agent-tools.js.map +1 -0
  230. package/lib/src/types.d.ts +84 -0
  231. package/lib/src/types.d.ts.map +1 -0
  232. package/lib/src/types.js +3 -0
  233. package/lib/src/types.js.map +1 -0
  234. package/lib/stats/engine.d.ts +18 -0
  235. package/lib/stats/engine.d.ts.map +1 -0
  236. package/lib/stats/engine.js +122 -0
  237. package/lib/stats/engine.js.map +1 -0
  238. package/lib/stats/pro-analysis.d.ts +7 -0
  239. package/lib/stats/pro-analysis.d.ts.map +1 -0
  240. package/lib/stats/pro-analysis.js +121 -0
  241. package/lib/stats/pro-analysis.js.map +1 -0
  242. package/lib/stats/weights.d.ts +8 -0
  243. package/lib/stats/weights.d.ts.map +1 -0
  244. package/lib/stats/weights.js +25 -0
  245. package/lib/stats/weights.js.map +1 -0
  246. package/lib/sync/fetch-official.d.ts +4 -0
  247. package/lib/sync/fetch-official.d.ts.map +1 -0
  248. package/lib/sync/fetch-official.js +160 -0
  249. package/lib/sync/fetch-official.js.map +1 -0
  250. package/lib/sync/run-sync.d.ts +8 -0
  251. package/lib/sync/run-sync.d.ts.map +1 -0
  252. package/lib/sync/run-sync.js +29 -0
  253. package/lib/sync/run-sync.js.map +1 -0
  254. package/lib/tools/agent-tools.d.ts +15 -0
  255. package/lib/tools/agent-tools.d.ts.map +1 -0
  256. package/lib/tools/agent-tools.js +211 -0
  257. package/lib/tools/agent-tools.js.map +1 -0
  258. package/lib/tsconfig.tsbuildinfo +1 -0
  259. package/lib/types.d.ts +84 -0
  260. package/lib/types.d.ts.map +1 -0
  261. package/lib/types.js +3 -0
  262. package/lib/types.js.map +1 -0
  263. package/package.json +73 -0
  264. package/src/agent/prompts.ts +21 -0
  265. package/src/agent/read-skills.ts +27 -0
  266. package/src/agent/run-turn.ts +74 -0
  267. package/src/db.ts +132 -0
  268. package/src/evaluate/adaptive-sim.ts +210 -0
  269. package/src/evaluate/backtest.ts +128 -0
  270. package/src/evaluate/hit-rate.ts +132 -0
  271. package/src/evaluate/review-pending.ts +120 -0
  272. package/src/evaluate/tracker.ts +311 -0
  273. package/src/evaluate/train-weights.ts +134 -0
  274. package/src/evaluate/weight-guard.ts +68 -0
  275. package/src/games/kl8-groups.ts +98 -0
  276. package/src/games/registry.ts +55 -0
  277. package/src/index.ts +216 -0
  278. package/src/lottery-agent-deps.ts +29 -0
  279. package/src/lottery-tool-handlers.ts +166 -0
  280. package/src/pipeline.ts +206 -0
  281. package/src/push.ts +34 -0
  282. package/src/recommend/game-pick.ts +248 -0
  283. package/src/recommend/report.ts +211 -0
  284. package/src/stats/engine.ts +143 -0
  285. package/src/stats/pro-analysis.ts +136 -0
  286. package/src/stats/weights.ts +29 -0
  287. package/src/sync/fetch-official.ts +185 -0
  288. package/src/sync/run-sync.ts +33 -0
  289. package/src/types.ts +98 -0
package/src/index.ts ADDED
@@ -0,0 +1,216 @@
1
+ /**
2
+ * @zhin.js/plugin-lottery — scheduled pipeline: sync → review → recommend → push
3
+ */
4
+ import { formatCompact, MessageCommand, Schema, usePlugin } from 'zhin.js';
5
+ import type { GameId } from './types.js';
6
+ import { parseGameId, resolveEnabledGames } from './games/registry.js';
7
+ import {
8
+ defineLotteryTables,
9
+ loadDraws,
10
+ type LotteryDb,
11
+ } from './db.js';
12
+ import {
13
+ formatDailyReportText,
14
+ loadTodayReport,
15
+ } from './recommend/report.js';
16
+ import { recommendGame, formatPickLine, formatPickStats } from './recommend/game-pick.js';
17
+ import {
18
+ loadAccuracySnapshot,
19
+ loadGameWeights,
20
+ } from './evaluate/tracker.js';
21
+ import { setLotteryAgentDeps } from './lottery-agent-deps.js';
22
+ import { formatPipelineReply, runLotteryPipeline } from './pipeline.js';
23
+ import { formatBacktestSection, formatTrainReport, runBacktestForGames, trainAllGameWeights } from './evaluate/backtest.js';
24
+ import { resolveKl8Config, type Kl8ConfigInput } from './games/kl8-groups.js';
25
+
26
+ const plugin = usePlugin();
27
+ const { logger, addCommand, useContext, onDispose, declareConfig } = plugin;
28
+
29
+ const config = declareConfig(
30
+ 'lottery',
31
+ Schema.object({
32
+ pickCount: Schema.number().default(5).min(1).max(10).description('KL8 pick count'),
33
+ scheduleCron: Schema.string().default('0 0 18 * * *').description('Daily pipeline cron (sync→review→recommend→push)'),
34
+ historyLimit: Schema.number().default(500).min(50).max(2000).description('Max draws kept per game'),
35
+ scheduleEnabled: Schema.boolean().default(true).description('Enable scheduled pipeline'),
36
+ agentEnabled: Schema.boolean().default(true).description('AI narrative on daily report (optional)'),
37
+ backtestEnabled: Schema.boolean().default(true).description('Walk-forward backtest vs random baseline in report'),
38
+ backtestWindow: Schema.number().default(50).min(10).max(300).description('Backtest periods per game'),
39
+ backtestRandomTrials: Schema.number().default(64).min(10).max(500).description('Monte Carlo random trials per period'),
40
+ backtestMinHistory: Schema.number().default(30).min(10).max(150).description('Min history before walk-forward'),
41
+ backtestAdaptive: Schema.boolean().default(true).description('Per-period weight tuning in backtest simulation'),
42
+ weightPersistEnabled: Schema.boolean().default(true).description('Persist simulated F/O/T weights to lottery_model_weights'),
43
+ weightHoldoutFallback: Schema.boolean().default(true).description('Use DEFAULT weights when holdout adaptive loses to random'),
44
+ kl8: Schema.object({
45
+ pickCount: Schema.number().default(5).min(1).max(10).description('Numbers per KL8 group (选N)'),
46
+ recommendGroups: Schema.number().default(3).min(1).max(10).description('KL8 recommendation rows per day'),
47
+ groupStrategies: Schema.list(Schema.string()).default(['balanced', 'hot', 'cold']),
48
+ }).description('KL8 multi-strategy groups'),
49
+ games: Schema.list(Schema.string()).default(['kl8', 'ssq', 'dlt', 'fc3d', 'pl3', 'pl5']),
50
+ }),
51
+ );
52
+
53
+ let _db: LotteryDb | null = null;
54
+
55
+ function getDb(): LotteryDb | null {
56
+ return _db;
57
+ }
58
+
59
+ function enabledGames(): GameId[] {
60
+ return resolveEnabledGames(config.games as string[]);
61
+ }
62
+
63
+ function pipelineDeps() {
64
+ const kl8 = resolveKl8Config(config.pickCount, config.kl8 as Kl8ConfigInput | undefined);
65
+ return {
66
+ getDb,
67
+ plugin,
68
+ enabledGames,
69
+ historyLimit: config.historyLimit,
70
+ pickCount: config.pickCount,
71
+ kl8,
72
+ agentEnabled: config.agentEnabled,
73
+ backtest: {
74
+ enabled: config.backtestEnabled,
75
+ window: config.backtestWindow,
76
+ randomTrials: config.backtestRandomTrials,
77
+ minHistory: config.backtestMinHistory,
78
+ adaptive: config.backtestAdaptive,
79
+ },
80
+ weightPersist: config.weightPersistEnabled,
81
+ weightHoldoutFallback: config.weightHoldoutFallback,
82
+ };
83
+ }
84
+
85
+ function refreshLotteryAgentDeps(): void {
86
+ setLotteryAgentDeps({
87
+ getDb: () => _db,
88
+ getConfig: () => ({
89
+ pickCount: config.pickCount,
90
+ historyLimit: config.historyLimit,
91
+ kl8: resolveKl8Config(config.pickCount, config.kl8 as Kl8ConfigInput | undefined),
92
+ }),
93
+ plugin,
94
+ enabledGames,
95
+ scheduleCron: () => config.scheduleCron,
96
+ scheduleEnabled: () => config.scheduleEnabled,
97
+ pipelinePush: true,
98
+ });
99
+ }
100
+
101
+ refreshLotteryAgentDeps();
102
+
103
+ useContext('database', (db: unknown) => {
104
+ const d = db as { define: (n: string, s: Record<string, unknown>) => void };
105
+ defineLotteryTables(d);
106
+ _db = db as LotteryDb;
107
+ refreshLotteryAgentDeps();
108
+ logger.debug(formatCompact({ op: 'lottery-ready', schedule: config.scheduleEnabled }));
109
+ });
110
+
111
+ onDispose(() => {});
112
+
113
+ // ─── Commands ───────────────────────────────────────────────────────────────
114
+
115
+ addCommand(
116
+ new MessageCommand('lottery [game:word]')
117
+ .desc('Run full pipeline: sync → review → recommend (manual, no push)')
118
+ .action(async (_msg, result) => {
119
+ const gid = parseGameId(result.params.game ?? '');
120
+ const out = await runLotteryPipeline(pipelineDeps(), { gameId: gid ?? undefined, push: false });
121
+ return formatPipelineReply(out);
122
+ }),
123
+ );
124
+
125
+ addCommand(
126
+ new MessageCommand('lottery-train [game:word]')
127
+ .desc('Full-history weight training: predict→compare→tune from DEFAULT, persist to DB')
128
+ .action(async (_msg, result) => {
129
+ const db = getDb();
130
+ if (!db) return '数据库未就绪';
131
+ const gid = parseGameId(result.params.game ?? '');
132
+ const gameIds = gid ? [gid] : enabledGames();
133
+ const results = await trainAllGameWeights(db, gameIds, {
134
+ pickCount: config.pickCount,
135
+ minHistory: config.backtestMinHistory,
136
+ historyLimit: config.historyLimit,
137
+ randomTrials: config.backtestRandomTrials,
138
+ holdoutWindow: config.backtestWindow,
139
+ holdoutFallback: config.weightHoldoutFallback,
140
+ persist: true,
141
+ });
142
+ return formatTrainReport(results);
143
+ }),
144
+ );
145
+
146
+ addCommand(
147
+ new MessageCommand('lottery-backtest [game:word]')
148
+ .desc('Walk-forward backtest: strategy vs random baseline (no betting)')
149
+ .action(async (_msg, result) => {
150
+ const db = getDb();
151
+ if (!db) return '数据库未就绪';
152
+ const gid = parseGameId(result.params.game ?? '');
153
+ const gameIds = gid ? [gid] : enabledGames();
154
+ const summaries = await runBacktestForGames(db, gameIds, {
155
+ pickCount: config.pickCount,
156
+ window: config.backtestWindow,
157
+ randomTrials: config.backtestRandomTrials,
158
+ minHistory: config.backtestMinHistory,
159
+ historyLimit: config.historyLimit,
160
+ adaptive: config.backtestAdaptive,
161
+ });
162
+ const text = formatBacktestSection(summaries);
163
+ return text || '历史数据不足,请先执行 lottery 同步开奖';
164
+ }),
165
+ );
166
+
167
+ addCommand(
168
+ new MessageCommand('lottery-today')
169
+ .desc('Show today published recommendation report')
170
+ .action(async () => {
171
+ const db = getDb();
172
+ if (!db) return '数据库未就绪';
173
+ const report = await loadTodayReport(db);
174
+ if (!report) return '今日尚无推荐,可执行 lottery 或等待定时任务';
175
+ return formatDailyReportText(report, '');
176
+ }),
177
+ );
178
+
179
+ addCommand(
180
+ new MessageCommand('lottery-stats <game:word>')
181
+ .desc('Single-game stats snapshot (code engine)')
182
+ .action(async (_msg, result) => {
183
+ const gid = parseGameId(result.params.game ?? '');
184
+ if (!gid) return '请指定玩法';
185
+ const db = getDb();
186
+ if (!db) return '数据库未就绪';
187
+ const draws = await loadDraws(db, gid, config.historyLimit);
188
+ if (!draws.length) return '暂无数据,请先 lottery';
189
+ const kl8 = resolveKl8Config(config.pickCount, config.kl8 as Kl8ConfigInput | undefined);
190
+ const pick = recommendGame(gid, draws, {
191
+ pickCount: gid === 'kl8' ? kl8.pickCount : config.pickCount,
192
+ tieSeed: 'stats',
193
+ weights: await loadGameWeights(db, gid),
194
+ accuracy: await loadAccuracySnapshot(db, gid),
195
+ kl8: gid === 'kl8' ? kl8 : undefined,
196
+ });
197
+ return [formatPickLine(pick), formatPickStats(pick), `样本 ${draws.length} 期`].join('\n');
198
+ }),
199
+ );
200
+
201
+ addCommand(
202
+ new MessageCommand('lottery-history <game:word> [count:number=10]')
203
+ .desc('List historical draw results')
204
+ .action(async (_msg, result) => {
205
+ const gid = parseGameId(result.params.game ?? '');
206
+ if (!gid) return '请指定玩法';
207
+ const db = getDb();
208
+ if (!db) return '数据库未就绪';
209
+ const count = Math.min(50, Math.max(1, Number(result.params.count) || 10));
210
+ const draws = await loadDraws(db, gid, count);
211
+ if (!draws.length) return '暂无数据';
212
+ return draws.map((d) => `${d.issue} ${d.drawTime} ${JSON.stringify(d.numbers)}`).join('\n');
213
+ }),
214
+ );
215
+
216
+ export default plugin;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shared runtime deps for lottery agent/ authoring tools.
3
+ * Set once from plugin index during init — never call getPlugin() from tool execute.
4
+ */
5
+ import type { Plugin } from 'zhin.js';
6
+ import type { GameId } from './types.js';
7
+ import type { LotteryDb } from './db.js';
8
+ import type { Kl8Config } from './games/kl8-groups.js';
9
+
10
+ export interface LotteryAgentDeps {
11
+ getDb: () => LotteryDb | null;
12
+ getConfig: () => { pickCount: number; historyLimit: number; kl8: Kl8Config };
13
+ plugin: Plugin;
14
+ enabledGames: () => GameId[];
15
+ scheduleCron: () => string;
16
+ scheduleEnabled: () => boolean;
17
+ pipelinePush: boolean;
18
+ }
19
+
20
+ let _deps: LotteryAgentDeps | null = null;
21
+
22
+ export function setLotteryAgentDeps(deps: LotteryAgentDeps): void {
23
+ _deps = deps;
24
+ }
25
+
26
+ export function getLotteryAgentDeps(): LotteryAgentDeps {
27
+ if (!_deps) throw new Error('lottery agent deps not initialized');
28
+ return _deps;
29
+ }
@@ -0,0 +1,166 @@
1
+ import { DISCLAIMER, type GameId } from './types.js';
2
+
3
+ import { parseGameId } from './games/registry.js';
4
+ import { loadDraws } from './db.js';
5
+ import { recommendGame } from './recommend/game-pick.js';
6
+ import {
7
+ loadGameWeights,
8
+ saveGameWeights,
9
+ loadAccuracySnapshot,
10
+ savePrediction,
11
+ listPendingPredictions,
12
+ submitAgentReview,
13
+ saveAgentMemory,
14
+ loadAgentMemory,
15
+ } from './evaluate/tracker.js';
16
+ import { saveDailyReport } from './recommend/report.js';
17
+ import { pushTextToMasters } from './push.js';
18
+ import { normalizeWeights } from './stats/weights.js';
19
+ import { getLotteryAgentDeps } from './lottery-agent-deps.js';
20
+
21
+ export async function handleGetModelState(game?: string): Promise<string> {
22
+ const deps = getLotteryAgentDeps();
23
+ const db = deps.getDb();
24
+ if (!db) return 'db not ready';
25
+ const gid = parseGameId(game ?? '');
26
+ const ids: GameId[] = gid ? [gid] : deps.enabledGames();
27
+ const out = [];
28
+ for (const id of ids) {
29
+ const w = await loadGameWeights(db, id);
30
+ const acc = await loadAccuracySnapshot(db, id);
31
+ out.push({ gameId: id, weights: w, accuracy: acc ?? null });
32
+ }
33
+ return JSON.stringify(out);
34
+ }
35
+
36
+ export async function handleComputeRecommend(game: string): Promise<string> {
37
+ const deps = getLotteryAgentDeps();
38
+ const gid = parseGameId(game);
39
+ if (!gid) return 'invalid game';
40
+ const db = deps.getDb();
41
+ if (!db) return 'db not ready';
42
+ const cfg = deps.getConfig();
43
+ const draws = await loadDraws(db, gid, cfg.historyLimit);
44
+ if (!draws.length) return 'no data, run lottery_sync first';
45
+ const pick = recommendGame(gid, draws, {
46
+ pickCount: gid === 'kl8' ? cfg.kl8.pickCount : cfg.pickCount,
47
+ tieSeed: new Date().toISOString().slice(0, 10),
48
+ weights: await loadGameWeights(db, gid),
49
+ accuracy: await loadAccuracySnapshot(db, gid),
50
+ kl8: gid === 'kl8' ? cfg.kl8 : undefined,
51
+ });
52
+ return JSON.stringify(pick);
53
+ }
54
+
55
+ export async function handleSavePrediction(game: string, numbersJson: string): Promise<string> {
56
+ const deps = getLotteryAgentDeps();
57
+ const gid = parseGameId(game);
58
+ if (!gid) return 'invalid game';
59
+ const db = deps.getDb();
60
+ if (!db) return 'db not ready';
61
+ const numbers = JSON.parse(numbersJson);
62
+ const weights = await loadGameWeights(db, gid);
63
+ await savePrediction(db, {
64
+ gameId: gid,
65
+ label: gid,
66
+ numbers,
67
+ weights,
68
+ stats: { gameId: gid, sampleSize: 0, hot: [], cold: [], detail: '' },
69
+ });
70
+ return JSON.stringify({ ok: true, gameId: gid });
71
+ }
72
+
73
+ export async function handleListPending(game?: string): Promise<string> {
74
+ const deps = getLotteryAgentDeps();
75
+ const db = deps.getDb();
76
+ if (!db) return 'db not ready';
77
+ const gid = parseGameId(game ?? '');
78
+ return JSON.stringify(await listPendingPredictions(db, gid ?? undefined));
79
+ }
80
+
81
+ export async function handleSubmitReview(input: {
82
+ game: string;
83
+ predict_at: string;
84
+ actual_issue: string;
85
+ actual_numbers_json: string;
86
+ analysis: string;
87
+ }): Promise<string> {
88
+ const deps = getLotteryAgentDeps();
89
+ const gid = parseGameId(input.game);
90
+ if (!gid) return 'invalid game';
91
+ const db = deps.getDb();
92
+ if (!db) return 'db not ready';
93
+ const hit = await submitAgentReview(
94
+ db,
95
+ {
96
+ gameId: gid,
97
+ predictAt: input.predict_at,
98
+ actualIssue: input.actual_issue,
99
+ actualNumbers: JSON.parse(input.actual_numbers_json),
100
+ analysis: input.analysis ?? '',
101
+ },
102
+ deps.getConfig().historyLimit,
103
+ );
104
+ return hit ? JSON.stringify(hit) : 'pending not found';
105
+ }
106
+
107
+ export async function handleSetWeights(game: string, freq: number, omit: number, trend: number): Promise<string> {
108
+ const deps = getLotteryAgentDeps();
109
+ const gid = parseGameId(game);
110
+ if (!gid) return 'invalid game';
111
+ const db = deps.getDb();
112
+ if (!db) return 'db not ready';
113
+ const w = normalizeWeights({ freq, omit, trend });
114
+ const snap = await loadAccuracySnapshot(db, gid);
115
+ await saveGameWeights(db, gid, w, snap?.evalCount ?? 0, snap?.avgHitRate ?? 0);
116
+ return JSON.stringify({ ok: true, weights: w });
117
+ }
118
+
119
+ export async function handleSaveMemory(content: string, memoryType?: string, game?: string): Promise<string> {
120
+ const deps = getLotteryAgentDeps();
121
+ const db = deps.getDb();
122
+ if (!db) return 'db not ready';
123
+ const gid = parseGameId(game ?? '');
124
+ await saveAgentMemory(db, content, memoryType || 'insight', gid ?? '');
125
+ return JSON.stringify({ ok: true });
126
+ }
127
+
128
+ export async function handleGetMemory(game?: string, limit?: number): Promise<string> {
129
+ const deps = getLotteryAgentDeps();
130
+ const db = deps.getDb();
131
+ if (!db) return 'db not ready';
132
+ const gid = parseGameId(game ?? '');
133
+ const lim = Math.min(50, limit || 20);
134
+ return JSON.stringify(await loadAgentMemory(db, gid ?? undefined, lim));
135
+ }
136
+
137
+ export async function handlePublishReport(content: string, push?: boolean): Promise<string> {
138
+ const deps = getLotteryAgentDeps();
139
+ const db = deps.getDb();
140
+ if (!db) return 'db not ready';
141
+ const text = content.trim();
142
+ if (!text.includes(DISCLAIMER)) return 'content must include disclaimer';
143
+ const date = new Date().toISOString().slice(0, 10);
144
+ await saveDailyReport(db, { date, picks: [] as never[], disclaimer: DISCLAIMER, body: text }, text);
145
+ const doPush = push === true;
146
+ if (doPush) await pushTextToMasters(deps.plugin, text);
147
+ return JSON.stringify({ ok: true, date, pushed: doPush });
148
+ }
149
+
150
+ export async function handleStatsSnapshot(game: string): Promise<string> {
151
+ const deps = getLotteryAgentDeps();
152
+ const gid = parseGameId(game);
153
+ if (!gid) return 'invalid game';
154
+ const db = deps.getDb();
155
+ if (!db) return 'db not ready';
156
+ const cfg = deps.getConfig();
157
+ const draws = await loadDraws(db, gid, Math.min(30, cfg.historyLimit));
158
+ const pick = recommendGame(gid, draws, {
159
+ pickCount: gid === 'kl8' ? cfg.kl8.pickCount : cfg.pickCount,
160
+ tieSeed: 'snapshot',
161
+ weights: await loadGameWeights(db, gid),
162
+ accuracy: await loadAccuracySnapshot(db, gid),
163
+ kl8: gid === 'kl8' ? cfg.kl8 : undefined,
164
+ });
165
+ return JSON.stringify({ sample: draws.length, pick, recentDraws: draws.slice(0, 5) });
166
+ }
@@ -0,0 +1,206 @@
1
+ import type { Plugin } from 'zhin.js';
2
+ import type { GameId } from './types.js';
3
+ import { syncGame, type LotteryDb } from './db.js';
4
+
5
+ import {
6
+ buildDailyReport,
7
+ explainReportWithAi,
8
+ formatDailyReportText,
9
+ hydrateReportPickWeights,
10
+ resolveTodayReport,
11
+ saveDailyReport,
12
+ } from './recommend/report.js';
13
+ import { evaluatePendingPredictions, gamesNeedingPrediction } from './evaluate/review-pending.js';
14
+ import { formatBacktestSection, refreshWeightsForGames, runBacktestForGames } from './evaluate/backtest.js';
15
+ import {
16
+ planRecommendWeights,
17
+ gamesToPredict,
18
+ runHoldoutBacktest,
19
+ } from './evaluate/weight-guard.js';
20
+ import { cancelTodayPendingPredictions } from './evaluate/tracker.js';
21
+ import { pushTextToMasters } from './push.js';
22
+ import type { Kl8Config } from './games/kl8-groups.js';
23
+
24
+ export interface PipelineDeps {
25
+ getDb: () => LotteryDb | null;
26
+ plugin: Plugin;
27
+ enabledGames: () => GameId[];
28
+ historyLimit: number;
29
+ pickCount: number;
30
+ kl8: Kl8Config;
31
+ agentEnabled: boolean;
32
+ backtest: {
33
+ enabled: boolean;
34
+ window: number;
35
+ randomTrials: number;
36
+ minHistory: number;
37
+ adaptive: boolean;
38
+ };
39
+ weightPersist: boolean;
40
+ weightHoldoutFallback: boolean;
41
+ }
42
+
43
+ function backtestOpts(deps: PipelineDeps) {
44
+ return {
45
+ pickCount: deps.pickCount,
46
+ window: deps.backtest.window,
47
+ randomTrials: deps.backtest.randomTrials,
48
+ minHistory: deps.backtest.minHistory,
49
+ historyLimit: deps.historyLimit,
50
+ adaptive: deps.backtest.adaptive,
51
+ };
52
+ }
53
+
54
+ async function attachBacktestOverview(
55
+ db: LotteryDb,
56
+ gameIds: GameId[],
57
+ deps: PipelineDeps,
58
+ holdoutSummaries: import('./evaluate/adaptive-sim.js').SimulationResult[] = [],
59
+ ): Promise<string> {
60
+ if (!deps.backtest.enabled || deps.backtest.window <= 0) return '';
61
+ const summaries = holdoutSummaries.length
62
+ ? holdoutSummaries
63
+ : await runBacktestForGames(db, gameIds, backtestOpts(deps));
64
+ return formatBacktestSection(summaries);
65
+ }
66
+
67
+ export interface PipelineOptions {
68
+ gameId?: GameId;
69
+ push?: boolean;
70
+ }
71
+
72
+ export interface PipelineStepResult {
73
+ sync: string;
74
+ review: string;
75
+ recommend: string;
76
+ pushed: boolean;
77
+ reportText: string;
78
+ }
79
+
80
+ export async function runLotteryPipeline(
81
+ deps: PipelineDeps,
82
+ options: PipelineOptions = {},
83
+ ): Promise<PipelineStepResult> {
84
+ const db = deps.getDb();
85
+ if (!db) {
86
+ return {
87
+ sync: 'db not ready',
88
+ review: 'skipped',
89
+ recommend: 'skipped',
90
+ pushed: false,
91
+ reportText: '数据库未就绪',
92
+ };
93
+ }
94
+
95
+ const gameIds = options.gameId ? [options.gameId] : deps.enabledGames();
96
+ const syncParts: string[] = [];
97
+ const insertedAll = [];
98
+
99
+ for (const id of gameIds) {
100
+ try {
101
+ const { count, inserted } = await syncGame(db, id, deps.historyLimit);
102
+ syncParts.push(`${id}:+${count}`);
103
+ insertedAll.push(...inserted);
104
+ } catch {
105
+ syncParts.push(`${id}:err`);
106
+ }
107
+ }
108
+
109
+ const reviewResult = await evaluatePendingPredictions(
110
+ db,
111
+ gameIds,
112
+ deps.historyLimit,
113
+ insertedAll,
114
+ );
115
+ const reviewText = reviewResult.skipped
116
+ ? 'skipped (no pending)'
117
+ : reviewResult.lines.length
118
+ ? reviewResult.lines.join('; ')
119
+ : 'no match';
120
+
121
+ await refreshWeightsForGames(db, gameIds, {
122
+ ...backtestOpts(deps),
123
+ persist: deps.weightPersist,
124
+ reviewEvaluated: reviewResult.evaluated,
125
+ holdoutWindow: deps.backtest.window,
126
+ holdoutFallback: deps.weightHoldoutFallback,
127
+ });
128
+
129
+ const holdoutSummaries = deps.backtest.enabled && deps.backtest.window > 0
130
+ ? await runHoldoutBacktest(db, gameIds, backtestOpts(deps))
131
+ : [];
132
+
133
+ const missingToday = await gamesNeedingPrediction(db, gameIds);
134
+ const weightPlan = planRecommendWeights(gameIds, holdoutSummaries, deps.weightHoldoutFallback);
135
+ for (const gid of weightPlan.fallbacks) {
136
+ if (!missingToday.includes(gid)) await cancelTodayPendingPredictions(db, gid);
137
+ }
138
+ const needPredict = gamesToPredict(gameIds, missingToday, weightPlan);
139
+
140
+ let reportText = '';
141
+ let recommendText: string;
142
+
143
+ if (!needPredict.length) {
144
+ const existing = await resolveTodayReport(db, gameIds);
145
+ if (existing) {
146
+ recommendText = 'reused today picks';
147
+ existing.weightFallbackGames = weightPlan.fallbacks.length ? weightPlan.fallbacks : undefined;
148
+ existing.backtestOverview = await attachBacktestOverview(db, gameIds, deps, holdoutSummaries);
149
+ reportText = formatDailyReportText(existing, '');
150
+ } else {
151
+ recommendText = 'skipped (no picks available)';
152
+ }
153
+ } else {
154
+ const report = await buildDailyReport(
155
+ db,
156
+ needPredict,
157
+ { pickCount: deps.pickCount, historyLimit: deps.historyLimit, kl8: deps.kl8 },
158
+ new Date(),
159
+ true,
160
+ weightPlan.overrides,
161
+ );
162
+ report.weightFallbackGames = weightPlan.fallbacks.length ? weightPlan.fallbacks : undefined;
163
+ report.backtestOverview = await attachBacktestOverview(db, gameIds, deps, holdoutSummaries);
164
+
165
+ if (needPredict.length < gameIds.length) {
166
+ const existing = await resolveTodayReport(db, gameIds);
167
+ if (existing?.picks?.length) {
168
+ const regen = new Set(needPredict);
169
+ report.picks = [
170
+ ...existing.picks.filter((p) => !regen.has(p.gameId)),
171
+ ...report.picks,
172
+ ];
173
+ }
174
+ }
175
+ report.picks = await hydrateReportPickWeights(db, report.picks);
176
+
177
+ let aiNote = '';
178
+ if (deps.agentEnabled) {
179
+ aiNote = await explainReportWithAi(deps.plugin, report);
180
+ }
181
+ reportText = formatDailyReportText(report, aiNote);
182
+ await saveDailyReport(db, report, aiNote);
183
+ recommendText = weightPlan.fallbacks.some((g) => needPredict.includes(g))
184
+ ? `saved ${needPredict.join(', ')} (holdout fallback regen)`
185
+ : `saved ${needPredict.join(', ')}`;
186
+ }
187
+
188
+ let pushed = false;
189
+ if (options.push && reportText) {
190
+ await pushTextToMasters(deps.plugin, reportText);
191
+ pushed = true;
192
+ }
193
+
194
+ return {
195
+ sync: syncParts.join(', ') || 'none',
196
+ review: reviewText,
197
+ recommend: recommendText,
198
+ pushed,
199
+ reportText,
200
+ };
201
+ }
202
+
203
+ export function formatPipelineReply(result: PipelineStepResult): string {
204
+ if (result.reportText.trim()) return result.reportText.trim();
205
+ return '暂无推荐号码,请确认已同步开奖数据。';
206
+ }
package/src/push.ts ADDED
@@ -0,0 +1,34 @@
1
+ import type { Plugin } from 'zhin.js';
2
+
3
+ export async function pushTextToMasters(plugin: Plugin, text: string): Promise<void> {
4
+ const root = plugin.root as {
5
+ adapters?: string[];
6
+ inject?: (key: string) => unknown;
7
+ };
8
+ const inject = root.inject?.bind(root);
9
+ if (!inject) return;
10
+ const adapterNames = root.adapters ?? [];
11
+ for (const adapterName of adapterNames) {
12
+ const adapter = inject(adapterName) as {
13
+ endpoints?: Map<string, { $config?: { master?: unknown; name?: string }; $id?: string }>;
14
+ sendMessage?: (opts: Record<string, unknown>) => Promise<unknown>;
15
+ } | null;
16
+ if (!adapter?.endpoints || !adapter.sendMessage) continue;
17
+ for (const [, endpoint] of adapter.endpoints) {
18
+ const master = endpoint.$config?.master;
19
+ if (master == null || master === '') continue;
20
+ const endpointId = endpoint.$id ?? endpoint.$config?.name ?? '';
21
+ try {
22
+ await adapter.sendMessage({
23
+ context: adapterName,
24
+ endpoint: endpointId,
25
+ type: 'private',
26
+ id: String(master),
27
+ content: text,
28
+ });
29
+ } catch (e) {
30
+ plugin.logger.warn(`lottery push failed: ${e instanceof Error ? e.message : String(e)}`);
31
+ }
32
+ }
33
+ }
34
+ }