import sqlite3
import json
from datetime import datetime, timedelta
from collections import defaultdict, Counter
import sys
import time
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
class TikkerVisualizer:
def __init__(self, database_path='tikker.db'):
self.database_path = database_path
self.connection = sqlite3.connect(database_path)
self.cursor = self.connection.cursor()
def get_total_events(self):
start_time = time.time()
logging.info("Retrieving total events...")
self.cursor.execute("SELECT COUNT(*) FROM kevent")
result = self.cursor.fetchone()[0]
duration = time.time() - start_time
logging.info(f"Total events retrieved in {duration:.2f} seconds")
return result, duration
def get_pressed_events(self):
start_time = time.time()
logging.info("Retrieving pressed events...")
self.cursor.execute("SELECT COUNT(*) FROM kevent WHERE event='PRESSED'")
result = self.cursor.fetchone()[0]
duration = time.time() - start_time
logging.info(f"Pressed events retrieved in {duration:.2f} seconds")
return result, duration
def get_released_events(self):
start_time = time.time()
logging.info("Retrieving released events...")
self.cursor.execute("SELECT COUNT(*) FROM kevent WHERE event='RELEASED'")
result = self.cursor.fetchone()[0]
duration = time.time() - start_time
logging.info(f"Released events retrieved in {duration:.2f} seconds")
return result, duration
def get_date_range(self):
start_time = time.time()
logging.info("Retrieving date range...")
self.cursor.execute("SELECT MIN(timestamp), MAX(timestamp) FROM kevent")
result = self.cursor.fetchone()
duration = time.time() - start_time
logging.info(f"Date range retrieved in {duration:.2f} seconds")
return result[0], result[1], duration
def get_hourly_activity(self, limit=1000):
start_time = time.time()
logging.info("Retrieving hourly activity...")
self.cursor.execute("""
SELECT strftime('%H', timestamp) as hour, COUNT(*) as count
FROM kevent
WHERE event='PRESSED'
GROUP BY hour
ORDER BY hour
""")
result = self.cursor.fetchall()
duration = time.time() - start_time
logging.info(f"Hourly activity retrieved in {duration:.2f} seconds")
return result, duration
def get_daily_activity(self, limit=365):
start_time = time.time()
logging.info("Retrieving daily activity...")
self.cursor.execute(f"""
SELECT strftime('%Y-%m-%d', timestamp) as day, COUNT(*) as count
FROM kevent
WHERE event='PRESSED'
GROUP BY day
ORDER BY day DESC
LIMIT {limit}
""")
result = list(reversed(self.cursor.fetchall()))
duration = time.time() - start_time
logging.info(f"Daily activity retrieved in {duration:.2f} seconds")
return result, duration
def get_weekday_activity(self):
start_time = time.time()
logging.info("Retrieving weekday activity...")
self.cursor.execute("""
SELECT
CASE CAST(strftime('%w', timestamp) AS INTEGER)
WHEN 0 THEN 'Sunday'
WHEN 1 THEN 'Monday'
WHEN 2 THEN 'Tuesday'
WHEN 3 THEN 'Wednesday'
WHEN 4 THEN 'Thursday'
WHEN 5 THEN 'Friday'
WHEN 6 THEN 'Saturday'
END as weekday,
COUNT(*) as count
FROM kevent
WHERE event='PRESSED'
GROUP BY strftime('%w', timestamp)
ORDER BY strftime('%w', timestamp)
""")
result = self.cursor.fetchall()
duration = time.time() - start_time
logging.info(f"Weekday activity retrieved in {duration:.2f} seconds")
return result, duration
def get_top_keys(self, limit=30):
start_time = time.time()
logging.info("Retrieving top keys...")
self.cursor.execute(f"""
SELECT char, COUNT(*) as count
FROM kevent
WHERE event='PRESSED' AND char IS NOT NULL AND char != ''
GROUP BY char
ORDER BY count DESC
LIMIT {limit}
""")
result = self.cursor.fetchall()
duration = time.time() - start_time
logging.info(f"Top keys retrieved in {duration:.2f} seconds")
return result, duration
def get_keyboard_heatmap_data(self):
start_time = time.time()
logging.info("Retrieving keyboard heatmap data...")
self.cursor.execute("""
SELECT char, COUNT(*) as count
FROM kevent
WHERE event='PRESSED' AND char IS NOT NULL AND char != ''
GROUP BY char
ORDER BY count DESC
""")
data = {}
for char, count in self.cursor.fetchall():
data[char] = count
duration = time.time() - start_time
logging.info(f"Keyboard heatmap data retrieved in {duration:.2f} seconds")
return data, duration
def get_hour_day_heatmap(self, days_back=30):
start_time = time.time()
logging.info("Retrieving hour-day heatmap...")
self.cursor.execute(f"""
SELECT
strftime('%Y-%m-%d', timestamp) as day,
strftime('%H', timestamp) as hour,
COUNT(*) as count
FROM kevent
WHERE event='PRESSED'
AND timestamp >= datetime('now', '-{days_back} days')
GROUP BY day, hour
ORDER BY day, hour
""")
result = self.cursor.fetchall()
duration = time.time() - start_time
logging.info(f"Hour-day heatmap retrieved in {duration:.2f} seconds")
return result, duration
def get_typing_speed_data(self, sample_size=10000):
start_time = time.time()
logging.info("Retrieving typing speed data...")
self.cursor.execute(f"""
SELECT
strftime('%Y-%m-%d %H:00:00', timestamp) as hour_bucket,
COUNT(*) as keypresses
FROM kevent
WHERE event='PRESSED'
GROUP BY hour_bucket
ORDER BY hour_bucket DESC
LIMIT {sample_size}
""")
result = list(reversed(self.cursor.fetchall()))
duration = time.time() - start_time
logging.info(f"Typing speed data retrieved in {duration:.2f} seconds")
return result, duration
def get_character_frequency(self):
start_time = time.time()
logging.info("Retrieving character frequency...")
letters = Counter()
numbers = Counter()
special = Counter()
self.cursor.execute("""
SELECT char, COUNT(*) as count
FROM kevent
WHERE event='PRESSED' AND char IS NOT NULL AND char != ''
GROUP BY char
""")
for char, count in self.cursor.fetchall():
if len(char) == 1:
if char.isalpha():
letters[char.lower()] += count
elif char.isdigit():
numbers[char] += count
else:
special[char] += count
result = {
'letters': dict(letters.most_common(26)),
'numbers': dict(numbers.most_common(10)),
'special': dict(special.most_common(20))
}
duration = time.time() - start_time
logging.info(f"Character frequency retrieved in {duration:.2f} seconds")
return result, duration
def get_monthly_stats(self):
start_time = time.time()
logging.info("Retrieving monthly stats...")
self.cursor.execute("""
SELECT
strftime('%Y-%m', timestamp) as month,
COUNT(*) as count
FROM kevent
WHERE event='PRESSED'
GROUP BY month
ORDER BY month
""")
result = self.cursor.fetchall()
duration = time.time() - start_time
logging.info(f"Monthly stats retrieved in {duration:.2f} seconds")
return result, duration
def get_peak_activity_times(self):
start_time = time.time()
logging.info("Retrieving peak activity times...")
self.cursor.execute("""
SELECT
strftime('%Y-%m-%d %H:00:00', timestamp) as hour_block,
COUNT(*) as count
FROM kevent
WHERE event='PRESSED'
GROUP BY hour_block
ORDER BY count DESC
LIMIT 10
""")
result = self.cursor.fetchall()
duration = time.time() - start_time
logging.info(f"Peak activity times retrieved in {duration:.2f} seconds")
return result, duration
# NEW: Words Per Minute (WPM) and Performance Metrics
def get_wpm_stats(self):
start_time = time.time()
logging.info("Calculating WPM and performance metrics...")
# Get total key presses and time span
self.cursor.execute("SELECT COUNT(*) FROM kevent WHERE event='PRESSED'")
total_presses = self.cursor.fetchone()[0]
self.cursor.execute("SELECT MIN(timestamp), MAX(timestamp) FROM kevent WHERE event='PRESSED'")
min_time, max_time = self.cursor.fetchone()
if not min_time or not max_time or total_presses == 0:
return {
'wpm_average': 0,
'wpm_peak_hour': 0,
'wpm_peak_day': 0,
'words_typed': 0,
'characters_typed': 0,
'total_typing_time_minutes': 0,
'typing_efficiency': 0,
'peak_hour': None,
'peak_day': None
}, 0.0
min_dt = datetime.fromisoformat(min_time)
max_dt = datetime.fromisoformat(max_time)
total_minutes = (max_dt - min_dt).total_seconds() / 60.0
# Estimate words: 5 characters per word (standard WPM formula)
characters_typed = total_presses
words_typed = characters_typed / 5.0
wpm_average = words_typed / total_minutes if total_minutes > 0 else 0
# Peak hour WPM
self.cursor.execute("""
SELECT strftime('%Y-%m-%d %H:00:00', timestamp) as hour, COUNT(*) as presses
FROM kevent WHERE event='PRESSED'
GROUP BY hour
ORDER BY presses DESC
LIMIT 1
""")
peak_hour_row = self.cursor.fetchone()
peak_hour_wpm = (peak_hour_row[1] / 5.0) if peak_hour_row else 0
# Peak day WPM
self.cursor.execute("""
SELECT strftime('%Y-%m-%d', timestamp) as day, COUNT(*) as presses
FROM kevent WHERE event='PRESSED'
GROUP BY day
ORDER BY presses DESC
LIMIT 1
""")
peak_day_row = self.cursor.fetchone()
peak_day_wpm = (peak_day_row[1] / 5.0 / 60.0) if peak_day_row else 0 # per minute
# Typing efficiency: presses per minute during active time
# Approximate active time as sum of intervals with activity
self.cursor.execute("""
SELECT COUNT(DISTINCT strftime('%Y-%m-%d %H', timestamp)) as active_hours
FROM kevent WHERE event='PRESSED'
""")
active_hours = self.cursor.fetchone()[0]
typing_efficiency = total_presses / (active_hours * 60) if active_hours > 0 else 0
result = {
'wpm_average': round(wpm_average, 2),
'wpm_peak_hour': round(peak_hour_wpm, 2),
'wpm_peak_day': round(peak_day_wpm, 2),
'words_typed': int(words_typed),
'characters_typed': characters_typed,
'total_typing_time_minutes': round(total_minutes, 1),
'typing_efficiency': round(typing_efficiency, 2),
'peak_hour': peak_hour_row[0] if peak_hour_row else None,
'peak_day': peak_day_row[0] if peak_day_row else None
}
duration = time.time() - start_time
logging.info(f"WPM stats calculated in {duration:.2f} seconds")
return result, duration
def get_performance_insights(self):
start_time = time.time()
logging.info("Generating performance insights...")
# === 1. Key type distribution and corrections ===
self.cursor.execute("""
SELECT
SUM(CASE WHEN char IS NOT NULL AND length(char) = 1 AND char GLOB '[a-zA-Z]' THEN 1 ELSE 0 END) as letters,
SUM(CASE WHEN char IS NOT NULL AND length(char) = 1 AND char GLOB '[0-9]' THEN 1 ELSE 0 END) as numbers,
SUM(CASE WHEN char IS NOT NULL AND length(char) = 1 AND char NOT GLOB '[a-zA-Z0-9]' THEN 1 ELSE 0 END) as special,
SUM(CASE WHEN char = ' ' THEN 1 ELSE 0 END) as spaces,
SUM(CASE WHEN char = 'Backspace' THEN 1 ELSE 0 END) as backspaces,
SUM(CASE WHEN char = 'Delete' THEN 1 ELSE 0 END) as deletes
FROM kevent WHERE event='PRESSED'
""")
row = self.cursor.fetchone()
letters = row[0] or 0
numbers = row[1] or 0
special = row[2] or 0
spaces = row[3] or 0
backspaces = row[4] or 0
deletes = row[5] or 0
total_edits = backspaces + deletes
valid_keystrokes = letters + numbers + special + spaces
error_rate = (total_edits / valid_keystrokes * 100) if valid_keystrokes > 0 else 0
# === 2. Daily activity consistency (manual STDDEV) ===
self.cursor.execute("""
SELECT COUNT(*) as count
FROM kevent WHERE event='PRESSED'
GROUP BY strftime('%Y-%m-%d', timestamp)
""")
daily_counts = [row[0] for row in self.cursor.fetchall()]
if daily_counts:
avg_daily = sum(daily_counts) / len(daily_counts)
variance = sum((x - avg_daily) ** 2 for x in daily_counts) / len(daily_counts)
std_daily = variance ** 0.5
consistency_score = 100 - (std_daily / avg_daily * 100) if avg_daily > 0 else 0
consistency_score = max(0, min(100, consistency_score))
else:
avg_daily = std_daily = 0
consistency_score = 0
result = {
'error_rate_percent': round(error_rate, 2),
'backspace_count': backspaces,
'delete_count': deletes,
'total_corrections': total_edits,
'consistency_score': round(consistency_score, 1),
'letters_typed': letters,
'numbers_typed': numbers,
'special_typed': special,
'spaces_typed': spaces
}
duration = time.time() - start_time
logging.info(f"Performance insights generated in {duration:.2f} seconds")
return result, duration
def close(self):
self.connection.close()
def generate_html(self, output_file='tikker_report.html'):
logging.info("Starting HTML generation...")
overall_start = time.time()
total_events, total_events_duration = self.get_total_events()
pressed_events, pressed_events_duration = self.get_pressed_events()
released_events, released_events_duration = self.get_released_events()
date_range_min, date_range_max, date_range_duration = self.get_date_range()
date_range = (date_range_min, date_range_max)
hourly_data, hourly_duration = self.get_hourly_activity()
daily_data, daily_duration = self.get_daily_activity()
weekday_data, weekday_duration = self.get_weekday_activity()
top_keys, top_keys_duration = self.get_top_keys()
keyboard_heatmap, keyboard_heatmap_duration = self.get_keyboard_heatmap_data()
hour_day_heatmap, hour_day_heatmap_duration = self.get_hour_day_heatmap()
typing_speed, typing_speed_duration = self.get_typing_speed_data()
char_frequency, char_frequency_duration = self.get_character_frequency()
monthly_stats, monthly_stats_duration = self.get_monthly_stats()
peak_times, peak_times_duration = self.get_peak_activity_times()
wpm_stats, wpm_duration = self.get_wpm_stats()
perf_insights, perf_duration = self.get_performance_insights()
durations = {
'total_events': total_events_duration,
'pressed_events': pressed_events_duration,
'released_events': released_events_duration,
'date_range': date_range_duration,
'hourly_activity': hourly_duration,
'daily_activity': daily_duration,
'weekday_activity': weekday_duration,
'top_keys': top_keys_duration,
'keyboard_heatmap': keyboard_heatmap_duration,
'hour_day_heatmap': hour_day_heatmap_duration,
'typing_speed': typing_speed_duration,
'character_frequency': char_frequency_duration,
'monthly_stats': monthly_stats_duration,
'peak_activity_times': peak_times_duration,
'wpm_calculation': wpm_duration,
'performance_insights': perf_duration
}
logging.info("Generating HTML content...")
html = f"""
Tikker Keyboard Performance Report
Total Events
{total_events:,}
Recorded Actions
Key Presses
{pressed_events:,}
Keys Pressed
Average WPM
{wpm_stats['wpm_average']}
Words Per Minute
Peak Hour WPM
{wpm_stats['wpm_peak_hour']}
Max Speed
Tracking Period
{(datetime.fromisoformat(date_range[1]) - datetime.fromisoformat(date_range[0])).days if date_range[0] and date_range[1] else 0}
Days of Data
Words Typed
{wpm_stats['words_typed']:,}
Total Output
Data Retrieval Performance
{''.join(f'- {key.replace("_", " ").title()}: {value:.3f}s
' for key, value in durations.items())}
Typing Speed & Performance
Detailed breakdown of words per minute, efficiency, and accuracy metrics.
Hourly Activity Distribution
Average key press volume by hour of day (00:00–23:00) across the complete dataset.
Daily Activity Timeline (Last 365 Days)
Daily key press totals over the past year, illustrating long-term usage patterns.
Activity by Day of Week
Distribution of typing activity across weekdays, highlighting weekly patterns.
Monthly Activity Trends
Monthly key press totals showing seasonal and long-term productivity trends.
Top 30 Most Frequently Used Keys
Ranking of the most pressed keys by total count, excluding system keys.
Keyboard Usage Heatmap
Visual intensity map of key usage. Color intensity corresponds to press frequency.
Low Activity
High Activity
Hour × Day Activity Matrix (Last 30 Days)
Detailed view of typing patterns by hour and day. Each cell represents one hour of activity.
00:00
06:00
12:00
18:00
23:00
Keypresses Per Hour Timeline
Hourly key press volume over time, highlighting peak productivity periods.
Letter Frequency Analysis
Relative frequency of letter usage (A-Z), case-insensitive.
Number Key Usage
Frequency distribution of numeric key usage (0-9).
Peak Activity Periods
Top 10 one-hour periods with highest key press volume.
| Rank |
Time Period |
Key Presses |
{''.join(f'| {i+1} | {time} | {count:,} |
' for i, (time, count) in enumerate(peak_times))}
"""
with open(output_file, 'w', encoding='utf-8') as f:
f.write(html)
overall_duration = time.time() - overall_start
logging.info(f"Enterprise HTML report generated in {overall_duration:.2f} seconds: {output_file}")
logging.info(f"Total events analyzed: {total_events:,}")
print(f"Enterprise performance report generated: {output_file}")
if __name__ == '__main__':
db_path = sys.argv[1] if len(sys.argv) > 1 else 'tikker.db'
output_path = sys.argv[2] if len(sys.argv) > 2 else 'tikker_report.html'
visualizer = TikkerVisualizer(db_path)
visualizer.generate_html(output_path)
visualizer.close()