def nextText(self):

        type = Settings.get('select_method')

        if type != 1:
            # Not in order
            v = DB.execute("select id,source,text from text where disabled is null order by random() limit %d" % Settings.get('num_rand')).fetchall()
            if len(v) == 0:
                v = None
            elif type == 2:
                v = min(v, key=self.diff_eval)
            elif type == 3:
                v = max(v, key=self.diff_eval)
            else:
                v = v[0] # random, just pick the first
        else:
            # Fetch in order
            lastid = (0,)
            g = DB.fetchone("""select r.text_id
                from result as r left join source as s on (r.source = s.rowid)
                where (s.discount is null) or (s.discount = 1) order by r.w desc limit 1""", None)
            if g is not None:
                lastid = DB.fetchone("select rowid from text where id = ?", lastid, g)
            v = DB.fetchone("select id,source,text from text where rowid > ? and disabled is null order by rowid asc limit 1", None, lastid)

        if v is None:
            v = self.defaultText

        self.emit(SIGNAL("setText"), v)
Exemple #2
0
    def nextText(self):

        type = Settings.get('select_method')

        if type != 1:
            # Not in order
            v = DB.execute("select id,source,text from text where disabled is null order by random() limit %d" % Settings.get('num_rand')).fetchall()
            if len(v) == 0:
                v = None
            elif type == 2:
                v = min(v, key=self.diff_eval)
            elif type == 3:
                v = max(v, key=self.diff_eval)
            else:
                v = v[0] # random, just pick the first
        else:
            # Fetch in order
            lastid = (0,)
            g = DB.fetchone("""select r.text_id
                from result as r left join source as s on (r.source = s.rowid)
                where (s.discount is null) or (s.discount = 1) order by r.w desc limit 1""", None)
            if g is not None:
                lastid = DB.fetchone("select rowid from text where id = ?", lastid, g)
            v = DB.fetchone("select id,source,text from text where rowid > ? and disabled is null order by rowid asc limit 1", None, lastid)

        if v is None:
            v = self.defaultText

        self.emit(SIGNAL("setText"), v)
Exemple #3
0
    def next_text(self):
        kind = Settings.get("select_method")
        if kind != 1:
            # Not in order
            targets = DB.execute(
                f"""select id,source,text from text where disabled is null
                order by random() limit {Settings.get("num_rand")}"""
            ).fetchall()
            if not targets:
                target = None
            elif kind == 2:
                target = min(targets, key=self.diff_eval)
            elif kind == 3:
                target = max(targets, key=self.diff_eval)
            else:
                target = targets[0]  # random, just pick the first
        else:
            # Fetch in order
            prev = (0, )
            result = DB.fetchone(
                """select r.text_id
                from result as r left join source as s on (r.source = s.rowid)
                where (s.discount is null) or (s.discount = 1) order by r.w desc limit 1""",
                None)
            if result is not None:
                prev = DB.fetchone("select rowid from text where id = ?", prev,
                                   result)
            target = DB.fetchone(
                """select id,source,text from text
                where rowid > ? and disabled is null order by rowid asc limit 1""",
                None, prev)
        if target is None:
            target = self.default_text

        self.emit("set-text", *target)
Exemple #4
0
    def update(self):
        self.progress_.show()
        n_text = DB.fetchone("""select count(*) from text""", (0,))[0]
        self.progress_.inc(2)
        n_res = DB.fetchone("""select count(*) from result""", (0,))[0]
        self.progress_.inc(2)
        n_words = DB.fetchall(
            """select count(*),sum(count) from statistic
            group by type order by type"""
        )
        self.progress_.inc(2)
        if len(n_words) != 3:
            n_words = [(0, 0), (0, 0), (0, 0)]
        n_first = DB.fetchone("""select w from result order by w asc limit 1""", (time.time(),))[0]
        self.progress_.hide()

        self.stats_.setText(
            locale.format_string(
                """Texts: %d
Results: %d
Analysis data: %d (%d keys, %d trigrams, %d words)
  %d characters and %d words typed total\n"""
                + ("First result was %.2f days ago.\n" % ((time.time() - n_first) / 86400.0)),
                tuple(
                    [n_text, n_res, sum(map(lambda x: x[0], n_words))]
                    + map(lambda x: x[0], n_words)
                    + [n_words[0][1], n_words[2][1]]
                ),
                True,
            )
        )
Exemple #5
0
    def update(self):
        self.progress_.show()
        n_text = DB.fetchone('''select count(*) from text''', (0, ))[0]
        self.progress_.inc(2)
        n_res = DB.fetchone('''select count(*) from result''', (0, ))[0]
        self.progress_.inc(2)
        n_words = DB.fetchall('''select count(*), sum(count) from statistic
            group by type order by type''')
        self.progress_.inc(2)
        if len(n_words) != 3:
            n_words = [(0, 0), (0, 0), (0, 0)]
        n_first = DB.fetchone(
            '''select w from result order by w asc limit 1''',
            (time.time(), ))[0]
        self.progress_.hide()

        self.stats_.setText(
            locale.format_string(
                '''Texts: %d
Results: %d
Analysis data: %d (%d keys, %d trigrams, %d words)
  %d characters and %d words typed total\n''' +
                ("First result was %.2f days ago.\n" %
                 ((time.time() - n_first) / 86400.0)),
                tuple([n_text, n_res,
                       sum(map(lambda x: x[0], n_words))] +
                      list(map(lambda x: x[0], n_words)) +
                      [n_words[0][1], n_words[2][1]]), True))
Exemple #6
0
    def lastText(self):
        # Fetch in order
        lastResultGuid = DB.fetchone("""select r.text_id
            from result as r left join source as s on (r.source = s.rowid)
            where (s.discount is null) or (s.discount = 1) order by r.w desc limit 1""", None)
        if lastResultGuid is not None:
            v = DB.fetchone("select id, source, text from text where id = ?", None, lastResultGuid)
        else:
            v = self.defaultText

        if v is None:
            v = self.defaultText

        self.emit(SIGNAL("setText"), v)
 def newReview(self, review):
     q = self.addTexts("<Reviews>", [review], lesson=2, update=False)
     if q:
         v = DB.fetchone("select id,source,text from text where id = ?", self.defaultText, q)
         self.emit(SIGNAL("setText"), v)
     else:
         self.nextText()
Exemple #8
0
 def newReview(self, review):
     q = self.addTexts("<Reviews>", [review], lesson=2, update=False)
     if q:
         v = DB.fetchone("select id,source,text from text where id = ?", self.defaultText, q)
         self.emit(SIGNAL("setText"), v)
     else:
         self.nextText()
Exemple #9
0
    def setTarget(self, text, guid):
        self.editflag = True
        self.target = text
        self.when = [0] * (len(self.target) + 1)

        # time for each character typed
        self.times = [0] * len(self.target)

        # whether each character was a mistake
        self.mistake = [False] * len(self.target)

        # mistake characters ( must be what was actually typed )
        self.mistakes = {}  #collections.defaultdict(lambda: [])
        self.where = 0
        self.clear()
        self.setPalette(self.palettes['inactive'])
        self.setText(self.getWaitText())
        self.selectAll()
        self.editflag = False
        self.is_lesson = DB.fetchone(
            "select discount from source where rowid=?", (None, ), (guid, ))[0]
        if self.is_lesson:
            self.mins = (Settings.get("min_lesson_wpm"),
                         Settings.get("min_lesson_acc"))
        else:
            self.mins = (Settings.get("min_wpm"), Settings.get("min_acc"))
Exemple #10
0
 def getStats(self):
     if self.when[0] == -1:
         t = self.times[1:]
         t.sort(reverse=True)
         v = DB.fetchone('select time from statistic where type = 0 and data = ? order by rowid desc limit 1', (t[len(t)//5], ), (self.target[0], ))
         self.times[0] = v[0]
         self.when[0] = self.when[1] - self.times[0]
     return self.when[self.where]-self.when[0], self.where, self.times, self.mistake, self.getMistakes()
Exemple #11
0
 def getStats(self):
     if self.when[0] == -1:
         t = self.times[1:]
         t.sort(reverse=True)
         v = DB.fetchone('select time from statistic where type = 0 and data = ? order by rowid desc limit 1', (t[len(t)//5], ), (self.target[0], ))
         self.times[0] = v[0]
         self.when[0] = self.when[1] - self.times[0]
     return self.when[self.where]-self.when[0], self.where, self.times, self.mistake, self.getMistakes()
Exemple #12
0
 def double_clicked(self, treeview, where, _column):
     row = Gtk.TreeModelRow(treeview.get_model(), where)
     target = DB.fetchone("select id,source,text from text where id = ?",
                          None, (row[0], ))
     if target is None:
         return
     self.emit("set-text", *target)
     self.emit("go-to-text")
Exemple #13
0
 def new_review(self, review):
     added = self.add_texts("<Reviews>", [review], lesson=2, update=False)
     if added:
         tgt = DB.fetchone("select id,source,text from text where id = ?",
                           self.default_text, added)
         self.emit("set-text", *tgt)
     else:
         self.next_text()
Exemple #14
0
 def updateResultLabel(self):
     spc = self.typer.getSpeed()
     accuracy = self.typer.getAccuracy()
     v2 = DB.fetchone("""select agg_median(wpm), agg_median(acc) from
         (select wpm, 100.0*accuracy as acc from result order by w desc limit %d)""" % Settings.get('def_group_by'), (0.0, 100.0))
     if Settings.get('show_since_fail_counter'):
         self.result.setText("Last: %.1fwpm (%.1f%%), last 10 average: %.1fwpm (%.1f%%) \n\nPerfect Count: (Current :%1d) (Max : %1d) (Last : %1d)"  % ((spc, 100.0*accuracy) + v2 + ( self.typer.count, self.typer.max_count, self.typer.last_count)))
     else:
         self.result.setText("Last: %.1fwpm (%.1f%%), last 10 average: %.1fwpm (%.1f%%)"  % ((spc, 100.0*accuracy) + v2 ))
Exemple #15
0
 def getStats(self):
     if self.when[0] == -1:
         # my refactoring mean this may never get hit, I'm not sure what when and times are for, so i'm not sure if I'm breaking some edge case here??
         t = self.times[1:]
         t.sort(reverse=True)
         v = DB.fetchone('select time from statistic where type = 0 and data = ? order by rowid desc limit 1', (t[len(t)//5], ), (self.target[0], ))
         self.times[0] = v[0]
         self.when[0] = self.when[1] - self.times[0]
     return self.getElapsed(), self.where, self.times, self.mistake, self.getMistakes()
Exemple #16
0
    def doubleClicked(self, idx):
        r = self.model.rows[idx.row()]

        v = DB.fetchone('select id,source,text from text where id = ?', None, (r[0], ))
        if v == None:
            return # silently ignore

        self.emit(SIGNAL("setText"), v)
        self.emit(SIGNAL("gotoText"))
Exemple #17
0
 def getStats(self):
     if self.when[0] == -1:
         # my refactoring mean this may never get hit, I'm not sure what when and times are for, so i'm not sure if I'm breaking some edge case here??
         t = self.times[1:]
         t.sort(reverse=True)
         v = DB.fetchone('select time from statistic where type = 0 and data = ? order by rowid desc limit 1', (t[len(t)//5], ), (self.target[0], ))
         self.times[0] = v[0]
         self.when[0] = self.when[1] - self.times[0]
     return self.getElapsed(), self.where, self.times, self.mistake, self.getMistakes()
Exemple #18
0
 def updateResultLabel(self):
     spc = self.typer.getSpeed()
     accuracy = self.typer.getAccuracy()
     v2 = DB.fetchone("""select agg_median(wpm), agg_median(acc) from
         (select wpm, 100.0*accuracy as acc from result order by w desc limit %d)""" % Settings.get('def_group_by'), (0.0, 100.0))
     if Settings.get('show_since_fail_counter'):
         self.result.setText("Last: %.1fwpm (%.1f%%), last 10 average: %.1fwpm (%.1f%%) \n\nPerfect Count: (Current :%1d) (Max : %1d) (Last : %1d)"  % ((spc, 100.0*accuracy) + v2 + ( self.typer.count, self.typer.max_count, self.typer.last_count)))
     else:
         self.result.setText("Last: %.1fwpm (%.1f%%), last 10 average: %.1fwpm (%.1f%%)"  % ((spc, 100.0*accuracy) + v2 ))
Exemple #19
0
 def get_stats(self):
     if self.when[0] == -1:
         times = sorted(self.times[1:], reverse=True)
         self.times[0] = DB.fetchone(
             "select time from statistic where type=0 and data=? order by rowid desc limit 1",
             (times[len(times) // 5], ), (self.target[0], ))[0]
         self.when[0] = self.when[1] - self.times[0]
     return (self.when[self.where] - self.when[0], self.where, self.times,
             self.mistake, self.get_mistakes())
Exemple #20
0
    def update(self):
        texts = DB.fetchone("select count(*) from text", (0,))[0]
        self.progressbar.set_fraction(1/4)
        results = DB.fetchone("select count(*) from result", (0,))[0]
        self.progressbar.set_fraction(2/4)
        keys, trigrams, words = DB.fetchall(
            "select count(*),sum(count) from statistic group by type order by type")
        self.progressbar.set_fraction(3/4)
        first = DB.fetchone("select w from result order by w asc limit 1", (time.time(), ))[0]
        self.progressbar.set_fraction(4/4)

        total = keys[0] + trigrams[0] + words[0]
        history = (time.time() - first) / 86400

        self.stats.set_text(f"""
Texts: {texts}
Results: {results}
Analysis data: {total} ({keys[0]} keys, {trigrams[0]} trigrams, {words[0]} words)
{keys[1]} characters and {words[1]} words typed in total.
First result was {round(history, 2)} days ago.
""")

        self.progressbar.set_fraction(0)
    def getStats(self):
        #TODO: redo when, times to avoid guessing time taken to hit zeroth char from old stat
        if self.when[0] == -1:
            t = self.times[1:]
            t.sort(reverse=True)
            v = DB.fetchone('select time from statistic where type = 0 and data = ? order by rowid desc limit 1', (t[len(t)//5], ), (self.target[0], ))
            self.when[0] = self.when[1] - v[0]

        if not self.when[-1]:
            self.when[-1] = timer()

        self.when = list(linearly_interpolate(self.when))
        
        for i in range(len(self.times)):
            #prevent division by zero when 0 time 
            time = self.when[i+1] - self.when[i]
            self.times[i] = MINIMUM_CHAR_TYPING_TIME if time == 0 else time   

        return self.when[-1]-self.when[0], len(self.target), self.times, self.mistake, self.getMistakes()
Exemple #22
0
    def getStats(self):
        #TODO: redo when, times to avoid guessing time taken to hit zeroth char from old stat
        if self.when[0] == -1:
            t = self.times[1:]
            t.sort(reverse=True)
            v = DB.fetchone(
                'select time from statistic where type = 0 and data = ? order by rowid desc limit 1',
                (t[len(t) // 5], ), (self.target[0], ))
            self.when[0] = self.when[1] - v[0]

        if not self.when[-1]:
            self.when[-1] = timer()

        self.when = list(linearly_interpolate(self.when))

        for i in range(len(self.times)):
            #prevent division by zero when 0 time
            time = self.when[i + 1] - self.when[i]
            self.times[i] = MINIMUM_CHAR_TYPING_TIME if time == 0 else time

        return self.when[-1] - self.when[0], len(
            self.target), self.times, self.mistake, self.getMistakes()
Exemple #23
0
    def setTarget(self, text, guid):
        self.editflag = True
        self.target = text
        self.when = [0] * (len(self.target)+1)

        # time for each character typed
        self.times = [0] * len(self.target)

        # whether each character was a mistake
        self.mistake = [False] * len(self.target)

        # mistake characters ( must be what was actually typed )
        self.mistakes = {} #collections.defaultdict(lambda: [])
        self.where = 0
        self.clear()
        self.setPalette(self.palettes['inactive'])
        self.setText(self.getWaitText())
        self.selectAll()
        self.editflag = False
        self.is_lesson = DB.fetchone("select discount from source where rowid=?", (None, ), (guid, ))[0]
        if self.is_lesson:
            self.mins = (Settings.get("min_lesson_wpm"), Settings.get("min_lesson_acc"))
        else:
            self.mins = (Settings.get("min_wpm"), Settings.get("min_acc"))
Exemple #24
0
    def done(self):
        print("DONE")
        # TODO split into smaller bits
        now = timer()
        elapsed, chars, times, mis, mistakes = self.typer.get_stats()
        text = self.text[2]

        assert chars == len(text)

        accuracy = 1.0 - sum(1 for f in mis if f) / chars
        spc = elapsed / chars
        viscosity = sum((t / spc - 1)**2 for t in times) / chars

        DB.execute(
            """insert into result (w, text_id, source, wpm, accuracy, viscosity)
                   values (?,?,?,?,?,?)""",
            (now, self.text[0], self.text[1], 12.0 / spc, accuracy, viscosity))

        wpm_median, acc_median = DB.fetchone(
            f"""select agg_median(wpm),agg_median(acc) from
            (select wpm,100.0*accuracy as acc from result order by w desc limit
             {Settings.get("def_group_by")})""", (0.0, 100.0))

        self.result.set_text(
            "Last: {:.1f}wpm ({:.1f}%), last 10 average: {:.1f}wpm ({:.1f}%)".
            format(12.0 / spc, 100.0 * accuracy, wpm_median, acc_median))

        self.emit("stats-changed")

        stats = collections.defaultdict(Statistic)
        viscs = collections.defaultdict(Statistic)

        for char, time, mistake in zip(text, times, mis):
            stats[char].append(time, mistake)
            viscs[char].append((time / spc - 1)**2)

        def gen_tup(start, end):
            span = end - start
            char_avg = sum(times[start:end]) / span
            visc = sum((t / char_avg - 1)**2 for t in times[start:end]) / span
            return (text[start:end], char_avg,
                    sum(1 for f in mis[start:end] if f), visc)

        for trigraph, time, mist, visc in [
                gen_tup(i, i + 3) for i in range(0, chars - 2)
        ]:
            stats[trigraph].append(time, mist > 0)
            viscs[trigraph].append(visc)

        regex = re.compile(r"(\w|'(?![A-Z]))+(-\w(\w|')*)*")
        for word, time, mist, visc in [
                gen_tup(*m.span()) for m in regex.finditer(text)
                if m.end() - m.start() > 3
        ]:
            stats[word].append(time, mist > 0)
            viscs[word].append(visc)

        def kind(key):
            if len(key) == 1:
                return 0
            if len(key) == 3:
                return 1
            return 2

        vals = []
        for key, stat in stats.items():
            visc = viscs[key].median()
            vals.append((stat.median(), visc * 100.0, now, len(stat),
                         stat.flawed(), kind(key), key))

        is_lesson = DB.fetchone("select discount from source where rowid=?",
                                (None, ), (self.text[1], ))[0]

        if Settings.get("use_lesson_stats") or not is_lesson:
            DB.executemany(
                """insert into statistic (time,viscosity,w,count,mistakes,type,data)
                    values (?,?,?,?,?,?,?)""", vals)
            DB.executemany(
                "insert into mistake (w,target,mistake,count) values (?,?,?,?)",
                [(now, k[0], k[1], v) for k, v in mistakes.items()])

        if is_lesson:
            mins = (Settings.get("min_lesson_wpm"),
                    Settings.get("min_lesson_acc"))
        else:
            mins = (Settings.get("min_wpm"), Settings.get("min_acc"))

        if 12.0 / spc < mins[0] or accuracy < mins[1] / 100.0:
            self.set_target(self.text)
        elif not is_lesson and Settings.get('auto_review'):
            words = [x for x in vals if x[5] == 2]
            if not words:
                self.emit("want-text")
                return
            words.sort(key=lambda x: (x[4], x[0]), reverse=True)
            i = 0
            while words[i][4] != 0:
                i += 1
            i += (len(words) - i) // 4

            # TODO support want-review
            # self.emit("want-review", [x[6] for x in words[0:i]])
        else:
            self.emit("want-text")
    def done(self):
        now = time.time()
        elapsed, chars, times, mis, mistakes = self.typer.getStats()

        assert chars == len(self.text[2])

        accuracy = 1.0 - len(filter(None, mis)) / chars
        spc = elapsed / chars
        
        viscosity = sum(map(lambda x: ((x-spc)/spc)**2, times)) / chars

        DB.execute('insert into result (w,text_id,source,wpm,accuracy,viscosity) values (?,?,?,?,?,?)',
                   (now, self.text[0], self.text[1], 12.0/spc, accuracy, viscosity))

        v2 = DB.fetchone("""select agg_median(wpm),agg_median(acc) from
            (select wpm,100.0*accuracy as acc from result order by w desc limit %d)""" % Settings.get('def_group_by'), (0.0, 100.0))
        self.result.setText("Last: %.1fwpm (%.1f%%), last 10 average: %.1fwpm (%.1f%%)"
            % ((12.0/spc, 100.0*accuracy) + v2))

        self.emit(SIGNAL("statsChanged"))

        stats = collections.defaultdict(Statistic)
        visc = collections.defaultdict(Statistic)
        text = self.text[2]

        for c, t, m in zip(text, times, mis):
            stats[c].append(t, m)
            visc[c].append(((t-spc)/spc)**2)

        def gen_tup(s, e):
            perch = sum(times[s:e])/(e-s)
            visc = sum(map(lambda x: ((x-perch)/perch)**2, times[s:e]))/(e-s)
            return (text[s:e], perch, len(filter(None, mis[s:e])), visc)

        for tri, t, m, v in [gen_tup(i, i+3) for i in xrange(0, chars-2)]:
            stats[tri].append(t, m > 0)
            visc[tri].append(v)

        regex = re.compile(r"(\w|'(?![A-Z]))+(-\w(\w|')*)*")

        for w, t, m, v in [gen_tup(*x.span()) for x in regex.finditer(text) if x.end()-x.start() > 3]:
            stats[w].append(t, m > 0)
            visc[w].append(v)

        def type(k):
            if len(k) == 1:
                return 0
            elif len(k) == 3:
                return 1
            return 2

        vals = []
        for k, s in stats.iteritems():
            v = visc[k].median()
            vals.append( (s.median(), v*100.0, now, len(s), s.flawed(), type(k), k) )

        is_lesson = DB.fetchone("select discount from source where rowid=?", (None,), (self.text[1], ))[0]

        if Settings.get('use_lesson_stats') or not is_lesson:
            DB.executemany_('''insert into statistic
                (time,viscosity,w,count,mistakes,type,data) values (?,?,?,?,?,?,?)''', vals)
            DB.executemany_('insert into mistake (w,target,mistake,count) values (?,?,?,?)',
                    [(now, k[0], k[1], v) for k, v in mistakes.iteritems()])

        if is_lesson:
            mins = (Settings.get("min_lesson_wpm"), Settings.get("min_lesson_acc"))
        else:
            mins = (Settings.get("min_wpm"), Settings.get("min_acc"))

        if 12.0/spc < mins[0] or accuracy < mins[1]/100.0:
            self.setText(self.text)
        elif not is_lesson and Settings.get('auto_review'):
            ws = filter(lambda x: x[5] == 2, vals)
            if len(ws) == 0:
                self.emit(SIGNAL("wantText"))
                return
            ws.sort(key=lambda x: (x[4],x[0]), reverse=True)
            i = 0
            while ws[i][4] != 0:
                i += 1
            i += (len(ws) - i) // 4

            self.emit(SIGNAL("wantReview"), map(lambda x:x[6], ws[0:i]))
        else:
            self.emit(SIGNAL("wantText"))
Exemple #26
0
 def isLesson(self):
     is_lesson = DB.fetchone("select discount from source where rowid=?",
                             (None, ), (self.text[1], ))[0]
     return is_lesson
Exemple #27
0
 def isLesson(self):
     is_lesson = DB.fetchone("select discount from source where rowid=?", (None, ), (self.text[1], ))[0]
     return is_lesson
Exemple #28
0
    def done(self):
        now = time.time()
        elapsed, chars, times, mis, mistakes = self.typer.getStats()

        assert chars == len(self.text[2])

        accuracy = 1.0 - len(filter(None, mis)) / chars
        spc = elapsed / chars

        viscosity = sum(map(lambda x: ((x - spc) / spc)**2, times)) / chars

        DB.execute(
            'insert into result (w,text_id,source,wpm,accuracy,viscosity) values (?,?,?,?,?,?)',
            (now, self.text[0], self.text[1], 12.0 / spc, accuracy, viscosity))

        v2 = DB.fetchone(
            """select agg_median(wpm),agg_median(acc) from
            (select wpm,100.0*accuracy as acc from result order by w desc limit %d)"""
            % Settings.get('def_group_by'), (0.0, 100.0))
        self.result.setText(
            "Last: %.1fwpm (%.1f%%), last 10 average: %.1fwpm (%.1f%%)" %
            ((12.0 / spc, 100.0 * accuracy) + v2))

        self.emit(SIGNAL("statsChanged"))

        stats = collections.defaultdict(Statistic)
        visc = collections.defaultdict(Statistic)
        text = self.text[2]

        for c, t, m in zip(text, times, mis):
            stats[c].append(t, m)
            visc[c].append(((t - spc) / spc)**2)

        def gen_tup(s, e):
            perch = sum(times[s:e]) / (e - s)
            visc = sum(map(lambda x:
                           ((x - perch) / perch)**2, times[s:e])) / (e - s)
            return (text[s:e], perch, len(filter(None, mis[s:e])), visc)

        for tri, t, m, v in [gen_tup(i, i + 3) for i in xrange(0, chars - 2)]:
            stats[tri].append(t, m > 0)
            visc[tri].append(v)

        regex = re.compile(r"(\w|'(?![A-Z]))+(-\w(\w|')*)*")

        for w, t, m, v in [
                gen_tup(*x.span()) for x in regex.finditer(text)
                if x.end() - x.start() > 3
        ]:
            stats[w].append(t, m > 0)
            visc[w].append(v)

        def type(k):
            if len(k) == 1:
                return 0
            elif len(k) == 3:
                return 1
            return 2

        vals = []
        for k, s in stats.iteritems():
            v = visc[k].median()
            vals.append(
                (s.median(), v * 100.0, now, len(s), s.flawed(), type(k), k))

        is_lesson = DB.fetchone("select discount from source where rowid=?",
                                (None, ), (self.text[1], ))[0]

        if Settings.get('use_lesson_stats') or not is_lesson:
            DB.executemany_(
                '''insert into statistic
                (time,viscosity,w,count,mistakes,type,data) values (?,?,?,?,?,?,?)''',
                vals)
            DB.executemany_(
                'insert into mistake (w,target,mistake,count) values (?,?,?,?)',
                [(now, k[0], k[1], v) for k, v in mistakes.iteritems()])

        if is_lesson:
            mins = (Settings.get("min_lesson_wpm"),
                    Settings.get("min_lesson_acc"))
        else:
            mins = (Settings.get("min_wpm"), Settings.get("min_acc"))

        if 12.0 / spc < mins[0] or accuracy < mins[1] / 100.0:
            self.setText(self.text)
        elif not is_lesson and Settings.get('auto_review'):
            ws = filter(lambda x: x[5] == 2, vals)
            if len(ws) == 0:
                self.emit(SIGNAL("wantText"))
                return
            ws.sort(key=lambda x: (x[4], x[0]), reverse=True)
            i = 0
            while ws[i][4] != 0:
                i += 1
            i += (len(ws) - i) // 4

            self.emit(SIGNAL("wantReview"), map(lambda x: x[6], ws[0:i]))
        else:
            self.emit(SIGNAL("wantText"))