content: audit phase 4 — lead with the work that differentiates
Brand/content phase. The engineering was never the weak part of this site;
the copy was. Decisions on title, bio angle, download figures and node copy
were the owner's — this implements them.
Identity:
- jobTitle was 'Software Development - Lead Associate', an internal HR grade
that means nothing to a recruiter or a search engine. Now 'Lead Software
Engineer' — a faithful translation, not a promotion.
- aboutMe opened with "Intermediate Software Engineer", grading himself down
in his own headline, and named the most generic possible stack
(React/Node/SQL/ShadCN/Docker). It now leads with the work almost nobody
else has done: WebRTC telehealth, real-time gesture recognition that made
chat usable for speech-impaired patients, and air-gapped deployment
tooling. Those were previously buried 3-4 clicks deep in leaf nodes and
appeared nowhere on the surface.
- Title/description/OG/Twitter copy realigned to match. JSON-LD needed no
change — it already derives from the origin node.
Node copy:
- The five job nodes had placeholder descriptions ('Junior developer at the
start of my career.', 'Where I am now.') that are the first thing a tooltip
shows. Each is now a one-line hook naming the most substantial thing from
that role.
- Normalised highlight punctuation: only exp-job5's bullets ended in periods.
Proof of work:
- `downloads` existed on ProjectNode and was used by zero nodes despite two
published PyPI packages. Populated with verified lifetime figures from
pypistats.org (tvdbAPI 2,277, tmdbAPI 784, checked 2026-08-09) and dated in
a comment. Lifetime, deliberately, not the recent rate — 27 and 12 in the
last month is not a number worth advertising.
- Nothing rendered the field. NodeModal now shows it beside the status badge,
and SeoContent renders it too so it's crawlable.
Data integrity:
- edu-degree's description said "Computer Science" while its own structured
fields said Applied Science / IT-Software Development. Its label was the
generic "University"; now Green River College.
- calculatePlayerLevel() hardcoded '2020-10-01', duplicating
exp-job1.startDate — two sources for one fact, free to drift. It now
derives the earliest start from the experience nodes, via an explicit
"Month YYYY" parser rather than handing a bare "October 2020" to
`new Date` (not a format engines are required to accept).
- Added the first tests for calculatePlayerLevel, previously the only
untested export in stats.ts. 14 -> 16 tests.
- Fixed the 'tool-vscod' id typo.
Corrections to the audit this phase acted on: the self-hosted repos at
git.tehriehldeal.com ARE publicly reachable (all three return 200
anonymously) — the earlier claim that recruiters couldn't reach them was
asserted without checking. All three live project sites are up too.
Verified in the production build: modal shows the new title and bio, the
downloads badge renders as "↓ 2,277 downloads", figures are crawlable in the
prerendered HTML, and no filler copy survives.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
746d81b151
commit
f83a317520
7 files changed
+91
-26
No files matched your search
+25
-3
@@ -2,7 +2,7 @@
|
||||
// STATS UTILS — pure aggregation for the RPG-style character panel.
|
||||
// ==========================================================================
|
||||
|
||||
import { treeNodes, type SkillNode } from '$lib/data';
|
||||
import { treeNodes, type SkillNode, type ExperienceNode } from '$lib/data';
|
||||
|
||||
/** Skill branch ids and their display labels (single source of truth). */
|
||||
export const BRANCH_IDS = [
|
||||
@@ -29,9 +29,31 @@ export const PROFICIENCY_ORDER: Record<string, number> = {
|
||||
beginner: 3
|
||||
};
|
||||
|
||||
/** Character "level" derived from years since the first job started. */
|
||||
/**
|
||||
* Parse a "Month YYYY" string (the format used by every startDate in data.ts)
|
||||
* into a timestamp. Built explicitly rather than handed straight to `new Date`:
|
||||
* "October 2020" alone is not a format the spec requires engines to accept.
|
||||
*/
|
||||
function parseMonthYear(value: string): number | null {
|
||||
const [month, year] = value.trim().split(/\s+/);
|
||||
if (!month || !year) return null;
|
||||
const parsed = new Date(`${month} 1, ${year}`).getTime();
|
||||
return Number.isNaN(parsed) ? null : parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Character "level" derived from years since the first job started.
|
||||
*
|
||||
* The start date is read from the experience nodes rather than hardcoded — it
|
||||
* previously duplicated exp-job1.startDate, so the two could silently drift.
|
||||
*/
|
||||
export function calculatePlayerLevel(): number {
|
||||
const earliestStart = new Date('2020-10-01'); // First job
|
||||
const starts = treeNodes
|
||||
.filter((n): n is ExperienceNode => n.category === 'experience' && !!n.startDate)
|
||||
.map((n) => parseMonthYear(n.startDate as string))
|
||||
.filter((t): t is number => t !== null);
|
||||
|
||||
const earliestStart = new Date(starts.length ? Math.min(...starts) : Date.now());
|
||||
const now = new Date();
|
||||
const millisPerYear = 1000 * 60 * 60 * 24 * 365.25;
|
||||
const years = (now.getTime() - earliestStart.getTime()) / millisPerYear;
|
||||
|
||||
Reference in new issue
Block a user