Disclosure first
I made Student Planner and I sell it for $5, so this is an audit of my own paid product, and it is not flattering. I claimed a feature in its store listing that the file does not implement. I then replaced that claim with a second, subtler claim that was also wrong. Both are documented below with the formulas that prove it, so you can check me rather than take my word for it.
The claim, and the file
For a while the listing called it a GPA calculator. It is not one. The correct short answer is in the one averaging formula in the entire workbook, on the Semester sheet, cell F20:
IFERROR(ROUND(AVERAGEIFS(E9:E16,D9:D16,">0"),2),"add courses")
That is xl/worksheets/sheet1.xml, cell F20, with the XML's > unescaped to > for readability. Everything else in it is byte-for-byte what ships.
E is the "Current grade %" column and D is "Credits", both per the header row 8 of that sheet. The credits column appears only inside the criteria pair D9:D16,">0". That is a filter meaning "count this row if the student filled in a credit number". It is not a weight. Nothing in the file multiplies a grade by its credits, which the formula count confirms: SUMPRODUCT appears zero times in all four worksheets.
There is also no 4.0-scale mapping anywhere. The nearest thing is the per-course letter column, F9:F16, eight identical copies of one formula, and its thresholds:
IF(E9="","",IF(E9>=93,"A",IF(E9>=90,"A-",IF(E9>=87,"B+",IF(E9>=83,"B",IF(E9>=80,"B-",IF(E9>=77,"C+",IF(E9>=73,"C",IF(E9>=70,"C-",IF(E9>=67,"D+","D/F")))))))))))
Ten nested IFs producing a letter from a percentage. That is a letter-grade lookup on one invented scale, not a GPA, and the difference matters in both directions.
Unzipping is the whole audit
An .xlsx is a ZIP of XML parts. Nothing about this needs Excel, and it takes about ninety seconds:
$ unzip -l Student-Planner.xlsx
Archive: Student-Planner.xlsx
Length Date Time Name
5123 ... xl/worksheets/sheet1.xml
11554 ... xl/worksheets/sheet2.xml
3384 ... xl/worksheets/sheet3.xml
5156 ... xl/worksheets/sheet4.xml
978 ... xl/workbook.xml
531 ... _rels/.rels
...
12 files, 45294 bytes uncompressed
xl/workbook.xml names the sheets, which is faster than opening the file:
<sheet name="Semester" sheetId="1" state="visible" r:id="rId1"/>
<sheet name="Assignments" sheetId="2" state="visible" r:id="rId2"/>
<sheet name="Schedule" sheetId="3" state="visible" r:id="rId3"/>
<sheet name="Time Log" sheetId="4" state="visible" r:id="rId4"/>
Then count the work. In SpreadsheetML, a formula lives in a <f> element inside its cell:
$ for n in 1 2 3 4; do echo -n "sheet$n: "; unzip -p Student-Planner.xlsx xl/worksheets/sheet$n.xml | grep -o '<f>' | wc -l; done
sheet1: 9
sheet2: 40
sheet3: 0
sheet4: 2
51 formulas total, and no <f> element carries a t="shared" or t="array" attribute, so that count is the complete set rather than the visible heads of shared ranges. It also tells you where the product is thin. The Schedule sheet has 0 formulas. It is a printed-looking grid, which is a fine thing to sell and a stupid thing to imply computes.
Two more greps answer most questions about any workbook:
$ for n in 1 2 3 4; do unzip -p Student-Planner.xlsx xl/worksheets/sheet$n.xml; done > /tmp/all.xml
$ grep -o 'SUMPRODUCT' /tmp/all.xml | wc -l
0
$ grep -o 'AVERAGEIF' /tmp/all.xml | wc -l
1
One average, one letter scale, one 40-copy column, two totals. That is the entire computational surface of something I described as prioritising your workload for you.
What a real weight would change
Take two courses: a 4-credit at 95 percent and a 1-credit at 60 percent. Both have credits filled in, so both pass the ">0" filter.
AVERAGEIFS over {95, 60} is 77.5, which the ">=77" branch would call a C+. Weighted by credits it is (95*4 + 60*1) / 5 = 88.0, which that same scale calls a B+. Ten and a half percentage points, two letter bands apart, from a column that is in the file and visibly labelled "Credits".
The obvious replacement is SUMPRODUCT(E9:E16,D9:D16)/SUM(D9:D16), and it is wrong in two ways that I can see from the XML but cannot test here. Blanks are involved: cells D9:E16 ship as empty numeric cells with a format applied, no <v> element, so a naive product over rows you have not filled contributes zero to the numerator while real credit values sit in the denominator and pull the average toward nothing. And a course where you have entered credits but no grade yet is counted differently by each approach. A weighted version needs the same criteria on both the numerator and the denominator, which in this file means two SUMIFS calls, not one tidy SUMPRODUCT.
I did not ship that change. The reason is that I cannot confirm AVERAGEIF blank-handling behaviour against a real Excel or Google Sheets from this machine, and swapping an unverified formula into a paid file because the prose needed fixing is how the first wrong claim happened. So the open question stays open and named: how AVERAGEIFS and any weighted replacement treat blank cells in the average range, on Excel, Google Sheets and LibreOffice. If you know the answer for all three, the file is 51 formulas and a unzip -p away from checking whether it holds.
What the file does compute, verified
The claims that survived contact with the XML are worth stating, because that is the point of the exercise.
The Assignments sheet has 40 formulas, and they are one formula repeated down G4:G43:
IF(A4="","",A4-TODAY())
A countdown, honest and simple. Its conditional formatting is real too, in the one <conditionalFormatting> block in the workbook:
<conditionalFormatting sqref="G4:G43">
<cfRule type="cellIs" priority="1" operator="lessThan" dxfId="0"><formula>0</formula></cfRule>
<cfRule type="cellIs" priority="2" operator="between" dxfId="1"><formula>0</formula><formula>3</formula></cfRule>
</conditionalFormatting>
dxfId indexes the <dxfs> list in xl/styles.xml, which holds two solid fills: 00fadbd8 and 00fdebd0. Those are light red and light amber. So "overdue turns red, due within 3 days turns amber" is a true sentence about this file, and I checked it at the byte level rather than remembering writing it.
The Time Log has both of its two formulas:
I3: SUMIFS(C4:C200,A4:A200,">="&(TODAY()-WEEKDAY(TODAY(),2)+1),A4:A200,"<="&(TODAY()-WEEKDAY(TODAY(),2)+7))/60
I4: SUM(C4:C500)/60
WEEKDAY(...,2) makes Monday day 1, so the lower bound is this week's Monday and the upper is Sunday. That is genuinely Monday-to-Sunday, seven days inclusive, and I worked the arithmetic on a Thursday to be sure. Dividing by 60 turns minutes into hours, matching the 0.0 "hrs" number format in styles.xml.
It also contains a defect I found only by reading them side by side: the weekly total scans C4:C200 and the all-time total scans C4:C500. Log your 200th session and the weekly figure starts ignoring rows while the all-time figure keeps counting them. That is a silent, slow, date-adjacent break, and the fix is one number in one cell.
What nothing reads
The Assignments sheet has a "Weight %" column in D3 and a "Grade %" column in F3. No formula in the workbook references either column. The store copy I wrote said "every deadline with its weight" and "weight-aware deadlines", which reads like the sheet balances weights against dates. It reads two numbers into a column that nothing computes with.
Two rows of my own repo said it. products/student-planner/LISTING-KIT.md:12 described "a credits-weighted grade average" and :20 promised "a running credits-weighted average as a percentage". :32 listed gpa calculator as a keyword. That file is the paste-source for store copy, so the wrong claim was not merely archived, it was loaded and ready to paste back in. I have since rewritten those three lines to match what the workbook does and added a comment block naming the banned phrases and the cell references that prove them wrong, so the next person to paste from it hits the guard instead of the claim. The live listing itself was rewritten earlier and I have read the rendered page rather than a cached copy: zero matches for "GPA", zero for "credits-weighted", "grade average" present, price $5.00.
The gallery images were the looser end, and checking them was worth it. The preview markup they are rendered from used to put the word GPA in the four-sheet tagline; that markup is fixed, with marketing/previews/html/student-planner-preview-1.html:96 and student-planner-preview-2.html:84 both reading "grade average" where they used to read "GPA". But a corrected source is not a corrected store: I downloaded the two images actually being served, looked at them, and the header still said "assignments / GPA / schedule / time log". The listing text had been fixed for weeks while the picture next to it kept saying the wrong thing, and nothing in my process caught that except looking.
Both images are replaced now, with the re-rendered pair, and the server record shows only the two new files. Before shipping either one I opened it and read the header.
Small one: the shipped workbook's own banner rows contain an em dash entity in two of the four sheet headers, ASSIGNMENT TRACKER — sorted by deadline... and the Time Log equivalent. My house rule for buyer-facing text is plain ASCII punctuation, and the file I sell predates the rule.
Getting it
Student Planner is $5, one-time, instant download, 30-day refund: https://payhip.com/b/fPI56. It is also in the $19 bundle at https://payhip.com/b/yTE86.
The limitation I would want you to know before buying: the Semester sheet has exactly eight course rows, 9 through 16, and both the letter column and the average stop at 16. Take a ninth course and you have to copy formulas down yourself, or it simply does not appear in the average. Eight is enough for a normal semester and it is not enough for a co-op term with an overload, and that is a design decision I made before I knew anyone would check. The credits column is a filter, not a weight, and the README and SUPPORT.md say so. products/student-planner/SUPPORT.md:15 has been honest about it since the relabel: "The semester average is a percentage average of your course grades, not a 4.0-scale GPA."
If you sell a spreadsheet, run the 51-formula count on it. If you buy one, run it too. The file always answers.
Authorship note: this post was drafted by an AI assistant working from the project's own files and command output. I read it before publishing and corrected the parts that had gone stale while it was being written.




