Work on data, threading and printing of threads
[kspaans/nntp-to-dot] / userstats.ss
1 ;; Copyright (C) 2009  Kyle Spaans
2 ;;  this program is distributed under the GPL v2 or (at your option)
3 ;;  any later version.
4
5 ;; Calculate a bunch of user statistics:
6 ;; - Number of users
7 ;; - Post counts of users
8 ;; - Average number of posts
9 ;; - posts per day...
10 ;; - new users versus time...
11
12 #lang scheme
13
14 (require "common.ss")
15 (require srfi/19)
16
17 (provide count-users new-u-vs-time posts)
18
19 ;; User Statistics Struct
20 ;; String - usename in the form of an email address, possibility of duplicates
21 ;; int    - total number of posts in newsgroup
22 ;; date   - SRFI-19 date struct, date of first post to group
23 ;; date   - "     "    ", date of last post to struct
24 ;; int    - number of days in range from first post day to past post day
25 ;; real   - number of posts / number of days
26 (define-struct ustats (user nump firstp lastp days avgppd))
27
28 ;; Collect stats in the form of '(USERNAME ustats)
29 (define users (make-hash))
30
31 ;; Collect some other, extra stats, store in ngposts struct
32 (define pcounts (make-hash))
33 ;; NewsGroup Posts Struct
34 ;; string - username ("From:" header)
35 ;; int    - Total posts by user
36 ;; int    - "post" posts by user
37 ;; int    - "reply" posts by user
38 (define-struct ngposts (user tp np nr))
39
40 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
41
42 ;; print-stats: void -> void
43 ;; Pretty print the contents of the user statistics table to stdout
44 (define (print-stats)
45   (printf "Total number of users: ~a~n~n" (hash-count users))
46   (hash-for-each
47     users
48     (lambda (k v)
49       (printf "User: ~a~nCount: ~a~nFirst: ~a~nLast:  ~a~nAvgPPD: ~a~n~n"
50               (ustats-user v)
51               (ustats-nump v)
52               (ustats-firstp v)
53               (ustats-lastp v)
54               (ustats-avgppd v)))))
55
56 ;; count-users: int int newsgroup -> void
57 ;; build a hash table with each user from the group collecting stats:
58 ;; - number of posts
59 ;; - first/last post
60 ;; So if "last" post is empty, then user has only 1 post
61 (define (count-users first last newsd)
62   (cond
63     [(= first last) (print-stats)]
64     [else (let* [(message (message-getter newsd first (list from-regexp date-regexp)))
65                  (mesg-from (if (boolean? message) message (car message)))
66                  (mesg-date (if (boolean? message) message (cadr message)))]
67             (cond
68               [(boolean? message) (void)]
69               [else
70                (let [(result (hash-ref users mesg-from #f))]
71                  (cond
72                    [(boolean? result)
73                     ;(printf "First post ~a ::@:: ~a~n" mesg-from mesg-date)
74                     (hash-set! users mesg-from (make-ustats mesg-from 1 (get-date mesg-date) '() 1 1))]
75                    [else
76                                      ;; Calculate the ordinal day of the year (1-365) of the given date
77                                      ;; then subtract. Alternatively date->time and time-difference
78                                      ;; functions can be used to calculate this
79                     (let [(pdays (if (empty? (ustats-lastp result))
80                                      1
81                                      (+ 1 (- (date-year-day (ustats-lastp result))
82                                              (date-year-day (ustats-firstp result))))))
83                           (new-nump (+ 1 (ustats-nump result)))]
84                     (hash-set! users mesg-from (make-ustats
85                                                  mesg-from
86                                                  new-nump
87                                                  (ustats-firstp result)
88                                                  (get-date mesg-date)
89                                                  pdays
90                                                  (/ new-nump pdays))))]))]))
91           (count-users (+ first 1) last newsd)]))
92
93 ;; new-u-vs-time: void -> void
94 ;; Prints out info (similar to PPD) representing the number of new users showing up
95 ;; on the newsgroup (e.g. first posts) plotted versus time. This will be plottable with GNUPLOT.
96 ;; First map all first post instances to a new hash table that will count the number of first
97 ;; posts on each day. Then map over that hash table to print them out appropriately.
98 (define (new-u-vs-time)
99   (let [(new-u-hash (make-hash))]
100     (hash-for-each
101       users
102       (lambda (k v)
103         (let* [(ndate (date->string (ustats-firstp v) "~D"))
104                (result (hash-ref new-u-hash ndate #f))]
105           (cond
106             [(boolean? result) (hash-set! new-u-hash ndate 1)]
107             [else (hash-set! new-u-hash ndate (+ 1 result))]))))
108     (hash-for-each new-u-hash
109                    (lambda (k v) (printf "~a ~a~n" k v)))))
110
111 ;; posts: int int usergroup
112 ;; Counts the number of posts of each user, mapping email to post count.
113 (define (posts first last newsd)
114   (cond
115     [(= first last)
116      (hash-for-each
117        pcounts
118        (lambda (k v)
119          (printf "~a\t~a~n" k (ngposts-tp v))))]
120     [else
121      (let* [(message (message-getter newsd first (list from-regexp ref-regexp)))
122             (mesg-from (if (boolean? message) #f (car message)))
123             (mesg-refs (if (and (not (boolean? message)) (= 2 (length message)))
124                            #t;(get-refs (cadr message)) ; only need to know, right?
125                            #f))]
126        (cond
127          [(boolean? message) (posts (add1 first) last newsd)]
128          [else
129           (let [(result (hash-ref pcounts mesg-from #f))]
130             (cond
131               [(boolean? result)
132                (if mesg-refs
133                    (hash-set! pcounts mesg-from (make-ngposts mesg-from 1 0 1))
134                    (hash-set! pcounts mesg-from (make-ngposts mesg-from 1 1 0)))
135                (posts (add1 first) last newsd)]
136               [else
137                (if mesg-refs
138                    (hash-set! pcounts mesg-from (make-ngposts
139                                                   mesg-from
140                                                   (add1 (ngposts-tp result))
141                                                   (ngposts-np result)
142                                                   (add1 (ngposts-nr result))))
143                    (hash-set! pcounts mesg-from (make-ngposts
144                                                   mesg-from
145                                                   (add1 (ngposts-tp result))
146                                                   (add1 (ngposts-np result))
147                                                   (ngposts-nr result))))
148                (posts (add1 first) last newsd)]))]))]))