@sudobility/sudojo_lib 0.0.201 → 0.0.203

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 (263) hide show
  1. package/dist/admin/adminApi.d.ts +34 -0
  2. package/dist/admin/adminApi.d.ts.map +1 -0
  3. package/dist/admin/adminApi.js +59 -0
  4. package/dist/admin/adminApi.js.map +1 -0
  5. package/dist/admin/boardRules.d.ts +75 -0
  6. package/dist/admin/boardRules.d.ts.map +1 -0
  7. package/dist/admin/boardRules.js +127 -0
  8. package/dist/admin/boardRules.js.map +1 -0
  9. package/dist/admin/boardStrings.d.ts +55 -0
  10. package/dist/admin/boardStrings.d.ts.map +1 -0
  11. package/dist/admin/boardStrings.js +99 -0
  12. package/dist/admin/boardStrings.js.map +1 -0
  13. package/dist/admin/examples.d.ts +58 -0
  14. package/dist/admin/examples.d.ts.map +1 -0
  15. package/dist/admin/examples.js +104 -0
  16. package/dist/admin/examples.js.map +1 -0
  17. package/dist/admin/extraction.d.ts +87 -0
  18. package/dist/admin/extraction.d.ts.map +1 -0
  19. package/dist/admin/extraction.js +141 -0
  20. package/dist/admin/extraction.js.map +1 -0
  21. package/dist/admin/index.d.ts +32 -0
  22. package/dist/admin/index.d.ts.map +1 -0
  23. package/dist/admin/index.js +22 -0
  24. package/dist/admin/index.js.map +1 -0
  25. package/dist/admin/jobs.d.ts +108 -0
  26. package/dist/admin/jobs.d.ts.map +1 -0
  27. package/dist/admin/jobs.js +435 -0
  28. package/dist/admin/jobs.js.map +1 -0
  29. package/dist/admin/regenerateHints.d.ts +16 -0
  30. package/dist/admin/regenerateHints.d.ts.map +1 -0
  31. package/dist/admin/regenerateHints.js +43 -0
  32. package/dist/admin/regenerateHints.js.map +1 -0
  33. package/dist/admin/types.d.ts +42 -0
  34. package/dist/admin/types.d.ts.map +1 -0
  35. package/dist/admin/types.js +21 -0
  36. package/dist/admin/types.js.map +1 -0
  37. package/dist/admin/useAdminJob.d.ts +28 -0
  38. package/dist/admin/useAdminJob.d.ts.map +1 -0
  39. package/dist/admin/useAdminJob.js +99 -0
  40. package/dist/admin/useAdminJob.js.map +1 -0
  41. package/dist/admin/useAdminStats.d.ts +46 -0
  42. package/dist/admin/useAdminStats.d.ts.map +1 -0
  43. package/dist/admin/useAdminStats.js +91 -0
  44. package/dist/admin/useAdminStats.js.map +1 -0
  45. package/dist/admin/useBoardGenerator.d.ts +29 -0
  46. package/dist/admin/useBoardGenerator.d.ts.map +1 -0
  47. package/dist/admin/useBoardGenerator.js +38 -0
  48. package/dist/admin/useBoardGenerator.js.map +1 -0
  49. package/dist/admin/useBoardTechniquesUpdater.d.ts +14 -0
  50. package/dist/admin/useBoardTechniquesUpdater.d.ts.map +1 -0
  51. package/dist/admin/useBoardTechniquesUpdater.js +28 -0
  52. package/dist/admin/useBoardTechniquesUpdater.js.map +1 -0
  53. package/dist/admin/useExampleCreator.d.ts +29 -0
  54. package/dist/admin/useExampleCreator.d.ts.map +1 -0
  55. package/dist/admin/useExampleCreator.js +58 -0
  56. package/dist/admin/useExampleCreator.js.map +1 -0
  57. package/dist/admin/useSingleBoardExtractor.d.ts +18 -0
  58. package/dist/admin/useSingleBoardExtractor.d.ts.map +1 -0
  59. package/dist/admin/useSingleBoardExtractor.js +19 -0
  60. package/dist/admin/useSingleBoardExtractor.js.map +1 -0
  61. package/dist/admin/useTechniqueExtractor.d.ts +26 -0
  62. package/dist/admin/useTechniqueExtractor.d.ts.map +1 -0
  63. package/dist/admin/useTechniqueExtractor.js +37 -0
  64. package/dist/admin/useTechniqueExtractor.js.map +1 -0
  65. package/dist/context/SudojoApiContext.d.ts +58 -0
  66. package/dist/context/SudojoApiContext.d.ts.map +1 -0
  67. package/dist/context/SudojoApiContext.js +52 -0
  68. package/dist/context/SudojoApiContext.js.map +1 -0
  69. package/dist/context/SudojoApiProvider.d.ts +21 -0
  70. package/dist/context/SudojoApiProvider.d.ts.map +1 -0
  71. package/dist/context/SudojoApiProvider.js +15 -0
  72. package/dist/context/SudojoApiProvider.js.map +1 -0
  73. package/dist/context/index.d.ts +3 -0
  74. package/dist/context/index.d.ts.map +1 -1
  75. package/dist/context/index.js +2 -0
  76. package/dist/context/index.js.map +1 -1
  77. package/dist/hooks/index.d.ts +24 -1
  78. package/dist/hooks/index.d.ts.map +1 -1
  79. package/dist/hooks/index.js +22 -0
  80. package/dist/hooks/index.js.map +1 -1
  81. package/dist/hooks/useBoardEntry.d.ts +65 -5
  82. package/dist/hooks/useBoardEntry.d.ts.map +1 -1
  83. package/dist/hooks/useBoardEntry.js +124 -27
  84. package/dist/hooks/useBoardEntry.js.map +1 -1
  85. package/dist/hooks/useBoardScan.d.ts +27 -0
  86. package/dist/hooks/useBoardScan.d.ts.map +1 -0
  87. package/dist/hooks/useBoardScan.js +27 -0
  88. package/dist/hooks/useBoardScan.js.map +1 -0
  89. package/dist/hooks/useCommunities.d.ts +6 -4
  90. package/dist/hooks/useCommunities.d.ts.map +1 -1
  91. package/dist/hooks/useCommunities.js +6 -1
  92. package/dist/hooks/useCommunities.js.map +1 -1
  93. package/dist/hooks/useCompletionTrigger.d.ts +23 -0
  94. package/dist/hooks/useCompletionTrigger.d.ts.map +1 -0
  95. package/dist/hooks/useCompletionTrigger.js +39 -0
  96. package/dist/hooks/useCompletionTrigger.js.map +1 -0
  97. package/dist/hooks/useDailyGame.d.ts +11 -6
  98. package/dist/hooks/useDailyGame.d.ts.map +1 -1
  99. package/dist/hooks/useDailyGame.js +20 -61
  100. package/dist/hooks/useDailyGame.js.map +1 -1
  101. package/dist/hooks/useDeleteAccount.d.ts +72 -0
  102. package/dist/hooks/useDeleteAccount.d.ts.map +1 -0
  103. package/dist/hooks/useDeleteAccount.js +71 -0
  104. package/dist/hooks/useDeleteAccount.js.map +1 -0
  105. package/dist/hooks/useEnteredGameSession.d.ts +73 -0
  106. package/dist/hooks/useEnteredGameSession.d.ts.map +1 -0
  107. package/dist/hooks/useEnteredGameSession.js +155 -0
  108. package/dist/hooks/useEnteredGameSession.js.map +1 -0
  109. package/dist/hooks/useGameSession.d.ts +8 -4
  110. package/dist/hooks/useGameSession.d.ts.map +1 -1
  111. package/dist/hooks/useGameSession.js +4 -1
  112. package/dist/hooks/useGameSession.js.map +1 -1
  113. package/dist/hooks/useGameTeaching.d.ts.map +1 -1
  114. package/dist/hooks/useGameTeaching.js +11 -13
  115. package/dist/hooks/useGameTeaching.js.map +1 -1
  116. package/dist/hooks/useHint.d.ts +7 -7
  117. package/dist/hooks/useHint.d.ts.map +1 -1
  118. package/dist/hooks/useHint.js +18 -9
  119. package/dist/hooks/useHint.js.map +1 -1
  120. package/dist/hooks/useIsSiteAdmin.d.ts +39 -0
  121. package/dist/hooks/useIsSiteAdmin.d.ts.map +1 -0
  122. package/dist/hooks/useIsSiteAdmin.js +31 -0
  123. package/dist/hooks/useIsSiteAdmin.js.map +1 -0
  124. package/dist/hooks/useLearning.d.ts +8 -8
  125. package/dist/hooks/useLearning.d.ts.map +1 -1
  126. package/dist/hooks/useLearning.js +5 -2
  127. package/dist/hooks/useLearning.js.map +1 -1
  128. package/dist/hooks/useLevelGame.d.ts +7 -7
  129. package/dist/hooks/useLevelGame.d.ts.map +1 -1
  130. package/dist/hooks/useLevelGame.js +13 -49
  131. package/dist/hooks/useLevelGame.js.map +1 -1
  132. package/dist/hooks/useLevels.d.ts +8 -8
  133. package/dist/hooks/useLevels.d.ts.map +1 -1
  134. package/dist/hooks/useLevels.js +7 -3
  135. package/dist/hooks/useLevels.js.map +1 -1
  136. package/dist/hooks/usePracticeGame.d.ts +53 -0
  137. package/dist/hooks/usePracticeGame.d.ts.map +1 -0
  138. package/dist/hooks/usePracticeGame.js +72 -0
  139. package/dist/hooks/usePracticeGame.js.map +1 -0
  140. package/dist/hooks/usePuzzleDistribution.d.ts +2 -0
  141. package/dist/hooks/usePuzzleDistribution.d.ts.map +1 -1
  142. package/dist/hooks/usePuzzleDistribution.js +4 -14
  143. package/dist/hooks/usePuzzleDistribution.js.map +1 -1
  144. package/dist/hooks/usePuzzleProgress.d.ts +37 -0
  145. package/dist/hooks/usePuzzleProgress.d.ts.map +1 -0
  146. package/dist/hooks/usePuzzleProgress.js +48 -0
  147. package/dist/hooks/usePuzzleProgress.js.map +1 -0
  148. package/dist/hooks/usePuzzleSession.d.ts +215 -0
  149. package/dist/hooks/usePuzzleSession.d.ts.map +1 -0
  150. package/dist/hooks/usePuzzleSession.js +358 -0
  151. package/dist/hooks/usePuzzleSession.js.map +1 -0
  152. package/dist/hooks/useResumeGame.d.ts +70 -0
  153. package/dist/hooks/useResumeGame.d.ts.map +1 -0
  154. package/dist/hooks/useResumeGame.js +123 -0
  155. package/dist/hooks/useResumeGame.js.map +1 -0
  156. package/dist/hooks/useStrategies.d.ts +39 -0
  157. package/dist/hooks/useStrategies.d.ts.map +1 -0
  158. package/dist/hooks/useStrategies.js +34 -0
  159. package/dist/hooks/useStrategies.js.map +1 -0
  160. package/dist/hooks/useSudoku.d.ts +5 -0
  161. package/dist/hooks/useSudoku.d.ts.map +1 -1
  162. package/dist/hooks/useSudoku.js +7 -1
  163. package/dist/hooks/useSudoku.js.map +1 -1
  164. package/dist/hooks/useTechniqueByPath.d.ts +46 -0
  165. package/dist/hooks/useTechniqueByPath.d.ts.map +1 -0
  166. package/dist/hooks/useTechniqueByPath.js +47 -0
  167. package/dist/hooks/useTechniqueByPath.js.map +1 -0
  168. package/dist/hooks/useTechniqueExample.d.ts +60 -0
  169. package/dist/hooks/useTechniqueExample.d.ts.map +1 -0
  170. package/dist/hooks/useTechniqueExample.js +63 -0
  171. package/dist/hooks/useTechniqueExample.js.map +1 -0
  172. package/dist/hooks/useTechniques.d.ts +8 -8
  173. package/dist/hooks/useTechniques.d.ts.map +1 -1
  174. package/dist/hooks/useTechniques.js +9 -21
  175. package/dist/hooks/useTechniques.js.map +1 -1
  176. package/dist/index.d.ts +418 -11
  177. package/dist/index.d.ts.map +1 -1
  178. package/dist/index.js +276 -10
  179. package/dist/index.js.map +1 -1
  180. package/dist/types/currentGame.d.ts +9 -1
  181. package/dist/types/currentGame.d.ts.map +1 -1
  182. package/dist/types/currentGame.js.map +1 -1
  183. package/dist/utils/auth.d.ts +6 -0
  184. package/dist/utils/auth.d.ts.map +1 -1
  185. package/dist/utils/auth.js +8 -0
  186. package/dist/utils/auth.js.map +1 -1
  187. package/dist/utils/community.d.ts +64 -0
  188. package/dist/utils/community.d.ts.map +1 -0
  189. package/dist/utils/community.js +66 -0
  190. package/dist/utils/community.js.map +1 -0
  191. package/dist/utils/date.d.ts +37 -0
  192. package/dist/utils/date.d.ts.map +1 -0
  193. package/dist/utils/date.js +68 -0
  194. package/dist/utils/date.js.map +1 -0
  195. package/dist/utils/entityTranslate.d.ts +120 -0
  196. package/dist/utils/entityTranslate.d.ts.map +1 -0
  197. package/dist/utils/entityTranslate.js +146 -0
  198. package/dist/utils/entityTranslate.js.map +1 -0
  199. package/dist/utils/gameFetchStatus.d.ts +74 -0
  200. package/dist/utils/gameFetchStatus.d.ts.map +1 -0
  201. package/dist/utils/gameFetchStatus.js +118 -0
  202. package/dist/utils/gameFetchStatus.js.map +1 -0
  203. package/dist/utils/gamification.d.ts +36 -0
  204. package/dist/utils/gamification.d.ts.map +1 -0
  205. package/dist/utils/gamification.js +33 -0
  206. package/dist/utils/gamification.js.map +1 -0
  207. package/dist/utils/hintAccess.d.ts +64 -0
  208. package/dist/utils/hintAccess.d.ts.map +1 -0
  209. package/dist/utils/hintAccess.js +57 -0
  210. package/dist/utils/hintAccess.js.map +1 -0
  211. package/dist/utils/index.d.ts +31 -10
  212. package/dist/utils/index.d.ts.map +1 -1
  213. package/dist/utils/index.js +20 -9
  214. package/dist/utils/index.js.map +1 -1
  215. package/dist/utils/language.d.ts +123 -0
  216. package/dist/utils/language.d.ts.map +1 -0
  217. package/dist/utils/language.js +95 -0
  218. package/dist/utils/language.js.map +1 -0
  219. package/dist/utils/localizedHint.d.ts +60 -0
  220. package/dist/utils/localizedHint.d.ts.map +1 -1
  221. package/dist/utils/localizedHint.js +90 -0
  222. package/dist/utils/localizedHint.js.map +1 -1
  223. package/dist/utils/progress.d.ts +10 -1
  224. package/dist/utils/progress.d.ts.map +1 -1
  225. package/dist/utils/progress.js +24 -0
  226. package/dist/utils/progress.js.map +1 -1
  227. package/dist/utils/scannedBoard.d.ts +79 -0
  228. package/dist/utils/scannedBoard.d.ts.map +1 -0
  229. package/dist/utils/scannedBoard.js +96 -0
  230. package/dist/utils/scannedBoard.js.map +1 -0
  231. package/dist/utils/shareUrl.d.ts +38 -15
  232. package/dist/utils/shareUrl.d.ts.map +1 -1
  233. package/dist/utils/shareUrl.js +75 -26
  234. package/dist/utils/shareUrl.js.map +1 -1
  235. package/dist/utils/subscription.d.ts +118 -0
  236. package/dist/utils/subscription.d.ts.map +1 -1
  237. package/dist/utils/subscription.js +212 -0
  238. package/dist/utils/subscription.js.map +1 -1
  239. package/dist/utils/sudokuPresenter.d.ts +42 -0
  240. package/dist/utils/sudokuPresenter.d.ts.map +1 -1
  241. package/dist/utils/sudokuPresenter.js +114 -1
  242. package/dist/utils/sudokuPresenter.js.map +1 -1
  243. package/dist/utils/sudokuScrambler.d.ts +8 -0
  244. package/dist/utils/sudokuScrambler.d.ts.map +1 -1
  245. package/dist/utils/sudokuScrambler.js +12 -0
  246. package/dist/utils/sudokuScrambler.js.map +1 -1
  247. package/dist/utils/technique.d.ts +112 -1
  248. package/dist/utils/technique.d.ts.map +1 -1
  249. package/dist/utils/technique.js +293 -1
  250. package/dist/utils/technique.js.map +1 -1
  251. package/dist/utils/techniqueBitmask.d.ts +31 -0
  252. package/dist/utils/techniqueBitmask.d.ts.map +1 -0
  253. package/dist/utils/techniqueBitmask.js +53 -0
  254. package/dist/utils/techniqueBitmask.js.map +1 -0
  255. package/dist/utils/techniqueExample.d.ts +42 -0
  256. package/dist/utils/techniqueExample.d.ts.map +1 -0
  257. package/dist/utils/techniqueExample.js +57 -0
  258. package/dist/utils/techniqueExample.js.map +1 -0
  259. package/dist/utils/theme.d.ts +2 -0
  260. package/dist/utils/theme.d.ts.map +1 -1
  261. package/dist/utils/theme.js +2 -0
  262. package/dist/utils/theme.js.map +1 -1
  263. package/package.json +7 -7
@@ -0,0 +1,435 @@
1
+ /**
2
+ * Admin batch jobs as framework-agnostic async functions over an
3
+ * {@link AdminApi}. The hooks in this folder wrap them with React state; the
4
+ * functions themselves hold the business rules (token refresh cadence, retry
5
+ * delays, stop conditions, technique → level search fallback).
6
+ *
7
+ * Ported from `sudojo_app/src/utils/admin/{BoardGenerator,TechniqueExtractor,
8
+ * ExampleCreator,boardTechniquesApi}.ts`; progress messages are unchanged.
9
+ */
10
+ import { getTechniqueNameById, techniqueBitmaskOf, } from '@sudobility/sudojo_types';
11
+ import { boardsAtLevelQuery, boardsWithoutTechniquesQuery, boardsWithTechniqueQuery, buildBoardTechniquesUpdate, buildValidatedBoard, compareExtractionWithSolver, parseGeneratedBoard, } from './boardRules';
12
+ import { describeTechniqueMask, errorMessage, formatTechniqueMaskHex, levelToSave, shortUuid, } from './boardStrings';
13
+ import { ADMIN_EXAMPLE_TARGET_PER_TECHNIQUE, ADMIN_TECHNIQUE_ORDER, BOARDS_PER_LEVEL_LIMIT, BOARDS_WITH_TECHNIQUE_LIMIT, buildExampleSaveRequests, buildTechniqueLevelMap, countForTechnique, hasReachedTarget, levelsToSearchForTechnique, } from './examples';
14
+ import { extractBoardTechniques, isExtractionError, isTechniqueFound, } from './extraction';
15
+ import { ADMIN_TOKEN_REFRESH_INTERVAL } from './types';
16
+ /**
17
+ * Get the starting token. Returns null when `getToken` yields none (the job
18
+ * should report "Not authenticated"). Refreshes log "Token refreshed,
19
+ * continuing..." through `onProgress`; refresh failures only warn.
20
+ */
21
+ export async function createAdminTokenManager(getToken, onProgress) {
22
+ const initial = await getToken();
23
+ if (!initial)
24
+ return null;
25
+ let current = initial;
26
+ return {
27
+ get current() {
28
+ return current;
29
+ },
30
+ async refresh() {
31
+ try {
32
+ const next = await getToken();
33
+ if (next) {
34
+ current = next;
35
+ onProgress('Token refreshed, continuing...');
36
+ }
37
+ }
38
+ catch (err) {
39
+ // The existing token might still be valid.
40
+ console.warn('[admin] Token refresh failed:', err);
41
+ }
42
+ },
43
+ };
44
+ }
45
+ /** True when the job should refresh its token before item `processed + 1`. */
46
+ export function shouldRefreshToken(processed, interval = ADMIN_TOKEN_REFRESH_INTERVAL) {
47
+ return processed > 0 && processed % interval === 0;
48
+ }
49
+ const defaultDelay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
50
+ const techniqueName = (id) => getTechniqueNameById(id);
51
+ // ============================================================================
52
+ // Board API helpers (were boardTechniquesApi.ts)
53
+ // ============================================================================
54
+ /**
55
+ * Set a board's techniques bitmask (sent as an exact string) and level.
56
+ * Returns false on any failure.
57
+ */
58
+ export async function updateBoardTechniques(api, token, boardUuid, techniques, level) {
59
+ try {
60
+ const response = await api.updateBoard(token, boardUuid, buildBoardTechniquesUpdate(techniques, level));
61
+ return response.success;
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ }
67
+ /**
68
+ * Boards with the technique's bit set (API `technique_bit` filter), any
69
+ * level. Returns [] on any failure.
70
+ */
71
+ export async function fetchBoardsWithTechnique(api, token, techniqueId, limit) {
72
+ try {
73
+ const response = await api.getBoards(token, boardsWithTechniqueQuery(techniqueId, limit));
74
+ return response.success && response.data ? response.data : [];
75
+ }
76
+ catch {
77
+ return [];
78
+ }
79
+ }
80
+ /** The next board whose techniques were never extracted, or null. */
81
+ export async function fetchBoardWithoutTechniques(api, token) {
82
+ try {
83
+ const response = await api.getBoards(token, boardsWithoutTechniquesQuery());
84
+ return (response.success && response.data?.[0]) || null;
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
90
+ /**
91
+ * Generate, validate and save boards until aborted or a save fails.
92
+ * Each board: /generate → /validate (source of truth for level, techniques,
93
+ * solution) → create. Generate/validate failures wait 1 s and retry.
94
+ * Returns the number of boards saved.
95
+ */
96
+ export async function runBoardGeneration(ctx, options) {
97
+ const { api, token, abort, onProgress, onError } = ctx;
98
+ const delay = ctx.delay ?? defaultDelay;
99
+ onProgress('Fetching levels...');
100
+ try {
101
+ const levels = await api.getLevels();
102
+ if (!levels.success || !levels.data) {
103
+ onError(new Error('Failed to fetch levels'));
104
+ return 0;
105
+ }
106
+ if (levels.data.length === 0) {
107
+ onError(new Error('No levels found'));
108
+ return 0;
109
+ }
110
+ }
111
+ catch (err) {
112
+ onError(err instanceof Error ? err : new Error(String(err)));
113
+ return 0;
114
+ }
115
+ onProgress('Starting generation...');
116
+ let count = 0;
117
+ while (!abort.shouldAbort()) {
118
+ if (shouldRefreshToken(count))
119
+ await token.refresh();
120
+ try {
121
+ onProgress(`Generating board ${count + 1}...`);
122
+ const generated = await api.generate(token.current, {
123
+ symmetrical: options.symmetrical,
124
+ });
125
+ if (!generated.success || !generated.data) {
126
+ onProgress(`Error generating board: ${generated.error || 'Unknown error'}`);
127
+ await delay(1000);
128
+ continue;
129
+ }
130
+ const puzzle = parseGeneratedBoard(generated.data);
131
+ if (!puzzle.ok) {
132
+ onProgress(puzzle.reason === 'invalid'
133
+ ? 'Error: Invalid response structure from solver'
134
+ : 'Error: Missing puzzle data from solver');
135
+ await delay(1000);
136
+ continue;
137
+ }
138
+ onProgress(`Validating board ${count + 1}...`);
139
+ const validated = await api.validate(token.current, {
140
+ original: puzzle.original,
141
+ });
142
+ if (!validated.success || !validated.data) {
143
+ onProgress(`Error validating board: ${validated.error || 'Unknown error'}`);
144
+ await delay(1000);
145
+ continue;
146
+ }
147
+ const board = buildValidatedBoard(puzzle, validated.data, options.symmetrical);
148
+ onProgress(`Saving board ${count + 1}...`);
149
+ const created = await api.createBoard(token.current, board.request);
150
+ if (!created.success) {
151
+ onProgress(`Error saving board: ${created.error || 'Unknown error'}. Stopping.`);
152
+ break;
153
+ }
154
+ count++;
155
+ options.onBoardGenerated?.(count, board.level, board.techniques);
156
+ onProgress(`Generated ${count} boards (level ${board.level}, techniques: ${formatTechniqueMaskHex(board.techniques)})`);
157
+ await delay(100);
158
+ }
159
+ catch (err) {
160
+ onProgress(`Error: ${err instanceof Error ? err.message : 'Unknown error'}`);
161
+ await delay(1000);
162
+ }
163
+ }
164
+ onProgress(`Stopped. Generated ${count} boards.`);
165
+ return count;
166
+ }
167
+ /**
168
+ * Process boards without techniques one by one until none are left, aborted,
169
+ * or a critical error (save failure, mismatch, invalid pencilmarks).
170
+ * Returns the number of boards saved.
171
+ */
172
+ export async function runTechniqueExtraction(ctx, options) {
173
+ const { api, token, abort, onProgress, onError } = ctx;
174
+ let extracted = 0;
175
+ let processed = 0;
176
+ /** Save; false = stop. */
177
+ const save = async (board, techniques, level) => {
178
+ const finalLevel = levelToSave(level);
179
+ onProgress(`Saving board ${shortUuid(board.uuid)} - ` +
180
+ `techniques: ${formatTechniqueMaskHex(techniques)}, level: ${finalLevel}...`);
181
+ const ok = await updateBoardTechniques(api, token.current, board.uuid, techniques, finalLevel);
182
+ if (!ok) {
183
+ onProgress(`Failed to save board ${shortUuid(board.uuid)}. Stopping.`);
184
+ return false;
185
+ }
186
+ extracted++;
187
+ options.onBoardExtracted?.();
188
+ onProgress(`Extracted ${extracted} boards. Finding next...`);
189
+ return true;
190
+ };
191
+ const validate = (board) => api.validate(token.current, { original: board.board });
192
+ const processSimple = async (board) => {
193
+ const id = shortUuid(board.uuid);
194
+ onProgress(`Validating board ${id}...`);
195
+ try {
196
+ const response = await validate(board);
197
+ if (!response.success || !response.data?.board) {
198
+ onProgress(`Validate failed for ${id}, skipping...`);
199
+ return true;
200
+ }
201
+ const solverBoard = response.data.board;
202
+ return await save(board, techniqueBitmaskOf(solverBoard), solverBoard.level);
203
+ }
204
+ catch (err) {
205
+ onProgress(`Validate error for ${id}: ${errorMessage(err)}`);
206
+ return true;
207
+ }
208
+ };
209
+ const compareAndSave = async (board, frontend) => {
210
+ const id = shortUuid(board.uuid);
211
+ if (frontend.techniques === 0n && frontend.level === 0) {
212
+ onProgress(`Skipped board ${id} (no techniques found). Finding next...`);
213
+ return true;
214
+ }
215
+ onProgress(`Validating board ${id}...`);
216
+ try {
217
+ const response = await validate(board);
218
+ if (!response.success || !response.data?.board) {
219
+ onProgress(`Validate failed for ${id}, skipping...`);
220
+ return true;
221
+ }
222
+ const solver = response.data.board;
223
+ const mismatch = compareExtractionWithSolver(board.uuid, frontend, solver);
224
+ if (mismatch) {
225
+ console.log('[TechniqueExtractor] MISMATCH DETECTED');
226
+ console.log('[TechniqueExtractor] Board UUID:', board.uuid);
227
+ console.log('[TechniqueExtractor] Board puzzle:', board.board);
228
+ console.log('[TechniqueExtractor] Frontend result:', {
229
+ ...describeTechniqueMask(frontend.techniques),
230
+ level: frontend.level,
231
+ });
232
+ console.log('[TechniqueExtractor] Solver (/validate) result:', {
233
+ ...describeTechniqueMask(mismatch.solverTechniques),
234
+ level: mismatch.solverLevel,
235
+ });
236
+ onError(new Error(mismatch.message));
237
+ return false; // Investigate the discrepancy.
238
+ }
239
+ return await save(board, frontend.techniques, frontend.level);
240
+ }
241
+ catch (err) {
242
+ onProgress(`Validate error for ${id}: ${errorMessage(err)}`);
243
+ return true;
244
+ }
245
+ };
246
+ const processWithTest = async (board) => {
247
+ onProgress(`Processing board ${shortUuid(board.uuid)}...`);
248
+ const result = await extractBoardTechniques(opts => api.solve(token.current, opts), board.board);
249
+ if (isExtractionError(result)) {
250
+ onError(new Error(`Board ${shortUuid(board.uuid)}: ${result.error}`));
251
+ // Invalid pencilmarks means a technique bug: stop and investigate.
252
+ return !result.error.includes('Invalid pencilmarks');
253
+ }
254
+ if (isTechniqueFound(result))
255
+ return true; // Not possible without a filter.
256
+ return compareAndSave(board, result);
257
+ };
258
+ const processBoard = options.testWithFrontend
259
+ ? processWithTest
260
+ : processSimple;
261
+ while (!abort.shouldAbort()) {
262
+ if (shouldRefreshToken(processed))
263
+ await token.refresh();
264
+ const board = await fetchBoardWithoutTechniques(api, token.current);
265
+ if (!board) {
266
+ onProgress(`Done! Extracted techniques for ${extracted} boards. No more boards.`);
267
+ break;
268
+ }
269
+ if (!(await processBoard(board)))
270
+ break;
271
+ processed++;
272
+ }
273
+ if (!abort.shouldAbort()) {
274
+ onProgress(`Done! Extracted techniques for ${extracted} boards.`);
275
+ }
276
+ return extracted;
277
+ }
278
+ /**
279
+ * Create technique examples (each with a linked practice). A board fetched by
280
+ * technique bit that turns out not to contain the technique is reset to
281
+ * techniques 0 so the extractor re-processes it. Any failed save stops the
282
+ * job (aborts). Returns the final per-technique counts.
283
+ */
284
+ export async function runExampleCreation(ctx, request, callbacks = {}) {
285
+ const { api, token, abort, onProgress, onError } = ctx;
286
+ const target = request.targetPerTechnique ?? ADMIN_EXAMPLE_TARGET_PER_TECHNIQUE;
287
+ const counts = { ...request.currentCounts };
288
+ let boardsProcessed = 0;
289
+ onProgress('Fetching techniques...');
290
+ let levelMap = new Map();
291
+ try {
292
+ const response = await api.getTechniques();
293
+ if (response.success)
294
+ levelMap = buildTechniqueLevelMap(response.data);
295
+ }
296
+ catch (err) {
297
+ console.error('Failed to fetch techniques:', err);
298
+ }
299
+ const levelOf = (id) => levelMap.get(id) ?? null;
300
+ const reached = (id) => hasReachedTarget(counts, id, target);
301
+ /** Save example + practice; false = stop. */
302
+ const saveFound = async (board, found) => {
303
+ const name = techniqueName(found.techniqueId);
304
+ onProgress(`Saving ${name} example...`);
305
+ const requests = buildExampleSaveRequests(board, found);
306
+ try {
307
+ const example = await api.createExample(token.current, requests.example);
308
+ if (!example.success || !example.data) {
309
+ onProgress(`Failed to save example: ${example.error || 'Unknown error'}. Stopping.`);
310
+ return false;
311
+ }
312
+ onProgress(`Saving ${name} practice...`);
313
+ const practice = await api.createPractice(token.current, {
314
+ ...requests.practice,
315
+ source_example_uuid: example.data.uuid,
316
+ });
317
+ if (!practice.success) {
318
+ onProgress(`Failed to save practice: ${practice.error || 'Unknown error'}. Stopping.`);
319
+ return false;
320
+ }
321
+ }
322
+ catch (err) {
323
+ onProgress(`Error saving example/practice: ${errorMessage(err)}. Stopping.`);
324
+ return false;
325
+ }
326
+ const newCount = countForTechnique(counts, found.techniqueId) + 1;
327
+ counts[found.techniqueId] = newCount;
328
+ callbacks.onExampleSaved?.(found.techniqueId, newCount);
329
+ onProgress(`Saved ${name} (${newCount}/${target})`);
330
+ return true;
331
+ };
332
+ const extract = async (board, techniqueId) => {
333
+ boardsProcessed++;
334
+ if (boardsProcessed % ADMIN_TOKEN_REFRESH_INTERVAL === 0) {
335
+ await token.refresh();
336
+ }
337
+ return extractBoardTechniques(opts => api.solve(token.current, opts), board.board, techniqueId);
338
+ };
339
+ /** Board found by bit; resets the board when the technique is absent. */
340
+ const processBitBoard = async (board, techniqueId) => {
341
+ const result = await extract(board, techniqueId);
342
+ if (isTechniqueFound(result))
343
+ return saveFound(board, result);
344
+ if (isExtractionError(result)) {
345
+ onError(new Error(`Board ${shortUuid(board.uuid)}: ${result.error}`));
346
+ return true;
347
+ }
348
+ if (abort.shouldAbort())
349
+ return true;
350
+ onProgress(`Technique ${techniqueName(techniqueId)} not found in board ${shortUuid(board.uuid)}, resetting techniques...`);
351
+ const ok = await updateBoardTechniques(api, token.current, board.uuid, 0n, null);
352
+ if (!ok) {
353
+ onProgress(`Failed to reset board ${shortUuid(board.uuid)}. Stopping.`);
354
+ return false;
355
+ }
356
+ callbacks.onBoardTechniquesReset?.(board.uuid);
357
+ return true;
358
+ };
359
+ /** Board found by level; nothing to reset when the technique is absent. */
360
+ const processLevelBoard = async (board, techniqueId) => {
361
+ const result = await extract(board, techniqueId);
362
+ return isTechniqueFound(result) ? saveFound(board, result) : true;
363
+ };
364
+ const searchByLevel = async (techniqueId) => {
365
+ const name = techniqueName(techniqueId);
366
+ const techniqueLevel = levelOf(techniqueId);
367
+ if (techniqueLevel === null) {
368
+ onProgress(`No level for ${name}, skipping...`);
369
+ return;
370
+ }
371
+ for (const level of levelsToSearchForTechnique(techniqueLevel)) {
372
+ if (abort.shouldAbort() || reached(techniqueId))
373
+ break;
374
+ onProgress(`Fetching level ${level} boards for ${name}...`);
375
+ try {
376
+ const response = await api.getBoards(token.current, boardsAtLevelQuery(level, BOARDS_PER_LEVEL_LIMIT));
377
+ const boards = response.success ? (response.data ?? []) : [];
378
+ if (boards.length === 0)
379
+ continue;
380
+ onProgress(`Found ${boards.length} level ${level} boards, searching for ${name}...`);
381
+ for (const board of boards) {
382
+ if (abort.shouldAbort() || reached(techniqueId))
383
+ break;
384
+ if (!(await processLevelBoard(board, techniqueId))) {
385
+ abort.abort();
386
+ return;
387
+ }
388
+ }
389
+ }
390
+ catch (err) {
391
+ console.error(`Failed to fetch level ${level} boards:`, err);
392
+ }
393
+ }
394
+ };
395
+ const searchByBit = async (techniqueId) => {
396
+ const name = techniqueName(techniqueId);
397
+ onProgress(`Fetching boards for ${name} (level ${levelOf(techniqueId) ?? 'any'})...`);
398
+ const boards = await fetchBoardsWithTechnique(api, token.current, techniqueId, BOARDS_WITH_TECHNIQUE_LIMIT);
399
+ if (boards.length === 0) {
400
+ onProgress(`No boards with ${name} bit set, searching by level...`);
401
+ await searchByLevel(techniqueId);
402
+ return;
403
+ }
404
+ for (const board of boards) {
405
+ if (abort.shouldAbort() || reached(techniqueId))
406
+ break;
407
+ if (!(await processBitBoard(board, techniqueId))) {
408
+ abort.abort();
409
+ return;
410
+ }
411
+ }
412
+ };
413
+ if (request.mode === 'all') {
414
+ for (const id of request.techniqueOrder ?? ADMIN_TECHNIQUE_ORDER) {
415
+ if (abort.shouldAbort())
416
+ break;
417
+ if (reached(id))
418
+ continue;
419
+ await searchByBit(id);
420
+ }
421
+ }
422
+ else if (!abort.shouldAbort()) {
423
+ if (request.mode === 'bit') {
424
+ await searchByBit(request.techniqueId);
425
+ }
426
+ else {
427
+ onProgress(`Searching by level for ${techniqueName(request.techniqueId)} (level ${levelOf(request.techniqueId) ?? 'any'})...`);
428
+ await searchByLevel(request.techniqueId);
429
+ }
430
+ }
431
+ if (!abort.shouldAbort())
432
+ onProgress('Done!');
433
+ return counts;
434
+ }
435
+ //# sourceMappingURL=jobs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jobs.js","sourceRoot":"","sources":["../../src/admin/jobs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Admin batch jobs as framework-agnostic async functions over an\n * {@link AdminApi}. The hooks in this folder wrap them with React state; the\n * functions themselves hold the business rules (token refresh cadence, retry\n * delays, stop conditions, technique → level search fallback).\n *\n * Ported from `sudojo_app/src/utils/admin/{BoardGenerator,TechniqueExtractor,\n * ExampleCreator,boardTechniquesApi}.ts`; progress messages are unchanged.\n */\n\nimport {\n type Board,\n getTechniqueNameById,\n techniqueBitmaskOf,\n type TechniqueId,\n} from '@sudobility/sudojo_types';\nimport type { AdminApi } from './adminApi';\nimport {\n boardsAtLevelQuery,\n boardsWithoutTechniquesQuery,\n boardsWithTechniqueQuery,\n buildBoardTechniquesUpdate,\n buildValidatedBoard,\n compareExtractionWithSolver,\n parseGeneratedBoard,\n} from './boardRules';\nimport {\n describeTechniqueMask,\n errorMessage,\n formatTechniqueMaskHex,\n levelToSave,\n shortUuid,\n} from './boardStrings';\nimport {\n ADMIN_EXAMPLE_TARGET_PER_TECHNIQUE,\n ADMIN_TECHNIQUE_ORDER,\n BOARDS_PER_LEVEL_LIMIT,\n BOARDS_WITH_TECHNIQUE_LIMIT,\n buildExampleSaveRequests,\n buildTechniqueLevelMap,\n countForTechnique,\n hasReachedTarget,\n levelsToSearchForTechnique,\n type TechniqueCounts,\n} from './examples';\nimport {\n type BoardExtractionOutcome,\n type BoardExtractionResult,\n extractBoardTechniques,\n isExtractionError,\n isTechniqueFound,\n type TechniqueExampleFound,\n} from './extraction';\nimport { type AbortHandle, ADMIN_TOKEN_REFRESH_INTERVAL } from './types';\n\n// ============================================================================\n// Shared plumbing\n// ============================================================================\n\n/** Current admin token, refreshable mid-job. */\nexport interface AdminTokenManager {\n readonly current: string;\n /** Ask `getToken` again; keeps the old token if it fails or returns null. */\n refresh(): Promise<void>;\n}\n\n/**\n * Get the starting token. Returns null when `getToken` yields none (the job\n * should report \"Not authenticated\"). Refreshes log \"Token refreshed,\n * continuing...\" through `onProgress`; refresh failures only warn.\n */\nexport async function createAdminTokenManager(\n getToken: () => Promise<string | null>,\n onProgress: (message: string) => void\n): Promise<AdminTokenManager | null> {\n const initial = await getToken();\n if (!initial) return null;\n let current = initial;\n return {\n get current() {\n return current;\n },\n async refresh() {\n try {\n const next = await getToken();\n if (next) {\n current = next;\n onProgress('Token refreshed, continuing...');\n }\n } catch (err) {\n // The existing token might still be valid.\n console.warn('[admin] Token refresh failed:', err);\n }\n },\n };\n}\n\n/** True when the job should refresh its token before item `processed + 1`. */\nexport function shouldRefreshToken(\n processed: number,\n interval: number = ADMIN_TOKEN_REFRESH_INTERVAL\n): boolean {\n return processed > 0 && processed % interval === 0;\n}\n\n/** What every job needs. */\nexport interface AdminJobContext {\n api: AdminApi;\n token: AdminTokenManager;\n abort: AbortHandle;\n onProgress: (message: string) => void;\n onError: (error: Error) => void;\n /** Wait between retries (default: setTimeout). Injectable for tests. */\n delay?: (ms: number) => Promise<void>;\n}\n\nconst defaultDelay = (ms: number) =>\n new Promise<void>(resolve => setTimeout(resolve, ms));\n\nconst techniqueName = (id: number) => getTechniqueNameById(id);\n\n// ============================================================================\n// Board API helpers (were boardTechniquesApi.ts)\n// ============================================================================\n\n/**\n * Set a board's techniques bitmask (sent as an exact string) and level.\n * Returns false on any failure.\n */\nexport async function updateBoardTechniques(\n api: AdminApi,\n token: string,\n boardUuid: string,\n techniques: bigint,\n level: number | null\n): Promise<boolean> {\n try {\n const response = await api.updateBoard(\n token,\n boardUuid,\n buildBoardTechniquesUpdate(techniques, level)\n );\n return response.success;\n } catch {\n return false;\n }\n}\n\n/**\n * Boards with the technique's bit set (API `technique_bit` filter), any\n * level. Returns [] on any failure.\n */\nexport async function fetchBoardsWithTechnique(\n api: AdminApi,\n token: string,\n techniqueId: TechniqueId,\n limit: number\n): Promise<Board[]> {\n try {\n const response = await api.getBoards(\n token,\n boardsWithTechniqueQuery(techniqueId, limit)\n );\n return response.success && response.data ? response.data : [];\n } catch {\n return [];\n }\n}\n\n/** The next board whose techniques were never extracted, or null. */\nexport async function fetchBoardWithoutTechniques(\n api: AdminApi,\n token: string\n): Promise<Board | null> {\n try {\n const response = await api.getBoards(token, boardsWithoutTechniquesQuery());\n return (response.success && response.data?.[0]) || null;\n } catch {\n return null;\n }\n}\n\n// ============================================================================\n// Board generation (was BoardGenerator.ts)\n// ============================================================================\n\nexport interface BoardGenerationOptions {\n symmetrical: boolean;\n /** After each saved board. `techniques` is the exact bitmask. */\n onBoardGenerated?: (count: number, level: number, techniques: bigint) => void;\n}\n\n/**\n * Generate, validate and save boards until aborted or a save fails.\n * Each board: /generate → /validate (source of truth for level, techniques,\n * solution) → create. Generate/validate failures wait 1 s and retry.\n * Returns the number of boards saved.\n */\nexport async function runBoardGeneration(\n ctx: AdminJobContext,\n options: BoardGenerationOptions\n): Promise<number> {\n const { api, token, abort, onProgress, onError } = ctx;\n const delay = ctx.delay ?? defaultDelay;\n\n onProgress('Fetching levels...');\n try {\n const levels = await api.getLevels();\n if (!levels.success || !levels.data) {\n onError(new Error('Failed to fetch levels'));\n return 0;\n }\n if (levels.data.length === 0) {\n onError(new Error('No levels found'));\n return 0;\n }\n } catch (err) {\n onError(err instanceof Error ? err : new Error(String(err)));\n return 0;\n }\n\n onProgress('Starting generation...');\n let count = 0;\n\n while (!abort.shouldAbort()) {\n if (shouldRefreshToken(count)) await token.refresh();\n\n try {\n onProgress(`Generating board ${count + 1}...`);\n const generated = await api.generate(token.current, {\n symmetrical: options.symmetrical,\n });\n if (!generated.success || !generated.data) {\n onProgress(\n `Error generating board: ${generated.error || 'Unknown error'}`\n );\n await delay(1000);\n continue;\n }\n\n const puzzle = parseGeneratedBoard(generated.data);\n if (!puzzle.ok) {\n onProgress(\n puzzle.reason === 'invalid'\n ? 'Error: Invalid response structure from solver'\n : 'Error: Missing puzzle data from solver'\n );\n await delay(1000);\n continue;\n }\n\n onProgress(`Validating board ${count + 1}...`);\n const validated = await api.validate(token.current, {\n original: puzzle.original,\n });\n if (!validated.success || !validated.data) {\n onProgress(\n `Error validating board: ${validated.error || 'Unknown error'}`\n );\n await delay(1000);\n continue;\n }\n\n const board = buildValidatedBoard(\n puzzle,\n validated.data,\n options.symmetrical\n );\n\n onProgress(`Saving board ${count + 1}...`);\n const created = await api.createBoard(token.current, board.request);\n if (!created.success) {\n onProgress(\n `Error saving board: ${created.error || 'Unknown error'}. Stopping.`\n );\n break;\n }\n\n count++;\n options.onBoardGenerated?.(count, board.level, board.techniques);\n onProgress(\n `Generated ${count} boards (level ${board.level}, techniques: ${formatTechniqueMaskHex(board.techniques)})`\n );\n await delay(100);\n } catch (err) {\n onProgress(\n `Error: ${err instanceof Error ? err.message : 'Unknown error'}`\n );\n await delay(1000);\n }\n }\n\n onProgress(`Stopped. Generated ${count} boards.`);\n return count;\n}\n\n// ============================================================================\n// Technique extraction (was TechniqueExtractor.ts)\n// ============================================================================\n\nexport interface TechniqueExtractionOptions {\n /**\n * false: trust /validate and save its techniques (fast).\n * true: walk the board with /solve, compare with /validate, stop on any\n * mismatch, and save only matching results.\n */\n testWithFrontend: boolean;\n /** After each saved board. */\n onBoardExtracted?: () => void;\n}\n\n/**\n * Process boards without techniques one by one until none are left, aborted,\n * or a critical error (save failure, mismatch, invalid pencilmarks).\n * Returns the number of boards saved.\n */\nexport async function runTechniqueExtraction(\n ctx: AdminJobContext,\n options: TechniqueExtractionOptions\n): Promise<number> {\n const { api, token, abort, onProgress, onError } = ctx;\n let extracted = 0;\n let processed = 0;\n\n /** Save; false = stop. */\n const save = async (\n board: Board,\n techniques: bigint,\n level: number\n ): Promise<boolean> => {\n const finalLevel = levelToSave(level);\n onProgress(\n `Saving board ${shortUuid(board.uuid)} - ` +\n `techniques: ${formatTechniqueMaskHex(techniques)}, level: ${finalLevel}...`\n );\n const ok = await updateBoardTechniques(\n api,\n token.current,\n board.uuid,\n techniques,\n finalLevel\n );\n if (!ok) {\n onProgress(`Failed to save board ${shortUuid(board.uuid)}. Stopping.`);\n return false;\n }\n extracted++;\n options.onBoardExtracted?.();\n onProgress(`Extracted ${extracted} boards. Finding next...`);\n return true;\n };\n\n const validate = (board: Board) =>\n api.validate(token.current, { original: board.board });\n\n const processSimple = async (board: Board): Promise<boolean> => {\n const id = shortUuid(board.uuid);\n onProgress(`Validating board ${id}...`);\n try {\n const response = await validate(board);\n if (!response.success || !response.data?.board) {\n onProgress(`Validate failed for ${id}, skipping...`);\n return true;\n }\n const solverBoard = response.data.board;\n return await save(\n board,\n techniqueBitmaskOf(solverBoard),\n solverBoard.level\n );\n } catch (err) {\n onProgress(`Validate error for ${id}: ${errorMessage(err)}`);\n return true;\n }\n };\n\n const compareAndSave = async (\n board: Board,\n frontend: BoardExtractionResult\n ): Promise<boolean> => {\n const id = shortUuid(board.uuid);\n if (frontend.techniques === 0n && frontend.level === 0) {\n onProgress(`Skipped board ${id} (no techniques found). Finding next...`);\n return true;\n }\n\n onProgress(`Validating board ${id}...`);\n try {\n const response = await validate(board);\n if (!response.success || !response.data?.board) {\n onProgress(`Validate failed for ${id}, skipping...`);\n return true;\n }\n const solver = response.data.board;\n const mismatch = compareExtractionWithSolver(\n board.uuid,\n frontend,\n solver\n );\n if (mismatch) {\n console.log('[TechniqueExtractor] MISMATCH DETECTED');\n console.log('[TechniqueExtractor] Board UUID:', board.uuid);\n console.log('[TechniqueExtractor] Board puzzle:', board.board);\n console.log('[TechniqueExtractor] Frontend result:', {\n ...describeTechniqueMask(frontend.techniques),\n level: frontend.level,\n });\n console.log('[TechniqueExtractor] Solver (/validate) result:', {\n ...describeTechniqueMask(mismatch.solverTechniques),\n level: mismatch.solverLevel,\n });\n onError(new Error(mismatch.message));\n return false; // Investigate the discrepancy.\n }\n return await save(board, frontend.techniques, frontend.level);\n } catch (err) {\n onProgress(`Validate error for ${id}: ${errorMessage(err)}`);\n return true;\n }\n };\n\n const processWithTest = async (board: Board): Promise<boolean> => {\n onProgress(`Processing board ${shortUuid(board.uuid)}...`);\n const result = await extractBoardTechniques(\n opts => api.solve(token.current, opts),\n board.board\n );\n if (isExtractionError(result)) {\n onError(new Error(`Board ${shortUuid(board.uuid)}: ${result.error}`));\n // Invalid pencilmarks means a technique bug: stop and investigate.\n return !result.error.includes('Invalid pencilmarks');\n }\n if (isTechniqueFound(result)) return true; // Not possible without a filter.\n return compareAndSave(board, result);\n };\n\n const processBoard = options.testWithFrontend\n ? processWithTest\n : processSimple;\n\n while (!abort.shouldAbort()) {\n if (shouldRefreshToken(processed)) await token.refresh();\n\n const board = await fetchBoardWithoutTechniques(api, token.current);\n if (!board) {\n onProgress(\n `Done! Extracted techniques for ${extracted} boards. No more boards.`\n );\n break;\n }\n if (!(await processBoard(board))) break;\n processed++;\n }\n\n if (!abort.shouldAbort()) {\n onProgress(`Done! Extracted techniques for ${extracted} boards.`);\n }\n return extracted;\n}\n\n// ============================================================================\n// Example creation (was ExampleCreator.ts)\n// ============================================================================\n\n/**\n * - `all`: every technique in `techniqueOrder` below target, by bit then level.\n * - `bit`: one technique; boards with its bit set, else the level search.\n * - `level`: one technique; level search only (finds techniques whose bit is\n * missing on boards).\n */\nexport type ExampleCreationRequest =\n | {\n mode: 'all';\n currentCounts: TechniqueCounts;\n targetPerTechnique?: number;\n techniqueOrder?: readonly TechniqueId[];\n }\n | {\n mode: 'bit' | 'level';\n techniqueId: TechniqueId;\n currentCounts: TechniqueCounts;\n targetPerTechnique?: number;\n };\n\nexport interface ExampleCreationCallbacks {\n /** After an example + practice pair is saved. */\n onExampleSaved?: (techniqueId: TechniqueId, newCount: number) => void;\n /** After a board whose bit lied was reset to techniques 0 / level null. */\n onBoardTechniquesReset?: (boardUuid: string) => void;\n}\n\n/**\n * Create technique examples (each with a linked practice). A board fetched by\n * technique bit that turns out not to contain the technique is reset to\n * techniques 0 so the extractor re-processes it. Any failed save stops the\n * job (aborts). Returns the final per-technique counts.\n */\nexport async function runExampleCreation(\n ctx: AdminJobContext,\n request: ExampleCreationRequest,\n callbacks: ExampleCreationCallbacks = {}\n): Promise<Record<number, number>> {\n const { api, token, abort, onProgress, onError } = ctx;\n const target =\n request.targetPerTechnique ?? ADMIN_EXAMPLE_TARGET_PER_TECHNIQUE;\n const counts: Record<number, number> = { ...request.currentCounts };\n let boardsProcessed = 0;\n\n onProgress('Fetching techniques...');\n let levelMap = new Map<number, number | null>();\n try {\n const response = await api.getTechniques();\n if (response.success) levelMap = buildTechniqueLevelMap(response.data);\n } catch (err) {\n console.error('Failed to fetch techniques:', err);\n }\n const levelOf = (id: TechniqueId) => levelMap.get(id) ?? null;\n const reached = (id: TechniqueId) => hasReachedTarget(counts, id, target);\n\n /** Save example + practice; false = stop. */\n const saveFound = async (\n board: Board,\n found: TechniqueExampleFound\n ): Promise<boolean> => {\n const name = techniqueName(found.techniqueId);\n onProgress(`Saving ${name} example...`);\n const requests = buildExampleSaveRequests(board, found);\n try {\n const example = await api.createExample(token.current, requests.example);\n if (!example.success || !example.data) {\n onProgress(\n `Failed to save example: ${example.error || 'Unknown error'}. Stopping.`\n );\n return false;\n }\n onProgress(`Saving ${name} practice...`);\n const practice = await api.createPractice(token.current, {\n ...requests.practice,\n source_example_uuid: example.data.uuid,\n });\n if (!practice.success) {\n onProgress(\n `Failed to save practice: ${practice.error || 'Unknown error'}. Stopping.`\n );\n return false;\n }\n } catch (err) {\n onProgress(\n `Error saving example/practice: ${errorMessage(err)}. Stopping.`\n );\n return false;\n }\n\n const newCount = countForTechnique(counts, found.techniqueId) + 1;\n counts[found.techniqueId] = newCount;\n callbacks.onExampleSaved?.(found.techniqueId, newCount);\n onProgress(`Saved ${name} (${newCount}/${target})`);\n return true;\n };\n\n const extract = async (\n board: Board,\n techniqueId: TechniqueId\n ): Promise<BoardExtractionOutcome> => {\n boardsProcessed++;\n if (boardsProcessed % ADMIN_TOKEN_REFRESH_INTERVAL === 0) {\n await token.refresh();\n }\n return extractBoardTechniques(\n opts => api.solve(token.current, opts),\n board.board,\n techniqueId\n );\n };\n\n /** Board found by bit; resets the board when the technique is absent. */\n const processBitBoard = async (\n board: Board,\n techniqueId: TechniqueId\n ): Promise<boolean> => {\n const result = await extract(board, techniqueId);\n if (isTechniqueFound(result)) return saveFound(board, result);\n if (isExtractionError(result)) {\n onError(new Error(`Board ${shortUuid(board.uuid)}: ${result.error}`));\n return true;\n }\n if (abort.shouldAbort()) return true;\n\n onProgress(\n `Technique ${techniqueName(techniqueId)} not found in board ${shortUuid(board.uuid)}, resetting techniques...`\n );\n const ok = await updateBoardTechniques(\n api,\n token.current,\n board.uuid,\n 0n,\n null\n );\n if (!ok) {\n onProgress(`Failed to reset board ${shortUuid(board.uuid)}. Stopping.`);\n return false;\n }\n callbacks.onBoardTechniquesReset?.(board.uuid);\n return true;\n };\n\n /** Board found by level; nothing to reset when the technique is absent. */\n const processLevelBoard = async (\n board: Board,\n techniqueId: TechniqueId\n ): Promise<boolean> => {\n const result = await extract(board, techniqueId);\n return isTechniqueFound(result) ? saveFound(board, result) : true;\n };\n\n const searchByLevel = async (techniqueId: TechniqueId): Promise<void> => {\n const name = techniqueName(techniqueId);\n const techniqueLevel = levelOf(techniqueId);\n if (techniqueLevel === null) {\n onProgress(`No level for ${name}, skipping...`);\n return;\n }\n\n for (const level of levelsToSearchForTechnique(techniqueLevel)) {\n if (abort.shouldAbort() || reached(techniqueId)) break;\n onProgress(`Fetching level ${level} boards for ${name}...`);\n try {\n const response = await api.getBoards(\n token.current,\n boardsAtLevelQuery(level, BOARDS_PER_LEVEL_LIMIT)\n );\n const boards = response.success ? (response.data ?? []) : [];\n if (boards.length === 0) continue;\n\n onProgress(\n `Found ${boards.length} level ${level} boards, searching for ${name}...`\n );\n for (const board of boards) {\n if (abort.shouldAbort() || reached(techniqueId)) break;\n if (!(await processLevelBoard(board, techniqueId))) {\n abort.abort();\n return;\n }\n }\n } catch (err) {\n console.error(`Failed to fetch level ${level} boards:`, err);\n }\n }\n };\n\n const searchByBit = async (techniqueId: TechniqueId): Promise<void> => {\n const name = techniqueName(techniqueId);\n onProgress(\n `Fetching boards for ${name} (level ${levelOf(techniqueId) ?? 'any'})...`\n );\n const boards = await fetchBoardsWithTechnique(\n api,\n token.current,\n techniqueId,\n BOARDS_WITH_TECHNIQUE_LIMIT\n );\n if (boards.length === 0) {\n onProgress(`No boards with ${name} bit set, searching by level...`);\n await searchByLevel(techniqueId);\n return;\n }\n for (const board of boards) {\n if (abort.shouldAbort() || reached(techniqueId)) break;\n if (!(await processBitBoard(board, techniqueId))) {\n abort.abort();\n return;\n }\n }\n };\n\n if (request.mode === 'all') {\n for (const id of request.techniqueOrder ?? ADMIN_TECHNIQUE_ORDER) {\n if (abort.shouldAbort()) break;\n if (reached(id)) continue;\n await searchByBit(id);\n }\n } else if (!abort.shouldAbort()) {\n if (request.mode === 'bit') {\n await searchByBit(request.techniqueId);\n } else {\n onProgress(\n `Searching by level for ${techniqueName(request.techniqueId)} (level ${levelOf(request.techniqueId) ?? 'any'})...`\n );\n await searchByLevel(request.techniqueId);\n }\n }\n\n if (!abort.shouldAbort()) onProgress('Done!');\n return counts;\n}\n"]}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Status line for the admin "Regenerate hints" action (web AdminPage).
3
+ */
4
+ import type { BaseResponse, PracticesRegenerateHintsData } from '@sudobility/sudojo_types';
5
+ /**
6
+ * Describe a regenerate-hints result, e.g.
7
+ * `Done: Examples: 10/12, Practices: 5/5 (15/17 total), 2 failed (examples/technique 4: 2).`
8
+ * Failures are grouped by table and technique.
9
+ */
10
+ export declare function describeRegenerateHintsResult(data: PracticesRegenerateHintsData): string;
11
+ /**
12
+ * Describe a regenerate-hints response: the result line on success,
13
+ * otherwise `Error: Failed to regenerate hints`.
14
+ */
15
+ export declare function describeRegenerateHintsResponse(response: BaseResponse<PracticesRegenerateHintsData>): string;
16
+ //# sourceMappingURL=regenerateHints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"regenerateHints.d.ts","sourceRoot":"","sources":["../../src/admin/regenerateHints.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,4BAA4B,EAC7B,MAAM,0BAA0B,CAAC;AAElC;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,4BAA4B,GACjC,MAAM,CAwBR;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,YAAY,CAAC,4BAA4B,CAAC,GACnD,MAAM,CAIR"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Status line for the admin "Regenerate hints" action (web AdminPage).
3
+ */
4
+ /**
5
+ * Describe a regenerate-hints result, e.g.
6
+ * `Done: Examples: 10/12, Practices: 5/5 (15/17 total), 2 failed (examples/technique 4: 2).`
7
+ * Failures are grouped by table and technique.
8
+ */
9
+ export function describeRegenerateHintsResult(data) {
10
+ const { examples, practices, updated, failed, total, failures } = data;
11
+ const parts = [];
12
+ if (examples)
13
+ parts.push(`Examples: ${examples.updated}/${examples.total}`);
14
+ if (practices) {
15
+ parts.push(`Practices: ${practices.updated}/${practices.total}`);
16
+ }
17
+ let msg = parts.length > 0
18
+ ? `Done: ${parts.join(', ')} (${updated}/${total} total)`
19
+ : `Done: ${updated}/${total} updated`;
20
+ if (failed > 0) {
21
+ msg += `, ${failed} failed`;
22
+ const byTableTechnique = new Map();
23
+ for (const f of failures ?? []) {
24
+ const key = `${f.table ?? 'unknown'}/technique ${f.technique ?? 0}`;
25
+ byTableTechnique.set(key, (byTableTechnique.get(key) ?? 0) + 1);
26
+ }
27
+ const details = Array.from(byTableTechnique.entries())
28
+ .map(([k, c]) => `${k}: ${c}`)
29
+ .join(', ');
30
+ msg += ` (${details})`;
31
+ }
32
+ return `${msg}.`;
33
+ }
34
+ /**
35
+ * Describe a regenerate-hints response: the result line on success,
36
+ * otherwise `Error: Failed to regenerate hints`.
37
+ */
38
+ export function describeRegenerateHintsResponse(response) {
39
+ return response.success && response.data
40
+ ? describeRegenerateHintsResult(response.data)
41
+ : 'Error: Failed to regenerate hints';
42
+ }
43
+ //# sourceMappingURL=regenerateHints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"regenerateHints.js","sourceRoot":"","sources":["../../src/admin/regenerateHints.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAC3C,IAAkC;IAElC,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IACvE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,aAAa,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAC5E,IAAI,SAAS,EAAE,CAAC;QACd,KAAK,CAAC,IAAI,CAAC,cAAc,SAAS,CAAC,OAAO,IAAI,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC;IACnE,CAAC;IACD,IAAI,GAAG,GACL,KAAK,CAAC,MAAM,GAAG,CAAC;QACd,CAAC,CAAC,SAAS,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,OAAO,IAAI,KAAK,SAAS;QACzD,CAAC,CAAC,SAAS,OAAO,IAAI,KAAK,UAAU,CAAC;IAC1C,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QACf,GAAG,IAAI,KAAK,MAAM,SAAS,CAAC;QAC5B,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAkB,CAAC;QACnD,KAAK,MAAM,CAAC,IAAI,QAAQ,IAAI,EAAE,EAAE,CAAC;YAC/B,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,KAAK,IAAI,SAAS,cAAc,CAAC,CAAC,SAAS,IAAI,CAAC,EAAE,CAAC;YACpE,gBAAgB,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAClE,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC;aACnD,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;aAC7B,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,GAAG,IAAI,KAAK,OAAO,GAAG,CAAC;IACzB,CAAC;IACD,OAAO,GAAG,GAAG,GAAG,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,+BAA+B,CAC7C,QAAoD;IAEpD,OAAO,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,IAAI;QACtC,CAAC,CAAC,6BAA6B,CAAC,QAAQ,CAAC,IAAI,CAAC;QAC9C,CAAC,CAAC,mCAAmC,CAAC;AAC1C,CAAC","sourcesContent":["/**\n * Status line for the admin \"Regenerate hints\" action (web AdminPage).\n */\n\nimport type {\n BaseResponse,\n PracticesRegenerateHintsData,\n} from '@sudobility/sudojo_types';\n\n/**\n * Describe a regenerate-hints result, e.g.\n * `Done: Examples: 10/12, Practices: 5/5 (15/17 total), 2 failed (examples/technique 4: 2).`\n * Failures are grouped by table and technique.\n */\nexport function describeRegenerateHintsResult(\n data: PracticesRegenerateHintsData\n): string {\n const { examples, practices, updated, failed, total, failures } = data;\n const parts: string[] = [];\n if (examples) parts.push(`Examples: ${examples.updated}/${examples.total}`);\n if (practices) {\n parts.push(`Practices: ${practices.updated}/${practices.total}`);\n }\n let msg =\n parts.length > 0\n ? `Done: ${parts.join(', ')} (${updated}/${total} total)`\n : `Done: ${updated}/${total} updated`;\n if (failed > 0) {\n msg += `, ${failed} failed`;\n const byTableTechnique = new Map<string, number>();\n for (const f of failures ?? []) {\n const key = `${f.table ?? 'unknown'}/technique ${f.technique ?? 0}`;\n byTableTechnique.set(key, (byTableTechnique.get(key) ?? 0) + 1);\n }\n const details = Array.from(byTableTechnique.entries())\n .map(([k, c]) => `${k}: ${c}`)\n .join(', ');\n msg += ` (${details})`;\n }\n return `${msg}.`;\n}\n\n/**\n * Describe a regenerate-hints response: the result line on success,\n * otherwise `Error: Failed to regenerate hints`.\n */\nexport function describeRegenerateHintsResponse(\n response: BaseResponse<PracticesRegenerateHintsData>\n): string {\n return response.success && response.data\n ? describeRegenerateHintsResult(response.data)\n : 'Error: Failed to regenerate hints';\n}\n"]}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Shared types for the admin batch jobs (board generation, technique
3
+ * extraction, example creation).
4
+ *
5
+ * Ported from `sudojo_app/src/utils/admin/types.ts`.
6
+ */
7
+ import type { NetworkClient } from '@sudobility/types';
8
+ /** Options every admin job hook takes. */
9
+ export interface AdminJobOptions {
10
+ networkClient: NetworkClient;
11
+ baseUrl: string;
12
+ /**
13
+ * Returns a valid admin ID token, or null when signed out. Called once when a
14
+ * job starts and again every {@link ADMIN_TOKEN_REFRESH_INTERVAL} boards, so
15
+ * a long job survives token expiry. Pass a function that forces a refresh
16
+ * (e.g. Firebase `getIdToken(true)`) or that falls back to the current token.
17
+ */
18
+ getToken: () => Promise<string | null>;
19
+ }
20
+ /** Cooperative cancellation flag shared by a running job. */
21
+ export interface AbortHandle {
22
+ shouldAbort: () => boolean;
23
+ abort: () => void;
24
+ }
25
+ /** Create a fresh, not-yet-aborted {@link AbortHandle}. */
26
+ export declare function createAbortHandle(): AbortHandle;
27
+ /** Refresh the admin token every N boards during a long job. */
28
+ export declare const ADMIN_TOKEN_REFRESH_INTERVAL = 50;
29
+ /** Most recent log lines a job hook keeps (older lines are dropped). */
30
+ export declare const ADMIN_JOB_LOG_LIMIT = 200;
31
+ /** State every admin job hook exposes. */
32
+ export interface AdminJobState {
33
+ /** True from `start` until the job finishes or is cancelled. */
34
+ isRunning: boolean;
35
+ /** Latest progress line ('' before the first run). */
36
+ progress: string;
37
+ /** Progress lines of the current run, oldest first, capped at {@link ADMIN_JOB_LOG_LIMIT}. */
38
+ log: string[];
39
+ /** Last error message of the current run, or null. */
40
+ error: string | null;
41
+ }
42
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/admin/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,0CAA0C;AAC1C,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,aAAa,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACxC;AAED,6DAA6D;AAC7D,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,OAAO,CAAC;IAC3B,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,2DAA2D;AAC3D,wBAAgB,iBAAiB,IAAI,WAAW,CAQ/C;AAED,gEAAgE;AAChE,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAE/C,wEAAwE;AACxE,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC,0CAA0C;AAC1C,MAAM,WAAW,aAAa;IAC5B,gEAAgE;IAChE,SAAS,EAAE,OAAO,CAAC;IACnB,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,8FAA8F;IAC9F,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,sDAAsD;IACtD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB"}