1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
#!/usr/bin/env python3
"""Weekly newOS4 course telemetry report (last 7 days). No PII, aggregate only."""
import json, glob, os
from collections import Counter
from datetime import datetime, timezone, timedelta
ROOT = '/var/www/webx11.duckdns.org/spaces/newos4'
DATA = os.path.join(ROOT, 'data', 'course')
COURSE = os.path.join(ROOT, 'course')
def load(p, d):
try:
with open(p, encoding='utf-8') as f: return json.load(f)
except Exception: return d
def main():
cutoff = (datetime.now(timezone.utc) - timedelta(days=7)).strftime('%Y-%m-%dT%H:%M:%S')
opens, completes, fails, questions, cmds = Counter(), Counter(), Counter(), Counter(), Counter()
sessions = set(); projects = Counter(); total = 0
for path in sorted(glob.glob(os.path.join(DATA, 'events-*.jsonl')))[-2:]:
for line in open(path, encoding='utf-8', errors='ignore'):
try: e = json.loads(line)
except Exception: continue
sid = e.get('sid') or ''
if sid.startswith('test') or (e.get('ts') or '') < cutoff: continue
total += 1; sessions.add(sid)
t, d = e.get('type'), e.get('detail') or {}
lesson = (d.get('lesson') or '')[:20]
if t == 'lesson_open' and lesson: opens[lesson] += 1
elif t == 'lesson_complete' and lesson: completes[lesson] += 1
elif t == 'exercise_fail':
if lesson: fails[lesson] += 1
c = (d.get('command') or '').strip().split(' ')[0][:30]
if c: cmds[c] += 1
elif t == 'question':
q = ' '.join((d.get('q') or '').lower().split())[:120]
if len(q) >= 8: questions[q] += 1
elif t in ('project_open', 'project_view') and d.get('project'):
projects[d['project'][:30]] += 1
feed = load(os.path.join(COURSE, 'feed.json'), {'items': []})
week = (datetime.now(timezone.utc) - timedelta(days=7)).strftime('%Y-%m-%d')
new_items = [i for i in feed.get('items', []) if (i.get('date') or '') >= week]
lessons = []
for lid in sorted(set(opens) | set(completes)):
o, c = opens[lid], completes[lid]
lessons.append((lid, o, c, round(c / o, 2) if o else 0.0))
weak = [x for x in lessons if x[1] >= 3 and x[3] < 0.3]
c_total = sum(completes.values())
print('๐ newOS4 Course โ weekly report (' + datetime.now(timezone.utc).strftime('%Y-%m-%d') + ')')
print('Sessions: ' + str(len(sessions)) + ' | events: ' + str(total) + ' | opens: ' + str(sum(opens.values())) + ' | completes: ' + str(c_total))
rate = round(c_total / sum(opens.values()), 2) if sum(opens.values()) else 0.0
print('Overall completion: ' + str(rate) + ' | exercise failures: ' + str(sum(fails.values())) + ' | questions: ' + str(sum(questions.values())))
if lessons:
print('Lessons: ' + ' ยท '.join(f'{l} {c}/{o}' for l, o, c, r in lessons[:10]))
print('Projects: ' + (', '.join(f'{k} {v}' for k, v in projects.items()) or 'none'))
print('Content flags: ' + (', '.join(f'{l} {r:.0%} of {o}' for l, o, c, r in weak) or 'none'))
print('Top questions: ' + (' | '.join(f'"{q}" x{n}' for q, n in questions.most_common(3)) or 'none'))
print('Failed commands: ' + (', '.join(f'{c} x{n}' for c, n in cmds.most_common(3)) or 'none'))
print('Daily feed: ' + str(len(feed.get('items', []))) + '/60 (' + str(len(new_items)) + ' new this week)')
if len(sessions) < 20:
print('Gate: paid Pro track still OFF โ need >=20 sessions and >=35% completion through 3.1 over 14 days.')
if __name__ == '__main__':
main()
|