GameSentenceMiner 2.18.15__py3-none-any.whl → 2.18.17__py3-none-any.whl

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 (37) hide show
  1. GameSentenceMiner/anki.py +8 -53
  2. GameSentenceMiner/owocr/owocr/ocr.py +3 -2
  3. GameSentenceMiner/owocr/owocr/run.py +5 -1
  4. GameSentenceMiner/ui/anki_confirmation.py +16 -2
  5. GameSentenceMiner/util/configuration.py +6 -9
  6. GameSentenceMiner/util/db.py +11 -7
  7. GameSentenceMiner/util/games_table.py +320 -0
  8. GameSentenceMiner/web/anki_api_endpoints.py +506 -0
  9. GameSentenceMiner/web/database_api.py +239 -117
  10. GameSentenceMiner/web/static/css/loading-skeleton.css +41 -0
  11. GameSentenceMiner/web/static/css/search.css +54 -0
  12. GameSentenceMiner/web/static/css/stats.css +76 -0
  13. GameSentenceMiner/web/static/js/anki_stats.js +304 -50
  14. GameSentenceMiner/web/static/js/database.js +44 -7
  15. GameSentenceMiner/web/static/js/heatmap.js +326 -0
  16. GameSentenceMiner/web/static/js/overview.js +20 -224
  17. GameSentenceMiner/web/static/js/search.js +190 -23
  18. GameSentenceMiner/web/static/js/stats.js +371 -1
  19. GameSentenceMiner/web/stats.py +188 -0
  20. GameSentenceMiner/web/templates/anki_stats.html +145 -58
  21. GameSentenceMiner/web/templates/components/date-range.html +19 -0
  22. GameSentenceMiner/web/templates/components/html-head.html +45 -0
  23. GameSentenceMiner/web/templates/components/js-config.html +37 -0
  24. GameSentenceMiner/web/templates/components/popups.html +15 -0
  25. GameSentenceMiner/web/templates/components/settings-modal.html +233 -0
  26. GameSentenceMiner/web/templates/database.html +13 -3
  27. GameSentenceMiner/web/templates/goals.html +9 -31
  28. GameSentenceMiner/web/templates/overview.html +16 -223
  29. GameSentenceMiner/web/templates/search.html +46 -0
  30. GameSentenceMiner/web/templates/stats.html +49 -311
  31. GameSentenceMiner/web/texthooking_page.py +4 -66
  32. {gamesentenceminer-2.18.15.dist-info → gamesentenceminer-2.18.17.dist-info}/METADATA +1 -1
  33. {gamesentenceminer-2.18.15.dist-info → gamesentenceminer-2.18.17.dist-info}/RECORD +37 -28
  34. {gamesentenceminer-2.18.15.dist-info → gamesentenceminer-2.18.17.dist-info}/WHEEL +0 -0
  35. {gamesentenceminer-2.18.15.dist-info → gamesentenceminer-2.18.17.dist-info}/entry_points.txt +0 -0
  36. {gamesentenceminer-2.18.15.dist-info → gamesentenceminer-2.18.17.dist-info}/licenses/LICENSE +0 -0
  37. {gamesentenceminer-2.18.15.dist-info → gamesentenceminer-2.18.17.dist-info}/top_level.txt +0 -0
@@ -1,32 +1,10 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
3
 
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>GSM Dashboard</title>
8
- <!-- Include Chart.js from a CDN -->
9
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
10
-
11
- <!-- Include html2canvas for screenshot functionality -->
12
- <script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
13
-
14
- <!-- Include shared theme styles -->
15
- {% include 'components/theme-styles.html' %}
16
-
17
- <!-- Include shared CSS -->
18
- <link rel="stylesheet" href="/static/css/shared.css">
19
-
20
- <!-- Include shared dashboard and popup CSS -->
21
- <link rel="stylesheet" href="/static/css/dashboard-shared.css">
22
- <link rel="stylesheet" href="/static/css/popups-shared.css">
23
-
24
- <!-- Include stats-specific CSS -->
25
- <link rel="stylesheet" href="/static/css/stats.css">
26
-
27
- <!-- Include shared kanji grid CSS -->
28
- <link rel="stylesheet" href="/static/css/kanji-grid.css">
29
- </head>
4
+ {% set page_title = 'GSM Dashboard' %}
5
+ {% set page_specific_css = 'stats.css' %}
6
+ {% set include_kanji_grid_css = true %}
7
+ {% include 'components/html-head.html' %}
30
8
 
31
9
  <body>
32
10
 
@@ -35,38 +13,40 @@
35
13
 
36
14
  <!-- Include shared navigation -->
37
15
  {% include 'components/navigation.html' %}
38
- <div class="dashboard-card date-range">
16
+
17
+ <!-- Include shared date range component -->
18
+ {% include 'components/date-range.html' %}
19
+
20
+ <!-- Include shared popup components -->
21
+ {% include 'components/popups.html' %}
22
+
23
+ <div class="dashboard-card peak-stats">
39
24
  <div class="dashboard-card-header">
40
25
  <h3 class="dashboard-card-title">
41
- <span class="dashboard-card-icon">📅</span>
42
- Date Range
26
+ <span class="dashboard-card-icon">🏆</span>
27
+ Peak Statistics
43
28
  </h3>
44
29
  </div>
45
- <div class="dashboard-date-range">
46
- <div class="dashboard-date-item tooltip" data-tooltip="Select the start date for your stats">
47
- <label for="fromDate">From</label>
48
- <input type="date" id="fromDate" class="dashboard-date-input">
30
+ <div class="dashboard-stats-grid">
31
+ <div class="dashboard-stat-item tooltip" data-tooltip="Maximum characters read in a single day">
32
+ <span class="dashboard-stat-value" id="maxDailyChars">-</span>
33
+ <span class="dashboard-stat-label">Most Chars/Day</span>
49
34
  </div>
50
- <div class="dashboard-date-item tooltip" data-tooltip="Select the end date for your stats">
51
- <label for="toDate">To</label>
52
- <input type="date" id="toDate" class="dashboard-date-input">
35
+ <div class="dashboard-stat-item tooltip" data-tooltip="Maximum hours studied in a single day">
36
+ <span class="dashboard-stat-value" id="maxDailyHours">-</span>
37
+ <span class="dashboard-stat-label">Most Hours/Day</span>
38
+ </div>
39
+ <div class="dashboard-stat-item tooltip" data-tooltip="Longest continuous reading session">
40
+ <span class="dashboard-stat-value" id="longestSession">-</span>
41
+ <span class="dashboard-stat-label">Longest Session</span>
42
+ </div>
43
+ <div class="dashboard-stat-item tooltip" data-tooltip="Maximum characters read in a single session">
44
+ <span class="dashboard-stat-value" id="maxSessionChars">-</span>
45
+ <span class="dashboard-stat-label">Most Chars/Session</span>
53
46
  </div>
54
47
  </div>
55
48
  </div>
56
- <div id="dateErrorPopup" class="dashboard-popup hidden">
57
- <div class="dashboard-popup-content">
58
- <div class="dashboard-popup-icon">⚠️</div>
59
- <div class="dashboard-popup-message">"From" date cannot be later than "To" date.</div>
60
- <button id="closePopupBtn" class="dashboard-popup-btn">OK</button>
61
- </div>
62
- </div>
63
- <div id="noDataPopup" class="no-data-popup hidden">
64
- <div class="no-data-content">
65
- <p>No data found for the selected range.</p>
66
- <button id="closeNoDataPopup">OK</button>
67
- </div>
68
- </div>
69
-
49
+ <br>
70
50
  <div class="chart-container">
71
51
  <h2>Lines Received Over Time</h2>
72
52
  <canvas id="linesChart"></canvas>
@@ -92,6 +72,16 @@
92
72
  <canvas id="readingSpeedPerGameChart"></canvas>
93
73
  </div>
94
74
 
75
+ <div class="chart-container">
76
+ <h2>Time of Day Activity Pattern</h2>
77
+ <canvas id="hourlyActivityChart"></canvas>
78
+ </div>
79
+
80
+ <div class="chart-container">
81
+ <h2>Hourly Reading Speed</h2>
82
+ <canvas id="hourlyReadingSpeedChart"></canvas>
83
+ </div>
84
+
95
85
  <div class="chart-container">
96
86
  <h2>Kanji Grid</h2>
97
87
  <div id="kanjiGridContainer">
@@ -112,226 +102,8 @@
112
102
  </div>
113
103
  </div>
114
104
 
115
- <!-- Settings Modal -->
116
- <div id="settingsModal" class="modal">
117
- <div class="modal-content">
118
- <div class="modal-header">
119
- <h3>Statistics Settings</h3>
120
- <span class="close-btn" id="closeSettingsModal">&times;</span>
121
- </div>
122
- <div class="modal-body">
123
- <p style="color: var(--text-secondary); margin-bottom: 20px;">
124
- Configure how reading time and sessions are calculated for your statistics.
125
- </p>
126
- <form id="settingsForm">
127
- <!-- <div style="margin-bottom: 20px;">
128
- <label for="timezone-select"
129
- style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
130
- Timezone
131
- </label>
132
- <div style="display: flex; gap: 8px;">
133
- <select id="timezone-select" name="timezone"
134
- style="flex-grow: 1; padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;">
135
- </select>
136
- <button id="set-local-timezone-btn"
137
- style="padding: 10px; border: none; border-radius: 5px; background: var(--primary-color); color: white; font-size: 14px; cursor: pointer;">
138
- Set to Local
139
- </button>
140
- </div>
141
- <small style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
142
- Select the timezone to base your statistics on
143
- </small>
144
- </div> -->
145
-
146
- <div style="margin-bottom: 20px;">
147
- <label for="afkTimer"
148
- style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
149
- AFK Timer (seconds)
150
- </label>
151
- <input type="number" id="afkTimer" name="afk_timer_seconds"
152
- style="padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;"
153
- placeholder="120">
154
- <small
155
- style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
156
- Maximum time between activities that still counts as active reading
157
- </small>
158
- </div>
159
-
160
- <div style="margin-bottom: 20px;">
161
- <label for="sessionGap"
162
- style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
163
- Session Gap (seconds)
164
- </label>
165
- <input type="number" id="sessionGap" name="session_gap_seconds"
166
- style="padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;"
167
- placeholder="3600">
168
- <small
169
- style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
170
- Time gap that triggers a new reading session
171
- </small>
172
- </div>
173
-
174
- <div style="margin-bottom: 20px;">
175
- <label for="streakRequirement"
176
- style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
177
- Streak Requirement (hours)
178
- </label>
179
- <input type="number" id="streakRequirement" name="streak_requirement_hours" min="0.01"
180
- max="24" step="0.01"
181
- style="padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;"
182
- placeholder="1.0">
183
- <small
184
- style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
185
- Minimum hours of reading activity required to maintain streak (0.01-24 hours)
186
- </small>
187
- </div>
188
-
189
- <!-- Reading Goals Section -->
190
- <div style="margin-bottom: 20px; padding-top: 20px; border-top: 1px solid var(--border-color);">
191
- <label
192
- style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
193
- Reading Goals Configuration
194
- </label>
195
- <p style="color: var(--text-secondary); margin-bottom: 20px; font-size: 14px;">
196
- Set your long-term reading targets for tracking progress and projections.
197
- </p>
198
-
199
- <div style="margin-bottom: 15px;">
200
- <label for="readingHoursTarget"
201
- style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
202
- Reading Hours Target
203
- </label>
204
- <input type="number" id="readingHoursTarget" name="reading_hours_target" min="1"
205
- max="10000"
206
- style="padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;"
207
- placeholder="1500">
208
- <small
209
- style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
210
- Total reading hours goal (1-10,000 hours) - Default: 1500 hours based on TMW N1
211
- achievement
212
- </small>
213
- </div>
214
-
215
- <div style="margin-bottom: 15px;">
216
- <label for="characterCountTarget"
217
- style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
218
- Character Count Target
219
- </label>
220
- <input type="number" id="characterCountTarget" name="character_count_target" min="1000"
221
- max="1000000000"
222
- style="padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;"
223
- placeholder="25000000">
224
- <small
225
- style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
226
- Total characters read goal (1,000-1,000,000,000) - Default: 25 million characters
227
- </small>
228
- </div>
229
-
230
- <div style="margin-bottom: 15px;">
231
- <label for="readingHoursTargetDate"
232
- style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
233
- Reading Hours Target Date
234
- </label>
235
- <input type="date" id="readingHoursTargetDate" name="reading_hours_target_date"
236
- style="width: 90%; padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;"
237
- data-date-format="yyyy-mm-dd">
238
- <small
239
- style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
240
- Target date to achieve your reading hours goal
241
- </small>
242
- </div>
243
-
244
- <div style="margin-bottom: 15px;">
245
- <label for="characterCountTargetDate"
246
- style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
247
- Character Count Target Date
248
- </label>
249
- <input type="date" id="characterCountTargetDate" name="character_count_target_date"
250
- style="width: 90%; padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;"
251
- data-date-format="yyyy-mm-dd">
252
- <small
253
- style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
254
- Target date to achieve your character count goal
255
- </small>
256
- </div>
257
-
258
- <div style="margin-bottom: 15px;">
259
- <label for="gamesTarget"
260
- style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
261
- Games/Visual Novels Target
262
- </label>
263
- <input type="number" id="gamesTarget" name="games_target" min="1" max="1000"
264
- style="width: 90%; padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;"
265
- placeholder="100">
266
- <small
267
- style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
268
- Number of games/visual novels to complete (1-1,000) - Default: 100 based on Refold
269
- standards
270
- </small>
271
- </div>
272
-
273
- <div style="margin-bottom: 15px;">
274
- <label for="gamesTargetDate"
275
- style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
276
- Games/Visual Novels Target Date
277
- </label>
278
- <input type="date" id="gamesTargetDate" name="games_target_date"
279
- style="width: 90%; padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;"
280
- data-date-format="yyyy-mm-dd">
281
- <small
282
- style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
283
- Target date to achieve your games goal
284
- </small>
285
- </div>
286
- </div>
287
-
288
- <!-- Import ExStatic Lines Section -->
289
- <div style="margin-bottom: 20px; padding-top: 20px; border-top: 1px solid var(--border-color);">
290
- <label
291
- style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
292
- Import ExStatic Lines
293
- </label>
294
- <div style="margin-bottom: 10px;">
295
- <input type="file" id="exstaticFile" accept=".csv"
296
- style="width: 90%; padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;">
297
- <small
298
- style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
299
- Select an ExStatic CSV file to import reading data into GSM
300
- </small>
301
- </div>
302
- <button type="button" id="importExstaticBtn"
303
- style="width: 90%; padding: 10px; background: #666; color: white; border: none; border-radius: 5px; font-size: 14px; cursor: not-allowed; margin-bottom: 10px;"
304
- disabled>
305
- Import ExStatic Lines
306
- </button>
307
- <div id="importProgress" style="display: none; margin-bottom: 10px;">
308
- <div
309
- style="background: var(--bg-tertiary); border-radius: 5px; overflow: hidden; height: 20px; position: relative;">
310
- <div id="importProgressBar"
311
- style="background: var(--primary-color); height: 100%; width: 0%; transition: width 0.3s ease;">
312
- </div>
313
- <span id="importProgressText"
314
- style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 12px; color: var(--text-primary);">0%</span>
315
- </div>
316
- </div>
317
- <div id="importStatus"
318
- style="display: none; padding: 10px; border-radius: 5px; font-size: 14px;"></div>
319
- </div>
320
- </form>
321
-
322
- <div id="settingsError"
323
- style="display: none; background: var(--danger-color); color: white; padding: 10px; border-radius: 5px; margin-bottom: 15px; font-size: 14px;">
324
- </div>
325
- <div id="settingsSuccess"
326
- style="display: none; background: var(--success-color); color: white; padding: 10px; border-radius: 5px; margin-bottom: 15px; font-size: 14px;">
327
- </div>
328
- </div>
329
- <div class="modal-footer">
330
- <button id="cancelSettingsBtn" class="cancel-btn">Cancel</button>
331
- <button id="saveSettingsBtn" class="confirm-delete-btn">Save Settings</button>
332
- </div>
333
- </div>
334
- </div>
105
+ <!-- Include shared settings modal -->
106
+ {% include 'components/settings-modal.html' %}
335
107
  </div>
336
108
 
337
109
 
@@ -343,7 +115,7 @@
343
115
  {% set games_target = stats_config.games_target | default(100) %}
344
116
  {% set heatmap_year = 'all' %}
345
117
 
346
- {% set stats_config = {
118
+ {% set stats_config_vars = {
347
119
  'afkTimerSeconds': afk_timer,
348
120
  'sessionGapSeconds': session_gap,
349
121
  'streakRequirementHours': streak_req,
@@ -353,46 +125,12 @@
353
125
  'heatmapDisplayYear': heatmap_year
354
126
  } %}
355
127
 
356
- <!-- Inject stats config values for frontend -->
357
- <script>
358
- window.statsConfig = {{ stats_config | tojson }};
359
- </script>
360
- <!-- Include shared JavaScript first (required dependency for stats.js) -->
361
- <script src="/static/js/shared.js"></script>
362
- <script src="/static/js/kanji-grid.js"></script>
363
- <script src="/static/js/stats.js"></script>
364
- <!-- <script>
365
- document.addEventListener('DOMContentLoaded', () => {
366
- const timezoneSelect = document.getElementById('timezone-select');
367
- const setLocalTimezoneBtn = document.getElementById('set-local-timezone-btn');
368
-
369
- function populateTimezoneSelect() {
370
- const timezones = Intl.supportedValuesOf('timeZone');
371
- const fragment = document.createDocumentFragment();
372
-
373
- timezones.forEach(tz => {
374
- const option = document.createElement('option');
375
- option.value = tz;
376
- option.textContent = tz.replace(/_/g, ' '); // Improve readability
377
- fragment.appendChild(option);
378
- });
379
-
380
- timezoneSelect.appendChild(fragment);
381
- }
382
-
383
- function setLocalTimezone() {
384
- const localTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
385
- timezoneSelect.value = localTimezone;
386
- }
387
-
388
- populateTimezoneSelect();
389
- setLocalTimezone();
390
-
391
- setLocalTimezoneBtn.addEventListener('click', () => {
392
- setLocalTimezone();
393
- });
394
- });
395
- </script> -->
128
+ <!-- Include shared JavaScript configuration -->
129
+ {% set config_vars = stats_config_vars %}
130
+ {% set config_object_name = 'statsConfig' %}
131
+ {% set include_kanji_grid_js = true %}
132
+ {% set page_specific_js = 'stats.js' %}
133
+ {% include 'components/js-config.html' %}
396
134
 
397
135
  </body>
398
136
 
@@ -46,6 +46,10 @@ app = flask.Flask(__name__)
46
46
  # Register database API routes
47
47
  register_database_api_routes(app)
48
48
 
49
+ # Register Anki API routes
50
+ from GameSentenceMiner.web.anki_api_endpoints import register_anki_api_endpoints
51
+ register_anki_api_endpoints(app)
52
+
49
53
  # Load data from the JSON file
50
54
  def load_data_from_file():
51
55
  if os.path.exists(TEXT_REPLACEMENTS_FILE):
@@ -303,72 +307,6 @@ def goals():
303
307
  master_config=get_master_config(),
304
308
  stats_config=get_stats_config())
305
309
 
306
- @app.route('/api/anki_earliest_date')
307
- def anki_earliest_date():
308
- """Returns the timestamp of earliest available card in anki collection."""
309
- from GameSentenceMiner.anki import get_anki_earliest_date
310
- earliest_card = get_anki_earliest_date()
311
- return jsonify({
312
- "earliest_card": earliest_card
313
- })
314
-
315
- @app.route('/api/anki_stats')
316
- def api_anki_stats():
317
- """
318
- API endpoint to provide Anki vs GSM kanji stats for the frontend.
319
- Returns:
320
- {
321
- "missing_kanji": [ { "kanji": "漢", "frequency": 42 }, ... ],
322
- "anki_kanji_count": 123,
323
- "gsm_kanji_count": 456,
324
- "coverage_percent": 27.0
325
- }
326
- """
327
- from GameSentenceMiner.anki import get_all_anki_first_field_kanji
328
- from GameSentenceMiner.web.stats import calculate_kanji_frequency, is_kanji
329
- from GameSentenceMiner.util.db import GameLinesTable
330
-
331
- start_timestamp = int(request.args.get('start_timestamp')) if request.args.get('start_timestamp') else None
332
- end_timestamp = int(request.args.get('end_timestamp')) if request.args.get('end_timestamp') else None
333
-
334
- # Get GSM lines and calculate kanji frequency
335
- try:
336
- all_lines = (
337
- GameLinesTable.get_lines_filtered_by_timestamp(start_timestamp / 1000, end_timestamp / 1000)
338
- if start_timestamp is not None and end_timestamp is not None
339
- else GameLinesTable.all()
340
- )
341
- except Exception as e:
342
- logger.warning(f"Failed to filter lines by timestamp: {e}, fetching all lines instead")
343
- all_lines = GameLinesTable.all()
344
-
345
- gsm_kanji_stats = calculate_kanji_frequency(all_lines)
346
- gsm_kanji_list = gsm_kanji_stats.get("kanji_data", [])
347
- gsm_kanji_set = set([k["kanji"] for k in gsm_kanji_list])
348
-
349
- # Get all kanji in Anki (first field only)
350
- anki_kanji_set = get_all_anki_first_field_kanji(start_timestamp, end_timestamp)
351
-
352
- # Find missing kanji (in GSM but not in Anki)
353
- missing_kanji = [
354
- {"kanji": k["kanji"], "frequency": k["frequency"]}
355
- for k in gsm_kanji_list if k["kanji"] not in anki_kanji_set
356
- ]
357
-
358
- # Sort missing kanji by frequency descending
359
- missing_kanji.sort(key=lambda x: x["frequency"], reverse=True)
360
-
361
- # Coverage stats
362
- anki_kanji_count = len(anki_kanji_set)
363
- gsm_kanji_count = len(gsm_kanji_set)
364
- coverage_percent = (anki_kanji_count / gsm_kanji_count * 100) if gsm_kanji_count else 0.0
365
-
366
- return jsonify({
367
- "missing_kanji": missing_kanji,
368
- "anki_kanji_count": anki_kanji_count,
369
- "gsm_kanji_count": gsm_kanji_count,
370
- "coverage_percent": round(coverage_percent, 1)
371
- })
372
310
 
373
311
  @app.route('/search')
374
312
  def search():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.18.15
3
+ Version: 2.18.17
4
4
  Summary: A tool for mining sentences from games. Update: Overlay?
5
5
  Author-email: Beangate <bpwhelan95@gmail.com>
6
6
  License: MIT License
@@ -1,5 +1,5 @@
1
1
  GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- GameSentenceMiner/anki.py,sha256=fQ5ccQGvhbH2hmdzNSDXXJTeqfIc_GVfFPqVgBds_fM,31255
2
+ GameSentenceMiner/anki.py,sha256=9pjjJrQS7IbA6iTtOaJ0Hs2nBddYXTCZBF5lZ5I4dOs,29593
3
3
  GameSentenceMiner/gametext.py,sha256=FBL3kgJ71hCg5Nczuo9dAEi_sLGdVIGgvc62bT5KhCc,10691
4
4
  GameSentenceMiner/gsm.py,sha256=0hEpEBDbI9FtiKtHeyrSLKV1nys-mKTKfxLY0Dk7mOQ,36387
5
5
  GameSentenceMiner/obs.py,sha256=PtO_zB8zhaBxm_Vp8ggf2JmYAJ72cclBLo0kPuUBIoc,36766
@@ -27,8 +27,8 @@ GameSentenceMiner/owocr/owocr/__init__.py,sha256=87hfN5u_PbL_onLfMACbc0F5j4KyIK9
27
27
  GameSentenceMiner/owocr/owocr/__main__.py,sha256=XQaqZY99EKoCpU-gWQjNbTs7Kg17HvBVE7JY8LqIE0o,157
28
28
  GameSentenceMiner/owocr/owocr/config.py,sha256=qM7kISHdUhuygGXOxmgU6Ef2nwBShrZtdqu4InDCViE,8103
29
29
  GameSentenceMiner/owocr/owocr/lens_betterproto.py,sha256=oNoISsPilVVRBBPVDtb4-roJtAhp8ZAuFTci3TGXtMc,39141
30
- GameSentenceMiner/owocr/owocr/ocr.py,sha256=8cqZEUF90UlV3jBIvxKBga6YBFGjNBCVu1UiBcwISG0,72215
31
- GameSentenceMiner/owocr/owocr/run.py,sha256=Z7VkoFrsoQbMTHc6CmwpcMzsOROK9A_RJRwhlxw15oA,81871
30
+ GameSentenceMiner/owocr/owocr/ocr.py,sha256=XR6tbcj8ctDXn8NlpXrRZIel60zj2h3R0NKWBtEE5M4,72273
31
+ GameSentenceMiner/owocr/owocr/run.py,sha256=z3EaF_a5m9T_ZrELYoaAzHPqzTO0cd7MQCndcnWXq_4,82035
32
32
  GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py,sha256=Na6XStbQBtpQUSdbN3QhEswtKuU1JjReFk_K8t5ezQE,3395
33
33
  GameSentenceMiner/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
34
  GameSentenceMiner/tools/audio_offset_selector.py,sha256=8Stk3BP-XVIuzRv9nl9Eqd2D-1yD3JrgU-CamBywJmY,8542
@@ -36,16 +36,17 @@ GameSentenceMiner/tools/furigana_filter_preview.py,sha256=DAT2-j6vSDHr9ufk6PiaLi
36
36
  GameSentenceMiner/tools/ss_selector.py,sha256=ob2oJdiYreDMMau7CvsglpnhZ1CDnJqop3lV54-PjRo,4782
37
37
  GameSentenceMiner/tools/window_transparency.py,sha256=GtbxbmZg0-UYPXhfHff-7IKZyY2DKe4B9GdyovfmpeM,8166
38
38
  GameSentenceMiner/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
- GameSentenceMiner/ui/anki_confirmation.py,sha256=GXNvujWSc3L8-tZ_93zdcEqJxAEBoh2oV5EP78Xcn7E,14685
39
+ GameSentenceMiner/ui/anki_confirmation.py,sha256=krrT3q3anTtXNTPHz5ahXSd4genEnEvS07v1JYftBFg,15174
40
40
  GameSentenceMiner/ui/config_gui.py,sha256=pCZK7-PyllrNc7QmnT4MQ2rADkSC8y7YJcwz8UqjP48,155072
41
41
  GameSentenceMiner/ui/furigana_filter_preview.py,sha256=DAT2-j6vSDHr9ufk6PiaLikEsbIp56B_OHIEeYLMwlk,17135
42
42
  GameSentenceMiner/ui/screenshot_selector.py,sha256=7QvDhOMpA0ej8x_lYtu6fhmrWbM1GCg-dps3XVWwk1Q,8234
43
43
  GameSentenceMiner/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
44
  GameSentenceMiner/util/audio_player.py,sha256=-yFsf0qoTSS1ga5rCmEJZJGUSJzXCvfZHY3t0NxycDk,7896
45
- GameSentenceMiner/util/configuration.py,sha256=8cXpeUJ4hOr8Qd4JPAx1qn_phBIFuKR5D-PybThu-Qk,48233
46
- GameSentenceMiner/util/db.py,sha256=1DjGjlwWnPefmQfzvMqqFPW0a0qeO-fIXE1YqKiok18,32000
45
+ GameSentenceMiner/util/configuration.py,sha256=qndhFAN4oC1dawklllS3UBhK2DCVSTloGdZxDoTUGr4,48137
46
+ GameSentenceMiner/util/db.py,sha256=CneZuFGIH6fosHblly4lcrWfU0Qjj5l0coxJy7m1igw,32237
47
47
  GameSentenceMiner/util/electron_config.py,sha256=KfeJToeFFVw0IR5MKa-gBzpzaGrU-lyJbR9z-sDEHYU,8767
48
48
  GameSentenceMiner/util/ffmpeg.py,sha256=cAzztfY36Xf2WvsJDjavoiMOvA9ac2GVdCrSB4LzHk4,29007
49
+ GameSentenceMiner/util/games_table.py,sha256=VM68MAsdyE6tpdwM4bDSk67qioBOvsEO8-TpnRmUnSo,12003
49
50
  GameSentenceMiner/util/get_overlay_coords.py,sha256=TJz3iVimiwhRLwkYNB1uOZnDR4BEWWUWYjoJEe7uSQs,25160
50
51
  GameSentenceMiner/util/gsm_utils.py,sha256=mASECTmN10c2yPL4NEfLg0Y0YWwFso1i6r_hhJPR3MY,10974
51
52
  GameSentenceMiner/util/model.py,sha256=R-_RYTYLSDNgBoVTPuPBcIHeOznIqi_vBzQ7VQ20WYk,6727
@@ -61,12 +62,13 @@ GameSentenceMiner/util/downloader/oneocr_dl.py,sha256=l3s9Z-x1b57GX048o5h-MVv0UT
61
62
  GameSentenceMiner/util/win10toast/__init__.py,sha256=6TL2w6rzNmpJEp6_v2cAJP_7ExA3UsKzwdM08pNcVfE,5341
62
63
  GameSentenceMiner/util/win10toast/__main__.py,sha256=5MYnBcFj8y_6Dyc1kiPd0_FsUuh4yl1cv5wsleU6V4w,668
63
64
  GameSentenceMiner/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
64
- GameSentenceMiner/web/database_api.py,sha256=wedzDy_zpeJG1XvUw2fDWQ6InwRu6WfuVRdKWYUfvbE,84792
65
+ GameSentenceMiner/web/anki_api_endpoints.py,sha256=r30OTT3YVfgbF6aJ-EGWZLF-j2D9L63jLkRXMycU0p8,23681
66
+ GameSentenceMiner/web/database_api.py,sha256=GyiMZWiT9q7fzA7D26YYE73DnAj3jCk9KUGT1P349y4,89996
65
67
  GameSentenceMiner/web/events.py,sha256=6Vyz5c9MdpMIa7Zqljqhap2XFQnAVYJ0CdQV64TSZsA,5119
66
68
  GameSentenceMiner/web/gsm_websocket.py,sha256=B0VKpxmsRu0WRh5nFWlpDPBQ6-K2ed7TEIa0O6YWeoo,4166
67
69
  GameSentenceMiner/web/service.py,sha256=6cgUmDgtp3ZKzuPFszowjPoq-BDtC1bS3ux6sykeaqo,6662
68
- GameSentenceMiner/web/stats.py,sha256=zIK0ZzyInvvlJh87KhAKYl2CCuMJWW6Wyv7UssURFbE,22366
69
- GameSentenceMiner/web/texthooking_page.py,sha256=YBcxXweFpcr-OaVYKrhIfEW3EPLZCizuWNWXhyvRCoY,16138
70
+ GameSentenceMiner/web/stats.py,sha256=LYMhekifcQo-cbfy2--b6vycKcu8RAoTnQA4TefcS6U,29037
71
+ GameSentenceMiner/web/texthooking_page.py,sha256=oXu_o1B4LlxVEBa1MPC996pxQrAysGkFXz9GTTUYKfk,13658
70
72
  GameSentenceMiner/web/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
73
  GameSentenceMiner/web/static/apple-touch-icon.png,sha256=OcMI8af_68DA_tweOsQ5LytTyMwm7-hPW07IfrOVgEs,46132
72
74
  GameSentenceMiner/web/static/favicon-96x96.png,sha256=lOePzjiKl1JY2J1kT_PMdyEnrlJmi5GWbmXJunM12B4,16502
@@ -78,28 +80,35 @@ GameSentenceMiner/web/static/web-app-manifest-192x192.png,sha256=EfSNnBmsSaLfESb
78
80
  GameSentenceMiner/web/static/web-app-manifest-512x512.png,sha256=wyqgCWCrLEUxSRXmaA3iJEESd-vM-ZmlTtZFBY4V8Pk,230819
79
81
  GameSentenceMiner/web/static/css/dashboard-shared.css,sha256=qWE6OXcMyVJrCd-UO2jQEq-6sPkJJDA4rJsrpfeodMU,5392
80
82
  GameSentenceMiner/web/static/css/kanji-grid.css,sha256=NzJg0Y85mMn4y0Ri5PPm7ATyIgfwGhTHDETQGogzeFM,4328
83
+ GameSentenceMiner/web/static/css/loading-skeleton.css,sha256=4DHa8AdnMvxpA8nH86m8abmm8oWkVfZaNECSl3g2iZk,855
81
84
  GameSentenceMiner/web/static/css/overview.css,sha256=FW1ZKLbMRvCm2do0Z5dfzA4sYLQkCTJuLO77aUiJIYU,16313
82
85
  GameSentenceMiner/web/static/css/popups-shared.css,sha256=OMbDk6rF5spkFp2Zk3CkkE84Sl4RP4H4h-AhA8lCjhM,2393
83
- GameSentenceMiner/web/static/css/search.css,sha256=x06xsErfThUpE1NAG4CA2sCPSZ-ofNWEI_ekV2Ok5hY,253
86
+ GameSentenceMiner/web/static/css/search.css,sha256=30O89k05YkvFR_CMVTwKYh4W9Mqrvh05zHxZuqEz7eM,1182
84
87
  GameSentenceMiner/web/static/css/shared.css,sha256=qIah6fFXr7ckRxnHSMfT79qu_tJ67fN-fCcDpU4exHA,19589
85
- GameSentenceMiner/web/static/css/stats.css,sha256=MJTzhC0gjRa9szN5ZpZSOwiXAuwr5nGoA2AplYGhw68,16670
86
- GameSentenceMiner/web/static/js/anki_stats.js,sha256=ZZqvqvePan9w17Nry7ui1ozR0SM0lAjfvZNI4XDPSzU,6871
87
- GameSentenceMiner/web/static/js/database.js,sha256=Yz72C0u2KbL8w9mkenNzeBi_v8mZrMyM3dEsxps7r5E,31133
88
+ GameSentenceMiner/web/static/css/stats.css,sha256=3K9M8qgFJzkU6-xiXRJXd85krK4desi7R8BpxIfHvUU,18093
89
+ GameSentenceMiner/web/static/js/anki_stats.js,sha256=WlfWDvVROSk13g2up9R29Tc8eI_ctsRmzTtLE5v_PxE,18231
90
+ GameSentenceMiner/web/static/js/database.js,sha256=kM5hDl0merhKjbKQhWklLGi1L_O1_tJbkqFXxRTZxe4,33202
88
91
  GameSentenceMiner/web/static/js/goals.js,sha256=PaJNS1jR1pIpBxYrI8Gog6XFQNPrnbaNWZ1QX7JVlAo,30938
92
+ GameSentenceMiner/web/static/js/heatmap.js,sha256=9E4SPGxjWX5-EWbXtiKFzNLGY3LtpbUmfmeATcrYRDU,14704
89
93
  GameSentenceMiner/web/static/js/kanji-grid.js,sha256=k9y25X4TvFYi2CEO7cusrcFVSQRBFWAT1LQ3zJhewYc,16365
90
- GameSentenceMiner/web/static/js/overview.js,sha256=X2zYM0FOxZ3rO9MIor1KBp7Wbffq7yFypHzoBXv6sG8,62410
91
- GameSentenceMiner/web/static/js/search.js,sha256=QrjsVXf90c1LzD09TPaK93RbIN9yEiXF8IKAKrDY4hw,10482
94
+ GameSentenceMiner/web/static/js/overview.js,sha256=-bENoz8GenHkEKdOFaO8WzsXBHOFS7-VaLapW1qRnUY,52400
95
+ GameSentenceMiner/web/static/js/search.js,sha256=QYbIpmBhFNaQ2O7mPN8k9ChSCikIGJvEo-3jOEU2WPM,16158
92
96
  GameSentenceMiner/web/static/js/shared.js,sha256=ZNibQkJxdypg93uMhmr_NEgoT5hJfxikzF6RKGz5Wy0,20416
93
- GameSentenceMiner/web/static/js/stats.js,sha256=kfJ756a9JPK8l9S8YUldxXuICAGMIHMGaDoJCKpjfEs,35604
94
- GameSentenceMiner/web/templates/anki_stats.html,sha256=0Y5lT-wGlliqgWHx9bgRZyiT_y2p-kqZcBA-ufwzR48,12715
95
- GameSentenceMiner/web/templates/database.html,sha256=fwqNfS4yX_LOXAMCSL3ieuuvzWz8KuwXizFplPFyzSs,17972
96
- GameSentenceMiner/web/templates/goals.html,sha256=kYUIteYzIArDWyZguuNzJScwMrrezWjFXxreU-LzzRk,21755
97
+ GameSentenceMiner/web/static/js/stats.js,sha256=zPhkdY3RXiQrhPmZyFyh6xwOn3DxQI8Jm6fqbJbjeDk,51322
98
+ GameSentenceMiner/web/templates/anki_stats.html,sha256=ixMg8nT5gXfTWVFG-xa7pppJuzEG2UAOaDhikWOWAK8,18187
99
+ GameSentenceMiner/web/templates/database.html,sha256=5SE7cSYa-ZDVLm0ykb1T11Yd4Bm_E600EWaQi2UWip8,18577
100
+ GameSentenceMiner/web/templates/goals.html,sha256=X5ViEeUT3YnCVM_kofCJ6A0_Wn2TVQQdmBiblZN5Gpo,20909
97
101
  GameSentenceMiner/web/templates/index.html,sha256=y1mrlcKWRdwmfBP8B06p1CBk1avAJOr32388nX8YX4A,229074
98
- GameSentenceMiner/web/templates/overview.html,sha256=bzV28od9JyrQYD2M2O4rnRSrw0H7Atr7XoeQ8XSSpCQ,26327
99
- GameSentenceMiner/web/templates/search.html,sha256=nao3M_hAbm5ftzThi91NtQ3ZiINMPUNx4ngFmqLnLQ4,4060
100
- GameSentenceMiner/web/templates/stats.html,sha256=IOOFK3DXa3UVSrTLR_kLLNl6iy3ei-_C4EGqdbvZCos,22811
102
+ GameSentenceMiner/web/templates/overview.html,sha256=HkEelQ60aJkt2OwNouqY2TxUF3fzRt85VHOe32C2-TM,13117
103
+ GameSentenceMiner/web/templates/search.html,sha256=34mv69GQXBGq-TdagyZ82QpXH9JYWGOXMDbCfoGUoGI,6150
104
+ GameSentenceMiner/web/templates/stats.html,sha256=RJhWQ1lyOGkYWrqxJNutnJs703IVdCQhBG9Cud0CMPs,5605
101
105
  GameSentenceMiner/web/templates/utility.html,sha256=KtqnZUMAYs5XsEdC9Tlsd40NKAVic0mu6sh-ReMDJpU,16940
106
+ GameSentenceMiner/web/templates/components/date-range.html,sha256=kgfsPCJYKbqn8N5mEZTpC8s2aPE6xSrXWEeSQyPfcdE,950
107
+ GameSentenceMiner/web/templates/components/html-head.html,sha256=8gD3iu3YhM3-q6PVfhLZ9L6rdWfbbpKKXY0CSXAitZc,1579
108
+ GameSentenceMiner/web/templates/components/js-config.html,sha256=-ZnbMWGJ7Ck190kqcEIBdjuglSiGUCt307ixztHuo0M,960
102
109
  GameSentenceMiner/web/templates/components/navigation.html,sha256=7FGv59-4QocYy3b_BolkPXsp2drwwLjHraL5GUM27b4,1165
110
+ GameSentenceMiner/web/templates/components/popups.html,sha256=hSe3HCp_TA8lEEOxI4ZLCTJvGH7RO8EI1b3ktxDgbls,789
111
+ GameSentenceMiner/web/templates/components/settings-modal.html,sha256=6d47ov5nVlHMFgFugGkq89Z9_Zp_yqmj7YfU0U7q-Y8,15779
103
112
  GameSentenceMiner/web/templates/components/theme-styles.html,sha256=hiq3zdJljpRjQO1iUA7gfFKwXebltG-IWW-gnKS4GHA,3439
104
113
  GameSentenceMiner/web/templates/components/kanji_grid/basic_kanji_book_bkb_v1_v2.json,sha256=jVf6zp1qCEng0YFZQJsBmHwJyWWgT-Q__bhkFzc9dHo,1868
105
114
  GameSentenceMiner/web/templates/components/kanji_grid/duolingo_kanji.json,sha256=0DqxCTtR2D6QxnXMHkr022254_OLRvc2lLSiybxEsJw,6338
@@ -126,9 +135,9 @@ GameSentenceMiner/web/templates/components/kanji_grid/thousand_character_classic
126
135
  GameSentenceMiner/web/templates/components/kanji_grid/wanikani_levels.json,sha256=8wjnnaYQqmho6t5tMxrIAc03512A2tYhQh5dfsQnfAM,11372
127
136
  GameSentenceMiner/web/templates/components/kanji_grid/words_hk_frequency_list.json,sha256=wRkqZNPzz6DT9OTPHpXwfqW96Qb96stCQNNgOL-ZdKk,17535
128
137
  GameSentenceMiner/wip/__init___.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
129
- gamesentenceminer-2.18.15.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
130
- gamesentenceminer-2.18.15.dist-info/METADATA,sha256=GKF6rzzvHB0ifiTpDpZKQFzpR_VU_lVhQIOcLVo1FT0,7488
131
- gamesentenceminer-2.18.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
132
- gamesentenceminer-2.18.15.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
133
- gamesentenceminer-2.18.15.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
134
- gamesentenceminer-2.18.15.dist-info/RECORD,,
138
+ gamesentenceminer-2.18.17.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
139
+ gamesentenceminer-2.18.17.dist-info/METADATA,sha256=P3n2dpzmGimmdKfstygPWNvooSBB_lHKHgzNA961zA8,7488
140
+ gamesentenceminer-2.18.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
141
+ gamesentenceminer-2.18.17.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
142
+ gamesentenceminer-2.18.17.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
143
+ gamesentenceminer-2.18.17.dist-info/RECORD,,