@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
@@ -0,0 +1,248 @@
1
+ import { type DrawNumbers, type GameId, type GamePick, type NormalizedDraw, type ScoreWeights, type StatsSnapshot, type AccuracySnapshot } from '../types.js';
2
+ import { getGameMeta } from '../games/registry.js';
3
+ import { recommendKl8Groups, resolveKl8Config, type Kl8Config } from '../games/kl8-groups.js';
4
+ import { pad2, pickTopUnique, scoreDigits, scorePool } from '../stats/engine.js';
5
+ import { computeDigitProMetrics, computeProMetrics } from '../stats/pro-analysis.js';
6
+
7
+ function buildSnapshot(
8
+ gameId: GameId,
9
+ draws: NormalizedDraw[],
10
+ hot: number[],
11
+ cold: number[],
12
+ detail: string,
13
+ pro: StatsSnapshot['pro'],
14
+ accuracy?: AccuracySnapshot,
15
+ _weights?: ScoreWeights,
16
+ ): StatsSnapshot {
17
+ return {
18
+ gameId,
19
+ sampleSize: draws.length,
20
+ hot: hot.map(String),
21
+ cold: cold.map(String),
22
+ detail,
23
+ pro,
24
+ accuracy,
25
+ };
26
+ }
27
+
28
+ function poolDraws(draws: NormalizedDraw[], extract: (n: DrawNumbers) => number[]): number[][] {
29
+ return draws.map((d) => extract(d.numbers)).filter((a) => a.length > 0);
30
+ }
31
+
32
+ export interface RecommendOptions {
33
+ pickCount: number;
34
+ tieSeed: string;
35
+ weights?: ScoreWeights;
36
+ accuracy?: AccuracySnapshot;
37
+ kl8?: Kl8Config;
38
+ }
39
+
40
+ export function recommendGame(
41
+ gameId: GameId,
42
+ draws: NormalizedDraw[],
43
+ options: RecommendOptions,
44
+ ): GamePick {
45
+ const meta = getGameMeta(gameId);
46
+ const tieSeed = `${options.tieSeed}:${gameId}`;
47
+ const weights = options.weights ?? { freq: 0.4, omit: 0.35, trend: 0.25 };
48
+
49
+ switch (gameId) {
50
+ case 'kl8': {
51
+ const kl8 = options.kl8 ?? resolveKl8Config(options.pickCount);
52
+ const rows = poolDraws(draws, (n) => n.main ?? []);
53
+ if (kl8.recommendGroups <= 1) {
54
+ const scores = scorePool(rows, 1, 80, tieSeed, weights);
55
+ const pick = pickTopUnique(scores, kl8.pickCount);
56
+ const hot = scores.slice(0, 5).map((s) => s.value);
57
+ const cold = [...scores].reverse().slice(0, 5).map((s) => s.value);
58
+ const pro = computeProMetrics(gameId, pick, draws, weights, 1, 80, 40);
59
+ return {
60
+ gameId,
61
+ label: `${meta.name}(选${pick.length})`,
62
+ numbers: { main: pick },
63
+ weights,
64
+ stats: buildSnapshot(
65
+ gameId,
66
+ draws,
67
+ hot,
68
+ cold,
69
+ `频率+遗漏+趋势 | 和值${pro.sum} 跨度${pro.span}`,
70
+ pro,
71
+ options.accuracy,
72
+ weights,
73
+ ),
74
+ };
75
+ }
76
+ const multi = recommendKl8Groups(draws, {
77
+ pickCount: kl8.pickCount,
78
+ recommendGroups: kl8.recommendGroups,
79
+ strategies: kl8.groupStrategies,
80
+ tieSeed,
81
+ baseWeights: weights,
82
+ });
83
+ const primary = multi.groups[0]?.numbers ?? [];
84
+ const hot = multi.scores.slice(0, 5).map((s) => s.value);
85
+ const cold = [...multi.scores].reverse().slice(0, 5).map((s) => s.value);
86
+ const pro = computeProMetrics(gameId, primary, draws, weights, 1, 80, 40);
87
+ const strategyNote = multi.groups.map((g) => g.label).join('/');
88
+ return {
89
+ gameId,
90
+ label: `${meta.name}(选${kl8.pickCount}·${multi.groups.length}组)`,
91
+ numbers: { main: primary },
92
+ weights,
93
+ kl8Groups: multi.groups,
94
+ stats: buildSnapshot(
95
+ gameId,
96
+ draws,
97
+ hot,
98
+ cold,
99
+ `多策略 ${strategyNote} | 和值${pro.sum} 跨度${pro.span}`,
100
+ pro,
101
+ options.accuracy,
102
+ weights,
103
+ ),
104
+ };
105
+ }
106
+ case 'ssq': {
107
+ const redRows = poolDraws(draws, (n) => n.red ?? []);
108
+ const blueRows = poolDraws(draws, (n) => n.blue ?? []);
109
+ const redScores = scorePool(redRows, 1, 33, `${tieSeed}:red`, weights);
110
+ const blueScores = scorePool(blueRows, 1, 16, `${tieSeed}:blue`, weights);
111
+ const red = pickTopUnique(redScores, 6);
112
+ const blue = pickTopUnique(blueScores, 1);
113
+ const pro = computeProMetrics(gameId, red, draws, weights, 1, 33, 17);
114
+ return {
115
+ gameId,
116
+ label: meta.name,
117
+ numbers: { red, blue },
118
+ weights,
119
+ stats: buildSnapshot(
120
+ gameId,
121
+ draws,
122
+ redScores.slice(0, 5).map((s) => s.value),
123
+ redScores.slice(-5).map((s) => s.value),
124
+ `红球 ${pro.oddEven} ${pro.bigSmall} | 蓝 ${blue.map(pad2).join(' ')}`,
125
+ pro,
126
+ options.accuracy,
127
+ weights,
128
+ ),
129
+ };
130
+ }
131
+ case 'dlt': {
132
+ const frontRows = poolDraws(draws, (n) => n.front ?? []);
133
+ const backRows = poolDraws(draws, (n) => n.back ?? []);
134
+ const frontScores = scorePool(frontRows, 1, 35, `${tieSeed}:front`, weights);
135
+ const backScores = scorePool(backRows, 1, 12, `${tieSeed}:back`, weights);
136
+ const front = pickTopUnique(frontScores, 5);
137
+ const back = pickTopUnique(backScores, 2);
138
+ const pro = computeProMetrics(gameId, front, draws, weights, 1, 35, 18);
139
+ return {
140
+ gameId,
141
+ label: meta.name,
142
+ numbers: { front, back },
143
+ weights,
144
+ stats: buildSnapshot(
145
+ gameId,
146
+ draws,
147
+ frontScores.slice(0, 5).map((s) => s.value),
148
+ frontScores.slice(-5).map((s) => s.value),
149
+ `前区 ${pro.oddEven} ${pro.zones ?? ''}`.trim(),
150
+ pro,
151
+ options.accuracy,
152
+ weights,
153
+ ),
154
+ };
155
+ }
156
+ case 'fc3d':
157
+ case 'pl3': {
158
+ const rows = poolDraws(draws, (n) => n.digits ?? []);
159
+ const digits = scoreDigits(rows, 3, tieSeed, weights);
160
+ const pro = computeDigitProMetrics(digits, draws, weights);
161
+ return {
162
+ gameId,
163
+ label: meta.name,
164
+ numbers: { digits },
165
+ weights,
166
+ stats: buildSnapshot(
167
+ gameId,
168
+ draws,
169
+ digits,
170
+ [],
171
+ `直选 ${pro.digitPos} | 和值${pro.sum}`,
172
+ pro,
173
+ options.accuracy,
174
+ weights,
175
+ ),
176
+ };
177
+ }
178
+ case 'pl5': {
179
+ const rows = poolDraws(draws, (n) => n.digits ?? []);
180
+ const digits = scoreDigits(rows, 5, tieSeed, weights);
181
+ const pro = computeDigitProMetrics(digits, draws, weights);
182
+ return {
183
+ gameId,
184
+ label: meta.name,
185
+ numbers: { digits },
186
+ weights,
187
+ stats: buildSnapshot(
188
+ gameId,
189
+ draws,
190
+ digits,
191
+ [],
192
+ `直选 ${pro.digitPos} | 和值${pro.sum}`,
193
+ pro,
194
+ options.accuracy,
195
+ weights,
196
+ ),
197
+ };
198
+ }
199
+ default:
200
+ throw new Error(`Unsupported game ${gameId}`);
201
+ }
202
+ }
203
+
204
+ export function formatPickLine(pick: GamePick): string {
205
+ const n = pick.numbers;
206
+ switch (pick.gameId) {
207
+ case 'kl8': {
208
+ if (pick.kl8Groups?.length) {
209
+ const header = `■ ${pick.label}`;
210
+ const rows = pick.kl8Groups.map(
211
+ (g) => ` 组${g.index}·${g.label}:${g.numbers.map(pad2).join(' ')}`,
212
+ );
213
+ return [header, ...rows].join('\n');
214
+ }
215
+ return `■ ${pick.label}:${(n.main ?? []).map(pad2).join(' ')}`;
216
+ }
217
+ case 'ssq':
218
+ return `■ ${pick.label}:红 ${(n.red ?? []).map(pad2).join(' ')} + 蓝 ${(n.blue ?? []).map(pad2).join(' ')}`;
219
+ case 'dlt':
220
+ return `■ ${pick.label}:前 ${(n.front ?? []).map(pad2).join(' ')} + 后 ${(n.back ?? []).map(pad2).join(' ')}`;
221
+ case 'fc3d':
222
+ case 'pl3':
223
+ case 'pl5':
224
+ return `■ ${pick.label}:${(n.digits ?? []).join(' ')}`;
225
+ default:
226
+ return `■ ${pick.label}`;
227
+ }
228
+ }
229
+
230
+ export function formatPickStats(pick: GamePick): string {
231
+ const parts: string[] = [];
232
+ const hot = pick.stats.hot.length ? `热号 ${pick.stats.hot.join(',')}` : '';
233
+ const cold = pick.stats.cold.length ? `冷号 ${pick.stats.cold.join(',')}` : '';
234
+ if (hot) parts.push(hot);
235
+ if (cold) parts.push(cold);
236
+ if (pick.stats.pro) {
237
+ const p = pick.stats.pro;
238
+ const morph = [p.oddEven, p.bigSmall, p.zones, p.recentHot, p.modelWeights].filter(Boolean).join(' | ');
239
+ if (morph) parts.push(morph);
240
+ if (p.digitPos) parts.push(p.digitPos);
241
+ }
242
+ if (pick.stats.accuracy) {
243
+ const a = pick.stats.accuracy;
244
+ parts.push(`历史命中 ${(a.avgHitRate * 100).toFixed(1)}% (${a.evalCount}期) 近5期 ${a.recent5}`);
245
+ }
246
+ parts.push(pick.stats.detail);
247
+ return ` ${parts.filter(Boolean).join('\n ')}`;
248
+ }
@@ -0,0 +1,211 @@
1
+ import { DEFAULT_WEIGHTS, DISCLAIMER, type DailyReport, type DrawNumbers, type GameId, type GamePick, type ScoreWeights } from '../types.js';
2
+
3
+ import { recommendGame, formatPickLine, formatPickStats } from './game-pick.js';
4
+ import { resolveKl8Config, type Kl8Config } from '../games/kl8-groups.js';
5
+
6
+ import { loadDraws, REPORTS_TABLE, parseStoredJson, type LotteryDb } from '../db.js';
7
+
8
+ import { listPendingPredictions, loadAccuracySnapshot, loadGameWeights, savePrediction } from '../evaluate/tracker.js';
9
+ import { formatWeights } from '../stats/weights.js';
10
+
11
+ export interface LotteryConfigSlice {
12
+ pickCount: number;
13
+ historyLimit: number;
14
+ kl8?: Kl8Config;
15
+ }
16
+
17
+ export async function buildDailyReport(
18
+ db: LotteryDb,
19
+ gameIds: GameId[],
20
+ config: LotteryConfigSlice,
21
+ date = new Date(),
22
+ savePredictions = true,
23
+ weightOverrides?: Map<GameId, ScoreWeights>,
24
+ ): Promise<DailyReport> {
25
+ const dateStr = date.toISOString().slice(0, 10);
26
+ const tieSeed = dateStr;
27
+ const picks = [];
28
+ for (const gameId of gameIds) {
29
+ const draws = await loadDraws(db, gameId, config.historyLimit);
30
+ if (!draws.length) continue;
31
+ const weights = weightOverrides?.get(gameId) ?? (await loadGameWeights(db, gameId));
32
+ const accuracy = await loadAccuracySnapshot(db, gameId);
33
+ const kl8 = config.kl8 ?? resolveKl8Config(config.pickCount);
34
+ const pick = recommendGame(gameId, draws, {
35
+ pickCount: gameId === 'kl8' ? kl8.pickCount : config.pickCount,
36
+ tieSeed,
37
+ weights,
38
+ accuracy,
39
+ kl8: gameId === 'kl8' ? kl8 : undefined,
40
+ });
41
+ picks.push(pick);
42
+ if (savePredictions) await savePrediction(db, pick, date);
43
+ }
44
+ const accuracyOverview = picks
45
+ .filter((p) => p.stats.accuracy)
46
+ .map((p) => `${p.gameId} ${((p.stats.accuracy?.avgHitRate ?? 0) * 100).toFixed(1)}%`)
47
+ .join(' | ');
48
+ return {
49
+ date: dateStr,
50
+ picks,
51
+ disclaimer: DISCLAIMER,
52
+ accuracyOverview: accuracyOverview || undefined,
53
+ };
54
+ }
55
+
56
+ export function formatDailyReportText(report: DailyReport, aiExplanation?: string): string {
57
+ if (report.body?.trim()) {
58
+ const extra = aiExplanation?.trim();
59
+ return extra ? `${report.body.trim()}\n\n${extra}` : report.body.trim();
60
+ }
61
+ const lines = [`【彩票每日推荐】${report.date}`, ''];
62
+ if (report.backtestOverview?.trim()) {
63
+ lines.push(report.backtestOverview.trim());
64
+ lines.push('');
65
+ }
66
+ if (report.weightFallbackGames?.length) {
67
+ lines.push(`【权重保护】近端 holdout 调优未跑赢随机,已回退默认 F40/O35/T25:${report.weightFallbackGames.join(', ')}`);
68
+ lines.push('');
69
+ }
70
+ if (report.accuracyOverview) {
71
+ lines.push(`【模型复盘】累计平均命中 ${report.accuracyOverview}`);
72
+ lines.push('');
73
+ }
74
+ for (const pick of report.picks) {
75
+ lines.push(formatPickLine(pick));
76
+ lines.push(formatPickStats(pick));
77
+ lines.push('');
78
+ }
79
+ if (aiExplanation?.trim()) {
80
+ lines.push(aiExplanation.trim());
81
+ lines.push('');
82
+ } else {
83
+ lines.push('【统计说明】号码由频率+遗漏+近期趋势综合打分,并根据历史命中自动调权。');
84
+ lines.push('');
85
+ }
86
+ lines.push(report.disclaimer);
87
+ return lines.join('\n').trim();
88
+ }
89
+
90
+ export async function saveDailyReport(
91
+ db: LotteryDb,
92
+ report: DailyReport,
93
+ aiExplanation: string,
94
+ ): Promise<void> {
95
+ const model = db.models.get(REPORTS_TABLE);
96
+ if (!model) return;
97
+ const existing = await model.select().where({ date: report.date });
98
+ if (Array.isArray(existing) && existing.length > 0) return;
99
+ await model.insert({
100
+ date: report.date,
101
+ report_json: JSON.stringify(report),
102
+ ai_explanation: aiExplanation,
103
+ created_at: new Date().toISOString(),
104
+ });
105
+ }
106
+
107
+ export async function loadTodayReport(db: LotteryDb, date = new Date()): Promise<DailyReport | null> {
108
+ const model = db.models.get(REPORTS_TABLE);
109
+ if (!model) return null;
110
+ const dateStr = date.toISOString().slice(0, 10);
111
+ const rows = (await model.select().where({ date: dateStr })) as Array<{
112
+ report_json: string | DailyReport;
113
+ ai_explanation: string;
114
+ }>;
115
+ if (!rows.length) return null;
116
+ const row = rows[0];
117
+ if (!row) return null;
118
+ const report = parseStoredJson<DailyReport>(row.report_json, {
119
+ date: dateStr,
120
+ picks: [],
121
+ disclaimer: '',
122
+ });
123
+ if (!report.picks?.length && !report.body?.trim()) return null;
124
+ return report;
125
+ }
126
+
127
+ /** Today's pending rows as displayable picks (when report table is empty). */
128
+ export async function loadTodayPendingPicks(
129
+ db: LotteryDb,
130
+ gameIds: GameId[],
131
+ date = new Date(),
132
+ ): Promise<GamePick[]> {
133
+ const today = date.toISOString().slice(0, 10);
134
+ const picks: GamePick[] = [];
135
+ for (const gameId of gameIds) {
136
+ const pending = (await listPendingPredictions(db, gameId)) as Array<{
137
+ predict_date: string;
138
+ numbers: string | DrawNumbers;
139
+ weights: string | ScoreWeights;
140
+ }>;
141
+ for (const row of pending.filter((p) => p.predict_date === today)) {
142
+ picks.push({
143
+ gameId,
144
+ label: gameId,
145
+ numbers: parseStoredJson<DrawNumbers>(row.numbers, {}),
146
+ weights: parseStoredJson<ScoreWeights>(row.weights, DEFAULT_WEIGHTS),
147
+ stats: { gameId, sampleSize: 0, hot: [], cold: [], detail: '' },
148
+ });
149
+ }
150
+ }
151
+ return picks;
152
+ }
153
+
154
+ /** Saved daily report, or reconstruct from today's pending picks. */
155
+ export async function resolveTodayReport(
156
+ db: LotteryDb,
157
+ gameIds: GameId[],
158
+ date = new Date(),
159
+ ): Promise<DailyReport | null> {
160
+ const saved = await loadTodayReport(db, date);
161
+ if (saved) return saved;
162
+ const picks = await loadTodayPendingPicks(db, gameIds, date);
163
+ if (!picks.length) return null;
164
+ return {
165
+ date: date.toISOString().slice(0, 10),
166
+ picks,
167
+ disclaimer: DISCLAIMER,
168
+ };
169
+ }
170
+
171
+ /** Backfill missing pick weights from DB (saved reports may omit weights). */
172
+ export async function hydrateReportPickWeights(db: LotteryDb, picks: GamePick[]): Promise<GamePick[]> {
173
+ const out: GamePick[] = [];
174
+ for (const pick of picks) {
175
+ if (pick.weights?.freq != null && pick.weights?.omit != null && pick.weights?.trend != null) {
176
+ out.push(pick);
177
+ continue;
178
+ }
179
+ out.push({ ...pick, weights: await loadGameWeights(db, pick.gameId) });
180
+ }
181
+ return out;
182
+ }
183
+
184
+ export async function explainReportWithAi(
185
+ root: { inject?: (key: string) => unknown },
186
+ report: DailyReport,
187
+ ): Promise<string> {
188
+ const ai = root.inject?.('ai') as
189
+ | { ask?: (prompt: string, opts?: { systemPrompt?: string }) => Promise<string> }
190
+ | undefined;
191
+ if (!ai?.ask) return '';
192
+ const summary = report.picks
193
+ .map((p) => {
194
+ const acc = p.stats.accuracy
195
+ ? `历史命中${(p.stats.accuracy.avgHitRate * 100).toFixed(1)}%`
196
+ : '';
197
+ const w = p.stats.pro?.modelWeights ?? formatWeights(p.weights);
198
+ const nums = p.gameId === 'kl8' && p.kl8Groups?.length
199
+ ? p.kl8Groups.map((g) => `组${g.index}(${g.label}) ${g.numbers.join(',')}`).join('; ')
200
+ : formatPickLine(p).replace('■ ', '').replace(/\n/g, ' ');
201
+ return `${p.label}: ${nums} | ${p.stats.detail} | ${w} | ${acc}`;
202
+ })
203
+ .join('\n');
204
+ const systemPrompt =
205
+ '你是专业彩票数据分析师。根据统计形态、模型权重与历史命中率写解读(250字内)。不得修改推荐号码。';
206
+ try {
207
+ return await ai.ask(`请专业解读以下推荐:\n\n${summary}`, { systemPrompt });
208
+ } catch {
209
+ return '';
210
+ }
211
+ }
@@ -0,0 +1,143 @@
1
+ import { DEFAULT_WEIGHTS, type ScoreWeights } from '../types.js';
2
+
3
+ import { normalizeWeights } from './weights.js';
4
+
5
+ const RECENT_WINDOW = 30;
6
+
7
+ export function analyzePoolTrend(draws: number[][], poolMin: number, poolMax: number): Map<number, number> {
8
+ const recent = draws.slice(0, RECENT_WINDOW);
9
+ const freq = new Map<number, number>();
10
+ for (let i = poolMin; i <= poolMax; i++) freq.set(i, 0);
11
+ for (const row of recent) {
12
+ for (const n of row) {
13
+ if (n >= poolMin && n <= poolMax) freq.set(n, (freq.get(n) ?? 0) + 1);
14
+ }
15
+ }
16
+ const max = Math.max(1, ...freq.values());
17
+ for (const [k, v] of freq) freq.set(k, v / max);
18
+ return freq;
19
+ }
20
+
21
+ export function parseNumberList(raw: string | undefined | null): number[] {
22
+ if (!raw) return [];
23
+ return raw
24
+ .split(/[,,\s+|]+/)
25
+ .map((s) => s.trim())
26
+ .filter(Boolean)
27
+ .map((s) => Number.parseInt(s, 10))
28
+ .filter((n) => Number.isFinite(n));
29
+ }
30
+
31
+ export function stableHash(seed: string): number {
32
+ let h = 0;
33
+ for (let i = 0; i < seed.length; i++) {
34
+ h = (h * 31 + seed.charCodeAt(i)) >>> 0;
35
+ }
36
+ return h;
37
+ }
38
+
39
+ export interface PoolScore {
40
+ value: number;
41
+ freq: number;
42
+ omit: number;
43
+ trend: number;
44
+ score: number;
45
+ }
46
+
47
+ /** 号码池:频率 + 遗漏 + 近期趋势 综合打分 */
48
+ export function scorePool(
49
+ draws: number[][],
50
+ poolMin: number,
51
+ poolMax: number,
52
+ tieSeed: string,
53
+ weights: ScoreWeights = DEFAULT_WEIGHTS,
54
+ ): PoolScore[] {
55
+ const w = normalizeWeights(weights);
56
+ const freq = new Map<number, number>();
57
+ const lastSeen = new Map<number, number>();
58
+ for (let i = poolMin; i <= poolMax; i++) {
59
+ freq.set(i, 0);
60
+ lastSeen.set(i, draws.length);
61
+ }
62
+ draws.forEach((nums, idx) => {
63
+ for (const n of nums) {
64
+ if (n < poolMin || n > poolMax) continue;
65
+ freq.set(n, (freq.get(n) ?? 0) + 1);
66
+ lastSeen.set(n, idx);
67
+ }
68
+ });
69
+ const trend = analyzePoolTrend(draws, poolMin, poolMax);
70
+ const maxFreq = Math.max(1, ...freq.values());
71
+ const maxOmit = Math.max(1, ...lastSeen.values());
72
+ const scores: PoolScore[] = [];
73
+ for (let v = poolMin; v <= poolMax; v++) {
74
+ const f = (freq.get(v) ?? 0) / maxFreq;
75
+ const o = (lastSeen.get(v) ?? 0) / maxOmit;
76
+ const t = trend.get(v) ?? 0;
77
+ scores.push({
78
+ value: v,
79
+ freq: f,
80
+ omit: o,
81
+ trend: t,
82
+ score: f * w.freq + o * w.omit + t * w.trend,
83
+ });
84
+ }
85
+ scores.sort((a, b) => {
86
+ if (b.score !== a.score) return b.score - a.score;
87
+ return stableHash(`${tieSeed}:${a.value}`) - stableHash(`${tieSeed}:${b.value}`);
88
+ });
89
+ return scores;
90
+ }
91
+
92
+ export function pickTopUnique(scores: PoolScore[], count: number): number[] {
93
+ return scores.slice(0, count).map((s) => s.value).sort((a, b) => a - b);
94
+ }
95
+
96
+ /** 定位胆:每位 0-9 独立打分(含近期趋势) */
97
+ export function scoreDigits(
98
+ draws: number[][],
99
+ positions: number,
100
+ tieSeed: string,
101
+ weights: ScoreWeights = DEFAULT_WEIGHTS,
102
+ ): number[] {
103
+ const w = normalizeWeights(weights);
104
+ const picks: number[] = [];
105
+ for (let pos = 0; pos < positions; pos++) {
106
+ const column = draws.map((d) => d[pos]).filter((n) => n !== undefined);
107
+ const recent = column.slice(0, 30);
108
+ const freq = new Map<number, number>();
109
+ const lastSeen = new Map<number, number>();
110
+ const trend = new Map<number, number>();
111
+ for (let i = 0; i <= 9; i++) {
112
+ freq.set(i, 0);
113
+ lastSeen.set(i, column.length);
114
+ trend.set(i, 0);
115
+ }
116
+ column.forEach((n, idx) => {
117
+ freq.set(n, (freq.get(n) ?? 0) + 1);
118
+ lastSeen.set(n, idx);
119
+ });
120
+ recent.forEach((n) => trend.set(n, (trend.get(n) ?? 0) + 1));
121
+ const maxFreq = Math.max(1, ...freq.values());
122
+ const maxOmit = Math.max(1, ...lastSeen.values());
123
+ const maxTrend = Math.max(1, ...trend.values());
124
+ let best = 0;
125
+ let bestScore = -1;
126
+ for (let d = 0; d <= 9; d++) {
127
+ const f = (freq.get(d) ?? 0) / maxFreq;
128
+ const o = (lastSeen.get(d) ?? 0) / maxOmit;
129
+ const t = (trend.get(d) ?? 0) / maxTrend;
130
+ const score = f * w.freq + o * w.omit + t * w.trend;
131
+ if (score > bestScore || (score === bestScore && stableHash(`${tieSeed}:${pos}:${d}`) < stableHash(`${tieSeed}:${pos}:${best}`))) {
132
+ bestScore = score;
133
+ best = d;
134
+ }
135
+ }
136
+ picks.push(best);
137
+ }
138
+ return picks;
139
+ }
140
+
141
+ export function pad2(n: number): string {
142
+ return n < 10 ? `0${n}` : String(n);
143
+ }