GameSentenceMiner 2.16.11__py3-none-any.whl → 2.16.12__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.
- GameSentenceMiner/web/static/js/stats.js +61 -22
- GameSentenceMiner/web/stats.py +9 -4
- GameSentenceMiner/web/templates/stats.html +22 -0
- GameSentenceMiner/web/texthooking_page.py +5 -1
- {gamesentenceminer-2.16.11.dist-info → gamesentenceminer-2.16.12.dist-info}/METADATA +1 -1
- {gamesentenceminer-2.16.11.dist-info → gamesentenceminer-2.16.12.dist-info}/RECORD +10 -10
- {gamesentenceminer-2.16.11.dist-info → gamesentenceminer-2.16.12.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.16.11.dist-info → gamesentenceminer-2.16.12.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.16.11.dist-info → gamesentenceminer-2.16.12.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.16.11.dist-info → gamesentenceminer-2.16.12.dist-info}/top_level.txt +0 -0
|
@@ -152,11 +152,12 @@ document.addEventListener('DOMContentLoaded', function () {
|
|
|
152
152
|
tempStreak = 0;
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
-
|
|
156
|
-
// Calculate current streak from today backwards
|
|
155
|
+
|
|
156
|
+
// Calculate current streak from today backwards, using streak requirement hours from config
|
|
157
157
|
const date = new Date();
|
|
158
158
|
const today = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
|
159
|
-
|
|
159
|
+
const streakRequirement = window.statsConfig ? window.statsConfig.streakRequirementHours : 1.0;
|
|
160
|
+
|
|
160
161
|
// Find today's index or the most recent date before today
|
|
161
162
|
let todayIndex = -1;
|
|
162
163
|
for (let i = dates.length - 1; i >= 0; i--) {
|
|
@@ -165,11 +166,11 @@ document.addEventListener('DOMContentLoaded', function () {
|
|
|
165
166
|
break;
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
|
-
|
|
169
|
+
|
|
169
170
|
// Count backwards from today (or most recent date)
|
|
170
171
|
if (todayIndex >= 0) {
|
|
171
172
|
for (let i = todayIndex; i >= 0; i--) {
|
|
172
|
-
if (dates[i].activity
|
|
173
|
+
if (dates[i].activity >= streakRequirement) {
|
|
173
174
|
currentStreak++;
|
|
174
175
|
} else {
|
|
175
176
|
break;
|
|
@@ -196,18 +197,24 @@ document.addEventListener('DOMContentLoaded', function () {
|
|
|
196
197
|
// Calculate reading time for each day with activity
|
|
197
198
|
let totalHours = 0;
|
|
198
199
|
let activeDays = 0;
|
|
199
|
-
|
|
200
|
-
|
|
200
|
+
let afkTimerSeconds = window.statsConfig ? window.statsConfig.afkTimerSeconds : 120;
|
|
201
|
+
// Try to get AFK timer from settings modal if available and valid
|
|
202
|
+
const afkTimerInput = document.getElementById('afkTimer');
|
|
203
|
+
if (afkTimerInput && afkTimerInput.value) {
|
|
204
|
+
const parsed = parseInt(afkTimerInput.value, 10);
|
|
205
|
+
if (!isNaN(parsed) && parsed > 0) afkTimerSeconds = parsed;
|
|
206
|
+
}
|
|
207
|
+
|
|
201
208
|
for (const [dateStr, timestamps] of Object.entries(dailyTimestamps)) {
|
|
202
209
|
if (timestamps.length >= 2) {
|
|
203
210
|
timestamps.sort((a, b) => a - b);
|
|
204
211
|
let dayReadingTime = 0;
|
|
205
|
-
|
|
212
|
+
|
|
206
213
|
for (let i = 1; i < timestamps.length; i++) {
|
|
207
214
|
const gap = timestamps[i] - timestamps[i-1];
|
|
208
215
|
dayReadingTime += Math.min(gap, afkTimerSeconds);
|
|
209
216
|
}
|
|
210
|
-
|
|
217
|
+
|
|
211
218
|
if (dayReadingTime > 0) {
|
|
212
219
|
totalHours += dayReadingTime / 3600;
|
|
213
220
|
activeDays++;
|
|
@@ -837,14 +844,20 @@ document.addEventListener('DOMContentLoaded', function () {
|
|
|
837
844
|
}
|
|
838
845
|
|
|
839
846
|
// Goal Progress Chart functionality
|
|
840
|
-
let goalSettings = {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
};
|
|
847
|
+
let goalSettings = window.statsConfig || {};
|
|
848
|
+
if (!goalSettings.reading_hours_target) goalSettings.reading_hours_target = 1500;
|
|
849
|
+
if (!goalSettings.character_count_target) goalSettings.character_count_target = 25000000;
|
|
850
|
+
if (!goalSettings.games_target) goalSettings.games_target = 100;
|
|
845
851
|
|
|
846
|
-
// Function to load goal settings from API
|
|
852
|
+
// Function to load goal settings from API (fallback)
|
|
847
853
|
async function loadGoalSettings() {
|
|
854
|
+
// Use global config if available, otherwise fetch
|
|
855
|
+
if (window.statsConfig) {
|
|
856
|
+
goalSettings.reading_hours_target = window.statsConfig.readingHoursTarget || 1500;
|
|
857
|
+
goalSettings.character_count_target = window.statsConfig.characterCountTarget || 25000000;
|
|
858
|
+
goalSettings.games_target = window.statsConfig.gamesTarget || 100;
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
848
861
|
try {
|
|
849
862
|
const response = await fetch('/api/settings');
|
|
850
863
|
if (response.ok) {
|
|
@@ -899,8 +912,14 @@ document.addEventListener('DOMContentLoaded', function () {
|
|
|
899
912
|
if (timestamps.length >= 2) {
|
|
900
913
|
timestamps.sort((a, b) => a - b);
|
|
901
914
|
let dayHours = 0;
|
|
902
|
-
|
|
903
|
-
|
|
915
|
+
let afkTimerSeconds = window.statsConfig ? window.statsConfig.afkTimerSeconds : 120;
|
|
916
|
+
// Try to get AFK timer from settings modal if available and valid
|
|
917
|
+
const afkTimerInput = document.getElementById('afkTimer');
|
|
918
|
+
if (afkTimerInput && afkTimerInput.value) {
|
|
919
|
+
const parsed = parseInt(afkTimerInput.value, 10);
|
|
920
|
+
if (!isNaN(parsed) && parsed > 0) afkTimerSeconds = parsed;
|
|
921
|
+
}
|
|
922
|
+
|
|
904
923
|
for (let i = 1; i < timestamps.length; i++) {
|
|
905
924
|
const gap = timestamps[i] - timestamps[i-1];
|
|
906
925
|
dayHours += Math.min(gap, afkTimerSeconds) / 3600;
|
|
@@ -1099,9 +1118,30 @@ document.addEventListener('DOMContentLoaded', function () {
|
|
|
1099
1118
|
}
|
|
1100
1119
|
|
|
1101
1120
|
// Initial load with saved year preference
|
|
1102
|
-
const savedYear = localStorage.getItem('selectedHeatmapYear') || 'all';
|
|
1121
|
+
const savedYear = localStorage.getItem('selectedHeatmapYear') || window.statsConfig?.heatmapDisplayYear || 'all';
|
|
1103
1122
|
loadStatsData(savedYear);
|
|
1104
1123
|
|
|
1124
|
+
// Populate settings modal with global config values on load
|
|
1125
|
+
if (window.statsConfig) {
|
|
1126
|
+
const sessionGapInput = document.getElementById('sessionGap');
|
|
1127
|
+
if (sessionGapInput) sessionGapInput.value = window.statsConfig.sessionGapSeconds || 3600;
|
|
1128
|
+
|
|
1129
|
+
const streakReqInput = document.getElementById('streakRequirement');
|
|
1130
|
+
if (streakReqInput) streakReqInput.value = window.statsConfig.streakRequirementHours || 1.0;
|
|
1131
|
+
|
|
1132
|
+
const heatmapYearSelect = document.getElementById('heatmapYear');
|
|
1133
|
+
if (heatmapYearSelect) heatmapYearSelect.value = window.statsConfig.heatmapDisplayYear || 'all';
|
|
1134
|
+
|
|
1135
|
+
const hoursTargetInput = document.getElementById('readingHoursTarget');
|
|
1136
|
+
if (hoursTargetInput) hoursTargetInput.value = window.statsConfig.readingHoursTarget || 1500;
|
|
1137
|
+
|
|
1138
|
+
const charsTargetInput = document.getElementById('characterCountTarget');
|
|
1139
|
+
if (charsTargetInput) charsTargetInput.value = window.statsConfig.characterCountTarget || 25000000;
|
|
1140
|
+
|
|
1141
|
+
const gamesTargetInput = document.getElementById('gamesTarget');
|
|
1142
|
+
if (gamesTargetInput) gamesTargetInput.value = window.statsConfig.gamesTarget || 100;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1105
1145
|
// Function to update goal progress using existing stats data
|
|
1106
1146
|
async function updateGoalProgressWithData(statsData) {
|
|
1107
1147
|
const goalProgressChart = document.getElementById('goalProgressChart');
|
|
@@ -1178,9 +1218,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
|
|
1178
1218
|
|
|
1179
1219
|
// Calculate sessions (count gaps > session threshold as new sessions)
|
|
1180
1220
|
let sessions = 0;
|
|
1181
|
-
|
|
1182
|
-
// Try to get session gap from settings modal if available
|
|
1183
|
-
let sessionGap = sessionGapDefault;
|
|
1221
|
+
let sessionGap = window.statsConfig ? window.statsConfig.sessionGapSeconds : 3600;
|
|
1222
|
+
// Try to get session gap from settings modal if available and valid
|
|
1184
1223
|
const sessionGapInput = document.getElementById('sessionGap');
|
|
1185
1224
|
if (sessionGapInput && sessionGapInput.value) {
|
|
1186
1225
|
const parsed = parseInt(sessionGapInput.value, 10);
|
|
@@ -1214,7 +1253,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|
|
1214
1253
|
.filter(ts => !isNaN(ts))
|
|
1215
1254
|
.sort((a, b) => a - b);
|
|
1216
1255
|
// Get AFK timer from settings modal if available
|
|
1217
|
-
let afkTimerSeconds = 120;
|
|
1256
|
+
let afkTimerSeconds = window.statsConfig ? window.statsConfig.afkTimerSeconds : 120;
|
|
1218
1257
|
const afkTimerInput = document.getElementById('afkTimer');
|
|
1219
1258
|
if (afkTimerInput && afkTimerInput.value) {
|
|
1220
1259
|
const parsed = parseInt(afkTimerInput.value, 10);
|
GameSentenceMiner/web/stats.py
CHANGED
|
@@ -345,8 +345,11 @@ def calculate_time_based_streak(lines, streak_requirement_hours=None):
|
|
|
345
345
|
int: Current streak in days
|
|
346
346
|
"""
|
|
347
347
|
if streak_requirement_hours is None:
|
|
348
|
-
|
|
349
|
-
|
|
348
|
+
# Prefer stats_config if available, fallback to config.advanced, then 1.0
|
|
349
|
+
try:
|
|
350
|
+
streak_requirement_hours = get_stats_config().streak_requirement_hours
|
|
351
|
+
except AttributeError:
|
|
352
|
+
streak_requirement_hours = getattr(get_config().advanced, 'streak_requirement_hours', 1.0)
|
|
350
353
|
# Add debug logging
|
|
351
354
|
logger.debug(f"Calculating streak with requirement: {streak_requirement_hours} hours")
|
|
352
355
|
logger.debug(f"Processing {len(lines)} lines for streak calculation")
|
|
@@ -440,9 +443,10 @@ def calculate_current_game_stats(all_lines):
|
|
|
440
443
|
# Calculate sessions (gaps of more than session_gap_seconds = new session)
|
|
441
444
|
sorted_timestamps = sorted(timestamps)
|
|
442
445
|
sessions = 1
|
|
446
|
+
session_gap = get_stats_config().session_gap_seconds
|
|
443
447
|
for i in range(1, len(sorted_timestamps)):
|
|
444
448
|
time_gap = sorted_timestamps[i] - sorted_timestamps[i-1]
|
|
445
|
-
if time_gap >
|
|
449
|
+
if time_gap > session_gap:
|
|
446
450
|
sessions += 1
|
|
447
451
|
|
|
448
452
|
# Calculate daily activity for progress trend
|
|
@@ -533,9 +537,10 @@ def calculate_all_games_stats(all_lines):
|
|
|
533
537
|
# Calculate sessions across all games (gaps of more than 1 hour = new session)
|
|
534
538
|
sorted_timestamps = sorted(timestamps)
|
|
535
539
|
sessions = 1
|
|
540
|
+
session_gap = get_stats_config().session_gap_seconds
|
|
536
541
|
for i in range(1, len(sorted_timestamps)):
|
|
537
542
|
time_gap = sorted_timestamps[i] - sorted_timestamps[i-1]
|
|
538
|
-
if time_gap >
|
|
543
|
+
if time_gap > session_gap:
|
|
539
544
|
sessions += 1
|
|
540
545
|
|
|
541
546
|
# Calculate daily activity for progress trend
|
|
@@ -520,6 +520,28 @@
|
|
|
520
520
|
</div>
|
|
521
521
|
|
|
522
522
|
|
|
523
|
+
{% set afk_timer = stats_config.afk_timer_seconds | default(120) %}
|
|
524
|
+
{% set session_gap = stats_config.session_gap_seconds | default(3600) %}
|
|
525
|
+
{% set streak_req = config.advanced.streak_requirement_hours | default(1.0) %}
|
|
526
|
+
{% set hours_target = stats_config.reading_hours_target | default(1500) %}
|
|
527
|
+
{% set chars_target = stats_config.character_count_target | default(25000000) %}
|
|
528
|
+
{% set games_target = stats_config.games_target | default(100) %}
|
|
529
|
+
{% set heatmap_year = 'all' %}
|
|
530
|
+
|
|
531
|
+
{% set stats_config = {
|
|
532
|
+
'afkTimerSeconds': afk_timer,
|
|
533
|
+
'sessionGapSeconds': session_gap,
|
|
534
|
+
'streakRequirementHours': streak_req,
|
|
535
|
+
'readingHoursTarget': hours_target,
|
|
536
|
+
'characterCountTarget': chars_target,
|
|
537
|
+
'gamesTarget': games_target,
|
|
538
|
+
'heatmapDisplayYear': heatmap_year
|
|
539
|
+
} %}
|
|
540
|
+
|
|
541
|
+
<!-- Inject stats config values for frontend -->
|
|
542
|
+
<script>
|
|
543
|
+
window.statsConfig = {{ stats_config | tojson }};
|
|
544
|
+
</script>
|
|
523
545
|
<!-- Include shared JavaScript first (required dependency for stats.js) -->
|
|
524
546
|
<script src="/static/js/shared.js"></script>
|
|
525
547
|
<script src="/static/js/kanji-grid.js"></script>
|
|
@@ -261,7 +261,11 @@ def datetimeformat(value, format='%Y-%m-%d %H:%M:%S'):
|
|
|
261
261
|
@app.route('/stats')
|
|
262
262
|
def stats():
|
|
263
263
|
"""Renders the stats page."""
|
|
264
|
-
|
|
264
|
+
from GameSentenceMiner.util.configuration import get_master_config, get_stats_config
|
|
265
|
+
return render_template('stats.html',
|
|
266
|
+
config=get_config(),
|
|
267
|
+
master_config=get_master_config(),
|
|
268
|
+
stats_config=get_stats_config())
|
|
265
269
|
|
|
266
270
|
@app.route('/api/anki_stats')
|
|
267
271
|
def api_anki_stats():
|
|
@@ -60,8 +60,8 @@ GameSentenceMiner/web/database_api.py,sha256=XkFJDVm9X15Coz6rakNLKQj-218MYziGOeo
|
|
|
60
60
|
GameSentenceMiner/web/events.py,sha256=6Vyz5c9MdpMIa7Zqljqhap2XFQnAVYJ0CdQV64TSZsA,5119
|
|
61
61
|
GameSentenceMiner/web/gsm_websocket.py,sha256=IwwQo6VtgPqeOuc-datgfJyLpX3LwB2MISDqA6EkiSA,4131
|
|
62
62
|
GameSentenceMiner/web/service.py,sha256=YZchmScTn7AX_GkwV1ULEK6qjdOnJcpc3qfMwDf7cUE,5363
|
|
63
|
-
GameSentenceMiner/web/stats.py,sha256=
|
|
64
|
-
GameSentenceMiner/web/texthooking_page.py,sha256=
|
|
63
|
+
GameSentenceMiner/web/stats.py,sha256=h4tRA_00OdQ9uVYrQuAE5SmeSGHMLfTUHPOy6re_h5s,22366
|
|
64
|
+
GameSentenceMiner/web/texthooking_page.py,sha256=zQx9c8AQ4pWN8JgxvW0MKWkvtoIyrhXBlGdT-acBupU,13484
|
|
65
65
|
GameSentenceMiner/web/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
66
66
|
GameSentenceMiner/web/static/apple-touch-icon.png,sha256=OcMI8af_68DA_tweOsQ5LytTyMwm7-hPW07IfrOVgEs,46132
|
|
67
67
|
GameSentenceMiner/web/static/favicon-96x96.png,sha256=lOePzjiKl1JY2J1kT_PMdyEnrlJmi5GWbmXJunM12B4,16502
|
|
@@ -80,19 +80,19 @@ GameSentenceMiner/web/static/js/database.js,sha256=-SjMmhXzU8a3QNGrwGtJCu55ZXXfk
|
|
|
80
80
|
GameSentenceMiner/web/static/js/kanji-grid.js,sha256=rUa8_TGFm4Z8CtURoAlZjCN032PLe0YmHvN52S4_sE0,7181
|
|
81
81
|
GameSentenceMiner/web/static/js/search.js,sha256=QrjsVXf90c1LzD09TPaK93RbIN9yEiXF8IKAKrDY4hw,10482
|
|
82
82
|
GameSentenceMiner/web/static/js/shared.js,sha256=UL7wDJ5FsqeIspkCcC4s_jOv6a93I2gxFi57gQT8Bn0,21001
|
|
83
|
-
GameSentenceMiner/web/static/js/stats.js,sha256=
|
|
83
|
+
GameSentenceMiner/web/static/js/stats.js,sha256=uAh_J0JngC1QjB_Y9YxRKfWrxfPsdyy6Ypk5WFerZKU,86149
|
|
84
84
|
GameSentenceMiner/web/templates/anki_stats.html,sha256=FdIMl-kY0-3as9Wn0i-wKIIkl1xXn4nWMbWPypN_1T0,10955
|
|
85
85
|
GameSentenceMiner/web/templates/database.html,sha256=Gd54rgZmfcV7ufoJ69COeMncs5Q5u-rSJcsIvROVCEo,13732
|
|
86
86
|
GameSentenceMiner/web/templates/index.html,sha256=7ChQ1j602MOiYU95wXAKP_Ezsh_JgdlGz2uXIzS2j0g,227894
|
|
87
87
|
GameSentenceMiner/web/templates/search.html,sha256=nao3M_hAbm5ftzThi91NtQ3ZiINMPUNx4ngFmqLnLQ4,4060
|
|
88
|
-
GameSentenceMiner/web/templates/stats.html,sha256=
|
|
88
|
+
GameSentenceMiner/web/templates/stats.html,sha256=G3S2YBZ2sQ-D4wTQQBe8411cVTE9QPQq-wr4kyDPi_w,32508
|
|
89
89
|
GameSentenceMiner/web/templates/utility.html,sha256=KtqnZUMAYs5XsEdC9Tlsd40NKAVic0mu6sh-ReMDJpU,16940
|
|
90
90
|
GameSentenceMiner/web/templates/components/navigation.html,sha256=6y9PvM3nh8LY6JWrZb6zVOm0vqkBLDc6d3gB9X5lT_w,1055
|
|
91
91
|
GameSentenceMiner/web/templates/components/theme-styles.html,sha256=hiq3zdJljpRjQO1iUA7gfFKwXebltG-IWW-gnKS4GHA,3439
|
|
92
92
|
GameSentenceMiner/wip/__init___.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
93
|
-
gamesentenceminer-2.16.
|
|
94
|
-
gamesentenceminer-2.16.
|
|
95
|
-
gamesentenceminer-2.16.
|
|
96
|
-
gamesentenceminer-2.16.
|
|
97
|
-
gamesentenceminer-2.16.
|
|
98
|
-
gamesentenceminer-2.16.
|
|
93
|
+
gamesentenceminer-2.16.12.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
94
|
+
gamesentenceminer-2.16.12.dist-info/METADATA,sha256=SgT5FkcLIkIfeGJbk_hyrvw0fZCNY27li0gD42HsKrs,7349
|
|
95
|
+
gamesentenceminer-2.16.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
96
|
+
gamesentenceminer-2.16.12.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
|
97
|
+
gamesentenceminer-2.16.12.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
|
98
|
+
gamesentenceminer-2.16.12.dist-info/RECORD,,
|
|
File without changes
|
{gamesentenceminer-2.16.11.dist-info → gamesentenceminer-2.16.12.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{gamesentenceminer-2.16.11.dist-info → gamesentenceminer-2.16.12.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|