Mi Español
I built this because I was getting bored of learning Spanish.
I’ve been learning for about two years. I live in Alicante, my wife is Spanish, and I speak to my tutor Alberto three mornings a week. I’m somewhere around B2. I can have most of the conversations I need to have, but it’s getting harder to tell whether I’m improving.
Early on, progress was obvious. I could do things I couldn’t do a month before. Now I put in the hours and make slightly fewer mistakes, probably, but I couldn’t tell you which ones. I wanted to see whether the work was paying off and get a better idea of what to practise.
I also knew I wasn’t going to maintain another study log. There was already two years of evidence in the tools I used, so I built something to pull it together.
My homework was in Notion. Lessons were in Google Calendar, spread across several tutors and booking systems. I had a Claude project where I practised writing, Kindle highlights from Spanish books, and lesson recordings in Granola. Spotify had my podcast history, although getting hold of that turned out to be a problem.
Each source keeps its own raw data in Postgres. A function turns it all into an activity_log: the date, source, type of activity, estimated minutes and skill practised. The dashboard reads from that.
*Fig. 1 — Six sources, four ingest routes, one table that every page reads*Keeping the raw data means I can change how I count something without fetching it again. Rebuilding the 654 activity rows takes about a second, which I’ve done quite a lot because my first attempts at counting were wrong.
Getting the data out took longer.
With Notion, I started with an endpoint that appeared in all the tutorials and no longer worked with the API version I was using. Queries had moved from databases to data sources. I also spent time checking a perfectly good page ID because Notion returned a 404. The integration hadn’t been given access to the page.
For Google Calendar, I used a service account and shared my calendar with its email address. That avoided the refresh-token expiry problem you get when an OAuth app is left in testing mode. You do need to query your actual calendar address, though. Asking for primary gives you the service account’s empty calendar.
Claude required a manual export. The API I could access didn’t let me read my existing conversations, so I downloaded the ZIP from account settings.
The export had a folder for my Spanish project and a file containing 1,322 conversations, but no field connecting the two. I’d assumed I could select everything in the Spanish project. Instead, I had to identify the conversations by their contents.
I used the ratio of Spanish to English stopwords, plus a check for Spanish in the title. That found 143 conversations containing 4,545 messages and 72,282 words of my own Spanish. I hadn’t realised how much I’d written.
I’d recently moved to Granola after a Fireflies setup that never recorded a call. A lot of the instructions I found relied on reading a local cache file, which had since been encrypted. I used Granola’s public API and a webhook instead.
For my calls with Alberto, the transcripts distinguish between my audio and his. Each segment has a start and end time, so I can calculate speaking time and words per minute myself. I also added a count of Spanish filler words: este, o sea, pues, vale, bueno. I’m interested to see how often I use those when I can’t think of what to say.
Reading was easier to estimate than I expected. My Kindle highlights include locations, so the furthest highlight in each book gives me a rough indication of how far I’ve read.
Using about 18 words per location and an estimated reading speed of 125 Spanish words a minute, El Alquimista came out at 39,000 words. I’d finished it. Harry Potter y la Cámara Secreta came out at 51,000, and I was about halfway through. Close enough to use as an estimate, provided I remember that highlighting a page doesn’t tell me how long I spent reading it. Across nine books, that gave me about 49 hours.
Spotify is still missing. I couldn’t get my podcast listening history through the Web API, so I requested a privacy export. While I wait for it, the dashboard says I’ve done half an hour of listening, which is obviously wrong.
The first version had some other fairly obvious problems.
One day in June showed twenty-five hours of homework. I’d imported fifty pages into Notion at once, and because the homework table had no date field, I’d used each page’s creation time as the date I did the work.
I now spread bulk imports across the preceding year. It’s a guess. The dates are lost, and distributing the work seems more useful than pretending I did all of it in one day.
The first calendar sync found 62 completed lessons, which felt low. It was looking for Alberto’s name, but my lessons had been booked under four different naming conventions, including “ELE con Pancito” and the less helpful “Clase - 55 minutos”. Widening the search found 170 lesson events going back to August 2024.
I only caught that because I’d expected a bigger number. The app had no way of knowing there was another tutor to look for.
There were also 137 recurring “Spanish Block” events in my calendar. I definitely hadn’t done all 137 hours. I’ve counted those at thirty minutes each, split between writing and comprehension, which is my best estimate of how much of that planned time I used.
The streak is harder to defend. I asked for it to start at 100 and increase every Monday, whether or not I’d logged anything. My reasoning was that I always do some Spanish, and losing a streak because an app hadn’t recorded it would annoy me.
Having used it, I think it’s the weakest part of the dashboard. I told the computer to make the number go up. It’s still there, but it shouldn’t be presented with the same confidence as a recorded lesson.
The overview: level, hours, this week's focus, and two years of weekly bars
Once the activity tracking worked, I started looking at the homework itself.
There were two years of corrections sitting in Notion and Claude. Alberto tends to write “Original” followed by “Corregido”. Claude uses incorrect/correct pairs. Translation exercises have my attempt followed by the corrected version. I already had dated examples of mistakes I’d made and how they’d been corrected.
I wanted to pull those out, group them, and see which ones kept coming back.
I run the analysis through Claude Code using my existing subscription. A session fetches unprocessed material from the app, extracts corrections, vocabulary and topics, then posts the results back. A /coach skill contains the instructions so I don’t have to explain the job each time.
The app tracks a hash of each item’s text to tell whether it has changed since the last analysis. Running it again updates existing records rather than duplicating them.
This does mean I have to run it myself, roughly once a week. I could put an API-backed scheduled job behind the same endpoints later, but for now I’m happy doing it this way.
I put two restrictions on the analysis.
First, it can only extract corrections already present in the source. It can’t decide to mark up my Spanish again. If Alberto or Claude corrected something at the time, it can include that pair. Otherwise, it leaves it alone.
I wanted to review the feedback I’d already received. Asking a model to find more mistakes would make it harder to trace each result back to the lesson or exercise it came from.
Second, grammar topics have to come from a fixed list of forty B1–C1 topics. Otherwise I’d end up counting “subjunctive”, “el subjuntivo” and “subjunctive mood” separately. The fixed list also lets me see which topics haven’t appeared in the material analysed so far.
Vocabulary gets divided into three groups:
- Used: words I’ve used correctly in my own writing.
- Met: words I’ve highlighted, seen on a flashcard or been corrected to, but haven’t yet used correctly in the material analysed.
- Target: words suggested for future practice.
So far, there are 135 words in “used” and 214 in “met”. That gives me a useful list to practise from. I’ve encountered those 214 words, but there’s no evidence here that I’m using them yet.
The vocabulary bank: 349 words, each tagged as one I use or one I have only met
The first 94 items produced 97 corrections and 349 vocabulary entries. Word choice was the most common error category, with 17 corrections, followed by prepositions at 15, agreement at 14 and the subjunctive at 11.
The prepositions were predictable. I keep dropping them before que, as in me di cuenta había… instead of me di cuenta de que había…. I need to learn which prepositions go with which verbs, and I haven’t put enough time into that.
I’d expected the word-choice errors to be awkward synonyms. A lot were false friends or direct translations from English: la costa for el coste, contento for contenido, reservar un mecánico where I meant buscar.
The subjunctive was more useful to see. It was my most-practised topic, with twenty separate homework sessions, and still my fourth-most-common source of errors.
I usually get it right in exercises where I know that’s what I’m being tested on. In free writing, I make mistakes like ojalá que podré instead of ojalá que pueda. I’d kept doing more subjunctive exercises because I knew I struggled with it. Seeing the corrections together made me think I need more practice using it while concentrating on what I’m trying to say.
The weekly plan uses those results to suggest three focus areas, with examples from my own writing. It also gives me two exercises for a thirty-minute study block, twelve words from my books and corrections, and a sentence in Spanish I can send to Alberto explaining what I want to work on.
That last bit is useful. I already have a tutor I speak to three times a week; I’d like to turn up with something more specific than “I think I still need help with the subjunctive”.
The focus page: three areas, each with real sentences of mine as evidence, and a line to paste to Alberto
I switched the whole interface to Spanish at the end. I should have done that at the start.
At the time of writing, the dashboard has been live for a day. It shows an estimated 323.5 hours of study, 179 homework items, 143 Claude conversations and nine books. There are 170 lesson events in the calendar; the displayed lesson count is 197 because I’ve added an allowance for older Preply bookings that never appeared there.
Writing accounts for about 125 hours, speaking 89 and reading 49. The material analysed so far touches 33 of the forty grammar topics. Those numbers will change as I work through the backlog and replace estimates with better data.
Granola is connected and waiting for the next lesson. I’m still waiting for Spotify to send the export.