@zhin.js/plugin-lottery 1.0.1 → 1.0.2

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 (200) hide show
  1. package/README.md +25 -26
  2. package/agent/tools/compute_recommend.ts +2 -2
  3. package/agent/tools/get_model_state.ts +2 -2
  4. package/agent/tools/history.ts +2 -2
  5. package/agent/tools/list_pending.ts +2 -2
  6. package/agent/tools/save_prediction.ts +2 -2
  7. package/agent/tools/stats_snapshot.ts +2 -2
  8. package/agent/tools/sync.ts +2 -2
  9. package/commands/lottery/[game:string=].ts +25 -0
  10. package/commands/lottery-backtest/[game:string=].ts +34 -0
  11. package/commands/lottery-history/[game:string].ts +20 -0
  12. package/commands/lottery-stats/[game:string].ts +34 -0
  13. package/commands/lottery-today.ts +15 -0
  14. package/commands/lottery-train/[game:string=].ts +34 -0
  15. package/lib/agent/prompts.d.ts +11 -0
  16. package/lib/agent/prompts.d.ts.map +1 -0
  17. package/lib/agent/prompts.js.map +1 -0
  18. package/lib/agent/read-skills.d.ts +3 -0
  19. package/lib/agent/read-skills.d.ts.map +1 -0
  20. package/lib/agent/read-skills.js.map +1 -0
  21. package/lib/command-helpers.d.ts +8 -0
  22. package/lib/command-helpers.d.ts.map +1 -0
  23. package/lib/command-helpers.js +24 -0
  24. package/lib/command-helpers.js.map +1 -0
  25. package/lib/config.d.ts +30 -0
  26. package/lib/config.d.ts.map +1 -0
  27. package/lib/config.js +52 -0
  28. package/lib/config.js.map +1 -0
  29. package/lib/db-store.d.ts +10 -0
  30. package/lib/db-store.d.ts.map +1 -0
  31. package/lib/db-store.js +30 -0
  32. package/lib/db-store.js.map +1 -0
  33. package/lib/db.d.ts +33 -0
  34. package/lib/db.d.ts.map +1 -0
  35. package/lib/db.js.map +1 -0
  36. package/lib/evaluate/adaptive-sim.d.ts +31 -0
  37. package/lib/evaluate/adaptive-sim.d.ts.map +1 -0
  38. package/lib/evaluate/adaptive-sim.js.map +1 -0
  39. package/lib/evaluate/backtest.d.ts +29 -0
  40. package/lib/evaluate/backtest.d.ts.map +1 -0
  41. package/lib/evaluate/backtest.js.map +1 -0
  42. package/lib/evaluate/hit-rate.d.ts +7 -0
  43. package/lib/evaluate/hit-rate.d.ts.map +1 -0
  44. package/lib/evaluate/hit-rate.js.map +1 -0
  45. package/lib/evaluate/review-pending.d.ts +13 -0
  46. package/lib/evaluate/review-pending.d.ts.map +1 -0
  47. package/lib/evaluate/review-pending.js.map +1 -0
  48. package/lib/evaluate/tracker.d.ts +27 -0
  49. package/lib/evaluate/tracker.d.ts.map +1 -0
  50. package/lib/evaluate/tracker.js.map +1 -0
  51. package/lib/evaluate/train-weights.d.ts +28 -0
  52. package/lib/evaluate/train-weights.d.ts.map +1 -0
  53. package/lib/evaluate/train-weights.js.map +1 -0
  54. package/lib/evaluate/weight-guard.d.ts +15 -0
  55. package/lib/evaluate/weight-guard.d.ts.map +1 -0
  56. package/lib/evaluate/weight-guard.js.map +1 -0
  57. package/lib/games/kl8-groups.d.ts +30 -0
  58. package/lib/games/kl8-groups.d.ts.map +1 -0
  59. package/lib/games/kl8-groups.js.map +1 -0
  60. package/lib/games/registry.d.ts +13 -0
  61. package/lib/games/registry.d.ts.map +1 -0
  62. package/lib/games/registry.js.map +1 -0
  63. package/lib/index.d.ts +8 -0
  64. package/lib/index.d.ts.map +1 -0
  65. package/lib/index.js +6 -0
  66. package/lib/index.js.map +1 -0
  67. package/lib/lottery-agent-deps.d.ts +24 -0
  68. package/lib/lottery-agent-deps.d.ts.map +1 -0
  69. package/lib/lottery-agent-deps.js +22 -0
  70. package/lib/lottery-agent-deps.js.map +1 -0
  71. package/lib/lottery-tool-handlers.d.ts +17 -0
  72. package/lib/lottery-tool-handlers.d.ts.map +1 -0
  73. package/lib/{src/lottery-tool-handlers.js → lottery-tool-handlers.js} +3 -3
  74. package/lib/lottery-tool-handlers.js.map +1 -0
  75. package/lib/memory-db.d.ts +7 -0
  76. package/lib/memory-db.d.ts.map +1 -0
  77. package/lib/memory-db.js +49 -0
  78. package/lib/memory-db.js.map +1 -0
  79. package/lib/pipeline.d.ts +33 -0
  80. package/lib/pipeline.d.ts.map +1 -0
  81. package/lib/{src/pipeline.js → pipeline.js} +5 -8
  82. package/lib/pipeline.js.map +1 -0
  83. package/lib/push.d.ts +14 -0
  84. package/lib/push.d.ts.map +1 -0
  85. package/lib/push.js +24 -0
  86. package/lib/push.js.map +1 -0
  87. package/lib/recommend/game-pick.d.ts +13 -0
  88. package/lib/recommend/game-pick.d.ts.map +1 -0
  89. package/lib/recommend/game-pick.js.map +1 -0
  90. package/lib/recommend/report.d.ts +19 -0
  91. package/lib/recommend/report.d.ts.map +1 -0
  92. package/lib/{src/recommend → recommend}/report.js +0 -25
  93. package/lib/recommend/report.js.map +1 -0
  94. package/lib/runtime-state.d.ts +12 -0
  95. package/lib/runtime-state.d.ts.map +1 -0
  96. package/lib/runtime-state.js +13 -0
  97. package/lib/runtime-state.js.map +1 -0
  98. package/lib/stats/engine.d.ts +18 -0
  99. package/lib/stats/engine.d.ts.map +1 -0
  100. package/lib/stats/engine.js.map +1 -0
  101. package/lib/stats/pro-analysis.d.ts +7 -0
  102. package/lib/stats/pro-analysis.d.ts.map +1 -0
  103. package/lib/stats/pro-analysis.js.map +1 -0
  104. package/lib/stats/weights.d.ts +7 -0
  105. package/lib/stats/weights.d.ts.map +1 -0
  106. package/lib/stats/weights.js.map +1 -0
  107. package/lib/sync/fetch-official.d.ts +4 -0
  108. package/lib/sync/fetch-official.d.ts.map +1 -0
  109. package/lib/sync/fetch-official.js.map +1 -0
  110. package/lib/sync/run-sync.d.ts +8 -0
  111. package/lib/sync/run-sync.d.ts.map +1 -0
  112. package/lib/sync/run-sync.js.map +1 -0
  113. package/lib/types.d.ts +84 -0
  114. package/lib/types.d.ts.map +1 -0
  115. package/lib/types.js.map +1 -0
  116. package/package.json +30 -7
  117. package/plugin.ts +106 -0
  118. package/schema.json +52 -0
  119. package/src/command-helpers.ts +35 -0
  120. package/src/config.ts +83 -0
  121. package/src/db-store.ts +34 -0
  122. package/src/index.ts +13 -216
  123. package/src/lottery-agent-deps.ts +14 -4
  124. package/src/lottery-tool-handlers.ts +3 -3
  125. package/src/memory-db.ts +60 -0
  126. package/src/pipeline.ts +4 -11
  127. package/src/push.ts +32 -31
  128. package/src/recommend/report.ts +0 -30
  129. package/src/runtime-state.ts +23 -0
  130. package/agent/schedules/daily_pipeline.ts +0 -34
  131. package/lib/agent/agent.js +0 -6
  132. package/lib/agent/agent.js.map +0 -1
  133. package/lib/agent/schedules/daily_pipeline.js +0 -36
  134. package/lib/agent/schedules/daily_pipeline.js.map +0 -1
  135. package/lib/agent/tools/compute_recommend.js +0 -11
  136. package/lib/agent/tools/compute_recommend.js.map +0 -1
  137. package/lib/agent/tools/get_model_state.js +0 -11
  138. package/lib/agent/tools/get_model_state.js.map +0 -1
  139. package/lib/agent/tools/history.js +0 -24
  140. package/lib/agent/tools/history.js.map +0 -1
  141. package/lib/agent/tools/list_pending.js +0 -11
  142. package/lib/agent/tools/list_pending.js.map +0 -1
  143. package/lib/agent/tools/save_prediction.js +0 -14
  144. package/lib/agent/tools/save_prediction.js.map +0 -1
  145. package/lib/agent/tools/stats_snapshot.js +0 -11
  146. package/lib/agent/tools/stats_snapshot.js.map +0 -1
  147. package/lib/agent/tools/sync.js +0 -19
  148. package/lib/agent/tools/sync.js.map +0 -1
  149. package/lib/evals/pipeline_smoke.eval.js +0 -8
  150. package/lib/evals/pipeline_smoke.eval.js.map +0 -1
  151. package/lib/src/agent/prompts.js.map +0 -1
  152. package/lib/src/agent/read-skills.js.map +0 -1
  153. package/lib/src/agent/run-turn.js +0 -47
  154. package/lib/src/agent/run-turn.js.map +0 -1
  155. package/lib/src/db.js.map +0 -1
  156. package/lib/src/evaluate/adaptive-sim.js.map +0 -1
  157. package/lib/src/evaluate/backtest.js.map +0 -1
  158. package/lib/src/evaluate/hit-rate.js.map +0 -1
  159. package/lib/src/evaluate/review-pending.js.map +0 -1
  160. package/lib/src/evaluate/tracker.js.map +0 -1
  161. package/lib/src/evaluate/train-weights.js.map +0 -1
  162. package/lib/src/evaluate/weight-guard.js.map +0 -1
  163. package/lib/src/games/kl8-groups.js.map +0 -1
  164. package/lib/src/games/registry.js.map +0 -1
  165. package/lib/src/index.js +0 -183
  166. package/lib/src/index.js.map +0 -1
  167. package/lib/src/lottery-agent-deps.js +0 -10
  168. package/lib/src/lottery-agent-deps.js.map +0 -1
  169. package/lib/src/lottery-tool-handlers.js.map +0 -1
  170. package/lib/src/pipeline.js.map +0 -1
  171. package/lib/src/push.js +0 -31
  172. package/lib/src/push.js.map +0 -1
  173. package/lib/src/recommend/game-pick.js.map +0 -1
  174. package/lib/src/recommend/report.js.map +0 -1
  175. package/lib/src/stats/engine.js.map +0 -1
  176. package/lib/src/stats/pro-analysis.js.map +0 -1
  177. package/lib/src/stats/weights.js.map +0 -1
  178. package/lib/src/sync/fetch-official.js.map +0 -1
  179. package/lib/src/sync/run-sync.js.map +0 -1
  180. package/lib/src/types.js.map +0 -1
  181. package/src/agent/run-turn.ts +0 -74
  182. /package/lib/{src/agent → agent}/prompts.js +0 -0
  183. /package/lib/{src/agent → agent}/read-skills.js +0 -0
  184. /package/lib/{src/db.js → db.js} +0 -0
  185. /package/lib/{src/evaluate → evaluate}/adaptive-sim.js +0 -0
  186. /package/lib/{src/evaluate → evaluate}/backtest.js +0 -0
  187. /package/lib/{src/evaluate → evaluate}/hit-rate.js +0 -0
  188. /package/lib/{src/evaluate → evaluate}/review-pending.js +0 -0
  189. /package/lib/{src/evaluate → evaluate}/tracker.js +0 -0
  190. /package/lib/{src/evaluate → evaluate}/train-weights.js +0 -0
  191. /package/lib/{src/evaluate → evaluate}/weight-guard.js +0 -0
  192. /package/lib/{src/games → games}/kl8-groups.js +0 -0
  193. /package/lib/{src/games → games}/registry.js +0 -0
  194. /package/lib/{src/recommend → recommend}/game-pick.js +0 -0
  195. /package/lib/{src/stats → stats}/engine.js +0 -0
  196. /package/lib/{src/stats → stats}/pro-analysis.js +0 -0
  197. /package/lib/{src/stats → stats}/weights.js +0 -0
  198. /package/lib/{src/sync → sync}/fetch-official.js +0 -0
  199. /package/lib/{src/sync → sync}/run-sync.js +0 -0
  200. /package/lib/{src/types.js → types.js} +0 -0
package/plugin.ts ADDED
@@ -0,0 +1,106 @@
1
+ import {
2
+ definePlugin,
3
+ databaseHostToken,
4
+ outboundHostToken,
5
+ scheduleHostToken,
6
+ } from '@zhin.js/plugin-runtime';
7
+ import {
8
+ lotteryEnabledGames,
9
+ lotteryKl8,
10
+ resolveLotteryConfig,
11
+ type LotteryConfig,
12
+ } from './src/config.js';
13
+ import { defineLotteryTables } from './src/db.js';
14
+ import { registerLotteryDb } from './src/db-store.js';
15
+ import { registerLotteryAgentDeps } from './src/lottery-agent-deps.js';
16
+ import { createInMemoryLotteryDb } from './src/memory-db.js';
17
+ import { lotteryRuntimeToken } from './src/runtime-state.js';
18
+ import { runLotteryPipeline } from './src/pipeline.js';
19
+ import { registerLotteryOutboundPush } from './src/push.js';
20
+
21
+ /**
22
+ * Plugin Runtime:
23
+ * - DB: prefer databaseHostToken; else memory.
24
+ * - Push: OutboundHost + config.pushTargets (cron / publish).
25
+ * - Cron: scheduleHostToken daily pipeline.
26
+ */
27
+ export default definePlugin<LotteryConfig>({
28
+ name: 'lottery',
29
+ metadata: {
30
+ displayName: 'Lottery',
31
+ },
32
+ setup(context) {
33
+ const config = resolveLotteryConfig(context.config.get());
34
+ const db = context.resources.has(databaseHostToken)
35
+ ? context.resources.use(databaseHostToken)
36
+ : createInMemoryLotteryDb();
37
+ if (context.resources.has(databaseHostToken)) {
38
+ defineLotteryTables(db);
39
+ }
40
+ context.resources.provide(lotteryRuntimeToken, { db });
41
+ context.lifecycle.add(registerLotteryDb(db));
42
+
43
+ let outboundPush: ((text: string) => Promise<void>) | null = null;
44
+ if (context.resources.has(outboundHostToken) && config.pushTargets.length > 0) {
45
+ const outbound = context.resources.use(outboundHostToken);
46
+ outboundPush = async (text) => {
47
+ for (const target of config.pushTargets) {
48
+ try {
49
+ await outbound.send({
50
+ adapter: target.adapter,
51
+ endpointId: target.endpointId || target.adapter,
52
+ channelType: target.channelType || 'private',
53
+ channelId: target.channelId,
54
+ content: text,
55
+ });
56
+ } catch {
57
+ // OutboundHost logs; continue remaining targets.
58
+ }
59
+ }
60
+ };
61
+ }
62
+ context.lifecycle.add(registerLotteryOutboundPush(outboundPush));
63
+
64
+ context.lifecycle.add(registerLotteryAgentDeps({
65
+ getDb: () => db,
66
+ getConfig: () => ({
67
+ pickCount: config.pickCount,
68
+ historyLimit: config.historyLimit,
69
+ kl8: lotteryKl8(config),
70
+ }),
71
+ enabledGames: () => lotteryEnabledGames(config),
72
+ scheduleCron: () => config.scheduleCron,
73
+ scheduleEnabled: () => config.scheduleEnabled,
74
+ pipelinePush: config.pushTargets.length > 0,
75
+ }));
76
+
77
+ if (!context.resources.has(scheduleHostToken) || !config.scheduleEnabled) {
78
+ return;
79
+ }
80
+ const schedule = context.resources.use(scheduleHostToken);
81
+ const dispose = schedule.register({
82
+ id: 'lottery/daily_pipeline',
83
+ cron: config.scheduleCron || '0 0 18 * * *',
84
+ description: 'Daily lottery pipeline',
85
+ async execute() {
86
+ await runLotteryPipeline({
87
+ getDb: () => db,
88
+ enabledGames: () => lotteryEnabledGames(config),
89
+ historyLimit: config.historyLimit,
90
+ pickCount: config.pickCount,
91
+ kl8: lotteryKl8(config),
92
+ backtest: {
93
+ enabled: config.backtestEnabled,
94
+ window: config.backtestWindow,
95
+ randomTrials: config.backtestRandomTrials,
96
+ minHistory: config.backtestMinHistory,
97
+ adaptive: config.backtestAdaptive,
98
+ },
99
+ weightPersist: config.weightPersistEnabled,
100
+ weightHoldoutFallback: config.weightHoldoutFallback,
101
+ }, { push: config.pushTargets.length > 0 });
102
+ },
103
+ });
104
+ context.lifecycle.add(dispose);
105
+ },
106
+ });
package/schema.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "additionalProperties": false,
5
+ "properties": {
6
+ "pickCount": { "type": "number", "default": 5, "minimum": 1, "maximum": 10 },
7
+ "scheduleCron": { "type": "string", "default": "0 0 18 * * *" },
8
+ "historyLimit": { "type": "number", "default": 500, "minimum": 50, "maximum": 2000 },
9
+ "scheduleEnabled": { "type": "boolean", "default": true },
10
+ "backtestEnabled": { "type": "boolean", "default": true },
11
+ "backtestWindow": { "type": "number", "default": 50 },
12
+ "backtestRandomTrials": { "type": "number", "default": 64 },
13
+ "backtestMinHistory": { "type": "number", "default": 30 },
14
+ "backtestAdaptive": { "type": "boolean", "default": true },
15
+ "weightPersistEnabled": { "type": "boolean", "default": true },
16
+ "weightHoldoutFallback": { "type": "boolean", "default": true },
17
+ "games": {
18
+ "type": "array",
19
+ "items": { "type": "string" },
20
+ "default": ["kl8", "ssq", "dlt", "fc3d", "pl3", "pl5"]
21
+ },
22
+ "pushTargets": {
23
+ "type": "array",
24
+ "description": "OutboundHost push destinations for cron/publish reports",
25
+ "items": {
26
+ "type": "object",
27
+ "additionalProperties": false,
28
+ "required": ["adapter", "channelId"],
29
+ "properties": {
30
+ "adapter": { "type": "string" },
31
+ "endpointId": { "type": "string" },
32
+ "channelType": { "type": "string", "default": "private" },
33
+ "channelId": { "type": "string" }
34
+ }
35
+ },
36
+ "default": []
37
+ },
38
+ "kl8": {
39
+ "type": "object",
40
+ "additionalProperties": false,
41
+ "properties": {
42
+ "pickCount": { "type": "number", "default": 5 },
43
+ "recommendGroups": { "type": "number", "default": 3 },
44
+ "groupStrategies": {
45
+ "type": "array",
46
+ "items": { "type": "string" },
47
+ "default": ["balanced", "hot", "cold"]
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,35 @@
1
+ import {
2
+ lotteryEnabledGames,
3
+ lotteryKl8,
4
+ resolveLotteryConfig,
5
+ type LotteryConfig,
6
+ } from './config.js';
7
+ import { getLotteryDb } from './db-store.js';
8
+ import type { LotteryDb } from './db.js';
9
+ import { formatPipelineReply, runLotteryPipeline, type PipelineDeps } from './pipeline.js';
10
+
11
+ export function buildPipelineDeps(
12
+ raw: Partial<LotteryConfig> | undefined,
13
+ db: LotteryDb | null = getLotteryDb(),
14
+ ): PipelineDeps {
15
+ const config = resolveLotteryConfig(raw);
16
+ return {
17
+ getDb: () => db,
18
+ enabledGames: () => lotteryEnabledGames(config),
19
+ historyLimit: config.historyLimit,
20
+ pickCount: config.pickCount,
21
+ kl8: lotteryKl8(config),
22
+ backtest: {
23
+ enabled: config.backtestEnabled,
24
+ window: config.backtestWindow,
25
+ randomTrials: config.backtestRandomTrials,
26
+ minHistory: config.backtestMinHistory,
27
+ adaptive: config.backtestAdaptive,
28
+ },
29
+ weightPersist: config.weightPersistEnabled,
30
+ weightHoldoutFallback: config.weightHoldoutFallback,
31
+ };
32
+ }
33
+
34
+ export { formatPipelineReply, runLotteryPipeline, resolveLotteryConfig, getLotteryDb };
35
+ export type { LotteryConfig };
package/src/config.ts ADDED
@@ -0,0 +1,83 @@
1
+ import type { Kl8Config, Kl8ConfigInput } from './games/kl8-groups.js';
2
+ import { resolveKl8Config } from './games/kl8-groups.js';
3
+ import type { GameId } from './types.js';
4
+ import { resolveEnabledGames } from './games/registry.js';
5
+
6
+ export interface LotteryConfig {
7
+ readonly pickCount: number;
8
+ readonly scheduleCron: string;
9
+ readonly historyLimit: number;
10
+ readonly scheduleEnabled: boolean;
11
+ readonly backtestEnabled: boolean;
12
+ readonly backtestWindow: number;
13
+ readonly backtestRandomTrials: number;
14
+ readonly backtestMinHistory: number;
15
+ readonly backtestAdaptive: boolean;
16
+ readonly weightPersistEnabled: boolean;
17
+ readonly weightHoldoutFallback: boolean;
18
+ readonly kl8?: Kl8ConfigInput;
19
+ readonly games: readonly string[];
20
+ /** Plugin Runtime OutboundHost push targets (cron / publish). */
21
+ readonly pushTargets: readonly LotteryPushTargetConfig[];
22
+ }
23
+
24
+ export interface LotteryPushTargetConfig {
25
+ readonly adapter: string;
26
+ readonly endpointId?: string;
27
+ readonly channelType?: string;
28
+ readonly channelId: string;
29
+ }
30
+
31
+ export const DEFAULT_LOTTERY_CONFIG: LotteryConfig = {
32
+ pickCount: 5,
33
+ scheduleCron: '0 0 18 * * *',
34
+ historyLimit: 500,
35
+ scheduleEnabled: true,
36
+ backtestEnabled: true,
37
+ backtestWindow: 50,
38
+ backtestRandomTrials: 64,
39
+ backtestMinHistory: 30,
40
+ backtestAdaptive: true,
41
+ weightPersistEnabled: true,
42
+ weightHoldoutFallback: true,
43
+ kl8: {
44
+ pickCount: 5,
45
+ recommendGroups: 3,
46
+ groupStrategies: ['balanced', 'hot', 'cold'],
47
+ },
48
+ games: ['kl8', 'ssq', 'dlt', 'fc3d', 'pl3', 'pl5'],
49
+ pushTargets: [],
50
+ };
51
+
52
+ export function resolveLotteryConfig(raw: Partial<LotteryConfig> | undefined): LotteryConfig {
53
+ const pushTargets = Array.isArray(raw?.pushTargets)
54
+ ? raw!.pushTargets
55
+ .filter((t): t is LotteryPushTargetConfig =>
56
+ !!t && typeof t === 'object'
57
+ && typeof t.adapter === 'string'
58
+ && typeof t.channelId === 'string'
59
+ && t.adapter.length > 0
60
+ && t.channelId.length > 0)
61
+ .map((t) => Object.freeze({
62
+ adapter: t.adapter,
63
+ endpointId: typeof t.endpointId === 'string' ? t.endpointId : t.adapter,
64
+ channelType: typeof t.channelType === 'string' ? t.channelType : 'private',
65
+ channelId: t.channelId,
66
+ }))
67
+ : [];
68
+ return {
69
+ ...DEFAULT_LOTTERY_CONFIG,
70
+ ...raw,
71
+ games: raw?.games?.length ? [...raw.games] : [...DEFAULT_LOTTERY_CONFIG.games],
72
+ kl8: { ...DEFAULT_LOTTERY_CONFIG.kl8, ...raw?.kl8 },
73
+ pushTargets,
74
+ };
75
+ }
76
+
77
+ export function lotteryKl8(config: LotteryConfig): Kl8Config {
78
+ return resolveKl8Config(config.pickCount, config.kl8);
79
+ }
80
+
81
+ export function lotteryEnabledGames(config: LotteryConfig): GameId[] {
82
+ return resolveEnabledGames([...config.games]);
83
+ }
@@ -0,0 +1,34 @@
1
+ import type { LotteryDb } from './db.js';
2
+ import { createInMemoryLotteryDb } from './memory-db.js';
3
+
4
+ let _db: LotteryDb | null = null;
5
+ const registrations: Array<{ readonly value: LotteryDb }> = [];
6
+
7
+ /** Ensure an in-memory store when Runtime database Resource is not wired yet. */
8
+ export function ensureLotteryMemoryDb(): LotteryDb {
9
+ if (!_db) _db = createInMemoryLotteryDb();
10
+ return _db;
11
+ }
12
+
13
+ export function getLotteryDb(): LotteryDb | null {
14
+ return registrations[registrations.length - 1]?.value ?? ensureLotteryMemoryDb();
15
+ }
16
+
17
+ export function setLotteryDb(db: LotteryDb | null): void {
18
+ _db = db;
19
+ }
20
+
21
+ /** Generation-owned database binding used by Plugin Runtime setup(). */
22
+ export function registerLotteryDb(db: LotteryDb): () => void {
23
+ const registration = Object.freeze({ value: db });
24
+ registrations.push(registration);
25
+ return () => {
26
+ const index = registrations.lastIndexOf(registration);
27
+ if (index >= 0) registrations.splice(index, 1);
28
+ };
29
+ }
30
+
31
+ /** Test helper: clear module store. */
32
+ export function resetLotteryDb(): void {
33
+ _db = null;
34
+ }
package/src/index.ts CHANGED
@@ -1,216 +1,13 @@
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;
1
+ export { resolveLotteryConfig, DEFAULT_LOTTERY_CONFIG } from './config.js';
2
+ export type { LotteryConfig } from './config.js';
3
+ export {
4
+ getLotteryDb,
5
+ registerLotteryDb,
6
+ setLotteryDb,
7
+ ensureLotteryMemoryDb,
8
+ resetLotteryDb,
9
+ } from './db-store.js';
10
+ export { createInMemoryLotteryDb } from './memory-db.js';
11
+ export { lotteryRuntimeToken, resolveLotteryRuntime } from './runtime-state.js';
12
+ export type { LotteryRuntime } from './runtime-state.js';
13
+ export { formatPipelineReply, runLotteryPipeline } from './pipeline.js';
@@ -2,7 +2,6 @@
2
2
  * Shared runtime deps for lottery agent/ authoring tools.
3
3
  * Set once from plugin index during init — never call getPlugin() from tool execute.
4
4
  */
5
- import type { Plugin } from 'zhin.js';
6
5
  import type { GameId } from './types.js';
7
6
  import type { LotteryDb } from './db.js';
8
7
  import type { Kl8Config } from './games/kl8-groups.js';
@@ -10,7 +9,6 @@ import type { Kl8Config } from './games/kl8-groups.js';
10
9
  export interface LotteryAgentDeps {
11
10
  getDb: () => LotteryDb | null;
12
11
  getConfig: () => { pickCount: number; historyLimit: number; kl8: Kl8Config };
13
- plugin: Plugin;
14
12
  enabledGames: () => GameId[];
15
13
  scheduleCron: () => string;
16
14
  scheduleEnabled: () => boolean;
@@ -18,12 +16,24 @@ export interface LotteryAgentDeps {
18
16
  }
19
17
 
20
18
  let _deps: LotteryAgentDeps | null = null;
19
+ const registrations: Array<{ readonly value: LotteryAgentDeps }> = [];
21
20
 
22
21
  export function setLotteryAgentDeps(deps: LotteryAgentDeps): void {
23
22
  _deps = deps;
24
23
  }
25
24
 
25
+ /** Generation-owned Agent dependency binding used by Plugin Runtime setup(). */
26
+ export function registerLotteryAgentDeps(deps: LotteryAgentDeps): () => void {
27
+ const registration = Object.freeze({ value: deps });
28
+ registrations.push(registration);
29
+ return () => {
30
+ const index = registrations.lastIndexOf(registration);
31
+ if (index >= 0) registrations.splice(index, 1);
32
+ };
33
+ }
34
+
26
35
  export function getLotteryAgentDeps(): LotteryAgentDeps {
27
- if (!_deps) throw new Error('lottery agent deps not initialized');
28
- return _deps;
36
+ const deps = registrations[registrations.length - 1]?.value ?? _deps;
37
+ if (!deps) throw new Error('lottery agent deps not initialized');
38
+ return deps;
29
39
  }
@@ -14,7 +14,7 @@ import {
14
14
  loadAgentMemory,
15
15
  } from './evaluate/tracker.js';
16
16
  import { saveDailyReport } from './recommend/report.js';
17
- import { pushTextToMasters } from './push.js';
17
+ import { pushLotteryReport, getLotteryOutboundPush } from './push.js';
18
18
  import { normalizeWeights } from './stats/weights.js';
19
19
  import { getLotteryAgentDeps } from './lottery-agent-deps.js';
20
20
 
@@ -142,8 +142,8 @@ export async function handlePublishReport(content: string, push?: boolean): Prom
142
142
  if (!text.includes(DISCLAIMER)) return 'content must include disclaimer';
143
143
  const date = new Date().toISOString().slice(0, 10);
144
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);
145
+ const doPush = push === true && Boolean(getLotteryOutboundPush());
146
+ if (doPush) await pushLotteryReport(text);
147
147
  return JSON.stringify({ ok: true, date, pushed: doPush });
148
148
  }
149
149
 
@@ -0,0 +1,60 @@
1
+ import type { LotteryDb, LotteryModel } from './db.js';
2
+ import {
3
+ DRAWS_TABLE,
4
+ REPORTS_TABLE,
5
+ } from './db.js';
6
+ import {
7
+ MEMORY_TABLE,
8
+ PREDICTIONS_TABLE,
9
+ WEIGHTS_TABLE,
10
+ } from './evaluate/tracker.js';
11
+
12
+ /** Minimal in-memory model matching LotteryModel (select/insert/delete). */
13
+ function createMemoryModel(): LotteryModel {
14
+ const rows: Record<string, unknown>[] = [];
15
+
16
+ function matches(row: Record<string, unknown>, query: Record<string, unknown>): boolean {
17
+ return Object.entries(query).every(([key, value]) => row[key] === value);
18
+ }
19
+
20
+ return {
21
+ select: () => ({
22
+ where: async (query: Record<string, unknown>) => rows.filter((row) => matches(row, query)),
23
+ }),
24
+ insert: async (row: Record<string, unknown>) => {
25
+ rows.push({ ...row });
26
+ return row;
27
+ },
28
+ delete: () => ({
29
+ where: async (query: Record<string, unknown>) => {
30
+ for (let i = rows.length - 1; i >= 0; i -= 1) {
31
+ if (matches(rows[i]!, query)) rows.splice(i, 1);
32
+ }
33
+ },
34
+ }),
35
+ };
36
+ }
37
+
38
+ const TABLE_NAMES = [
39
+ DRAWS_TABLE,
40
+ REPORTS_TABLE,
41
+ PREDICTIONS_TABLE,
42
+ WEIGHTS_TABLE,
43
+ MEMORY_TABLE,
44
+ ] as const;
45
+
46
+ /**
47
+ * Slice-2 fallback while Plugin Runtime has no DatabaseFeature Resource path.
48
+ * Enough for smoke: today/history/stats/train/backtest/pipeline no longer return「数据库未就绪」.
49
+ */
50
+ export function createInMemoryLotteryDb(): LotteryDb {
51
+ const models = new Map<string, LotteryModel>();
52
+ for (const name of TABLE_NAMES) {
53
+ models.set(name, createMemoryModel());
54
+ }
55
+ return {
56
+ models: {
57
+ get: (name: string) => models.get(name),
58
+ },
59
+ };
60
+ }