GameSentenceMiner 2.19.16__py3-none-any.whl → 2.20.0__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.

Potentially problematic release.


This version of GameSentenceMiner might be problematic. Click here for more details.

Files changed (70) hide show
  1. GameSentenceMiner/__init__.py +39 -0
  2. GameSentenceMiner/anki.py +6 -3
  3. GameSentenceMiner/gametext.py +13 -2
  4. GameSentenceMiner/gsm.py +40 -3
  5. GameSentenceMiner/locales/en_us.json +4 -0
  6. GameSentenceMiner/locales/ja_jp.json +4 -0
  7. GameSentenceMiner/locales/zh_cn.json +4 -0
  8. GameSentenceMiner/obs.py +4 -1
  9. GameSentenceMiner/owocr/owocr/ocr.py +304 -134
  10. GameSentenceMiner/owocr/owocr/run.py +1 -1
  11. GameSentenceMiner/ui/anki_confirmation.py +4 -2
  12. GameSentenceMiner/ui/config_gui.py +12 -0
  13. GameSentenceMiner/util/configuration.py +6 -2
  14. GameSentenceMiner/util/cron/__init__.py +12 -0
  15. GameSentenceMiner/util/cron/daily_rollup.py +613 -0
  16. GameSentenceMiner/util/cron/jiten_update.py +397 -0
  17. GameSentenceMiner/util/cron/populate_games.py +154 -0
  18. GameSentenceMiner/util/cron/run_crons.py +148 -0
  19. GameSentenceMiner/util/cron/setup_populate_games_cron.py +118 -0
  20. GameSentenceMiner/util/cron_table.py +334 -0
  21. GameSentenceMiner/util/db.py +236 -49
  22. GameSentenceMiner/util/ffmpeg.py +23 -4
  23. GameSentenceMiner/util/games_table.py +340 -93
  24. GameSentenceMiner/util/jiten_api_client.py +188 -0
  25. GameSentenceMiner/util/stats_rollup_table.py +216 -0
  26. GameSentenceMiner/web/anki_api_endpoints.py +438 -220
  27. GameSentenceMiner/web/database_api.py +955 -1259
  28. GameSentenceMiner/web/jiten_database_api.py +1015 -0
  29. GameSentenceMiner/web/rollup_stats.py +672 -0
  30. GameSentenceMiner/web/static/css/dashboard-shared.css +75 -13
  31. GameSentenceMiner/web/static/css/overview.css +604 -47
  32. GameSentenceMiner/web/static/css/search.css +226 -0
  33. GameSentenceMiner/web/static/css/shared.css +762 -0
  34. GameSentenceMiner/web/static/css/stats.css +221 -0
  35. GameSentenceMiner/web/static/js/components/bar-chart.js +339 -0
  36. GameSentenceMiner/web/static/js/database-bulk-operations.js +320 -0
  37. GameSentenceMiner/web/static/js/database-game-data.js +390 -0
  38. GameSentenceMiner/web/static/js/database-game-operations.js +213 -0
  39. GameSentenceMiner/web/static/js/database-helpers.js +44 -0
  40. GameSentenceMiner/web/static/js/database-jiten-integration.js +750 -0
  41. GameSentenceMiner/web/static/js/database-popups.js +89 -0
  42. GameSentenceMiner/web/static/js/database-tabs.js +64 -0
  43. GameSentenceMiner/web/static/js/database-text-management.js +371 -0
  44. GameSentenceMiner/web/static/js/database.js +86 -718
  45. GameSentenceMiner/web/static/js/goals.js +79 -18
  46. GameSentenceMiner/web/static/js/heatmap.js +29 -23
  47. GameSentenceMiner/web/static/js/overview.js +1205 -339
  48. GameSentenceMiner/web/static/js/regex-patterns.js +100 -0
  49. GameSentenceMiner/web/static/js/search.js +215 -18
  50. GameSentenceMiner/web/static/js/shared.js +193 -39
  51. GameSentenceMiner/web/static/js/stats.js +1536 -179
  52. GameSentenceMiner/web/stats.py +1142 -269
  53. GameSentenceMiner/web/stats_api.py +2104 -0
  54. GameSentenceMiner/web/templates/anki_stats.html +4 -18
  55. GameSentenceMiner/web/templates/components/date-range.html +118 -3
  56. GameSentenceMiner/web/templates/components/html-head.html +40 -6
  57. GameSentenceMiner/web/templates/components/js-config.html +8 -8
  58. GameSentenceMiner/web/templates/components/regex-input.html +160 -0
  59. GameSentenceMiner/web/templates/database.html +564 -117
  60. GameSentenceMiner/web/templates/goals.html +41 -5
  61. GameSentenceMiner/web/templates/overview.html +159 -129
  62. GameSentenceMiner/web/templates/search.html +78 -9
  63. GameSentenceMiner/web/templates/stats.html +159 -5
  64. GameSentenceMiner/web/texthooking_page.py +280 -111
  65. {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/METADATA +43 -2
  66. {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/RECORD +70 -47
  67. {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/WHEEL +0 -0
  68. {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/entry_points.txt +0 -0
  69. {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/licenses/LICENSE +0 -0
  70. {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/top_level.txt +0 -0
@@ -74,7 +74,7 @@ document.addEventListener('DOMContentLoaded', function () {
74
74
  // Calculate current progress
75
75
  const currentHours = allGamesStats.total_time_hours || 0;
76
76
  const currentCharacters = allGamesStats.total_characters || 0;
77
- const currentGames = allGamesStats.unique_games || 0;
77
+ const currentGames = allGamesStats.completed_games || 0;
78
78
 
79
79
  // Calculate daily averages for projections using 90-day lookback period (reusing logic from stats.js)
80
80
  const dailyHoursAvg = calculateDailyAverage(allLinesData, 'hours');
@@ -299,6 +299,25 @@ document.addEventListener('DOMContentLoaded', function () {
299
299
  document.getElementById('charsDaysRemaining').style.display = 'none';
300
300
  }
301
301
 
302
+ // Update cards mined goal
303
+ const cardsGoalItem = document.getElementById('cardsGoalItem');
304
+ if (data.cards && data.cards.has_target) {
305
+ hasAnyTarget = true;
306
+ cardsGoalItem.style.display = 'block';
307
+
308
+ document.getElementById('todayCardsProgress').textContent = data.cards.progress;
309
+ document.getElementById('todayCardsRequired').textContent = data.cards.required;
310
+
311
+ // Add green highlight if goal is met
312
+ if (data.cards.progress >= data.cards.required) {
313
+ cardsGoalItem.classList.add('goal-met');
314
+ } else {
315
+ cardsGoalItem.classList.remove('goal-met');
316
+ }
317
+ } else {
318
+ cardsGoalItem.style.display = 'none';
319
+ }
320
+
302
321
  // Show/hide sections based on whether any targets are set
303
322
  if (hasAnyTarget) {
304
323
  document.getElementById('noTargetsMessage').style.display = 'none';
@@ -333,33 +352,48 @@ document.addEventListener('DOMContentLoaded', function () {
333
352
  document.getElementById('projectionHoursValue').textContent =
334
353
  Math.floor(data.hours.projection).toLocaleString() + 'h';
335
354
 
355
+ // Update label with target date (formatted in user's locale)
356
+ const hoursLabel = document.getElementById('hoursProjectionLabel');
357
+ if (hoursLabel) {
358
+ const targetDate = new Date(data.hours.target_date);
359
+ const formattedTargetDate = targetDate.toLocaleDateString(navigator.language);
360
+ hoursLabel.textContent = `Total Hours by ${formattedTargetDate}`;
361
+ }
362
+
336
363
  // Calculate percentage difference
337
364
  const hoursPercentDiff = ((data.hours.projection - data.hours.target) / data.hours.target) * 100;
338
365
 
339
- // Status message with pace badge
366
+ // Calculate projected completion date
367
+ const hoursRemaining = Math.max(0, data.hours.target - data.hours.current);
368
+ const hoursDaysToComplete = data.hours.daily_average > 0 ? Math.ceil(hoursRemaining / data.hours.daily_average) : 0;
369
+ const hoursCompletionDate = new Date();
370
+ hoursCompletionDate.setDate(hoursCompletionDate.getDate() + hoursDaysToComplete);
371
+ const hoursCompletionDateStr = hoursCompletionDate.toLocaleDateString(navigator.language);
372
+
373
+ // Status message with pace badge and completion date
340
374
  const hoursStatus = document.getElementById('hoursProjectionStatus');
341
375
  if (hoursPercentDiff >= 5) {
342
376
  // Over-achieving by 5% or more
343
377
  const badge = `<span class="pace-badge pace-ahead">+${Math.floor(hoursPercentDiff)}%</span>`;
344
- hoursStatus.innerHTML = `On Track! 🎉 ${badge}`;
378
+ hoursStatus.innerHTML = `On Track! 🎉 ${badge}<br><small style="font-size: 0.85em; opacity: 0.9;">Est. completion: ${hoursCompletionDateStr}</small>`;
345
379
  hoursStatus.className = 'dashboard-progress-value positive';
346
380
  } else if (hoursPercentDiff >= -5) {
347
381
  // Within ±5% - perfect pace
348
382
  const badge = `<span class="pace-badge pace-perfect">±${Math.abs(Math.floor(hoursPercentDiff))}%</span>`;
349
- hoursStatus.innerHTML = `Perfect Pace! ✅ ${badge}`;
383
+ hoursStatus.innerHTML = `Perfect Pace! ✅ ${badge}<br><small style="font-size: 0.85em; opacity: 0.9;">Est. completion: ${hoursCompletionDateStr}</small>`;
350
384
  hoursStatus.className = 'dashboard-progress-value positive';
351
385
  } else if (hoursPercentDiff >= -15) {
352
386
  // Slightly behind (-5% to -15%)
353
387
  const shortfall = data.hours.target - data.hours.projection;
354
388
  const badge = `<span class="pace-badge pace-behind-mild">${Math.floor(hoursPercentDiff)}%</span>`;
355
- hoursStatus.innerHTML = `${Math.floor(shortfall)}h short ${badge}`;
389
+ hoursStatus.innerHTML = `${Math.floor(shortfall)}h short ${badge}<br><small style="font-size: 0.85em; opacity: 0.9;">Est. completion: ${hoursCompletionDateStr}</small>`;
356
390
  hoursStatus.className = 'dashboard-progress-value';
357
391
  hoursStatus.style.color = 'var(--warning-color)';
358
392
  } else {
359
393
  // Significantly behind (< -15%)
360
394
  const shortfall = data.hours.target - data.hours.projection;
361
395
  const badge = `<span class="pace-badge pace-behind">${Math.floor(hoursPercentDiff)}%</span>`;
362
- hoursStatus.innerHTML = `${Math.floor(shortfall)}h short ${badge}`;
396
+ hoursStatus.innerHTML = `${Math.floor(shortfall)}h short ${badge}<br><small style="font-size: 0.85em; opacity: 0.9;">Est. completion: ${hoursCompletionDateStr}</small>`;
363
397
  hoursStatus.className = 'dashboard-progress-value';
364
398
  hoursStatus.style.color = 'var(--danger-color)';
365
399
  }
@@ -376,33 +410,48 @@ document.addEventListener('DOMContentLoaded', function () {
376
410
 
377
411
  document.getElementById('projectionCharsValue').textContent = formatGoalNumber(data.characters.projection);
378
412
 
413
+ // Update label with target date (formatted in user's locale)
414
+ const charsLabel = document.getElementById('charsProjectionLabel');
415
+ if (charsLabel) {
416
+ const targetDate = new Date(data.characters.target_date);
417
+ const formattedTargetDate = targetDate.toLocaleDateString(navigator.language);
418
+ charsLabel.textContent = `Total Characters by ${formattedTargetDate}`;
419
+ }
420
+
379
421
  // Calculate percentage difference
380
422
  const charsPercentDiff = ((data.characters.projection - data.characters.target) / data.characters.target) * 100;
381
423
 
382
- // Status message with pace badge
424
+ // Calculate projected completion date
425
+ const charsRemaining = Math.max(0, data.characters.target - data.characters.current);
426
+ const charsDaysToComplete = data.characters.daily_average > 0 ? Math.ceil(charsRemaining / data.characters.daily_average) : 0;
427
+ const charsCompletionDate = new Date();
428
+ charsCompletionDate.setDate(charsCompletionDate.getDate() + charsDaysToComplete);
429
+ const charsCompletionDateStr = charsCompletionDate.toLocaleDateString(navigator.language);
430
+
431
+ // Status message with pace badge and completion date
383
432
  const charsStatus = document.getElementById('charsProjectionStatus');
384
433
  if (charsPercentDiff >= 5) {
385
434
  // Over-achieving by 5% or more
386
435
  const badge = `<span class="pace-badge pace-ahead">+${Math.floor(charsPercentDiff)}%</span>`;
387
- charsStatus.innerHTML = `On Track! 🎉 ${badge}`;
436
+ charsStatus.innerHTML = `On Track! 🎉 ${badge}<br><small style="font-size: 0.85em; opacity: 0.9;">Est. completion: ${charsCompletionDateStr}</small>`;
388
437
  charsStatus.className = 'dashboard-progress-value positive';
389
438
  } else if (charsPercentDiff >= -5) {
390
439
  // Within ±5% - perfect pace
391
440
  const badge = `<span class="pace-badge pace-perfect">±${Math.abs(Math.floor(charsPercentDiff))}%</span>`;
392
- charsStatus.innerHTML = `Perfect Pace! ✅ ${badge}`;
441
+ charsStatus.innerHTML = `Perfect Pace! ✅ ${badge}<br><small style="font-size: 0.85em; opacity: 0.9;">Est. completion: ${charsCompletionDateStr}</small>`;
393
442
  charsStatus.className = 'dashboard-progress-value positive';
394
443
  } else if (charsPercentDiff >= -15) {
395
444
  // Slightly behind (-5% to -15%)
396
445
  const shortfall = data.characters.target - data.characters.projection;
397
446
  const badge = `<span class="pace-badge pace-behind-mild">${Math.floor(charsPercentDiff)}%</span>`;
398
- charsStatus.innerHTML = `${formatGoalNumber(shortfall)} short ${badge}`;
447
+ charsStatus.innerHTML = `${formatGoalNumber(shortfall)} short ${badge}<br><small style="font-size: 0.85em; opacity: 0.9;">Est. completion: ${charsCompletionDateStr}</small>`;
399
448
  charsStatus.className = 'dashboard-progress-value';
400
449
  charsStatus.style.color = 'var(--warning-color)';
401
450
  } else {
402
451
  // Significantly behind (< -15%)
403
452
  const shortfall = data.characters.target - data.characters.projection;
404
453
  const badge = `<span class="pace-badge pace-behind">${Math.floor(charsPercentDiff)}%</span>`;
405
- charsStatus.innerHTML = `${formatGoalNumber(shortfall)} short ${badge}`;
454
+ charsStatus.innerHTML = `${formatGoalNumber(shortfall)} short ${badge}<br><small style="font-size: 0.85em; opacity: 0.9;">Est. completion: ${charsCompletionDateStr}</small>`;
406
455
  charsStatus.className = 'dashboard-progress-value';
407
456
  charsStatus.style.color = 'var(--danger-color)';
408
457
  }
@@ -422,30 +471,37 @@ document.addEventListener('DOMContentLoaded', function () {
422
471
  // Calculate percentage difference
423
472
  const gamesPercentDiff = ((data.games.projection - data.games.target) / data.games.target) * 100;
424
473
 
425
- // Status message with pace badge
474
+ // Calculate projected completion date
475
+ const gamesRemaining = Math.max(0, data.games.target - data.games.current);
476
+ const gamesDaysToComplete = data.games.daily_average > 0 ? Math.ceil(gamesRemaining / data.games.daily_average) : 0;
477
+ const gamesCompletionDate = new Date();
478
+ gamesCompletionDate.setDate(gamesCompletionDate.getDate() + gamesDaysToComplete);
479
+ const gamesCompletionDateStr = gamesCompletionDate.toLocaleDateString(navigator.language);
480
+
481
+ // Status message with pace badge and completion date
426
482
  const gamesStatus = document.getElementById('gamesProjectionStatus');
427
483
  if (gamesPercentDiff >= 5) {
428
484
  // Over-achieving by 5% or more
429
485
  const badge = `<span class="pace-badge pace-ahead">+${Math.floor(gamesPercentDiff)}%</span>`;
430
- gamesStatus.innerHTML = `On Track! 🎉 ${badge}`;
486
+ gamesStatus.innerHTML = `On Track! 🎉 ${badge}<br><small style="font-size: 0.85em; opacity: 0.9;">Est. completion: ${gamesCompletionDateStr}</small>`;
431
487
  gamesStatus.className = 'dashboard-progress-value positive';
432
488
  } else if (gamesPercentDiff >= -5) {
433
489
  // Within ±5% - perfect pace
434
490
  const badge = `<span class="pace-badge pace-perfect">±${Math.abs(Math.floor(gamesPercentDiff))}%</span>`;
435
- gamesStatus.innerHTML = `Perfect Pace! ✅ ${badge}`;
491
+ gamesStatus.innerHTML = `Perfect Pace! ✅ ${badge}<br><small style="font-size: 0.85em; opacity: 0.9;">Est. completion: ${gamesCompletionDateStr}</small>`;
436
492
  gamesStatus.className = 'dashboard-progress-value positive';
437
493
  } else if (gamesPercentDiff >= -15) {
438
494
  // Slightly behind (-5% to -15%)
439
495
  const shortfall = data.games.target - data.games.projection;
440
496
  const badge = `<span class="pace-badge pace-behind-mild">${Math.floor(gamesPercentDiff)}%</span>`;
441
- gamesStatus.innerHTML = `${shortfall} short ${badge}`;
497
+ gamesStatus.innerHTML = `${shortfall} short ${badge}<br><small style="font-size: 0.85em; opacity: 0.9;">Est. completion: ${gamesCompletionDateStr}</small>`;
442
498
  gamesStatus.className = 'dashboard-progress-value';
443
499
  gamesStatus.style.color = 'var(--warning-color)';
444
500
  } else {
445
501
  // Significantly behind (< -15%)
446
502
  const shortfall = data.games.target - data.games.projection;
447
503
  const badge = `<span class="pace-badge pace-behind">${Math.floor(gamesPercentDiff)}%</span>`;
448
- gamesStatus.innerHTML = `${shortfall} short ${badge}`;
504
+ gamesStatus.innerHTML = `${shortfall} short ${badge}<br><small style="font-size: 0.85em; opacity: 0.9;">Est. completion: ${gamesCompletionDateStr}</small>`;
449
505
  gamesStatus.className = 'dashboard-progress-value';
450
506
  gamesStatus.style.color = 'var(--danger-color)';
451
507
  }
@@ -502,6 +558,9 @@ document.addEventListener('DOMContentLoaded', function () {
502
558
 
503
559
  const gamesDateInput = document.getElementById('gamesTargetDate');
504
560
  if (gamesDateInput) gamesDateInput.value = window.statsConfig.gamesTargetDate || '';
561
+
562
+ const cardsDailyTargetInput = document.getElementById('cardsMinedDailyTarget');
563
+ if (cardsDailyTargetInput) cardsDailyTargetInput.value = window.statsConfig.cardsMinedDailyTarget || 10;
505
564
  }
506
565
 
507
566
  // Open settings modal
@@ -543,7 +602,8 @@ document.addEventListener('DOMContentLoaded', function () {
543
602
  games_target: parseInt(document.getElementById('gamesTarget').value),
544
603
  reading_hours_target_date: document.getElementById('readingHoursTargetDate').value || '',
545
604
  character_count_target_date: document.getElementById('characterCountTargetDate').value || '',
546
- games_target_date: document.getElementById('gamesTargetDate').value || ''
605
+ games_target_date: document.getElementById('gamesTargetDate').value || '',
606
+ cards_mined_daily_target: parseInt(document.getElementById('cardsMinedDailyTarget').value) || 10
547
607
  };
548
608
 
549
609
  const response = await fetch('/api/settings', {
@@ -565,7 +625,8 @@ document.addEventListener('DOMContentLoaded', function () {
565
625
  gamesTarget: formData.games_target,
566
626
  readingHoursTargetDate: formData.reading_hours_target_date,
567
627
  characterCountTargetDate: formData.character_count_target_date,
568
- gamesTargetDate: formData.games_target_date
628
+ gamesTargetDate: formData.games_target_date,
629
+ cardsMinedDailyTarget: formData.cards_mined_daily_target
569
630
  };
570
631
 
571
632
  // Show success message
@@ -222,34 +222,40 @@ class HeatmapRenderer {
222
222
  const activity = yearData[dateStr] || 0;
223
223
 
224
224
  if (activity > 0 && maxActivity > 0) {
225
- // Calculate percentage of maximum activity
226
- const percentage = (activity / maxActivity) * 100;
227
-
228
- // Assign discrete color levels based on percentage thresholds
229
- let colorLevel;
230
- if (percentage <= 25) {
231
- colorLevel = 1; // Light green
232
- } else if (percentage <= 50) {
233
- colorLevel = 2; // Medium green
234
- } else if (percentage <= 75) {
235
- colorLevel = 3; // Dark green
225
+ // Check if custom color function is provided
226
+ if (this.customColorFunction) {
227
+ cell.style.backgroundColor = this.customColorFunction(activity, maxActivity);
236
228
  } else {
237
- colorLevel = 4; // Darkest green
229
+ // Default color calculation
230
+ // Calculate percentage of maximum activity
231
+ const percentage = (activity / maxActivity) * 100;
232
+
233
+ // Assign discrete color levels based on percentage thresholds
234
+ let colorLevel;
235
+ if (percentage <= 25) {
236
+ colorLevel = 1; // Light green
237
+ } else if (percentage <= 50) {
238
+ colorLevel = 2; // Medium green
239
+ } else if (percentage <= 75) {
240
+ colorLevel = 3; // Dark green
241
+ } else {
242
+ colorLevel = 4; // Darkest green
243
+ }
244
+
245
+ // Define discrete colors for each level
246
+ const colors = {
247
+ 1: '#c6e48b', // Light green (1-25%)
248
+ 2: '#7bc96f', // Medium green (26-50%)
249
+ 3: '#239a3b', // Dark green (51-75%)
250
+ 4: '#196127' // Darkest green (76-100%)
251
+ };
252
+
253
+ cell.style.backgroundColor = colors[colorLevel];
238
254
  }
239
-
240
- // Define discrete colors for each level
241
- const colors = {
242
- 1: '#c6e48b', // Light green (1-25%)
243
- 2: '#7bc96f', // Medium green (26-50%)
244
- 3: '#239a3b', // Dark green (51-75%)
245
- 4: '#196127' // Darkest green (76-100%)
246
- };
247
-
248
- cell.style.backgroundColor = colors[colorLevel];
249
255
  }
250
256
 
251
257
  // Format tooltip based on metric type
252
- const activityLabel = this.metricName === 'sentences'
258
+ const activityLabel = this.metricName === 'sentences'
253
259
  ? `sentence${activity !== 1 ? 's' : ''} mined`
254
260
  : `${this.metricLabel}`;
255
261
  cell.title = `${dateStr}: ${activity} ${activityLabel}`;