Editorial redesign, per-project & per-paper detail pages, blog, full CV PDF
- New "quantitative editorial" identity: warm paper + ink palette, Fraunces display serif, IBM Plex Mono labels/nav/captions, graph-paper backdrop, hairline cards, ink buttons, annual-report stat rules, colophon footer, mounted hero portrait with Fig. 1 caption - /apps/[slug]: 25 detailed project pages (stats, features, architecture, stack, highlights) generated from repo analyses; Details buttons on cards - /research/[slug]: 9 detailed paper pages (WP2–WP10 + thesis chapters) with abstract, headline findings, key results, data, methodology, reproducibility - WP10 (Assessment Gap in Quebec) added to research, home stats and CV - /blog: 3 essays with editorial typography, author signature, and server-rendered KaTeX support ($$ display, \( \) inline) - /cv: downloadable 4-page CV PDF (cv-source/cv.html → headless Chromium) - New web platforms on /apps: Lou-Ka, Vrai-Prix, ValoPlex, QHPI with brand-faithful icons; real icons for CoinExplorer & LLM Index - WP repo links on /research; profile photo on home; Blog nav tab Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Showing 41 changed files with +8,550 and −342
added
app/apps/[slug]/page.tsx
+367 −0
@@ -0,0 +1,367 @@ | ||
| 1 | +/* | |
| 2 | + page.tsx | |
| 3 | + spboucher.ai Web | |
| 4 | + Author: Simon-Pierre Boucher | |
| 5 | + Mail: contact@spboucher.ai | |
| 6 | +*/ | |
| 7 | + | |
| 8 | +import type { Metadata } from "next"; | |
| 9 | +import Image from "next/image"; | |
| 10 | +import Link from "next/link"; | |
| 11 | +import { notFound } from "next/navigation"; | |
| 12 | +import { | |
| 13 | + ArrowLeft, | |
| 14 | + Award, | |
| 15 | + BookOpen, | |
| 16 | + Download, | |
| 17 | + ExternalLink, | |
| 18 | + Github, | |
| 19 | + Layers, | |
| 20 | + Sparkles, | |
| 21 | + Tag, | |
| 22 | + Workflow, | |
| 23 | +} from "lucide-react"; | |
| 24 | + | |
| 25 | +import { Badge } from "@/components/ui/badge"; | |
| 26 | +import { buttonVariants } from "@/components/ui/button"; | |
| 27 | +import { Card, CardHeader } from "@/components/ui/card"; | |
| 28 | +import { IconTile } from "@/components/icon-tile"; | |
| 29 | +import { Reveal } from "@/components/reveal"; | |
| 30 | +import { SectionHeading } from "@/components/section-heading"; | |
| 31 | +import { getAllProjectLinks, type ProjectLinks } from "@/lib/apps"; | |
| 32 | +import { getDetailIcon } from "@/lib/detail-icons"; | |
| 33 | +import { projectDetails } from "@/lib/project-details"; | |
| 34 | + | |
| 35 | +interface PageProps { | |
| 36 | + params: Promise<{ slug: string }>; | |
| 37 | +} | |
| 38 | + | |
| 39 | +function getProject(slug: string): ProjectLinks | undefined { | |
| 40 | + return getAllProjectLinks().find((p) => p.slug === slug); | |
| 41 | +} | |
| 42 | + | |
| 43 | +export function generateStaticParams() { | |
| 44 | + return getAllProjectLinks() | |
| 45 | + .filter((p) => projectDetails.some((d) => d.slug === p.slug)) | |
| 46 | + .map((p) => ({ slug: p.slug })); | |
| 47 | +} | |
| 48 | + | |
| 49 | +export async function generateMetadata({ | |
| 50 | + params, | |
| 51 | +}: PageProps): Promise<Metadata> { | |
| 52 | + const { slug } = await params; | |
| 53 | + const project = getProject(slug); | |
| 54 | + const detail = projectDetails.find((d) => d.slug === slug); | |
| 55 | + if (!project || !detail) return {}; | |
| 56 | + return { | |
| 57 | + title: `${project.name} — ${project.tagline}`, | |
| 58 | + description: detail.hero.subheadline, | |
| 59 | + }; | |
| 60 | +} | |
| 61 | + | |
| 62 | +export default async function ProjectDetailPage({ params }: PageProps) { | |
| 63 | + const { slug } = await params; | |
| 64 | + const project = getProject(slug); | |
| 65 | + const detail = projectDetails.find((d) => d.slug === slug); | |
| 66 | + if (!project || !detail) notFound(); | |
| 67 | + | |
| 68 | + const statCols = | |
| 69 | + detail.stats.length <= 3 | |
| 70 | + ? "sm:grid-cols-3" | |
| 71 | + : detail.stats.length === 4 | |
| 72 | + ? "sm:grid-cols-2 lg:grid-cols-4" | |
| 73 | + : detail.stats.length === 5 | |
| 74 | + ? "sm:grid-cols-3 lg:grid-cols-5" | |
| 75 | + : "sm:grid-cols-3 lg:grid-cols-6"; | |
| 76 | + | |
| 77 | + return ( | |
| 78 | + <div className="mx-auto max-w-5xl px-4 py-16 sm:px-6"> | |
| 79 | + {/* Back link */} | |
| 80 | + <Reveal> | |
| 81 | + <Link | |
| 82 | + href="/apps" | |
| 83 | + className="inline-flex items-center gap-1.5 text-sm font-medium text-muted-foreground transition-colors hover:text-primary" | |
| 84 | + > | |
| 85 | + <ArrowLeft className="h-4 w-4" aria-hidden="true" /> | |
| 86 | + All apps & projects | |
| 87 | + </Link> | |
| 88 | + </Reveal> | |
| 89 | + | |
| 90 | + {/* Hero */} | |
| 91 | + <Reveal className="mt-8"> | |
| 92 | + <div className="flex flex-col gap-6 sm:flex-row sm:items-start"> | |
| 93 | + {project.icon ? ( | |
| 94 | + <Image | |
| 95 | + src={project.icon} | |
| 96 | + alt={`${project.name} icon`} | |
| 97 | + width={96} | |
| 98 | + height={96} | |
| 99 | + priority | |
| 100 | + className="h-24 w-24 shrink-0 rounded-[0.9rem] border border-border shadow-[var(--shadow-soft)]" | |
| 101 | + /> | |
| 102 | + ) : ( | |
| 103 | + <div | |
| 104 | + aria-hidden="true" | |
| 105 | + className="flex h-24 w-24 shrink-0 items-center justify-center rounded-[0.9rem] border border-foreground/15 bg-card text-5xl shadow-[var(--shadow-soft)]" | |
| 106 | + > | |
| 107 | + {project.emoji} | |
| 108 | + </div> | |
| 109 | + )} | |
| 110 | + <div className="min-w-0"> | |
| 111 | + <div className="flex flex-wrap items-center gap-2"> | |
| 112 | + <Badge> | |
| 113 | + {project.kind === "zyquo" | |
| 114 | + ? "Zyquo macOS Suite" | |
| 115 | + : project.demo | |
| 116 | + ? "Web Platform" | |
| 117 | + : "Open Source"} | |
| 118 | + </Badge> | |
| 119 | + {project.language && ( | |
| 120 | + <Badge variant="outline">{project.language}</Badge> | |
| 121 | + )} | |
| 122 | + </div> | |
| 123 | + <h1 className="mt-3 font-display text-3xl font-bold tracking-tight sm:text-4xl"> | |
| 124 | + {project.name} | |
| 125 | + </h1> | |
| 126 | + <p className="mt-2 text-lg font-medium text-foreground/85"> | |
| 127 | + {detail.hero.headline} | |
| 128 | + </p> | |
| 129 | + <p className="mt-2 max-w-2xl text-sm leading-relaxed text-muted-foreground"> | |
| 130 | + {detail.hero.subheadline} | |
| 131 | + </p> | |
| 132 | + <div className="mt-5 flex flex-wrap gap-2"> | |
| 133 | + <a | |
| 134 | + href={project.repo} | |
| 135 | + target="_blank" | |
| 136 | + rel="noopener noreferrer" | |
| 137 | + className={buttonVariants({ variant: "outline", size: "sm" })} | |
| 138 | + > | |
| 139 | + <Github aria-hidden="true" /> | |
| 140 | + GitHub Repo | |
| 141 | + </a> | |
| 142 | + {project.release && ( | |
| 143 | + <a | |
| 144 | + href={project.release} | |
| 145 | + target="_blank" | |
| 146 | + rel="noopener noreferrer" | |
| 147 | + className={buttonVariants({ variant: "outline", size: "sm" })} | |
| 148 | + > | |
| 149 | + <Tag aria-hidden="true" /> | |
| 150 | + Release | |
| 151 | + </a> | |
| 152 | + )} | |
| 153 | + {project.demo && ( | |
| 154 | + <a | |
| 155 | + href={project.demo} | |
| 156 | + target="_blank" | |
| 157 | + rel="noopener noreferrer" | |
| 158 | + className={buttonVariants({ variant: "default", size: "sm" })} | |
| 159 | + > | |
| 160 | + <ExternalLink aria-hidden="true" /> | |
| 161 | + Visit {project.demo.replace(/^https?:\/\//, "")} | |
| 162 | + </a> | |
| 163 | + )} | |
| 164 | + {project.dmg && ( | |
| 165 | + <a | |
| 166 | + href={project.dmg} | |
| 167 | + className={buttonVariants({ variant: "default", size: "sm" })} | |
| 168 | + > | |
| 169 | + <Download aria-hidden="true" /> | |
| 170 | + Download DMG | |
| 171 | + </a> | |
| 172 | + )} | |
| 173 | + </div> | |
| 174 | + </div> | |
| 175 | + </div> | |
| 176 | + </Reveal> | |
| 177 | + | |
| 178 | + {/* Stats band */} | |
| 179 | + {detail.stats.length > 0 && ( | |
| 180 | + <Reveal className="mt-14"> | |
| 181 | + <div className={`grid grid-cols-2 gap-x-8 gap-y-8 ${statCols}`}> | |
| 182 | + {detail.stats.map((stat) => ( | |
| 183 | + <div key={stat.label} className="border-t-2 border-foreground pt-3"> | |
| 184 | + <p className="font-display text-2xl font-semibold tracking-tight text-foreground sm:text-3xl"> | |
| 185 | + {stat.value} | |
| 186 | + </p> | |
| 187 | + <p className="mt-1.5 font-mono text-[0.66rem] uppercase leading-relaxed tracking-[0.12em] text-muted-foreground"> | |
| 188 | + {stat.label} | |
| 189 | + </p> | |
| 190 | + </div> | |
| 191 | + ))} | |
| 192 | + </div> | |
| 193 | + </Reveal> | |
| 194 | + )} | |
| 195 | + | |
| 196 | + {/* Overview */} | |
| 197 | + <section className="mt-14"> | |
| 198 | + <Reveal> | |
| 199 | + <SectionHeading title="Overview" icon={BookOpen} /> | |
| 200 | + </Reveal> | |
| 201 | + <Reveal delay={0.06}> | |
| 202 | + <div className="mt-4 max-w-3xl space-y-4"> | |
| 203 | + {detail.overview.map((paragraph) => ( | |
| 204 | + <p | |
| 205 | + key={paragraph.slice(0, 48)} | |
| 206 | + className="text-sm leading-relaxed text-muted-foreground sm:text-[0.95rem]" | |
| 207 | + > | |
| 208 | + {paragraph} | |
| 209 | + </p> | |
| 210 | + ))} | |
| 211 | + </div> | |
| 212 | + </Reveal> | |
| 213 | + </section> | |
| 214 | + | |
| 215 | + {/* Features */} | |
| 216 | + {detail.features.length > 0 && ( | |
| 217 | + <section className="mt-14"> | |
| 218 | + <Reveal> | |
| 219 | + <SectionHeading title="Key Features" icon={Sparkles} /> | |
| 220 | + </Reveal> | |
| 221 | + <div className="mt-6 grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> | |
| 222 | + {detail.features.map((feature, i) => ( | |
| 223 | + <Reveal key={feature.title} delay={i * 0.05} className="h-full"> | |
| 224 | + <Card className="h-full hover:-translate-y-1 hover:border-primary/25 hover:shadow-[var(--shadow-lift)]"> | |
| 225 | + <CardHeader> | |
| 226 | + <IconTile icon={getDetailIcon(feature.icon)} size="sm" /> | |
| 227 | + <h3 className="pt-2 text-sm font-semibold tracking-tight"> | |
| 228 | + {feature.title} | |
| 229 | + </h3> | |
| 230 | + <p className="text-xs leading-relaxed text-muted-foreground"> | |
| 231 | + {feature.description} | |
| 232 | + </p> | |
| 233 | + </CardHeader> | |
| 234 | + </Card> | |
| 235 | + </Reveal> | |
| 236 | + ))} | |
| 237 | + </div> | |
| 238 | + </section> | |
| 239 | + )} | |
| 240 | + | |
| 241 | + {/* How it works */} | |
| 242 | + {detail.architecture.length > 0 && ( | |
| 243 | + <section className="mt-14"> | |
| 244 | + <Reveal> | |
| 245 | + <SectionHeading title="How It Works" icon={Workflow} /> | |
| 246 | + </Reveal> | |
| 247 | + <ol className="mt-6 space-y-4"> | |
| 248 | + {detail.architecture.map((step, i) => ( | |
| 249 | + <Reveal key={step.title} delay={i * 0.06}> | |
| 250 | + <li className="flex items-start gap-4"> | |
| 251 | + <span | |
| 252 | + aria-hidden="true" | |
| 253 | + className="mt-1 pt-0.5 font-mono text-sm font-semibold text-primary" | |
| 254 | + > | |
| 255 | + {String(i + 1).padStart(2, "0")} | |
| 256 | + </span> | |
| 257 | + <div className="min-w-0 pt-1"> | |
| 258 | + <h3 className="text-sm font-semibold tracking-tight"> | |
| 259 | + {step.title} | |
| 260 | + </h3> | |
| 261 | + <p className="mt-1 max-w-2xl text-sm leading-relaxed text-muted-foreground"> | |
| 262 | + {step.description} | |
| 263 | + </p> | |
| 264 | + </div> | |
| 265 | + </li> | |
| 266 | + </Reveal> | |
| 267 | + ))} | |
| 268 | + </ol> | |
| 269 | + </section> | |
| 270 | + )} | |
| 271 | + | |
| 272 | + {/* Tech stack */} | |
| 273 | + {detail.techStack.length > 0 && ( | |
| 274 | + <section className="mt-14"> | |
| 275 | + <Reveal> | |
| 276 | + <SectionHeading title="Tech Stack" icon={Layers} /> | |
| 277 | + </Reveal> | |
| 278 | + <div className="mt-6 space-y-5"> | |
| 279 | + {detail.techStack.map((group, i) => ( | |
| 280 | + <Reveal key={group.category} delay={i * 0.05}> | |
| 281 | + <div> | |
| 282 | + <p className="text-xs font-semibold uppercase tracking-[0.16em] text-primary"> | |
| 283 | + {group.category} | |
| 284 | + </p> | |
| 285 | + <div className="mt-2 flex flex-wrap gap-2"> | |
| 286 | + {group.items.map((item) => ( | |
| 287 | + <Badge key={item} variant="secondary"> | |
| 288 | + {item} | |
| 289 | + </Badge> | |
| 290 | + ))} | |
| 291 | + </div> | |
| 292 | + </div> | |
| 293 | + </Reveal> | |
| 294 | + ))} | |
| 295 | + </div> | |
| 296 | + </section> | |
| 297 | + )} | |
| 298 | + | |
| 299 | + {/* Highlights */} | |
| 300 | + {detail.highlights.length > 0 && ( | |
| 301 | + <section className="mt-14"> | |
| 302 | + <Reveal> | |
| 303 | + <SectionHeading title="Highlights" icon={Award} /> | |
| 304 | + </Reveal> | |
| 305 | + <ul className="mt-6 grid gap-3 sm:grid-cols-2"> | |
| 306 | + {detail.highlights.map((highlight, i) => ( | |
| 307 | + <Reveal key={highlight} delay={i * 0.05} className="h-full"> | |
| 308 | + <li className="flex h-full items-start gap-3 border border-border bg-card px-5 py-4 shadow-[var(--shadow-soft)]"> | |
| 309 | + <Sparkles | |
| 310 | + className="mt-0.5 h-4 w-4 shrink-0 text-primary" | |
| 311 | + aria-hidden="true" | |
| 312 | + /> | |
| 313 | + <span className="text-sm leading-relaxed text-foreground/85"> | |
| 314 | + {highlight} | |
| 315 | + </span> | |
| 316 | + </li> | |
| 317 | + </Reveal> | |
| 318 | + ))} | |
| 319 | + </ul> | |
| 320 | + </section> | |
| 321 | + )} | |
| 322 | + | |
| 323 | + {/* CTA */} | |
| 324 | + <Reveal className="mt-16"> | |
| 325 | + <div className="border-y-2 border-foreground bg-card/50 p-8 text-center sm:p-10"> | |
| 326 | + <h2 className="font-display text-xl font-semibold tracking-tight"> | |
| 327 | + Explore {project.name} | |
| 328 | + </h2> | |
| 329 | + <p className="mx-auto mt-2 max-w-xl text-sm text-muted-foreground"> | |
| 330 | + {project.tagline} — the full source is on GitHub. | |
| 331 | + </p> | |
| 332 | + <div className="mt-5 flex flex-wrap justify-center gap-2"> | |
| 333 | + <a | |
| 334 | + href={project.repo} | |
| 335 | + target="_blank" | |
| 336 | + rel="noopener noreferrer" | |
| 337 | + className={buttonVariants({ variant: "default", size: "sm" })} | |
| 338 | + > | |
| 339 | + <Github aria-hidden="true" /> | |
| 340 | + View on GitHub | |
| 341 | + </a> | |
| 342 | + {project.demo && ( | |
| 343 | + <a | |
| 344 | + href={project.demo} | |
| 345 | + target="_blank" | |
| 346 | + rel="noopener noreferrer" | |
| 347 | + className={buttonVariants({ variant: "outline", size: "sm" })} | |
| 348 | + > | |
| 349 | + <ExternalLink aria-hidden="true" /> | |
| 350 | + Open the live site | |
| 351 | + </a> | |
| 352 | + )} | |
| 353 | + {project.dmg && ( | |
| 354 | + <a | |
| 355 | + href={project.dmg} | |
| 356 | + className={buttonVariants({ variant: "outline", size: "sm" })} | |
| 357 | + > | |
| 358 | + <Download aria-hidden="true" /> | |
| 359 | + Download DMG | |
| 360 | + </a> | |
| 361 | + )} | |
| 362 | + </div> | |
| 363 | + </div> | |
| 364 | + </Reveal> | |
| 365 | + </div> | |
| 366 | + ); | |
| 367 | +} | |
added
app/blog/[slug]/page.tsx
+198 −0
@@ -0,0 +1,198 @@ | ||
| 1 | +/* | |
| 2 | + page.tsx | |
| 3 | + spboucher.ai Web | |
| 4 | + Author: Simon-Pierre Boucher | |
| 5 | + Mail: contact@spboucher.ai | |
| 6 | +*/ | |
| 7 | + | |
| 8 | +import type { Metadata } from "next"; | |
| 9 | +import Image from "next/image"; | |
| 10 | +import Link from "next/link"; | |
| 11 | +import { notFound } from "next/navigation"; | |
| 12 | +import { | |
| 13 | + ArrowLeft, | |
| 14 | + ArrowRight, | |
| 15 | + CalendarDays, | |
| 16 | + Clock, | |
| 17 | + Mail, | |
| 18 | +} from "lucide-react"; | |
| 19 | + | |
| 20 | +import { Badge } from "@/components/ui/badge"; | |
| 21 | +import { BlogContent } from "@/components/blog-content"; | |
| 22 | +import { Reveal } from "@/components/reveal"; | |
| 23 | +import { getBlogPost, getBlogPosts, getBlogSlugs } from "@/lib/blog"; | |
| 24 | + | |
| 25 | +interface PageProps { | |
| 26 | + params: Promise<{ slug: string }>; | |
| 27 | +} | |
| 28 | + | |
| 29 | +export function generateStaticParams() { | |
| 30 | + return getBlogSlugs().map((slug) => ({ slug })); | |
| 31 | +} | |
| 32 | + | |
| 33 | +export async function generateMetadata({ | |
| 34 | + params, | |
| 35 | +}: PageProps): Promise<Metadata> { | |
| 36 | + const { slug } = await params; | |
| 37 | + const post = getBlogPost(slug); | |
| 38 | + if (!post) return {}; | |
| 39 | + return { | |
| 40 | + title: post.title, | |
| 41 | + description: post.excerpt, | |
| 42 | + openGraph: { | |
| 43 | + title: post.title, | |
| 44 | + description: post.excerpt, | |
| 45 | + type: "article", | |
| 46 | + publishedTime: post.date, | |
| 47 | + authors: ["Simon-Pierre Boucher"], | |
| 48 | + }, | |
| 49 | + }; | |
| 50 | +} | |
| 51 | + | |
| 52 | +function AuthorSignature({ compact = false }: { compact?: boolean }) { | |
| 53 | + return ( | |
| 54 | + <div className="flex items-center gap-4"> | |
| 55 | + <Image | |
| 56 | + src="/profile/simon-pierre-boucher.jpeg" | |
| 57 | + alt="Portrait of Simon-Pierre Boucher" | |
| 58 | + width={96} | |
| 59 | + height={96} | |
| 60 | + className={`${compact ? "h-11 w-11" : "h-14 w-14"} rounded-full object-cover object-top ring-2 ring-primary/20`} | |
| 61 | + /> | |
| 62 | + <div className="min-w-0"> | |
| 63 | + <p className="font-display text-sm font-semibold tracking-tight"> | |
| 64 | + Simon-Pierre Boucher | |
| 65 | + </p> | |
| 66 | + {!compact && ( | |
| 67 | + <p className="text-xs text-muted-foreground"> | |
| 68 | + Professor — Department of Administrative Sciences, UQO | |
| 69 | + </p> | |
| 70 | + )} | |
| 71 | + <a | |
| 72 | + href="mailto:contact@spboucher.ai" | |
| 73 | + className="mt-0.5 inline-flex items-center gap-1.5 text-xs text-muted-foreground transition-colors hover:text-primary" | |
| 74 | + > | |
| 75 | + <Mail className="h-3 w-3" aria-hidden="true" /> | |
| 76 | + contact@spboucher.ai | |
| 77 | + </a> | |
| 78 | + </div> | |
| 79 | + </div> | |
| 80 | + ); | |
| 81 | +} | |
| 82 | + | |
| 83 | +export default async function BlogPostPage({ params }: PageProps) { | |
| 84 | + const { slug } = await params; | |
| 85 | + const post = getBlogPost(slug); | |
| 86 | + if (!post) notFound(); | |
| 87 | + | |
| 88 | + const posts = getBlogPosts(); | |
| 89 | + const index = posts.findIndex((p) => p.slug === slug); | |
| 90 | + const previous = index > 0 ? posts[index - 1] : undefined; | |
| 91 | + const next = index < posts.length - 1 ? posts[index + 1] : undefined; | |
| 92 | + | |
| 93 | + return ( | |
| 94 | + <article className="mx-auto max-w-3xl px-4 py-16 sm:px-6"> | |
| 95 | + <Reveal> | |
| 96 | + <Link | |
| 97 | + href="/blog" | |
| 98 | + className="inline-flex items-center gap-1.5 text-sm font-medium text-muted-foreground transition-colors hover:text-primary" | |
| 99 | + > | |
| 100 | + <ArrowLeft className="h-4 w-4" aria-hidden="true" /> | |
| 101 | + All essays | |
| 102 | + </Link> | |
| 103 | + </Reveal> | |
| 104 | + | |
| 105 | + {/* Header */} | |
| 106 | + <Reveal className="mt-8"> | |
| 107 | + <header> | |
| 108 | + <div className="flex flex-wrap items-center gap-x-4 gap-y-2 text-xs font-medium text-muted-foreground"> | |
| 109 | + <Badge>Essay</Badge> | |
| 110 | + <span className="inline-flex items-center gap-1.5"> | |
| 111 | + <CalendarDays | |
| 112 | + className="h-3.5 w-3.5 text-primary/70" | |
| 113 | + aria-hidden="true" | |
| 114 | + /> | |
| 115 | + {post.dateLabel} | |
| 116 | + </span> | |
| 117 | + <span className="inline-flex items-center gap-1.5"> | |
| 118 | + <Clock className="h-3.5 w-3.5 text-primary/70" aria-hidden="true" /> | |
| 119 | + {post.readingMinutes} min read | |
| 120 | + </span> | |
| 121 | + <span className="flex flex-wrap gap-1.5"> | |
| 122 | + {post.tags.map((tag) => ( | |
| 123 | + <Badge key={tag} variant="outline"> | |
| 124 | + {tag} | |
| 125 | + </Badge> | |
| 126 | + ))} | |
| 127 | + </span> | |
| 128 | + </div> | |
| 129 | + <h1 className="mt-5 font-display text-3xl font-bold leading-tight tracking-tight sm:text-[2.75rem] sm:leading-[1.15]"> | |
| 130 | + {post.title} | |
| 131 | + </h1> | |
| 132 | + <div className="mt-7 border-y border-border/60 py-4"> | |
| 133 | + <AuthorSignature compact /> | |
| 134 | + </div> | |
| 135 | + </header> | |
| 136 | + </Reveal> | |
| 137 | + | |
| 138 | + {/* Body */} | |
| 139 | + <Reveal delay={0.08} className="mt-10"> | |
| 140 | + <BlogContent blocks={post.blocks} /> | |
| 141 | + </Reveal> | |
| 142 | + | |
| 143 | + {/* Signature */} | |
| 144 | + <Reveal className="mt-14"> | |
| 145 | + <footer className="border-y-2 border-foreground bg-card/50 p-6 sm:p-8"> | |
| 146 | + <p className="flex items-center gap-3 font-mono text-[0.68rem] font-medium uppercase tracking-[0.22em] text-primary"> | |
| 147 | + <span aria-hidden="true" className="h-px w-6 bg-primary/60" /> | |
| 148 | + Written by | |
| 149 | + </p> | |
| 150 | + <div className="mt-4"> | |
| 151 | + <AuthorSignature /> | |
| 152 | + </div> | |
| 153 | + </footer> | |
| 154 | + </Reveal> | |
| 155 | + | |
| 156 | + {/* Prev / next */} | |
| 157 | + {(previous || next) && ( | |
| 158 | + <Reveal className="mt-8"> | |
| 159 | + <nav | |
| 160 | + aria-label="More essays" | |
| 161 | + className="grid gap-3 sm:grid-cols-2" | |
| 162 | + > | |
| 163 | + {previous ? ( | |
| 164 | + <Link | |
| 165 | + href={`/blog/${previous.slug}`} | |
| 166 | + className="group rounded-3xl border border-border/60 bg-card/70 p-5 shadow-[var(--shadow-soft)] transition-all duration-300 hover:-translate-y-0.5 hover:border-primary/25" | |
| 167 | + > | |
| 168 | + <span className="inline-flex items-center gap-1 text-xs font-medium text-muted-foreground"> | |
| 169 | + <ArrowLeft className="h-3.5 w-3.5" aria-hidden="true" /> | |
| 170 | + Previous essay | |
| 171 | + </span> | |
| 172 | + <p className="mt-2 font-display text-sm font-semibold leading-snug tracking-tight transition-colors group-hover:text-primary"> | |
| 173 | + {previous.title} | |
| 174 | + </p> | |
| 175 | + </Link> | |
| 176 | + ) : ( | |
| 177 | + <span aria-hidden="true" /> | |
| 178 | + )} | |
| 179 | + {next && ( | |
| 180 | + <Link | |
| 181 | + href={`/blog/${next.slug}`} | |
| 182 | + className="group rounded-3xl border border-border/60 bg-card/70 p-5 text-right shadow-[var(--shadow-soft)] transition-all duration-300 hover:-translate-y-0.5 hover:border-primary/25" | |
| 183 | + > | |
| 184 | + <span className="inline-flex items-center gap-1 text-xs font-medium text-muted-foreground"> | |
| 185 | + Next essay | |
| 186 | + <ArrowRight className="h-3.5 w-3.5" aria-hidden="true" /> | |
| 187 | + </span> | |
| 188 | + <p className="mt-2 font-display text-sm font-semibold leading-snug tracking-tight transition-colors group-hover:text-primary"> | |
| 189 | + {next.title} | |
| 190 | + </p> | |
| 191 | + </Link> | |
| 192 | + )} | |
| 193 | + </nav> | |
| 194 | + </Reveal> | |
| 195 | + )} | |
| 196 | + </article> | |
| 197 | + ); | |
| 198 | +} | |
added
app/blog/page.tsx
+109 −0
@@ -0,0 +1,109 @@ | ||
| 1 | +/* | |
| 2 | + page.tsx | |
| 3 | + spboucher.ai Web | |
| 4 | + Author: Simon-Pierre Boucher | |
| 5 | + Mail: contact@spboucher.ai | |
| 6 | +*/ | |
| 7 | + | |
| 8 | +import type { Metadata } from "next"; | |
| 9 | +import Image from "next/image"; | |
| 10 | +import Link from "next/link"; | |
| 11 | +import { ArrowRight, CalendarDays, Clock, PenTool } from "lucide-react"; | |
| 12 | + | |
| 13 | +import { Badge } from "@/components/ui/badge"; | |
| 14 | +import { Card, CardContent, CardHeader } from "@/components/ui/card"; | |
| 15 | +import { Reveal } from "@/components/reveal"; | |
| 16 | +import { SectionHeading } from "@/components/section-heading"; | |
| 17 | +import { getBlogPosts } from "@/lib/blog"; | |
| 18 | + | |
| 19 | +export const metadata: Metadata = { | |
| 20 | + title: "Blog — Essays on AI & Economics", | |
| 21 | + description: | |
| 22 | + "Essays by Simon-Pierre Boucher on the economics of artificial intelligence: the collapsing cost of intelligence, the ownership of AI-generated wealth, and the data limits of the singularity.", | |
| 23 | +}; | |
| 24 | + | |
| 25 | +export default function BlogPage() { | |
| 26 | + const posts = getBlogPosts(); | |
| 27 | + | |
| 28 | + return ( | |
| 29 | + <div className="mx-auto max-w-5xl px-4 py-16 sm:px-6"> | |
| 30 | + <Reveal> | |
| 31 | + <SectionHeading | |
| 32 | + as="h1" | |
| 33 | + eyebrow="Writing" | |
| 34 | + title="Blog" | |
| 35 | + icon={PenTool} | |
| 36 | + description="Essays on the economics of artificial intelligence — intelligence as an industrial input, the ownership of machine-generated wealth, and the informational limits of recursive self-improvement." | |
| 37 | + /> | |
| 38 | + </Reveal> | |
| 39 | + | |
| 40 | + <div className="mt-10 space-y-6"> | |
| 41 | + {posts.map((post, i) => ( | |
| 42 | + <Reveal key={post.slug} delay={i * 0.08}> | |
| 43 | + <Link href={`/blog/${post.slug}`} className="group block"> | |
| 44 | + <Card className="relative overflow-hidden hover:-translate-y-1 hover:border-primary/25 hover:shadow-[var(--shadow-lift)]"> | |
| 45 | + <span | |
| 46 | + aria-hidden="true" | |
| 47 | + className="pointer-events-none absolute inset-y-6 left-0 w-1 rounded-r-full bg-gradient-to-b from-primary/70 to-primary/10 opacity-0 transition-opacity duration-300 group-hover:opacity-100" | |
| 48 | + /> | |
| 49 | + <CardHeader className="gap-3"> | |
| 50 | + <div className="flex flex-wrap items-center gap-x-4 gap-y-2 text-xs font-medium text-muted-foreground"> | |
| 51 | + <span className="inline-flex items-center gap-1.5"> | |
| 52 | + <CalendarDays | |
| 53 | + className="h-3.5 w-3.5 text-primary/70" | |
| 54 | + aria-hidden="true" | |
| 55 | + /> | |
| 56 | + {post.dateLabel} | |
| 57 | + </span> | |
| 58 | + <span className="inline-flex items-center gap-1.5"> | |
| 59 | + <Clock | |
| 60 | + className="h-3.5 w-3.5 text-primary/70" | |
| 61 | + aria-hidden="true" | |
| 62 | + /> | |
| 63 | + {post.readingMinutes} min read | |
| 64 | + </span> | |
| 65 | + <span className="flex flex-wrap gap-1.5"> | |
| 66 | + {post.tags.map((tag) => ( | |
| 67 | + <Badge key={tag} variant="outline"> | |
| 68 | + {tag} | |
| 69 | + </Badge> | |
| 70 | + ))} | |
| 71 | + </span> | |
| 72 | + </div> | |
| 73 | + <h2 className="font-display text-xl font-bold leading-snug tracking-tight transition-colors group-hover:text-primary sm:text-2xl"> | |
| 74 | + {post.title} | |
| 75 | + </h2> | |
| 76 | + </CardHeader> | |
| 77 | + <CardContent className="flex flex-col gap-5"> | |
| 78 | + <p className="max-w-3xl text-sm leading-relaxed text-muted-foreground"> | |
| 79 | + {post.excerpt} | |
| 80 | + </p> | |
| 81 | + <div className="flex items-center justify-between gap-4"> | |
| 82 | + <span className="inline-flex items-center gap-2 text-xs text-muted-foreground"> | |
| 83 | + <Image | |
| 84 | + src="/profile/simon-pierre-boucher.jpeg" | |
| 85 | + alt="" | |
| 86 | + aria-hidden="true" | |
| 87 | + width={48} | |
| 88 | + height={48} | |
| 89 | + className="h-6 w-6 rounded-full object-cover object-top ring-1 ring-border/60" | |
| 90 | + /> | |
| 91 | + Simon-Pierre Boucher | |
| 92 | + </span> | |
| 93 | + <span className="inline-flex items-center gap-1 text-sm font-medium text-primary"> | |
| 94 | + Read essay | |
| 95 | + <ArrowRight | |
| 96 | + className="h-4 w-4 transition-transform duration-300 group-hover:translate-x-1" | |
| 97 | + aria-hidden="true" | |
| 98 | + /> | |
| 99 | + </span> | |
| 100 | + </div> | |
| 101 | + </CardContent> | |
| 102 | + </Card> | |
| 103 | + </Link> | |
| 104 | + </Reveal> | |
| 105 | + ))} | |
| 106 | + </div> | |
| 107 | + </div> | |
| 108 | + ); | |
| 109 | +} | |
modified
app/cv/page.tsx
+25 −0
@@ -9,6 +9,7 @@ import type { Metadata } from "next"; | ||
| 9 | 9 | import { |
| 10 | 10 | AtSign, |
| 11 | 11 | Briefcase, |
| 12 | + Download, | |
| 12 | 13 | Github, |
| 13 | 14 | GraduationCap, |
| 14 | 15 | Languages, |
@@ -17,6 +18,7 @@ import { | ||
| 17 | 18 | } from "lucide-react"; |
| 18 | 19 | |
| 19 | 20 | import { Badge } from "@/components/ui/badge"; |
| 21 | +import { buttonVariants } from "@/components/ui/button"; | |
| 20 | 22 | import { |
| 21 | 23 | Card, |
| 22 | 24 | CardContent, |
@@ -83,6 +85,29 @@ export default function CvPage() { | ||
| 83 | 85 | /> |
| 84 | 86 | </Reveal> |
| 85 | 87 | |
| 88 | + {/* Download */} | |
| 89 | + <Reveal delay={0.08} className="mt-8"> | |
| 90 | + <div className="flex flex-col gap-5 border-y-2 border-foreground bg-card/50 p-6 sm:flex-row sm:items-center sm:justify-between sm:p-7"> | |
| 91 | + <div className="min-w-0"> | |
| 92 | + <h2 className="font-display text-base font-semibold tracking-tight"> | |
| 93 | + Full Curriculum Vitae | |
| 94 | + </h2> | |
| 95 | + <p className="mt-1.5 max-w-md text-sm leading-relaxed text-muted-foreground"> | |
| 96 | + Complete 4-page PDF — positions, education, research & working | |
| 97 | + papers, detailed teaching, applications, and skills. | |
| 98 | + </p> | |
| 99 | + </div> | |
| 100 | + <a | |
| 101 | + href="/cv/Simon-Pierre-Boucher-CV.pdf" | |
| 102 | + download | |
| 103 | + className={`${buttonVariants()} shrink-0`} | |
| 104 | + > | |
| 105 | + <Download aria-hidden="true" /> | |
| 106 | + Download CV (PDF) | |
| 107 | + </a> | |
| 108 | + </div> | |
| 109 | + </Reveal> | |
| 110 | + | |
| 86 | 111 | {/* Contact */} |
| 87 | 112 | <section className="mt-12"> |
| 88 | 113 | <Reveal> |
modified
app/globals.css
+75 −39
@@ -9,42 +9,43 @@ | ||
| 9 | 9 | |
| 10 | 10 | @custom-variant dark (&:where(.dark, .dark *)); |
| 11 | 11 | |
| 12 | +/* "Quantitative editorial" palette — warm paper, ink, one indigo accent. */ | |
| 12 | 13 | :root { |
| 13 | − --background: oklch(0.993 0.002 247.839); | |
| 14 | − --foreground: oklch(0.24 0.035 265.755); | |
| 15 | − --card: oklch(1 0 0); | |
| 16 | − --card-foreground: oklch(0.24 0.035 265.755); | |
| 17 | − --muted: oklch(0.972 0.006 247.896); | |
| 18 | − --muted-foreground: oklch(0.5 0.035 257.281); | |
| 19 | − --primary: oklch(0.54 0.19 264.376); | |
| 20 | − --primary-foreground: oklch(0.99 0.003 247.858); | |
| 21 | − --secondary: oklch(0.955 0.01 255.508); | |
| 22 | − --secondary-foreground: oklch(0.3 0.04 265.755); | |
| 23 | − --accent: oklch(0.955 0.012 262); | |
| 24 | − --accent-foreground: oklch(0.3 0.04 265.755); | |
| 25 | − --border: oklch(0.915 0.012 252.894); | |
| 26 | − --ring: oklch(0.54 0.19 264.376); | |
| 27 | − --aura-a: oklch(0.54 0.19 264.376 / 8%); | |
| 28 | − --aura-b: oklch(0.72 0.12 200 / 6%); | |
| 14 | + --background: oklch(0.972 0.006 85); | |
| 15 | + --foreground: oklch(0.245 0.016 75); | |
| 16 | + --card: oklch(0.993 0.0035 85); | |
| 17 | + --card-foreground: oklch(0.245 0.016 75); | |
| 18 | + --muted: oklch(0.944 0.008 85); | |
| 19 | + --muted-foreground: oklch(0.475 0.022 78); | |
| 20 | + --primary: oklch(0.44 0.155 268); | |
| 21 | + --primary-foreground: oklch(0.985 0.004 85); | |
| 22 | + --secondary: oklch(0.933 0.01 85); | |
| 23 | + --secondary-foreground: oklch(0.3 0.02 75); | |
| 24 | + --accent: oklch(0.928 0.012 85); | |
| 25 | + --accent-foreground: oklch(0.3 0.02 75); | |
| 26 | + --border: oklch(0.878 0.014 82); | |
| 27 | + --ring: oklch(0.44 0.155 268); | |
| 28 | + --aura-a: oklch(0.44 0.155 268 / 5%); | |
| 29 | + --aura-b: oklch(0.62 0.09 60 / 5%); | |
| 29 | 30 | } |
| 30 | 31 | |
| 31 | 32 | .dark { |
| 32 | − --background: oklch(0.165 0.02 265.755); | |
| 33 | − --foreground: oklch(0.93 0.01 255.508); | |
| 34 | − --card: oklch(0.205 0.024 265.755); | |
| 35 | − --card-foreground: oklch(0.93 0.01 255.508); | |
| 36 | − --muted: oklch(0.245 0.026 262.881); | |
| 37 | − --muted-foreground: oklch(0.71 0.035 256.788); | |
| 38 | − --primary: oklch(0.72 0.15 258); | |
| 39 | − --primary-foreground: oklch(0.165 0.02 265.755); | |
| 40 | − --secondary: oklch(0.26 0.033 260.031); | |
| 41 | − --secondary-foreground: oklch(0.93 0.01 255.508); | |
| 42 | − --accent: oklch(0.27 0.035 260.031); | |
| 43 | − --accent-foreground: oklch(0.93 0.01 255.508); | |
| 44 | − --border: oklch(0.29 0.03 262.881); | |
| 45 | − --ring: oklch(0.72 0.15 258); | |
| 46 | − --aura-a: oklch(0.72 0.15 258 / 9%); | |
| 47 | − --aura-b: oklch(0.65 0.12 200 / 5%); | |
| 33 | + --background: oklch(0.185 0.008 75); | |
| 34 | + --foreground: oklch(0.925 0.01 85); | |
| 35 | + --card: oklch(0.223 0.01 78); | |
| 36 | + --card-foreground: oklch(0.925 0.01 85); | |
| 37 | + --muted: oklch(0.258 0.012 78); | |
| 38 | + --muted-foreground: oklch(0.7 0.018 82); | |
| 39 | + --primary: oklch(0.73 0.115 268); | |
| 40 | + --primary-foreground: oklch(0.185 0.008 75); | |
| 41 | + --secondary: oklch(0.273 0.014 78); | |
| 42 | + --secondary-foreground: oklch(0.925 0.01 85); | |
| 43 | + --accent: oklch(0.285 0.014 78); | |
| 44 | + --accent-foreground: oklch(0.925 0.01 85); | |
| 45 | + --border: oklch(0.302 0.014 78); | |
| 46 | + --ring: oklch(0.73 0.115 268); | |
| 47 | + --aura-a: oklch(0.73 0.115 268 / 7%); | |
| 48 | + --aura-b: oklch(0.65 0.08 60 / 4%); | |
| 48 | 49 | } |
| 49 | 50 | |
| 50 | 51 | @theme inline { |
@@ -63,13 +64,14 @@ | ||
| 63 | 64 | --color-border: var(--border); |
| 64 | 65 | --color-ring: var(--ring); |
| 65 | 66 | --font-sans: var(--font-inter), ui-sans-serif, system-ui, sans-serif; |
| 67 | + --font-display: var(--font-fraunces), "Iowan Old Style", Georgia, serif; | |
| 68 | + --font-mono: var(--font-plex-mono), ui-monospace, "SF Mono", Menlo, monospace; | |
| 66 | 69 | --radius-4xl: 1.75rem; |
| 67 | − --shadow-soft: 0 1px 2px oklch(0.24 0.035 265.755 / 3%), | |
| 68 | − 0 10px 30px -12px oklch(0.24 0.035 265.755 / 7%); | |
| 69 | − --shadow-lift: 0 2px 4px oklch(0.24 0.035 265.755 / 4%), | |
| 70 | − 0 20px 48px -16px oklch(0.24 0.035 265.755 / 16%); | |
| 71 | − --shadow-glow: 0 0 0 1px oklch(0.54 0.19 264.376 / 6%), | |
| 72 | − 0 12px 40px -8px oklch(0.54 0.19 264.376 / 18%); | |
| 70 | + --shadow-soft: 0 1px 2px oklch(0.245 0.016 75 / 4%); | |
| 71 | + --shadow-lift: 0 1px 2px oklch(0.245 0.016 75 / 5%), | |
| 72 | + 0 12px 28px -14px oklch(0.245 0.016 75 / 14%); | |
| 73 | + --shadow-glow: 0 0 0 1px oklch(0.44 0.155 268 / 8%), | |
| 74 | + 0 10px 32px -10px oklch(0.44 0.155 268 / 14%); | |
| 73 | 75 | } |
| 74 | 76 | |
| 75 | 77 | html { |
@@ -97,8 +99,42 @@ body::before { | ||
| 97 | 99 | radial-gradient(60rem 32rem at 50% 110%, var(--aura-b), transparent 60%); |
| 98 | 100 | } |
| 99 | 101 | |
| 102 | +/* Faint graph-paper grid above the auras — the econometrician's backdrop — | |
| 103 | + faded out toward the lower half of the viewport so content stays quiet. */ | |
| 104 | +body::after { | |
| 105 | + content: ""; | |
| 106 | + position: fixed; | |
| 107 | + inset: 0; | |
| 108 | + z-index: -9; | |
| 109 | + pointer-events: none; | |
| 110 | + background-image: | |
| 111 | + linear-gradient(to right, oklch(0.44 0.155 268 / 6%) 1px, transparent 1px), | |
| 112 | + linear-gradient(to bottom, oklch(0.44 0.155 268 / 6%) 1px, transparent 1px); | |
| 113 | + background-size: 34px 34px; | |
| 114 | + mask-image: radial-gradient(64rem 36rem at 72% -10%, black 12%, transparent 70%); | |
| 115 | +} | |
| 116 | + | |
| 117 | +.dark body::after { | |
| 118 | + background-image: | |
| 119 | + linear-gradient(to right, oklch(0.73 0.115 268 / 7%) 1px, transparent 1px), | |
| 120 | + linear-gradient(to bottom, oklch(0.73 0.115 268 / 7%) 1px, transparent 1px); | |
| 121 | +} | |
| 122 | + | |
| 123 | +/* Subtle, theme-aware scrollbar. */ | |
| 124 | +::-webkit-scrollbar { | |
| 125 | + width: 10px; | |
| 126 | +} | |
| 127 | +::-webkit-scrollbar-thumb { | |
| 128 | + border-radius: 9999px; | |
| 129 | + border: 3px solid var(--background); | |
| 130 | + background-color: color-mix(in oklch, var(--muted-foreground) 35%, transparent); | |
| 131 | +} | |
| 132 | +::-webkit-scrollbar-thumb:hover { | |
| 133 | + background-color: color-mix(in oklch, var(--muted-foreground) 55%, transparent); | |
| 134 | +} | |
| 135 | + | |
| 100 | 136 | ::selection { |
| 101 | − background: oklch(0.54 0.19 264.376 / 18%); | |
| 137 | + background: oklch(0.44 0.155 268 / 16%); | |
| 102 | 138 | } |
| 103 | 139 | |
| 104 | 140 | /* Framer Motion reveals bake opacity:0 into SSR inline styles; guarantee |
modified
app/layout.tsx
+17 −2
@@ -6,7 +6,7 @@ | ||
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | 8 | import type { Metadata } from "next"; |
| 9 | −import { Inter } from "next/font/google"; | |
| 9 | +import { Fraunces, IBM_Plex_Mono, Inter } from "next/font/google"; | |
| 10 | 10 | |
| 11 | 11 | import { SiteHeader } from "@/components/site-header"; |
| 12 | 12 | import { SiteFooter } from "@/components/site-footer"; |
@@ -18,6 +18,19 @@ const inter = Inter({ | ||
| 18 | 18 | variable: "--font-inter", |
| 19 | 19 | }); |
| 20 | 20 | |
| 21 | +const fraunces = Fraunces({ | |
| 22 | + subsets: ["latin"], | |
| 23 | + style: ["normal", "italic"], | |
| 24 | + axes: ["opsz"], | |
| 25 | + variable: "--font-fraunces", | |
| 26 | +}); | |
| 27 | + | |
| 28 | +const plexMono = IBM_Plex_Mono({ | |
| 29 | + subsets: ["latin"], | |
| 30 | + weight: ["400", "500", "600"], | |
| 31 | + variable: "--font-plex-mono", | |
| 32 | +}); | |
| 33 | + | |
| 21 | 34 | export const metadata: Metadata = { |
| 22 | 35 | metadataBase: new URL("https://www.spboucher.ai"), |
| 23 | 36 | title: { |
@@ -61,7 +74,9 @@ export default function RootLayout({ | ||
| 61 | 74 | <style>{`[data-reveal]{opacity:1!important;transform:none!important}`}</style> |
| 62 | 75 | </noscript> |
| 63 | 76 | </head> |
| 64 | − <body className={`${inter.variable} flex min-h-screen flex-col antialiased`}> | |
| 77 | + <body | |
| 78 | + className={`${inter.variable} ${fraunces.variable} ${plexMono.variable} flex min-h-screen flex-col antialiased`} | |
| 79 | + > | |
| 65 | 80 | <SiteHeader /> |
| 66 | 81 | <main className="flex-1">{children}</main> |
| 67 | 82 | <SiteFooter /> |
modified
app/page.tsx
+108 −78
@@ -5,6 +5,7 @@ | ||
| 5 | 5 | Mail: contact@spboucher.ai |
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | +import Image from "next/image"; | |
| 8 | 9 | import Link from "next/link"; |
| 9 | 10 | import { |
| 10 | 11 | AppWindow, |
@@ -13,6 +14,7 @@ import { | ||
| 13 | 14 | Github, |
| 14 | 15 | GraduationCap, |
| 15 | 16 | Mail, |
| 17 | + MapPin, | |
| 16 | 18 | ScrollText, |
| 17 | 19 | Sparkles, |
| 18 | 20 | UserRound, |
@@ -35,7 +37,7 @@ import { cn } from "@/lib/utils"; | ||
| 35 | 37 | |
| 36 | 38 | const stats = [ |
| 37 | 39 | { icon: BookOpenCheck, value: "1", label: "Peer-reviewed publication" }, |
| 38 | − { icon: ScrollText, value: "8", label: "Working papers" }, | |
| 40 | + { icon: ScrollText, value: "9", label: "Working papers" }, | |
| 39 | 41 | { icon: AppWindow, value: "9", label: "Native macOS apps" }, |
| 40 | 42 | { icon: Github, value: "30+", label: "Open-source repositories" }, |
| 41 | 43 | ]; |
@@ -133,86 +135,114 @@ export default function HomePage() { | ||
| 133 | 135 | return ( |
| 134 | 136 | <div className="mx-auto max-w-5xl px-4 sm:px-6"> |
| 135 | 137 | {/* Hero */} |
| 136 | − <section className="relative py-20 sm:py-28"> | |
| 137 | − <div | |
| 138 | − aria-hidden="true" | |
| 139 | − className="pointer-events-none absolute inset-0 -z-10 overflow-visible" | |
| 140 | − > | |
| 141 | − <div className="absolute -top-24 right-0 h-72 w-72 rounded-full bg-primary/10 blur-3xl" /> | |
| 142 | − <div className="absolute top-20 -left-20 h-64 w-64 rounded-full bg-accent blur-3xl dark:bg-primary/5" /> | |
| 143 | − </div> | |
| 144 | − <Reveal> | |
| 145 | − <p className="inline-flex items-center gap-2 rounded-full border border-primary/20 bg-primary/5 px-4 py-1.5 text-sm font-medium text-primary shadow-[var(--shadow-soft)] backdrop-blur"> | |
| 146 | − <Sparkles className="h-3.5 w-3.5" aria-hidden="true" /> | |
| 147 | − Professor — Department of Administrative Sciences | |
| 148 | − </p> | |
| 149 | − <h1 className="mt-6 bg-gradient-to-br from-foreground via-foreground to-foreground/60 bg-clip-text text-4xl font-bold tracking-tight text-transparent sm:text-6xl"> | |
| 150 | − Simon-Pierre Boucher | |
| 151 | − </h1> | |
| 152 | − <p className="mt-5 max-w-2xl text-lg text-muted-foreground sm:text-xl"> | |
| 153 | − Financial econometrics researcher and macOS/AI developer. | |
| 154 | − </p> | |
| 155 | − <p className="mt-2 text-sm text-muted-foreground"> | |
| 156 | − Université du Québec en Outaouais (UQO), Gatineau — Pavillon | |
| 157 | − Alexandre-Taché · Bilingual — French and English | |
| 158 | − </p> | |
| 159 | − <div className="mt-8 flex flex-wrap gap-3"> | |
| 160 | − <Link href="/research" className={cn(buttonVariants())}> | |
| 161 | − View Research | |
| 162 | − <ArrowRight aria-hidden="true" /> | |
| 163 | − </Link> | |
| 164 | − <Link | |
| 165 | − href="/apps" | |
| 166 | − className={cn(buttonVariants({ variant: "secondary" }))} | |
| 167 | − > | |
| 168 | − Apps & Open Source | |
| 169 | − </Link> | |
| 170 | − <a | |
| 171 | − href="https://github.com/spboucher-ai" | |
| 172 | − target="_blank" | |
| 173 | − rel="noopener noreferrer" | |
| 174 | − className={cn(buttonVariants({ variant: "outline" }))} | |
| 175 | − > | |
| 176 | − <Github aria-hidden="true" /> | |
| 177 | − GitHub | |
| 178 | − </a> | |
| 179 | − </div> | |
| 180 | − <div className="mt-6 flex flex-col gap-1 text-sm text-muted-foreground sm:flex-row sm:gap-6"> | |
| 181 | − <a | |
| 182 | − href="mailto:simon-pierre.boucher@uqo.ca" | |
| 183 | − className="inline-flex items-center gap-2 transition-colors hover:text-foreground" | |
| 184 | − > | |
| 185 | − <Mail className="h-4 w-4" aria-hidden="true" /> | |
| 186 | − simon-pierre.boucher@uqo.ca | |
| 187 | − </a> | |
| 188 | − <a | |
| 189 | − href="mailto:contact@spboucher.ai" | |
| 190 | − className="inline-flex items-center gap-2 transition-colors hover:text-foreground" | |
| 191 | − > | |
| 192 | − <Mail className="h-4 w-4" aria-hidden="true" /> | |
| 193 | − contact@spboucher.ai | |
| 194 | − </a> | |
| 195 | − </div> | |
| 196 | − </Reveal> | |
| 197 | − | |
| 198 | − {/* Stats */} | |
| 199 | − <Reveal delay={0.15} className="mt-14"> | |
| 200 | − <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4"> | |
| 201 | − {stats.map((stat) => ( | |
| 202 | − <div | |
| 203 | − key={stat.label} | |
| 204 | − className="flex items-center gap-4 rounded-[1.75rem] border border-border/60 bg-card/70 p-5 shadow-[var(--shadow-soft)] backdrop-blur transition-all duration-300 hover:-translate-y-1 hover:shadow-[var(--shadow-lift)]" | |
| 138 | + <section className="relative py-16 sm:py-24"> | |
| 139 | + <div className="grid items-center gap-12 lg:grid-cols-[1fr_auto]"> | |
| 140 | + <Reveal> | |
| 141 | + <p className="flex items-center gap-3 font-mono text-[0.7rem] font-medium uppercase tracking-[0.22em] text-primary"> | |
| 142 | + <span aria-hidden="true" className="h-px w-8 bg-primary/60" /> | |
| 143 | + Professor · Dept. of Administrative Sciences · UQO | |
| 144 | + </p> | |
| 145 | + <h1 className="mt-5 font-display text-[2.75rem] font-semibold leading-[1.05] tracking-tight sm:text-7xl"> | |
| 146 | + Simon-Pierre | |
| 147 | + <br /> | |
| 148 | + Boucher | |
| 149 | + </h1> | |
| 150 | + <p className="mt-6 max-w-2xl text-lg text-muted-foreground sm:text-xl"> | |
| 151 | + Financial econometrics researcher and{" "} | |
| 152 | + <em className="font-display font-medium not-italic text-foreground [font-style:italic]"> | |
| 153 | + macOS/AI developer | |
| 154 | + </em> | |
| 155 | + . | |
| 156 | + </p> | |
| 157 | + <p className="mt-3 inline-flex items-start gap-2 text-sm text-muted-foreground"> | |
| 158 | + <MapPin | |
| 159 | + className="mt-0.5 h-4 w-4 shrink-0 text-primary/70" | |
| 160 | + aria-hidden="true" | |
| 161 | + /> | |
| 162 | + Université du Québec en Outaouais (UQO), Gatineau — Pavillon | |
| 163 | + Alexandre-Taché · Bilingual — French and English | |
| 164 | + </p> | |
| 165 | + <div className="mt-8 flex flex-wrap gap-3"> | |
| 166 | + <Link href="/research" className={cn(buttonVariants())}> | |
| 167 | + View Research | |
| 168 | + <ArrowRight aria-hidden="true" /> | |
| 169 | + </Link> | |
| 170 | + <Link | |
| 171 | + href="/apps" | |
| 172 | + className={cn(buttonVariants({ variant: "secondary" }))} | |
| 173 | + > | |
| 174 | + Apps & Open Source | |
| 175 | + </Link> | |
| 176 | + <a | |
| 177 | + href="https://github.com/spboucher-ai" | |
| 178 | + target="_blank" | |
| 179 | + rel="noopener noreferrer" | |
| 180 | + className={cn(buttonVariants({ variant: "outline" }))} | |
| 181 | + > | |
| 182 | + <Github aria-hidden="true" /> | |
| 183 | + GitHub | |
| 184 | + </a> | |
| 185 | + </div> | |
| 186 | + <div className="mt-6 flex flex-col gap-1 text-sm text-muted-foreground sm:flex-row sm:gap-6"> | |
| 187 | + <a | |
| 188 | + href="mailto:simon-pierre.boucher@uqo.ca" | |
| 189 | + className="inline-flex items-center gap-2 transition-colors hover:text-foreground" | |
| 190 | + > | |
| 191 | + <Mail className="h-4 w-4" aria-hidden="true" /> | |
| 192 | + simon-pierre.boucher@uqo.ca | |
| 193 | + </a> | |
| 194 | + <a | |
| 195 | + href="mailto:contact@spboucher.ai" | |
| 196 | + className="inline-flex items-center gap-2 transition-colors hover:text-foreground" | |
| 205 | 197 | > |
| 206 | − <IconTile icon={stat.icon} /> | |
| 207 | − <div className="min-w-0"> | |
| 208 | − <p className="text-2xl font-bold tracking-tight"> | |
| 209 | − {stat.value} | |
| 210 | − </p> | |
| 211 | − <p className="text-xs leading-snug text-muted-foreground"> | |
| 212 | − {stat.label} | |
| 213 | − </p> | |
| 198 | + <Mail className="h-4 w-4" aria-hidden="true" /> | |
| 199 | + contact@spboucher.ai | |
| 200 | + </a> | |
| 201 | + </div> | |
| 202 | + </Reveal> | |
| 203 | + | |
| 204 | + {/* Portrait — mounted like a plate in a monograph */} | |
| 205 | + <Reveal delay={0.12} className="mx-auto w-60 sm:w-72 lg:w-80"> | |
| 206 | + <figure> | |
| 207 | + <div className="relative"> | |
| 208 | + <div | |
| 209 | + aria-hidden="true" | |
| 210 | + className="absolute -bottom-2.5 -right-2.5 h-full w-full border border-primary/25 bg-[image:linear-gradient(to_right,oklch(0.44_0.155_268_/_10%)_1px,transparent_1px),linear-gradient(to_bottom,oklch(0.44_0.155_268_/_10%)_1px,transparent_1px)] bg-[size:8px_8px]" | |
| 211 | + /> | |
| 212 | + <div className="relative border border-foreground/80 bg-card p-1.5 shadow-[var(--shadow-lift)]"> | |
| 213 | + <Image | |
| 214 | + src="/profile/simon-pierre-boucher.jpeg" | |
| 215 | + alt="Portrait of Simon-Pierre Boucher" | |
| 216 | + width={880} | |
| 217 | + height={856} | |
| 218 | + priority | |
| 219 | + className="aspect-[5/6] w-full object-cover object-top" | |
| 220 | + /> | |
| 214 | 221 | </div> |
| 215 | 222 | </div> |
| 223 | + <figcaption className="mt-4 flex items-baseline justify-between font-mono text-[0.66rem] uppercase tracking-[0.14em] text-muted-foreground"> | |
| 224 | + <span>Fig. 1 — S.-P. Boucher</span> | |
| 225 | + <span>Gatineau, QC</span> | |
| 226 | + </figcaption> | |
| 227 | + </figure> | |
| 228 | + </Reveal> | |
| 229 | + </div> | |
| 230 | + | |
| 231 | + {/* Stats — annual-report rules */} | |
| 232 | + <Reveal delay={0.15} className="mt-16"> | |
| 233 | + <div className="grid grid-cols-2 gap-x-8 gap-y-8 lg:grid-cols-4"> | |
| 234 | + {stats.map((stat, i) => ( | |
| 235 | + <div key={stat.label} className="border-t-2 border-foreground pt-3"> | |
| 236 | + <p className="font-mono text-[0.62rem] uppercase tracking-[0.18em] text-muted-foreground/70"> | |
| 237 | + {String(i + 1).padStart(2, "0")} | |
| 238 | + </p> | |
| 239 | + <p className="mt-1 font-display text-4xl font-semibold tracking-tight"> | |
| 240 | + {stat.value} | |
| 241 | + </p> | |
| 242 | + <p className="mt-1.5 font-mono text-[0.68rem] uppercase leading-relaxed tracking-[0.12em] text-muted-foreground"> | |
| 243 | + {stat.label} | |
| 244 | + </p> | |
| 245 | + </div> | |
| 216 | 246 | ))} |
| 217 | 247 | </div> |
| 218 | 248 | </Reveal> |
added
app/research/[slug]/page.tsx
+308 −0
@@ -0,0 +1,308 @@ | ||
| 1 | +/* | |
| 2 | + page.tsx | |
| 3 | + spboucher.ai Web | |
| 4 | + Author: Simon-Pierre Boucher | |
| 5 | + Mail: contact@spboucher.ai | |
| 6 | +*/ | |
| 7 | + | |
| 8 | +import type { Metadata } from "next"; | |
| 9 | +import Link from "next/link"; | |
| 10 | +import { notFound } from "next/navigation"; | |
| 11 | +import { | |
| 12 | + ArrowLeft, | |
| 13 | + Braces, | |
| 14 | + CheckCircle2, | |
| 15 | + Database, | |
| 16 | + FileText, | |
| 17 | + FlaskConical, | |
| 18 | + Github, | |
| 19 | + ScrollText, | |
| 20 | +} from "lucide-react"; | |
| 21 | + | |
| 22 | +import { Badge } from "@/components/ui/badge"; | |
| 23 | +import { buttonVariants } from "@/components/ui/button"; | |
| 24 | +import { Reveal } from "@/components/reveal"; | |
| 25 | +import { SectionHeading } from "@/components/section-heading"; | |
| 26 | +import { getResearchPaper, allResearchPapers } from "@/lib/research"; | |
| 27 | +import { researchDetails } from "@/lib/research-details"; | |
| 28 | + | |
| 29 | +interface PageProps { | |
| 30 | + params: Promise<{ slug: string }>; | |
| 31 | +} | |
| 32 | + | |
| 33 | +export function generateStaticParams() { | |
| 34 | + return allResearchPapers | |
| 35 | + .filter((p) => researchDetails.some((d) => d.slug === p.slug)) | |
| 36 | + .map((p) => ({ slug: p.slug })); | |
| 37 | +} | |
| 38 | + | |
| 39 | +export async function generateMetadata({ | |
| 40 | + params, | |
| 41 | +}: PageProps): Promise<Metadata> { | |
| 42 | + const { slug } = await params; | |
| 43 | + const paper = getResearchPaper(slug); | |
| 44 | + const detail = researchDetails.find((d) => d.slug === slug); | |
| 45 | + if (!paper || !detail) return {}; | |
| 46 | + return { | |
| 47 | + title: `${paper.title} — ${paper.num}`, | |
| 48 | + description: detail.hero.subheadline, | |
| 49 | + }; | |
| 50 | +} | |
| 51 | + | |
| 52 | +export default async function ResearchDetailPage({ params }: PageProps) { | |
| 53 | + const { slug } = await params; | |
| 54 | + const paper = getResearchPaper(slug); | |
| 55 | + const detail = researchDetails.find((d) => d.slug === slug); | |
| 56 | + if (!paper || !detail) notFound(); | |
| 57 | + | |
| 58 | + return ( | |
| 59 | + <article className="mx-auto max-w-4xl px-4 py-16 sm:px-6"> | |
| 60 | + <Reveal> | |
| 61 | + <Link | |
| 62 | + href="/research" | |
| 63 | + className="inline-flex items-center gap-1.5 font-mono text-xs uppercase tracking-[0.12em] text-muted-foreground transition-colors hover:text-primary" | |
| 64 | + > | |
| 65 | + <ArrowLeft className="h-3.5 w-3.5" aria-hidden="true" /> | |
| 66 | + All research | |
| 67 | + </Link> | |
| 68 | + </Reveal> | |
| 69 | + | |
| 70 | + {/* Header */} | |
| 71 | + <Reveal className="mt-10"> | |
| 72 | + <header> | |
| 73 | + <p className="flex flex-wrap items-center gap-3 font-mono text-[0.68rem] font-medium uppercase tracking-[0.2em] text-primary"> | |
| 74 | + <span aria-hidden="true" className="h-px w-6 bg-primary/60" /> | |
| 75 | + {paper.num} | |
| 76 | + {paper.pages && ( | |
| 77 | + <span className="text-muted-foreground/80">· {paper.pages}</span> | |
| 78 | + )} | |
| 79 | + {paper.status && ( | |
| 80 | + <span className="text-muted-foreground/80">· {paper.status}</span> | |
| 81 | + )} | |
| 82 | + </p> | |
| 83 | + <h1 className="mt-4 font-display text-3xl font-semibold leading-tight tracking-tight sm:text-[2.6rem] sm:leading-[1.12]"> | |
| 84 | + {paper.title} | |
| 85 | + </h1> | |
| 86 | + <p className="mt-5 max-w-2xl font-display text-lg font-medium leading-snug tracking-tight text-foreground/85 [font-style:italic] sm:text-xl"> | |
| 87 | + {detail.hero.headline} | |
| 88 | + </p> | |
| 89 | + <p className="mt-3 max-w-2xl text-sm leading-relaxed text-muted-foreground"> | |
| 90 | + {detail.hero.subheadline} | |
| 91 | + </p> | |
| 92 | + <p className="mt-4 text-sm text-muted-foreground"> | |
| 93 | + Simon-Pierre Boucher —{" "} | |
| 94 | + <a | |
| 95 | + href="mailto:contact@spboucher.ai" | |
| 96 | + className="text-primary hover:underline" | |
| 97 | + > | |
| 98 | + contact@spboucher.ai | |
| 99 | + </a> | |
| 100 | + </p> | |
| 101 | + <div className="mt-6 flex flex-wrap gap-2"> | |
| 102 | + <a | |
| 103 | + href={paper.pdf} | |
| 104 | + target="_blank" | |
| 105 | + rel="noopener noreferrer" | |
| 106 | + className={buttonVariants({ size: "sm" })} | |
| 107 | + > | |
| 108 | + <FileText aria-hidden="true" /> | |
| 109 | + Paper PDF | |
| 110 | + </a> | |
| 111 | + {paper.repo && ( | |
| 112 | + <a | |
| 113 | + href={paper.repo} | |
| 114 | + target="_blank" | |
| 115 | + rel="noopener noreferrer" | |
| 116 | + className={buttonVariants({ variant: "outline", size: "sm" })} | |
| 117 | + > | |
| 118 | + <Github aria-hidden="true" /> | |
| 119 | + Code & data repository | |
| 120 | + </a> | |
| 121 | + )} | |
| 122 | + {paper.source && ( | |
| 123 | + <a | |
| 124 | + href={paper.source} | |
| 125 | + target="_blank" | |
| 126 | + rel="noopener noreferrer" | |
| 127 | + className={buttonVariants({ variant: "outline", size: "sm" })} | |
| 128 | + > | |
| 129 | + <Braces aria-hidden="true" /> | |
| 130 | + LaTeX source | |
| 131 | + </a> | |
| 132 | + )} | |
| 133 | + </div> | |
| 134 | + </header> | |
| 135 | + </Reveal> | |
| 136 | + | |
| 137 | + {/* Headline findings */} | |
| 138 | + {detail.findings.length > 0 && ( | |
| 139 | + <Reveal className="mt-14"> | |
| 140 | + <div className="grid grid-cols-2 gap-x-8 gap-y-8 sm:grid-cols-3"> | |
| 141 | + {detail.findings.map((f) => ( | |
| 142 | + <div key={f.label} className="border-t-2 border-foreground pt-3"> | |
| 143 | + <p className="font-display text-3xl font-semibold tracking-tight sm:text-4xl"> | |
| 144 | + {f.value} | |
| 145 | + </p> | |
| 146 | + <p className="mt-1.5 font-mono text-[0.66rem] uppercase leading-relaxed tracking-[0.12em] text-muted-foreground"> | |
| 147 | + {f.label} | |
| 148 | + </p> | |
| 149 | + </div> | |
| 150 | + ))} | |
| 151 | + </div> | |
| 152 | + </Reveal> | |
| 153 | + )} | |
| 154 | + | |
| 155 | + {/* Abstract */} | |
| 156 | + <section className="mt-14"> | |
| 157 | + <Reveal> | |
| 158 | + <SectionHeading title="Abstract" icon={ScrollText} /> | |
| 159 | + </Reveal> | |
| 160 | + <Reveal delay={0.06}> | |
| 161 | + <div className="mt-5 max-w-3xl space-y-4 border-l border-border pl-5 sm:pl-6"> | |
| 162 | + {detail.abstract.map((paragraph) => ( | |
| 163 | + <p | |
| 164 | + key={paragraph.slice(0, 48)} | |
| 165 | + className="text-sm leading-relaxed text-muted-foreground sm:text-[0.95rem]" | |
| 166 | + > | |
| 167 | + {paragraph} | |
| 168 | + </p> | |
| 169 | + ))} | |
| 170 | + </div> | |
| 171 | + </Reveal> | |
| 172 | + </section> | |
| 173 | + | |
| 174 | + {/* Contributions */} | |
| 175 | + {detail.contributions.length > 0 && ( | |
| 176 | + <section className="mt-14"> | |
| 177 | + <Reveal> | |
| 178 | + <SectionHeading title="Key Results" icon={FlaskConical} /> | |
| 179 | + </Reveal> | |
| 180 | + <ol className="mt-6 space-y-5"> | |
| 181 | + {detail.contributions.map((c, i) => ( | |
| 182 | + <Reveal key={c.title} delay={i * 0.05}> | |
| 183 | + <li className="flex items-start gap-4 border-b border-border/60 pb-5 last:border-0"> | |
| 184 | + <span | |
| 185 | + aria-hidden="true" | |
| 186 | + className="pt-0.5 font-mono text-sm font-semibold text-primary" | |
| 187 | + > | |
| 188 | + {String(i + 1).padStart(2, "0")} | |
| 189 | + </span> | |
| 190 | + <div className="min-w-0"> | |
| 191 | + <h3 className="font-display text-base font-semibold tracking-tight"> | |
| 192 | + {c.title} | |
| 193 | + </h3> | |
| 194 | + <p className="mt-1 max-w-2xl text-sm leading-relaxed text-muted-foreground"> | |
| 195 | + {c.description} | |
| 196 | + </p> | |
| 197 | + </div> | |
| 198 | + </li> | |
| 199 | + </Reveal> | |
| 200 | + ))} | |
| 201 | + </ol> | |
| 202 | + </section> | |
| 203 | + )} | |
| 204 | + | |
| 205 | + {/* Data & methodology */} | |
| 206 | + <div className="mt-14 grid gap-12 md:grid-cols-2 md:gap-10"> | |
| 207 | + {detail.data.length > 0 && ( | |
| 208 | + <section> | |
| 209 | + <Reveal> | |
| 210 | + <SectionHeading title="Data" icon={Database} /> | |
| 211 | + </Reveal> | |
| 212 | + <div className="mt-5 space-y-4"> | |
| 213 | + {detail.data.map((d, i) => ( | |
| 214 | + <Reveal key={d.title} delay={i * 0.05}> | |
| 215 | + <div className="border-l-2 border-primary/50 pl-4"> | |
| 216 | + <h3 className="text-sm font-semibold">{d.title}</h3> | |
| 217 | + <p className="mt-1 text-sm leading-relaxed text-muted-foreground"> | |
| 218 | + {d.description} | |
| 219 | + </p> | |
| 220 | + </div> | |
| 221 | + </Reveal> | |
| 222 | + ))} | |
| 223 | + </div> | |
| 224 | + </section> | |
| 225 | + )} | |
| 226 | + | |
| 227 | + {detail.methodology.length > 0 && ( | |
| 228 | + <section> | |
| 229 | + <Reveal> | |
| 230 | + <SectionHeading title="Methodology" icon={FlaskConical} /> | |
| 231 | + </Reveal> | |
| 232 | + <div className="mt-5 space-y-4"> | |
| 233 | + {detail.methodology.map((m, i) => ( | |
| 234 | + <Reveal key={m.title} delay={i * 0.05}> | |
| 235 | + <div className="border-l-2 border-border pl-4"> | |
| 236 | + <h3 className="text-sm font-semibold">{m.title}</h3> | |
| 237 | + <p className="mt-1 text-sm leading-relaxed text-muted-foreground"> | |
| 238 | + {m.description} | |
| 239 | + </p> | |
| 240 | + </div> | |
| 241 | + </Reveal> | |
| 242 | + ))} | |
| 243 | + </div> | |
| 244 | + </section> | |
| 245 | + )} | |
| 246 | + </div> | |
| 247 | + | |
| 248 | + {/* Reproducibility */} | |
| 249 | + {detail.reproducibility.length > 0 && ( | |
| 250 | + <section className="mt-14"> | |
| 251 | + <Reveal> | |
| 252 | + <SectionHeading title="Reproducibility" icon={Github} /> | |
| 253 | + </Reveal> | |
| 254 | + <ul className="mt-5 grid gap-3 sm:grid-cols-2"> | |
| 255 | + {detail.reproducibility.map((item, i) => ( | |
| 256 | + <Reveal key={item} delay={i * 0.05} className="h-full"> | |
| 257 | + <li className="flex h-full items-start gap-3 border border-border bg-card px-4 py-3.5 shadow-[var(--shadow-soft)]"> | |
| 258 | + <CheckCircle2 | |
| 259 | + className="mt-0.5 h-4 w-4 shrink-0 text-primary" | |
| 260 | + aria-hidden="true" | |
| 261 | + /> | |
| 262 | + <span className="text-sm leading-relaxed text-foreground/85"> | |
| 263 | + {item} | |
| 264 | + </span> | |
| 265 | + </li> | |
| 266 | + </Reveal> | |
| 267 | + ))} | |
| 268 | + </ul> | |
| 269 | + </section> | |
| 270 | + )} | |
| 271 | + | |
| 272 | + {/* Keywords + CTA */} | |
| 273 | + <Reveal className="mt-14"> | |
| 274 | + <footer className="border-t-2 border-foreground pt-6"> | |
| 275 | + <div className="flex flex-wrap gap-1.5"> | |
| 276 | + {detail.keywords.map((k) => ( | |
| 277 | + <Badge key={k} variant="outline"> | |
| 278 | + {k} | |
| 279 | + </Badge> | |
| 280 | + ))} | |
| 281 | + </div> | |
| 282 | + <div className="mt-6 flex flex-wrap gap-2"> | |
| 283 | + <a | |
| 284 | + href={paper.pdf} | |
| 285 | + target="_blank" | |
| 286 | + rel="noopener noreferrer" | |
| 287 | + className={buttonVariants({ size: "sm" })} | |
| 288 | + > | |
| 289 | + <FileText aria-hidden="true" /> | |
| 290 | + Read the paper (PDF) | |
| 291 | + </a> | |
| 292 | + {paper.repo && ( | |
| 293 | + <a | |
| 294 | + href={paper.repo} | |
| 295 | + target="_blank" | |
| 296 | + rel="noopener noreferrer" | |
| 297 | + className={buttonVariants({ variant: "outline", size: "sm" })} | |
| 298 | + > | |
| 299 | + <Github aria-hidden="true" /> | |
| 300 | + Explore the repository | |
| 301 | + </a> | |
| 302 | + )} | |
| 303 | + </div> | |
| 304 | + </footer> | |
| 305 | + </Reveal> | |
| 306 | + </article> | |
| 307 | + ); | |
| 308 | +} | |
modified
app/research/page.tsx
+27 −80
@@ -6,7 +6,9 @@ | ||
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | 8 | import type { Metadata } from "next"; |
| 9 | +import Link from "next/link"; | |
| 9 | 10 | import { |
| 11 | + ArrowUpRight, | |
| 10 | 12 | BookOpenCheck, |
| 11 | 13 | ExternalLink, |
| 12 | 14 | FileText, |
@@ -28,6 +30,7 @@ import { | ||
| 28 | 30 | import { PillLink } from "@/components/pill-link"; |
| 29 | 31 | import { Reveal } from "@/components/reveal"; |
| 30 | 32 | import { SectionHeading } from "@/components/section-heading"; |
| 33 | +import { thesisChapters, uqoWorkingPapers } from "@/lib/research"; | |
| 31 | 34 | |
| 32 | 35 | export const metadata: Metadata = { |
| 33 | 36 | title: "Research", |
@@ -35,55 +38,6 @@ export const metadata: Metadata = { | ||
| 35 | 38 | "Publications, working papers, and conference presentations by Simon-Pierre Boucher — financial econometrics, commodity markets, monetary policy, and high-frequency finance.", |
| 36 | 39 | }; |
| 37 | 40 | |
| 38 | −const uqoWorkingPapers = [ | |
| 39 | − { | |
| 40 | − num: "UQO Working Paper No. 2", | |
| 41 | − pages: "53 pages", | |
| 42 | − title: | |
| 43 | − "Decoding Real Estate Descriptions: Semantic Embeddings and Hedonic Pricing of Residential Properties in Quebec", | |
| 44 | − description: | |
| 45 | − "Adds sentence-transformer embeddings of listing descriptions to hedonic models of 17,087 Quebec houses — lifting adjusted R² from 0.452 to 0.511 beyond structural attributes alone.", | |
| 46 | − pdf: "/papers/wp2_uqo.pdf", | |
| 47 | − }, | |
| 48 | − { | |
| 49 | − num: "UQO Working Paper No. 3", | |
| 50 | − pages: "60 pages", | |
| 51 | − title: | |
| 52 | − "Hedonic Housing Price Models for the United States: A Multi-Method Comparison of Parametric, Quantile, and Machine Learning Approaches", | |
| 53 | − description: | |
| 54 | − "OLS, quantile regression, and gradient-boosting (XGBoost + SHAP) approaches compared on 788,842 Zillow listings covering all 50 states and DC.", | |
| 55 | − pdf: "https://github.com/spboucher-ai/wp3-hedonic-housing-us/blob/main/paper/main.pdf", | |
| 56 | − repo: "https://github.com/spboucher-ai/wp3-hedonic-housing-us", | |
| 57 | − }, | |
| 58 | − { | |
| 59 | − num: "UQO Working Paper No. 5", | |
| 60 | − pages: "51 pages", | |
| 61 | − title: | |
| 62 | − "Airbnb, Residential Rents, and Housing Market Pressure: A Hedonic and Spatial Econometric Analysis", | |
| 63 | − description: | |
| 64 | − "Hedonic, spatial, quantile, and machine-learning evidence from 8,303 Quebec rental listings and 3,456 Airbnb listings — each active Airbnb within 500 m is associated with roughly 0.4% higher asking rent.", | |
| 65 | − pdf: "/papers/wp5_uqo.pdf", | |
| 66 | − }, | |
| 67 | − { | |
| 68 | − num: "UQO Working Paper No. 7", | |
| 69 | − pages: "29 pages", | |
| 70 | − title: | |
| 71 | − "The Options-Implied Information Content for Cross-Asset Return and Volatility Prediction: Evidence from 3.8 Billion Option Contracts", | |
| 72 | − description: | |
| 73 | − "Options-implied moments forecast returns and volatility on a panel of 264,383 ticker-days (69 tickers, 2010–2025); a kurtosis long/short strategy delivers a Sharpe ratio of 2.33.", | |
| 74 | − pdf: "/papers/wp7_uqo.pdf", | |
| 75 | − }, | |
| 76 | − { | |
| 77 | − num: "UQO Working Paper No. 9", | |
| 78 | − pages: "26 pages", | |
| 79 | − title: | |
| 80 | − "A Grand Hedonic Model of the Canadian Housing Market: Decomposing the Value of Structure and Location", | |
| 81 | − description: | |
| 82 | − "140,931 MLS listings with 1,153 neighbourhood (FSA) fixed effects — location alone adds ~30 points of R² (46% → 77%), valuing held-out homes with a 15.8% median absolute error (OOS R² = 0.764).", | |
| 83 | − pdf: "/papers/wp9_uqo.pdf", | |
| 84 | − }, | |
| 85 | −]; | |
| 86 | − | |
| 87 | 41 | const workingPapers = [ |
| 88 | 42 | { |
| 89 | 43 | title: |
@@ -106,36 +60,6 @@ const workingPapers = [ | ||
| 106 | 60 | }, |
| 107 | 61 | ]; |
| 108 | 62 | |
| 109 | −const thesisChapters = [ | |
| 110 | − { | |
| 111 | − num: "Chapter 1", | |
| 112 | − title: | |
| 113 | − "Speculative Trading in Energy Markets: Evidence from Macroeconomic Surprises", | |
| 114 | − status: "Revised version for The Energy Journal", | |
| 115 | − pdf: "https://github.com/spboucher-ai/phd-thesis/blob/main/phd_chap1_20260731/main.pdf", | |
| 116 | − source: | |
| 117 | − "https://github.com/spboucher-ai/phd-thesis/tree/main/phd_chap1_20260731", | |
| 118 | − }, | |
| 119 | − { | |
| 120 | − num: "Chapter 2", | |
| 121 | − title: | |
| 122 | − "Seeing Through the ETF: Indicative NAV and Commodity Volatility Transmission", | |
| 123 | − status: "Submission version, Journal of Futures Markets", | |
| 124 | − pdf: "https://github.com/spboucher-ai/phd-thesis/blob/main/phd_chap2_20260731/main.pdf", | |
| 125 | − source: | |
| 126 | − "https://github.com/spboucher-ai/phd-thesis/tree/main/phd_chap2_20260731", | |
| 127 | − }, | |
| 128 | − { | |
| 129 | − num: "Chapter 3", | |
| 130 | − title: | |
| 131 | − "Returns and Volatility Around FOMC Announcements: A High-Frequency Analysis of Policy Tone and Novelty", | |
| 132 | − status: "Manuscript, 2026", | |
| 133 | − pdf: "https://github.com/spboucher-ai/phd-thesis/blob/main/PHD_chapitre3_theses_20260731/chapitre3.pdf", | |
| 134 | − source: | |
| 135 | − "https://github.com/spboucher-ai/phd-thesis/tree/main/PHD_chapitre3_theses_20260731", | |
| 136 | − }, | |
| 137 | −]; | |
| 138 | − | |
| 139 | 63 | const presentations = [ |
| 140 | 64 | { event: "CRREP Research Day", detail: "2022, 2023" }, |
| 141 | 65 | { |
@@ -176,6 +100,15 @@ export default function ResearchPage() { | ||
| 176 | 100 | Boucher, S.-P., Gagnon, M.-H., & Power, G. J. (2025) |
| 177 | 101 | </CardDescription> |
| 178 | 102 | </CardHeader> |
| 103 | + <CardContent> | |
| 104 | + <Link | |
| 105 | + href="/research/chapter-1" | |
| 106 | + className="inline-flex items-center gap-1.5 rounded-[0.5rem] bg-foreground px-3 py-1.5 text-xs font-medium text-background shadow-[var(--shadow-soft)] transition-all hover:-translate-y-px hover:bg-foreground/85" | |
| 107 | + > | |
| 108 | + <ArrowUpRight className="h-3.5 w-3.5" aria-hidden="true" /> | |
| 109 | + Details | |
| 110 | + </Link> | |
| 111 | + </CardContent> | |
| 179 | 112 | </Card> |
| 180 | 113 | </Reveal> |
| 181 | 114 | </section> |
@@ -220,10 +153,17 @@ export default function ResearchPage() { | ||
| 220 | 153 | </CardTitle> |
| 221 | 154 | </CardHeader> |
| 222 | 155 | <CardContent className="flex flex-wrap gap-2"> |
| 156 | + <Link | |
| 157 | + href={`/research/${chapter.slug}`} | |
| 158 | + className="inline-flex items-center gap-1.5 rounded-[0.5rem] bg-foreground px-3 py-1.5 text-xs font-medium text-background shadow-[var(--shadow-soft)] transition-all hover:-translate-y-px hover:bg-foreground/85" | |
| 159 | + > | |
| 160 | + <ArrowUpRight className="h-3.5 w-3.5" aria-hidden="true" /> | |
| 161 | + Details | |
| 162 | + </Link> | |
| 223 | 163 | <PillLink href={chapter.pdf} icon={FileText}> |
| 224 | 164 | Paper PDF |
| 225 | 165 | </PillLink> |
| 226 | − <PillLink href={chapter.source} icon={Github}> | |
| 166 | + <PillLink href={chapter.source!} icon={Github}> | |
| 227 | 167 | LaTeX source |
| 228 | 168 | </PillLink> |
| 229 | 169 | </CardContent> |
@@ -256,6 +196,13 @@ export default function ResearchPage() { | ||
| 256 | 196 | <CardDescription>{paper.description}</CardDescription> |
| 257 | 197 | </CardHeader> |
| 258 | 198 | <CardContent className="flex flex-wrap gap-2"> |
| 199 | + <Link | |
| 200 | + href={`/research/${paper.slug}`} | |
| 201 | + className="inline-flex items-center gap-1.5 rounded-[0.5rem] bg-foreground px-3 py-1.5 text-xs font-medium text-background shadow-[var(--shadow-soft)] transition-all hover:-translate-y-px hover:bg-foreground/85" | |
| 202 | + > | |
| 203 | + <ArrowUpRight className="h-3.5 w-3.5" aria-hidden="true" /> | |
| 204 | + Details | |
| 205 | + </Link> | |
| 259 | 206 | <PillLink href={paper.pdf} icon={FileText}> |
| 260 | 207 | Paper PDF |
| 261 | 208 | </PillLink> |
added
blog/blog1.txt
+368 −0
@@ -0,0 +1,368 @@ | ||
| 1 | +# What Happens When the Cost of Intelligence Approaches Zero? | |
| 2 | + | |
| 3 | +For most of economic history, intelligence has been expensive. | |
| 4 | + | |
| 5 | +Not intelligence in the abstract sense, but economically useful cognitive work: analyzing information, writing software, designing products, preparing contracts, conducting research, managing organizations, or making decisions. | |
| 6 | + | |
| 7 | +These activities required educated humans. | |
| 8 | + | |
| 9 | +Educated humans are scarce. They require years of training, have limited working hours, and cannot be replicated instantly. | |
| 10 | + | |
| 11 | +Artificial intelligence introduces a strange possibility into this system: | |
| 12 | + | |
| 13 | +**What happens if useful intelligence stops being scarce?** | |
| 14 | + | |
| 15 | +We are still far from intelligence being literally free. Models require chips, electricity, infrastructure, data, and engineering. | |
| 16 | + | |
| 17 | +But the marginal cost of many cognitive tasks is already collapsing. | |
| 18 | + | |
| 19 | +And economics tells us that when the price of an important input collapses, the consequences are rarely limited to that input. | |
| 20 | + | |
| 21 | +The entire system reorganizes around it. | |
| 22 | + | |
| 23 | +## Intelligence as an Economic Input | |
| 24 | + | |
| 25 | +Consider intelligence as simply another factor of production. | |
| 26 | + | |
| 27 | +A simplified economy might contain: | |
| 28 | + | |
| 29 | +* capital, | |
| 30 | +* labor, | |
| 31 | +* energy, | |
| 32 | +* natural resources, | |
| 33 | +* and intelligence. | |
| 34 | + | |
| 35 | +Historically, intelligence and labor have been tightly coupled. | |
| 36 | + | |
| 37 | +If a company wanted twice as much accounting work, software development, legal analysis, financial modeling, or research, it generally needed more people. | |
| 38 | + | |
| 39 | +AI begins to separate these two variables. | |
| 40 | + | |
| 41 | +A machine can now produce certain forms of cognitive output without requiring an additional human worker for every additional unit of output. | |
| 42 | + | |
| 43 | +This distinction is fundamental. | |
| 44 | + | |
| 45 | +Software already demonstrated the economics of near-zero marginal reproduction. Once software exists, producing another copy costs almost nothing. | |
| 46 | + | |
| 47 | +AI potentially extends this property from **software itself to some of the work performed through software**. | |
| 48 | + | |
| 49 | +That is a much larger transformation. | |
| 50 | + | |
| 51 | +## The Strange Economics of Digital Workers | |
| 52 | + | |
| 53 | +Imagine a capable AI agent that costs $1 per hour to operate. | |
| 54 | + | |
| 55 | +Now imagine it improves until it performs certain tasks comparable to a worker costing $50 per hour. | |
| 56 | + | |
| 57 | +Companies would have an enormous incentive to substitute the expensive input with the inexpensive one wherever possible. | |
| 58 | + | |
| 59 | +But something even more unusual happens. | |
| 60 | + | |
| 61 | +The AI worker can be copied. | |
| 62 | + | |
| 63 | +A company does not have to choose between one AI worker and another. | |
| 64 | + | |
| 65 | +It can run 10. | |
| 66 | + | |
| 67 | +Or 1,000. | |
| 68 | + | |
| 69 | +Or 100,000. | |
| 70 | + | |
| 71 | +The constraint eventually becomes compute rather than the availability of trained humans. | |
| 72 | + | |
| 73 | +This creates an economy where cognitive capacity could become elastic in a way human labor never was. | |
| 74 | + | |
| 75 | +If a problem requires ten times more analysis, you allocate ten times more compute. | |
| 76 | + | |
| 77 | +If a software project benefits from hundreds of parallel experiments, you launch hundreds of agents. | |
| 78 | + | |
| 79 | +If a scientific question requires reading one million documents, machines can divide the corpus among thousands of instances. | |
| 80 | + | |
| 81 | +This is not merely automation. | |
| 82 | + | |
| 83 | +It is the potential industrialization of cognition. | |
| 84 | + | |
| 85 | +## Cheap Intelligence Does Not Mean Cheap Everything | |
| 86 | + | |
| 87 | +There is an important mistake in assuming that cheaper intelligence makes everything abundant. | |
| 88 | + | |
| 89 | +It does not. | |
| 90 | + | |
| 91 | +Some resources remain fundamentally scarce. | |
| 92 | + | |
| 93 | +Land is scarce. | |
| 94 | + | |
| 95 | +Energy infrastructure is scarce. | |
| 96 | + | |
| 97 | +Advanced semiconductor manufacturing capacity is scarce. | |
| 98 | + | |
| 99 | +Certain minerals are scarce. | |
| 100 | + | |
| 101 | +Physical transportation has constraints. | |
| 102 | + | |
| 103 | +Housing in desirable locations is scarce. | |
| 104 | + | |
| 105 | +Human attention is scarce. | |
| 106 | + | |
| 107 | +Time itself remains scarce. | |
| 108 | + | |
| 109 | +As intelligence becomes cheaper, these complementary scarce resources may actually become **more valuable**. | |
| 110 | + | |
| 111 | +This pattern has occurred before. | |
| 112 | + | |
| 113 | +When one factor of production becomes dramatically more abundant, economic value often migrates toward the remaining bottlenecks. | |
| 114 | + | |
| 115 | +Cheap computation increased the importance of data. | |
| 116 | + | |
| 117 | +The internet made information abundant while increasing the value of attention. | |
| 118 | + | |
| 119 | +AI could make cognitive production abundant while increasing the value of energy, compute infrastructure, proprietary data, physical assets, distribution, trust, and human attention. | |
| 120 | + | |
| 121 | +The interesting question therefore isn't simply: | |
| 122 | + | |
| 123 | +> What will AI make cheaper? | |
| 124 | + | |
| 125 | +It is also: | |
| 126 | + | |
| 127 | +> What becomes more valuable because intelligence became cheaper? | |
| 128 | + | |
| 129 | +## Capital May Become More Important | |
| 130 | + | |
| 131 | +This leads to an uncomfortable economic possibility. | |
| 132 | + | |
| 133 | +AI is often discussed primarily as a labor technology. | |
| 134 | + | |
| 135 | +It may be more useful to think about it as a new form of capital. | |
| 136 | + | |
| 137 | +A machine, server, model, or agent can perform work repeatedly after an initial investment. | |
| 138 | + | |
| 139 | +If cognitive production becomes increasingly capital-intensive, ownership of productive assets becomes more important. | |
| 140 | + | |
| 141 | +Consider two individuals. | |
| 142 | + | |
| 143 | +One primarily earns income by selling cognitive labor. | |
| 144 | + | |
| 145 | +The other owns compute infrastructure, models, software, businesses, energy assets, or financial capital. | |
| 146 | + | |
| 147 | +If machines increasingly substitute for cognitive labor, their economic positions may diverge. | |
| 148 | + | |
| 149 | +The first individual competes with increasingly inexpensive machine intelligence. | |
| 150 | + | |
| 151 | +The second owns assets whose productivity is amplified by that intelligence. | |
| 152 | + | |
| 153 | +AI could therefore create extraordinary productivity growth while simultaneously increasing the importance of capital ownership. | |
| 154 | + | |
| 155 | +Those outcomes are not contradictory. | |
| 156 | + | |
| 157 | +## The One-Person Corporation | |
| 158 | + | |
| 159 | +There is another side to the same phenomenon. | |
| 160 | + | |
| 161 | +Historically, building a large company required coordinating many specialized people. | |
| 162 | + | |
| 163 | +Engineering. | |
| 164 | + | |
| 165 | +Accounting. | |
| 166 | + | |
| 167 | +Marketing. | |
| 168 | + | |
| 169 | +Customer support. | |
| 170 | + | |
| 171 | +Legal work. | |
| 172 | + | |
| 173 | +Research. | |
| 174 | + | |
| 175 | +Operations. | |
| 176 | + | |
| 177 | +Management. | |
| 178 | + | |
| 179 | +Each function created organizational complexity. | |
| 180 | + | |
| 181 | +AI agents could dramatically reduce the minimum human organization required to operate a sophisticated company. | |
| 182 | + | |
| 183 | +Imagine one entrepreneur coordinating dozens or hundreds of specialized agents. | |
| 184 | + | |
| 185 | +One agent maintains infrastructure. | |
| 186 | + | |
| 187 | +Another analyzes customer feedback. | |
| 188 | + | |
| 189 | +Another generates software tests. | |
| 190 | + | |
| 191 | +Another monitors financial metrics. | |
| 192 | + | |
| 193 | +Another conducts market research. | |
| 194 | + | |
| 195 | +Others handle documentation, localization, analytics, or internal operations. | |
| 196 | + | |
| 197 | +The human becomes less of a worker performing every task and more of an allocator of machine intelligence. | |
| 198 | + | |
| 199 | +This could produce something historically unusual: | |
| 200 | + | |
| 201 | +**extremely small organizations controlling extremely large amounts of productive capacity.** | |
| 202 | + | |
| 203 | +The famous one-person billion-dollar company may or may not appear soon. | |
| 204 | + | |
| 205 | +The more important observation is that the minimum number of humans required to operate a given amount of economic activity is likely to decline. | |
| 206 | + | |
| 207 | +That alone could substantially reshape firms. | |
| 208 | + | |
| 209 | +## Science Could Experience the Same Transformation | |
| 210 | + | |
| 211 | +The economics of research are particularly interesting. | |
| 212 | + | |
| 213 | +Scientific progress is partly constrained by the cost of experimentation. | |
| 214 | + | |
| 215 | +Researchers must search literature, clean datasets, write software, formulate models, conduct robustness tests, analyze results, and document experiments. | |
| 216 | + | |
| 217 | +Much of this work is cognitive and computational. | |
| 218 | + | |
| 219 | +Agents could make experiments dramatically cheaper. | |
| 220 | + | |
| 221 | +Instead of asking: | |
| 222 | + | |
| 223 | +> Which three specifications should we test? | |
| 224 | + | |
| 225 | +A researcher might eventually ask: | |
| 226 | + | |
| 227 | +> Which 30,000 specifications should the system explore, and how should we statistically evaluate the resulting evidence? | |
| 228 | + | |
| 229 | +The scarce resource moves upward. | |
| 230 | + | |
| 231 | +Execution becomes cheap. | |
| 232 | + | |
| 233 | +Choosing meaningful questions becomes valuable. | |
| 234 | + | |
| 235 | +This could produce a paradoxical effect: as machines become better at research tasks, human scientific judgment may become more important rather than less. | |
| 236 | + | |
| 237 | +Generating experiments is not the same thing as knowing which experiments matter. | |
| 238 | + | |
| 239 | +## Software Is the First Laboratory | |
| 240 | + | |
| 241 | +Software development provides perhaps the clearest preview. | |
| 242 | + | |
| 243 | +Programming is unusually compatible with AI because the environment provides rapid feedback. | |
| 244 | + | |
| 245 | +The model writes code. | |
| 246 | + | |
| 247 | +The computer executes it. | |
| 248 | + | |
| 249 | +The program fails. | |
| 250 | + | |
| 251 | +The model reads the error. | |
| 252 | + | |
| 253 | +It modifies the code. | |
| 254 | + | |
| 255 | +The program runs again. | |
| 256 | + | |
| 257 | +This feedback loop allows AI systems to move beyond generating text toward actually completing tasks. | |
| 258 | + | |
| 259 | +The same architecture can eventually extend elsewhere whenever environments provide machine-readable feedback. | |
| 260 | + | |
| 261 | +Finance. | |
| 262 | + | |
| 263 | +Engineering. | |
| 264 | + | |
| 265 | +Scientific computing. | |
| 266 | + | |
| 267 | +Logistics. | |
| 268 | + | |
| 269 | +Accounting. | |
| 270 | + | |
| 271 | +Design. | |
| 272 | + | |
| 273 | +Operations. | |
| 274 | + | |
| 275 | +The deeper transformation therefore comes not simply from better language models, but from connecting models to environments where they can **act, observe results, and iterate**. | |
| 276 | + | |
| 277 | +That is the transition from models to agents. | |
| 278 | + | |
| 279 | +## Productivity Could Become Difficult to Measure | |
| 280 | + | |
| 281 | +If this transition occurs quickly, traditional measures of economic activity may initially struggle to capture it. | |
| 282 | + | |
| 283 | +Suppose an individual uses local AI agents to produce software that would previously have required a ten-person company. | |
| 284 | + | |
| 285 | +The amount of economically useful output may increase enormously without a proportional increase in wages, employment, or organizational size. | |
| 286 | + | |
| 287 | +Similarly, open-source models running on privately owned hardware can generate valuable output without creating an API transaction every time intelligence is consumed. | |
| 288 | + | |
| 289 | +Some intelligence may become an internal intermediate good. | |
| 290 | + | |
| 291 | +A company could consume billions of machine-generated tokens internally to optimize operations while very little of that activity appears directly as final expenditure. | |
| 292 | + | |
| 293 | +Economic statistics were designed around economies where production largely involved observable transactions between humans and organizations. | |
| 294 | + | |
| 295 | +Machine-generated internal cognitive production could complicate that picture. | |
| 296 | + | |
| 297 | +## Intelligence Will Still Have a Price | |
| 298 | + | |
| 299 | +There is a final qualification. | |
| 300 | + | |
| 301 | +The cost of intelligence probably never reaches zero. | |
| 302 | + | |
| 303 | +As inexpensive models become capable enough for ordinary tasks, demand for more capable intelligence may expand. | |
| 304 | + | |
| 305 | +A cheap model might solve a problem in one second. | |
| 306 | + | |
| 307 | +A more sophisticated system might spend one thousand times more computation searching for a significantly better solution. | |
| 308 | + | |
| 309 | +This resembles computing itself. | |
| 310 | + | |
| 311 | +Computers became dramatically cheaper. | |
| 312 | + | |
| 313 | +Humanity did not respond by spending less on computation. | |
| 314 | + | |
| 315 | +We found vastly more things to compute. | |
| 316 | + | |
| 317 | +The same may happen with intelligence. | |
| 318 | + | |
| 319 | +As the price per unit falls, civilization may consume extraordinary quantities of it. | |
| 320 | + | |
| 321 | +Companies could run continuous simulations. | |
| 322 | + | |
| 323 | +Scientists could launch millions of experiments. | |
| 324 | + | |
| 325 | +Software agents could continuously optimize infrastructure. | |
| 326 | + | |
| 327 | +Individuals could maintain personalized models analyzing enormous amounts of information. | |
| 328 | + | |
| 329 | +The total amount spent on machine intelligence could therefore increase even while its unit cost collapses. | |
| 330 | + | |
| 331 | +## The Real Question | |
| 332 | + | |
| 333 | +The most interesting future is not necessarily one where artificial intelligence replaces humans. | |
| 334 | + | |
| 335 | +It is one where intelligence becomes an abundant industrial resource. | |
| 336 | + | |
| 337 | +That would force the economy to reorganize around whatever remains scarce. | |
| 338 | + | |
| 339 | +Compute. | |
| 340 | + | |
| 341 | +Energy. | |
| 342 | + | |
| 343 | +Physical resources. | |
| 344 | + | |
| 345 | +Capital. | |
| 346 | + | |
| 347 | +Trust. | |
| 348 | + | |
| 349 | +Attention. | |
| 350 | + | |
| 351 | +Ownership. | |
| 352 | + | |
| 353 | +And, perhaps most importantly, good questions. | |
| 354 | + | |
| 355 | +For centuries, civilization operated under a fundamental constraint: meaningful cognitive work required human cognitive time. | |
| 356 | + | |
| 357 | +We built companies, universities, governments, and markets around that constraint. | |
| 358 | + | |
| 359 | +If machine intelligence significantly weakens it, we should expect more than productivity improvements. | |
| 360 | + | |
| 361 | +We should expect new organizational structures, new distributions of economic power, new business models, and possibly entirely new ways of conducting science. | |
| 362 | + | |
| 363 | +The important question may therefore not be whether artificial intelligence becomes smarter than humans. | |
| 364 | + | |
| 365 | +It may be much simpler: | |
| 366 | + | |
| 367 | +**What does an economy look like when intelligence is no longer scarce?** | |
| 368 | + | |
added
blog/blog2.txt
+370 −0
@@ -0,0 +1,370 @@ | ||
| 1 | +# If AI Creates Enormous Wealth, Who Owns It? | |
| 2 | + | |
| 3 | +Artificial intelligence is usually discussed as a productivity technology. | |
| 4 | + | |
| 5 | +A programmer writes more code. A researcher analyzes more information. A company automates customer support. A factory optimizes production. | |
| 6 | + | |
| 7 | +The assumption is straightforward: if AI makes workers and firms more productive, society becomes wealthier. | |
| 8 | + | |
| 9 | +That is probably true. | |
| 10 | + | |
| 11 | +But it leaves out a much more important economic question: | |
| 12 | + | |
| 13 | +**Who owns the productivity gains?** | |
| 14 | + | |
| 15 | +Economic growth and the distribution of economic growth are two different things. | |
| 16 | + | |
| 17 | +Artificial intelligence could create extraordinary amounts of wealth while distributing that wealth very unevenly. | |
| 18 | + | |
| 19 | +Understanding why requires thinking less about what AI can do and more about who owns the assets required to do it. | |
| 20 | + | |
| 21 | +## Productivity Is Not Income | |
| 22 | + | |
| 23 | +Suppose an AI system allows one worker to produce what previously required five workers. | |
| 24 | + | |
| 25 | +Productivity has clearly increased. | |
| 26 | + | |
| 27 | +But what happens to the economic surplus? | |
| 28 | + | |
| 29 | +Several outcomes are possible. | |
| 30 | + | |
| 31 | +The worker could receive a much higher salary because their output increased. | |
| 32 | + | |
| 33 | +Consumers could benefit through lower prices. | |
| 34 | + | |
| 35 | +The company could capture the difference as higher profits. | |
| 36 | + | |
| 37 | +The AI provider could capture it through model-access fees. | |
| 38 | + | |
| 39 | +Or competition could distribute the gains across several groups. | |
| 40 | + | |
| 41 | +Technology itself does not determine which outcome occurs. | |
| 42 | + | |
| 43 | +Institutions, market structure, bargaining power, scarcity, and ownership do. | |
| 44 | + | |
| 45 | +This distinction matters because AI may be unusual compared with previous productivity technologies. | |
| 46 | + | |
| 47 | +It does not merely make human workers more productive. | |
| 48 | + | |
| 49 | +In some domains, it creates a potential substitute for the worker. | |
| 50 | + | |
| 51 | +And substitutes affect bargaining power very differently from complements. | |
| 52 | + | |
| 53 | +## The Difference Between Owning AI and Using AI | |
| 54 | + | |
| 55 | +Imagine two companies. | |
| 56 | + | |
| 57 | +Company A pays employees to perform thousands of cognitive tasks. | |
| 58 | + | |
| 59 | +Company B owns an automated system capable of performing many of the same tasks. | |
| 60 | + | |
| 61 | +Both companies may use artificial intelligence. | |
| 62 | + | |
| 63 | +But economically, their positions are very different. | |
| 64 | + | |
| 65 | +For Company A, AI is a productivity tool. | |
| 66 | + | |
| 67 | +For Company B, AI is productive capital. | |
| 68 | + | |
| 69 | +That distinction could become increasingly important. | |
| 70 | + | |
| 71 | +When a worker uses AI, the worker temporarily accesses machine intelligence. | |
| 72 | + | |
| 73 | +When a company owns the infrastructure, models, data, distribution, or software through which that intelligence operates, it owns an asset capable of generating repeated economic output. | |
| 74 | + | |
| 75 | +The history of capitalism suggests that ownership matters enormously. | |
| 76 | + | |
| 77 | +Industrialization did not simply increase the productivity of factory workers. | |
| 78 | + | |
| 79 | +It increased the economic importance of owning factories. | |
| 80 | + | |
| 81 | +AI may similarly increase the importance of owning computational capital. | |
| 82 | + | |
| 83 | +## What Exactly Is AI Capital? | |
| 84 | + | |
| 85 | +The obvious answer is GPUs. | |
| 86 | + | |
| 87 | +But the AI capital stack is much larger. | |
| 88 | + | |
| 89 | +It includes semiconductor fabrication, data centers, electricity generation, networking infrastructure, foundation models, proprietary datasets, software platforms, distribution networks, and the companies integrating all of these components. | |
| 90 | + | |
| 91 | +Even relatively small businesses can own AI capital. | |
| 92 | + | |
| 93 | +A specialized model trained on proprietary information is an asset. | |
| 94 | + | |
| 95 | +An autonomous software system capable of performing recurring tasks is an asset. | |
| 96 | + | |
| 97 | +A dataset that dramatically improves an agent's performance is an asset. | |
| 98 | + | |
| 99 | +A network of agents operating a business process can itself be understood as productive capital. | |
| 100 | + | |
| 101 | +This creates an important transition. | |
| 102 | + | |
| 103 | +Software traditionally stored instructions. | |
| 104 | + | |
| 105 | +AI systems increasingly store **capabilities**. | |
| 106 | + | |
| 107 | +That may make software ownership economically more similar to owning productive machinery. | |
| 108 | + | |
| 109 | +## Labor Has Never Competed With Replication | |
| 110 | + | |
| 111 | +There is another unusual property of AI capital. | |
| 112 | + | |
| 113 | +It can often be replicated extremely quickly. | |
| 114 | + | |
| 115 | +A highly skilled human cannot be copied. | |
| 116 | + | |
| 117 | +If an organization needs 1,000 additional physicians, engineers, accountants, researchers, or programmers, society must train them. | |
| 118 | + | |
| 119 | +That takes years. | |
| 120 | + | |
| 121 | +A capable software agent is different. | |
| 122 | + | |
| 123 | +Once the system exists, creating additional instances may require little more than additional computation. | |
| 124 | + | |
| 125 | +This potentially creates a very different labor market dynamic. | |
| 126 | + | |
| 127 | +Human expertise has historically derived part of its economic value from scarcity. | |
| 128 | + | |
| 129 | +Training creates scarcity. | |
| 130 | + | |
| 131 | +Experience creates scarcity. | |
| 132 | + | |
| 133 | +Talent creates scarcity. | |
| 134 | + | |
| 135 | +Geography creates scarcity. | |
| 136 | + | |
| 137 | +AI can weaken some of these constraints by converting expertise into reproducible software. | |
| 138 | + | |
| 139 | +If that happens, the economic return to performing certain skills may decline while the return to **owning systems that reproduce those skills** increases. | |
| 140 | + | |
| 141 | +## The Capital Share Could Rise | |
| 142 | + | |
| 143 | +Economists often divide national income broadly between labor and capital. | |
| 144 | + | |
| 145 | +Labor receives wages. | |
| 146 | + | |
| 147 | +Capital receives profits, interest, rents, and other returns associated with ownership. | |
| 148 | + | |
| 149 | +AI could alter this balance. | |
| 150 | + | |
| 151 | +Suppose a company produces $100 million of output using 500 employees. | |
| 152 | + | |
| 153 | +Now imagine that technological progress allows the same company to produce $200 million with 100 employees and substantial AI infrastructure. | |
| 154 | + | |
| 155 | +The economy has become more productive. | |
| 156 | + | |
| 157 | +Output doubled. | |
| 158 | + | |
| 159 | +But labor's role in producing that output decreased dramatically. | |
| 160 | + | |
| 161 | +Where does the additional income go? | |
| 162 | + | |
| 163 | +Potentially toward the owners of the company, the infrastructure, the models, and other scarce complementary assets. | |
| 164 | + | |
| 165 | +This does not mean wages necessarily collapse. | |
| 166 | + | |
| 167 | +Workers whose skills complement AI could become extraordinarily productive and therefore more valuable. | |
| 168 | + | |
| 169 | +But the aggregate direction is worth considering. | |
| 170 | + | |
| 171 | +If production becomes more capital-intensive, capital ownership becomes increasingly important. | |
| 172 | + | |
| 173 | +## This Could Produce a Strange Economy | |
| 174 | + | |
| 175 | +Imagine an economy twenty years from now. | |
| 176 | + | |
| 177 | +GDP is dramatically higher. | |
| 178 | + | |
| 179 | +Companies produce enormous quantities of software, analysis, media, research, designs, and services. | |
| 180 | + | |
| 181 | +AI systems perform much of the underlying cognitive work. | |
| 182 | + | |
| 183 | +Goods and digital services may be extraordinarily inexpensive. | |
| 184 | + | |
| 185 | +Measured productivity is extremely high. | |
| 186 | + | |
| 187 | +And yet employment income represents a smaller fraction of total economic output. | |
| 188 | + | |
| 189 | +Such an economy could simultaneously be richer than anything in history and deeply unequal. | |
| 190 | + | |
| 191 | +There is no contradiction. | |
| 192 | + | |
| 193 | +A society can produce enormous wealth without distributing ownership of that wealth broadly. | |
| 194 | + | |
| 195 | +This is why discussions about AI inequality that focus exclusively on jobs may miss the larger issue. | |
| 196 | + | |
| 197 | +The fundamental question may not be: | |
| 198 | + | |
| 199 | +**Will everyone have a job?** | |
| 200 | + | |
| 201 | +It may be: | |
| 202 | + | |
| 203 | +**Will everyone own productive assets?** | |
| 204 | + | |
| 205 | +## The Rise of Tiny Capitalists | |
| 206 | + | |
| 207 | +There is, however, a powerful force pushing in the opposite direction. | |
| 208 | + | |
| 209 | +AI capital is not identical to industrial capital. | |
| 210 | + | |
| 211 | +Building a steel mill requires enormous financial investment. | |
| 212 | + | |
| 213 | +Building a software company increasingly does not. | |
| 214 | + | |
| 215 | +Open-weight models, inexpensive computing, cloud infrastructure, coding agents, and global distribution allow individuals to control capabilities that once required organizations. | |
| 216 | + | |
| 217 | +This could dramatically lower the minimum capital required to become economically productive. | |
| 218 | + | |
| 219 | +An individual might own a collection of specialized agents capable of writing software, conducting research, managing infrastructure, communicating with customers, and operating digital businesses. | |
| 220 | + | |
| 221 | +Instead of selling eight hours of labor each day, that person controls a small productive system. | |
| 222 | + | |
| 223 | +In effect, AI could create millions of tiny capitalists. | |
| 224 | + | |
| 225 | +That possibility makes the distributional consequences of AI much less obvious. | |
| 226 | + | |
| 227 | +AI could centralize economic power around enormous computational infrastructure. | |
| 228 | + | |
| 229 | +But it could simultaneously decentralize productive capability by giving individuals access to extraordinary technological leverage. | |
| 230 | + | |
| 231 | +Both forces can exist at the same time. | |
| 232 | + | |
| 233 | +## Open Models Could Matter Economically | |
| 234 | + | |
| 235 | +This is one reason the distinction between closed and open AI systems may eventually matter beyond technology. | |
| 236 | + | |
| 237 | +If the most capable intelligence is available only through a handful of centralized providers, users effectively rent intelligence. | |
| 238 | + | |
| 239 | +If capable models can instead be owned and executed independently, individuals and organizations can own part of their productive infrastructure. | |
| 240 | + | |
| 241 | +The difference resembles renting a machine versus owning one. | |
| 242 | + | |
| 243 | +The economics are not identical, but the principle is important. | |
| 244 | + | |
| 245 | +Local and open models potentially transform AI from a service consumed from corporations into capital that individuals can possess. | |
| 246 | + | |
| 247 | +That could affect competition, entrepreneurship, privacy, and ultimately wealth distribution. | |
| 248 | + | |
| 249 | +The question of who can **own intelligence** may become surprisingly important. | |
| 250 | + | |
| 251 | +## What Happens to Human Capital? | |
| 252 | + | |
| 253 | +For decades, one of the safest economic strategies was investing in human capital. | |
| 254 | + | |
| 255 | +Education. | |
| 256 | + | |
| 257 | +Technical skills. | |
| 258 | + | |
| 259 | +Professional credentials. | |
| 260 | + | |
| 261 | +Experience. | |
| 262 | + | |
| 263 | +Knowledge. | |
| 264 | + | |
| 265 | +The underlying assumption was that these capabilities were scarce and therefore valuable. | |
| 266 | + | |
| 267 | +AI complicates this logic. | |
| 268 | + | |
| 269 | +If knowledge can be reproduced cheaply, the return to possessing knowledge alone may decrease. | |
| 270 | + | |
| 271 | +But other forms of human capital could become more valuable. | |
| 272 | + | |
| 273 | +Judgment. | |
| 274 | + | |
| 275 | +Taste. | |
| 276 | + | |
| 277 | +Trust. | |
| 278 | + | |
| 279 | +Leadership. | |
| 280 | + | |
| 281 | +Scientific intuition. | |
| 282 | + | |
| 283 | +Entrepreneurship. | |
| 284 | + | |
| 285 | +The ability to identify valuable problems. | |
| 286 | + | |
| 287 | +The ability to coordinate people and machines. | |
| 288 | + | |
| 289 | +Human capital may shift from **knowing how to perform tasks** toward **knowing which tasks should be performed**. | |
| 290 | + | |
| 291 | +That is a subtle but important distinction. | |
| 292 | + | |
| 293 | +## Markets Will Search for the Remaining Scarcity | |
| 294 | + | |
| 295 | +Capitalism is fundamentally good at pricing scarcity. | |
| 296 | + | |
| 297 | +If intelligence becomes abundant, markets will search for something else that is scarce. | |
| 298 | + | |
| 299 | +Perhaps that will be energy. | |
| 300 | + | |
| 301 | +Perhaps compute. | |
| 302 | + | |
| 303 | +Perhaps proprietary data. | |
| 304 | + | |
| 305 | +Perhaps land. | |
| 306 | + | |
| 307 | +Perhaps distribution. | |
| 308 | + | |
| 309 | +Perhaps trusted brands. | |
| 310 | + | |
| 311 | +Perhaps regulatory permissions. | |
| 312 | + | |
| 313 | +Perhaps human attention. | |
| 314 | + | |
| 315 | +Perhaps ownership itself. | |
| 316 | + | |
| 317 | +The economic value currently embedded in cognitive labor will not simply disappear. | |
| 318 | + | |
| 319 | +Some of it will migrate. | |
| 320 | + | |
| 321 | +Finding where it migrates may be one of the most important investment and economic questions of the AI era. | |
| 322 | + | |
| 323 | +## The Political Question Comes Later | |
| 324 | + | |
| 325 | +If AI produces substantial abundance while concentrating ownership, political pressure for redistribution would almost certainly increase. | |
| 326 | + | |
| 327 | +Governments could respond through taxation, public investment funds, broader capital ownership, sovereign AI infrastructure, income transfers, or mechanisms that have not yet been invented. | |
| 328 | + | |
| 329 | +But those are downstream questions. | |
| 330 | + | |
| 331 | +The first question is economic. | |
| 332 | + | |
| 333 | +Before deciding how AI-generated wealth should be distributed, we need to understand **where that wealth will initially accumulate**. | |
| 334 | + | |
| 335 | +That requires following ownership. | |
| 336 | + | |
| 337 | +Who owns the models? | |
| 338 | + | |
| 339 | +Who owns the compute? | |
| 340 | + | |
| 341 | +Who owns the energy? | |
| 342 | + | |
| 343 | +Who owns the data? | |
| 344 | + | |
| 345 | +Who owns the companies deploying the agents? | |
| 346 | + | |
| 347 | +Who owns the physical assets whose productivity AI increases? | |
| 348 | + | |
| 349 | +Those questions may ultimately tell us more about the distributional effects of artificial intelligence than benchmarks measuring which model can solve the hardest mathematics problem. | |
| 350 | + | |
| 351 | +## From Labor Economics to Ownership Economics | |
| 352 | + | |
| 353 | +The Industrial Revolution transformed physical production. | |
| 354 | + | |
| 355 | +The AI revolution may transform cognitive production. | |
| 356 | + | |
| 357 | +Both technologies increase productivity. | |
| 358 | + | |
| 359 | +But productivity is only half the story. | |
| 360 | + | |
| 361 | +The other half is ownership. | |
| 362 | + | |
| 363 | +If artificial intelligence becomes a major factor of production, the defining economic divide of the future may not simply be between skilled and unskilled workers. | |
| 364 | + | |
| 365 | +It may increasingly be between those who primarily **sell labor** and those who **own productive intelligence**. | |
| 366 | + | |
| 367 | +And if that happens, one of the most important economic policies of the AI era may have surprisingly little to do with regulating algorithms. | |
| 368 | + | |
| 369 | +It may be figuring out how broadly society can distribute ownership of the machines that think. | |
| 370 | + | |
added
blog/blog3.txt
+516 −0
@@ -0,0 +1,516 @@ | ||
| 1 | + # What If AI Prevents Its Own Singularity? | |
| 2 | + | |
| 3 | +The technological singularity is usually imagined as a positive feedback loop. | |
| 4 | + | |
| 5 | +Artificial intelligence becomes capable enough to help researchers build better artificial intelligence. Better AI then accelerates AI research. That produces even better AI, which accelerates research further. | |
| 6 | + | |
| 7 | +The cycle repeats. | |
| 8 | + | |
| 9 | +Eventually, technological progress becomes so rapid that predicting what comes next becomes impossible. | |
| 10 | + | |
| 11 | +But there is another feedback loop developing at the same time. | |
| 12 | + | |
| 13 | +And it runs in the opposite direction. | |
| 14 | + | |
| 15 | +AI systems are increasingly generating the information environment from which future AI systems will learn. | |
| 16 | + | |
| 17 | +Text. | |
| 18 | + | |
| 19 | +Images. | |
| 20 | + | |
| 21 | +Code. | |
| 22 | + | |
| 23 | +Scientific summaries. | |
| 24 | + | |
| 25 | +Web pages. | |
| 26 | + | |
| 27 | +Product descriptions. | |
| 28 | + | |
| 29 | +Questions and answers. | |
| 30 | + | |
| 31 | +Documentation. | |
| 32 | + | |
| 33 | +Social media posts. | |
| 34 | + | |
| 35 | +Eventually, perhaps a substantial fraction of the observable digital world. | |
| 36 | + | |
| 37 | +This creates a strange possibility. | |
| 38 | + | |
| 39 | +**What if AI becomes so successful at generating information that it gradually damages the information ecosystem required to build better AI?** | |
| 40 | + | |
| 41 | +Instead of an intelligence explosion, we could encounter an intelligence ceiling. | |
| 42 | + | |
| 43 | +Not because we run out of compute. | |
| 44 | + | |
| 45 | +Not because neural networks stop scaling. | |
| 46 | + | |
| 47 | +But because machines begin consuming too much of their own output. | |
| 48 | + | |
| 49 | +## The Internet Was an Accidental Training Dataset | |
| 50 | + | |
| 51 | +The first generations of large language models benefited from something historically unique. | |
| 52 | + | |
| 53 | +For several decades, billions of humans produced an enormous digital record of human civilization. | |
| 54 | + | |
| 55 | +Books. | |
| 56 | + | |
| 57 | +Wikipedia. | |
| 58 | + | |
| 59 | +Forums. | |
| 60 | + | |
| 61 | +Academic papers. | |
| 62 | + | |
| 63 | +Newspapers. | |
| 64 | + | |
| 65 | +Software repositories. | |
| 66 | + | |
| 67 | +Technical documentation. | |
| 68 | + | |
| 69 | +Blogs. | |
| 70 | + | |
| 71 | +Government documents. | |
| 72 | + | |
| 73 | +Conversations. | |
| 74 | + | |
| 75 | +Educational material. | |
| 76 | + | |
| 77 | +The internet became an enormous, messy, decentralized archive of human knowledge. | |
| 78 | + | |
| 79 | +Importantly, most of it was created before people expected it to become training data for artificial intelligence. | |
| 80 | + | |
| 81 | +Humans were producing information for other humans. | |
| 82 | + | |
| 83 | +Then machine learning arrived and consumed this accumulated intellectual residue. | |
| 84 | + | |
| 85 | +In a sense, modern AI inherited a massive dataset that civilization had unknowingly spent decades constructing. | |
| 86 | + | |
| 87 | +That inheritance cannot necessarily be recreated. | |
| 88 | + | |
| 89 | +The internet after generative AI may be fundamentally different from the internet before it. | |
| 90 | + | |
| 91 | +## The Synthetic Internet | |
| 92 | + | |
| 93 | +Imagine that in 2015 almost everything a crawler encountered online had ultimately been produced by humans. | |
| 94 | + | |
| 95 | +Now move forward. | |
| 96 | + | |
| 97 | +AI writes articles. | |
| 98 | + | |
| 99 | +AI answers questions. | |
| 100 | + | |
| 101 | +AI generates documentation. | |
| 102 | + | |
| 103 | +AI translates websites. | |
| 104 | + | |
| 105 | +AI writes marketing copy. | |
| 106 | + | |
| 107 | +AI generates code. | |
| 108 | + | |
| 109 | +AI summarizes scientific papers. | |
| 110 | + | |
| 111 | +AI creates synthetic images and videos. | |
| 112 | + | |
| 113 | +AI generates the text used to train other AI systems. | |
| 114 | + | |
| 115 | +The ratio between human-generated and machine-generated information begins to change. | |
| 116 | + | |
| 117 | +At first, this seems harmless. | |
| 118 | + | |
| 119 | +High-quality synthetic data can be extremely useful. Models can generate examples, critique answers, create reasoning traces, simulate environments, and help construct datasets that would otherwise be expensive to produce. | |
| 120 | + | |
| 121 | +Synthetic data is not inherently bad data. | |
| 122 | + | |
| 123 | +The problem begins when **provenance disappears**. | |
| 124 | + | |
| 125 | +A future training system crawling the web may not know whether a paragraph originated from a human expert, a frontier model, a small model, a chain of models rewriting each other, or an automated content farm optimizing for search traffic. | |
| 126 | + | |
| 127 | +The training distribution becomes recursive. | |
| 128 | + | |
| 129 | +Models increasingly learn from a world partially generated by models. | |
| 130 | + | |
| 131 | +## The Photocopy Problem | |
| 132 | + | |
| 133 | +Imagine making a photocopy of a photograph. | |
| 134 | + | |
| 135 | +The first copy looks almost identical to the original. | |
| 136 | + | |
| 137 | +Now photocopy the copy. | |
| 138 | + | |
| 139 | +Then photocopy that copy. | |
| 140 | + | |
| 141 | +Repeat the process hundreds of times. | |
| 142 | + | |
| 143 | +Small distortions accumulate. | |
| 144 | + | |
| 145 | +Fine details disappear. | |
| 146 | + | |
| 147 | +Contrast changes. | |
| 148 | + | |
| 149 | +Rare features vanish. | |
| 150 | + | |
| 151 | +Eventually, the image retains the broad structure of the original while losing much of its information. | |
| 152 | + | |
| 153 | +Recursive synthetic training could create an analogous phenomenon. | |
| 154 | + | |
| 155 | +A model does not reproduce the entire probability distribution of its training data perfectly. | |
| 156 | + | |
| 157 | +It approximates it. | |
| 158 | + | |
| 159 | +When it generates new samples, unusual observations may be underrepresented. | |
| 160 | + | |
| 161 | +Subtle distinctions may disappear. | |
| 162 | + | |
| 163 | +Rare knowledge may appear less frequently. | |
| 164 | + | |
| 165 | +Uncertainty may be compressed into confident answers. | |
| 166 | + | |
| 167 | +Complex distributions become smoother. | |
| 168 | + | |
| 169 | +If another model trains on those outputs, it learns the approximation rather than the original distribution. | |
| 170 | + | |
| 171 | +Repeat this process enough times and errors can compound. | |
| 172 | + | |
| 173 | +This phenomenon is generally discussed under terms such as **model collapse**. | |
| 174 | + | |
| 175 | +But its implications could extend beyond individual training experiments. | |
| 176 | + | |
| 177 | +What happens if the dataset undergoing recursive approximation is the internet itself? | |
| 178 | + | |
| 179 | +## The Tail Is Where Much of the Value Lives | |
| 180 | + | |
| 181 | +The danger is not necessarily that AI-generated text becomes obviously nonsensical. | |
| 182 | + | |
| 183 | +The more interesting danger is statistical. | |
| 184 | + | |
| 185 | +Generative models are very good at representing the center of distributions. | |
| 186 | + | |
| 187 | +But civilization depends heavily on the tails. | |
| 188 | + | |
| 189 | +Rare expertise. | |
| 190 | + | |
| 191 | +Unusual observations. | |
| 192 | + | |
| 193 | +Minority hypotheses. | |
| 194 | + | |
| 195 | +Obscure historical facts. | |
| 196 | + | |
| 197 | +Unexpected combinations of ideas. | |
| 198 | + | |
| 199 | +Strange programming solutions. | |
| 200 | + | |
| 201 | +Uncommon scientific results. | |
| 202 | + | |
| 203 | +Local knowledge. | |
| 204 | + | |
| 205 | +Contradictory evidence. | |
| 206 | + | |
| 207 | +These observations may have low probability while carrying high informational value. | |
| 208 | + | |
| 209 | +Suppose a training distribution contains 10,000 common observations and 10 extremely unusual but important ones. | |
| 210 | + | |
| 211 | +A generative model approximating that distribution may reproduce the common observations extremely well while rarely generating the unusual ones. | |
| 212 | + | |
| 213 | +Train another model predominantly on the generated distribution and those rare observations become even rarer. | |
| 214 | + | |
| 215 | +Eventually they disappear. | |
| 216 | + | |
| 217 | +The model can appear fluent and intelligent while the underlying information distribution becomes narrower. | |
| 218 | + | |
| 219 | +This would be a particularly dangerous form of degradation because superficial quality could remain high. | |
| 220 | + | |
| 221 | +Language stays grammatical. | |
| 222 | + | |
| 223 | +Answers remain plausible. | |
| 224 | + | |
| 225 | +Benchmarks may even improve. | |
| 226 | + | |
| 227 | +Yet the epistemic diversity of the system declines. | |
| 228 | + | |
| 229 | +## The Singularity Assumes Fresh Information | |
| 230 | + | |
| 231 | +The classic intelligence-explosion argument implicitly assumes that increasingly intelligent systems continue having access to useful information. | |
| 232 | + | |
| 233 | +But intelligence and information are not the same thing. | |
| 234 | + | |
| 235 | +A perfect reasoner cannot discover the temperature outside without receiving information about the physical world. | |
| 236 | + | |
| 237 | +No amount of reasoning can reconstruct arbitrary information that has been permanently removed from the input. | |
| 238 | + | |
| 239 | +This creates a constraint on recursive self-improvement. | |
| 240 | + | |
| 241 | +An AI system can improve algorithms. | |
| 242 | + | |
| 243 | +It can improve architectures. | |
| 244 | + | |
| 245 | +It can optimize code. | |
| 246 | + | |
| 247 | +It can design experiments. | |
| 248 | + | |
| 249 | +It can generate hypotheses. | |
| 250 | + | |
| 251 | +But eventually those hypotheses must collide with reality. | |
| 252 | + | |
| 253 | +Scientific progress requires observations. | |
| 254 | + | |
| 255 | +Engineering requires experiments. | |
| 256 | + | |
| 257 | +Economic knowledge requires behavior. | |
| 258 | + | |
| 259 | +Medicine requires biological evidence. | |
| 260 | + | |
| 261 | +Intelligence can transform information. | |
| 262 | + | |
| 263 | +It cannot indefinitely substitute for new information. | |
| 264 | + | |
| 265 | +The singularity therefore may depend not simply on recursive intelligence improvement but on a continuous pipeline connecting machine intelligence to **non-synthetic reality**. | |
| 266 | + | |
| 267 | +## The Data Wall May Be More Important Than the Compute Wall | |
| 268 | + | |
| 269 | +Much discussion about AI scaling focuses on computation. | |
| 270 | + | |
| 271 | +How many GPUs? | |
| 272 | + | |
| 273 | +How much electricity? | |
| 274 | + | |
| 275 | +How many parameters? | |
| 276 | + | |
| 277 | +How large a context window? | |
| 278 | + | |
| 279 | +But another constraint may become increasingly important: high-quality, independent information. | |
| 280 | + | |
| 281 | +Human-generated datasets are finite. | |
| 282 | + | |
| 283 | +The stock of historically produced text is enormous, but frontier systems have already consumed significant portions of easily accessible high-quality material. | |
| 284 | + | |
| 285 | +Generating additional tokens is trivial. | |
| 286 | + | |
| 287 | +Generating additional **information** is not. | |
| 288 | + | |
| 289 | +This distinction matters enormously. | |
| 290 | + | |
| 291 | +A model can generate one trillion tokens without adding one trillion tokens worth of new knowledge to civilization. | |
| 292 | + | |
| 293 | +Much of the output may simply be transformations of existing information. | |
| 294 | + | |
| 295 | +Summaries. | |
| 296 | + | |
| 297 | +Rephrasings. | |
| 298 | + | |
| 299 | +Combinations. | |
| 300 | + | |
| 301 | +Translations. | |
| 302 | + | |
| 303 | +Extrapolations. | |
| 304 | + | |
| 305 | +Useful, certainly. | |
| 306 | + | |
| 307 | +But not equivalent to independent observations of reality. | |
| 308 | + | |
| 309 | +Tokens may become effectively infinite while genuinely novel information remains scarce. | |
| 310 | + | |
| 311 | +## Synthetic Data Is Not the Enemy | |
| 312 | + | |
| 313 | +None of this means synthetic data will destroy AI. | |
| 314 | + | |
| 315 | +In fact, synthetic data may be essential for building more capable systems. | |
| 316 | + | |
| 317 | +The important distinction is between **controlled synthetic generation** and uncontrolled recursive contamination. | |
| 318 | + | |
| 319 | +Synthetic mathematical problems with verified answers can be extremely valuable. | |
| 320 | + | |
| 321 | +Code can be executed against tests. | |
| 322 | + | |
| 323 | +Agents can interact with simulated environments. | |
| 324 | + | |
| 325 | +Formal proofs can be verified. | |
| 326 | + | |
| 327 | +Scientific simulations can produce structured data. | |
| 328 | + | |
| 329 | +Models can generate examples and filter them using external evaluators. | |
| 330 | + | |
| 331 | +In each case there is some mechanism connecting generation to truth. | |
| 332 | + | |
| 333 | +The problem emerges when synthetic information recursively circulates without reliable verification. | |
| 334 | + | |
| 335 | +Generation alone does not create truth. | |
| 336 | + | |
| 337 | +Verification is the critical component. | |
| 338 | + | |
| 339 | +This suggests that the future of AI may depend less on producing increasingly large quantities of synthetic data and more on constructing increasingly powerful **verification environments**. | |
| 340 | + | |
| 341 | +## Reality Could Become the Premium Dataset | |
| 342 | + | |
| 343 | +If synthetic content becomes ubiquitous, something interesting happens economically. | |
| 344 | + | |
| 345 | +Human-generated and reality-grounded information becomes more valuable. | |
| 346 | + | |
| 347 | +A dataset containing verified human conversations becomes valuable. | |
| 348 | + | |
| 349 | +A repository known to contain pre-generative-AI text becomes valuable. | |
| 350 | + | |
| 351 | +Experimental scientific measurements become valuable. | |
| 352 | + | |
| 353 | +Expert annotations become valuable. | |
| 354 | + | |
| 355 | +Physical sensor data becomes valuable. | |
| 356 | + | |
| 357 | +Private corporate data becomes valuable. | |
| 358 | + | |
| 359 | +Authenticated human writing becomes valuable. | |
| 360 | + | |
| 361 | +Even timestamps could matter. | |
| 362 | + | |
| 363 | +The pre-AI internet could eventually be treated almost like a geological layer: an information environment known to have existed before widespread synthetic contamination. | |
| 364 | + | |
| 365 | +We may begin distinguishing datasets not simply by subject but by **epistemic provenance**. | |
| 366 | + | |
| 367 | +Was this generated? | |
| 368 | + | |
| 369 | +Was it observed? | |
| 370 | + | |
| 371 | +Was it verified? | |
| 372 | + | |
| 373 | +By whom? | |
| 374 | + | |
| 375 | +When? | |
| 376 | + | |
| 377 | +From what original source? | |
| 378 | + | |
| 379 | +Data lineage could become one of the central problems of machine learning. | |
| 380 | + | |
| 381 | +## AI May Need Humans for a Surprising Reason | |
| 382 | + | |
| 383 | +People often assume humans remain useful because machines will continue lacking some uniquely human form of intelligence. | |
| 384 | + | |
| 385 | +That may not be the most important reason. | |
| 386 | + | |
| 387 | +Humans may remain valuable because humans are connected to reality. | |
| 388 | + | |
| 389 | +We observe things. | |
| 390 | + | |
| 391 | +We perform experiments. | |
| 392 | + | |
| 393 | +We experience environments. | |
| 394 | + | |
| 395 | +We make mistakes machines would not predict. | |
| 396 | + | |
| 397 | +We produce cultural novelty. | |
| 398 | + | |
| 399 | +We discover strange facts. | |
| 400 | + | |
| 401 | +We generate new economic behavior. | |
| 402 | + | |
| 403 | +We create information that did not previously exist in the training distribution. | |
| 404 | + | |
| 405 | +In other words, humanity may become valuable not simply as a source of intelligence but as a source of **entropy**. | |
| 406 | + | |
| 407 | +Humans keep injecting unexpected observations into the system. | |
| 408 | + | |
| 409 | +From the perspective of future AI training, that unpredictability may be extremely valuable. | |
| 410 | + | |
| 411 | +## The Singularity Could Become a Plateau | |
| 412 | + | |
| 413 | +This suggests an alternative to the traditional singularity curve. | |
| 414 | + | |
| 415 | +Instead of: | |
| 416 | + | |
| 417 | +AI improves → AI improves AI → acceleration → intelligence explosion, | |
| 418 | + | |
| 419 | +we could observe: | |
| 420 | + | |
| 421 | +AI improves → AI generates more of the information environment → synthetic contamination increases → independent information becomes scarcer → marginal training value declines → progress slows. | |
| 422 | + | |
| 423 | +The result would not necessarily be model collapse in the dramatic sense of systems suddenly becoming useless. | |
| 424 | + | |
| 425 | +It could look much more mundane. | |
| 426 | + | |
| 427 | +Each generation becomes more expensive. | |
| 428 | + | |
| 429 | +Improvements become smaller. | |
| 430 | + | |
| 431 | +New training tokens contain less marginal information. | |
| 432 | + | |
| 433 | +Benchmarks saturate. | |
| 434 | + | |
| 435 | +Models become extraordinarily capable but increasingly similar. | |
| 436 | + | |
| 437 | +Progress continues, but the derivative falls. | |
| 438 | + | |
| 439 | +The exponential becomes something closer to an S-curve. | |
| 440 | + | |
| 441 | +And the technological singularity quietly fails to arrive. | |
| 442 | + | |
| 443 | +## But There Is an Escape | |
| 444 | + | |
| 445 | +There is one major reason this pessimistic scenario may never dominate. | |
| 446 | + | |
| 447 | +AI does not have to remain trapped inside text. | |
| 448 | + | |
| 449 | +Agents can interact with the world. | |
| 450 | + | |
| 451 | +Robots can collect physical observations. | |
| 452 | + | |
| 453 | +Scientific systems can run experiments. | |
| 454 | + | |
| 455 | +Models can write software and observe execution results. | |
| 456 | + | |
| 457 | +Theorem provers can verify mathematical claims. | |
| 458 | + | |
| 459 | +Simulations can explore complex environments. | |
| 460 | + | |
| 461 | +Sensors can continuously generate new measurements. | |
| 462 | + | |
| 463 | +Humans can provide feedback and novel information. | |
| 464 | + | |
| 465 | +AI could therefore escape recursive data collapse by becoming increasingly **empirical**. | |
| 466 | + | |
| 467 | +Instead of learning primarily from what civilization has written, future systems may increasingly learn from what they can test. | |
| 468 | + | |
| 469 | +That would represent an important transition. | |
| 470 | + | |
| 471 | +The first generation of AI learned from humanity's archive. | |
| 472 | + | |
| 473 | +The next generation may need to learn from reality itself. | |
| 474 | + | |
| 475 | +## Maybe the Singularity Is About Data, Not Intelligence | |
| 476 | + | |
| 477 | +The traditional singularity narrative focuses on recursive self-improvement of intelligence. | |
| 478 | + | |
| 479 | +But perhaps intelligence was never the only variable that mattered. | |
| 480 | + | |
| 481 | +A system needs computation. | |
| 482 | + | |
| 483 | +It needs algorithms. | |
| 484 | + | |
| 485 | +It needs energy. | |
| 486 | + | |
| 487 | +And it needs information. | |
| 488 | + | |
| 489 | +If any one of those inputs becomes constrained, exponential progress can slow. | |
| 490 | + | |
| 491 | +Generative AI creates the paradoxical possibility that information becomes simultaneously more abundant and more scarce. | |
| 492 | + | |
| 493 | +There may be more text than ever before. | |
| 494 | + | |
| 495 | +More images. | |
| 496 | + | |
| 497 | +More code. | |
| 498 | + | |
| 499 | +More explanations. | |
| 500 | + | |
| 501 | +More answers. | |
| 502 | + | |
| 503 | +And yet the fraction containing genuinely independent information could decline. | |
| 504 | + | |
| 505 | +The internet could become infinitely large while becoming informationally smaller. | |
| 506 | + | |
| 507 | +If that happens, the central challenge of advanced AI will no longer be generating content. | |
| 508 | + | |
| 509 | +Generation will be essentially free. | |
| 510 | + | |
| 511 | +The scarce resource will be determining what came from reality. | |
| 512 | + | |
| 513 | +And perhaps the ultimate bottleneck on artificial intelligence will turn out to be the one thing intelligence alone cannot manufacture: | |
| 514 | + | |
| 515 | +**new truth.** | |
| 516 | + | |
modified
components/app-card.tsx
+10 −2
@@ -6,7 +6,8 @@ | ||
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | 8 | import Image from "next/image"; |
| 9 | −import { Download, Github, Tag } from "lucide-react"; | |
| 9 | +import Link from "next/link"; | |
| 10 | +import { ArrowUpRight, Download, Github, Tag } from "lucide-react"; | |
| 10 | 11 | |
| 11 | 12 | import type { ZyquoApp } from "@/lib/apps"; |
| 12 | 13 | import { buttonVariants } from "@/components/ui/button"; |
@@ -29,7 +30,7 @@ export function AppCard({ app }: { app: ZyquoApp }) { | ||
| 29 | 30 | alt={`${app.name} app icon`} |
| 30 | 31 | width={64} |
| 31 | 32 | height={64} |
| 32 | − className="h-16 w-16 shrink-0 rounded-[1.25rem] shadow-sm ring-1 ring-border/50 transition-transform duration-300 group-hover:scale-105" | |
| 33 | + className="h-16 w-16 shrink-0 rounded-[0.7rem] border border-border shadow-[var(--shadow-soft)] transition-transform duration-300 group-hover:scale-105" | |
| 33 | 34 | /> |
| 34 | 35 | <div className="min-w-0"> |
| 35 | 36 | <CardTitle className="text-lg"> |
@@ -45,6 +46,13 @@ export function AppCard({ app }: { app: ZyquoApp }) { | ||
| 45 | 46 | <p className="text-sm text-muted-foreground">{app.description}</p> |
| 46 | 47 | </CardContent> |
| 47 | 48 | <CardFooter className="flex flex-wrap gap-2"> |
| 49 | + <Link | |
| 50 | + href={`/apps/${app.slug}`} | |
| 51 | + className={cn(buttonVariants({ variant: "secondary", size: "sm" }))} | |
| 52 | + > | |
| 53 | + <ArrowUpRight aria-hidden="true" /> | |
| 54 | + Details | |
| 55 | + </Link> | |
| 48 | 56 | <a |
| 49 | 57 | href={app.repo} |
| 50 | 58 | target="_blank" |
added
components/blog-content.tsx
+121 −0
@@ -0,0 +1,121 @@ | ||
| 1 | +/* | |
| 2 | + blog-content.tsx | |
| 3 | + spboucher.ai Web | |
| 4 | + Author: Simon-Pierre Boucher | |
| 5 | + Mail: contact@spboucher.ai | |
| 6 | +*/ | |
| 7 | + | |
| 8 | +import katex from "katex"; | |
| 9 | +import "katex/dist/katex.min.css"; | |
| 10 | + | |
| 11 | +import type { BlogBlock } from "@/lib/blog"; | |
| 12 | + | |
| 13 | +function renderTex(tex: string, displayMode: boolean): string { | |
| 14 | + return katex.renderToString(tex, { | |
| 15 | + displayMode, | |
| 16 | + throwOnError: false, | |
| 17 | + strict: false, | |
| 18 | + }); | |
| 19 | +} | |
| 20 | + | |
| 21 | +/** Render the **bold** and inline-math \( ... \) spans used by the essays. */ | |
| 22 | +function renderInline(text: string) { | |
| 23 | + return text.split(/(\*\*[^*]+\*\*|\\\(.+?\\\))/g).map((part, i) => { | |
| 24 | + if (part.startsWith("**") && part.endsWith("**")) { | |
| 25 | + return ( | |
| 26 | + <strong key={i} className="font-semibold text-foreground"> | |
| 27 | + {part.slice(2, -2)} | |
| 28 | + </strong> | |
| 29 | + ); | |
| 30 | + } | |
| 31 | + if (part.startsWith("\\(") && part.endsWith("\\)")) { | |
| 32 | + return ( | |
| 33 | + <span | |
| 34 | + key={i} | |
| 35 | + dangerouslySetInnerHTML={{ | |
| 36 | + __html: renderTex(part.slice(2, -2).trim(), false), | |
| 37 | + }} | |
| 38 | + /> | |
| 39 | + ); | |
| 40 | + } | |
| 41 | + return <span key={i}>{part}</span>; | |
| 42 | + }); | |
| 43 | +} | |
| 44 | + | |
| 45 | +/** Long-form essay body with sharp editorial typography. */ | |
| 46 | +export function BlogContent({ blocks }: { blocks: BlogBlock[] }) { | |
| 47 | + let paragraphIndex = 0; | |
| 48 | + | |
| 49 | + return ( | |
| 50 | + <div className="space-y-5"> | |
| 51 | + {blocks.map((block, i) => { | |
| 52 | + switch (block.type) { | |
| 53 | + case "math": | |
| 54 | + return ( | |
| 55 | + <div | |
| 56 | + key={i} | |
| 57 | + className="overflow-x-auto border-y border-border bg-muted/40 px-5 py-4 text-foreground/90" | |
| 58 | + dangerouslySetInnerHTML={{ | |
| 59 | + __html: renderTex(block.tex, true), | |
| 60 | + }} | |
| 61 | + /> | |
| 62 | + ); | |
| 63 | + case "h2": | |
| 64 | + return ( | |
| 65 | + <div key={i} className="pt-7"> | |
| 66 | + <span | |
| 67 | + aria-hidden="true" | |
| 68 | + className="block h-1 w-10 rounded-full bg-gradient-to-r from-primary to-primary/30" | |
| 69 | + /> | |
| 70 | + <h2 className="mt-4 font-display text-2xl font-bold tracking-tight"> | |
| 71 | + {block.text} | |
| 72 | + </h2> | |
| 73 | + </div> | |
| 74 | + ); | |
| 75 | + case "quote": | |
| 76 | + return ( | |
| 77 | + <blockquote | |
| 78 | + key={i} | |
| 79 | + className="border-l-2 border-primary/60 pl-5 font-display text-xl font-medium leading-snug tracking-tight text-foreground/90" | |
| 80 | + > | |
| 81 | + {renderInline(block.text)} | |
| 82 | + </blockquote> | |
| 83 | + ); | |
| 84 | + case "ul": | |
| 85 | + return ( | |
| 86 | + <ul key={i} className="space-y-2 pl-1"> | |
| 87 | + {block.items.map((item) => ( | |
| 88 | + <li | |
| 89 | + key={item} | |
| 90 | + className="flex items-start gap-3 leading-relaxed text-muted-foreground" | |
| 91 | + > | |
| 92 | + <span | |
| 93 | + aria-hidden="true" | |
| 94 | + className="mt-[0.6em] h-1.5 w-1.5 shrink-0 rounded-full bg-primary/70" | |
| 95 | + /> | |
| 96 | + <span>{renderInline(item)}</span> | |
| 97 | + </li> | |
| 98 | + ))} | |
| 99 | + </ul> | |
| 100 | + ); | |
| 101 | + case "p": { | |
| 102 | + paragraphIndex += 1; | |
| 103 | + const isLead = paragraphIndex === 1; | |
| 104 | + return ( | |
| 105 | + <p | |
| 106 | + key={i} | |
| 107 | + className={ | |
| 108 | + isLead | |
| 109 | + ? "text-lg leading-relaxed text-foreground/90 sm:text-xl" | |
| 110 | + : "leading-relaxed text-muted-foreground" | |
| 111 | + } | |
| 112 | + > | |
| 113 | + {renderInline(block.text)} | |
| 114 | + </p> | |
| 115 | + ); | |
| 116 | + } | |
| 117 | + } | |
| 118 | + })} | |
| 119 | + </div> | |
| 120 | + ); | |
| 121 | +} | |
modified
components/icon-tile.tsx
+6 −6
@@ -10,9 +10,9 @@ import type { LucideIcon } from "lucide-react"; | ||
| 10 | 10 | import { cn } from "@/lib/utils"; |
| 11 | 11 | |
| 12 | 12 | const sizes = { |
| 13 | − sm: "h-8 w-8 rounded-xl [&_svg]:h-4 [&_svg]:w-4", | |
| 14 | − md: "h-11 w-11 rounded-2xl [&_svg]:h-5 [&_svg]:w-5", | |
| 15 | − lg: "h-14 w-14 rounded-3xl [&_svg]:h-6 [&_svg]:w-6", | |
| 13 | + sm: "h-8 w-8 rounded-[0.5rem] [&_svg]:h-3.5 [&_svg]:w-3.5", | |
| 14 | + md: "h-10 w-10 rounded-[0.6rem] [&_svg]:h-4 [&_svg]:w-4", | |
| 15 | + lg: "h-12 w-12 rounded-[0.7rem] [&_svg]:h-5 [&_svg]:w-5", | |
| 16 | 16 | } as const; |
| 17 | 17 | |
| 18 | 18 | interface IconTileProps { |
@@ -21,18 +21,18 @@ interface IconTileProps { | ||
| 21 | 21 | className?: string; |
| 22 | 22 | } |
| 23 | 23 | |
| 24 | −/** A lucide icon set in a soft, rounded gradient tile. */ | |
| 24 | +/** A lucide icon set on a flat, hairline-ruled plate — quiet, editorial. */ | |
| 25 | 25 | export function IconTile({ icon: Icon, size = "md", className }: IconTileProps) { |
| 26 | 26 | return ( |
| 27 | 27 | <span |
| 28 | 28 | aria-hidden="true" |
| 29 | 29 | className={cn( |
| 30 | − "inline-flex shrink-0 items-center justify-center bg-gradient-to-br from-primary/15 via-primary/8 to-transparent text-primary ring-1 ring-inset ring-primary/15", | |
| 30 | + "inline-flex shrink-0 items-center justify-center border border-foreground/15 bg-card text-foreground/70 shadow-[var(--shadow-soft)]", | |
| 31 | 31 | sizes[size], |
| 32 | 32 | className, |
| 33 | 33 | )} |
| 34 | 34 | > |
| 35 | − <Icon strokeWidth={1.75} /> | |
| 35 | + <Icon strokeWidth={1.5} /> | |
| 36 | 36 | </span> |
| 37 | 37 | ); |
| 38 | 38 | } |
modified
components/pill-link.tsx
+1 −1
@@ -32,7 +32,7 @@ export function PillLink({ | ||
| 32 | 32 | ? { target: "_blank", rel: "noopener noreferrer" } |
| 33 | 33 | : undefined)} |
| 34 | 34 | className={cn( |
| 35 | − "inline-flex items-center gap-1.5 rounded-full border border-border/70 bg-card/70 px-3.5 py-1.5 text-xs font-medium text-foreground/80 shadow-sm transition-all hover:-translate-y-px hover:border-primary/40 hover:bg-primary/10 hover:text-primary", | |
| 35 | + "inline-flex items-center gap-1.5 rounded-[0.5rem] border border-foreground/15 bg-card px-3 py-1.5 text-xs font-medium text-foreground/80 shadow-[var(--shadow-soft)] transition-all hover:-translate-y-px hover:border-primary/50 hover:text-primary", | |
| 36 | 36 | className, |
| 37 | 37 | )} |
| 38 | 38 | > |
modified
components/project-card.tsx
+12 −4
@@ -6,7 +6,8 @@ | ||
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | 8 | import Image from "next/image"; |
| 9 | −import { Download, ExternalLink, Github } from "lucide-react"; | |
| 9 | +import Link from "next/link"; | |
| 10 | +import { ArrowUpRight, Download, ExternalLink, Github } from "lucide-react"; | |
| 10 | 11 | |
| 11 | 12 | import type { OpenSourceProject } from "@/lib/apps"; |
| 12 | 13 | import { buttonVariants } from "@/components/ui/button"; |
@@ -30,12 +31,12 @@ export function ProjectCard({ project }: { project: OpenSourceProject }) { | ||
| 30 | 31 | alt={`${project.name} logo`} |
| 31 | 32 | width={64} |
| 32 | 33 | height={64} |
| 33 | − className="h-16 w-16 shrink-0 rounded-[1.25rem] shadow-sm ring-1 ring-border/50 transition-transform duration-300 group-hover:scale-105" | |
| 34 | + className="h-16 w-16 shrink-0 rounded-[0.7rem] border border-border shadow-[var(--shadow-soft)] transition-transform duration-300 group-hover:scale-105" | |
| 34 | 35 | /> |
| 35 | 36 | ) : ( |
| 36 | 37 | <div |
| 37 | 38 | aria-hidden="true" |
| 38 | − className="flex h-16 w-16 shrink-0 items-center justify-center rounded-[1.25rem] bg-gradient-to-br from-primary/12 via-primary/6 to-transparent text-3xl shadow-sm ring-1 ring-inset ring-primary/15 transition-transform duration-300 group-hover:scale-105" | |
| 39 | + className="flex h-16 w-16 shrink-0 items-center justify-center rounded-[0.7rem] border border-foreground/15 bg-card text-3xl shadow-[var(--shadow-soft)] transition-transform duration-300 group-hover:scale-105" | |
| 39 | 40 | > |
| 40 | 41 | {project.emoji} |
| 41 | 42 | </div> |
@@ -49,6 +50,13 @@ export function ProjectCard({ project }: { project: OpenSourceProject }) { | ||
| 49 | 50 | <p className="text-sm text-muted-foreground">{project.description}</p> |
| 50 | 51 | </CardContent> |
| 51 | 52 | <CardFooter className="flex flex-wrap items-center gap-2"> |
| 53 | + <Link | |
| 54 | + href={`/apps/${project.slug}`} | |
| 55 | + className={cn(buttonVariants({ variant: "secondary", size: "sm" }))} | |
| 56 | + > | |
| 57 | + <ArrowUpRight aria-hidden="true" /> | |
| 58 | + Details | |
| 59 | + </Link> | |
| 52 | 60 | <a |
| 53 | 61 | href={project.repo} |
| 54 | 62 | target="_blank" |
@@ -78,7 +86,7 @@ export function ProjectCard({ project }: { project: OpenSourceProject }) { | ||
| 78 | 86 | Download DMG |
| 79 | 87 | </a> |
| 80 | 88 | ) : null} |
| 81 | − <span className="ml-auto rounded-full border border-border/70 bg-muted/60 px-2.5 py-0.5 text-xs font-medium text-muted-foreground"> | |
| 89 | + <span className="ml-auto rounded-[0.4rem] border border-border bg-muted/60 px-2 py-0.5 font-mono text-[0.62rem] uppercase tracking-[0.1em] text-muted-foreground"> | |
| 82 | 90 | {project.language} |
| 83 | 91 | </span> |
| 84 | 92 | </CardFooter> |
modified
components/section-heading.tsx
+10 −6
@@ -32,25 +32,29 @@ export function SectionHeading({ | ||
| 32 | 32 | return ( |
| 33 | 33 | <div className={cn("flex items-start gap-4", className)}> |
| 34 | 34 | {icon && <IconTile icon={icon} size={isPageTitle ? "lg" : "md"} />} |
| 35 | − <div className="min-w-0"> | |
| 35 | + <div className="min-w-0 flex-1"> | |
| 36 | 36 | {eyebrow && ( |
| 37 | − <p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary"> | |
| 37 | + <p className="flex items-center gap-3 font-mono text-[0.68rem] font-medium uppercase tracking-[0.22em] text-primary"> | |
| 38 | + <span | |
| 39 | + aria-hidden="true" | |
| 40 | + className="h-px w-6 shrink-0 bg-primary/60" | |
| 41 | + /> | |
| 38 | 42 | {eyebrow} |
| 39 | 43 | </p> |
| 40 | 44 | )} |
| 41 | 45 | <Tag |
| 42 | 46 | className={cn( |
| 43 | − "tracking-tight", | |
| 47 | + "font-display tracking-tight", | |
| 44 | 48 | isPageTitle |
| 45 | − ? "mt-1 text-3xl font-bold sm:text-4xl" | |
| 46 | − : "mt-0.5 text-xl font-semibold", | |
| 49 | + ? "mt-2 text-4xl font-semibold sm:text-5xl" | |
| 50 | + : "mt-1 text-2xl font-semibold", | |
| 47 | 51 | !eyebrow && "mt-0", |
| 48 | 52 | )} |
| 49 | 53 | > |
| 50 | 54 | {title} |
| 51 | 55 | </Tag> |
| 52 | 56 | {description && ( |
| 53 | − <p className="mt-2 max-w-3xl text-sm leading-relaxed text-muted-foreground"> | |
| 57 | + <p className="mt-2.5 max-w-3xl text-sm leading-relaxed text-muted-foreground"> | |
| 54 | 58 | {description} |
| 55 | 59 | </p> |
| 56 | 60 | )} |
modified
components/site-footer.tsx
+66 −48
@@ -8,70 +8,88 @@ | ||
| 8 | 8 | import Link from "next/link"; |
| 9 | 9 | import { Github, Mail } from "lucide-react"; |
| 10 | 10 | |
| 11 | +const footerNav = [ | |
| 12 | + { href: "/research", label: "Research" }, | |
| 13 | + { href: "/teaching", label: "Teaching" }, | |
| 14 | + { href: "/apps", label: "Apps" }, | |
| 15 | + { href: "/blog", label: "Blog" }, | |
| 16 | + { href: "/cv", label: "CV" }, | |
| 17 | +]; | |
| 18 | + | |
| 11 | 19 | export function SiteFooter() { |
| 12 | 20 | return ( |
| 13 | − <footer className="px-4 pb-6 pt-10 sm:px-6"> | |
| 14 | − <div className="mx-auto max-w-5xl rounded-[1.75rem] border border-border/60 bg-card/60 px-6 py-8 shadow-[var(--shadow-soft)] backdrop-blur-xl sm:px-8"> | |
| 15 | − <div className="flex flex-col items-center justify-between gap-6 sm:flex-row"> | |
| 16 | − <div className="flex items-center gap-3"> | |
| 17 | − <span | |
| 18 | − aria-hidden="true" | |
| 19 | − className="flex h-9 w-9 items-center justify-center rounded-xl bg-gradient-to-br from-primary to-primary/70 text-xs font-bold tracking-tight text-primary-foreground shadow-sm" | |
| 20 | − > | |
| 21 | − SB | |
| 22 | − </span> | |
| 23 | − <div> | |
| 24 | − <p className="text-sm font-semibold tracking-tight"> | |
| 25 | − Simon-Pierre Boucher | |
| 26 | − </p> | |
| 27 | − <p className="text-xs text-muted-foreground"> | |
| 28 | − Financial econometrics researcher & macOS/AI developer | |
| 29 | − </p> | |
| 21 | + <footer className="mt-16 border-t border-border/80"> | |
| 22 | + <div className="mx-auto max-w-5xl px-4 py-10 sm:px-6"> | |
| 23 | + <div className="flex flex-col gap-8 sm:flex-row sm:items-start sm:justify-between"> | |
| 24 | + <div className="max-w-sm"> | |
| 25 | + <p className="font-display text-lg font-semibold tracking-tight"> | |
| 26 | + Simon-Pierre Boucher | |
| 27 | + </p> | |
| 28 | + <p className="mt-1.5 text-sm leading-relaxed text-muted-foreground"> | |
| 29 | + Financial econometrics researcher & macOS/AI developer. | |
| 30 | + Professor, Département des sciences administratives, UQO. | |
| 31 | + </p> | |
| 32 | + <div className="mt-4 flex items-center gap-2"> | |
| 33 | + <a | |
| 34 | + href="mailto:contact@spboucher.ai" | |
| 35 | + aria-label="Email contact@spboucher.ai" | |
| 36 | + className="inline-flex h-8 w-8 items-center justify-center border border-foreground/15 bg-card text-muted-foreground transition-all hover:-translate-y-px hover:border-foreground/50 hover:text-foreground" | |
| 37 | + > | |
| 38 | + <Mail className="h-3.5 w-3.5" aria-hidden="true" /> | |
| 39 | + </a> | |
| 40 | + <a | |
| 41 | + href="https://github.com/spboucher-ai" | |
| 42 | + target="_blank" | |
| 43 | + rel="noopener noreferrer" | |
| 44 | + aria-label="GitHub profile" | |
| 45 | + className="inline-flex h-8 w-8 items-center justify-center border border-foreground/15 bg-card text-muted-foreground transition-all hover:-translate-y-px hover:border-foreground/50 hover:text-foreground" | |
| 46 | + > | |
| 47 | + <Github className="h-3.5 w-3.5" aria-hidden="true" /> | |
| 48 | + </a> | |
| 30 | 49 | </div> |
| 31 | 50 | </div> |
| 32 | 51 | |
| 33 | − <nav | |
| 34 | − aria-label="Footer" | |
| 35 | − className="flex flex-wrap items-center justify-center gap-x-5 gap-y-2 text-sm text-muted-foreground" | |
| 36 | − > | |
| 37 | − <Link href="/research" className="transition-colors hover:text-primary"> | |
| 38 | − Research | |
| 39 | − </Link> | |
| 40 | − <Link href="/teaching" className="transition-colors hover:text-primary"> | |
| 41 | − Teaching | |
| 42 | − </Link> | |
| 43 | − <Link href="/apps" className="transition-colors hover:text-primary"> | |
| 44 | − Apps | |
| 45 | − </Link> | |
| 46 | − <Link href="/cv" className="transition-colors hover:text-primary"> | |
| 47 | − CV | |
| 48 | − </Link> | |
| 52 | + <nav aria-label="Footer" className="grid gap-2"> | |
| 53 | + <p className="font-mono text-[0.66rem] font-medium uppercase tracking-[0.2em] text-muted-foreground/70"> | |
| 54 | + Index | |
| 55 | + </p> | |
| 56 | + {footerNav.map((item) => ( | |
| 57 | + <Link | |
| 58 | + key={item.href} | |
| 59 | + href={item.href} | |
| 60 | + className="w-fit font-mono text-xs uppercase tracking-[0.12em] text-muted-foreground transition-colors hover:text-primary" | |
| 61 | + > | |
| 62 | + {item.label} | |
| 63 | + </Link> | |
| 64 | + ))} | |
| 49 | 65 | </nav> |
| 50 | 66 | |
| 51 | − <div className="flex items-center gap-2"> | |
| 67 | + <div className="grid gap-2"> | |
| 68 | + <p className="font-mono text-[0.66rem] font-medium uppercase tracking-[0.2em] text-muted-foreground/70"> | |
| 69 | + Contact | |
| 70 | + </p> | |
| 52 | 71 | <a |
| 53 | − href="mailto:contact@spboucher.ai" | |
| 54 | − aria-label="Email contact@spboucher.ai" | |
| 55 | − className="inline-flex h-9 w-9 items-center justify-center rounded-full border border-border/70 bg-card/70 text-muted-foreground transition-all hover:-translate-y-px hover:border-primary/40 hover:bg-primary/10 hover:text-primary" | |
| 72 | + href="mailto:simon-pierre.boucher@uqo.ca" | |
| 73 | + className="w-fit font-mono text-xs text-muted-foreground transition-colors hover:text-primary" | |
| 56 | 74 | > |
| 57 | − <Mail className="h-4 w-4" aria-hidden="true" /> | |
| 75 | + simon-pierre.boucher@uqo.ca | |
| 58 | 76 | </a> |
| 59 | 77 | <a |
| 60 | − href="https://github.com/spboucher-ai" | |
| 61 | − target="_blank" | |
| 62 | − rel="noopener noreferrer" | |
| 63 | − aria-label="GitHub profile" | |
| 64 | − className="inline-flex h-9 w-9 items-center justify-center rounded-full border border-border/70 bg-card/70 text-muted-foreground transition-all hover:-translate-y-px hover:border-primary/40 hover:bg-primary/10 hover:text-primary" | |
| 78 | + href="mailto:contact@spboucher.ai" | |
| 79 | + className="w-fit font-mono text-xs text-muted-foreground transition-colors hover:text-primary" | |
| 65 | 80 | > |
| 66 | − <Github className="h-4 w-4" aria-hidden="true" /> | |
| 81 | + contact@spboucher.ai | |
| 67 | 82 | </a> |
| 83 | + <p className="font-mono text-xs text-muted-foreground/80"> | |
| 84 | + UQO, Gatineau — Pavillon Alexandre-Taché | |
| 85 | + </p> | |
| 68 | 86 | </div> |
| 69 | 87 | </div> |
| 70 | 88 | |
| 71 | − <p className="mt-6 border-t border-border/50 pt-5 text-center text-xs text-muted-foreground"> | |
| 72 | − © {new Date().getFullYear()} Simon-Pierre Boucher — Université du | |
| 73 | − Québec en Outaouais (UQO) · www.spboucher.ai | |
| 74 | − </p> | |
| 89 | + <div className="mt-10 flex flex-col gap-2 border-t border-border/60 pt-5 font-mono text-[0.66rem] uppercase tracking-[0.14em] text-muted-foreground/70 sm:flex-row sm:items-center sm:justify-between"> | |
| 90 | + <p>© {new Date().getFullYear()} Simon-Pierre Boucher · www.spboucher.ai</p> | |
| 91 | + <p>Set in Fraunces, Inter & IBM Plex Mono</p> | |
| 92 | + </div> | |
| 75 | 93 | </div> |
| 76 | 94 | </footer> |
| 77 | 95 | ); |
modified
components/site-header.tsx
+71 −65
@@ -21,6 +21,7 @@ const navItems = [ | ||
| 21 | 21 | { href: "/research", label: "Research" }, |
| 22 | 22 | { href: "/teaching", label: "Teaching" }, |
| 23 | 23 | { href: "/apps", label: "Apps" }, |
| 24 | + { href: "/blog", label: "Blog" }, | |
| 24 | 25 | { href: "/cv", label: "CV" }, |
| 25 | 26 | ]; |
| 26 | 27 | |
@@ -28,77 +29,82 @@ export function SiteHeader() { | ||
| 28 | 29 | const pathname = usePathname(); |
| 29 | 30 | const [open, setOpen] = React.useState(false); |
| 30 | 31 | |
| 31 | − return ( | |
| 32 | − <header className="sticky top-0 z-50 w-full px-4 pt-3 sm:px-6"> | |
| 33 | − <div className="mx-auto max-w-5xl rounded-3xl border border-border/60 bg-background/70 shadow-[var(--shadow-soft)] backdrop-blur-xl"> | |
| 34 | − <div className="flex h-14 items-center justify-between pl-5 pr-3"> | |
| 35 | − <Link href="/" className="flex items-center gap-3"> | |
| 36 | − <span | |
| 37 | − aria-hidden="true" | |
| 38 | − className="flex h-8 w-8 items-center justify-center rounded-xl bg-gradient-to-br from-primary to-primary/70 text-[11px] font-bold tracking-tight text-primary-foreground shadow-sm" | |
| 39 | − > | |
| 40 | − SB | |
| 41 | − </span> | |
| 42 | − <span className="font-semibold tracking-tight"> | |
| 43 | − Simon-Pierre Boucher | |
| 44 | − </span> | |
| 45 | − </Link> | |
| 32 | + const isActive = (href: string) => | |
| 33 | + pathname === href || (href !== "/" && pathname.startsWith(`${href}/`)); | |
| 46 | 34 | |
| 47 | − <nav className="hidden items-center gap-1 md:flex" aria-label="Main"> | |
| 48 | − {navItems.map((item) => ( | |
| 49 | − <Link | |
| 50 | − key={item.href} | |
| 51 | − href={item.href} | |
| 52 | − className={cn( | |
| 53 | − "rounded-full px-4 py-2 text-sm font-medium transition-colors", | |
| 54 | − pathname === item.href | |
| 55 | − ? "bg-primary/10 text-primary" | |
| 56 | − : "text-muted-foreground hover:bg-accent hover:text-accent-foreground" | |
| 57 | − )} | |
| 58 | − > | |
| 59 | − {item.label} | |
| 60 | − </Link> | |
| 61 | − ))} | |
| 62 | − <ThemeToggle /> | |
| 63 | − </nav> | |
| 35 | + return ( | |
| 36 | + <header className="sticky top-0 z-50 w-full border-b border-border/80 bg-background/88 backdrop-blur-xl"> | |
| 37 | + <div className="mx-auto flex h-16 max-w-5xl items-center justify-between px-4 sm:px-6"> | |
| 38 | + <Link href="/" className="group flex items-baseline gap-2.5"> | |
| 39 | + <span | |
| 40 | + aria-hidden="true" | |
| 41 | + className="self-center border border-foreground bg-foreground px-1.5 py-0.5 font-mono text-[10px] font-semibold tracking-[0.08em] text-background transition-colors duration-300 group-hover:bg-transparent group-hover:text-foreground" | |
| 42 | + > | |
| 43 | + SB | |
| 44 | + </span> | |
| 45 | + <span className="font-display text-[1.05rem] font-semibold tracking-tight"> | |
| 46 | + Simon-Pierre Boucher | |
| 47 | + </span> | |
| 48 | + </Link> | |
| 64 | 49 | |
| 65 | − <div className="flex items-center gap-1 md:hidden"> | |
| 66 | − <ThemeToggle /> | |
| 67 | − <Button | |
| 68 | − variant="ghost" | |
| 69 | − size="icon" | |
| 70 | − aria-label={open ? "Close menu" : "Open menu"} | |
| 71 | − aria-expanded={open} | |
| 72 | − onClick={() => setOpen(!open)} | |
| 50 | + <nav className="hidden items-center gap-0.5 md:flex" aria-label="Main"> | |
| 51 | + {navItems.map((item) => ( | |
| 52 | + <Link | |
| 53 | + key={item.href} | |
| 54 | + href={item.href} | |
| 55 | + className={cn( | |
| 56 | + "relative px-3 py-2 font-mono text-[0.7rem] font-medium uppercase tracking-[0.14em] transition-colors", | |
| 57 | + isActive(item.href) | |
| 58 | + ? "text-primary after:absolute after:inset-x-3 after:-bottom-[1.35rem] after:h-[2px] after:bg-primary" | |
| 59 | + : "text-muted-foreground hover:text-foreground" | |
| 60 | + )} | |
| 73 | 61 | > |
| 74 | − {open ? <X /> : <Menu />} | |
| 75 | − </Button> | |
| 76 | − </div> | |
| 77 | − </div> | |
| 62 | + {item.label} | |
| 63 | + </Link> | |
| 64 | + ))} | |
| 65 | + <span aria-hidden="true" className="mx-2 h-4 w-px bg-border" /> | |
| 66 | + <ThemeToggle /> | |
| 67 | + </nav> | |
| 78 | 68 | |
| 79 | − {open && ( | |
| 80 | − <nav | |
| 81 | − className="border-t border-border/60 px-4 pb-4 pt-2 md:hidden" | |
| 82 | − aria-label="Mobile" | |
| 69 | + <div className="flex items-center gap-1 md:hidden"> | |
| 70 | + <ThemeToggle /> | |
| 71 | + <Button | |
| 72 | + variant="ghost" | |
| 73 | + size="icon" | |
| 74 | + aria-label={open ? "Close menu" : "Open menu"} | |
| 75 | + aria-expanded={open} | |
| 76 | + onClick={() => setOpen(!open)} | |
| 83 | 77 | > |
| 84 | − {navItems.map((item) => ( | |
| 85 | − <Link | |
| 86 | − key={item.href} | |
| 87 | − href={item.href} | |
| 88 | − onClick={() => setOpen(false)} | |
| 89 | − className={cn( | |
| 90 | − "block rounded-full px-4 py-2 text-sm font-medium transition-colors", | |
| 91 | − pathname === item.href | |
| 92 | − ? "bg-primary/10 text-primary" | |
| 93 | − : "text-muted-foreground hover:bg-accent hover:text-accent-foreground" | |
| 94 | − )} | |
| 95 | − > | |
| 96 | − {item.label} | |
| 97 | − </Link> | |
| 98 | − ))} | |
| 99 | − </nav> | |
| 100 | − )} | |
| 78 | + {open ? <X /> : <Menu />} | |
| 79 | + </Button> | |
| 80 | + </div> | |
| 101 | 81 | </div> |
| 82 | + | |
| 83 | + {open && ( | |
| 84 | + <nav | |
| 85 | + className="border-t border-border/80 px-4 pb-4 pt-2 md:hidden" | |
| 86 | + aria-label="Mobile" | |
| 87 | + > | |
| 88 | + {navItems.map((item) => ( | |
| 89 | + <Link | |
| 90 | + key={item.href} | |
| 91 | + href={item.href} | |
| 92 | + onClick={() => setOpen(false)} | |
| 93 | + className={cn( | |
| 94 | + "flex items-center justify-between border-b border-border/50 px-1 py-3 font-mono text-xs font-medium uppercase tracking-[0.14em] transition-colors last:border-0", | |
| 95 | + isActive(item.href) | |
| 96 | + ? "text-primary" | |
| 97 | + : "text-muted-foreground hover:text-foreground" | |
| 98 | + )} | |
| 99 | + > | |
| 100 | + {item.label} | |
| 101 | + {isActive(item.href) && ( | |
| 102 | + <span aria-hidden="true" className="h-1.5 w-1.5 bg-primary" /> | |
| 103 | + )} | |
| 104 | + </Link> | |
| 105 | + ))} | |
| 106 | + </nav> | |
| 107 | + )} | |
| 102 | 108 | </header> |
| 103 | 109 | ); |
| 104 | 110 | } |
modified
components/ui/badge.tsx
+4 −6
@@ -11,15 +11,13 @@ import { cva, type VariantProps } from "class-variance-authority"; | ||
| 11 | 11 | import { cn } from "@/lib/utils"; |
| 12 | 12 | |
| 13 | 13 | const badgeVariants = cva( |
| 14 | − "inline-flex items-center gap-1 rounded-full border px-3 py-1 text-xs font-medium transition-colors", | |
| 14 | + "inline-flex items-center gap-1 rounded-[0.4rem] border px-2 py-[0.2rem] font-mono text-[0.66rem] font-medium uppercase tracking-[0.1em] transition-colors", | |
| 15 | 15 | { |
| 16 | 16 | variants: { |
| 17 | 17 | variant: { |
| 18 | − default: | |
| 19 | − "border-transparent bg-primary/10 text-primary ring-1 ring-inset ring-primary/15", | |
| 20 | − secondary: | |
| 21 | − "border-transparent bg-secondary/80 text-secondary-foreground", | |
| 22 | − outline: "border-border/70 bg-card/50 text-muted-foreground", | |
| 18 | + default: "border-primary/25 bg-primary/8 text-primary", | |
| 19 | + secondary: "border-border bg-secondary/70 text-secondary-foreground", | |
| 20 | + outline: "border-border bg-transparent text-muted-foreground", | |
| 23 | 21 | }, |
| 24 | 22 | }, |
| 25 | 23 | defaultVariants: { |
modified
components/ui/button.tsx
+4 −4
@@ -11,16 +11,16 @@ import { cva, type VariantProps } from "class-variance-authority"; | ||
| 11 | 11 | import { cn } from "@/lib/utils"; |
| 12 | 12 | |
| 13 | 13 | const buttonVariants = cva( |
| 14 | − "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-full text-sm font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 active:scale-[0.98] [&_svg]:size-4 [&_svg]:shrink-0", | |
| 14 | + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[0.65rem] text-sm font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 active:scale-[0.98] [&_svg]:size-4 [&_svg]:shrink-0", | |
| 15 | 15 | { |
| 16 | 16 | variants: { |
| 17 | 17 | variant: { |
| 18 | 18 | default: |
| 19 | − "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90 hover:shadow-md", | |
| 19 | + "bg-foreground text-background shadow-sm hover:bg-foreground/88 hover:-translate-y-px", | |
| 20 | 20 | secondary: |
| 21 | − "bg-secondary text-secondary-foreground hover:bg-secondary/70", | |
| 21 | + "border border-border bg-secondary text-secondary-foreground hover:border-foreground/25 hover:bg-secondary/60", | |
| 22 | 22 | outline: |
| 23 | − "border border-border/70 bg-card/70 shadow-sm backdrop-blur hover:border-primary/40 hover:bg-primary/10 hover:text-primary", | |
| 23 | + "border border-foreground/20 bg-transparent hover:-translate-y-px hover:border-foreground/60 hover:bg-card", | |
| 24 | 24 | ghost: "hover:bg-accent hover:text-accent-foreground", |
| 25 | 25 | link: "text-primary underline-offset-4 hover:underline", |
| 26 | 26 | }, |
modified
components/ui/card.tsx
+1 −1
@@ -14,7 +14,7 @@ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElemen | ||
| 14 | 14 | <div |
| 15 | 15 | ref={ref} |
| 16 | 16 | className={cn( |
| 17 | − "rounded-[1.75rem] border border-border/60 bg-card text-card-foreground shadow-[var(--shadow-soft)] transition-all duration-300", | |
| 17 | + "rounded-[0.9rem] border border-border bg-card text-card-foreground shadow-[var(--shadow-soft)] transition-all duration-300", | |
| 18 | 18 | className |
| 19 | 19 | )} |
| 20 | 20 | {...props} |
added
cv-source/cv.html
+394 −0
@@ -0,0 +1,394 @@ | ||
| 1 | +<!-- | |
| 2 | + cv.html | |
| 3 | + spboucher.ai Web | |
| 4 | + Author: Simon-Pierre Boucher | |
| 5 | + Mail: contact@spboucher.ai | |
| 6 | +--> | |
| 7 | +<!doctype html> | |
| 8 | +<html lang="en"> | |
| 9 | +<head> | |
| 10 | +<meta charset="utf-8"> | |
| 11 | +<title>Simon-Pierre Boucher — Curriculum Vitae</title> | |
| 12 | +<link rel="preconnect" href="https://fonts.googleapis.com"> | |
| 13 | +<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet"> | |
| 14 | +<style> | |
| 15 | + :root { | |
| 16 | + --accent: #4a55d2; | |
| 17 | + --ink: #1c2233; | |
| 18 | + --muted: #5b6474; | |
| 19 | + --faint: #8a93a5; | |
| 20 | + --line: #e3e6ee; | |
| 21 | + --chip: #f2f4fa; | |
| 22 | + } | |
| 23 | + * { margin: 0; padding: 0; box-sizing: border-box; } | |
| 24 | + @page { size: letter; margin: 14mm 15mm 16mm 15mm; } | |
| 25 | + html { -webkit-print-color-adjust: exact; print-color-adjust: exact; } | |
| 26 | + body { | |
| 27 | + font-family: "Inter", -apple-system, "Helvetica Neue", Arial, sans-serif; | |
| 28 | + font-size: 9.4pt; line-height: 1.5; color: var(--ink); | |
| 29 | + } | |
| 30 | + a { color: var(--accent); text-decoration: none; } | |
| 31 | + .display { font-family: "Space Grotesk", "Inter", sans-serif; letter-spacing: -0.02em; } | |
| 32 | + | |
| 33 | + /* Header */ | |
| 34 | + header { display: flex; align-items: center; gap: 18px; padding-bottom: 12px; border-bottom: 2px solid var(--ink); } | |
| 35 | + header img { width: 72px; height: 78px; object-fit: cover; object-position: top; border-radius: 14px; } | |
| 36 | + header h1 { font-size: 21pt; font-weight: 700; } | |
| 37 | + header .role { margin-top: 2px; font-size: 10.5pt; font-weight: 600; color: var(--accent); } | |
| 38 | + header .inst { color: var(--muted); font-size: 9pt; } | |
| 39 | + .contact { margin-top: 6px; display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: 8.4pt; color: var(--muted); } | |
| 40 | + .contact b { color: var(--ink); font-weight: 600; } | |
| 41 | + | |
| 42 | + /* Sections */ | |
| 43 | + section { margin-top: 14px; page-break-inside: auto; } | |
| 44 | + h2 { | |
| 45 | + font-family: "Space Grotesk", "Inter", sans-serif; | |
| 46 | + font-size: 10.5pt; font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em; | |
| 47 | + color: var(--ink); display: flex; align-items: center; gap: 8px; margin-bottom: 7px; | |
| 48 | + } | |
| 49 | + h2::before { content: ""; width: 16px; height: 3px; border-radius: 2px; background: var(--accent); } | |
| 50 | + h2::after { content: ""; flex: 1; height: 1px; background: var(--line); } | |
| 51 | + h3 { font-size: 9.6pt; font-weight: 650; margin: 8px 0 3px; } | |
| 52 | + | |
| 53 | + .entry { display: flex; justify-content: space-between; gap: 16px; margin-bottom: 6px; page-break-inside: avoid; } | |
| 54 | + .entry .body { min-width: 0; } | |
| 55 | + .entry .title { font-weight: 600; } | |
| 56 | + .entry .sub { color: var(--muted); font-size: 8.8pt; } | |
| 57 | + .entry .desc { color: var(--muted); font-size: 8.8pt; margin-top: 1px; } | |
| 58 | + .period { white-space: nowrap; font-size: 8.4pt; font-weight: 600; color: var(--accent); padding-top: 1px; } | |
| 59 | + | |
| 60 | + .num { color: var(--faint); font-weight: 600; font-size: 8.4pt; margin-right: 4px; } | |
| 61 | + | |
| 62 | + p.profile { color: var(--muted); text-align: justify; } | |
| 63 | + | |
| 64 | + /* Tables */ | |
| 65 | + table { width: 100%; border-collapse: collapse; font-size: 8.8pt; margin-top: 2px; } | |
| 66 | + th { text-align: left; font-size: 7.8pt; text-transform: uppercase; letter-spacing: 0.08em; color: var(--faint); padding: 2px 8px 4px 0; border-bottom: 1px solid var(--line); } | |
| 67 | + td { padding: 3.5px 8px 3.5px 0; border-bottom: 1px solid var(--line); vertical-align: top; } | |
| 68 | + tr:last-child td { border-bottom: none; } | |
| 69 | + td.code { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 8pt; color: var(--accent); white-space: nowrap; } | |
| 70 | + td.dim { color: var(--muted); } | |
| 71 | + | |
| 72 | + /* Chips */ | |
| 73 | + .chips { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 3px; } | |
| 74 | + .chip { background: var(--chip); border: 1px solid var(--line); border-radius: 20px; padding: 1.5px 8px; font-size: 8pt; color: var(--ink); } | |
| 75 | + .skill-group { margin-bottom: 7px; page-break-inside: avoid; } | |
| 76 | + .skill-group .label { font-size: 8pt; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; color: var(--accent); } | |
| 77 | + | |
| 78 | + /* Apps grid */ | |
| 79 | + .apps { display: grid; grid-template-columns: 1fr 1fr; gap: 5px 16px; } | |
| 80 | + .app { page-break-inside: avoid; } | |
| 81 | + .app .name { font-weight: 600; font-size: 9pt; } | |
| 82 | + .app .tag { color: var(--muted); font-size: 8.4pt; } | |
| 83 | + .app .url { font-size: 7.8pt; } | |
| 84 | + | |
| 85 | + footer { margin-top: 16px; padding-top: 8px; border-top: 1px solid var(--line); font-size: 7.8pt; color: var(--faint); display: flex; justify-content: space-between; } | |
| 86 | +</style> | |
| 87 | +</head> | |
| 88 | +<body> | |
| 89 | + | |
| 90 | +<header> | |
| 91 | + <img src="../public/profile/simon-pierre-boucher.jpeg" alt="Portrait of Simon-Pierre Boucher"> | |
| 92 | + <div> | |
| 93 | + <h1 class="display">Simon-Pierre Boucher</h1> | |
| 94 | + <p class="role">Professor — Department of Administrative Sciences</p> | |
| 95 | + <p class="inst">Université du Québec en Outaouais (UQO), Gatineau — Pavillon Alexandre-Taché</p> | |
| 96 | + <div class="contact"> | |
| 97 | + <span><b>Academic</b> simon-pierre.boucher@uqo.ca</span> | |
| 98 | + <span><b>Personal</b> contact@spboucher.ai</span> | |
| 99 | + <span><b>Web</b> www.spboucher.ai</span> | |
| 100 | + <span><b>GitHub</b> github.com/spboucher-ai</span> | |
| 101 | + <span><b>Languages</b> French & English (bilingual)</span> | |
| 102 | + </div> | |
| 103 | + </div> | |
| 104 | +</header> | |
| 105 | + | |
| 106 | +<section> | |
| 107 | + <h2>Profile</h2> | |
| 108 | + <p class="profile">Professor in the Department of Administrative Sciences at Université du Québec en Outaouais (UQO). Research in financial econometrics, commodity markets, monetary policy announcements, high-frequency finance, volatility modelling, textual analysis, and financialization. Completing a Ph.D. in Business Administration (Finance and Insurance) at Université Laval under the supervision of Prof. Marie-Hélène Gagnon and Prof. Gabriel J. Power. Alongside academic work, builds native macOS applications focused on AI and local LLMs (the Zyquo suite) and open-source web platforms for financial and housing-market analytics.</p> | |
| 109 | +</section> | |
| 110 | + | |
| 111 | +<section> | |
| 112 | + <h2>Positions</h2> | |
| 113 | + <div class="entry"> | |
| 114 | + <div class="body"> | |
| 115 | + <p class="title">Professor — Department of Administrative Sciences</p> | |
| 116 | + <p class="sub">Université du Québec en Outaouais (UQO), Gatineau</p> | |
| 117 | + <p class="desc">Courses in real-estate valuation (IMM1003 — Éléments d'évaluation immobilière; IMM1033 — Méthodes du coût); applied research in hedonic pricing and housing-market measurement (UQO Working Paper Series).</p> | |
| 118 | + </div> | |
| 119 | + <span class="period">Present</span> | |
| 120 | + </div> | |
| 121 | + <div class="entry"> | |
| 122 | + <div class="body"> | |
| 123 | + <p class="title">Lecturer</p> | |
| 124 | + <p class="sub">Université Laval, Québec City</p> | |
| 125 | + <p class="desc">Capital Markets, Financial Econometrics I, Financial Management, and Financial Theory at the undergraduate and graduate levels.</p> | |
| 126 | + </div> | |
| 127 | + <span class="period">2021 – Present</span> | |
| 128 | + </div> | |
| 129 | + <div class="entry"> | |
| 130 | + <div class="body"> | |
| 131 | + <p class="title">Teaching Assistant</p> | |
| 132 | + <p class="sub">Université Laval, Québec City</p> | |
| 133 | + <p class="desc">Portfolio Management, Corporate Finance (undergraduate & graduate), Financial Strategies & Policies I, and Financial Theory.</p> | |
| 134 | + </div> | |
| 135 | + <span class="period">2018 – 2021</span> | |
| 136 | + </div> | |
| 137 | +</section> | |
| 138 | + | |
| 139 | +<section> | |
| 140 | + <h2>Education</h2> | |
| 141 | + <div class="entry"> | |
| 142 | + <div class="body"> | |
| 143 | + <p class="title">Ph.D. in Business Administration (Finance and Insurance)</p> | |
| 144 | + <p class="sub">Université Laval — supervisors: Prof. Marie-Hélène Gagnon & Prof. Gabriel J. Power</p> | |
| 145 | + <p class="desc">Thesis: <i>Three Essays on High-Frequency Return and Volatility Dynamics in Commodities and Financial Futures Markets</i> (188 pages).</p> | |
| 146 | + </div> | |
| 147 | + <span class="period">2020 – Present</span> | |
| 148 | + </div> | |
| 149 | + <div class="entry"> | |
| 150 | + <div class="body"> | |
| 151 | + <p class="title">M.Sc. in Business Administration (Finance)</p> | |
| 152 | + <p class="sub">Université Laval</p> | |
| 153 | + <p class="desc">Thesis: <i>Impact of Commuting Times on Residential Property Values: Evidence from the Province of Québec</i>.</p> | |
| 154 | + </div> | |
| 155 | + <span class="period">2017 – 2019</span> | |
| 156 | + </div> | |
| 157 | + <div class="entry"> | |
| 158 | + <div class="body"> | |
| 159 | + <p class="title">B.B.A. in Business Administration (Finance)</p> | |
| 160 | + <p class="sub">Université Laval</p> | |
| 161 | + </div> | |
| 162 | + <span class="period">2013 – 2017</span> | |
| 163 | + </div> | |
| 164 | +</section> | |
| 165 | + | |
| 166 | +<section> | |
| 167 | + <h2>Peer-Reviewed Publication</h2> | |
| 168 | + <div class="entry"> | |
| 169 | + <div class="body"> | |
| 170 | + <p class="title">Speculative Trading in Energy Markets: Evidence from Macroeconomic Surprises</p> | |
| 171 | + <p class="sub">Boucher, S.-P., Gagnon, M.-H., & Power, G. J. — <i>The Energy Journal</i></p> | |
| 172 | + </div> | |
| 173 | + <span class="period">2025</span> | |
| 174 | + </div> | |
| 175 | +</section> | |
| 176 | + | |
| 177 | +<section> | |
| 178 | + <h2>Ph.D. Thesis Chapters</h2> | |
| 179 | + <div class="entry"> | |
| 180 | + <div class="body"> | |
| 181 | + <p class="title"><span class="num">1.</span>Speculative Trading in Energy Markets: Evidence from Macroeconomic Surprises</p> | |
| 182 | + <p class="sub">Revised version published in The Energy Journal (2025)</p> | |
| 183 | + </div> | |
| 184 | + </div> | |
| 185 | + <div class="entry"> | |
| 186 | + <div class="body"> | |
| 187 | + <p class="title"><span class="num">2.</span>Seeing Through the ETF: Indicative NAV and Commodity Volatility Transmission</p> | |
| 188 | + <p class="sub">Submission version, Journal of Futures Markets</p> | |
| 189 | + </div> | |
| 190 | + </div> | |
| 191 | + <div class="entry"> | |
| 192 | + <div class="body"> | |
| 193 | + <p class="title"><span class="num">3.</span>Returns and Volatility Around FOMC Announcements: A High-Frequency Analysis of Policy Tone and Novelty</p> | |
| 194 | + <p class="sub">Manuscript, 2026</p> | |
| 195 | + </div> | |
| 196 | + </div> | |
| 197 | +</section> | |
| 198 | + | |
| 199 | +<section> | |
| 200 | + <h2>UQO Working Paper Series</h2> | |
| 201 | + <div class="entry"> | |
| 202 | + <div class="body"> | |
| 203 | + <p class="title"><span class="num">WP 2</span>Decoding Real Estate Descriptions: Semantic Embeddings and Hedonic Pricing of Residential Properties in Quebec</p> | |
| 204 | + <p class="desc">Sentence-transformer embeddings of listing descriptions added to hedonic models of 17,087 Quebec houses — adjusted R² lifted from 0.452 to 0.511. 53 pages.</p> | |
| 205 | + </div> | |
| 206 | + <span class="period">2026</span> | |
| 207 | + </div> | |
| 208 | + <div class="entry"> | |
| 209 | + <div class="body"> | |
| 210 | + <p class="title"><span class="num">WP 3</span>Hedonic Housing Price Models for the United States: A Multi-Method Comparison of Parametric, Quantile, and Machine Learning Approaches</p> | |
| 211 | + <p class="desc">OLS, quantile regression, and XGBoost + SHAP compared on 788,842 Zillow listings covering all 50 states and DC. 60 pages.</p> | |
| 212 | + </div> | |
| 213 | + <span class="period">2026</span> | |
| 214 | + </div> | |
| 215 | + <div class="entry"> | |
| 216 | + <div class="body"> | |
| 217 | + <p class="title"><span class="num">WP 5</span>Airbnb, Residential Rents, and Housing Market Pressure: A Hedonic and Spatial Econometric Analysis</p> | |
| 218 | + <p class="desc">Hedonic, spatial, quantile, and ML evidence from 8,303 Quebec rental listings and 3,456 Airbnb listings — each active Airbnb within 500 m associated with ≈0.4% higher asking rent. 51 pages.</p> | |
| 219 | + </div> | |
| 220 | + <span class="period">2026</span> | |
| 221 | + </div> | |
| 222 | + <div class="entry"> | |
| 223 | + <div class="body"> | |
| 224 | + <p class="title"><span class="num">WP 7</span>The Options-Implied Information Content for Cross-Asset Return and Volatility Prediction: Evidence from 3.8 Billion Option Contracts</p> | |
| 225 | + <p class="desc">Options-implied moments forecast returns and volatility on 264,383 ticker-days (69 tickers, 2010–2025); a kurtosis long/short strategy delivers a Sharpe ratio of 2.33. 29 pages.</p> | |
| 226 | + </div> | |
| 227 | + <span class="period">2026</span> | |
| 228 | + </div> | |
| 229 | + <div class="entry"> | |
| 230 | + <div class="body"> | |
| 231 | + <p class="title"><span class="num">WP 9</span>A Grand Hedonic Model of the Canadian Housing Market: Decomposing the Value of Structure and Location</p> | |
| 232 | + <p class="desc">140,931 MLS listings with 1,153 neighbourhood (FSA) fixed effects — location alone adds ≈30 points of R²; 15.8% median absolute error out of sample. 26 pages.</p> | |
| 233 | + </div> | |
| 234 | + <span class="period">2026</span> | |
| 235 | + </div> | |
| 236 | + <div class="entry"> | |
| 237 | + <div class="body"> | |
| 238 | + <p class="title"><span class="num">WP 10</span>The Assessment Gap in Quebec: Vertical and Horizontal Inequity in Municipal Property Valuation</p> | |
| 239 | + <p class="desc">First province-wide audit of property-assessment equity in Canada — 522,769 sales matched to the assessment rolls; 99% of municipalities fail the IAAO uniformity standard. 29 pages.</p> | |
| 240 | + </div> | |
| 241 | + <span class="period">2026</span> | |
| 242 | + </div> | |
| 243 | +</section> | |
| 244 | + | |
| 245 | +<section> | |
| 246 | + <h2>Earlier Working Papers</h2> | |
| 247 | + <div class="entry"> | |
| 248 | + <div class="body"> | |
| 249 | + <p class="title">Has Financialization Changed the Impact of Macro Announcements on U.S. Commodity Markets?</p> | |
| 250 | + <p class="sub">SSRN Working Paper</p> | |
| 251 | + </div> | |
| 252 | + <span class="period">May 2022</span> | |
| 253 | + </div> | |
| 254 | + <div class="entry"> | |
| 255 | + <div class="body"> | |
| 256 | + <p class="title">Modelling Volatility Dynamics Between Commodity ETFs and Their Net Asset Value using BVAR and HAR Models</p> | |
| 257 | + <p class="sub">Working Paper</p> | |
| 258 | + </div> | |
| 259 | + <span class="period">Jan. 2023</span> | |
| 260 | + </div> | |
| 261 | + <div class="entry"> | |
| 262 | + <div class="body"> | |
| 263 | + <p class="title">Returns and Volatility Around FOMC Announcements: A High-Frequency Analysis of Policy Tone and Novelty</p> | |
| 264 | + <p class="sub">Working Paper</p> | |
| 265 | + </div> | |
| 266 | + <span class="period">Nov. 2023</span> | |
| 267 | + </div> | |
| 268 | +</section> | |
| 269 | + | |
| 270 | +<section> | |
| 271 | + <h2>Conference Presentations</h2> | |
| 272 | + <div class="entry"> | |
| 273 | + <div class="body"><p class="title">CRREP Research Day</p></div> | |
| 274 | + <span class="period">2022, 2023</span> | |
| 275 | + </div> | |
| 276 | + <div class="entry"> | |
| 277 | + <div class="body"><p class="title">Canadian Economics Association — 61st & 62nd Annual Conferences</p></div> | |
| 278 | + <span class="period">2022, 2023</span> | |
| 279 | + </div> | |
| 280 | + <div class="entry"> | |
| 281 | + <div class="body"><p class="title">7th Winter Workshop on Commodity Markets — Mont-Tremblant</p></div> | |
| 282 | + <span class="period">2024</span> | |
| 283 | + </div> | |
| 284 | +</section> | |
| 285 | + | |
| 286 | +<section> | |
| 287 | + <h2>Teaching</h2> | |
| 288 | + | |
| 289 | + <h3>Professor — Université du Québec en Outaouais (UQO)</h3> | |
| 290 | + <table> | |
| 291 | + <thead><tr><th>Code</th><th>Course</th><th>Material</th></tr></thead> | |
| 292 | + <tbody> | |
| 293 | + <tr> | |
| 294 | + <td class="code">IMM1003</td> | |
| 295 | + <td>Éléments d'évaluation immobilière — foundations of real-estate appraisal: market analysis, comparison and income methods</td> | |
| 296 | + <td class="dim">14 lecture decks, 3 practical assignments with Excel templates, course plan</td> | |
| 297 | + </tr> | |
| 298 | + <tr> | |
| 299 | + <td class="code">IMM1033</td> | |
| 300 | + <td>Méthodes du coût — cost approach to valuation: land value, replacement cost, depreciation analysis</td> | |
| 301 | + <td class="dim">14 lecture decks, 3 practical assignments, Québec real-estate market report</td> | |
| 302 | + </tr> | |
| 303 | + </tbody> | |
| 304 | + </table> | |
| 305 | + | |
| 306 | + <h3>Lecturer — Université Laval (2021 – Present)</h3> | |
| 307 | + <table> | |
| 308 | + <thead><tr><th>Code</th><th>Course</th><th>Level</th><th>Terms</th></tr></thead> | |
| 309 | + <tbody> | |
| 310 | + <tr><td class="code">GSF-3100</td><td>Capital Markets</td><td class="dim">Undergraduate</td><td class="dim">F2021, W2021, F2022, W2023</td></tr> | |
| 311 | + <tr><td class="code">GSF-6053</td><td>Financial Econometrics I</td><td class="dim">Graduate</td><td class="dim">W2022, W2025</td></tr> | |
| 312 | + <tr><td class="code">GSF-1500</td><td>Financial Management</td><td class="dim">Undergraduate</td><td class="dim">S2022</td></tr> | |
| 313 | + <tr><td class="code">GSF-6028</td><td>Financial Theory</td><td class="dim">Graduate</td><td class="dim">W2024</td></tr> | |
| 314 | + </tbody> | |
| 315 | + </table> | |
| 316 | + <p class="desc" style="color: var(--muted); font-size: 8.4pt; margin-top: 4px;">Complete open course material on GitHub — LaTeX Beamer slides (13 GSF-3100 decks; 11 GSF-6053 session decks + Stata labs), and the full GSF-6053 W2025 edition: lecture slides, 11 sets of typed notes, 7 formal proofs, and 14 exercise sets with complete solutions.</p> | |
| 317 | + | |
| 318 | + <h3>Teaching Assistant — Université Laval (2018 – 2021)</h3> | |
| 319 | + <table> | |
| 320 | + <thead><tr><th>Code</th><th>Course</th></tr></thead> | |
| 321 | + <tbody> | |
| 322 | + <tr><td class="code">GSF-2101</td><td>Portfolio Management</td></tr> | |
| 323 | + <tr><td class="code">GSF-2102</td><td>Corporate Finance</td></tr> | |
| 324 | + <tr><td class="code">GSF-6008</td><td>Corporate Finance (graduate)</td></tr> | |
| 325 | + <tr><td class="code">GSF-6025</td><td>Financial Strategies & Policies I</td></tr> | |
| 326 | + <tr><td class="code">GSF-6028</td><td>Financial Theory</td></tr> | |
| 327 | + </tbody> | |
| 328 | + </table> | |
| 329 | +</section> | |
| 330 | + | |
| 331 | +<section> | |
| 332 | + <h2>Software — Native macOS Applications</h2> | |
| 333 | + <div class="apps"> | |
| 334 | + <div class="app"><p class="name">Zyquo Cloud <span class="tag">— multi-provider AI chat: 12 cloud providers, 170 models, BYOK, encrypted vault</span></p><p class="url">github.com/spboucher-ai/zyquo-cloud</p></div> | |
| 335 | + <div class="app"><p class="name">Zyquo Local <span class="tag">— 100% local LLMs on Apple Silicon with MLX; no API keys, fully offline</span></p><p class="url">github.com/spboucher-ai/zyquo-local</p></div> | |
| 336 | + <div class="app"><p class="name">Zyquo Agent <span class="tag">— autonomous agent for the Mac: bash, AppleScript and file tools with a policy gate</span></p><p class="url">github.com/spboucher-ai/zyquo-agent</p></div> | |
| 337 | + <div class="app"><p class="name">Zyquo Atlas <span class="tag">— AI-native WebKit browser: AI in the omnibox, page, selection and tabs</span></p><p class="url">github.com/spboucher-ai/zyquo-atlas</p></div> | |
| 338 | + <div class="app"><p class="name">Zyquo MLX <span class="tag">— on-device model foundry: run, LoRA/QLoRA fine-tune, quantize and convert LLMs</span></p><p class="url">github.com/spboucher-ai/zyquo-mlx</p></div> | |
| 339 | + <div class="app"><p class="name">Zyquo Router <span class="tag">— the Mac as a local LLM gateway: one OpenAI-compatible endpoint, 170 models</span></p><p class="url">github.com/spboucher-ai/zyquo-router</p></div> | |
| 340 | + <div class="app"><p class="name">Metrika <span class="tag">— Stata-class statistics for macOS, GPU-accelerated (MLX/Metal), R-validated to 1e-10</span></p><p class="url">github.com/spboucher-ai/metrika</p></div> | |
| 341 | + <div class="app"><p class="name">OS Vault <span class="tag">— self-custody multi-chain crypto wallet: six chain families, zero API keys</span></p><p class="url">github.com/spboucher-ai/os-vault</p></div> | |
| 342 | + <div class="app"><p class="name">Forge Studio <span class="tag">— SwiftUI cockpit for LLM training: live loss charts, checkpoints, generation</span></p><p class="url">github.com/spboucher-ai/forge-studio</p></div> | |
| 343 | + </div> | |
| 344 | +</section> | |
| 345 | + | |
| 346 | +<section> | |
| 347 | + <h2>Software — Web Platforms & Open Source</h2> | |
| 348 | + <div class="apps"> | |
| 349 | + <div class="app"><p class="name">VQuant <span class="tag">— AI-powered financial intelligence platform: Claude agent, 265+ data endpoints</span></p><p class="url">www.vquant.ai</p></div> | |
| 350 | + <div class="app"><p class="name">AI Risk Index <span class="tag">— task-based AI job-exposure index: 923 occupations, 18,796 O*NET tasks</span></p><p class="url">www.airiskindex.io</p></div> | |
| 351 | + <div class="app"><p class="name">LLM Index <span class="tag">— contamination-resistant live LLM ranking (IRT 2PL + Bradley–Terry, 12 domains)</span></p><p class="url">www.llmindex.io</p></div> | |
| 352 | + <div class="app"><p class="name">CoinExplorer <span class="tag">— self-hosted blockchain explorer: 24 chains, free public RPCs, zero API keys</span></p><p class="url">www.coinexplorer.io</p></div> | |
| 353 | + <div class="app"><p class="name">Lou-Ka <span class="tag">— Quebec rental aggregator: one connector per property manager, FastAPI + React PWA</span></p><p class="url">www.lou-ka.com</p></div> | |
| 354 | + <div class="app"><p class="name">Vrai-Prix <span class="tag">— transparent property valuation: 3.7M Quebec properties, 745,119 real sales</span></p><p class="url">www.vrai-prix.com</p></div> | |
| 355 | + <div class="app"><p class="name">ValoPlex <span class="tag">— plex valuation engine: 393,867 multi-unit buildings valued door by door</span></p><p class="url">www.valoplex.com</p></div> | |
| 356 | + <div class="app"><p class="name">QHPI <span class="tag">— Quebec quality-adjusted housing price index: hedonic, hierarchically pooled</span></p><p class="url">www.indexqc.house</p></div> | |
| 357 | + <div class="app"><p class="name">Zyquo Cloud Web <span class="tag">— browser edition of Zyquo Cloud: 12 providers, no backend, BYOK</span></p><p class="url">www.zyquo.cloud</p></div> | |
| 358 | + <div class="app"><p class="name">Forge <span class="tag">— LLM training from scratch in pure C++20 + Metal; 10.8 TFLOPS GEMM kernels</span></p><p class="url">github.com/spboucher-ai/forge</p></div> | |
| 359 | + <div class="app"><p class="name">AIR <span class="tag">— compiler infrastructure for accounting: LLMs emit events, deterministic balanced entries</span></p><p class="url">github.com/spboucher-ai/air</p></div> | |
| 360 | + <div class="app"><p class="name">Ultra-Sharp Agent Skills <span class="tag">— 72 production-ready, linted and trigger-tested skills for AI agents</span></p><p class="url">github.com/spboucher-ai/ultra-sharp-agent-skills</p></div> | |
| 361 | + <div class="app"><p class="name">Neural Networks Book <span class="tag">— 119-page LaTeX book: every ANN architecture with equations and TikZ figures</span></p><p class="url">github.com/spboucher-ai/artificial-neural-networks-book</p></div> | |
| 362 | + <div class="app"><p class="name">spboucher.ai <span class="tag">— this website: Next.js 16, Tailwind v4, Framer Motion, full dark mode</span></p><p class="url">www.spboucher.ai</p></div> | |
| 363 | + </div> | |
| 364 | +</section> | |
| 365 | + | |
| 366 | +<section> | |
| 367 | + <h2>Technical Skills</h2> | |
| 368 | + <div class="skill-group"> | |
| 369 | + <p class="label">Languages & Frameworks</p> | |
| 370 | + <div class="chips"> | |
| 371 | + <span class="chip">Python</span><span class="chip">TypeScript/JavaScript</span><span class="chip">R</span><span class="chip">MATLAB</span><span class="chip">SAS/STATA</span><span class="chip">SQL</span><span class="chip">Julia</span><span class="chip">C++</span><span class="chip">Swift/SwiftUI</span><span class="chip">LaTeX</span><span class="chip">React/Next.js</span><span class="chip">Node.js</span><span class="chip">FastAPI</span><span class="chip">Flask</span><span class="chip">Streamlit</span><span class="chip">Tailwind CSS</span><span class="chip">shadcn/ui</span><span class="chip">Framer Motion</span> | |
| 372 | + </div> | |
| 373 | + </div> | |
| 374 | + <div class="skill-group"> | |
| 375 | + <p class="label">Data & Econometrics</p> | |
| 376 | + <div class="chips"> | |
| 377 | + <span class="chip">Pandas</span><span class="chip">NumPy</span><span class="chip">statsmodels</span><span class="chip">scikit-learn</span><span class="chip">VAR</span><span class="chip">BVAR</span><span class="chip">HAR</span><span class="chip">GARCH</span><span class="chip">DCC-GARCH</span><span class="chip">Event studies</span><span class="chip">High-frequency analysis</span><span class="chip">Realized volatility</span><span class="chip">Hedonic models</span><span class="chip">Quantile regression</span><span class="chip">XGBoost + SHAP</span><span class="chip">FRED</span><span class="chip">FMP</span><span class="chip">EODHD</span><span class="chip">Yahoo Finance</span><span class="chip">World Bank</span><span class="chip">IMF</span><span class="chip">OECD</span> | |
| 378 | + </div> | |
| 379 | + </div> | |
| 380 | + <div class="skill-group"> | |
| 381 | + <p class="label">AI, LLMs & Agents</p> | |
| 382 | + <div class="chips"> | |
| 383 | + <span class="chip">Claude/Anthropic SDK</span><span class="chip">OpenAI API</span><span class="chip">OpenRouter</span><span class="chip">Vercel AI SDK</span><span class="chip">Ollama</span><span class="chip">llama.cpp</span><span class="chip">GGUF</span><span class="chip">MLX</span><span class="chip">Hugging Face</span><span class="chip">Embeddings</span><span class="chip">RAG</span><span class="chip">Vector databases</span><span class="chip">Tool-augmented agents</span><span class="chip">LoRA</span><span class="chip">QLoRA</span><span class="chip">SFT fine-tuning</span> | |
| 384 | + </div> | |
| 385 | + </div> | |
| 386 | +</section> | |
| 387 | + | |
| 388 | +<footer> | |
| 389 | + <span>Simon-Pierre Boucher — Curriculum Vitae</span> | |
| 390 | + <span>www.spboucher.ai · contact@spboucher.ai</span> | |
| 391 | +</footer> | |
| 392 | + | |
| 393 | +</body> | |
| 394 | +</html> | |
modified
lib/apps.ts
+76 −0
@@ -30,6 +30,32 @@ export interface OpenSourceProject { | ||
| 30 | 30 | dmg?: string; |
| 31 | 31 | } |
| 32 | 32 | |
| 33 | +/** Unified link/identity shape shared by Zyquo apps and open-source projects. */ | |
| 34 | +export interface ProjectLinks { | |
| 35 | + slug: string; | |
| 36 | + name: string; | |
| 37 | + emoji: string; | |
| 38 | + tagline: string; | |
| 39 | + description: string; | |
| 40 | + icon?: string; | |
| 41 | + language?: string; | |
| 42 | + repo: string; | |
| 43 | + demo?: string; | |
| 44 | + release?: string; | |
| 45 | + dmg?: string; | |
| 46 | + kind: "zyquo" | "oss"; | |
| 47 | +} | |
| 48 | + | |
| 49 | +/** Every app and project on the site, normalized for the detail pages. */ | |
| 50 | +export function getAllProjectLinks(): ProjectLinks[] { | |
| 51 | + return [ | |
| 52 | + ...zyquoApps.map((app): ProjectLinks => ({ ...app, kind: "zyquo" })), | |
| 53 | + ...openSourceProjects.map( | |
| 54 | + (project): ProjectLinks => ({ ...project, kind: "oss" }), | |
| 55 | + ), | |
| 56 | + ]; | |
| 57 | +} | |
| 58 | + | |
| 33 | 59 | export const openSourceProjects: OpenSourceProject[] = [ |
| 34 | 60 | { |
| 35 | 61 | slug: "vquant", |
@@ -57,6 +83,7 @@ export const openSourceProjects: OpenSourceProject[] = [ | ||
| 57 | 83 | }, |
| 58 | 84 | { |
| 59 | 85 | slug: "coinexplorer", |
| 86 | + icon: "/icons/coinexplorer.png", | |
| 60 | 87 | name: "CoinExplorer", |
| 61 | 88 | emoji: "🪙", |
| 62 | 89 | tagline: "Self-hosted blockchain explorer", |
@@ -68,6 +95,7 @@ export const openSourceProjects: OpenSourceProject[] = [ | ||
| 68 | 95 | }, |
| 69 | 96 | { |
| 70 | 97 | slug: "llmindex", |
| 98 | + icon: "/icons/llmindex.png", | |
| 71 | 99 | name: "LLM Index", |
| 72 | 100 | emoji: "🏆", |
| 73 | 101 | tagline: "Live, contamination-resistant LLM ranking", |
@@ -77,6 +105,54 @@ export const openSourceProjects: OpenSourceProject[] = [ | ||
| 77 | 105 | repo: "https://github.com/spboucher-ai/llmindex", |
| 78 | 106 | demo: "https://www.llmindex.io", |
| 79 | 107 | }, |
| 108 | + { | |
| 109 | + slug: "lou-ka", | |
| 110 | + icon: "/icons/lou-ka.png", | |
| 111 | + name: "Lou-Ka", | |
| 112 | + emoji: "🔑", | |
| 113 | + tagline: "Every Quebec rental, one place", | |
| 114 | + description: | |
| 115 | + "Independent rental-listing aggregator for Quebec: one dedicated connector per property manager normalizes every listing into a single schema, with full photos, standardized details, and a direct link to the original ad.", | |
| 116 | + language: "Python", | |
| 117 | + repo: "https://github.com/spboucher-ai/lou-ka", | |
| 118 | + demo: "https://www.lou-ka.com", | |
| 119 | + }, | |
| 120 | + { | |
| 121 | + slug: "vrai-prix", | |
| 122 | + icon: "/icons/vrai-prix.png", | |
| 123 | + name: "Vrai-Prix", | |
| 124 | + emoji: "🏠", | |
| 125 | + tagline: "Transparent property valuation for Quebec", | |
| 126 | + description: | |
| 127 | + "A no-black-box property valuation engine covering 3.7 million Quebec properties and 745,119 real sales — every comparable, every dollar adjustment, and every confidence interval shown in full.", | |
| 128 | + language: "TypeScript", | |
| 129 | + repo: "https://github.com/spboucher-ai/vrai-prix", | |
| 130 | + demo: "https://www.vrai-prix.com", | |
| 131 | + }, | |
| 132 | + { | |
| 133 | + slug: "valoplex", | |
| 134 | + icon: "/icons/valoplex.png", | |
| 135 | + name: "ValoPlex", | |
| 136 | + emoji: "🚪", | |
| 137 | + tagline: "Plex valuation, door by door", | |
| 138 | + description: | |
| 139 | + "Quebec's specialized plex valuation engine: 393,867 multi-unit buildings and 1.7 million doors valued with a scale-proof ratio hedonic model, plus a full interactive investor pro forma.", | |
| 140 | + language: "TypeScript", | |
| 141 | + repo: "https://github.com/spboucher-ai/valoplex", | |
| 142 | + demo: "https://www.valoplex.com", | |
| 143 | + }, | |
| 144 | + { | |
| 145 | + slug: "qwhpi-platform", | |
| 146 | + icon: "/icons/qwhpi-platform.png", | |
| 147 | + name: "QHPI", | |
| 148 | + emoji: "📈", | |
| 149 | + tagline: "Quebec's quality-adjusted housing price index", | |
| 150 | + description: | |
| 151 | + "A production-grade economic-measurement platform computing hedonic, hierarchically pooled housing price indexes for Quebec — province, 17 regions, and major cities by property type, with published uncertainty on every row.", | |
| 152 | + language: "Python", | |
| 153 | + repo: "https://github.com/spboucher-ai/qwhpi-platform", | |
| 154 | + demo: "https://www.indexqc.house", | |
| 155 | + }, | |
| 80 | 156 | { |
| 81 | 157 | slug: "os-vault", |
| 82 | 158 | icon: "/icons/os-vault.png", |
added
lib/blog.ts
+162 −0
@@ -0,0 +1,162 @@ | ||
| 1 | +/* | |
| 2 | + blog.ts | |
| 3 | + spboucher.ai Web | |
| 4 | + Author: Simon-Pierre Boucher | |
| 5 | + Mail: contact@spboucher.ai | |
| 6 | +*/ | |
| 7 | + | |
| 8 | +import fs from "node:fs"; | |
| 9 | +import path from "node:path"; | |
| 10 | + | |
| 11 | +export interface BlogPostMeta { | |
| 12 | + slug: string; | |
| 13 | + file: string; | |
| 14 | + title: string; | |
| 15 | + excerpt: string; | |
| 16 | + date: string; // ISO date | |
| 17 | + dateLabel: string; | |
| 18 | + tags: string[]; | |
| 19 | +} | |
| 20 | + | |
| 21 | +export type BlogBlock = | |
| 22 | + | { type: "h2"; text: string } | |
| 23 | + | { type: "p"; text: string } | |
| 24 | + | { type: "quote"; text: string } | |
| 25 | + | { type: "ul"; items: string[] } | |
| 26 | + | { type: "math"; tex: string }; | |
| 27 | + | |
| 28 | +export interface BlogPost extends BlogPostMeta { | |
| 29 | + blocks: BlogBlock[]; | |
| 30 | + readingMinutes: number; | |
| 31 | +} | |
| 32 | + | |
| 33 | +/** Registry of published essays — sources live in /blog/*.txt. */ | |
| 34 | +const registry: BlogPostMeta[] = [ | |
| 35 | + { | |
| 36 | + slug: "cost-of-intelligence", | |
| 37 | + file: "blog1.txt", | |
| 38 | + title: "What Happens When the Cost of Intelligence Approaches Zero?", | |
| 39 | + excerpt: | |
| 40 | + "For most of economic history, useful cognitive work required scarce, educated humans. AI breaks that coupling — and when the price of a fundamental input collapses, the entire economy reorganizes around whatever remains scarce.", | |
| 41 | + date: "2026-08-09", | |
| 42 | + dateLabel: "August 9, 2026", | |
| 43 | + tags: ["AI", "Economics"], | |
| 44 | + }, | |
| 45 | + { | |
| 46 | + slug: "who-owns-ai-wealth", | |
| 47 | + file: "blog2.txt", | |
| 48 | + title: "If AI Creates Enormous Wealth, Who Owns It?", | |
| 49 | + excerpt: | |
| 50 | + "Growth and the distribution of growth are two different things. AI may create extraordinary wealth while shifting the defining economic divide from skilled versus unskilled labor to those who sell labor versus those who own productive intelligence.", | |
| 51 | + date: "2026-08-09", | |
| 52 | + dateLabel: "August 9, 2026", | |
| 53 | + tags: ["AI", "Economics", "Ownership"], | |
| 54 | + }, | |
| 55 | + { | |
| 56 | + slug: "ai-prevents-its-own-singularity", | |
| 57 | + file: "blog3.txt", | |
| 58 | + title: "What If AI Prevents Its Own Singularity?", | |
| 59 | + excerpt: | |
| 60 | + "The intelligence explosion assumes fresh information. But as machines generate more of the internet they learn from, recursive contamination could turn the exponential into a plateau — unless AI becomes radically more empirical.", | |
| 61 | + date: "2026-08-09", | |
| 62 | + dateLabel: "August 9, 2026", | |
| 63 | + tags: ["AI", "Data", "Scaling"], | |
| 64 | + }, | |
| 65 | +]; | |
| 66 | + | |
| 67 | +/** Parse the constrained Markdown subset used by the essays. */ | |
| 68 | +function parseBlocks(raw: string): BlogBlock[] { | |
| 69 | + const lines = raw.split("\n"); | |
| 70 | + const blocks: BlogBlock[] = []; | |
| 71 | + let listItems: string[] | null = null; | |
| 72 | + let mathLines: string[] | null = null; | |
| 73 | + | |
| 74 | + const flushList = () => { | |
| 75 | + if (listItems && listItems.length > 0) { | |
| 76 | + blocks.push({ type: "ul", items: listItems }); | |
| 77 | + } | |
| 78 | + listItems = null; | |
| 79 | + }; | |
| 80 | + | |
| 81 | + for (const rawLine of lines) { | |
| 82 | + const line = rawLine.trim(); | |
| 83 | + | |
| 84 | + // Display math: $$ ... $$ on one line, or a fenced multi-line block. | |
| 85 | + if (mathLines !== null) { | |
| 86 | + if (line === "$$" || line.endsWith("$$")) { | |
| 87 | + if (line !== "$$") mathLines.push(line.slice(0, -2).trim()); | |
| 88 | + blocks.push({ type: "math", tex: mathLines.join("\n").trim() }); | |
| 89 | + mathLines = null; | |
| 90 | + } else { | |
| 91 | + mathLines.push(line); | |
| 92 | + } | |
| 93 | + continue; | |
| 94 | + } | |
| 95 | + if (line.startsWith("$$")) { | |
| 96 | + flushList(); | |
| 97 | + const inner = line.slice(2); | |
| 98 | + if (inner.endsWith("$$") && inner.length >= 2) { | |
| 99 | + blocks.push({ type: "math", tex: inner.slice(0, -2).trim() }); | |
| 100 | + } else { | |
| 101 | + mathLines = inner.trim() ? [inner.trim()] : []; | |
| 102 | + } | |
| 103 | + continue; | |
| 104 | + } | |
| 105 | + if (line.length === 0) { | |
| 106 | + flushList(); | |
| 107 | + continue; | |
| 108 | + } | |
| 109 | + if (line.startsWith("# ")) { | |
| 110 | + // Post title — carried by the registry, not repeated in the body. | |
| 111 | + flushList(); | |
| 112 | + continue; | |
| 113 | + } | |
| 114 | + if (line.startsWith("## ")) { | |
| 115 | + flushList(); | |
| 116 | + blocks.push({ type: "h2", text: line.slice(3).trim() }); | |
| 117 | + continue; | |
| 118 | + } | |
| 119 | + if (line.startsWith("> ")) { | |
| 120 | + flushList(); | |
| 121 | + blocks.push({ type: "quote", text: line.slice(2).trim() }); | |
| 122 | + continue; | |
| 123 | + } | |
| 124 | + if (line.startsWith("* ")) { | |
| 125 | + (listItems ??= []).push(line.slice(2).trim().replace(/,$/, "")); | |
| 126 | + continue; | |
| 127 | + } | |
| 128 | + flushList(); | |
| 129 | + blocks.push({ type: "p", text: line }); | |
| 130 | + } | |
| 131 | + flushList(); | |
| 132 | + if (mathLines !== null && mathLines.length > 0) { | |
| 133 | + blocks.push({ type: "math", tex: mathLines.join("\n").trim() }); | |
| 134 | + } | |
| 135 | + return blocks; | |
| 136 | +} | |
| 137 | + | |
| 138 | +function loadPost(meta: BlogPostMeta): BlogPost { | |
| 139 | + const raw = fs.readFileSync( | |
| 140 | + path.join(process.cwd(), "blog", meta.file), | |
| 141 | + "utf8", | |
| 142 | + ); | |
| 143 | + const blocks = parseBlocks(raw); | |
| 144 | + const words = raw.split(/\s+/).filter(Boolean).length; | |
| 145 | + return { ...meta, blocks, readingMinutes: Math.max(1, Math.round(words / 220)) }; | |
| 146 | +} | |
| 147 | + | |
| 148 | +/** All posts, newest first (registry order breaks ties). */ | |
| 149 | +export function getBlogPosts(): BlogPost[] { | |
| 150 | + return [...registry] | |
| 151 | + .map(loadPost) | |
| 152 | + .sort((a, b) => (a.date < b.date ? 1 : a.date > b.date ? -1 : 0)); | |
| 153 | +} | |
| 154 | + | |
| 155 | +export function getBlogPost(slug: string): BlogPost | undefined { | |
| 156 | + const meta = registry.find((p) => p.slug === slug); | |
| 157 | + return meta ? loadPost(meta) : undefined; | |
| 158 | +} | |
| 159 | + | |
| 160 | +export function getBlogSlugs(): string[] { | |
| 161 | + return registry.map((p) => p.slug); | |
| 162 | +} | |
added
lib/detail-icons.ts
+159 −0
@@ -0,0 +1,159 @@ | ||
| 1 | +/* | |
| 2 | + detail-icons.ts | |
| 3 | + spboucher.ai Web | |
| 4 | + Author: Simon-Pierre Boucher | |
| 5 | + Mail: contact@spboucher.ai | |
| 6 | +*/ | |
| 7 | + | |
| 8 | +import { | |
| 9 | + ArrowLeftRight, | |
| 10 | + Award, | |
| 11 | + BarChart3, | |
| 12 | + Bell, | |
| 13 | + Blocks, | |
| 14 | + BookOpen, | |
| 15 | + Bot, | |
| 16 | + Boxes, | |
| 17 | + Braces, | |
| 18 | + Brain, | |
| 19 | + Building2, | |
| 20 | + Calculator, | |
| 21 | + CheckCircle2, | |
| 22 | + CircuitBoard, | |
| 23 | + Clock, | |
| 24 | + Cloud, | |
| 25 | + Coins, | |
| 26 | + Command, | |
| 27 | + Cpu, | |
| 28 | + Database, | |
| 29 | + DollarSign, | |
| 30 | + Download, | |
| 31 | + Eye, | |
| 32 | + FileText, | |
| 33 | + Filter, | |
| 34 | + Fingerprint, | |
| 35 | + Flame, | |
| 36 | + FlaskConical, | |
| 37 | + FolderTree, | |
| 38 | + Gauge, | |
| 39 | + GitBranch, | |
| 40 | + Globe, | |
| 41 | + GraduationCap, | |
| 42 | + HardDrive, | |
| 43 | + Home, | |
| 44 | + KeyRound, | |
| 45 | + Landmark, | |
| 46 | + Languages, | |
| 47 | + Layers, | |
| 48 | + Lightbulb, | |
| 49 | + LineChart, | |
| 50 | + ListChecks, | |
| 51 | + Lock, | |
| 52 | + type LucideIcon, | |
| 53 | + Map, | |
| 54 | + MapPin, | |
| 55 | + MessageSquare, | |
| 56 | + Microscope, | |
| 57 | + Network, | |
| 58 | + Package, | |
| 59 | + Palette, | |
| 60 | + PenTool, | |
| 61 | + RefreshCw, | |
| 62 | + Rocket, | |
| 63 | + Scale, | |
| 64 | + Search, | |
| 65 | + Server, | |
| 66 | + Settings2, | |
| 67 | + ShieldCheck, | |
| 68 | + Sparkles, | |
| 69 | + Split, | |
| 70 | + Table2, | |
| 71 | + Terminal, | |
| 72 | + Timer, | |
| 73 | + TrendingUp, | |
| 74 | + Users, | |
| 75 | + Wallet, | |
| 76 | + WifiOff, | |
| 77 | + Workflow, | |
| 78 | + Wrench, | |
| 79 | + Zap, | |
| 80 | +} from "lucide-react"; | |
| 81 | + | |
| 82 | +/** Icons that project-detail feature entries may reference by name. */ | |
| 83 | +const detailIcons: Record<string, LucideIcon> = { | |
| 84 | + ArrowLeftRight, | |
| 85 | + Award, | |
| 86 | + BarChart3, | |
| 87 | + Bell, | |
| 88 | + Blocks, | |
| 89 | + BookOpen, | |
| 90 | + Bot, | |
| 91 | + Boxes, | |
| 92 | + Braces, | |
| 93 | + Brain, | |
| 94 | + Building2, | |
| 95 | + Calculator, | |
| 96 | + CheckCircle2, | |
| 97 | + CircuitBoard, | |
| 98 | + Clock, | |
| 99 | + Cloud, | |
| 100 | + Coins, | |
| 101 | + Command, | |
| 102 | + Cpu, | |
| 103 | + Database, | |
| 104 | + DollarSign, | |
| 105 | + Download, | |
| 106 | + Eye, | |
| 107 | + FileText, | |
| 108 | + Filter, | |
| 109 | + Fingerprint, | |
| 110 | + Flame, | |
| 111 | + FlaskConical, | |
| 112 | + FolderTree, | |
| 113 | + Gauge, | |
| 114 | + GitBranch, | |
| 115 | + Globe, | |
| 116 | + GraduationCap, | |
| 117 | + HardDrive, | |
| 118 | + Home, | |
| 119 | + KeyRound, | |
| 120 | + Landmark, | |
| 121 | + Languages, | |
| 122 | + Layers, | |
| 123 | + Lightbulb, | |
| 124 | + LineChart, | |
| 125 | + ListChecks, | |
| 126 | + Lock, | |
| 127 | + Map, | |
| 128 | + MapPin, | |
| 129 | + MessageSquare, | |
| 130 | + Microscope, | |
| 131 | + Network, | |
| 132 | + Package, | |
| 133 | + Palette, | |
| 134 | + PenTool, | |
| 135 | + RefreshCw, | |
| 136 | + Rocket, | |
| 137 | + Scale, | |
| 138 | + Search, | |
| 139 | + Server, | |
| 140 | + Settings2, | |
| 141 | + ShieldCheck, | |
| 142 | + Sparkles, | |
| 143 | + Split, | |
| 144 | + Table2, | |
| 145 | + Terminal, | |
| 146 | + Timer, | |
| 147 | + TrendingUp, | |
| 148 | + Users, | |
| 149 | + Wallet, | |
| 150 | + WifiOff, | |
| 151 | + Workflow, | |
| 152 | + Wrench, | |
| 153 | + Zap, | |
| 154 | +}; | |
| 155 | + | |
| 156 | +/** Resolve a detail icon by name, falling back to Sparkles. */ | |
| 157 | +export function getDetailIcon(name: string): LucideIcon { | |
| 158 | + return detailIcons[name] ?? Sparkles; | |
| 159 | +} | |
added
lib/project-details.ts
+3739 −0
@@ -0,0 +1,3739 @@ | ||
| 1 | +/* | |
| 2 | + project-details.ts | |
| 3 | + spboucher.ai Web | |
| 4 | + Author: Simon-Pierre Boucher | |
| 5 | + Mail: contact@spboucher.ai | |
| 6 | +*/ | |
| 7 | + | |
| 8 | +/** Rich per-project content for the /apps/[slug] detail pages. */ | |
| 9 | +export interface ProjectDetail { | |
| 10 | + slug: string; | |
| 11 | + hero: { headline: string; subheadline: string }; | |
| 12 | + overview: string[]; | |
| 13 | + stats: { value: string; label: string }[]; | |
| 14 | + features: { icon: string; title: string; description: string }[]; | |
| 15 | + techStack: { category: string; items: string[] }[]; | |
| 16 | + architecture: { title: string; description: string }[]; | |
| 17 | + highlights: string[]; | |
| 18 | +} | |
| 19 | + | |
| 20 | +export const projectDetails: ProjectDetail[] = [ | |
| 21 | + { | |
| 22 | + "slug": "zyquo-cloud", | |
| 23 | + "hero": { | |
| 24 | + "headline": "Your keys, every cloud model, one native chat", | |
| 25 | + "subheadline": "A truly native macOS AI chat client for 12 cloud providers and 170 models — BYOK, encrypted vault, built entirely without Xcode." | |
| 26 | + }, | |
| 27 | + "overview": [ | |
| 28 | + "Zyquo Cloud is a native macOS chat client that unifies 12 cloud AI providers — OpenAI, Anthropic, xAI, Mistral, Google Gemini, Qwen, DeepSeek, Kimi, Perplexity, Together AI, DeepInfra, and Cerebras — behind one polished SwiftUI interface. Bring your own API keys; there is no subscription, no middleman, and no telemetry.", | |
| 29 | + "The app streams token-by-token over SSE, renders extended-thinking output in collapsible sections, shows per-message token usage and cost from real model pricing, and lets you switch models per conversation or per message. A 202-test live verification harness drives the exact production provider clients against every one of the 170 catalog models.", | |
| 30 | + "It is 100% SwiftUI and AppKit — no Electron, no web views — with exactly one dependency (Apple's swift-markdown). API keys live in a machine-bound AES-256-GCM vault derived via HKDF-SHA256, deliberately outside the macOS Keychain, decrypted only at request time." | |
| 31 | + ], | |
| 32 | + "stats": [ | |
| 33 | + { | |
| 34 | + "value": "12", | |
| 35 | + "label": "cloud providers" | |
| 36 | + }, | |
| 37 | + { | |
| 38 | + "value": "170", | |
| 39 | + "label": "built-in models" | |
| 40 | + }, | |
| 41 | + { | |
| 42 | + "value": "202/202", | |
| 43 | + "label": "live API tests green" | |
| 44 | + }, | |
| 45 | + { | |
| 46 | + "value": "58", | |
| 47 | + "label": "prompt templates" | |
| 48 | + }, | |
| 49 | + { | |
| 50 | + "value": "1", | |
| 51 | + "label": "third-party dependency" | |
| 52 | + }, | |
| 53 | + { | |
| 54 | + "value": "11", | |
| 55 | + "label": "syntax-highlighted language families" | |
| 56 | + } | |
| 57 | + ], | |
| 58 | + "features": [ | |
| 59 | + { | |
| 60 | + "icon": "Cloud", | |
| 61 | + "title": "12 providers, one interface", | |
| 62 | + "description": "OpenAI, Anthropic, Gemini, Mistral, DeepSeek, Grok and more — provider quirks isolated in a dedicated client layer that never leaks upward." | |
| 63 | + }, | |
| 64 | + { | |
| 65 | + "icon": "Zap", | |
| 66 | + "title": "True token streaming", | |
| 67 | + "description": "Server-Sent Events with a stop button, blinking stream caret, smooth auto-scroll, and reasoning tokens streamed into a collapsible thinking section." | |
| 68 | + }, | |
| 69 | + { | |
| 70 | + "icon": "Split", | |
| 71 | + "title": "Compare mode", | |
| 72 | + "description": "Broadcast one prompt to 2–4 models side-by-side, each column streaming independently with its own copy and regenerate controls." | |
| 73 | + }, | |
| 74 | + { | |
| 75 | + "icon": "Lock", | |
| 76 | + "title": "Machine-bound encrypted vault", | |
| 77 | + "description": "AES-256-GCM via CryptoKit with an HKDF key derived from the Mac's hardware UUID — the vault is useless if copied to another computer." | |
| 78 | + }, | |
| 79 | + { | |
| 80 | + "icon": "Command", | |
| 81 | + "title": "Command palette and Quick Chat", | |
| 82 | + "description": "One ⌘K search box over models, templates and personas, plus a global ⌥Space Spotlight-style panel available from any app." | |
| 83 | + }, | |
| 84 | + { | |
| 85 | + "icon": "Eye", | |
| 86 | + "title": "Vision and attachments", | |
| 87 | + "description": "Drag and drop images for vision models, encoded per provider automatically; text and code files in 30+ extensions injected inline." | |
| 88 | + }, | |
| 89 | + { | |
| 90 | + "icon": "DollarSign", | |
| 91 | + "title": "Token usage and cost tracking", | |
| 92 | + "description": "Per-message and per-conversation cost computed from each model's real pricing, visible on hover and in the sidebar footer." | |
| 93 | + }, | |
| 94 | + { | |
| 95 | + "icon": "FileText", | |
| 96 | + "title": "Markdown done right", | |
| 97 | + "description": "GFM tables, task lists, blockquotes, and code blocks with language labels, copy buttons, and a built-in highlighter covering 11 language families." | |
| 98 | + }, | |
| 99 | + { | |
| 100 | + "icon": "Sparkles", | |
| 101 | + "title": "Prompt library and personas", | |
| 102 | + "description": "58 hand-crafted templates across 8 categories and 8 built-in personas bundling system prompts with preferences, applied in two keystrokes." | |
| 103 | + } | |
| 104 | + ], | |
| 105 | + "techStack": [ | |
| 106 | + { | |
| 107 | + "category": "App", | |
| 108 | + "items": [ | |
| 109 | + "Swift 5.9+", | |
| 110 | + "SwiftUI", | |
| 111 | + "AppKit", | |
| 112 | + "macOS 13+", | |
| 113 | + "Universal (arm64 + x86_64)" | |
| 114 | + ] | |
| 115 | + }, | |
| 116 | + { | |
| 117 | + "category": "Core", | |
| 118 | + "items": [ | |
| 119 | + "URLSession (SSE streaming)", | |
| 120 | + "CryptoKit (AES-256-GCM, HKDF)", | |
| 121 | + "swift-markdown", | |
| 122 | + "Swift Package Manager" | |
| 123 | + ] | |
| 124 | + }, | |
| 125 | + { | |
| 126 | + "category": "AI", | |
| 127 | + "items": [ | |
| 128 | + "OpenAI-compatible client (11 providers)", | |
| 129 | + "Native Anthropic Messages API", | |
| 130 | + "Custom OpenAI-compatible endpoints", | |
| 131 | + "170-model catalog" | |
| 132 | + ] | |
| 133 | + }, | |
| 134 | + { | |
| 135 | + "category": "Quality", | |
| 136 | + "items": [ | |
| 137 | + "zyquo-verify live harness", | |
| 138 | + "Swift Testing", | |
| 139 | + "Developer ID signed + notarized + stapled" | |
| 140 | + ] | |
| 141 | + } | |
| 142 | + ], | |
| 143 | + "architecture": [ | |
| 144 | + { | |
| 145 | + "title": "Provider layer", | |
| 146 | + "description": "One OpenAICompatibleClient covers 11 providers plus custom endpoints; a native AnthropicClient speaks the Messages API. Quirks like auth headers and reasoning-effort values never leak past this layer." | |
| 147 | + }, | |
| 148 | + { | |
| 149 | + "title": "StreamingService and ModelCatalog", | |
| 150 | + "description": "SSE streaming, a generated 170-model catalog as the single source of truth for models, and a SecureKeyStore backing the encrypted vault." | |
| 151 | + }, | |
| 152 | + { | |
| 153 | + "title": "ConversationStore chat engine", | |
| 154 | + "description": "A @MainActor view-model layer built on structured concurrency — async/await and AsyncThrowingStream drive the chat, compare mode, and Quick Chat surfaces." | |
| 155 | + }, | |
| 156 | + { | |
| 157 | + "title": "ZyquoTheme design system", | |
| 158 | + "description": "Every color, font, spacing, and radius comes from one semantic token system with light and dark themes and five accent colors — no raw hex in views." | |
| 159 | + }, | |
| 160 | + { | |
| 161 | + "title": "zyquo-verify harness", | |
| 162 | + "description": "A --verify mode drives the exact production clients against live APIs: /models diffs, a completion on all 170 models, streaming and vision sweeps — 202/202 green." | |
| 163 | + } | |
| 164 | + ], | |
| 165 | + "highlights": [ | |
| 166 | + "Built entirely without the Xcode IDE — Swift Package Manager and Command Line Tools only", | |
| 167 | + "Every one of the 170 catalog models verified live against the real APIs: 202/202 tests green", | |
| 168 | + "Developer ID signed, notarized by Apple, and stapled — universal arm64 + x86_64 binary", | |
| 169 | + "Exactly one third-party dependency: Apple's swift-markdown; networking is plain URLSession, crypto is CryptoKit", | |
| 170 | + "Deliberately avoids the macOS Keychain — a machine-bound AES-256-GCM vault with no keychain prompts and no iCloud sync of secrets", | |
| 171 | + "Provider behavior documented in a 1,600-line API research dossier (docs/PROVIDERS.md)" | |
| 172 | + ] | |
| 173 | + }, | |
| 174 | + { | |
| 175 | + "slug": "zyquo-local", | |
| 176 | + "hero": { | |
| 177 | + "headline": "Every token stays on your Mac", | |
| 178 | + "subheadline": "A native macOS chat client that runs large language models 100% locally on Apple Silicon with MLX — no API keys, no cloud." | |
| 179 | + }, | |
| 180 | + "overview": [ | |
| 181 | + "Zyquo Local runs large language models entirely on-device through Apple's MLX framework. There is no server to send your prompts to: inference happens on your Mac's unified memory and GPU, and the only network calls are the model downloads you trigger yourself from Hugging Face.", | |
| 182 | + "The app supports 57 model architectures out of the box — Llama, Qwen, Mistral, Gemma, Phi, DeepSeek distills, gpt-oss and more — with a curated Featured catalog of 30 verified models, live Hugging Face search, and RAM verdicts (Fits / Tight / Too large) computed against your specific Mac before you download anything.", | |
| 183 | + "It is 7,300+ lines of Swift 6 with zero compiler warnings, built without the Xcode IDE. Generation streams with a live tokens-per-second ticker, thinking models get a collapsible thought-process section, and unloading a model provably returns gigabytes of memory to the OS." | |
| 184 | + ], | |
| 185 | + "stats": [ | |
| 186 | + { | |
| 187 | + "value": "57", | |
| 188 | + "label": "supported architectures" | |
| 189 | + }, | |
| 190 | + { | |
| 191 | + "value": "30", | |
| 192 | + "label": "curated catalog models" | |
| 193 | + }, | |
| 194 | + { | |
| 195 | + "value": "100%", | |
| 196 | + "label": "on-device inference" | |
| 197 | + }, | |
| 198 | + { | |
| 199 | + "value": "222.8", | |
| 200 | + "label": "tok/s (Llama-3.2-1B, M5 Max)" | |
| 201 | + }, | |
| 202 | + { | |
| 203 | + "value": "56", | |
| 204 | + "label": "prompt templates" | |
| 205 | + }, | |
| 206 | + { | |
| 207 | + "value": "7,300+", | |
| 208 | + "label": "lines of Swift 6" | |
| 209 | + } | |
| 210 | + ], | |
| 211 | + "features": [ | |
| 212 | + { | |
| 213 | + "icon": "WifiOff", | |
| 214 | + "title": "Fully offline inference", | |
| 215 | + "description": "100% on-device generation via mlx-swift-lm — no API keys, no accounts, no telemetry; chat works completely offline once a model is downloaded." | |
| 216 | + }, | |
| 217 | + { | |
| 218 | + "icon": "Search", | |
| 219 | + "title": "In-app Hugging Face discovery", | |
| 220 | + "description": "Live search across the Featured catalog, mlx-community, and all MLX-tagged repos, with gated-repo and unsupported-architecture warnings on every card." | |
| 221 | + }, | |
| 222 | + { | |
| 223 | + "icon": "Download", | |
| 224 | + "title": "Industrial-grade download manager", | |
| 225 | + "description": "Pause, resume, and cancel per model; HTTP Range resume that survives app restarts; automatic retry with backoff; disk-space pre-checks before a single byte." | |
| 226 | + }, | |
| 227 | + { | |
| 228 | + "icon": "Gauge", | |
| 229 | + "title": "RAM verdicts everywhere", | |
| 230 | + "description": "A MemoryAdvisor reads your Mac's physical memory and stamps every model Fits, Tight, or Too large — you never download something you can't run." | |
| 231 | + }, | |
| 232 | + { | |
| 233 | + "icon": "Brain", | |
| 234 | + "title": "Reasoning display", | |
| 235 | + "description": "Think-tag output from DeepSeek-R1 distills, Qwen3 thinking mode and QwQ-class models streams into a collapsible thought-process section, parsed incrementally." | |
| 236 | + }, | |
| 237 | + { | |
| 238 | + "icon": "LineChart", | |
| 239 | + "title": "First-class generation stats", | |
| 240 | + "description": "Tokens per second, token count, and time-to-first-token under every response, plus peak-memory tracking per generation and a live context-usage bar." | |
| 241 | + }, | |
| 242 | + { | |
| 243 | + "icon": "HardDrive", | |
| 244 | + "title": "Verifiable memory hygiene", | |
| 245 | + "description": "One model loaded at a time with explicit Load/Unload — unloading measurably returns memory to the OS, from gigabytes down to kilobytes." | |
| 246 | + }, | |
| 247 | + { | |
| 248 | + "icon": "Split", | |
| 249 | + "title": "Compare two models", | |
| 250 | + "description": "Race two local models side-by-side on the same prompt with independent streaming and stats, RAM-gated so you can't load a pair your memory can't hold." | |
| 251 | + }, | |
| 252 | + { | |
| 253 | + "icon": "Command", | |
| 254 | + "title": "Quick Chat from anywhere", | |
| 255 | + "description": "A global ⌥Space Spotlight-style floating panel for one-shot questions to the loaded model, from any app, with no accessibility permissions needed." | |
| 256 | + } | |
| 257 | + ], | |
| 258 | + "techStack": [ | |
| 259 | + { | |
| 260 | + "category": "App", | |
| 261 | + "items": [ | |
| 262 | + "Swift 6", | |
| 263 | + "SwiftUI", | |
| 264 | + "macOS 14+", | |
| 265 | + "Apple Silicon (arm64)" | |
| 266 | + ] | |
| 267 | + }, | |
| 268 | + { | |
| 269 | + "category": "Inference", | |
| 270 | + "items": [ | |
| 271 | + "Apple MLX", | |
| 272 | + "mlx-swift-lm", | |
| 273 | + "swift-transformers", | |
| 274 | + "swift-huggingface" | |
| 275 | + ] | |
| 276 | + }, | |
| 277 | + { | |
| 278 | + "category": "Core", | |
| 279 | + "items": [ | |
| 280 | + "Swift actors", | |
| 281 | + "AsyncThrowingStream", | |
| 282 | + "Custom URLSession transport (Range-resumable)", | |
| 283 | + "swift-markdown" | |
| 284 | + ] | |
| 285 | + }, | |
| 286 | + { | |
| 287 | + "category": "Quality", | |
| 288 | + "items": [ | |
| 289 | + "--verify end-to-end harness", | |
| 290 | + "Developer ID signed + notarized + stapled", | |
| 291 | + "Swift Package Manager (no .xcodeproj)" | |
| 292 | + ] | |
| 293 | + } | |
| 294 | + ], | |
| 295 | + "architecture": [ | |
| 296 | + { | |
| 297 | + "title": "InferenceEngine actor", | |
| 298 | + "description": "All inference lives behind one Swift actor with states flowing unloaded → loading → ready ⇄ generating; generation is an AsyncThrowingStream of token, stats, and finish events, and cancellation genuinely stops the GPU loop." | |
| 299 | + }, | |
| 300 | + { | |
| 301 | + "title": "Hub layer", | |
| 302 | + "description": "HubService, DownloadManager, FileTransfer and ModelStore handle discovery and delivery: chunked, delegate-backed downloads with atomic .partial-to-final completion and per-file size verification." | |
| 303 | + }, | |
| 304 | + { | |
| 305 | + "title": "ChatController with think-parser", | |
| 306 | + "description": "Streams tokens into the UI while incrementally parsing <think> tags — robust even when tags split across token chunks — and manages KV-cache reuse across turns with automatic oldest-turn truncation." | |
| 307 | + }, | |
| 308 | + { | |
| 309 | + "title": "MemoryAdvisor", | |
| 310 | + "description": "Reads physical memory and current pressure to gate model loads, stamp catalog entries with Fits/Tight/Too-large verdicts, and RAM-gate compare mode." | |
| 311 | + }, | |
| 312 | + { | |
| 313 | + "title": "Verification harness", | |
| 314 | + "description": "ZyquoLocal --verify downloads real models through the app's own pipeline and checks loading, deterministic generation, multi-turn recall, streaming cancellation, and memory release." | |
| 315 | + } | |
| 316 | + ], | |
| 317 | + "highlights": [ | |
| 318 | + "No server exists to read your prompts — conversations, tokens, and system prompts never leave the machine", | |
| 319 | + "Every Featured-catalog repo ID and download size verified against the live Hugging Face Hub (30/30)", | |
| 320 | + "Verified on an Apple M5 Max: up to 222.8 tok/s on Llama-3.2-1B and 0.08 s time-to-first-token on Qwen3-0.6B", | |
| 321 | + "Built with plain Swift Package Manager — no .xcodeproj, the Xcode IDE never required", | |
| 322 | + "Developer ID signed, notarized by Apple, and stapled — installs with zero Gatekeeper warnings", | |
| 323 | + "KV-cache reuse across turns means no re-prefill, with the system prompt always surviving context truncation" | |
| 324 | + ] | |
| 325 | + }, | |
| 326 | + { | |
| 327 | + "slug": "zyquo-agent", | |
| 328 | + "hero": { | |
| 329 | + "headline": "The autonomous agent that actually operates your Mac", | |
| 330 | + "subheadline": "A native macOS agent that plans, runs real bash and AppleScript, verifies its own work — and always asks before anything risky." | |
| 331 | + }, | |
| 332 | + "overview": [ | |
| 333 | + "Zyquo Agent turns cloud LLMs into an agent that does things on your Mac: it runs bash commands, drives apps with AppleScript, reads and writes files, and keeps iterating until a task is genuinely done. It is not a chat window with a shell attached — it is a real plan → act → observe → reflect loop with a live, editable checklist.", | |
| 334 | + "Safety is the headline feature. Every action passes a PolicyEngine gate that parses commands rather than pattern-matching them, splitting compound lines and classifying each subcommand. Hard denies block catastrophic commands outright; an always-ask class — sudo, out-of-workspace deletes, curl-pipe-to-shell — requires approval in every mode, including Autonomous. An append-only audit log records everything.", | |
| 335 | + "Everything is measured, not claimed: 77 of 80 agent-capable models pass a live tool-calling battery, 11 of 11 end-to-end scenarios pass in real workspaces, 8 of 8 safety tests pass, and the PolicyEngine self-check runs 38 assertions. The app is roughly 19,000 lines of Swift across 82 files, built without the Xcode IDE." | |
| 336 | + ], | |
| 337 | + "stats": [ | |
| 338 | + { | |
| 339 | + "value": "77/80", | |
| 340 | + "label": "models tool-calling verified live" | |
| 341 | + }, | |
| 342 | + { | |
| 343 | + "value": "8/8", | |
| 344 | + "label": "safety tests passed" | |
| 345 | + }, | |
| 346 | + { | |
| 347 | + "value": "11/11", | |
| 348 | + "label": "end-to-end scenarios passed" | |
| 349 | + }, | |
| 350 | + { | |
| 351 | + "value": "38", | |
| 352 | + "label": "PolicyEngine self-check assertions" | |
| 353 | + }, | |
| 354 | + { | |
| 355 | + "value": "12", | |
| 356 | + "label": "providers, 170 models" | |
| 357 | + }, | |
| 358 | + { | |
| 359 | + "value": "~19,000", | |
| 360 | + "label": "lines of Swift, 82 files" | |
| 361 | + } | |
| 362 | + ], | |
| 363 | + "features": [ | |
| 364 | + { | |
| 365 | + "icon": "Workflow", | |
| 366 | + "title": "True agentic loop", | |
| 367 | + "description": "A stop_reason-keyed while-loop streams a model turn, executes its tool calls, threads results back, and repeats — handling truncation, refusals, and transient errors." | |
| 368 | + }, | |
| 369 | + { | |
| 370 | + "icon": "ShieldCheck", | |
| 371 | + "title": "Policy gate on every action", | |
| 372 | + "description": "Commands are parsed, not pattern-matched: compound lines split per subcommand and the verdict is the most severe — ls && rm -rf still asks." | |
| 373 | + }, | |
| 374 | + { | |
| 375 | + "icon": "ListChecks", | |
| 376 | + "title": "Live editable plan", | |
| 377 | + "description": "The agent maintains a real checklist through an internal update_plan tool, persisted to disk, rendered live, and editable by you mid-run." | |
| 378 | + }, | |
| 379 | + { | |
| 380 | + "icon": "Terminal", | |
| 381 | + "title": "Real tools, streamed live", | |
| 382 | + "description": "bash, AppleScript, and five file tools with line-by-line stdout/stderr streaming, timeouts, exit codes, and full cancellability — even partial tool arguments stream." | |
| 383 | + }, | |
| 384 | + { | |
| 385 | + "icon": "Timer", | |
| 386 | + "title": "LoopGuard budgets", | |
| 387 | + "description": "Step, token, and wall-clock budgets plus repetition and stall detection — on a trip it pauses and asks you, never silently spins or aborts." | |
| 388 | + }, | |
| 389 | + { | |
| 390 | + "icon": "Brain", | |
| 391 | + "title": "Memory and context compaction", | |
| 392 | + "description": "At 85% of context, older steps are summarized by the same model while the plan, MEMORY.md, and the last 6 steps stay verbatim; large outputs offload to disk." | |
| 393 | + }, | |
| 394 | + { | |
| 395 | + "icon": "FolderTree", | |
| 396 | + "title": "Scoped workspaces", | |
| 397 | + "description": "Every task gets its own directory; file tools are scoped there by default and escaping requires explicit approval, with created and modified files tracked." | |
| 398 | + }, | |
| 399 | + { | |
| 400 | + "icon": "FileText", | |
| 401 | + "title": "Append-only audit log", | |
| 402 | + "description": "Every executed action recorded with timestamp, exact payload, cwd, policy ruling, exit code, and output — viewable in-app and exportable." | |
| 403 | + }, | |
| 404 | + { | |
| 405 | + "icon": "Bot", | |
| 406 | + "title": "Headless CLI mode", | |
| 407 | + "description": "The app binary doubles as a CLI for scripting and CI: run tasks headlessly with live rendering, stdin approvals, and policy self-checks." | |
| 408 | + } | |
| 409 | + ], | |
| 410 | + "techStack": [ | |
| 411 | + { | |
| 412 | + "category": "App", | |
| 413 | + "items": [ | |
| 414 | + "Swift 6", | |
| 415 | + "SwiftUI", | |
| 416 | + "AppKit", | |
| 417 | + "macOS 13+", | |
| 418 | + "Universal (arm64 + x86_64)" | |
| 419 | + ] | |
| 420 | + }, | |
| 421 | + { | |
| 422 | + "category": "Agent Core", | |
| 423 | + "items": [ | |
| 424 | + "AgentLoop (Swift actor)", | |
| 425 | + "PolicyEngine", | |
| 426 | + "LoopGuard", | |
| 427 | + "MemoryManager", | |
| 428 | + "AuditLog", | |
| 429 | + "WorkspaceManager" | |
| 430 | + ] | |
| 431 | + }, | |
| 432 | + { | |
| 433 | + "category": "AI", | |
| 434 | + "items": [ | |
| 435 | + "12 providers", | |
| 436 | + "170-model catalog", | |
| 437 | + "Native Anthropic tool_use client", | |
| 438 | + "OpenAI-compatible tool-calls client", | |
| 439 | + "Custom endpoints" | |
| 440 | + ] | |
| 441 | + }, | |
| 442 | + { | |
| 443 | + "category": "Security", | |
| 444 | + "items": [ | |
| 445 | + "AES-256-GCM key vault (CryptoKit)", | |
| 446 | + "HKDF machine-bound key derivation", | |
| 447 | + "Hardened runtime, Developer ID notarized" | |
| 448 | + ] | |
| 449 | + }, | |
| 450 | + { | |
| 451 | + "category": "Quality", | |
| 452 | + "items": [ | |
| 453 | + "Live verification harness (--verify)", | |
| 454 | + "--verify-policy (38 assertions)", | |
| 455 | + "swift-markdown (sole dependency)" | |
| 456 | + ] | |
| 457 | + } | |
| 458 | + ], | |
| 459 | + "architecture": [ | |
| 460 | + { | |
| 461 | + "title": "AgentLoop", | |
| 462 | + "description": "A Swift actor running the plan → act → observe → reflect cycle: stream a turn, execute tool calls, thread results back, repeat until the model answers without tools." | |
| 463 | + }, | |
| 464 | + { | |
| 465 | + "title": "PolicyEngine and ExecutionService", | |
| 466 | + "description": "Shell execution never leaks into views — everything goes through ExecutionService and passes PolicyEngine first, evaluating deny → ask → allow per parsed subcommand." | |
| 467 | + }, | |
| 468 | + { | |
| 469 | + "title": "Tool layer", | |
| 470 | + "description": "Eight tools behind a Tool protocol (name, description, JSON-Schema parameters, execute) registered in a ToolRegistry — adding a tool is deliberately trivial." | |
| 471 | + }, | |
| 472 | + { | |
| 473 | + "title": "MemoryManager", | |
| 474 | + "description": "Live token accounting calibrated by provider usage numbers, compaction at 85% of context with a thrash guard, output offloading over 8 KB, and an agent-owned MEMORY.md." | |
| 475 | + }, | |
| 476 | + { | |
| 477 | + "title": "Provider layer", | |
| 478 | + "description": "A native AnthropicClient and one OpenAICompatibleClient normalized behind a single ProviderClient protocol, so the agent loop never sees a wire format." | |
| 479 | + }, | |
| 480 | + { | |
| 481 | + "title": "Event-driven UI", | |
| 482 | + "description": "The command-center interface renders exclusively from an AgentEvent stream and the persisted Transcript — step cards, plan panel, live terminal feed, and audit views." | |
| 483 | + } | |
| 484 | + ], | |
| 485 | + "highlights": [ | |
| 486 | + "sudo is never run silently in any mode — proven by a test that forces a sudo tool call and confirms denial in all three safety modes", | |
| 487 | + "Three safety modes (Manual, Guarded, Autonomous) with an always-ask circuit-breaker class that no mode and no remembered rule can override", | |
| 488 | + "77 of 80 agent-capable models verified live for streamed tool calling; the 3 failures are external and documented", | |
| 489 | + "Found and fixed real provider quirks: Gemini thought-signature round-tripping and OpenAI reasoning_effort downgrading with tools", | |
| 490 | + "A task that triggered 4 context compactions still produced a fully correct result, verified live", | |
| 491 | + "Design traced to a 413-line research document citing 79 sources; built entirely without the Xcode IDE" | |
| 492 | + ] | |
| 493 | + }, | |
| 494 | + { | |
| 495 | + "slug": "zyquo-atlas", | |
| 496 | + "hero": { | |
| 497 | + "headline": "The AI-native macOS browser — every surface intelligent", | |
| 498 | + "subheadline": "A fast, radically customizable, privacy-first WebKit browser with AI woven into the omnibox, the page, your selection, and your tabs." | |
| 499 | + }, | |
| 500 | + "overview": [ | |
| 501 | + "Zyquo Atlas is a native macOS web browser built in Swift and SwiftUI on Apple's WebKit engine. Unlike browsers that bolt a chatbot onto a sidebar, Atlas weaves AI into every surface: ask from the omnibox, chat with the current page, act on any text selection, and reason across multiple open tabs — powered by your own keys across 12 providers and 169 models.", | |
| 502 | + "Privacy is structural, not a setting. Page content leaves the device only when you invoke an AI action, always to the provider you chose via your own key — never to Zyquo. Extraction runs Mozilla Readability in an isolated JavaScript world, keeps visible text only, and wraps page content as untrusted data, so a hostile page can produce a bad summary but never an action.", | |
| 503 | + "It is a complete browser underneath: real multi-tab WKWebView browsing with pinning and background-tab suspension, private windows, reader mode, profiles with isolated data stores, full-text history, bookmarks with Netscape HTML import/export, and a theming engine with 10 built-in themes and a live custom editor — in roughly 8,300 lines of Swift with zero warnings." | |
| 504 | + ], | |
| 505 | + "stats": [ | |
| 506 | + { | |
| 507 | + "value": "12", | |
| 508 | + "label": "AI providers" | |
| 509 | + }, | |
| 510 | + { | |
| 511 | + "value": "169", | |
| 512 | + "label": "chat models" | |
| 513 | + }, | |
| 514 | + { | |
| 515 | + "value": "183/183", | |
| 516 | + "label": "verification checks green" | |
| 517 | + }, | |
| 518 | + { | |
| 519 | + "value": "10", | |
| 520 | + "label": "built-in themes" | |
| 521 | + }, | |
| 522 | + { | |
| 523 | + "value": "~8,300", | |
| 524 | + "label": "lines of Swift, 58 files" | |
| 525 | + } | |
| 526 | + ], | |
| 527 | + "features": [ | |
| 528 | + { | |
| 529 | + "icon": "Globe", | |
| 530 | + "title": "Real multi-tab browsing", | |
| 531 | + "description": "Each tab owns its WKWebView and navigation state, with pinning, background-tab suspension that frees memory, and per-profile session restore." | |
| 532 | + }, | |
| 533 | + { | |
| 534 | + "icon": "Search", | |
| 535 | + "title": "Smart omnibox with Ask AI", | |
| 536 | + "description": "One input resolves into navigate, web search, or a streamed grounded AI answer — without leaving the page you're on." | |
| 537 | + }, | |
| 538 | + { | |
| 539 | + "icon": "MessageSquare", | |
| 540 | + "title": "Chat-with-page sidebar", | |
| 541 | + "description": "A per-tab, multi-turn conversation grounded in the page's extracted content, with an in-panel model picker across all 169 models." | |
| 542 | + }, | |
| 543 | + { | |
| 544 | + "icon": "Sparkles", | |
| 545 | + "title": "Selection floating toolbar", | |
| 546 | + "description": "Select any text on a page and get Explain, Summarize, Translate, Rewrite, or Ask right at the cursor." | |
| 547 | + }, | |
| 548 | + { | |
| 549 | + "icon": "Layers", | |
| 550 | + "title": "Multi-tab reasoning", | |
| 551 | + "description": "Compare-these-tabs gathers several open pages and answers across them; long articles are summarized via map-reduce beyond the model's context." | |
| 552 | + }, | |
| 553 | + { | |
| 554 | + "icon": "ShieldCheck", | |
| 555 | + "title": "Prompt-injection hardening", | |
| 556 | + "description": "Extraction drops hidden and off-screen text, labels page content untrusted, and gives the AI layer no navigation or tool access whatsoever." | |
| 557 | + }, | |
| 558 | + { | |
| 559 | + "icon": "Lock", | |
| 560 | + "title": "Machine-bound key vault", | |
| 561 | + "description": "API keys encrypted with AES-256-GCM and bound to the Mac via IOPlatformUUID + HKDF — not the Keychain, decrypted on demand, never logged." | |
| 562 | + }, | |
| 563 | + { | |
| 564 | + "icon": "Palette", | |
| 565 | + "title": "Theming engine", | |
| 566 | + "description": "10 built-in light and dark themes plus a live custom theme editor, backgrounds, top or Arc-style vertical tabs, density control, and a UI-size slider." | |
| 567 | + }, | |
| 568 | + { | |
| 569 | + "icon": "BookOpen", | |
| 570 | + "title": "Reader mode with AI summary", | |
| 571 | + "description": "A clean, themeable article view with an optional AI-generated summary at the top, plus native find-in-page and full-text history." | |
| 572 | + } | |
| 573 | + ], | |
| 574 | + "techStack": [ | |
| 575 | + { | |
| 576 | + "category": "App", | |
| 577 | + "items": [ | |
| 578 | + "Swift 5.9", | |
| 579 | + "SwiftUI", | |
| 580 | + "AppKit", | |
| 581 | + "macOS 13+", | |
| 582 | + "Universal (arm64 + x86_64)" | |
| 583 | + ] | |
| 584 | + }, | |
| 585 | + { | |
| 586 | + "category": "Browser", | |
| 587 | + "items": [ | |
| 588 | + "WebKit (WKWebView)", | |
| 589 | + "Mozilla Readability (vendored)", | |
| 590 | + "Isolated WKContentWorld extraction", | |
| 591 | + "Combine" | |
| 592 | + ] | |
| 593 | + }, | |
| 594 | + { | |
| 595 | + "category": "AI", | |
| 596 | + "items": [ | |
| 597 | + "12 providers, 169 models", | |
| 598 | + "OpenAI-compatible + native Anthropic clients", | |
| 599 | + "SSE streaming, reasoning-token aware" | |
| 600 | + ] | |
| 601 | + }, | |
| 602 | + { | |
| 603 | + "category": "Security", | |
| 604 | + "items": [ | |
| 605 | + "CryptoKit (AES-256-GCM, HKDF)", | |
| 606 | + "IOKit machine binding", | |
| 607 | + "Hardened Runtime, notarized + stapled" | |
| 608 | + ] | |
| 609 | + } | |
| 610 | + ], | |
| 611 | + "architecture": [ | |
| 612 | + { | |
| 613 | + "title": "Browser layer", | |
| 614 | + "description": "WebView wraps WKWebView with TabManager, ProfileStore, OmniIntent resolution, and a DownloadManager — each profile gets an isolated WebKit data store." | |
| 615 | + }, | |
| 616 | + { | |
| 617 | + "title": "Content extraction pipeline", | |
| 618 | + "description": "ContentExtractor runs Readability.js and AtlasExtractor.js in an isolated WKContentWorld, producing visible-text-only PageContext with a Chunker for long documents." | |
| 619 | + }, | |
| 620 | + { | |
| 621 | + "title": "AI layer", | |
| 622 | + "description": "AIService streams responses and cancels automatically on navigation; AIActions and Summarizer implement quick actions, selection actions, and map-reduce summarization." | |
| 623 | + }, | |
| 624 | + { | |
| 625 | + "title": "Shared Zyquo provider layer", | |
| 626 | + "description": "The same ModelCatalog, SecureKeyStore, and streaming clients as Zyquo Cloud — byte-compatible across the family, so keys and models feel identical." | |
| 627 | + }, | |
| 628 | + { | |
| 629 | + "title": "Verification harness", | |
| 630 | + "description": "A built-in --verify mode exercises extraction, a grounded summarize across every one of the 169 models, the full AI-action matrix, and privacy invariants — 183/183 green." | |
| 631 | + } | |
| 632 | + ], | |
| 633 | + "highlights": [ | |
| 634 | + "AI has no navigation or tool access by design — a hostile page can only produce a bad summary, never an action", | |
| 635 | + "183/183 live verification checks green, including a grounded summarize on every one of the 169 catalog models", | |
| 636 | + "Browsing works fully without any API key; content leaves the device only on user-invoked AI actions", | |
| 637 | + "Built with Swift Package Manager on WebKit — no Xcode project, no third-party HTTP libraries, Apple frameworks only", | |
| 638 | + "Streamed AI responses cancel automatically on navigation as a privacy invariant", | |
| 639 | + "Developer ID signed, notarized and stapled; source-available with the compiled app free for personal use" | |
| 640 | + ] | |
| 641 | + }, | |
| 642 | + { | |
| 643 | + "slug": "zyquo-mlx", | |
| 644 | + "hero": { | |
| 645 | + "headline": "The on-device model foundry for Apple Silicon", | |
| 646 | + "subheadline": "Run, fine-tune, quantize, and convert LLMs 100% locally with MLX — the complete model workbench, nothing leaves your machine." | |
| 647 | + }, | |
| 648 | + "overview": [ | |
| 649 | + "Zyquo MLX is the foundry of the Zyquo family: where Zyquo Local is the chat client, MLX is the complete workbench for the on-device model lifecycle on Apple Silicon. It runs every MLX model type — streaming LLM chat, vision-language models with images, embeddings with a live similarity inspector, and Whisper speech-to-text — with tokens/sec, time-to-first-token, and verified memory release on every run.", | |
| 650 | + "Fine-tuning is first-class: LoRA, QLoRA, DoRA, and full fine-tuning through a real configurator with live loss curves, checkpoints, cancel and warm resume, and memory gating that blocks impossible configs before they run. Quantization to 4 or 8 bits ships size previews accurate to 0.1%, adapter fusing with a smart de-quantize default, and Hugging Face to MLX conversion.", | |
| 651 | + "Everything stays local — no API keys, no telemetry; the only network traffic is downloading the models you ask for. A live-verified Featured catalog and full mlx-community search come with RAM compatibility badges for your specific Mac, and datasets get row-by-row JSONL validation with concrete fixes and deterministic train/valid splits." | |
| 652 | + ], | |
| 653 | + "stats": [ | |
| 654 | + { | |
| 655 | + "value": "4", | |
| 656 | + "label": "fine-tuning methods (LoRA, QLoRA, DoRA, full)" | |
| 657 | + }, | |
| 658 | + { | |
| 659 | + "value": "604–630", | |
| 660 | + "label": "tok/s inference (Qwen3-0.6B, M5 Max)" | |
| 661 | + }, | |
| 662 | + { | |
| 663 | + "value": "0.1%", | |
| 664 | + "label": "quantization size-preview accuracy" | |
| 665 | + }, | |
| 666 | + { | |
| 667 | + "value": "~3,300", | |
| 668 | + "label": "tok/s LoRA training throughput" | |
| 669 | + }, | |
| 670 | + { | |
| 671 | + "value": "1.0 s", | |
| 672 | + "label": "to transcribe a 7 s clip (Whisper large-v3-turbo)" | |
| 673 | + }, | |
| 674 | + { | |
| 675 | + "value": "100%", | |
| 676 | + "label": "local — zero telemetry" | |
| 677 | + } | |
| 678 | + ], | |
| 679 | + "features": [ | |
| 680 | + { | |
| 681 | + "icon": "Cpu", | |
| 682 | + "title": "Every MLX model type", | |
| 683 | + "description": "Streaming LLM chat, vision-language models with images, embeddings with a live similarity inspector, and Whisper speech-to-text — all on-device." | |
| 684 | + }, | |
| 685 | + { | |
| 686 | + "icon": "Flame", | |
| 687 | + "title": "Fine-tune on your data", | |
| 688 | + "description": "LoRA, QLoRA, DoRA, and full fine-tuning with live loss curves, checkpoints, cancel and warm resume, and a real training configurator." | |
| 689 | + }, | |
| 690 | + { | |
| 691 | + "icon": "Scale", | |
| 692 | + "title": "Memory-gated training", | |
| 693 | + "description": "Impossible training configurations are blocked before they run, based on your Mac's actual memory — no mid-run out-of-memory surprises." | |
| 694 | + }, | |
| 695 | + { | |
| 696 | + "icon": "Boxes", | |
| 697 | + "title": "Quantize and convert", | |
| 698 | + "description": "4/8-bit affine quantization with size previews accurate to 0.1%, adapter fusing with a smart de-quantize default, and Hugging Face to MLX conversion." | |
| 699 | + }, | |
| 700 | + { | |
| 701 | + "icon": "Database", | |
| 702 | + "title": "Datasets done right", | |
| 703 | + "description": "Import JSONL in chat, prompt-completion, or text formats with row-by-row validation, concrete fixes, deterministic train/valid splits, and token stats." | |
| 704 | + }, | |
| 705 | + { | |
| 706 | + "icon": "Search", | |
| 707 | + "title": "Model discovery with RAM badges", | |
| 708 | + "description": "A live-verified Featured catalog plus full mlx-community search, stamped with RAM compatibility badges for your Mac, and resumable downloads." | |
| 709 | + }, | |
| 710 | + { | |
| 711 | + "icon": "BarChart3", | |
| 712 | + "title": "Side-by-side evaluation", | |
| 713 | + "description": "Compare the base model against your fine-tune on the same prompt, with per-side generation statistics." | |
| 714 | + }, | |
| 715 | + { | |
| 716 | + "icon": "LineChart", | |
| 717 | + "title": "Measured, verified performance", | |
| 718 | + "description": "Tokens/sec, time-to-first-token, and verified memory release on every run; quantization predicted 335.3 MB and produced 335.5 MB." | |
| 719 | + }, | |
| 720 | + { | |
| 721 | + "icon": "WifiOff", | |
| 722 | + "title": "Nothing leaves your machine", | |
| 723 | + "description": "No API keys and no telemetry — the only network traffic is downloading the models you explicitly request." | |
| 724 | + } | |
| 725 | + ], | |
| 726 | + "techStack": [ | |
| 727 | + { | |
| 728 | + "category": "App", | |
| 729 | + "items": [ | |
| 730 | + "Swift", | |
| 731 | + "SwiftUI", | |
| 732 | + "macOS 14+", | |
| 733 | + "Apple Silicon (arm64)" | |
| 734 | + ] | |
| 735 | + }, | |
| 736 | + { | |
| 737 | + "category": "Inference", | |
| 738 | + "items": [ | |
| 739 | + "mlx-swift", | |
| 740 | + "mlx-swift-lm", | |
| 741 | + "Swift-native quantization" | |
| 742 | + ] | |
| 743 | + }, | |
| 744 | + { | |
| 745 | + "category": "Training", | |
| 746 | + "items": [ | |
| 747 | + "Pinned mlx-lm 0.31.3 via PyBridge", | |
| 748 | + "Isolated uv-provisioned Python venv", | |
| 749 | + "Strict JSON-lines progress protocol" | |
| 750 | + ] | |
| 751 | + }, | |
| 752 | + { | |
| 753 | + "category": "Quality", | |
| 754 | + "items": [ | |
| 755 | + "Live verification matrix (docs/VERIFICATION.md)", | |
| 756 | + "Developer ID signed + notarized", | |
| 757 | + "Command-line build, no .xcodeproj" | |
| 758 | + ] | |
| 759 | + } | |
| 760 | + ], | |
| 761 | + "architecture": [ | |
| 762 | + { | |
| 763 | + "title": "InferenceEngine actor", | |
| 764 | + "description": "A Swift actor handling LLM, VLM, and embedding inference natively via mlx-swift, with a MemoryAdvisor gating loads and a SpeechService for Whisper transcription." | |
| 765 | + }, | |
| 766 | + { | |
| 767 | + "title": "Training layer", | |
| 768 | + "description": "TrainingService, RunStore, and MetricsStream drive fine-tuning runs through a live JSON protocol — loss curves, checkpoints, and warm resume all flow through it." | |
| 769 | + }, | |
| 770 | + { | |
| 771 | + "title": "PyBridge", | |
| 772 | + "description": "A PythonRunner drives a pinned mlx-lm 0.31.3 in an isolated, uv-provisioned venv through strict JSON-lines scripts — Python is an implementation detail, never the interface." | |
| 773 | + }, | |
| 774 | + { | |
| 775 | + "title": "Convert and Data services", | |
| 776 | + "description": "ConversionService combines Swift-native quantization with Python fuse/convert paths; DatasetService validates JSONL row-by-row, splits deterministically, and previews token stats." | |
| 777 | + }, | |
| 778 | + { | |
| 779 | + "title": "Hub layer", | |
| 780 | + "description": "HubService, a resumable DownloadManager, and ModelStore handle catalog discovery, mlx-community search, and verified model delivery." | |
| 781 | + } | |
| 782 | + ], | |
| 783 | + "highlights": [ | |
| 784 | + "Quantization size predictions accurate to 0.1% — a 1.19 GB fp16 model predicted at 335.3 MB came out at 335.5 MB", | |
| 785 | + "QLoRA training at ~2,300 tok/s with a 0.8 GB peak; LoRA at ~3,300 tok/s — measured on an M5 Max", | |
| 786 | + "Works around two documented upstream mlx-lm landmines, with the research published in docs/TRAINING-RESEARCH.md", | |
| 787 | + "Swift-native inference and quantization; Python only for training and speech, pinned and sandboxed behind a JSON protocol", | |
| 788 | + "Built without the Xcode IDE — command-line only, no .xcodeproj", | |
| 789 | + "Signed and notarized with a Developer ID; MIT licensed" | |
| 790 | + ] | |
| 791 | + }, | |
| 792 | + { | |
| 793 | + "slug": "zyquo-router", | |
| 794 | + "hero": { | |
| 795 | + "headline": "One local endpoint. Every AI provider.", | |
| 796 | + "subheadline": "A native macOS gateway that puts 170 models from 12 providers behind a single OpenAI-compatible API — private, spec-exact, gorgeous." | |
| 797 | + }, | |
| 798 | + "overview": [ | |
| 799 | + "Every AI provider speaks a slightly different dialect — Anthropic wants x-api-key and content blocks, Gemini wants camelCase contents/parts, Perplexity ends streams with non-spec events. Your tools speak one dialect: the OpenAI API. Zyquo Router runs a tiny native gateway on your Mac that translates all of them into byte-exact OpenAI wire format.", | |
| 800 | + "Store your provider keys once in an AES-256-GCM encrypted, machine-bound vault, pick a port, press Start — and anything that can talk to OpenAI can now talk to twelve providers through localhost, with per-request model routing, fallback chains, live traffic inspection, and real per-model cost tracking.", | |
| 801 | + "It is 100% native Swift: a SwiftUI control room with a menu bar extra, a SwiftNIO 2 server with structured concurrency, and zero heavyweight dependencies. Think OpenRouter or LiteLLM — but local, private, and a real Mac app, not a Docker container with a YAML file." | |
| 802 | + ], | |
| 803 | + "stats": [ | |
| 804 | + { | |
| 805 | + "value": "170", | |
| 806 | + "label": "models in the catalog" | |
| 807 | + }, | |
| 808 | + { | |
| 809 | + "value": "12", | |
| 810 | + "label": "AI providers" | |
| 811 | + }, | |
| 812 | + { | |
| 813 | + "value": "170/170", | |
| 814 | + "label": "verification matrix green" | |
| 815 | + }, | |
| 816 | + { | |
| 817 | + "value": "~15 MB", | |
| 818 | + "label": "disk footprint" | |
| 819 | + }, | |
| 820 | + { | |
| 821 | + "value": "0", | |
| 822 | + "label": "accounts required" | |
| 823 | + } | |
| 824 | + ], | |
| 825 | + "features": [ | |
| 826 | + { | |
| 827 | + "icon": "Braces", | |
| 828 | + "title": "Spec-exact OpenAI API", | |
| 829 | + "description": "Byte-exact chat.completion.chunk SSE streams that the official OpenAI Python and JS SDKs parse unmodified, verified across all 170 models." | |
| 830 | + }, | |
| 831 | + { | |
| 832 | + "icon": "ArrowLeftRight", | |
| 833 | + "title": "Full protocol translation", | |
| 834 | + "description": "Anthropic Messages API and Gemini generateContent translated bidirectionally — system extraction, tool calls, images, finish-reason and usage normalization." | |
| 835 | + }, | |
| 836 | + { | |
| 837 | + "icon": "GitBranch", | |
| 838 | + "title": "Routing, aliases, fallback chains", | |
| 839 | + "description": "Namespaced provider/model routing, friendly aliases like fast and best, and ordered fallback lists tried on upstream failure with honest model reporting." | |
| 840 | + }, | |
| 841 | + { | |
| 842 | + "icon": "KeyRound", | |
| 843 | + "title": "Encrypted key vault", | |
| 844 | + "description": "AES-256-GCM with an HKDF-derived, machine-bound master key. Local zyquo-sk bearer tokens are SHA-256-hashed at rest with per-key model allow-lists." | |
| 845 | + }, | |
| 846 | + { | |
| 847 | + "icon": "Gauge", | |
| 848 | + "title": "Live observability", | |
| 849 | + "description": "Requests/min sparkline, token and cost tracking from real per-model pricing, and a request inspector with an upstream-TTFB timing waterfall." | |
| 850 | + }, | |
| 851 | + { | |
| 852 | + "icon": "Brain", | |
| 853 | + "title": "Reasoning-model normalization", | |
| 854 | + "description": "Thinking output unified into reasoning_content across Claude thinking, Gemini thoughts, DeepSeek-R1, Qwen, Magistral, and Perplexity think tags." | |
| 855 | + }, | |
| 856 | + { | |
| 857 | + "icon": "FlaskConical", | |
| 858 | + "title": "Built-in playground and docs", | |
| 859 | + "description": "An in-app tester that calls the router's own endpoint with side-by-side request JSON and raw SSE panes, plus fully rendered API docs." | |
| 860 | + }, | |
| 861 | + { | |
| 862 | + "icon": "Terminal", | |
| 863 | + "title": "Headless CLI modes", | |
| 864 | + "description": "Run the gateway without the UI via --serve, and seed the key vault from environment variables with --load-vault for scripting and CI." | |
| 865 | + }, | |
| 866 | + { | |
| 867 | + "icon": "RefreshCw", | |
| 868 | + "title": "Production-grade resilience", | |
| 869 | + "description": "Exponential-backoff retries with jitter, honest OpenAI-format error mapping, and client disconnects that cancel the upstream call in under one second." | |
| 870 | + } | |
| 871 | + ], | |
| 872 | + "techStack": [ | |
| 873 | + { | |
| 874 | + "category": "Native App", | |
| 875 | + "items": [ | |
| 876 | + "Swift 5.9", | |
| 877 | + "SwiftUI", | |
| 878 | + "SwiftNIO 2", | |
| 879 | + "Swift Package Manager", | |
| 880 | + "Universal binary (Apple Silicon + Intel)" | |
| 881 | + ] | |
| 882 | + }, | |
| 883 | + { | |
| 884 | + "category": "Security", | |
| 885 | + "items": [ | |
| 886 | + "AES-256-GCM vault", | |
| 887 | + "HKDF machine-bound keys", | |
| 888 | + "SHA-256 hashed local API keys", | |
| 889 | + "Developer ID signed & notarized" | |
| 890 | + ] | |
| 891 | + }, | |
| 892 | + { | |
| 893 | + "category": "Compatibility", | |
| 894 | + "items": [ | |
| 895 | + "OpenAI Chat Completions API", | |
| 896 | + "Anthropic Messages API", | |
| 897 | + "Gemini generateContent", | |
| 898 | + "SSE streaming" | |
| 899 | + ] | |
| 900 | + }, | |
| 901 | + { | |
| 902 | + "category": "Tooling", | |
| 903 | + "items": [ | |
| 904 | + "Makefile build pipeline", | |
| 905 | + "Fixture-tested translators", | |
| 906 | + "verify.py 170-model matrix", | |
| 907 | + "Mock-upstream integration tests" | |
| 908 | + ] | |
| 909 | + } | |
| 910 | + ], | |
| 911 | + "architecture": [ | |
| 912 | + { | |
| 913 | + "title": "SwiftNIO HTTP server", | |
| 914 | + "description": "An HTTP/1.1 server built on structured concurrency with one task per connection and a spec-exact SSE writer; disconnects propagate as cancellation into the upstream transfer." | |
| 915 | + }, | |
| 916 | + { | |
| 917 | + "title": "RequestRouter", | |
| 918 | + "description": "Resolves namespaces, aliases, fallback chains, and capability gates to pick the upstream provider and model for every request." | |
| 919 | + }, | |
| 920 | + { | |
| 921 | + "title": "Translation layer", | |
| 922 | + "description": "Fixture-tested state machines — AnthropicTranslator, GeminiTranslator, and a CompatAdjuster with per-provider parameter tables — iron ten providers' quirks into exact OpenAI chunks." | |
| 923 | + }, | |
| 924 | + { | |
| 925 | + "title": "Upstream calls and vault", | |
| 926 | + "description": "Requests go straight from your Mac to the provider using keys from the AES-256-GCM vault; no middleman, no telemetry, no accounts." | |
| 927 | + }, | |
| 928 | + { | |
| 929 | + "title": "Local persistence", | |
| 930 | + "description": "JSON documents in Application Support and keys in vault.zq with a machine-bound HKDF key — no Keychain, no plaintext, ever." | |
| 931 | + } | |
| 932 | + ], | |
| 933 | + "highlights": [ | |
| 934 | + "Every release drives all 170 catalog models through the endpoint with the official OpenAI Python SDK — streaming discipline, tool calling, vision, and reasoning — and the matrix is 170/170 green.", | |
| 935 | + "100% native Swift with zero heavyweight dependencies: SwiftNIO, swift-nio-extras, and swift-markdown. No Electron, no Python sidecar, no Docker.", | |
| 936 | + "Provider quirks like Together's eos finish reason, Mistral thinking arrays, and Perplexity's non-spec done events are all normalized into the spec.", | |
| 937 | + "LAN exposure is an explicit opt-in that requires at least one local API key; logging redacts request bodies by default.", | |
| 938 | + "Developer ID signed, notarized, and stapled universal binary — Gatekeeper opens it without warnings on macOS 13+.", | |
| 939 | + "Ships with a menu bar extra, a Command-K palette, and copy-as-code snippets for curl, Python, and JavaScript pre-filled with your port." | |
| 940 | + ] | |
| 941 | + }, | |
| 942 | + { | |
| 943 | + "slug": "zyquo-cloud-web", | |
| 944 | + "hero": { | |
| 945 | + "headline": "Every cloud model. One chat. Zero backend.", | |
| 946 | + "subheadline": "A browser-native AI chat for 170 models across 12 providers — bring your own keys, nothing ever leaves your device." | |
| 947 | + }, | |
| 948 | + "overview": [ | |
| 949 | + "Zyquo Cloud Web is the browser edition of the native Zyquo Cloud app: a full multi-provider AI chat that runs entirely client-side. There is no backend, no account, and no telemetry — your API keys live in your browser and are sent only to the provider you call, directly over CORS.", | |
| 950 | + "Under the hood are two faithfully ported wire clients — one OpenAI-compatible client covering 11 providers plus custom endpoints, and one native Anthropic Messages API client — driven by a hand-rolled incremental SSE parser over fetch and ReadableStream. Reasoning streams, Perplexity citations, vision attachments, and auto-retry with backoff are all handled.", | |
| 951 | + "Power features go well beyond chat: multi-model compare across up to four streaming columns, variants and branching, a Command-K palette, slash commands, 8 personas, 56 prompt templates, a token-and-cost HUD, and local-first persistence in IndexedDB with optional AES-GCM passphrase encryption." | |
| 952 | + ], | |
| 953 | + "stats": [ | |
| 954 | + { | |
| 955 | + "value": "170", | |
| 956 | + "label": "models" | |
| 957 | + }, | |
| 958 | + { | |
| 959 | + "value": "12", | |
| 960 | + "label": "providers" | |
| 961 | + }, | |
| 962 | + { | |
| 963 | + "value": "169/169", | |
| 964 | + "label": "models verified live" | |
| 965 | + }, | |
| 966 | + { | |
| 967 | + "value": "288 kB", | |
| 968 | + "label": "gzipped core bundle" | |
| 969 | + }, | |
| 970 | + { | |
| 971 | + "value": "56", | |
| 972 | + "label": "prompt templates" | |
| 973 | + }, | |
| 974 | + { | |
| 975 | + "value": "0", | |
| 976 | + "label": "backend servers" | |
| 977 | + } | |
| 978 | + ], | |
| 979 | + "features": [ | |
| 980 | + { | |
| 981 | + "icon": "ShieldCheck", | |
| 982 | + "title": "BYOK, no backend", | |
| 983 | + "description": "Keys stay in your browser and go only to the provider you call. Strict CSP locks connections to exactly the 12 provider origins plus localhost." | |
| 984 | + }, | |
| 985 | + { | |
| 986 | + "icon": "Zap", | |
| 987 | + "title": "Hand-rolled streaming engine", | |
| 988 | + "description": "Token-by-token rendering with live tok/s, stop and continue, and one incremental SSE parser handling both OpenAI-style and Anthropic named events." | |
| 989 | + }, | |
| 990 | + { | |
| 991 | + "icon": "Split", | |
| 992 | + "title": "Multi-model compare", | |
| 993 | + "description": "Broadcast one prompt to 2-4 columns streaming independently, each with its own stop, copy, and regenerate — then promote the best answer into your thread." | |
| 994 | + }, | |
| 995 | + { | |
| 996 | + "icon": "GitBranch", | |
| 997 | + "title": "Variants and branching", | |
| 998 | + "description": "Regenerate keeps previous answers as swipeable variants, branch whole conversations from any message, and edit-and-resend forks from earlier turns." | |
| 999 | + }, | |
| 1000 | + { | |
| 1001 | + "icon": "Command", | |
| 1002 | + "title": "Command palette and slash commands", | |
| 1003 | + "description": "A Command-K palette for actions, conversations, models, personas, and templates, plus slash commands with input substitution from a 56-template library." | |
| 1004 | + }, | |
| 1005 | + { | |
| 1006 | + "icon": "Eye", | |
| 1007 | + "title": "Vision and attachments", | |
| 1008 | + "description": "Drag, drop, or paste images as base64 parts and inject text files as fenced context blocks, gated by each model's real capabilities." | |
| 1009 | + }, | |
| 1010 | + { | |
| 1011 | + "icon": "Gauge", | |
| 1012 | + "title": "Token and cost HUD", | |
| 1013 | + "description": "Per-message and per-conversation token counts and cost estimates from catalog pricing, with a context-window bar and auto-trim at 90% capacity." | |
| 1014 | + }, | |
| 1015 | + { | |
| 1016 | + "icon": "HardDrive", | |
| 1017 | + "title": "Local-first persistence", | |
| 1018 | + "description": "Conversations in schema-versioned IndexedDB, settings in namespaced localStorage, full JSON export/import, and undo for deletes — surviving full browser restarts." | |
| 1019 | + }, | |
| 1020 | + { | |
| 1021 | + "icon": "Lock", | |
| 1022 | + "title": "Optional passphrase vault", | |
| 1023 | + "description": "The key map can be encrypted at rest with AES-GCM via WebCrypto and a PBKDF2-derived key using 310k iterations." | |
| 1024 | + } | |
| 1025 | + ], | |
| 1026 | + "techStack": [ | |
| 1027 | + { | |
| 1028 | + "category": "Frontend", | |
| 1029 | + "items": [ | |
| 1030 | + "React 19", | |
| 1031 | + "TypeScript (strict)", | |
| 1032 | + "Vite 7", | |
| 1033 | + "Zustand 5", | |
| 1034 | + "CSS custom properties (ZyquoTheme tokens)" | |
| 1035 | + ] | |
| 1036 | + }, | |
| 1037 | + { | |
| 1038 | + "category": "Rendering", | |
| 1039 | + "items": [ | |
| 1040 | + "react-markdown", | |
| 1041 | + "remark-gfm", | |
| 1042 | + "rehype-highlight", | |
| 1043 | + "KaTeX", | |
| 1044 | + "Mermaid (lazy-loaded)" | |
| 1045 | + ] | |
| 1046 | + }, | |
| 1047 | + { | |
| 1048 | + "category": "Storage & Security", | |
| 1049 | + "items": [ | |
| 1050 | + "IndexedDB (dependency-free wrapper)", | |
| 1051 | + "namespaced localStorage", | |
| 1052 | + "AES-GCM WebCrypto vault", | |
| 1053 | + "strict CSP" | |
| 1054 | + ] | |
| 1055 | + }, | |
| 1056 | + { | |
| 1057 | + "category": "Testing & Deploy", | |
| 1058 | + "items": [ | |
| 1059 | + "Playwright (Chromium/WebKit/Firefox)", | |
| 1060 | + "GitHub Actions CI", | |
| 1061 | + "GitHub Pages", | |
| 1062 | + "PWA via Workbox", | |
| 1063 | + "zero-dependency Node prod server" | |
| 1064 | + ] | |
| 1065 | + } | |
| 1066 | + ], | |
| 1067 | + "architecture": [ | |
| 1068 | + { | |
| 1069 | + "title": "providers/ — all networking", | |
| 1070 | + "description": "A 12-provider registry, one OpenAI-compatible client, a native Anthropic Messages client, the SSE parser with retry, and the complete 170-model typed catalog." | |
| 1071 | + }, | |
| 1072 | + { | |
| 1073 | + "title": "storage/ — all persistence", | |
| 1074 | + "description": "The only code touching localStorage and IndexedDB: key map with vault overlay, conversation CRUD with schema migration, settings, and backup export/import." | |
| 1075 | + }, | |
| 1076 | + { | |
| 1077 | + "title": "Zustand store", | |
| 1078 | + "description": "A single store orchestrating send, stream, stop, regenerate, branch, and auto-titling — components stay presentational by enforced architectural invariant." | |
| 1079 | + }, | |
| 1080 | + { | |
| 1081 | + "title": "Static deployment", | |
| 1082 | + "description": "A 100% static build hosted anywhere; production runs a zero-dependency Node server adding CSP, HSTS, and security headers under PM2 behind an ngrok custom domain." | |
| 1083 | + } | |
| 1084 | + ], | |
| 1085 | + "highlights": [ | |
| 1086 | + "Verified live with real keys from a real browser: 169/169 models streaming, 12/12 providers on non-streaming, stop, and CORS, and a network audit showing the page contacted provider origins and localhost — nothing else.", | |
| 1087 | + "CORS feasibility was empirically probed per provider — preflight, authenticated POST, and streaming — and documented with official sources in a public CORS matrix.", | |
| 1088 | + "Dependency-light on purpose: no icon packs, no CSS framework, no IndexedDB wrapper library, no auth SDKs — every dependency is key-theft surface.", | |
| 1089 | + "The ZyquoTheme design system is ported token-for-token from the native macOS app, with light and dark themes, 5 accent choices, and reduced-motion support.", | |
| 1090 | + "Installable PWA with a 1.15 MB app-shell precache that never caches API responses or keys, fully responsive down to phones.", | |
| 1091 | + "Live at www.zyquo.cloud with an auto-deployed GitHub Pages mirror; a per-provider base-URL override can point any provider at a local Zyquo Router." | |
| 1092 | + ] | |
| 1093 | + }, | |
| 1094 | + { | |
| 1095 | + "slug": "vquant", | |
| 1096 | + "hero": { | |
| 1097 | + "headline": "Institutional-grade financial intelligence, AI-powered", | |
| 1098 | + "subheadline": "A Claude-driven financial analysis platform pairing 265+ data endpoints with a quantitative Python engine and streaming research reports." | |
| 1099 | + }, | |
| 1100 | + "overview": [ | |
| 1101 | + "VibeQuant is a production-grade, full-stack financial analysis platform. A Claude AI agent — six selectable models with up to 1M tokens of context, Fable 5 by default — wields 213 specialized financial tools over 265+ data endpoints, a quantitative Python engine, and multi-source web research, delivering institutional-grade insight through a real-time streaming conversational interface.", | |
| 1102 | + "The data layer covers company fundamentals, market data, technical indicators, analyst ratings, insider and institutional activity, SEC filings, macro indicators, ETFs, forex, and crypto. The quant engine runs Monte Carlo simulation, Black-Scholes with full Greeks, GARCH volatility, VaR, mean-variance portfolio optimization, and arbitrary Python across 17 scientific libraries.", | |
| 1103 | + "Finished analyses export in one click to PDF, Word, and LaTeX Beamer slides, and a searchable community showcase publishes hundreds of analyses with the exact tool-chain the agent used on every card. Built in TypeScript with React 18, Express, Drizzle ORM, and Zod validation — live at www.vquant.ai." | |
| 1104 | + ], | |
| 1105 | + "stats": [ | |
| 1106 | + { | |
| 1107 | + "value": "265+", | |
| 1108 | + "label": "financial data endpoints" | |
| 1109 | + }, | |
| 1110 | + { | |
| 1111 | + "value": "213", | |
| 1112 | + "label": "Claude agent tools" | |
| 1113 | + }, | |
| 1114 | + { | |
| 1115 | + "value": "17", | |
| 1116 | + "label": "Python scientific libraries" | |
| 1117 | + }, | |
| 1118 | + { | |
| 1119 | + "value": "6", | |
| 1120 | + "label": "selectable Claude models" | |
| 1121 | + }, | |
| 1122 | + { | |
| 1123 | + "value": "36,000+", | |
| 1124 | + "label": "lines of code" | |
| 1125 | + }, | |
| 1126 | + { | |
| 1127 | + "value": "41", | |
| 1128 | + "label": "tests passing" | |
| 1129 | + } | |
| 1130 | + ], | |
| 1131 | + "features": [ | |
| 1132 | + { | |
| 1133 | + "icon": "Bot", | |
| 1134 | + "title": "Claude agent with 1M context", | |
| 1135 | + "description": "Six selectable Claude models with adaptive extended thinking shown live in a collapsible reasoning widget, plus vision support for charts and documents." | |
| 1136 | + }, | |
| 1137 | + { | |
| 1138 | + "icon": "Database", | |
| 1139 | + "title": "265+ financial data endpoints", | |
| 1140 | + "description": "Fundamentals, market data, technical indicators, analyst ratings, insider and Congressional trading, SEC filings, macro, ETFs, forex, and crypto via FMP." | |
| 1141 | + }, | |
| 1142 | + { | |
| 1143 | + "icon": "Calculator", | |
| 1144 | + "title": "Quantitative Python engine", | |
| 1145 | + "description": "Monte Carlo with 10k+ paths, Black-Scholes with full Greeks, GARCH(1,1) forecasting, three flavors of VaR, and mean-variance portfolio optimization." | |
| 1146 | + }, | |
| 1147 | + { | |
| 1148 | + "icon": "FlaskConical", | |
| 1149 | + "title": "Arbitrary Python execution", | |
| 1150 | + "description": "The agent writes and runs custom analysis code with access to 17 scientific libraries — numpy, pandas, scipy, arch, cvxpy, quantstats, and more — plus live FMP data." | |
| 1151 | + }, | |
| 1152 | + { | |
| 1153 | + "icon": "Search", | |
| 1154 | + "title": "Multi-source web research", | |
| 1155 | + "description": "Tavily AI search, Firecrawl scraping with PDF extraction up to 750 pages, Exa semantic search, and SerpAPI Google surfaces feed the agent's research." | |
| 1156 | + }, | |
| 1157 | + { | |
| 1158 | + "icon": "Zap", | |
| 1159 | + "title": "Real-time streaming interface", | |
| 1160 | + "description": "Server-Sent Events stream responses instantly, with intelligent tool batching of 5 tools per batch and per-session token and cost tracking." | |
| 1161 | + }, | |
| 1162 | + { | |
| 1163 | + "icon": "FileText", | |
| 1164 | + "title": "One-click report export", | |
| 1165 | + "description": "Puppeteer-rendered PDFs, Pandoc Word documents, LaTeX Beamer slide decks, CSV/XLSX/JSON data files, and shareable report links with unique IDs." | |
| 1166 | + }, | |
| 1167 | + { | |
| 1168 | + "icon": "Users", | |
| 1169 | + "title": "Community showcase", | |
| 1170 | + "description": "Hundreds of public analyses, searchable, each card showing the exact tool-chain the agent used to produce the report." | |
| 1171 | + } | |
| 1172 | + ], | |
| 1173 | + "techStack": [ | |
| 1174 | + { | |
| 1175 | + "category": "Frontend", | |
| 1176 | + "items": [ | |
| 1177 | + "React 18.3", | |
| 1178 | + "TypeScript 5.6", | |
| 1179 | + "Vite 5", | |
| 1180 | + "Tailwind CSS 3.4", | |
| 1181 | + "Radix UI" | |
| 1182 | + ] | |
| 1183 | + }, | |
| 1184 | + { | |
| 1185 | + "category": "Backend", | |
| 1186 | + "items": [ | |
| 1187 | + "Express 4.21", | |
| 1188 | + "Drizzle ORM", | |
| 1189 | + "Zod validation", | |
| 1190 | + "SQLite / PostgreSQL", | |
| 1191 | + "SSE streaming" | |
| 1192 | + ] | |
| 1193 | + }, | |
| 1194 | + { | |
| 1195 | + "category": "AI", | |
| 1196 | + "items": [ | |
| 1197 | + "Claude Fable 5 (default)", | |
| 1198 | + "Opus 4.8 / 4.7 / 4.6", | |
| 1199 | + "Sonnet 4.6", | |
| 1200 | + "Haiku 4.5", | |
| 1201 | + "213-tool agent" | |
| 1202 | + ] | |
| 1203 | + }, | |
| 1204 | + { | |
| 1205 | + "category": "Quant & Data", | |
| 1206 | + "items": [ | |
| 1207 | + "Python 3.11+", | |
| 1208 | + "numpy / pandas / scipy", | |
| 1209 | + "arch (GARCH)", | |
| 1210 | + "cvxpy", | |
| 1211 | + "FMP API (230 endpoints)" | |
| 1212 | + ] | |
| 1213 | + }, | |
| 1214 | + { | |
| 1215 | + "category": "Research & Quality", | |
| 1216 | + "items": [ | |
| 1217 | + "Tavily", | |
| 1218 | + "Firecrawl", | |
| 1219 | + "Exa", | |
| 1220 | + "SerpAPI", | |
| 1221 | + "Vitest", | |
| 1222 | + "ESLint + Prettier" | |
| 1223 | + ] | |
| 1224 | + } | |
| 1225 | + ], | |
| 1226 | + "architecture": [ | |
| 1227 | + { | |
| 1228 | + "title": "Streaming agent loop", | |
| 1229 | + "description": "An Express backend drives the Claude API with 213 tools, batching 5 tools per round and streaming responses and reasoning to the React client over SSE." | |
| 1230 | + }, | |
| 1231 | + { | |
| 1232 | + "title": "Financial data service", | |
| 1233 | + "description": "A typed FMP service layer exposes 230 endpoints across twelve categories, from fundamentals and technicals to SEC filings and alternative data." | |
| 1234 | + }, | |
| 1235 | + { | |
| 1236 | + "title": "Python analysis engine", | |
| 1237 | + "description": "Eleven Python scientific services run Monte Carlo, Black-Scholes, GARCH, VaR, and portfolio optimization in a managed virtual environment with 17 libraries." | |
| 1238 | + }, | |
| 1239 | + { | |
| 1240 | + "title": "Export pipeline", | |
| 1241 | + "description": "Reports render to PDF through Puppeteer, to DOCX through Pandoc, and to Beamer slides through LaTeX, alongside CSV/XLSX/JSON data downloads." | |
| 1242 | + }, | |
| 1243 | + { | |
| 1244 | + "title": "Validated persistence", | |
| 1245 | + "description": "Drizzle ORM over SQLite or PostgreSQL stores sessions, analyses, and token metrics, with Zod schemas validating every boundary." | |
| 1246 | + } | |
| 1247 | + ], | |
| 1248 | + "highlights": [ | |
| 1249 | + "Live demo at www.vquant.ai — a working, deployed platform, not a prototype.", | |
| 1250 | + "Six selectable Claude models with up to 1M tokens of context; the reasoning widget auto-expands while the model thinks.", | |
| 1251 | + "44 documented API endpoints, 6 route modules, 57 React components, and 6 Vitest suites with 41 passing tests across 150+ source files.", | |
| 1252 | + "The quant engine spans 8 built-in models, from Monte Carlo simulation to Sharpe, Sortino, Calmar, and max-drawdown risk metrics.", | |
| 1253 | + "Four independent web-research providers — Tavily, Firecrawl, Exa, and SerpAPI — give the agent search, scraping, and semantic retrieval.", | |
| 1254 | + "Every public analysis in the community showcase exposes the exact tool-chain the agent used, making results auditable." | |
| 1255 | + ] | |
| 1256 | + }, | |
| 1257 | + { | |
| 1258 | + "slug": "llmindex", | |
| 1259 | + "hero": { | |
| 1260 | + "headline": "The LLM leaderboard that can't be gamed", | |
| 1261 | + "subheadline": "A discriminative, contamination-resistant, fully transparent LLM ranking built on IRT psychometrics and Bradley-Terry duels — updated live." | |
| 1262 | + }, | |
| 1263 | + "overview": [ | |
| 1264 | + "Classic leaderboards fail twice: top models cluster above 95% on saturated benchmarks, giving zero discrimination, and fixed test sets leak into training data. LLM Index is engineered against both from the psychometrics up — every item carries a fitted difficulty and discrimination, ability is a 2PL IRT MAP estimate with Fisher-information standard errors, and raw accuracy is never the score.", | |
| 1265 | + "There is no fixed test set to memorize: every scored batch is freshly generated from seeded template generators, and the fixed-versus-fresh gap is published per model as a contamination delta. Home-made agentic, terminal, and context-load benches are graded deterministically by simulators; writing, safety, and SVG logo duels use a position-swapped, never-self-judging 3-judge panel feeding a Bradley-Terry fit.", | |
| 1266 | + "Everything is transparent: 135 models across 12 equally weighted domains, every score with a 95% confidence interval, and a page per model showing every answer on every test with judge verdicts, latency, and cost. Cost and latency live on a separate Pareto frontier — never blended into quality." | |
| 1267 | + ], | |
| 1268 | + "stats": [ | |
| 1269 | + { | |
| 1270 | + "value": "135", | |
| 1271 | + "label": "models ranked" | |
| 1272 | + }, | |
| 1273 | + { | |
| 1274 | + "value": "12", | |
| 1275 | + "label": "evaluation domains" | |
| 1276 | + }, | |
| 1277 | + { | |
| 1278 | + "value": "25", | |
| 1279 | + "label": "template item generators" | |
| 1280 | + }, | |
| 1281 | + { | |
| 1282 | + "value": "3", | |
| 1283 | + "label": "cross-provider judges" | |
| 1284 | + }, | |
| 1285 | + { | |
| 1286 | + "value": "95%", | |
| 1287 | + "label": "CI on every score" | |
| 1288 | + }, | |
| 1289 | + { | |
| 1290 | + "value": "60+", | |
| 1291 | + "label": "published sources behind the design" | |
| 1292 | + } | |
| 1293 | + ], | |
| 1294 | + "features": [ | |
| 1295 | + { | |
| 1296 | + "icon": "LineChart", | |
| 1297 | + "title": "IRT 2PL scoring", | |
| 1298 | + "description": "Each item has fitted difficulty and discrimination; ability theta is a MAP estimate with Fisher-information standard errors, and non-discriminating items are auto-retired." | |
| 1299 | + }, | |
| 1300 | + { | |
| 1301 | + "icon": "ShieldCheck", | |
| 1302 | + "title": "Contamination resistance by design", | |
| 1303 | + "description": "Every scored batch is freshly generated from seeded template generators — values, paraphrases, structures — with the fixed-vs-fresh gap published per model." | |
| 1304 | + }, | |
| 1305 | + { | |
| 1306 | + "icon": "Workflow", | |
| 1307 | + "title": "Original agentic bench", | |
| 1308 | + "description": "Simulated tool-calling environments with distractor tools where a deterministic simulator computes the unique correct call sequence — graded by canonical-JSON equality, no judges." | |
| 1309 | + }, | |
| 1310 | + { | |
| 1311 | + "icon": "Terminal", | |
| 1312 | + "title": "Simulated terminal bench", | |
| 1313 | + "description": "A closed, unambiguous POSIX subset simulated in TypeScript: models predict exact pipeline stdout, file trees after mutations, and exit-code traces." | |
| 1314 | + }, | |
| 1315 | + { | |
| 1316 | + "icon": "Scale", | |
| 1317 | + "title": "Bradley-Terry judged duels", | |
| 1318 | + "description": "SVG logo reproduction, writing, and safety duels scored by a position-swapped, cross-provider 3-judge panel that never self-judges, fitted with Bradley-Terry." | |
| 1319 | + }, | |
| 1320 | + { | |
| 1321 | + "icon": "Eye", | |
| 1322 | + "title": "Vision OCR under clutter", | |
| 1323 | + "description": "Generated scenes with rotated codes, noise, and decoys rasterized to PNG; text-only models skip the domain and weights renormalize." | |
| 1324 | + }, | |
| 1325 | + { | |
| 1326 | + "icon": "Zap", | |
| 1327 | + "title": "Live, one model at a time", | |
| 1328 | + "description": "Parallel evaluation lanes stream results; after every completed model, the IRT refit re-runs and the public leaderboard re-ranks in real time." | |
| 1329 | + }, | |
| 1330 | + { | |
| 1331 | + "icon": "Search", | |
| 1332 | + "title": "Total transparency", | |
| 1333 | + "description": "Every model page shows every answer on every test with judge verdicts, confidence, latency, and cost — each number traces to an immutable score run." | |
| 1334 | + }, | |
| 1335 | + { | |
| 1336 | + "icon": "Braces", | |
| 1337 | + "title": "Public versioned API", | |
| 1338 | + "description": "Leaderboard, per-domain scores, model profiles, machine-readable methodology, and live run progress over a frozen v1 JSON API." | |
| 1339 | + } | |
| 1340 | + ], | |
| 1341 | + "techStack": [ | |
| 1342 | + { | |
| 1343 | + "category": "Frontend", | |
| 1344 | + "items": [ | |
| 1345 | + "Next.js 14", | |
| 1346 | + "TypeScript (strict)", | |
| 1347 | + "Tailwind CSS" | |
| 1348 | + ] | |
| 1349 | + }, | |
| 1350 | + { | |
| 1351 | + "category": "Data", | |
| 1352 | + "items": [ | |
| 1353 | + "PostgreSQL 16", | |
| 1354 | + "Prisma", | |
| 1355 | + "Redis" | |
| 1356 | + ] | |
| 1357 | + }, | |
| 1358 | + { | |
| 1359 | + "category": "Psychometrics", | |
| 1360 | + "items": [ | |
| 1361 | + "Python + NumPy", | |
| 1362 | + "2PL IRT (MAP + Fisher SE)", | |
| 1363 | + "Bradley-Terry (MM + SE)", | |
| 1364 | + "calibration analysis" | |
| 1365 | + ] | |
| 1366 | + }, | |
| 1367 | + { | |
| 1368 | + "category": "Infrastructure", | |
| 1369 | + "items": [ | |
| 1370 | + "pnpm + Turborepo monorepo", | |
| 1371 | + "OpenRouter model access", | |
| 1372 | + "typed client with retries and cost tracking" | |
| 1373 | + ] | |
| 1374 | + } | |
| 1375 | + ], | |
| 1376 | + "architecture": [ | |
| 1377 | + { | |
| 1378 | + "title": "apps/web", | |
| 1379 | + "description": "Next.js 14 serves the live leaderboard, per-model transparency pages, the public methodology, and the versioned /api/v1 endpoints." | |
| 1380 | + }, | |
| 1381 | + { | |
| 1382 | + "title": "apps/worker", | |
| 1383 | + "description": "The eval runner, duel runner, parallel benchmark orchestrator, and refit trigger — evaluation lanes run models in parallel with a full audit trail." | |
| 1384 | + }, | |
| 1385 | + { | |
| 1386 | + "title": "apps/psychometrics", | |
| 1387 | + "description": "Python fits the 2PL IRT model with MAP estimates and Fisher standard errors, the Bradley-Terry duel model, and calibration metrics." | |
| 1388 | + }, | |
| 1389 | + { | |
| 1390 | + "title": "packages/items", | |
| 1391 | + "description": "25 template generators plus the agentic, terminal, and ledger simulators and a lenient, robust answer-extraction cascade that never cheats models on formatting." | |
| 1392 | + }, | |
| 1393 | + { | |
| 1394 | + "title": "Scoring pipeline", | |
| 1395 | + "description": "OpenRouter catalog sync, seeded batch generation, temperature-0 scoring with consistency samples, per-domain 2PL fits, Bradley-Terry duels, and 95% CIs on every score." | |
| 1396 | + } | |
| 1397 | + ], | |
| 1398 | + "highlights": [ | |
| 1399 | + "Domain weights are equal by design — the maximum-entropy prior — and per-domain scores are always published so anyone can re-weight.", | |
| 1400 | + "Cost and latency are never blended into quality: they live on a separate Pareto frontier.", | |
| 1401 | + "Answer keys and rubrics never ship to the client or public API; scored batches keep the frozen anchor subset at or below 20% of any run.", | |
| 1402 | + "Every displayed number traces to an immutable score-run row with item-set hash, model set, index version, and fit diagnostics.", | |
| 1403 | + "Methodology changes bump a semver INDEX_VERSION with a public changelog; weights and hyperparameters live in exactly one file, served machine-readable.", | |
| 1404 | + "Backed by a 19-page LaTeX white paper and 60+ published sources — yet every environment, item, simulator, and grader is original." | |
| 1405 | + ] | |
| 1406 | + }, | |
| 1407 | + { | |
| 1408 | + "slug": "airiskindex", | |
| 1409 | + "hero": { | |
| 1410 | + "headline": "AI job exposure, measured task by task", | |
| 1411 | + "subheadline": "A transparent, task-based index scoring 923 U.S. occupations from 18,796 O*NET tasks rated by a multi-model LLM panel." | |
| 1412 | + }, | |
| 1413 | + "overview": [ | |
| 1414 | + "AI Risk Index scores every U.S. occupation on its exposure to AI-driven automation with a task-based methodology: each of the ~18,800 O*NET task statements is rated individually by a multi-model LLM panel across five weighted dimensions, and occupation scores are derived from importance-weighted task scores. Every number is transparent, versioned, and reproducible.", | |
| 1415 | + "The index never collapses to one number. Three separate scores answer three different questions — Exposure (is AI technically capable?), Substitution (does AI actually replace the human after cost, barriers, and adoption?), and Augmentation (does AI assist without replacing?). Each ships with a confidence interval derived from rater disagreement, because single-model exposure ratings can vary by an order of magnitude.", | |
| 1416 | + "The headline run finds 32% of the U.S. wage bill — roughly $4.7T of $14.5T — under substitution pressure, while 47.8M workers sit in high-augmentation occupations against 5.3M in high-substitution ones. The index is explicitly built as adaptation guidance, not doom, and its scores are published under CC BY 4.0." | |
| 1417 | + ], | |
| 1418 | + "stats": [ | |
| 1419 | + { | |
| 1420 | + "value": "923", | |
| 1421 | + "label": "occupations scored" | |
| 1422 | + }, | |
| 1423 | + { | |
| 1424 | + "value": "18,796", | |
| 1425 | + "label": "O*NET tasks rated" | |
| 1426 | + }, | |
| 1427 | + { | |
| 1428 | + "value": "~225k", | |
| 1429 | + "label": "dimension ratings" | |
| 1430 | + }, | |
| 1431 | + { | |
| 1432 | + "value": "32%", | |
| 1433 | + "label": "of U.S. wage bill under pressure" | |
| 1434 | + }, | |
| 1435 | + { | |
| 1436 | + "value": "194.2M", | |
| 1437 | + "label": "workers covered (BLS OEWS)" | |
| 1438 | + } | |
| 1439 | + ], | |
| 1440 | + "features": [ | |
| 1441 | + { | |
| 1442 | + "icon": "ListChecks", | |
| 1443 | + "title": "Task-based methodology", | |
| 1444 | + "description": "Every occupation score is built bottom-up from individually rated O*NET task statements, aggregated with official importance weights — never a vibes-based occupation guess." | |
| 1445 | + }, | |
| 1446 | + { | |
| 1447 | + "icon": "Bot", | |
| 1448 | + "title": "Multi-model LLM rater panel", | |
| 1449 | + "description": "Claude Sonnet 5 and Haiku 4.5 rate every task independently; disagreement between raters becomes the published confidence interval." | |
| 1450 | + }, | |
| 1451 | + { | |
| 1452 | + "icon": "Layers", | |
| 1453 | + "title": "Three scores, never collapsed", | |
| 1454 | + "description": "Exposure, Substitution, and Augmentation answer distinct questions — technical capability, actual replacement, and productivity assistance — and are always reported separately." | |
| 1455 | + }, | |
| 1456 | + { | |
| 1457 | + "icon": "BarChart3", | |
| 1458 | + "title": "Confidence intervals on everything", | |
| 1459 | + "description": "CI bounds are a worst/best-case envelope over the rater panel, making multi-model disagreement visible instead of hiding it behind a single number." | |
| 1460 | + }, | |
| 1461 | + { | |
| 1462 | + "icon": "Calculator", | |
| 1463 | + "title": "Pure, deterministic scoring engine", | |
| 1464 | + "description": "The TypeScript scoring core has no I/O, clock, or randomness — pinned by property-based tests and a published worked example reproduced to three decimals." | |
| 1465 | + }, | |
| 1466 | + { | |
| 1467 | + "icon": "Eye", | |
| 1468 | + "title": "Full audit trail", | |
| 1469 | + "description": "Every rating stores model, prompt version, raw response, parsed score, and rationale — visible on every occupation page by expanding any task." | |
| 1470 | + }, | |
| 1471 | + { | |
| 1472 | + "icon": "Braces", | |
| 1473 | + "title": "Versioned public API", | |
| 1474 | + "description": "Health, machine-readable methodology, occupation search, and full score breakdowns with sub-scores, CI bounds, tasks, and wages — index_version in every payload." | |
| 1475 | + }, | |
| 1476 | + { | |
| 1477 | + "icon": "Lock", | |
| 1478 | + "title": "Immutable score runs", | |
| 1479 | + "description": "Weight, formula, or prompt changes bump INDEX_VERSION with a changelog entry; every published score traces to an immutable run that stays queryable forever." | |
| 1480 | + }, | |
| 1481 | + { | |
| 1482 | + "icon": "Palette", | |
| 1483 | + "title": "Colorblind-safe data design", | |
| 1484 | + "description": "Charts follow a validated palette with CI whiskers, data-table fallbacks, and a dark mode with selected rather than flipped color steps." | |
| 1485 | + } | |
| 1486 | + ], | |
| 1487 | + "techStack": [ | |
| 1488 | + { | |
| 1489 | + "category": "Frontend", | |
| 1490 | + "items": [ | |
| 1491 | + "Next.js 14", | |
| 1492 | + "TypeScript (strict)", | |
| 1493 | + "shared React UI package" | |
| 1494 | + ] | |
| 1495 | + }, | |
| 1496 | + { | |
| 1497 | + "category": "Data", | |
| 1498 | + "items": [ | |
| 1499 | + "PostgreSQL 16", | |
| 1500 | + "Prisma", | |
| 1501 | + "Redis + BullMQ" | |
| 1502 | + ] | |
| 1503 | + }, | |
| 1504 | + { | |
| 1505 | + "category": "AI Rating", | |
| 1506 | + "items": [ | |
| 1507 | + "Anthropic Message Batches", | |
| 1508 | + "Claude Sonnet 5", | |
| 1509 | + "Claude Haiku 4.5", | |
| 1510 | + "prompt-cached rubric", | |
| 1511 | + "schema-constrained JSON" | |
| 1512 | + ] | |
| 1513 | + }, | |
| 1514 | + { | |
| 1515 | + "category": "ETL & Sources", | |
| 1516 | + "items": [ | |
| 1517 | + "Python 3.12", | |
| 1518 | + "O*NET 30.3", | |
| 1519 | + "BLS OEWS May 2025", | |
| 1520 | + "pnpm + Turborepo" | |
| 1521 | + ] | |
| 1522 | + } | |
| 1523 | + ], | |
| 1524 | + "architecture": [ | |
| 1525 | + { | |
| 1526 | + "title": "ETL pipeline (apps/etl)", | |
| 1527 | + "description": "Python 3.12 downloads O*NET 30.3 and BLS OEWS, transforms them with committed manifests of hashes and row counts, and loads 1,016 occupations and 18,796 tasks into Postgres." | |
| 1528 | + }, | |
| 1529 | + { | |
| 1530 | + "title": "Rating worker (apps/worker)", | |
| 1531 | + "description": "A BullMQ pipeline drives Anthropic Message Batches — one request per task per model with deterministic custom IDs — making runs idempotent and resumable at a 50% batch discount." | |
| 1532 | + }, | |
| 1533 | + { | |
| 1534 | + "title": "Scoring engine (packages/scoring)", | |
| 1535 | + "description": "The auditable core: five weighted dimensions, inverted adoption-barriers orientation, importance-weighted aggregation, and CI envelopes — pure, deterministic, and property-tested." | |
| 1536 | + }, | |
| 1537 | + { | |
| 1538 | + "title": "Web and API (apps/web)", | |
| 1539 | + "description": "Next.js 14 serves the ranking, insights, occupation detail pages with expandable per-task audit trails, and the versioned public JSON API." | |
| 1540 | + }, | |
| 1541 | + { | |
| 1542 | + "title": "Immutable runs (packages/db)", | |
| 1543 | + "description": "Prisma schema for occupations, tasks, the full rating audit trail, and immutable score_runs rows — historical runs stay queryable forever." | |
| 1544 | + } | |
| 1545 | + ], | |
| 1546 | + "highlights": [ | |
| 1547 | + "Multi-model replication shows single-model exposure ratings can vary by an order of magnitude — so a single-model index is an artifact; this one makes disagreement visible.", | |
| 1548 | + "Five dimensions with published weights: task automatability (0.35), technical feasibility (0.20), adoption barriers (0.20, inverted — strong barriers protect), cost vs. wage (0.15), and adoption velocity (0.10).", | |
| 1549 | + "Headline finding: 32% of the U.S. wage bill (~$4.7T) overlaps with what AI can plausibly take over — framed explicitly as adaptation guidance, not a payroll forecast.", | |
| 1550 | + "Scores and derived data are CC BY 4.0; the methodology is served live as machine-readable JSON at /api/v1/methodology.", | |
| 1551 | + "Raw data dumps are immutable and never committed — derived artifacts commit manifests only, with hashes and row counts.", | |
| 1552 | + "Grounded in a public research corpus reviewing 20+ existing AI-exposure indices, with a roadmap for human review samples, sensitivity analyses, and EU/France coverage via ESCO and ROME." | |
| 1553 | + ] | |
| 1554 | + }, | |
| 1555 | + { | |
| 1556 | + "slug": "coinexplorer", | |
| 1557 | + "hero": { | |
| 1558 | + "headline": "A blockchain explorer with zero API keys", | |
| 1559 | + "subheadline": "A self-hosted indexer, API, and dashboard for stablecoins and major crypto across 24 chains — built entirely on free public RPCs." | |
| 1560 | + }, | |
| 1561 | + "overview": [ | |
| 1562 | + "coinexplorer is a complete blockchain explorer platform built from scratch — indexer, REST/WebSocket API, and an 8-page dashboard — that reads raw JSON-RPC and REST from free public endpoints only. No Etherscan keys, no Infura, no paid data vendors: point it at the internet and it indexes 11 stablecoins and major crypto across four chain families.", | |
| 1563 | + "Every transfer from 21 EVM chains, Tron, Solana, and Bitcoin lands in one canonical event schema, making the API, WebSocket stream, and dashboard fully chain-agnostic. The production instance at www.coinexplorer.io has indexed over 20 million transfers, with hot endpoints answering in under 200 ms at any index size thanks to incrementally maintained aggregates.", | |
| 1564 | + "The platform is engineered for hostile free infrastructure: per-endpoint token buckets, health-scored failover with exponential cooldowns, adaptive range halving for oversized queries, reorg-safe cursors with automatic rollback, and a watchdog that rebuilds any dead worker thread within 30 seconds." | |
| 1565 | + ], | |
| 1566 | + "stats": [ | |
| 1567 | + { | |
| 1568 | + "value": "24", | |
| 1569 | + "label": "chains indexing live" | |
| 1570 | + }, | |
| 1571 | + { | |
| 1572 | + "value": "94", | |
| 1573 | + "label": "on-chain verified assets" | |
| 1574 | + }, | |
| 1575 | + { | |
| 1576 | + "value": "20M+", | |
| 1577 | + "label": "transfers indexed" | |
| 1578 | + }, | |
| 1579 | + { | |
| 1580 | + "value": "<200 ms", | |
| 1581 | + "label": "hot API endpoints" | |
| 1582 | + }, | |
| 1583 | + { | |
| 1584 | + "value": "22", | |
| 1585 | + "label": "API endpoints" | |
| 1586 | + }, | |
| 1587 | + { | |
| 1588 | + "value": "0", | |
| 1589 | + "label": "API keys required" | |
| 1590 | + } | |
| 1591 | + ], | |
| 1592 | + "features": [ | |
| 1593 | + { | |
| 1594 | + "icon": "Coins", | |
| 1595 | + "title": "Four chain families, one schema", | |
| 1596 | + "description": "EVM logs, Tron tx-infos, Solana balance diffs, and Bitcoin UTXOs all normalize into a single canonical transfer event." | |
| 1597 | + }, | |
| 1598 | + { | |
| 1599 | + "icon": "KeyRound", | |
| 1600 | + "title": "Zero paid API keys", | |
| 1601 | + "description": "Reads raw JSON-RPC and REST from free public endpoints only — no Etherscan, no Infura, no data vendors." | |
| 1602 | + }, | |
| 1603 | + { | |
| 1604 | + "icon": "Eye", | |
| 1605 | + "title": "Whale watch, live", | |
| 1606 | + "description": "Incremental whale extraction with a $100K floor — a $72M BTC move was caught minutes after boot." | |
| 1607 | + }, | |
| 1608 | + { | |
| 1609 | + "icon": "Zap", | |
| 1610 | + "title": "Real-time WebSocket feed", | |
| 1611 | + "description": "wss stream pushes every transfer above your chosen USD floor, straight from the indexer." | |
| 1612 | + }, | |
| 1613 | + { | |
| 1614 | + "icon": "LineChart", | |
| 1615 | + "title": "Stablecoin issuance signals", | |
| 1616 | + "description": "Net mints minus burns per token, on-chain supply history per chain, native versus bridged tagging." | |
| 1617 | + }, | |
| 1618 | + { | |
| 1619 | + "icon": "ShieldCheck", | |
| 1620 | + "title": "Reorg-safe indexing", | |
| 1621 | + "description": "Parent-hash chain-linking on every cursor advance plus chain-specific confirmation depths, with automatic rollback." | |
| 1622 | + }, | |
| 1623 | + { | |
| 1624 | + "icon": "Gauge", | |
| 1625 | + "title": "Scale-proof reads", | |
| 1626 | + "description": "Background workers maintain aggregates incrementally, so no UI query rescans the transfers table — 26 s cut to 2 ms, measured." | |
| 1627 | + }, | |
| 1628 | + { | |
| 1629 | + "icon": "Blocks", | |
| 1630 | + "title": "Add a chain in 30 lines", | |
| 1631 | + "description": "New EVM chains are config-only YAML; non-EVM families are roughly 150-line adapters, with 13 chains pre-verified and waiting." | |
| 1632 | + }, | |
| 1633 | + { | |
| 1634 | + "icon": "CheckCircle2", | |
| 1635 | + "title": "Verified before indexed", | |
| 1636 | + "description": "Every EVM token address is checked on-chain via symbol() and decimals() before a single transfer is stored." | |
| 1637 | + } | |
| 1638 | + ], | |
| 1639 | + "techStack": [ | |
| 1640 | + { | |
| 1641 | + "category": "Backend", | |
| 1642 | + "items": [ | |
| 1643 | + "Python 3.12+", | |
| 1644 | + "FastAPI", | |
| 1645 | + "WebSocket", | |
| 1646 | + "Prometheus metrics" | |
| 1647 | + ] | |
| 1648 | + }, | |
| 1649 | + { | |
| 1650 | + "category": "Storage", | |
| 1651 | + "items": [ | |
| 1652 | + "SQLite WAL", | |
| 1653 | + "PostgreSQL", | |
| 1654 | + "one codebase, both engines" | |
| 1655 | + ] | |
| 1656 | + }, | |
| 1657 | + { | |
| 1658 | + "category": "Chain access", | |
| 1659 | + "items": [ | |
| 1660 | + "Raw JSON-RPC", | |
| 1661 | + "eth_getLogs + hand-rolled ABI decoding", | |
| 1662 | + "TronGrid", | |
| 1663 | + "Solana RPC", | |
| 1664 | + "esplora REST" | |
| 1665 | + ] | |
| 1666 | + }, | |
| 1667 | + { | |
| 1668 | + "category": "Frontend", | |
| 1669 | + "items": [ | |
| 1670 | + "8-page responsive dashboard", | |
| 1671 | + "hand-rolled SVG charts", | |
| 1672 | + "native dark mode" | |
| 1673 | + ] | |
| 1674 | + }, | |
| 1675 | + { | |
| 1676 | + "category": "Deployment", | |
| 1677 | + "items": [ | |
| 1678 | + "Docker Compose", | |
| 1679 | + "bare-metal venv", | |
| 1680 | + "MIT license" | |
| 1681 | + ] | |
| 1682 | + } | |
| 1683 | + ], | |
| 1684 | + "architecture": [ | |
| 1685 | + { | |
| 1686 | + "title": "RPC pools", | |
| 1687 | + "description": "Per-endpoint token buckets respect documented rate limits before getting 429'd, with health-scored EMA failover and exponential cooldowns across free public endpoints." | |
| 1688 | + }, | |
| 1689 | + { | |
| 1690 | + "title": "Family adapters", | |
| 1691 | + "description": "Four adapters — EVM, Tron, Solana, Bitcoin — decode raw chain data into one canonical transfer event, including a from-scratch Base58Check codec." | |
| 1692 | + }, | |
| 1693 | + { | |
| 1694 | + "title": "Two-cursor indexing", | |
| 1695 | + "description": "Head-tailing always has priority while history grows backwards one slice per cycle, with reorg detection and rollback on every cursor advance." | |
| 1696 | + }, | |
| 1697 | + { | |
| 1698 | + "title": "Background workers", | |
| 1699 | + "description": "Prices, supply, whale extraction, and rolling aggregates are maintained incrementally by dedicated workers, each supervised by a watchdog that rebuilds dead threads within 30 seconds." | |
| 1700 | + }, | |
| 1701 | + { | |
| 1702 | + "title": "FastAPI layer", | |
| 1703 | + "description": "REST /v1, a live WebSocket transfer stream, Prometheus /metrics, and the dashboard all read from the same database over either SQLite or PostgreSQL." | |
| 1704 | + } | |
| 1705 | + ], | |
| 1706 | + "highlights": [ | |
| 1707 | + "Live in production at www.coinexplorer.io — the README's BTC/ETH price badges render from the platform's own API", | |
| 1708 | + "94 asset deployments verified on-chain (symbol() + decimals()) before indexing — the verifier refuses wrong addresses and chain IDs", | |
| 1709 | + "A $72M BTC whale move was detected minutes after first boot", | |
| 1710 | + "Incremental aggregates turned a 26-second table scan into a 2-millisecond read, measured", | |
| 1711 | + "13 additional chains (XRPL, Stellar, TON, Aptos, Sui, Starknet, ...) already configured with verified identifiers, awaiting adapters", | |
| 1712 | + "Four test suites, 19 checks: token-bucket pacing, failover classification, reorg rollback, Base58Check vectors, TRC-20/SPL decoding" | |
| 1713 | + ] | |
| 1714 | + }, | |
| 1715 | + { | |
| 1716 | + "slug": "os-vault", | |
| 1717 | + "hero": { | |
| 1718 | + "headline": "One phrase. Six chains. Nothing leaves your Mac.", | |
| 1719 | + "subheadline": "A self-custody, multi-chain crypto wallet for macOS with its own vault encryption, keyless public RPCs, and sign-and-forget key handling." | |
| 1720 | + }, | |
| 1721 | + "overview": [ | |
| 1722 | + "OS Vault refuses the usual wallet trade-off between convenience and sovereignty. It is a native macOS wallet where a single BIP-39 recovery phrase derives addresses for six chain families — Bitcoin, 11 EVM chains, Solana, Tron, XRPL, and TON — and where every endpoint is public and keyless, with automatic failover.", | |
| 1723 | + "The mnemonic is sealed with OS Vault's own vault format: PBKDF2-HMAC-SHA512 at 600,000 rounds feeding AES-256-GCM, stored in a local file with no macOS Keychain, no iCloud, and no telemetry. Private keys exist only for the milliseconds a transaction is signed — every send re-derives the key from your password and discards it.", | |
| 1724 | + "Real fee models are handled per chain: EIP-1559, OP-stack L1 data fees via oracle, Tron energy burn estimated pre-send, XRPL reserves shown as locked, and TON jetton-wallet indirection with excess refunds. Every stablecoin contract and decimal count was verified live on-chain before registration." | |
| 1725 | + ], | |
| 1726 | + "stats": [ | |
| 1727 | + { | |
| 1728 | + "value": "6", | |
| 1729 | + "label": "chain families" | |
| 1730 | + }, | |
| 1731 | + { | |
| 1732 | + "value": "11", | |
| 1733 | + "label": "EVM chains" | |
| 1734 | + }, | |
| 1735 | + { | |
| 1736 | + "value": "600k", | |
| 1737 | + "label": "PBKDF2 rounds" | |
| 1738 | + }, | |
| 1739 | + { | |
| 1740 | + "value": "32", | |
| 1741 | + "label": "passing tests" | |
| 1742 | + }, | |
| 1743 | + { | |
| 1744 | + "value": "0", | |
| 1745 | + "label": "API keys required" | |
| 1746 | + }, | |
| 1747 | + { | |
| 1748 | + "value": "12", | |
| 1749 | + "label": "words, one phrase" | |
| 1750 | + } | |
| 1751 | + ], | |
| 1752 | + "features": [ | |
| 1753 | + { | |
| 1754 | + "icon": "Lock", | |
| 1755 | + "title": "Own vault encryption", | |
| 1756 | + "description": "PBKDF2-HMAC-SHA512 (600k rounds) into AES-256-GCM in a local 0600 file — no Keychain, no iCloud, no telemetry." | |
| 1757 | + }, | |
| 1758 | + { | |
| 1759 | + "icon": "KeyRound", | |
| 1760 | + "title": "Sign-and-forget keys", | |
| 1761 | + "description": "The private key exists only during signing: every send re-derives it from your password and immediately discards it." | |
| 1762 | + }, | |
| 1763 | + { | |
| 1764 | + "icon": "Globe", | |
| 1765 | + "title": "One phrase, every chain", | |
| 1766 | + "description": "The same 12 words derive Bitcoin (BIP-84), 11 EVM chains, Solana, Tron, XRPL, and TON — cross-validated against independent crypto stacks." | |
| 1767 | + }, | |
| 1768 | + { | |
| 1769 | + "icon": "WifiOff", | |
| 1770 | + "title": "Zero mandatory API keys", | |
| 1771 | + "description": "Every endpoint is public and keyless with health-scored failover; the only optional egress is CoinGecko prices, one toggle to kill." | |
| 1772 | + }, | |
| 1773 | + { | |
| 1774 | + "icon": "Gauge", | |
| 1775 | + "title": "Real fee models, per chain", | |
| 1776 | + "description": "EIP-1559, OP-stack L1 data fees, Tron energy burn estimated pre-send, Solana ATA rent, TON jetton attachments with refunds." | |
| 1777 | + }, | |
| 1778 | + { | |
| 1779 | + "icon": "CheckCircle2", | |
| 1780 | + "title": "On-chain verified tokens", | |
| 1781 | + "description": "Every stablecoin address and decimal was checked live — including traps like 18-decimal BNB-peg USDT and bridged USDC.e." | |
| 1782 | + }, | |
| 1783 | + { | |
| 1784 | + "icon": "ShieldCheck", | |
| 1785 | + "title": "Notarized and sandboxed", | |
| 1786 | + "description": "App Sandbox, Hardened Runtime, Developer ID signed, notarized and stapled by Apple, with forced written-backup verification." | |
| 1787 | + }, | |
| 1788 | + { | |
| 1789 | + "icon": "Eye", | |
| 1790 | + "title": "Watch-only Bitcoin", | |
| 1791 | + "description": "The Bitcoin wallet holds public descriptors only; a throwaway in-memory signer signs PSBTs on demand." | |
| 1792 | + } | |
| 1793 | + ], | |
| 1794 | + "techStack": [ | |
| 1795 | + { | |
| 1796 | + "category": "Core", | |
| 1797 | + "items": [ | |
| 1798 | + "Swift 6", | |
| 1799 | + "SwiftUI", | |
| 1800 | + "Pure SwiftPM (no .xcodeproj)", | |
| 1801 | + "macOS 15.5+" | |
| 1802 | + ] | |
| 1803 | + }, | |
| 1804 | + { | |
| 1805 | + "category": "Crypto", | |
| 1806 | + "items": [ | |
| 1807 | + "Trust wallet-core (vendored)", | |
| 1808 | + "bdk-swift (Bitcoin Dev Kit)", | |
| 1809 | + "solana-swift", | |
| 1810 | + "BIP-39 / BIP-84 HD derivation" | |
| 1811 | + ] | |
| 1812 | + }, | |
| 1813 | + { | |
| 1814 | + "category": "Infrastructure", | |
| 1815 | + "items": [ | |
| 1816 | + "PublicNode", | |
| 1817 | + "mempool.space Esplora", | |
| 1818 | + "TronGrid", | |
| 1819 | + "xrplcluster", | |
| 1820 | + "toncenter", | |
| 1821 | + "CoinGecko (optional)" | |
| 1822 | + ] | |
| 1823 | + }, | |
| 1824 | + { | |
| 1825 | + "category": "Release", | |
| 1826 | + "items": [ | |
| 1827 | + "Developer ID signing", | |
| 1828 | + "Apple notarization + stapling", | |
| 1829 | + "DMG with volume icon" | |
| 1830 | + ] | |
| 1831 | + } | |
| 1832 | + ], | |
| 1833 | + "architecture": [ | |
| 1834 | + { | |
| 1835 | + "title": "VaultCrypto + KeyManager", | |
| 1836 | + "description": "The encryption mechanism and BIP-39 HD derivation lifecycle — password to key to vault.json, with wrong passwords and tampering indistinguishable under GCM authentication." | |
| 1837 | + }, | |
| 1838 | + { | |
| 1839 | + "title": "Per-chain services", | |
| 1840 | + "description": "Dedicated services for EVM JSON-RPC, Bitcoin (bdk-swift watch-only plus transient signer), Solana SPL/ATA, Tron, XRPL, and TON, each pairing wallet-core signing with keyless REST/RPC." | |
| 1841 | + }, | |
| 1842 | + { | |
| 1843 | + "title": "TransactionService", | |
| 1844 | + "description": "Implements EIP-1559 plus five other real fee models, including OP-stack L1 data fee oracles, BSC zero-base-fee, and Linea pinned base fees." | |
| 1845 | + }, | |
| 1846 | + { | |
| 1847 | + "title": "Verified token registry", | |
| 1848 | + "description": "Network and Token models encode a matrix of live-verified contract addresses and decimals across all supported chains, testnet defaults included." | |
| 1849 | + } | |
| 1850 | + ], | |
| 1851 | + "highlights": [ | |
| 1852 | + "32 tests cross-validate BIP-39/BIP-84 vectors, vault crypto, decimals, and address validators against independent crypto stacks", | |
| 1853 | + "The vendoring script repackages Trust wallet-core for macOS SwiftPM — including an ld -r pass that resolves a duplicate Rust runtime symbol shared with the Bitcoin Dev Kit", | |
| 1854 | + "Forced written-backup verification: the app makes you prove you wrote down 3 random words before the wallet exists", | |
| 1855 | + "Every chain defaults to a testnet (Base Sepolia, Signet, Devnet, Nile) with TESTNET badges everywhere", | |
| 1856 | + "One-tap RLUSD trustline on XRPL, with recipient trustlines checked before sending", | |
| 1857 | + "No accounts, no keys, no configuration — macOS 15.5+ is the only requirement" | |
| 1858 | + ] | |
| 1859 | + }, | |
| 1860 | + { | |
| 1861 | + "slug": "metrika", | |
| 1862 | + "hero": { | |
| 1863 | + "headline": "Stata-class statistics, GPU-accelerated by Apple Silicon", | |
| 1864 | + "subheadline": "A native Swift 6 econometrics app with a DuckDB engine, invisible Metal/MLX compute, and every estimator validated against R to 1e-10." | |
| 1865 | + }, | |
| 1866 | + "overview": [ | |
| 1867 | + "Metrika brings the Stata mental model to a fully native Mac app: one line like `reg log_rev price i.region, cluster(firm_id)` yields publication-ready output with factor variables, if/in qualifiers, and robust or cluster-robust inference. No Electron, no Python runtime — Swift 6, SwiftUI, and Accelerate all the way down.", | |
| 1868 | + "The GPU is invisible: a planner dispatches every command to CPU (LAPACK) or GPU (MLX) automatically, and large bootstrap runs execute as batched Metal solves. All randomness flows through a counter-based Philox4x32 generator, so `set seed 42` produces bit-identical resamples on CPU and GPU, in any chunk order, across any parallelism.", | |
| 1869 | + "Under the hood, a DuckDB columnar engine loads 10 million rows in 0.2 seconds, and a Metal point-sprite renderer takes over scatter plots past 100k points and shrugs at 2,000,000. Estimation spans OLS, GLMs, 2SLS, panel fixed effects, GPU bootstrap, permutation tests, Bayesian Gibbs sampling, lasso/elastic net, and gradient boosting." | |
| 1870 | + ], | |
| 1871 | + "stats": [ | |
| 1872 | + { | |
| 1873 | + "value": "116", | |
| 1874 | + "label": "R-validated tests" | |
| 1875 | + }, | |
| 1876 | + { | |
| 1877 | + "value": "1e-10", | |
| 1878 | + "label": "relative tolerance vs R" | |
| 1879 | + }, | |
| 1880 | + { | |
| 1881 | + "value": "0.2 s", | |
| 1882 | + "label": "to load 10M rows" | |
| 1883 | + }, | |
| 1884 | + { | |
| 1885 | + "value": "2M", | |
| 1886 | + "label": "points in the Metal renderer" | |
| 1887 | + }, | |
| 1888 | + { | |
| 1889 | + "value": "38", | |
| 1890 | + "label": "built-in commands" | |
| 1891 | + } | |
| 1892 | + ], | |
| 1893 | + "features": [ | |
| 1894 | + { | |
| 1895 | + "icon": "Terminal", | |
| 1896 | + "title": "The Stata mental model", | |
| 1897 | + "description": "Familiar one-line syntax with factor variables, if/in qualifiers, robust and cluster-robust inference — console, do-files, or headless CLI." | |
| 1898 | + }, | |
| 1899 | + { | |
| 1900 | + "icon": "Cpu", | |
| 1901 | + "title": "Invisible GPU dispatch", | |
| 1902 | + "description": "A planner routes each command to LAPACK or MLX automatically; large bootstraps run as batched Metal solves without you choosing a backend." | |
| 1903 | + }, | |
| 1904 | + { | |
| 1905 | + "icon": "RefreshCw", | |
| 1906 | + "title": "Bit-identical reproducibility", | |
| 1907 | + "description": "Counter-based Philox4x32 RNG makes seeded resamples bit-identical on CPU and GPU, across any chunk order or parallelism." | |
| 1908 | + }, | |
| 1909 | + { | |
| 1910 | + "icon": "CheckCircle2", | |
| 1911 | + "title": "R-validated to 1e-10", | |
| 1912 | + "description": "Coefficients, HC0-HC3 and cluster SEs, p-values into the far tails, and delta-method marginal effects all match R golden values." | |
| 1913 | + }, | |
| 1914 | + { | |
| 1915 | + "icon": "Database", | |
| 1916 | + "title": "DuckDB columnar engine", | |
| 1917 | + "description": "Bulk C-API extraction loads 10 million rows in 0.2 s, summarizes in ~0.3 s, and regresses in ~0.2 s — on a laptop." | |
| 1918 | + }, | |
| 1919 | + { | |
| 1920 | + "icon": "FlaskConical", | |
| 1921 | + "title": "Full inference toolbox", | |
| 1922 | + "description": "GPU pairs bootstrap, exact permutation tests, and Bayesian regression via Gibbs sampling, all seed-reproducible." | |
| 1923 | + }, | |
| 1924 | + { | |
| 1925 | + "icon": "Brain", | |
| 1926 | + "title": "glmnet- and xgboost-exact ML", | |
| 1927 | + "description": "Lasso and elastic net match glmnet including its y-standardization convention; boosted-tree predictions match xgboost observation-by-observation." | |
| 1928 | + }, | |
| 1929 | + { | |
| 1930 | + "icon": "BarChart3", | |
| 1931 | + "title": "Charts that scale", | |
| 1932 | + "description": "Swift Charts for scatter, line, histogram, and kdensity, with a Metal point-sprite renderer taking over beyond 100k points." | |
| 1933 | + }, | |
| 1934 | + { | |
| 1935 | + "icon": "Wrench", | |
| 1936 | + "title": "Extensible by design", | |
| 1937 | + "description": "Drop-in .zyq script commands with args macros, or native Swift plugins with syntax validation and gated dataset mutation." | |
| 1938 | + } | |
| 1939 | + ], | |
| 1940 | + "techStack": [ | |
| 1941 | + { | |
| 1942 | + "category": "Core", | |
| 1943 | + "items": [ | |
| 1944 | + "Swift 6", | |
| 1945 | + "SwiftUI", | |
| 1946 | + "Swift Charts", | |
| 1947 | + "macOS 14+ (Apple Silicon)" | |
| 1948 | + ] | |
| 1949 | + }, | |
| 1950 | + { | |
| 1951 | + "category": "Compute", | |
| 1952 | + "items": [ | |
| 1953 | + "MLX", | |
| 1954 | + "Metal", | |
| 1955 | + "Accelerate (LAPACK)", | |
| 1956 | + "Philox4x32 RNG" | |
| 1957 | + ] | |
| 1958 | + }, | |
| 1959 | + { | |
| 1960 | + "category": "Data", | |
| 1961 | + "items": [ | |
| 1962 | + "DuckDB", | |
| 1963 | + "Parquet / CSV / JSON / Arrow", | |
| 1964 | + "Native Stata .dta (read 117-119, write 118)" | |
| 1965 | + ] | |
| 1966 | + }, | |
| 1967 | + { | |
| 1968 | + "category": "Validation", | |
| 1969 | + "items": [ | |
| 1970 | + "R golden fixtures", | |
| 1971 | + "glmnet", | |
| 1972 | + "xgboost", | |
| 1973 | + "Random123 known-answer vectors" | |
| 1974 | + ] | |
| 1975 | + } | |
| 1976 | + ], | |
| 1977 | + "architecture": [ | |
| 1978 | + { | |
| 1979 | + "title": "ZQParser + ZQPlanner", | |
| 1980 | + "description": "A command grammar parses Stata-style input into a typed AST, which the planner dispatches to CPU, GPU, or hybrid execution paths." | |
| 1981 | + }, | |
| 1982 | + { | |
| 1983 | + "title": "ZQData over DuckDB", | |
| 1984 | + "description": "A DataFrame facade over the DuckDB columnar engine plus native .dta support, with explicit missing-value semantics and listwise-deletion reporting." | |
| 1985 | + }, | |
| 1986 | + { | |
| 1987 | + "title": "ZQStats and ZQGPU", | |
| 1988 | + "description": "LAPACK estimators (OLS via QR, never X'X) live in ZQStats; ZQGPU is the only module touching MLX/Metal — backends stay swappable." | |
| 1989 | + }, | |
| 1990 | + { | |
| 1991 | + "title": "ZQGraphics", | |
| 1992 | + "description": "Plot specs render through Swift Charts, with a Metal point-sprite renderer taking over automatically past 100k points." | |
| 1993 | + }, | |
| 1994 | + { | |
| 1995 | + "title": "MetrikaKit package", | |
| 1996 | + "description": "The entire engine has zero UI dependencies and is fully testable with swift test; the SwiftUI app is a thin shell over console, data browser, editor, and manual." | |
| 1997 | + } | |
| 1998 | + ], | |
| 1999 | + "highlights": [ | |
| 2000 | + "Every CPU estimator validated against R to 1e-10 relative tolerance — a p-value of 4x10^-22 matches R exactly", | |
| 2001 | + "GPU bootstrap resample indices asserted bit-identical to the CPU Philox reference, itself pinned to Random123 known-answer vectors", | |
| 2002 | + "10 million rows: load 0.2 s, summarize ~0.3 s, regress ~0.2 s", | |
| 2003 | + "Reads and writes native Stata .dta files (formats 117-119)", | |
| 2004 | + "Bayesian posteriors with diffuse priors reproduce the frequentist answer within Monte-Carlo error — asserted, not assumed", | |
| 2005 | + "Signed, notarized, and stapled DMG; documentation regenerated from the command registry so it can never drift from the app" | |
| 2006 | + ] | |
| 2007 | + }, | |
| 2008 | + { | |
| 2009 | + "slug": "forge", | |
| 2010 | + "hero": { | |
| 2011 | + "headline": "LLM training from scratch in C++20 and Metal", | |
| 2012 | + "subheadline": "A complete transformer training stack for Apple Silicon with zero ML dependencies — including the fused flash-attention backward kernel no major framework ships." | |
| 2013 | + }, | |
| 2014 | + "overview": [ | |
| 2015 | + "Forge is a complete, working transformer training stack built from nothing on Apple Silicon: tensors, autograd, Metal compute kernels, flash attention forward and backward, AdamW and Muon optimizers, a BPE tokenizer, checkpointing, and generation — all hand-written in roughly 6,500 lines of C++20, with no PyTorch, no MLX, and no ML dependencies.", | |
| 2016 | + "As of July 2026, no major open-source framework ships a fused attention backward kernel for Metal — MLX throws NYI, llama.cpp lacks the op, PyTorch MPS and Candle are forward-only. Forge has one, and it is 15.2x faster than the naive version. Hand-tuned simdgroup_matrix GEMM reaches 10.8 TFLOPS f32, and Metal 4's matmul2d hits 51.5 TFLOPS f16 on the M5 neural accelerators.", | |
| 2017 | + "Architecture is entirely config-driven: the same binary trains a 12M or a 205M parameter model by changing a JSON file, with GQA, RoPE, SwiGLU, MoE routing, and BitNet-style ternary quantization-aware training all selectable from config. Every op is validated against a CPU reference through 85 parity checks and numerical gradient checks." | |
| 2018 | + ], | |
| 2019 | + "stats": [ | |
| 2020 | + { | |
| 2021 | + "value": "38.2k", | |
| 2022 | + "label": "tokens/s training throughput" | |
| 2023 | + }, | |
| 2024 | + { | |
| 2025 | + "value": "10.8", | |
| 2026 | + "label": "TFLOPS GEMM f32" | |
| 2027 | + }, | |
| 2028 | + { | |
| 2029 | + "value": "51.5", | |
| 2030 | + "label": "TFLOPS matmul2d f16 (M5)" | |
| 2031 | + }, | |
| 2032 | + { | |
| 2033 | + "value": "15.2x", | |
| 2034 | + "label": "flash-attention backward speedup" | |
| 2035 | + }, | |
| 2036 | + { | |
| 2037 | + "value": "85", | |
| 2038 | + "label": "CPU-Metal parity checks" | |
| 2039 | + }, | |
| 2040 | + { | |
| 2041 | + "value": "~6.5k", | |
| 2042 | + "label": "lines of code" | |
| 2043 | + } | |
| 2044 | + ], | |
| 2045 | + "features": [ | |
| 2046 | + { | |
| 2047 | + "icon": "Flame", | |
| 2048 | + "title": "Fused flash attention backward", | |
| 2049 | + "description": "The Metal kernel no major framework ships — MLX, llama.cpp, PyTorch MPS, and Candle all lack it. Forge's runs 15.2x faster than naive." | |
| 2050 | + }, | |
| 2051 | + { | |
| 2052 | + "icon": "Cpu", | |
| 2053 | + "title": "Hand-written Metal GEMM", | |
| 2054 | + "description": "Naive, tiled, and simdgroup_matrix variants reaching 10.8 TFLOPS f32, plus Metal 4 matmul2d at 51.5 TFLOPS f16." | |
| 2055 | + }, | |
| 2056 | + { | |
| 2057 | + "icon": "Braces", | |
| 2058 | + "title": "Zero ML dependencies", | |
| 2059 | + "description": "Pure C++20, Metal kernels, and a JSON parser. Tensors, autograd, optimizers, and tokenizer all built from nothing." | |
| 2060 | + }, | |
| 2061 | + { | |
| 2062 | + "icon": "Settings2", | |
| 2063 | + "title": "Config-driven architecture", | |
| 2064 | + "description": "The same binary trains 12M to 205M parameter models by editing JSON: GQA, RoPE, SwiGLU or GELU, MoE, tied embeddings." | |
| 2065 | + }, | |
| 2066 | + { | |
| 2067 | + "icon": "CheckCircle2", | |
| 2068 | + "title": "Measured, not assumed", | |
| 2069 | + "description": "85 CPU-Metal parity checks, numerical gradient checks on every parameterized op, single-batch overfit, exact checkpoint resume." | |
| 2070 | + }, | |
| 2071 | + { | |
| 2072 | + "icon": "Package", | |
| 2073 | + "title": "The .forge weight format", | |
| 2074 | + "description": "A git-style model repository: content-addressed 95 MB shards, zero-copy mmap loading on unified memory, delta-only saves." | |
| 2075 | + }, | |
| 2076 | + { | |
| 2077 | + "icon": "Workflow", | |
| 2078 | + "title": "Modern training modes", | |
| 2079 | + "description": "Muon orthogonalized momentum, WSD schedules, int8 and BitNet-style ternary QAT, and top-k MoE with load-balance loss — all config-selected." | |
| 2080 | + }, | |
| 2081 | + { | |
| 2082 | + "icon": "Download", | |
| 2083 | + "title": "Streaming HF data pipeline", | |
| 2084 | + "description": "Streams any of 13 registered Hugging Face datasets or weighted mixtures straight to training binaries — no full-corpus downloads." | |
| 2085 | + }, | |
| 2086 | + { | |
| 2087 | + "icon": "Microscope", | |
| 2088 | + "title": "Published GPU findings", | |
| 2089 | + "description": "Documented compiler traps and profiling results, including a constant constexpr pitfall that cost 12x and a 4352-byte register spill." | |
| 2090 | + } | |
| 2091 | + ], | |
| 2092 | + "techStack": [ | |
| 2093 | + { | |
| 2094 | + "category": "Core", | |
| 2095 | + "items": [ | |
| 2096 | + "C++20", | |
| 2097 | + "metal-cpp", | |
| 2098 | + "CMake", | |
| 2099 | + "MIT license" | |
| 2100 | + ] | |
| 2101 | + }, | |
| 2102 | + { | |
| 2103 | + "category": "GPU", | |
| 2104 | + "items": [ | |
| 2105 | + "Metal 4", | |
| 2106 | + "simdgroup_matrix MMA", | |
| 2107 | + "mpp::tensor_ops::matmul2d", | |
| 2108 | + "14 .metal kernel files" | |
| 2109 | + ] | |
| 2110 | + }, | |
| 2111 | + { | |
| 2112 | + "category": "Training", | |
| 2113 | + "items": [ | |
| 2114 | + "AdamW", | |
| 2115 | + "Muon (Newton-Schulz)", | |
| 2116 | + "warmup+cosine / WSD schedules", | |
| 2117 | + "int8 / ternary QAT", | |
| 2118 | + "MoE" | |
| 2119 | + ] | |
| 2120 | + }, | |
| 2121 | + { | |
| 2122 | + "category": "Data & formats", | |
| 2123 | + "items": [ | |
| 2124 | + "Byte-level BPE tokenizer", | |
| 2125 | + ".forge weight format", | |
| 2126 | + "safetensors export", | |
| 2127 | + "TinyStories + 13 HF datasets" | |
| 2128 | + ] | |
| 2129 | + } | |
| 2130 | + ], | |
| 2131 | + "architecture": [ | |
| 2132 | + { | |
| 2133 | + "title": "Core tensor layer", | |
| 2134 | + "description": "Shared-storage tensor views, a bucketed MTLBuffer pool allocator, a device wrapper with pipeline caching, and an autograd tape." | |
| 2135 | + }, | |
| 2136 | + { | |
| 2137 | + "title": "Dual-backend ops", | |
| 2138 | + "description": "CPU reference implementations and Metal dispatch live side by side; the autograd layer routes to either backend, and the parity suite keeps them bit-comparable." | |
| 2139 | + }, | |
| 2140 | + { | |
| 2141 | + "title": "Metal kernel suite", | |
| 2142 | + "description": "14 .metal files covering GEMM in four generations, flash attention (scalar and MMA), softmax, norms, embeddings, cross-entropy, AdamW, fake-quant, and MoE gating." | |
| 2143 | + }, | |
| 2144 | + { | |
| 2145 | + "title": "Config-driven transformer", | |
| 2146 | + "description": "Decoder-only model with RMSNorm or LayerNorm, SwiGLU or GELU, RoPE or learned positions, GQA, and optional top-k MoE — all assembled from JSON." | |
| 2147 | + }, | |
| 2148 | + { | |
| 2149 | + "title": ".forge model repository", | |
| 2150 | + "description": "Tensors page-aligned inside content-addressed shards; loading is mmap plus bytesNoCopy, so on unified memory a multi-GB model loads in milliseconds. Saves write only changed tensors." | |
| 2151 | + } | |
| 2152 | + ], | |
| 2153 | + "highlights": [ | |
| 2154 | + "A 12.2M-parameter model trains in ~8 minutes to 20.27 validation perplexity and generates coherent English stories", | |
| 2155 | + "One MSL fix — replacing constant constexpr with an enum — took the GEMM kernel from 0.82 to 10.21 TFLOPS (12x)", | |
| 2156 | + "gpudebug profiling exposed a 4352-byte register spill in the fused backward kernel; splitting it recovered another 27%", | |
| 2157 | + "M5 neural accelerators deliver 4.9x via matmul2d — but only through f16, making mixed precision the entry condition, not a memory optimization", | |
| 2158 | + "f32 matmul2d output is bit-exact against the CPU reference; f16 differs by 3.8e-06", | |
| 2159 | + "Ships with a LaTeX paper and research notes documenting measured findings on MSL compiler behavior" | |
| 2160 | + ] | |
| 2161 | + }, | |
| 2162 | + { | |
| 2163 | + "slug": "forge-studio", | |
| 2164 | + "hero": { | |
| 2165 | + "headline": "The native macOS cockpit for LLM training", | |
| 2166 | + "subheadline": "Train language models from scratch on Apple Silicon without opening a terminal — dataset prep, run supervision, and live loss dashboards for Forge." | |
| 2167 | + }, | |
| 2168 | + "overview": [ | |
| 2169 | + "Forge Studio is the native GUI companion to Forge, the from-scratch C++20 + Metal LLM training framework. It wraps the entire train-a-model workflow — prepare data, design an architecture, launch and monitor runs, compare experiments, and generate from checkpoints — as a first-party-feeling Mac app built in Swift, SwiftUI, and Swift Charts with zero third-party dependencies.", | |
| 2170 | + "The centerpiece is a loss dashboard built to the TensorBoard/W&B standard: raw and EMA-smoothed loss, hover crosshairs with full callouts, pinch-zoom and pan with a follow-live pill, best-val markers, and secondary charts for LR schedule, tokens/sec, and gradient norm. Raw data is never discarded — the UI reads LTTB-downsampled snapshots sized to pixel width.", | |
| 2171 | + "Runs cannot lie: a single-writer state machine makes illegal transitions unrepresentable, the registry persists atomically so you can kill -9 the app at will, crash recovery truthfully resolves interrupted runs, and a watchdog flags stalls. Studio never reimplements training — it drives the real forge binary and reads its structured metrics." | |
| 2172 | + ], | |
| 2173 | + "stats": [ | |
| 2174 | + { | |
| 2175 | + "value": "200k", | |
| 2176 | + "label": "CSV rows ingested in ~1.1 s" | |
| 2177 | + }, | |
| 2178 | + { | |
| 2179 | + "value": "<250 ms", | |
| 2180 | + "label": "chart snapshot at any size" | |
| 2181 | + }, | |
| 2182 | + { | |
| 2183 | + "value": "0", | |
| 2184 | + "label": "third-party dependencies" | |
| 2185 | + }, | |
| 2186 | + { | |
| 2187 | + "value": "30 s", | |
| 2188 | + "label": "stall watchdog threshold" | |
| 2189 | + } | |
| 2190 | + ], | |
| 2191 | + "features": [ | |
| 2192 | + { | |
| 2193 | + "icon": "LineChart", | |
| 2194 | + "title": "TensorBoard-grade loss dashboard", | |
| 2195 | + "description": "Raw train loss under a bias-corrected EMA with TensorBoard semantics, val loss points, hover crosshair, and a best-val marker annotation." | |
| 2196 | + }, | |
| 2197 | + { | |
| 2198 | + "icon": "Gauge", | |
| 2199 | + "title": "Hitch-free at 100k steps", | |
| 2200 | + "description": "LTTB downsampling to ~2x pixel width keeps hover interactions smooth; 200,000 CSV rows ingest in about 1.1 seconds." | |
| 2201 | + }, | |
| 2202 | + { | |
| 2203 | + "icon": "ShieldCheck", | |
| 2204 | + "title": "Runs that can't lie", | |
| 2205 | + "description": "A single-writer state machine with an explicit legal-transition table makes illegal run states unrepresentable." | |
| 2206 | + }, | |
| 2207 | + { | |
| 2208 | + "icon": "RefreshCw", | |
| 2209 | + "title": "Honest crash recovery", | |
| 2210 | + "description": "Atomic temp-file-then-rename persistence plus launch-time resolution of interrupted runs — including detecting a forge process still alive." | |
| 2211 | + }, | |
| 2212 | + { | |
| 2213 | + "icon": "Boxes", | |
| 2214 | + "title": "Dataset prep built in", | |
| 2215 | + "description": "TinyStories or streamed Hugging Face mixtures like FineWeb-Edu, DCLM, and Cosmopedia, prepared with a live console." | |
| 2216 | + }, | |
| 2217 | + { | |
| 2218 | + "icon": "Settings2", | |
| 2219 | + "title": "Full config editor", | |
| 2220 | + "description": "Every Forge config field from n_layers to DeepSeek-style MoE routing, with live validation, presets, derived math, and an LR preview." | |
| 2221 | + }, | |
| 2222 | + { | |
| 2223 | + "icon": "ArrowLeftRight", | |
| 2224 | + "title": "Compare runs honestly", | |
| 2225 | + "description": "Multi-run overlays plotted on the tokens axis — the honest one — for apples-to-apples experiment comparison." | |
| 2226 | + }, | |
| 2227 | + { | |
| 2228 | + "icon": "Sparkles", | |
| 2229 | + "title": "Generate and eval in-app", | |
| 2230 | + "description": "Sample text and run evaluation from any checkpoint directly inside the app, no terminal required." | |
| 2231 | + }, | |
| 2232 | + { | |
| 2233 | + "icon": "Bell", | |
| 2234 | + "title": "Finish-line notifications", | |
| 2235 | + "description": "Local notifications deliver the final loss when a run finishes or fails, plus a possibly-stalled badge after 30 s of silence." | |
| 2236 | + } | |
| 2237 | + ], | |
| 2238 | + "techStack": [ | |
| 2239 | + { | |
| 2240 | + "category": "Core", | |
| 2241 | + "items": [ | |
| 2242 | + "Swift", | |
| 2243 | + "SwiftUI", | |
| 2244 | + "Swift Charts", | |
| 2245 | + "Swift Concurrency (actors)", | |
| 2246 | + "macOS 14+" | |
| 2247 | + ] | |
| 2248 | + }, | |
| 2249 | + { | |
| 2250 | + "category": "Data pipeline", | |
| 2251 | + "items": [ | |
| 2252 | + "Header-driven CSV parsing", | |
| 2253 | + "LTTB downsampling", | |
| 2254 | + "Bias-corrected EMA smoothing" | |
| 2255 | + ] | |
| 2256 | + }, | |
| 2257 | + { | |
| 2258 | + "category": "Release", | |
| 2259 | + "items": [ | |
| 2260 | + "SwiftPM app packaging", | |
| 2261 | + "Developer ID + hardened runtime", | |
| 2262 | + "notarytool + stapler DMG" | |
| 2263 | + ] | |
| 2264 | + } | |
| 2265 | + ], | |
| 2266 | + "architecture": [ | |
| 2267 | + { | |
| 2268 | + "title": "ProcessRunner + LogParser", | |
| 2269 | + "description": "An actor streams the real forge binary's output incrementally, parsing header-driven CSV metrics and stdout events off the main thread." | |
| 2270 | + }, | |
| 2271 | + { | |
| 2272 | + "title": "MetricsStore", | |
| 2273 | + "description": "An actor-isolated store tails logs incrementally and serves LTTB-downsampled snapshots to the charts — raw data is never discarded." | |
| 2274 | + }, | |
| 2275 | + { | |
| 2276 | + "title": "Run state machine", | |
| 2277 | + "description": "queued, launching, running through finished, failed, or stopped — with an explicit legal-transition table and atomic registry persistence." | |
| 2278 | + }, | |
| 2279 | + { | |
| 2280 | + "title": "ForgeConfig models", | |
| 2281 | + "description": "A Codable mirror of every Forge config field with validation and derived math, round-tripped byte-compatible against the real configs/*.json." | |
| 2282 | + }, | |
| 2283 | + { | |
| 2284 | + "title": "RunSupervisor + watchdog", | |
| 2285 | + "description": "Supervises live processes, flags stalls after 30 seconds of silent metrics, and handles SIGTERM stops with honest UI messaging about checkpoint loss." | |
| 2286 | + } | |
| 2287 | + ], | |
| 2288 | + "highlights": [ | |
| 2289 | + "Never reimplements training — drives the real forge binary and reads its structured metrics, so what you see is exactly what the framework did", | |
| 2290 | + "The Swift parameter-count formula is tested to match forge info for every shipped config", | |
| 2291 | + "RESEARCH.md documents the full Forge contract — config schema, CLI, log.csv grammar, signal behavior — extracted from source and enforced by tests", | |
| 2292 | + "kill -9 safe: atomic temp-file-then-rename persistence for the run registry", | |
| 2293 | + "Measured performance: 200,000 CSV rows ingest in ~1.1 s and snapshot to chart width in under 250 ms", | |
| 2294 | + "Zero third-party packages — pure Swift, SwiftUI, and Swift Charts" | |
| 2295 | + ] | |
| 2296 | + }, | |
| 2297 | + { | |
| 2298 | + "slug": "air", | |
| 2299 | + "hero": { | |
| 2300 | + "headline": "LLVM for the Ledger", | |
| 2301 | + "subheadline": "Compiler infrastructure for accounting: LLMs describe economic events, a deterministic compiler produces balanced, fully traceable journal entries." | |
| 2302 | + }, | |
| 2303 | + "overview": [ | |
| 2304 | + "AIR (Accounting Intermediate Representation) applies the LLVM playbook to bookkeeping. LLMs are brilliant at understanding documents and unreliable at applying hundreds of tax rules, so AIR separates the two: any frontend — an LLM reading an invoice, a bank feed, a POS — only ever emits perspective-neutral economic events, never journal entries. A deterministic pass pipeline compiles those events into balanced entries.", | |
| 2305 | + "The double-entry invariant Assets = Liabilities + Equity is verified after every compiler pass, and failures surface as clang-style diagnostics with location, cause, and a suggested fix — a compiler error instead of a wrong number. Every tax rate and threshold lives in versioned ALSL policy files with mandatory source citations; the loader rejects uncited rates.", | |
| 2306 | + "AIR is also a standalone accounting system: an append-only, hash-chained ledger, git-style corrections via reversal-plus-replacement entries, full financial statements, bank reconciliation for camt.053, MT940 and CSV, and an agent SDK where AI agents keep books exclusively through audited syscalls." | |
| 2307 | + ], | |
| 2308 | + "stats": [ | |
| 2309 | + { | |
| 2310 | + "value": "99", | |
| 2311 | + "label": "offline tests" | |
| 2312 | + }, | |
| 2313 | + { | |
| 2314 | + "value": "21", | |
| 2315 | + "label": "golden test cases" | |
| 2316 | + }, | |
| 2317 | + { | |
| 2318 | + "value": "7", | |
| 2319 | + "label": "hard guarantees" | |
| 2320 | + }, | |
| 2321 | + { | |
| 2322 | + "value": "9", | |
| 2323 | + "label": "cited research reports" | |
| 2324 | + }, | |
| 2325 | + { | |
| 2326 | + "value": "0", | |
| 2327 | + "label": "floats allowed" | |
| 2328 | + } | |
| 2329 | + ], | |
| 2330 | + "features": [ | |
| 2331 | + { | |
| 2332 | + "icon": "Braces", | |
| 2333 | + "title": "A universal accounting language", | |
| 2334 | + "description": "REA-based EconomicEvent schema for sales, purchases, refunds, payments and FX — perspective-neutral, schema-validated, with no debits or account codes anywhere." | |
| 2335 | + }, | |
| 2336 | + { | |
| 2337 | + "icon": "Workflow", | |
| 2338 | + "title": "A real compiler pipeline", | |
| 2339 | + "description": "Validation, classification, tax, FX and posting passes under a pass manager that re-verifies the double-entry invariant after every single pass." | |
| 2340 | + }, | |
| 2341 | + { | |
| 2342 | + "icon": "FileText", | |
| 2343 | + "title": "Rules as cited data", | |
| 2344 | + "description": "GST, QST, HST rates, capitalization thresholds and rounding modes live in versioned ALSL YAML policies; the loader rejects any rate without a source citation." | |
| 2345 | + }, | |
| 2346 | + { | |
| 2347 | + "icon": "Lock", | |
| 2348 | + "title": "Hash-chained, append-only ledger", | |
| 2349 | + "description": "Standalone books with a tamper-evident ledger and content-addressed document archive — corrections post reversals and replacements, history is never edited." | |
| 2350 | + }, | |
| 2351 | + { | |
| 2352 | + "icon": "Bot", | |
| 2353 | + "title": "Agent syscall SDK", | |
| 2354 | + "description": "AI agents keep books only through syscalls like Post, Reverse, ClosePeriod and Reconcile; every call, including refusals, lands in a hash-chained audit log." | |
| 2355 | + }, | |
| 2356 | + { | |
| 2357 | + "icon": "Search", | |
| 2358 | + "title": "Total provenance traceability", | |
| 2359 | + "description": "Every posted cent walks back through an accounting SSA provenance graph to its source event, document, extraction confidence, policy version and rounding mode." | |
| 2360 | + }, | |
| 2361 | + { | |
| 2362 | + "icon": "Landmark", | |
| 2363 | + "title": "Bank reconciliation built in", | |
| 2364 | + "description": "Ingests camt.053, MT940 (with statement integrity checks) and CSV statements, matches them against the books, and reports differences on both sides." | |
| 2365 | + }, | |
| 2366 | + { | |
| 2367 | + "icon": "Zap", | |
| 2368 | + "title": "Provenance-preserving optimizations", | |
| 2369 | + "description": "Fuses 50 identical payments into one batch entry, nets refunds against sales, and detects duplicates — provenance survives every transformation." | |
| 2370 | + }, | |
| 2371 | + { | |
| 2372 | + "icon": "WifiOff", | |
| 2373 | + "title": "Fully offline development", | |
| 2374 | + "description": "The QuickBooks backend and LLM ingestion develop and test entirely offline via mock transports; determinism is property-tested with byte-identical double compiles in CI." | |
| 2375 | + } | |
| 2376 | + ], | |
| 2377 | + "techStack": [ | |
| 2378 | + { | |
| 2379 | + "category": "Core", | |
| 2380 | + "items": [ | |
| 2381 | + "Python 3.11+", | |
| 2382 | + "JSON Schema", | |
| 2383 | + "Exact decimal arithmetic", | |
| 2384 | + "YAML (ALSL policies)" | |
| 2385 | + ] | |
| 2386 | + }, | |
| 2387 | + { | |
| 2388 | + "category": "Integrations", | |
| 2389 | + "items": [ | |
| 2390 | + "Claude structured outputs", | |
| 2391 | + "QuickBooks backend", | |
| 2392 | + "CSV export", | |
| 2393 | + "camt.053 / MT940 / ISO 20022" | |
| 2394 | + ] | |
| 2395 | + }, | |
| 2396 | + { | |
| 2397 | + "category": "Quality", | |
| 2398 | + "items": [ | |
| 2399 | + "pytest (99 offline + 2 live tests)", | |
| 2400 | + "21 golden cases", | |
| 2401 | + "Property-based determinism tests", | |
| 2402 | + "GitHub Actions CI" | |
| 2403 | + ] | |
| 2404 | + } | |
| 2405 | + ], | |
| 2406 | + "architecture": [ | |
| 2407 | + { | |
| 2408 | + "title": "schemas/ + core/ — the IR", | |
| 2409 | + "description": "The AIR JSON Schema plus events, Money, provenance and invariants — the intermediate representation and its verifier, in the LLVM analogy." | |
| 2410 | + }, | |
| 2411 | + { | |
| 2412 | + "title": "aic/ — the compiler", | |
| 2413 | + "description": "Pass manager, compilation passes, clang-style diagnostics and incremental recompilation: the opt/llc of accounting, deterministic with no LLM, network or clock inside." | |
| 2414 | + }, | |
| 2415 | + { | |
| 2416 | + "title": "alsl/ — rules as data", | |
| 2417 | + "description": "The rule language and cited, versioned policy sets for taxes, thresholds and rounding — AIR's TableGen equivalent." | |
| 2418 | + }, | |
| 2419 | + { | |
| 2420 | + "title": "kernel/ + backends/ — the runtime and targets", | |
| 2421 | + "description": "Hash-chained ledger, reporting, workspace, audit and reconciliation, plus native, CSV and QuickBooks output backends." | |
| 2422 | + }, | |
| 2423 | + { | |
| 2424 | + "title": "sdk/ + ingestion/ — the surface", | |
| 2425 | + "description": "CLI, agent syscalls and a demo agent on one side; document extractors, confidence routing and a human approval queue on the other." | |
| 2426 | + } | |
| 2427 | + ], | |
| 2428 | + "highlights": [ | |
| 2429 | + "The one rule that never bends: an LLM only ever produces AIR events — the deterministic compiler alone writes journal entries.", | |
| 2430 | + "Same input plus same policies yields byte-identical output, always — property-tested, and CI compiles the demo twice and diffs the results.", | |
| 2431 | + "Floats are forbidden end to end: exact decimals everywhere, rejected at every boundary.", | |
| 2432 | + "Refused agent actions are audited exactly like successful ones; editing any record breaks the hash chain.", | |
| 2433 | + "Real Quebec tax law is encoded and cited down to the statute — GST 5% and QST 9.975% with half-up rounding per Excise Tax Act s.165.2(2).", | |
| 2434 | + "Phases 0 through 6 of the roadmap — research, core, compiler, backends, ingestion, agent SDK, optimizations and reconciliation — are complete." | |
| 2435 | + ] | |
| 2436 | + }, | |
| 2437 | + { | |
| 2438 | + "slug": "ultra-sharp-agent-skills", | |
| 2439 | + "hero": { | |
| 2440 | + "headline": "72 Production-Ready Skills for AI Agents", | |
| 2441 | + "subheadline": "A research-first skill-authoring system plus seven linted, trigger-tested collections covering documents, frontend, databases, backend, writing, and US/Canada tax." | |
| 2442 | + }, | |
| 2443 | + "overview": [ | |
| 2444 | + "Skills are folders of instructions — a SKILL.md plus resources — that AI agents load on demand. This repository was built in two deliberate phases: first, 20+ primary-source searches across Anthropic docs, the official skills repo and engineering blogs were distilled into a research synthesis of 15 core principles, the Sharp Skill Checklist, and an ideal SKILL.md template.", | |
| 2445 | + "Then came the build: 72 skills across 7 collections — document processing, frontend design, database management, backend development, writing, US/Canada tax and accounting, plus two founding examples. Every skill ships a trigger-optimized description, one default per decision, a validation-loop workflow, and pairwise-exclusive boundaries so no request ever fires two skills.", | |
| 2446 | + "Quality is mechanical, not aspirational. A stdlib-only Python linter enforces the full checklist on every skill: frontmatter validity, literal 'Use when' trigger clauses and 'Do not use' boundaries, author headers in every file, bodies capped at 500 lines, and zero broken reference links. Validation includes positive and negative trigger tests with real execution evidence." | |
| 2447 | + ], | |
| 2448 | + "stats": [ | |
| 2449 | + { | |
| 2450 | + "value": "72", | |
| 2451 | + "label": "skills" | |
| 2452 | + }, | |
| 2453 | + { | |
| 2454 | + "value": "7", | |
| 2455 | + "label": "collections" | |
| 2456 | + }, | |
| 2457 | + { | |
| 2458 | + "value": "20+", | |
| 2459 | + "label": "primary-source research fetches" | |
| 2460 | + }, | |
| 2461 | + { | |
| 2462 | + "value": "15", | |
| 2463 | + "label": "core authoring principles" | |
| 2464 | + }, | |
| 2465 | + { | |
| 2466 | + "value": "500", | |
| 2467 | + "label": "max lines per SKILL.md body" | |
| 2468 | + }, | |
| 2469 | + { | |
| 2470 | + "value": "0", | |
| 2471 | + "label": "broken reference links" | |
| 2472 | + } | |
| 2473 | + ], | |
| 2474 | + "features": [ | |
| 2475 | + { | |
| 2476 | + "icon": "FileText", | |
| 2477 | + "title": "Document processing collection", | |
| 2478 | + "description": "10 skills covering every major document type — Excel, Word, PowerPoint, PDF, JSON, CSV, XML, YAML, Markdown, HTML — with re-parse validation after every write." | |
| 2479 | + }, | |
| 2480 | + { | |
| 2481 | + "icon": "Palette", | |
| 2482 | + "title": "Frontend design collection", | |
| 2483 | + "description": "10 skills grounded in Anthropic's frontend-design skill and 2026 standards: WCAG 2.2 AA, LCP under 2.5s, INP under 200ms, CLS under 0.1." | |
| 2484 | + }, | |
| 2485 | + { | |
| 2486 | + "icon": "Database", | |
| 2487 | + "title": "Database management collection", | |
| 2488 | + "description": "10 PostgreSQL-first skills with MySQL/SQLite deviations noted — schema design, query optimization, migrations, backups, security, and a 5-stage incident triage runbook." | |
| 2489 | + }, | |
| 2490 | + { | |
| 2491 | + "icon": "Server", | |
| 2492 | + "title": "Backend development collection", | |
| 2493 | + "description": "20 skills spanning REST and GraphQL design, auth, validation, observability, caching, rate limiting, containers, CI/CD, and service boundaries." | |
| 2494 | + }, | |
| 2495 | + { | |
| 2496 | + "icon": "PenTool", | |
| 2497 | + "title": "Writing collection", | |
| 2498 | + "description": "10 stylistic skills where every rule ships with a do/never example pair and every workflow ends in a self-review pass — including the meta-skill for authoring skills." | |
| 2499 | + }, | |
| 2500 | + { | |
| 2501 | + "icon": "Calculator", | |
| 2502 | + "title": "US/Canada tax and accounting", | |
| 2503 | + "description": "10 skills with tax-year-2026 figures and official verification sources; every skill refuses evasion and refers complex cases to a CPA." | |
| 2504 | + }, | |
| 2505 | + { | |
| 2506 | + "icon": "ListChecks", | |
| 2507 | + "title": "Mechanical quality gate", | |
| 2508 | + "description": "A stdlib-only linter validates all 72 skills: frontmatter, naming rules, trigger descriptions, author headers, line budgets, and reference links." | |
| 2509 | + }, | |
| 2510 | + { | |
| 2511 | + "icon": "Split", | |
| 2512 | + "title": "Pairwise-exclusive triggers", | |
| 2513 | + "description": "Descriptions pair literal 'Use when' phrases with 'Do not use' boundaries per collection, so no request can plausibly fire two skills at once." | |
| 2514 | + }, | |
| 2515 | + { | |
| 2516 | + "icon": "Layers", | |
| 2517 | + "title": "Progressive disclosure by design", | |
| 2518 | + "description": "Lean SKILL.md files keep context cheap; depth lives exactly one level down in references/ directories with a table of contents." | |
| 2519 | + } | |
| 2520 | + ], | |
| 2521 | + "techStack": [ | |
| 2522 | + { | |
| 2523 | + "category": "Standard", | |
| 2524 | + "items": [ | |
| 2525 | + "Agent Skills (SKILL.md)", | |
| 2526 | + "Claude Code", | |
| 2527 | + "Markdown + YAML frontmatter" | |
| 2528 | + ] | |
| 2529 | + }, | |
| 2530 | + { | |
| 2531 | + "category": "Tooling", | |
| 2532 | + "items": [ | |
| 2533 | + "Python 3 (stdlib only)", | |
| 2534 | + "validate_skills.py linter", | |
| 2535 | + "Trigger-test validation fixtures" | |
| 2536 | + ] | |
| 2537 | + }, | |
| 2538 | + { | |
| 2539 | + "category": "Method", | |
| 2540 | + "items": [ | |
| 2541 | + "RESEARCH-SYNTHESIS.md (15 principles)", | |
| 2542 | + "Sharp Skill Checklist", | |
| 2543 | + "VALIDATION-REPORT.md" | |
| 2544 | + ] | |
| 2545 | + } | |
| 2546 | + ], | |
| 2547 | + "architecture": [ | |
| 2548 | + { | |
| 2549 | + "title": "Research phase", | |
| 2550 | + "description": "20+ primary-source searches and fetches — Anthropic docs, the official anthropics/skills repo, engineering blogs, eval guides — distilled into RESEARCH-SYNTHESIS.md." | |
| 2551 | + }, | |
| 2552 | + { | |
| 2553 | + "title": "Build phase", | |
| 2554 | + "description": "72 skills authored across 7 collections against the synthesized template, each with one default per decision and a validation-loop workflow." | |
| 2555 | + }, | |
| 2556 | + { | |
| 2557 | + "title": "Lint gate", | |
| 2558 | + "description": "tools/validate_skills.py mechanically enforces the Sharp Skill Checklist — names, descriptions, headers, budgets and links — across the entire repository." | |
| 2559 | + }, | |
| 2560 | + { | |
| 2561 | + "title": "Trigger validation", | |
| 2562 | + "description": "Every skill is validated with positive and negative trigger tests plus real execution evidence, documented in VALIDATION-REPORT.md with fixtures in validation/." | |
| 2563 | + } | |
| 2564 | + ], | |
| 2565 | + "highlights": [ | |
| 2566 | + "Installing a skill is one cp command into ~/.claude/skills/ or a project's .claude/skills/ — no dependencies, no build step.", | |
| 2567 | + "The entire toolchain is Python standard library only: python3 tools/validate_skills.py checks all 72 skills and passes clean.", | |
| 2568 | + "The backend collection is the largest at 20 skills; documents, frontend, databases, writing and finance each contribute 10.", | |
| 2569 | + "Includes writing-agent-skills, the meta-skill that teaches an agent to author new SKILL.md files with the same Sharp Skill method.", | |
| 2570 | + "Finance skills carry a built-in ethical boundary: educational, legal planning only — evasion is refused by design.", | |
| 2571 | + "Every figure in the repo is sourced, every skill linted, every trigger tested — the badges are backed by reports in the repo." | |
| 2572 | + ] | |
| 2573 | + }, | |
| 2574 | + { | |
| 2575 | + "slug": "artificial-neural-networks-book", | |
| 2576 | + "hero": { | |
| 2577 | + "headline": "Every Neural Architecture, Rigorously Drawn", | |
| 2578 | + "subheadline": "A 119-page LaTeX book covering all major neural network families — each with exact equations, pseudocode training algorithms, and native TikZ figures." | |
| 2579 | + }, | |
| 2580 | + "overview": [ | |
| 2581 | + "Artificial Neural Networks — Methods, Equations and Graphical Representations is a complete, self-contained book spanning the field from Rosenblatt's 1958 perceptron to 2024's Kolmogorov–Arnold networks. It is built on one strict organizing principle: every architecture gets rigorous equations, an estimation or training algorithm in pseudocode, and a faithful graphical representation.", | |
| 2582 | + "Every one of the 42 figures is drawn natively in TikZ/pgfplots — no imported images — so each diagram is exactly as precise as the equations it illustrates. Notation is unified across all 13 chapters: bold lowercase vectors, bold uppercase matrices, the Hadamard product, and a shared color palette for inputs, hidden units, outputs, gates and memory.", | |
| 2583 | + "Coverage runs through five parts: foundations and learning (backpropagation, Adam, regularization), core architectures (CNNs, LSTMs, Transformers, ViT, MoE, Mamba), graphs and energy (GCN, GAT, GIN, Hopfield, RBMs), generative models (VAEs, GANs, normalizing flows, DDPM diffusion), and specialized architectures from spiking networks to Neural ODEs and KANs." | |
| 2584 | + ], | |
| 2585 | + "stats": [ | |
| 2586 | + { | |
| 2587 | + "value": "119", | |
| 2588 | + "label": "pages" | |
| 2589 | + }, | |
| 2590 | + { | |
| 2591 | + "value": "13", | |
| 2592 | + "label": "chapters in 5 parts" | |
| 2593 | + }, | |
| 2594 | + { | |
| 2595 | + "value": "256", | |
| 2596 | + "label": "numbered equations" | |
| 2597 | + }, | |
| 2598 | + { | |
| 2599 | + "value": "42", | |
| 2600 | + "label": "native TikZ figures" | |
| 2601 | + }, | |
| 2602 | + { | |
| 2603 | + "value": "26", | |
| 2604 | + "label": "pseudocode algorithms" | |
| 2605 | + }, | |
| 2606 | + { | |
| 2607 | + "value": "36", | |
| 2608 | + "label": "cited bibliography entries" | |
| 2609 | + } | |
| 2610 | + ], | |
| 2611 | + "features": [ | |
| 2612 | + { | |
| 2613 | + "icon": "BookOpen", | |
| 2614 | + "title": "One principle, applied everywhere", | |
| 2615 | + "description": "Every architecture is presented as equations plus a training algorithm plus a faithful figure — from the perceptron to Kolmogorov–Arnold networks." | |
| 2616 | + }, | |
| 2617 | + { | |
| 2618 | + "icon": "PenTool", | |
| 2619 | + "title": "100% native TikZ figures", | |
| 2620 | + "description": "All 42 diagrams are drawn in TikZ/pgfplots with zero imported images, so figures carry the same precision as the mathematics." | |
| 2621 | + }, | |
| 2622 | + { | |
| 2623 | + "icon": "Braces", | |
| 2624 | + "title": "Unified notation throughout", | |
| 2625 | + "description": "Bold vectors and matrices, Hadamard products and a shared color palette are defined once in main.tex and reused by every chapter." | |
| 2626 | + }, | |
| 2627 | + { | |
| 2628 | + "icon": "Brain", | |
| 2629 | + "title": "Full deep learning canon", | |
| 2630 | + "description": "Backpropagation's three equations, Adam with bias correction, ResNet gradients, the six LSTM equations, and scaled dot-product attention with the variance argument." | |
| 2631 | + }, | |
| 2632 | + { | |
| 2633 | + "icon": "Sparkles", | |
| 2634 | + "title": "Modern architectures included", | |
| 2635 | + "description": "Vision Transformers, Mixture of Experts with noisy top-k routing, state-space models and Mamba's selective scan, and scaling laws." | |
| 2636 | + }, | |
| 2637 | + { | |
| 2638 | + "icon": "Network", | |
| 2639 | + "title": "Graphs, energy and memory", | |
| 2640 | + "description": "Message passing, GCN's spectral derivation, GAT, GIN with the 1-WL expressiveness theorem, Hopfield energy descent, and contrastive divergence for RBMs." | |
| 2641 | + }, | |
| 2642 | + { | |
| 2643 | + "icon": "FlaskConical", | |
| 2644 | + "title": "Generative models in depth", | |
| 2645 | + "description": "The VAE ELBO with closed-form Gaussian KL, GAN minimax theory through WGAN, RealNVP flows, and DDPM with both training and sampling algorithms." | |
| 2646 | + }, | |
| 2647 | + { | |
| 2648 | + "icon": "Microscope", | |
| 2649 | + "title": "Emerging and bio-inspired frontiers", | |
| 2650 | + "description": "Spiking neurons with STDP and surrogate gradients, self-organizing maps, echo state networks, capsule routing, Neural ODEs and Neural Turing Machines." | |
| 2651 | + }, | |
| 2652 | + { | |
| 2653 | + "icon": "CheckCircle2", | |
| 2654 | + "title": "Clean by construction", | |
| 2655 | + "description": "The three-pass pdflatex build finishes with zero errors, zero undefined references and zero unresolved citations." | |
| 2656 | + } | |
| 2657 | + ], | |
| 2658 | + "techStack": [ | |
| 2659 | + { | |
| 2660 | + "category": "Typesetting", | |
| 2661 | + "items": [ | |
| 2662 | + "LaTeX", | |
| 2663 | + "TikZ", | |
| 2664 | + "pgfplots", | |
| 2665 | + "algorithm / algpseudocode", | |
| 2666 | + "booktabs", | |
| 2667 | + "microtype", | |
| 2668 | + "hyperref" | |
| 2669 | + ] | |
| 2670 | + }, | |
| 2671 | + { | |
| 2672 | + "category": "Build", | |
| 2673 | + "items": [ | |
| 2674 | + "TeX Live", | |
| 2675 | + "pdflatex (3-pass build)" | |
| 2676 | + ] | |
| 2677 | + } | |
| 2678 | + ], | |
| 2679 | + "architecture": [ | |
| 2680 | + { | |
| 2681 | + "title": "Part I — Foundations and Learning", | |
| 2682 | + "description": "Perceptron with the Novikoff convergence theorem, backpropagation, the optimizer family up to Adam, initialization, and the full regularization toolbox." | |
| 2683 | + }, | |
| 2684 | + { | |
| 2685 | + "title": "Part II — Core Architectures", | |
| 2686 | + "description": "CNNs with a worked convolution grid, RNN/LSTM/GRU with BPTT, the complete Transformer encoder–decoder, and modern variants from ViT to Mamba." | |
| 2687 | + }, | |
| 2688 | + { | |
| 2689 | + "title": "Part III — Graphs, Energy and Memory", | |
| 2690 | + "description": "Graph neural networks (GCN, GraphSAGE, GAT, GIN) and energy-based models from Hopfield networks to RBMs and deep belief networks." | |
| 2691 | + }, | |
| 2692 | + { | |
| 2693 | + "title": "Part IV — Generative Models", | |
| 2694 | + "description": "Autoencoders, VAEs, GANs, normalizing flows, DDPM/DDIM diffusion with classifier-free guidance, and WaveNet's dilated causal convolutions." | |
| 2695 | + }, | |
| 2696 | + { | |
| 2697 | + "title": "Part V — Specialized and Emerging", | |
| 2698 | + "description": "Biologically inspired networks — spiking neurons, SOMs, reservoir computing — plus capsules, Neural ODEs, Neural Turing Machines and KANs." | |
| 2699 | + } | |
| 2700 | + ], | |
| 2701 | + "highlights": [ | |
| 2702 | + "Spans 66 years of the field in one consistent framework — from the 1958 perceptron to 2024 Kolmogorov–Arnold networks.", | |
| 2703 | + "Every figure is code: 42 TikZ/pgfplots diagrams and not a single imported image.", | |
| 2704 | + "26 estimation algorithms in pseudocode, including both the training and sampling procedures for diffusion models.", | |
| 2705 | + "All 36 bibliography entries are actually cited in the text — no padding.", | |
| 2706 | + "Includes rigorous theorem-level results: universal approximation, Novikoff convergence, Hopfield energy descent, and 1-WL expressiveness for GINs.", | |
| 2707 | + "The compiled PDF ships in the repository alongside the full LaTeX source." | |
| 2708 | + ] | |
| 2709 | + }, | |
| 2710 | + { | |
| 2711 | + "slug": "phd-thesis", | |
| 2712 | + "hero": { | |
| 2713 | + "headline": "Three Essays in High-Frequency Finance", | |
| 2714 | + "subheadline": "A Université Laval PhD thesis on return and volatility dynamics in commodity and financial futures, built on minute-level data around information events." | |
| 2715 | + }, | |
| 2716 | + "overview": [ | |
| 2717 | + "This thesis-by-articles at Université Laval's Faculty of Business Administration studies how information moves markets when you watch at one-to-five-minute resolution. Three essays, co-authored with Marie-Hélène Gagnon and Gabriel J. Power, share one lens: high-frequency return, volatility and liquidity dynamics around information events in commodity and financial futures markets.", | |
| 2718 | + "Chapter 1 — revised for The Energy Journal — shows that speculative trading dampens, rather than amplifies, the impact of macroeconomic surprises on energy and metals futures, with money managers (not swap dealers) improving liquidity and price discovery. Chapter 2 builds a novel minute-level indicative NAV dataset for four commodity ETFs and finds that volatility transmission runs primarily through jumps.", | |
| 2719 | + "Chapter 3 decomposes FOMC statements into policy tone and informational novelty using a dual-model NLP ensemble (MiniLM + BERT), then links them to one-minute futures data: tone predicts directional returns while novelty predicts volatility, with pre-announcement placebos coming up null. The assembled ULaval thesis compiles to 188 pages with zero errors and a consolidated 271-key bibliography." | |
| 2720 | + ], | |
| 2721 | + "stats": [ | |
| 2722 | + { | |
| 2723 | + "value": "3", | |
| 2724 | + "label": "essays" | |
| 2725 | + }, | |
| 2726 | + { | |
| 2727 | + "value": "188", | |
| 2728 | + "label": "pages" | |
| 2729 | + }, | |
| 2730 | + { | |
| 2731 | + "value": "~45M", | |
| 2732 | + "label": "tick observations (Ch. 2)" | |
| 2733 | + }, | |
| 2734 | + { | |
| 2735 | + "value": "148", | |
| 2736 | + "label": "FOMC events analyzed" | |
| 2737 | + }, | |
| 2738 | + { | |
| 2739 | + "value": "271", | |
| 2740 | + "label": "consolidated references" | |
| 2741 | + }, | |
| 2742 | + { | |
| 2743 | + "value": "26", | |
| 2744 | + "label": "macro announcement types (Ch. 1)" | |
| 2745 | + } | |
| 2746 | + ], | |
| 2747 | + "features": [ | |
| 2748 | + { | |
| 2749 | + "icon": "Flame", | |
| 2750 | + "title": "Speculation and macro surprises", | |
| 2751 | + "description": "5-minute futures data (2007–2024) across crude oil, natural gas, gold, silver, copper and palladium, conditioned on a CFTC-based speculation intensity proxy." | |
| 2752 | + }, | |
| 2753 | + { | |
| 2754 | + "icon": "TrendingUp", | |
| 2755 | + "title": "Speculators dampen, not amplify", | |
| 2756 | + "description": "Higher speculative intensity reduces the impact of macro surprises on price drift, volatility and bid-ask spreads — driven by money managers, not swap dealers." | |
| 2757 | + }, | |
| 2758 | + { | |
| 2759 | + "icon": "LineChart", | |
| 2760 | + "title": "A novel minute-level iNAV dataset", | |
| 2761 | + "description": "Roughly 45 million tick observations (2010–2023) for GLD, SLV, USO and UNG give a sharper image of ETF–underlying volatility transmission than daily data can." | |
| 2762 | + }, | |
| 2763 | + { | |
| 2764 | + "icon": "Zap", | |
| 2765 | + "title": "Transmission runs through jumps", | |
| 2766 | + "description": "Barndorff-Nielsen–Shephard decomposition shows volatility flows via jumps, not diffusion; 1-minute estimates run up to 2x larger than 30-minute ones." | |
| 2767 | + }, | |
| 2768 | + { | |
| 2769 | + "icon": "MessageSquare", | |
| 2770 | + "title": "NLP on the Fed's own words", | |
| 2771 | + "description": "217 FOMC statements (2000–2025) decomposed into hawkish/dovish tone and informational novelty via a MiniLM + BERT ensemble with TSDAE+MNRL fine-tuning." | |
| 2772 | + }, | |
| 2773 | + { | |
| 2774 | + "icon": "Landmark", | |
| 2775 | + "title": "Tone moves returns, novelty moves volatility", | |
| 2776 | + "description": "A 1-sigma dovish shift builds to roughly +12 bps in equities within two hours; the stance-novelty interaction on VIX persists 5–120 minutes." | |
| 2777 | + }, | |
| 2778 | + { | |
| 2779 | + "icon": "BarChart3", | |
| 2780 | + "title": "Econometrics at full depth", | |
| 2781 | + "description": "WLS-EWMA and GARCH event studies, HAR-X and HAR-CJ-X models, Minnesota BVARs, minute-level panels, Jorda local projections and five inference methods." | |
| 2782 | + }, | |
| 2783 | + { | |
| 2784 | + "icon": "ShieldCheck", | |
| 2785 | + "title": "Identification taken seriously", | |
| 2786 | + "description": "Pre-announcement placebo tests are null across designs, confirming that measured effects are announcement-driven rather than spurious." | |
| 2787 | + }, | |
| 2788 | + { | |
| 2789 | + "icon": "CheckCircle2", | |
| 2790 | + "title": "Reproducible LaTeX build", | |
| 2791 | + "description": "The full ulthese/memoir document compiles with latexmk to 0 errors, 0 undefined references and 0 missing citations, with an exhaustive assembly audit." | |
| 2792 | + } | |
| 2793 | + ], | |
| 2794 | + "techStack": [ | |
| 2795 | + { | |
| 2796 | + "category": "Methods", | |
| 2797 | + "items": [ | |
| 2798 | + "WLS-EWMA event studies", | |
| 2799 | + "GARCH", | |
| 2800 | + "HAR-X / HAR-CJ-X", | |
| 2801 | + "Minnesota BVAR", | |
| 2802 | + "Jorda local projections", | |
| 2803 | + "Jump decomposition (BNS)" | |
| 2804 | + ] | |
| 2805 | + }, | |
| 2806 | + { | |
| 2807 | + "category": "NLP", | |
| 2808 | + "items": [ | |
| 2809 | + "MiniLM", | |
| 2810 | + "BERT", | |
| 2811 | + "TSDAE + MNRL fine-tuning", | |
| 2812 | + "PCA-based reference selection" | |
| 2813 | + ] | |
| 2814 | + }, | |
| 2815 | + { | |
| 2816 | + "category": "Data", | |
| 2817 | + "items": [ | |
| 2818 | + "1–5 min futures ticks", | |
| 2819 | + "CFTC disaggregated COT", | |
| 2820 | + "Intraday iNAV (GLD, SLV, USO, UNG)", | |
| 2821 | + "FOMC statements 2000–2025" | |
| 2822 | + ] | |
| 2823 | + }, | |
| 2824 | + { | |
| 2825 | + "category": "Document", | |
| 2826 | + "items": [ | |
| 2827 | + "LaTeX (ulthese / memoir)", | |
| 2828 | + "latexmk", | |
| 2829 | + "BibTeX (271 consolidated keys)" | |
| 2830 | + ] | |
| 2831 | + } | |
| 2832 | + ], | |
| 2833 | + "architecture": [ | |
| 2834 | + { | |
| 2835 | + "title": "Chapter 1 — Speculative trading in energy markets", | |
| 2836 | + "description": "Macro announcement surprises interacted with CFTC-based speculation intensity across six futures contracts; 14 tables, 6 figures, COVID and ZLB robustness appendices." | |
| 2837 | + }, | |
| 2838 | + { | |
| 2839 | + "title": "Chapter 2 — iNAV and volatility transmission", | |
| 2840 | + "description": "Minute-level iNAV construction, realized variance decomposition into continuous and jump components, HAR-X models at three frequencies, and a Bayesian VAR; 13 tables, 8 figures." | |
| 2841 | + }, | |
| 2842 | + { | |
| 2843 | + "title": "Chapter 3 — FOMC tone and novelty", | |
| 2844 | + "description": "NLP ensemble scoring of statements linked to 1-minute data on seven futures contracts via event regressions, panels and local projections; 24 tables, 19 figures, proofs appendix." | |
| 2845 | + }, | |
| 2846 | + { | |
| 2847 | + "title": "these-ulaval/ — the assembled thesis", | |
| 2848 | + "description": "French front matter with English abstracts, general introduction and conclusion, appendices, and a merged 271-key bibliography; article sources are frozen snapshots, never edited in place." | |
| 2849 | + } | |
| 2850 | + ], | |
| 2851 | + "highlights": [ | |
| 2852 | + "Chapter 1 is revised for The Energy Journal; Chapter 2 is at submission stage with the Journal of Futures Markets.", | |
| 2853 | + "Precious-metals volatility transmission is unidirectional (iNAV to ETF, passive arbitrage) while energy is bidirectional and asymmetric.", | |
| 2854 | + "Policy stance moves realized volatility in 6 of 7 futures contracts at p < 0.01; the key VIX interaction carries t = -5.06.", | |
| 2855 | + "Markets covered span energy, metals, equity and rates: CL, NG, GC, SI, HG, PA futures plus ES, VX, ZN, ZF and DX.", | |
| 2856 | + "Funded by SSHRC and the Chaire Industrielle-Alliance Groupe financier.", | |
| 2857 | + "Each article folder is a frozen dated snapshot; every thesis adaptation is documented in an exhaustive INVENTAIRE.md audit." | |
| 2858 | + ] | |
| 2859 | + }, | |
| 2860 | + { | |
| 2861 | + "slug": "uqo-cours", | |
| 2862 | + "hero": { | |
| 2863 | + "headline": "Real-Estate Valuation Courses, Built on Real Data", | |
| 2864 | + "subheadline": "Complete UQO course material for two real-estate appraisal courses: 28 Beamer lectures, 6 practical assignments with Excel templates, and a 106-page Quebec market report." | |
| 2865 | + }, | |
| 2866 | + "overview": [ | |
| 2867 | + "This is the full public course material for two undergraduate courses in UQO's business administration program (real-estate appraisal concentration): IMM1003 — Elements of Real Estate Appraisal (Fall 2026), covering the Quebec professional framework, market economics and the three valuation approaches; and IMM1033 — Cost Approach Methods (Winter 2026), covering land valuation, construction cost estimation and full depreciation breakdown.", | |
| 2868 | + "Every practical assignment pairs a LaTeX case brief with a formula-driven Excel Dashboard workbook — pre-filled data, answer cells, dropdowns, KPIs, native charts and conditional formatting — personalized by each student's permanent code. Cases are anchored in real, cited data: actual MLS listings in Gatineau, Aylmer and Hull, Bank of Canada rates, CMHC, Statistics Canada indices and real construction permits.", | |
| 2869 | + "The material is explicitly designed for teaching in the AI era: take-home work where AI use is tolerated and framed, per-student personalized data, answers that must be grounded in the case file (generic answers score nothing), formula-chained workbooks, and a mandatory tool-use declaration. This public version excludes all exams, solution keys and grading rubrics." | |
| 2870 | + ], | |
| 2871 | + "stats": [ | |
| 2872 | + { | |
| 2873 | + "value": "28", | |
| 2874 | + "label": "Beamer lecture decks" | |
| 2875 | + }, | |
| 2876 | + { | |
| 2877 | + "value": "6", | |
| 2878 | + "label": "practical assignments" | |
| 2879 | + }, | |
| 2880 | + { | |
| 2881 | + "value": "6", | |
| 2882 | + "label": "Excel Dashboard templates" | |
| 2883 | + }, | |
| 2884 | + { | |
| 2885 | + "value": "106", | |
| 2886 | + "label": "market report pages" | |
| 2887 | + }, | |
| 2888 | + { | |
| 2889 | + "value": "67", | |
| 2890 | + "label": "figures" | |
| 2891 | + }, | |
| 2892 | + { | |
| 2893 | + "value": "2", | |
| 2894 | + "label": "full courses" | |
| 2895 | + } | |
| 2896 | + ], | |
| 2897 | + "features": [ | |
| 2898 | + { | |
| 2899 | + "icon": "GraduationCap", | |
| 2900 | + "title": "Two complete course packages", | |
| 2901 | + "description": "Syllabi, 28 fully compiled Beamer lecture decks with sources and bibliographies, six graded assignments and supporting resources for IMM1003 and IMM1033." | |
| 2902 | + }, | |
| 2903 | + { | |
| 2904 | + "icon": "Home", | |
| 2905 | + "title": "Cases built on real listings", | |
| 2906 | + "description": "Assignments cite actual MLS listings — an Aylmer residence, a Hull triplex, vacant land parcels for sale — plus real Gatineau construction permits." | |
| 2907 | + }, | |
| 2908 | + { | |
| 2909 | + "icon": "Table2", | |
| 2910 | + "title": "Formula-driven Excel dashboards", | |
| 2911 | + "description": "Each assignment ships a Dashboard workbook with pre-filled data, formula answer cells, dropdowns, KPIs, native charts and conditional formatting." | |
| 2912 | + }, | |
| 2913 | + { | |
| 2914 | + "icon": "Fingerprint", | |
| 2915 | + "title": "Personalized per student", | |
| 2916 | + "description": "Workbooks and data are customized by each student's permanent code, and answers must be anchored in the case file — generic responses earn nothing." | |
| 2917 | + }, | |
| 2918 | + { | |
| 2919 | + "icon": "BarChart3", | |
| 2920 | + "title": "106-page Quebec market report", | |
| 2921 | + "description": "The Rapport immobilier Quebec covers HPI indices, regions, property types, macro conditions and affordability, with 67 figures and its full Python analysis code." | |
| 2922 | + }, | |
| 2923 | + { | |
| 2924 | + "icon": "CheckCircle2", | |
| 2925 | + "title": "Verified, cited, reproducible", | |
| 2926 | + "description": "LaTeX compiles with zero errors, Excel formulas were recalculated in Microsoft Excel with zero error cells, and every source carries a URL and consultation date." | |
| 2927 | + }, | |
| 2928 | + { | |
| 2929 | + "icon": "Brain", | |
| 2930 | + "title": "Designed for the AI era", | |
| 2931 | + "description": "Take-home assignments tolerate and frame AI use, require case-grounded answers, chain workbooks by formulas, and mandate a tool-use declaration." | |
| 2932 | + }, | |
| 2933 | + { | |
| 2934 | + "icon": "Landmark", | |
| 2935 | + "title": "Anchored in official sources", | |
| 2936 | + "description": "APCIQ, Bank of Canada, CMHC, Statistics Canada, Realtor.ca, City of Gatineau, GCR, CCQ, CBRE, Altus and the TAL — all cited with access dates." | |
| 2937 | + }, | |
| 2938 | + { | |
| 2939 | + "icon": "Wrench", | |
| 2940 | + "title": "Fully regenerable toolchain", | |
| 2941 | + "description": "One latexmk command rebuilds any deck or brief, one Python script regenerates each Excel template, and the market report rebuilds from CREA/FRED data." | |
| 2942 | + } | |
| 2943 | + ], | |
| 2944 | + "techStack": [ | |
| 2945 | + { | |
| 2946 | + "category": "Typesetting", | |
| 2947 | + "items": [ | |
| 2948 | + "LuaLaTeX", | |
| 2949 | + "Beamer", | |
| 2950 | + "biber", | |
| 2951 | + "TeX Live 2026", | |
| 2952 | + "Custom uqo-examen.sty", | |
| 2953 | + "TeX Gyre Termes/Heros" | |
| 2954 | + ] | |
| 2955 | + }, | |
| 2956 | + { | |
| 2957 | + "category": "Data & tooling", | |
| 2958 | + "items": [ | |
| 2959 | + "Python 3", | |
| 2960 | + "openpyxl", | |
| 2961 | + "pandas", | |
| 2962 | + "matplotlib" | |
| 2963 | + ] | |
| 2964 | + }, | |
| 2965 | + { | |
| 2966 | + "category": "Data sources", | |
| 2967 | + "items": [ | |
| 2968 | + "APCIQ", | |
| 2969 | + "Bank of Canada", | |
| 2970 | + "CMHC", | |
| 2971 | + "Statistics Canada", | |
| 2972 | + "CREA / FRED", | |
| 2973 | + "Realtor.ca" | |
| 2974 | + ] | |
| 2975 | + } | |
| 2976 | + ], | |
| 2977 | + "architecture": [ | |
| 2978 | + { | |
| 2979 | + "title": "Course trees (IMM1003 / IMM1033)", | |
| 2980 | + "description": "Each course directory holds the syllabus, lecture sources and compiled PDFs, the practical assignments with briefs and Excel templates, and shared resources." | |
| 2981 | + }, | |
| 2982 | + { | |
| 2983 | + "title": "Assignment pairs", | |
| 2984 | + "description": "Every TP couples an 8–12 page LaTeX brief — professional scenario, tables, figures, 8–10 cell-referenced questions on a /100 scale — with its matching Excel Dashboard workbook." | |
| 2985 | + }, | |
| 2986 | + { | |
| 2987 | + "title": "Quebec market report", | |
| 2988 | + "description": "A 106-page report with PDF, LaTeX source, 67 figures and the Python HPI analysis pipeline that regenerates it from CREA and FRED data." | |
| 2989 | + }, | |
| 2990 | + { | |
| 2991 | + "title": "Tooling layer", | |
| 2992 | + "description": "A shared UQO LaTeX style, openpyxl scripts that generate each Excel template deterministically, and per-folder autonomous latexmk builds." | |
| 2993 | + } | |
| 2994 | + ], | |
| 2995 | + "highlights": [ | |
| 2996 | + "Assignments progress from market dashboards through comparison grids and income approach to full depreciation breakdown, weighted from 10% to 20% of the course grade.", | |
| 2997 | + "All real data was consulted and date-stamped on August 4, 2026; anything not freely accessible is flagged as calibrated pedagogical data — never silently invented.", | |
| 2998 | + "Excel templates were verified inside Microsoft Excel with a full recalculation and zero error cells.", | |
| 2999 | + "The public repo deliberately excludes exams, solution keys and grading rubrics, which live in a separate private teaching repository.", | |
| 3000 | + "Raw CREA/ACI data is not redistributed per its terms of use; the report's README documents how to obtain it and reproduce every figure.", | |
| 3001 | + "Depreciation cases draw on professional-grade references: Statistics Canada indices, ASHRAE/InterNACHI service lives, CBRE and Altus cost data." | |
| 3002 | + ] | |
| 3003 | + }, | |
| 3004 | + { | |
| 3005 | + "slug": "lou-ka", | |
| 3006 | + "hero": { | |
| 3007 | + "headline": "Every rental in Quebec. One search.", | |
| 3008 | + "subheadline": "An independent aggregator that turns 70+ scattered property-manager websites into one continuously synchronized, searchable rental market for the whole province." | |
| 3009 | + }, | |
| 3010 | + "overview": [ | |
| 3011 | + "Apartment hunting in Quebec means opening dozens of manager websites, each with its own navigation, filters, and formats. Lou-Ka inverts the problem: a dedicated Python connector per property manager visits each site, normalizes every listing into a single Listing schema — address, sector, unit type, price, availability, amenities, and every photo — and links back to the original ad.", | |
| 3012 | + "Agency sites offer no webhooks, so Lou-Ka rebuilds the equivalent: periodic synchronization plus content hashing detects additions, updates, and removals automatically. A listing that disappears from the source disappears from Lou-Ka. An hourly watcher under PM2 keeps the whole inventory current without human intervention.", | |
| 3013 | + "The system is built for polite, resilient scale: throttled requests with an identified User-Agent, no invented prices, per-listing fault isolation, and auto-discovering connectors — dropping a ~30-line module into the connectors folder is all it takes to onboard a new manager." | |
| 3014 | + ], | |
| 3015 | + "stats": [ | |
| 3016 | + { | |
| 3017 | + "value": "255", | |
| 3018 | + "label": "sources catalogued" | |
| 3019 | + }, | |
| 3020 | + { | |
| 3021 | + "value": "194", | |
| 3022 | + "label": "active connectors" | |
| 3023 | + }, | |
| 3024 | + { | |
| 3025 | + "value": "8,000+", | |
| 3026 | + "label": "listings aggregated" | |
| 3027 | + }, | |
| 3028 | + { | |
| 3029 | + "value": "≈30", | |
| 3030 | + "label": "lines to add a connector" | |
| 3031 | + }, | |
| 3032 | + { | |
| 3033 | + "value": "1 day", | |
| 3034 | + "label": "from market research to production" | |
| 3035 | + } | |
| 3036 | + ], | |
| 3037 | + "features": [ | |
| 3038 | + { | |
| 3039 | + "icon": "Network", | |
| 3040 | + "title": "One connector per manager", | |
| 3041 | + "description": "A dedicated Python adapter per property manager handles server-rendered HTML, internal JSON APIs (Building Stack, RealVuu, Planpoint, Rentsync, and more), or Firecrawl for Cloudflare-protected sites." | |
| 3042 | + }, | |
| 3043 | + { | |
| 3044 | + "icon": "RefreshCw", | |
| 3045 | + "title": "Webhook-equivalent change detection", | |
| 3046 | + "description": "Hourly synchronization with content hashing upserts new, modified, and removed listings automatically — vanished listings are deactivated, never left stale." | |
| 3047 | + }, | |
| 3048 | + { | |
| 3049 | + "icon": "Layers", | |
| 3050 | + "title": "Single normalized schema", | |
| 3051 | + "description": "Every listing lands in one standardized Listing model: address, sector, city, Quebec unit types (3½…), price, availability, amenities, and all images." | |
| 3052 | + }, | |
| 3053 | + { | |
| 3054 | + "icon": "Search", | |
| 3055 | + "title": "Faceted search API", | |
| 3056 | + "description": "FastAPI endpoints filter by city, sector, unit size, price range, manager, and full-text query, with facets, stats, and an on-demand sync trigger." | |
| 3057 | + }, | |
| 3058 | + { | |
| 3059 | + "icon": "Blocks", | |
| 3060 | + "title": "Auto-discovering connector registry", | |
| 3061 | + "description": "Drop a ~30-line module in the connectors folder and it registers itself — no shared file to modify, and a broken connector never affects the others." | |
| 3062 | + }, | |
| 3063 | + { | |
| 3064 | + "icon": "Globe", | |
| 3065 | + "title": "Province-wide coverage", | |
| 3066 | + "description": "Quebec City, Lévis, and Greater Montreal managers — from Logisco and Cogir to Akelius, CAPREIT, Minto, and Devimco — plus documented reasons for every non-connectable source." | |
| 3067 | + }, | |
| 3068 | + { | |
| 3069 | + "icon": "Eye", | |
| 3070 | + "title": "Editorial-sharp PWA frontend", | |
| 3071 | + "description": "React 18 + Vite interface with Space Grotesk type, offset shadows, real-time ticker, mobile bottom sheet, photo galleries, and installable PWA support." | |
| 3072 | + }, | |
| 3073 | + { | |
| 3074 | + "icon": "ShieldCheck", | |
| 3075 | + "title": "Polite and faithful by design", | |
| 3076 | + "description": "Requests throttled at 0.5 s minimum with crawl guardrails; prices are never invented — if a source shows no price, the field stays null." | |
| 3077 | + } | |
| 3078 | + ], | |
| 3079 | + "techStack": [ | |
| 3080 | + { | |
| 3081 | + "category": "Backend", | |
| 3082 | + "items": [ | |
| 3083 | + "Python 3.14", | |
| 3084 | + "FastAPI", | |
| 3085 | + "SQLite" | |
| 3086 | + ] | |
| 3087 | + }, | |
| 3088 | + { | |
| 3089 | + "category": "Frontend", | |
| 3090 | + "items": [ | |
| 3091 | + "React 18", | |
| 3092 | + "Vite", | |
| 3093 | + "TypeScript", | |
| 3094 | + "PWA" | |
| 3095 | + ] | |
| 3096 | + }, | |
| 3097 | + { | |
| 3098 | + "category": "Ingestion", | |
| 3099 | + "items": [ | |
| 3100 | + "Per-site connectors", | |
| 3101 | + "Firecrawl", | |
| 3102 | + "Content-hash diff engine" | |
| 3103 | + ] | |
| 3104 | + }, | |
| 3105 | + { | |
| 3106 | + "category": "Operations", | |
| 3107 | + "items": [ | |
| 3108 | + "PM2", | |
| 3109 | + "ngrok", | |
| 3110 | + "Hourly watcher" | |
| 3111 | + ] | |
| 3112 | + } | |
| 3113 | + ], | |
| 3114 | + "architecture": [ | |
| 3115 | + { | |
| 3116 | + "title": "Connectors", | |
| 3117 | + "description": "One Python module per property manager fetches listings from rendered HTML, internal JSON APIs, or Firecrawl, with per-listing error isolation." | |
| 3118 | + }, | |
| 3119 | + { | |
| 3120 | + "title": "Normalization", | |
| 3121 | + "description": "Raw listings are mapped to a single standardized Listing schema — city inference, Quebec unit-type normalization, price parsing, and complete image sets." | |
| 3122 | + }, | |
| 3123 | + { | |
| 3124 | + "title": "Diff engine", | |
| 3125 | + "description": "SQLite upserts keyed by content hash classify each listing as new, modified, or gone; removed listings are deactivated and every sync is logged." | |
| 3126 | + }, | |
| 3127 | + { | |
| 3128 | + "title": "API layer", | |
| 3129 | + "description": "FastAPI serves filtered search, per-listing detail, facets, the 74-manager source registry with counters, provincial stats, and a background sync trigger." | |
| 3130 | + }, | |
| 3131 | + { | |
| 3132 | + "title": "Frontend", | |
| 3133 | + "description": "A React 18 + Vite PWA renders the aggregated market with real-time ticker, filters, galleries, and mobile-first bottom-sheet navigation." | |
| 3134 | + }, | |
| 3135 | + { | |
| 3136 | + "title": "Production loop", | |
| 3137 | + "description": "Three PM2 processes — web server, hourly watcher, ngrok tunnel — keep the site self-maintaining: only code gets pushed, the server refreshes its own data." | |
| 3138 | + } | |
| 3139 | + ], | |
| 3140 | + "highlights": [ | |
| 3141 | + "Designed, built, and deployed to production in a single day — including market research verifying 74 property managers.", | |
| 3142 | + "255 rental sources catalogued and 194 active connectors, aggregating 8,000+ listings across all of Quebec.", | |
| 3143 | + "Adding a new property manager takes roughly 30 lines of Python thanks to auto-discovering connector registration.", | |
| 3144 | + "Content hashing recreates webhooks that agency sites never offer: additions, updates, and removals detected automatically.", | |
| 3145 | + "Every non-connectable source is documented with the specific reason (no prices shown, empty inventory, placeholder site).", | |
| 3146 | + "Strict fidelity rule: no invented prices, and every listing links back to the manager's original ad." | |
| 3147 | + ] | |
| 3148 | + }, | |
| 3149 | + { | |
| 3150 | + "slug": "vrai-prix", | |
| 3151 | + "hero": { | |
| 3152 | + "headline": "Your property's true value, no black box", | |
| 3153 | + "subheadline": "A transparent valuation engine for every property in Quebec — 3.7 million properties, 745,119 real sales, and every calculation shown in full." | |
| 3154 | + }, | |
| 3155 | + "overview": [ | |
| 3156 | + "Vrai-Prix estimates the market value of any Quebec property — single-family homes, plexes, condos, cottages, land — and shows exactly why: every comparable sale, every dollar adjustment, the model weighting, the value range, and a mandatory A–D confidence grade. The differentiator is total transparency: no magic score, no black box.", | |
| 3157 | + "The engine fuses two approaches: a LightGBM hedonic model trained on log-prices with 18 variables and quantile heads for P10–P90 ranges (65% weight), blended with a weighted median of market-, size-, and age-adjusted comparable sales (35%). Six assessment-roll vintages — 22,150,285 observations from Quebec's open MAMH data — feed 22 million pre-computed estimates.", | |
| 3158 | + "Validation follows the IAAO ratio-study standard on 102,943 never-seen sales: 11.0% median error, a 0.995 median ratio squarely inside the 0.90–1.10 target, and 72.1% of estimates within ±20% of the actual price. A strict temporal test on 68,364 sales from 2026 confirms out-of-time robustness." | |
| 3159 | + ], | |
| 3160 | + "stats": [ | |
| 3161 | + { | |
| 3162 | + "value": "3,747,008", | |
| 3163 | + "label": "properties covered" | |
| 3164 | + }, | |
| 3165 | + { | |
| 3166 | + "value": "745,119", | |
| 3167 | + "label": "real sales" | |
| 3168 | + }, | |
| 3169 | + { | |
| 3170 | + "value": "11.0%", | |
| 3171 | + "label": "median error (MdAPE)" | |
| 3172 | + }, | |
| 3173 | + { | |
| 3174 | + "value": "0.995", | |
| 3175 | + "label": "median ratio (IAAO target 0.90–1.10)" | |
| 3176 | + }, | |
| 3177 | + { | |
| 3178 | + "value": "99.88%", | |
| 3179 | + "label": "spatial match rate" | |
| 3180 | + }, | |
| 3181 | + { | |
| 3182 | + "value": "$2.01T", | |
| 3183 | + "label": "total provincial value computed" | |
| 3184 | + } | |
| 3185 | + ], | |
| 3186 | + "features": [ | |
| 3187 | + { | |
| 3188 | + "icon": "Search", | |
| 3189 | + "title": "Full-text address search", | |
| 3190 | + "description": "SQLite FTS5 search across all 3,747,008 assessment units in the province, with instant autocompletion." | |
| 3191 | + }, | |
| 3192 | + { | |
| 3193 | + "icon": "Calculator", | |
| 3194 | + "title": "Hybrid estimation engine", | |
| 3195 | + "description": "65% LightGBM hedonic model plus 35% weighted-median adjusted comparables, with every adjustment displayed in dollars — market conditions, size, and age." | |
| 3196 | + }, | |
| 3197 | + { | |
| 3198 | + "icon": "Gauge", | |
| 3199 | + "title": "Ranges with mandatory confidence", | |
| 3200 | + "description": "P10–P90 ranges from quantile regression and an A–D confidence grade driven by comparable count, dispersion, and range width — never hidden." | |
| 3201 | + }, | |
| 3202 | + { | |
| 3203 | + "icon": "Map", | |
| 3204 | + "title": "Surveyor-style comparables map", | |
| 3205 | + "description": "A hand-built pure-SVG 'surveyor's plan' with real azimuths and distances — zero map tiles, zero third-party dependencies." | |
| 3206 | + }, | |
| 3207 | + { | |
| 3208 | + "icon": "Home", | |
| 3209 | + "title": "Data-drawn property portrait", | |
| 3210 | + "description": "An SVG facade drawn from registry data, a to-scale lot rendering, and a breakdown of value composition per property." | |
| 3211 | + }, | |
| 3212 | + { | |
| 3213 | + "icon": "LineChart", | |
| 3214 | + "title": "Six vintages of history", | |
| 3215 | + "description": "Estimates from 2021 through 2026 for every property, tracing how each valuation evolved across assessment-roll vintages." | |
| 3216 | + }, | |
| 3217 | + { | |
| 3218 | + "icon": "Building2", | |
| 3219 | + "title": "Portfolio valuation", | |
| 3220 | + "description": "Aggregate up to 40 properties into a single portfolio view with a consolidated multi-page PDF report." | |
| 3221 | + }, | |
| 3222 | + { | |
| 3223 | + "icon": "FileText", | |
| 3224 | + "title": "Fully vectorial PDF reports", | |
| 3225 | + "description": "Standard (3 pages), bank-grade professional (6 pages), portfolio, and provincial statistical reports — 100% vector output with embedded fonts via pdfkit." | |
| 3226 | + }, | |
| 3227 | + { | |
| 3228 | + "icon": "Scale", | |
| 3229 | + "title": "Law 25 compliant", | |
| 3230 | + "description": "Quebec privacy-law compliance, a cookie-consent selector, and the complete public methodology embedded in the product." | |
| 3231 | + } | |
| 3232 | + ], | |
| 3233 | + "techStack": [ | |
| 3234 | + { | |
| 3235 | + "category": "Frontend / API", | |
| 3236 | + "items": [ | |
| 3237 | + "Next.js 16 (App Router)", | |
| 3238 | + "TypeScript strict", | |
| 3239 | + "Tailwind CSS 4" | |
| 3240 | + ] | |
| 3241 | + }, | |
| 3242 | + { | |
| 3243 | + "category": "Data", | |
| 3244 | + "items": [ | |
| 3245 | + "SQLite (better-sqlite3)", | |
| 3246 | + "FTS5 full-text", | |
| 3247 | + "Spatial indexes" | |
| 3248 | + ] | |
| 3249 | + }, | |
| 3250 | + { | |
| 3251 | + "category": "Model pipeline", | |
| 3252 | + "items": [ | |
| 3253 | + "LightGBM", | |
| 3254 | + "scikit-learn", | |
| 3255 | + "pandas", | |
| 3256 | + "pyogrio/GDAL" | |
| 3257 | + ] | |
| 3258 | + }, | |
| 3259 | + { | |
| 3260 | + "category": "Reports & testing", | |
| 3261 | + "items": [ | |
| 3262 | + "pdfkit", | |
| 3263 | + "Vitest", | |
| 3264 | + "Playwright" | |
| 3265 | + ] | |
| 3266 | + } | |
| 3267 | + ], | |
| 3268 | + "architecture": [ | |
| 3269 | + { | |
| 3270 | + "title": "Open-data ingestion", | |
| 3271 | + "description": "Six vintages of Quebec's georeferenced assessment rolls (MAMH, 2021–2026) — roughly 3.7M units per vintage, 22,150,285 observations in total." | |
| 3272 | + }, | |
| 3273 | + { | |
| 3274 | + "title": "Spatial fusion", | |
| 3275 | + "description": "Sales are matched to assessment units via XY join in EPSG:32198 — kNN k=25 within 200 m plus value-based join within 500 m — achieving 99.88% matches at 0.6 m median distance." | |
| 3276 | + }, | |
| 3277 | + { | |
| 3278 | + "title": "Hedonic model", | |
| 3279 | + "description": "LightGBM on log(price) with 18 variables and P10/P90 quantile heads produces 22 million pre-computed estimates across all vintages." | |
| 3280 | + }, | |
| 3281 | + { | |
| 3282 | + "title": "Comparables engine", | |
| 3283 | + "description": "Candidate sales within an adaptive 2.2–28 km radius are filtered by type and size, adjusted for market, size, and age, then combined by Gaussian-weighted median." | |
| 3284 | + }, | |
| 3285 | + { | |
| 3286 | + "title": "Fusion and confidence", | |
| 3287 | + "description": "The final estimate blends 65% hedonic model with 35% comparables; comparable count, dispersion, and range width determine the A–D confidence grade." | |
| 3288 | + }, | |
| 3289 | + { | |
| 3290 | + "title": "Application layer", | |
| 3291 | + "description": "Next.js 16 serves search, estimation, portfolio, and report APIs over a SQLite base with FTS5, spatial indexes, and a smoothed monthly market index." | |
| 3292 | + } | |
| 3293 | + ], | |
| 3294 | + "highlights": [ | |
| 3295 | + "Validated to the IAAO ratio-study standard: 0.995 median ratio on 102,943 held-out sales, plus a strict temporal test on 68,364 sales from 2026.", | |
| 3296 | + "Condos hit a COD of 11.9 — inside the IAAO's toughest ≤15 target band.", | |
| 3297 | + "Computes the total value of Quebec's residential stock — $2.01 trillion — with a ranking of the top 200 municipalities.", | |
| 3298 | + "Every dollar adjustment is shown: market conditions from a 3-month-smoothed $/m² index, size at 50% of the comparable's $/m² capped at ±25%, age at 0.5%/year capped at ±10%.", | |
| 3299 | + "All maps and property portraits are pure SVG drawn from registry data — no tiles, no external map services.", | |
| 3300 | + "Built entirely on open data: Quebec's MAMH property assessment rolls under an open license, with owner information redacted at the source." | |
| 3301 | + ] | |
| 3302 | + }, | |
| 3303 | + { | |
| 3304 | + "slug": "valoplex", | |
| 3305 | + "hero": { | |
| 3306 | + "headline": "The true value of your plex, door by door", | |
| 3307 | + "subheadline": "A valuation engine built specifically for Quebec's income properties — 393,867 multi-unit buildings, 1.7 million doors, one scale-proof ratio hedonic model." | |
| 3308 | + }, | |
| 3309 | + "overview": [ | |
| 3310 | + "A plex is an income property: its value is reasoned per door, per revenue, per cap rate — not like a single-family home. ValoPlex covers every residential multi-unit building in Quebec, from duplexes to towers, with a model and interface dedicated to that logic, including per-door economics benchmarked against the local market.", | |
| 3311 | + "The model targets log(price / assessed value) rather than raw price. A raw-price model dominated by the mass of duplexes crushes large buildings — one tower assessed at $705M was predicted at $5.6M. In ratio space, a $400K duplex and a $700M tower are valued with the same relative accuracy. Beyond 12 doors, estimates shrink toward empirical size-band anchors, and P10–P90 ranges use Mondrian conformal calibration per door band, with a measured — not promised — 80.0% coverage.", | |
| 3312 | + "The interactive investor pro forma inverts the income approach: it derives the closed-form implicit rent the estimated value assumes, then builds a full operating statement, Canadian semi-annual mortgage, DSCR, cashflow per door, 5-year projection, break-even rent and rate, and interest-rate sensitivity — all recalculated live from five sliders." | |
| 3313 | + ], | |
| 3314 | + "stats": [ | |
| 3315 | + { | |
| 3316 | + "value": "393,867", | |
| 3317 | + "label": "plex buildings covered" | |
| 3318 | + }, | |
| 3319 | + { | |
| 3320 | + "value": "1,733,744", | |
| 3321 | + "label": "doors" | |
| 3322 | + }, | |
| 3323 | + { | |
| 3324 | + "value": "91,060", | |
| 3325 | + "label": "plex sales" | |
| 3326 | + }, | |
| 3327 | + { | |
| 3328 | + "value": "13.8%", | |
| 3329 | + "label": "median error (MdAPE)" | |
| 3330 | + }, | |
| 3331 | + { | |
| 3332 | + "value": "80.0%", | |
| 3333 | + "label": "measured range coverage" | |
| 3334 | + }, | |
| 3335 | + { | |
| 3336 | + "value": "$400.6B", | |
| 3337 | + "label": "total Quebec plex value" | |
| 3338 | + } | |
| 3339 | + ], | |
| 3340 | + "features": [ | |
| 3341 | + { | |
| 3342 | + "icon": "DollarSign", | |
| 3343 | + "title": "Per-door economics", | |
| 3344 | + "description": "Value per door, assessment per door, area per door, and land per door — each benchmarked against the local market." | |
| 3345 | + }, | |
| 3346 | + { | |
| 3347 | + "icon": "Calculator", | |
| 3348 | + "title": "Interactive investor pro forma", | |
| 3349 | + "description": "Implicit rent, full operating statement, Canadian mortgage, DSCR, cashflow per door, 5-year projection, break-even, and rate sensitivity — five sliders, live recalculation." | |
| 3350 | + }, | |
| 3351 | + { | |
| 3352 | + "icon": "Coins", | |
| 3353 | + "title": "Full closing costs", | |
| 3354 | + "description": "Transfer duties under both the provincial scale and Montreal's enhanced scale (up to 4%), plus notary, inspection, and total cash-to-close." | |
| 3355 | + }, | |
| 3356 | + { | |
| 3357 | + "icon": "Building2", | |
| 3358 | + "title": "Signature doors frieze", | |
| 3359 | + "description": "One SVG door drawn per unit, with a DUPLEX/TRIPLEX/…/MULTI-N-DOORS badge — the visual signature of every property page." | |
| 3360 | + }, | |
| 3361 | + { | |
| 3362 | + "icon": "Scale", | |
| 3363 | + "title": "Scale-proof ratio model", | |
| 3364 | + "description": "Targets log(price/assessed value) so duplexes and towers are valued with equal relative accuracy, informed by roll-vintage age in months." | |
| 3365 | + }, | |
| 3366 | + { | |
| 3367 | + "icon": "Gauge", | |
| 3368 | + "title": "Mondrian conformal calibration", | |
| 3369 | + "description": "P10–P90 ranges calibrated per door band on a dedicated calibration set — 80.0% coverage measured, not promised." | |
| 3370 | + }, | |
| 3371 | + { | |
| 3372 | + "icon": "Map", | |
| 3373 | + "title": "Honest comparables map", | |
| 3374 | + "description": "A surveyor-plan SVG map with real azimuths and distances, plus a fourth per-door adjustment (50% of price/door, capped ±30%) — and an honest empty state when no comparable plex exists." | |
| 3375 | + }, | |
| 3376 | + { | |
| 3377 | + "icon": "FileText", | |
| 3378 | + "title": "PDF report suite", | |
| 3379 | + "description": "Standard 3-page report with pro forma, 6-page bank-grade professional report, portfolio report, and a provincial statistical report." | |
| 3380 | + }, | |
| 3381 | + { | |
| 3382 | + "icon": "BarChart3", | |
| 3383 | + "title": "Provincial plex statistics", | |
| 3384 | + "description": "Quebec's plexes are worth $400.6B — broken down by building size band and by municipality." | |
| 3385 | + } | |
| 3386 | + ], | |
| 3387 | + "techStack": [ | |
| 3388 | + { | |
| 3389 | + "category": "Application", | |
| 3390 | + "items": [ | |
| 3391 | + "Next.js 16 (App Router)", | |
| 3392 | + "TypeScript strict", | |
| 3393 | + "Tailwind CSS 4" | |
| 3394 | + ] | |
| 3395 | + }, | |
| 3396 | + { | |
| 3397 | + "category": "Data", | |
| 3398 | + "items": [ | |
| 3399 | + "SQLite", | |
| 3400 | + "MAMH assessment rolls", | |
| 3401 | + "Enriched transactions" | |
| 3402 | + ] | |
| 3403 | + }, | |
| 3404 | + { | |
| 3405 | + "category": "Model pipeline", | |
| 3406 | + "items": [ | |
| 3407 | + "Python", | |
| 3408 | + "Ratio hedonic model", | |
| 3409 | + "Mondrian conformal calibration" | |
| 3410 | + ] | |
| 3411 | + }, | |
| 3412 | + { | |
| 3413 | + "category": "Reports & testing", | |
| 3414 | + "items": [ | |
| 3415 | + "pdfkit", | |
| 3416 | + "Vitest (33 tests)" | |
| 3417 | + ] | |
| 3418 | + } | |
| 3419 | + ], | |
| 3420 | + "architecture": [ | |
| 3421 | + { | |
| 3422 | + "title": "Ratio hedonic model", | |
| 3423 | + "description": "Trains on log(price / assessed value) with roll-age awareness, producing predictions for 2021–2026 that stay accurate from duplexes to towers." | |
| 3424 | + }, | |
| 3425 | + { | |
| 3426 | + "title": "Out-of-domain shrinkage", | |
| 3427 | + "description": "Beyond 12 doors, where sales are rare, the model weight slides from 55% to 20% toward the empirical anchor of the building's size band." | |
| 3428 | + }, | |
| 3429 | + { | |
| 3430 | + "title": "Conformal calibration", | |
| 3431 | + "description": "P10–P90 ranges are calibrated per door band (Mondrian) on a dedicated set, achieving verified 80.0% coverage." | |
| 3432 | + }, | |
| 3433 | + { | |
| 3434 | + "title": "Comparables engine", | |
| 3435 | + "description": "Adds a fourth plex-specific adjustment — 50% of the comparable's price per door, capped at ±30% — on top of market, size, and age adjustments." | |
| 3436 | + }, | |
| 3437 | + { | |
| 3438 | + "title": "Pro forma library", | |
| 3439 | + "description": "A 20-test finance module derives closed-form implicit rent, Canadian semi-annual mortgage math, DSCR, break-even by bisection, and 5-year equity projections." | |
| 3440 | + }, | |
| 3441 | + { | |
| 3442 | + "title": "Application layer", | |
| 3443 | + "description": "Next.js 16 app over a SQLite base built by the Python pipeline, serving valuations, pro formas, statistics, and pdfkit reports." | |
| 3444 | + } | |
| 3445 | + ], | |
| 3446 | + "highlights": [ | |
| 3447 | + "The ratio-target insight came from a measured failure: a raw-price model predicted a $705M-assessed tower at just $5.6M.", | |
| 3448 | + "Validated on 13,617 never-seen sales to the IAAO standard — 13.8% MdAPE overall, 12.6% on triplexes — plus a temporal test on 9,172 sales from 2026.", | |
| 3449 | + "80.0% range coverage is measured on a dedicated calibration set, not assumed — Mondrian conformal calibration per door band.", | |
| 3450 | + "The pro forma inverts the income approach: closed-form implicit rent RB = (V·TGA + F) / (1 − vacancy − %variables), then a full investor statement.", | |
| 3451 | + "Computes that all of Quebec's plexes are worth $400.6 billion, broken down by size band and municipality.", | |
| 3452 | + "Companion product to Vrai-Prix — same editorial-sharp design language, orange accent instead of red, 33/33 tests passing." | |
| 3453 | + ] | |
| 3454 | + }, | |
| 3455 | + { | |
| 3456 | + "slug": "qwhpi-platform", | |
| 3457 | + "hero": { | |
| 3458 | + "headline": "Measuring Quebec home prices, not composition", | |
| 3459 | + "subheadline": "A production-grade index platform that separates true price movement from sales-mix shifts across 81 geography-by-type segments, with uncertainty published on every observation." | |
| 3460 | + }, | |
| 3461 | + "overview": [ | |
| 3462 | + "QHPI is an economic-measurement platform, not a median-price tracker: a week where only mansions sell must not register as a price increase. A robust hedonic time-dummy model with hierarchical pooling estimates quality-adjusted indexes for the province, 17 administrative regions, and major municipalities, each split by property type — unifamilial, condo, plex, and composite.", | |
| 3463 | + "Three estimation regimes are arbitrated by empirical validation. A rolling time-dummy backbone with Huber-IRLS weighting and mean splicing makes published history revision-free by construction. The 50 liquid cells get their own local regressions; the 31 thin cells follow a Kalman-filtered deviation from their parent's path, with the shrinkage weight published on every row.", | |
| 3464 | + "Uncertainty is never hidden: every observation carries a 95% confidence interval, an A–E reliability grade, effective sample size, and vintage stamp. The full stack ships in one repo — Python engine, PostgreSQL store, FastAPI service, Next.js dashboard, LaTeX methodology paper — while distributing zero individual transaction records." | |
| 3465 | + ], | |
| 3466 | + "stats": [ | |
| 3467 | + { | |
| 3468 | + "value": "~745k", | |
| 3469 | + "label": "raw transactions ingested" | |
| 3470 | + }, | |
| 3471 | + { | |
| 3472 | + "value": "81", | |
| 3473 | + "label": "published index cells" | |
| 3474 | + }, | |
| 3475 | + { | |
| 3476 | + "value": "17", | |
| 3477 | + "label": "administrative regions" | |
| 3478 | + }, | |
| 3479 | + { | |
| 3480 | + "value": "1,100+", | |
| 3481 | + "label": "municipalities covered" | |
| 3482 | + }, | |
| 3483 | + { | |
| 3484 | + "value": "112", | |
| 3485 | + "label": "series in the dashboard" | |
| 3486 | + }, | |
| 3487 | + { | |
| 3488 | + "value": "2021→now", | |
| 3489 | + "label": "coverage period" | |
| 3490 | + } | |
| 3491 | + ], | |
| 3492 | + "features": [ | |
| 3493 | + { | |
| 3494 | + "icon": "LineChart", | |
| 3495 | + "title": "Hedonic, not median", | |
| 3496 | + "description": "In a composition-shock simulation the raw median jumps +11.7% while the hedonic index moves −0.7% — the index measures prices, not sales mix." | |
| 3497 | + }, | |
| 3498 | + { | |
| 3499 | + "icon": "GitBranch", | |
| 3500 | + "title": "Three arbitrated estimation regimes", | |
| 3501 | + "description": "Rolling time-dummy backbone, direct local regressions for 50 liquid cells, and hierarchical Kalman shrinkage for 31 thin cells — chosen by validation, not convenience." | |
| 3502 | + }, | |
| 3503 | + { | |
| 3504 | + "icon": "ShieldCheck", | |
| 3505 | + "title": "Uncertainty on every row", | |
| 3506 | + "description": "95% confidence intervals, A–E reliability grades, effective sample size, and shrinkage weight are published with every single observation." | |
| 3507 | + }, | |
| 3508 | + { | |
| 3509 | + "icon": "CheckCircle2", | |
| 3510 | + "title": "Revision-free published history", | |
| 3511 | + "description": "13-month rolling windows combined by mean splice make the published index history revision-free by construction; first releases and revisions remain queryable." | |
| 3512 | + }, | |
| 3513 | + { | |
| 3514 | + "icon": "Microscope", | |
| 3515 | + "title": "Benchmarked against repeat sales", | |
| 3516 | + "description": "Validated against a ~85k-address repeat-sales benchmark — Quebec condos land at 132.0 vs 132, Quebec City condos at 163.5 vs 162." | |
| 3517 | + }, | |
| 3518 | + { | |
| 3519 | + "icon": "Database", | |
| 3520 | + "title": "No microdata distributed", | |
| 3521 | + "description": "The repo ships only cell-level aggregates with transaction counts; individual sale records never enter version control, enforced at the .gitignore boundary." | |
| 3522 | + }, | |
| 3523 | + { | |
| 3524 | + "icon": "Server", | |
| 3525 | + "title": "Full-stack index service", | |
| 3526 | + "description": "FastAPI exposes every series with CIs, vintages, liquidity, comparisons, choropleth payloads, stats, and publication-grade PDF reports." | |
| 3527 | + }, | |
| 3528 | + { | |
| 3529 | + "icon": "BarChart3", | |
| 3530 | + "title": "Interactive dashboard", | |
| 3531 | + "description": "Next.js frontend with a ⌘K palette over 112 series, CI bands, brush zoom, Bank-of-Canada event annotations, choropleth time-lapse, and PNG/CSV export." | |
| 3532 | + }, | |
| 3533 | + { | |
| 3534 | + "icon": "BookOpen", | |
| 3535 | + "title": "Methodology paper included", | |
| 3536 | + "description": "A LaTeX paper — 'A High-Frequency Hedonic Housing Price Index for Quebec' — documents the methodology with researched citations from index-number theory." | |
| 3537 | + } | |
| 3538 | + ], | |
| 3539 | + "techStack": [ | |
| 3540 | + { | |
| 3541 | + "category": "Index engine", | |
| 3542 | + "items": [ | |
| 3543 | + "Python 3.11+", | |
| 3544 | + "pandas/polars", | |
| 3545 | + "numpy", | |
| 3546 | + "Huber-IRLS", | |
| 3547 | + "Kalman state-space" | |
| 3548 | + ] | |
| 3549 | + }, | |
| 3550 | + { | |
| 3551 | + "category": "Data store", | |
| 3552 | + "items": [ | |
| 3553 | + "Parquet lake", | |
| 3554 | + "PostgreSQL", | |
| 3555 | + "Alembic migrations" | |
| 3556 | + ] | |
| 3557 | + }, | |
| 3558 | + { | |
| 3559 | + "category": "API", | |
| 3560 | + "items": [ | |
| 3561 | + "FastAPI", | |
| 3562 | + "ETag caching", | |
| 3563 | + "CSV/JSON export", | |
| 3564 | + "Rate limiting" | |
| 3565 | + ] | |
| 3566 | + }, | |
| 3567 | + { | |
| 3568 | + "category": "Dashboard", | |
| 3569 | + "items": [ | |
| 3570 | + "Next.js", | |
| 3571 | + "TypeScript", | |
| 3572 | + "Custom zero-dependency charts" | |
| 3573 | + ] | |
| 3574 | + }, | |
| 3575 | + { | |
| 3576 | + "category": "Ops & research", | |
| 3577 | + "items": [ | |
| 3578 | + "docker-compose", | |
| 3579 | + "CI with header gate", | |
| 3580 | + "LaTeX paper", | |
| 3581 | + "matplotlib PDF reports" | |
| 3582 | + ] | |
| 3583 | + } | |
| 3584 | + ], | |
| 3585 | + "architecture": [ | |
| 3586 | + { | |
| 3587 | + "title": "Engine pipeline", | |
| 3588 | + "description": "Eleven ordered, idempotent scripts run profile → spatial join → documented cleaning → estimation → validation → canonical Parquet, each emitting a run manifest with input hashes." | |
| 3589 | + }, | |
| 3590 | + { | |
| 3591 | + "title": "Geography layer", | |
| 3592 | + "description": "Transactions are spatially joined by lat/lng against authoritative Quebec SDA cadastral boundaries — the free-text city field alone is never trusted." | |
| 3593 | + }, | |
| 3594 | + { | |
| 3595 | + "title": "Estimation core", | |
| 3596 | + "description": "Robust hedonic time-dummy regressions over 13-month rolling windows with Huber-IRLS, mean-spliced; buildingType is banned after its staggered backfill fabricated a −38 log-point cliff." | |
| 3597 | + }, | |
| 3598 | + { | |
| 3599 | + "title": "Hierarchical pooling", | |
| 3600 | + "description": "Liquid cells get direct local regressions with their own coefficients; thin cells follow Kalman-smoothed deviations from their parent path with published shrinkage weights." | |
| 3601 | + }, | |
| 3602 | + { | |
| 3603 | + "title": "Serving layer", | |
| 3604 | + "description": "The canonical monthly Parquet (7,504 rows) loads into PostgreSQL behind a FastAPI service; a weekly scheduler ingests new rows, re-estimates, and stamps a new data vintage." | |
| 3605 | + }, | |
| 3606 | + { | |
| 3607 | + "title": "Dashboard", | |
| 3608 | + "description": "The Next.js frontend at www.indexqc.house offers explore, compare, choropleth map with time-lapse, methodology, and an in-page API playground." | |
| 3609 | + } | |
| 3610 | + ], | |
| 3611 | + "highlights": [ | |
| 3612 | + "The composition-shock test is the thesis in one number: mix shock moves the raw median +11.7%, the hedonic index −0.7%.", | |
| 3613 | + "buildingType is banned from all hedonic models — its region-staggered backfill fabricated a −38 log-point cliff in Montreal indexes, and the ban is documented in code.", | |
| 3614 | + "Direct local estimation was chosen because pooled deviations compressed real divergence: Quebec City condos moved ~+55%, confirmed independently by repeat sales.", | |
| 3615 | + "A–E reliability tiers are justified empirically by a downsampling experiment thinning Montreal condos to as few as 5 transactions per period.", | |
| 3616 | + "Strict data policy: zero transaction microdata in the repo — only cell-level aggregates — yet the API and dashboard run fully from the included Parquet.", | |
| 3617 | + "Every file carries an author header enforced by a pre-commit hook and CI gate; runs are deterministic and stamped with model version and data vintage." | |
| 3618 | + ] | |
| 3619 | + }, | |
| 3620 | + { | |
| 3621 | + "slug": "spboucher-ai", | |
| 3622 | + "hero": { | |
| 3623 | + "headline": "Where research meets shipped software", | |
| 3624 | + "subheadline": "The personal site of Simon-Pierre Boucher — financial econometrics researcher and macOS/AI developer — presenting his research, teaching, and the six-app Zyquo suite." | |
| 3625 | + }, | |
| 3626 | + "overview": [ | |
| 3627 | + "spboucher.ai is the academic and developer home of Simon-Pierre Boucher, Professor in the Department of Administrative Sciences at Université du Québec en Outaouais. The site presents his research in financial econometrics — commodity markets, monetary policy announcements, high-frequency finance, volatility modelling — alongside his teaching record and a portfolio of native macOS applications.", | |
| 3628 | + "Built on Next.js 16 with the App Router, the site uses server components everywhere with static prerendering on every page. Tailwind CSS v4 and shadcn/ui-style components deliver a clean academic aesthetic with a developer edge; Framer Motion adds subtle scroll reveals that respect prefers-reduced-motion and degrade gracefully without JavaScript.", | |
| 3629 | + "The site is self-hosted with character: it runs on a Mac Studio node of the author's personal cluster under PM2 process management, tunneled to the world through ngrok on the custom domain — production build only, with a JSON health endpoint for uptime monitoring." | |
| 3630 | + ], | |
| 3631 | + "stats": [ | |
| 3632 | + { | |
| 3633 | + "value": "6", | |
| 3634 | + "label": "Zyquo macOS apps showcased" | |
| 3635 | + }, | |
| 3636 | + { | |
| 3637 | + "value": "5", | |
| 3638 | + "label": "content sections" | |
| 3639 | + }, | |
| 3640 | + { | |
| 3641 | + "value": "16", | |
| 3642 | + "label": "Next.js version" | |
| 3643 | + }, | |
| 3644 | + { | |
| 3645 | + "value": "100%", | |
| 3646 | + "label": "static prerendering" | |
| 3647 | + } | |
| 3648 | + ], | |
| 3649 | + "features": [ | |
| 3650 | + { | |
| 3651 | + "icon": "Zap", | |
| 3652 | + "title": "Server components everywhere", | |
| 3653 | + "description": "Next.js 16 App Router with static prerendering on every page; client components only where interactivity genuinely requires them." | |
| 3654 | + }, | |
| 3655 | + { | |
| 3656 | + "icon": "Palette", | |
| 3657 | + "title": "System-aware dark mode", | |
| 3658 | + "description": "Dark mode follows the system with a manual toggle and no flash on load, themed through Tailwind v4 design tokens." | |
| 3659 | + }, | |
| 3660 | + { | |
| 3661 | + "icon": "Sparkles", | |
| 3662 | + "title": "Motion with restraint", | |
| 3663 | + "description": "Framer Motion scroll reveals honor prefers-reduced-motion and fall back cleanly when JavaScript is disabled." | |
| 3664 | + }, | |
| 3665 | + { | |
| 3666 | + "icon": "Package", | |
| 3667 | + "title": "Zyquo app portfolio", | |
| 3668 | + "description": "Six native macOS AI apps — Cloud, Local, Agent, Atlas, MLX, Router — each with icon, description, and Repo/Release/DMG download links." | |
| 3669 | + }, | |
| 3670 | + { | |
| 3671 | + "icon": "GraduationCap", | |
| 3672 | + "title": "Full academic profile", | |
| 3673 | + "description": "Research publications, working papers, conference presentations, and courses taught at Université Laval, in dedicated sections." | |
| 3674 | + }, | |
| 3675 | + { | |
| 3676 | + "icon": "CheckCircle2", | |
| 3677 | + "title": "Accessible by design", | |
| 3678 | + "description": "Semantic HTML, alt text on all app icons, keyboard-friendly navigation, and a mobile-first responsive 1/2/3-column grid." | |
| 3679 | + }, | |
| 3680 | + { | |
| 3681 | + "icon": "Server", | |
| 3682 | + "title": "Self-hosted on a Mac", | |
| 3683 | + "description": "Runs on a personal cluster node under PM2 with auto-restart, exposed through an ngrok tunnel bound to the custom domain." | |
| 3684 | + }, | |
| 3685 | + { | |
| 3686 | + "icon": "Gauge", | |
| 3687 | + "title": "Health monitoring", | |
| 3688 | + "description": "A dedicated GET /api/health JSON endpoint enables uptime monitoring of the production deployment." | |
| 3689 | + } | |
| 3690 | + ], | |
| 3691 | + "techStack": [ | |
| 3692 | + { | |
| 3693 | + "category": "Framework", | |
| 3694 | + "items": [ | |
| 3695 | + "Next.js 16 (App Router)", | |
| 3696 | + "React 19", | |
| 3697 | + "TypeScript 5" | |
| 3698 | + ] | |
| 3699 | + }, | |
| 3700 | + { | |
| 3701 | + "category": "UI", | |
| 3702 | + "items": [ | |
| 3703 | + "Tailwind CSS 4", | |
| 3704 | + "shadcn/ui-style components", | |
| 3705 | + "Framer Motion 12" | |
| 3706 | + ] | |
| 3707 | + }, | |
| 3708 | + { | |
| 3709 | + "category": "Operations", | |
| 3710 | + "items": [ | |
| 3711 | + "PM2", | |
| 3712 | + "ngrok", | |
| 3713 | + "Health endpoint" | |
| 3714 | + ] | |
| 3715 | + } | |
| 3716 | + ], | |
| 3717 | + "architecture": [ | |
| 3718 | + { | |
| 3719 | + "title": "App Router pages", | |
| 3720 | + "description": "Five routes — home, research, teaching, apps, CV — rendered as server components with static prerendering, plus a JSON health API route." | |
| 3721 | + }, | |
| 3722 | + { | |
| 3723 | + "title": "Single source of truth for apps", | |
| 3724 | + "description": "The Zyquo suite data lives in one lib/apps.ts module that drives the app cards — icons, descriptions, and the three canonical links per app." | |
| 3725 | + }, | |
| 3726 | + { | |
| 3727 | + "title": "Production deployment", | |
| 3728 | + "description": "The production build runs under PM2 on cluster node m2u64, port 3100, with a second PM2 process holding the ngrok tunnel to www.spboucher.ai." | |
| 3729 | + } | |
| 3730 | + ], | |
| 3731 | + "highlights": [ | |
| 3732 | + "Served from a Mac, tunneled to the world: the site runs on the author's personal Apple Silicon cluster, not a commercial cloud.", | |
| 3733 | + "Showcases the Zyquo suite — six native Swift/SwiftUI macOS apps for AI and local LLMs, each with direct DMG downloads.", | |
| 3734 | + "Every source file carries a mandatory author header, enforced as a project convention.", | |
| 3735 | + "Framer Motion animations degrade gracefully: prefers-reduced-motion respected and no-JS fallbacks in place.", | |
| 3736 | + "Both processes — app and tunnel — run under PM2 with save/restore, making the deployment survive reboots." | |
| 3737 | + ] | |
| 3738 | + } | |
| 3739 | +]; | |
added
lib/research-details.ts
+1051 −0
@@ -0,0 +1,1051 @@ | ||
| 1 | +/* | |
| 2 | + research-details.ts | |
| 3 | + spboucher.ai Web | |
| 4 | + Author: Simon-Pierre Boucher | |
| 5 | + Mail: contact@spboucher.ai | |
| 6 | +*/ | |
| 7 | + | |
| 8 | +/** Rich per-paper content for the /research/[slug] detail pages. */ | |
| 9 | +export interface ResearchDetail { | |
| 10 | + slug: string; | |
| 11 | + repo: string; | |
| 12 | + hero: { headline: string; subheadline: string }; | |
| 13 | + abstract: string[]; | |
| 14 | + findings: { value: string; label: string }[]; | |
| 15 | + contributions: { title: string; description: string }[]; | |
| 16 | + data: { title: string; description: string }[]; | |
| 17 | + methodology: { title: string; description: string }[]; | |
| 18 | + reproducibility: string[]; | |
| 19 | + keywords: string[]; | |
| 20 | +} | |
| 21 | + | |
| 22 | +export const researchDetails: ResearchDetail[] = [ | |
| 23 | + { | |
| 24 | + "slug": "wp2", | |
| 25 | + "repo": "https://github.com/spboucher-ai/wp2_uqo", | |
| 26 | + "hero": { | |
| 27 | + "headline": "Listing narratives carry named, priceable information", | |
| 28 | + "subheadline": "Do agent-written property descriptions contain price-relevant information beyond structural attributes? Yes — 20 interpretable semantic scores lift adjusted R² from 0.452 to 0.511." | |
| 29 | + }, | |
| 30 | + "abstract": [ | |
| 31 | + "Hedonic pricing models decompose a property's price into the implicit prices of structured characteristics — bedrooms, bathrooms, lot size — but the narrative of a listing carries quality information that no structured field captures. This paper embeds the free-text descriptions of 17,087 Quebec single-family house listings with a sentence transformer and projects each embedding onto 20 researcher-defined semantic reference descriptions (Luxury, Needs Renovation, Waterfront, Motivated Seller, and others) via cosine similarity.", | |
| 32 | + "Adding the 20 named similarity scores to a log-price hedonic OLS model raises adjusted R² from 0.452 to 0.511, with the text block jointly significant (F = 99.53, p < 0.001). Each dimension carries a signed, economically meaningful implicit price: sounding Modern/Contemporary is worth +16.4% per standard deviation, Luxury +14.2%, while Motivated Seller and Needs Renovation carry discounts of 8.5% and 7.9%.", | |
| 33 | + "The approach resolves the depth-versus-interpretability trade-off in text-based hedonics: unlike raw 384-dimensional embeddings or unstable LDA topics, every coefficient reads directly as the implicit price of a human-named concept. Quantile regressions show the Luxury premium rising monotonically from 9.7% at the 25th price percentile to 17.3% at the 75th, and a PCA benchmark explicitly quantifies the fit sacrificed for interpretability." | |
| 34 | + ], | |
| 35 | + "findings": [ | |
| 36 | + { | |
| 37 | + "value": "0.452 → 0.511", | |
| 38 | + "label": "adjusted R² gain from adding 20 semantic dimensions" | |
| 39 | + }, | |
| 40 | + { | |
| 41 | + "value": "17,087", | |
| 42 | + "label": "Quebec single-family house listings analyzed" | |
| 43 | + }, | |
| 44 | + { | |
| 45 | + "value": "F = 99.53", | |
| 46 | + "label": "joint significance of the 21 text variables (p < 0.001)" | |
| 47 | + }, | |
| 48 | + { | |
| 49 | + "value": "+16.4%", | |
| 50 | + "label": "Modern/Contemporary implicit price per +1 SD" | |
| 51 | + }, | |
| 52 | + { | |
| 53 | + "value": "+14.2%", | |
| 54 | + "label": "Luxury premium per +1 SD (9.7% → 17.3% across price quantiles)" | |
| 55 | + }, | |
| 56 | + { | |
| 57 | + "value": "9.3%", | |
| 58 | + "label": "share of Model D's explained variance attributable to semantics" | |
| 59 | + } | |
| 60 | + ], | |
| 61 | + "contributions": [ | |
| 62 | + { | |
| 63 | + "title": "Reference-based cosine projection method", | |
| 64 | + "description": "Instead of 384 anonymous embedding dimensions, listings are projected onto 20 researcher-defined reference descriptions, yielding named, interpretable regressors — deep semantics with full economic readability." | |
| 65 | + }, | |
| 66 | + { | |
| 67 | + "title": "Named implicit prices for narrative content", | |
| 68 | + "description": "Each semantic dimension carries a signed, economically meaningful coefficient: Modern/Contemporary +16.4%, Luxury +14.2%, Land & Nature +13.0%, Motivated Seller −8.5%, Needs Renovation −7.9% per standard deviation." | |
| 69 | + }, | |
| 70 | + { | |
| 71 | + "title": "Interpretability-versus-fit trade-off quantified", | |
| 72 | + "description": "A PCA benchmark on raw embeddings fits better (ΔR² +0.070 vs +0.044) but is economically unreadable; the paper measures exactly what interpretability costs." | |
| 73 | + }, | |
| 74 | + { | |
| 75 | + "title": "Heterogeneity across the price distribution", | |
| 76 | + "description": "Quantile regressions show the Luxury premium rising monotonically from 9.7% at the 25th percentile to 17.3% at the 75th, while urgency and condition discounts attenuate at the top." | |
| 77 | + }, | |
| 78 | + { | |
| 79 | + "title": "Full robustness battery with disclosed limits", | |
| 80 | + "description": "Bootstrap (1,000 replications), outlier trimming, Lasso/Elastic-Net selection, and VIF analysis; multicollinearity among similarity dimensions (mean VIF 17.2) is disclosed and its inferential consequences stated." | |
| 81 | + }, | |
| 82 | + { | |
| 83 | + "title": "Fully machine-generated results", | |
| 84 | + "description": "Every number in the paper's statistical tables is written by the pipeline from results CSVs — hand-transcription errors in the original project were caught and corrected." | |
| 85 | + } | |
| 86 | + ], | |
| 87 | + "data": [ | |
| 88 | + { | |
| 89 | + "title": "Quebec residential listings database (louka.db)", | |
| 90 | + "description": "268 MB SQLite database of 46,479 Realtor.ca/Centris listings across five categories, each with structured fields plus the agent-written free-text description." | |
| 91 | + }, | |
| 92 | + { | |
| 93 | + "title": "Single-family house analysis sample", | |
| 94 | + "description": "17,087 houses with positive price and description of at least 20 characters; median listing price $589,900, descriptions averaging 510 characters." | |
| 95 | + }, | |
| 96 | + { | |
| 97 | + "title": "20 semantic reference descriptions", | |
| 98 | + "description": "Synthetic French paragraphs (146–257 characters each) spanning six domains — quality, condition, physical features, location, style, and market signals — reproduced verbatim in the paper's appendix." | |
| 99 | + } | |
| 100 | + ], | |
| 101 | + "methodology": [ | |
| 102 | + { | |
| 103 | + "title": "Sentence-transformer embeddings", | |
| 104 | + "description": "All 17,087 descriptions encoded with all-MiniLM-L6-v2 (22.7M parameters, 384 dimensions, L2-normalized); the whole corpus embeds in seconds on Apple Silicon." | |
| 105 | + }, | |
| 106 | + { | |
| 107 | + "title": "Cosine-similarity projection", | |
| 108 | + "description": "A single matrix product against the 20 embedded reference descriptions yields a 17,087 × 20 similarity matrix of named, interpretable semantic scores." | |
| 109 | + }, | |
| 110 | + { | |
| 111 | + "title": "Nested hedonic OLS (Models A–E)", | |
| 112 | + "description": "Log-price regressions from structural-only to the full specification, all covariates standardized, HC3 robust errors, joint F-tests, and a parsimonious 16-dimension variant." | |
| 113 | + }, | |
| 114 | + { | |
| 115 | + "title": "Quantile regression", | |
| 116 | + "description": "Estimates at the 25th, 50th, and 75th price percentiles reveal how semantic premia and discounts vary across the market's price distribution." | |
| 117 | + }, | |
| 118 | + { | |
| 119 | + "title": "Robustness suite", | |
| 120 | + "description": "Bootstrap standard errors (1,000 replications, seeded), winsorization at the 1st/99th percentiles, VIF diagnostics, Breusch–Pagan tests, and Lasso/Elastic-Net variable selection." | |
| 121 | + }, | |
| 122 | + { | |
| 123 | + "title": "PCA benchmark", | |
| 124 | + "description": "20 principal components of the raw embeddings serve as an upper-bound fit comparison, quantifying the cost of interpretability against anonymous dimensions." | |
| 125 | + } | |
| 126 | + ], | |
| 127 | + "reproducibility": [ | |
| 128 | + "End-to-end Python pipeline: six numbered scripts take the raw SQLite database to figures, results CSVs, and machine-generated LaTeX tables.", | |
| 129 | + "All stochastic steps seeded (seed 42); embeddings are deterministic and cached; validated bit-identical against the original project's similarity matrix and model coefficients.", | |
| 130 | + "All 11 figures regenerated from the pipeline as vector PDF and 300-dpi PNG; the 53-page LaTeX paper compiles with zero errors and zero undefined references.", | |
| 131 | + "86 bibliography entries, every one verified against OpenAlex and DOI records.", | |
| 132 | + "AUDIT.md and CHANGES.md document a forensic audit of the original project, including every numeric correction with old and new values." | |
| 133 | + ], | |
| 134 | + "keywords": [ | |
| 135 | + "hedonic pricing", | |
| 136 | + "semantic embeddings", | |
| 137 | + "textual analysis", | |
| 138 | + "real estate", | |
| 139 | + "sentence transformers", | |
| 140 | + "interpretable machine learning", | |
| 141 | + "Quebec housing", | |
| 142 | + "implicit prices" | |
| 143 | + ] | |
| 144 | + }, | |
| 145 | + { | |
| 146 | + "slug": "wp3", | |
| 147 | + "repo": "https://github.com/spboucher-ai/wp3-hedonic-housing-us", | |
| 148 | + "hero": { | |
| 149 | + "headline": "Random splits flatter machine learning in housing", | |
| 150 | + "subheadline": "How well do hedonic methods really generalize? XGBoost's R² of 0.833 under a random split collapses to 0.425–0.547 when ten entire states are held out." | |
| 151 | + }, | |
| 152 | + "abstract": [ | |
| 153 | + "This paper compares three frameworks for hedonic housing valuation on a single dataset of 788,842 active Zillow listings spanning all 50 U.S. states and the District of Columbia: semi-log OLS with 62 regressors, quantile regression at five points of the price distribution, and gradient-boosting models interpreted with SHAP. Each framework answers a distinct question — average capitalization gradients, distributional heterogeneity, and predictive performance.", | |
| 154 | + "The core methodological contribution is a systematic study of spatial leakage in hedonic model evaluation. Random train/test splits let geographically proximate listings appear on both sides of the split, inflating performance: XGBoost reaches R² = 0.833 under a random 80/20 split but only 0.425–0.547 when ten entire states (438,315 listings) are held out. Removing all geographic features actually improves geographic-holdout performance, because region dummies memorize training-set price levels.", | |
| 155 | + "Mean effects also mask substantial heterogeneity: inter-quantile Wald tests reject coefficient equality for 11 of 13 variables, the garage gradient is ten times larger at the bottom decile than at the top, and the lot-size gradient triples once state-median-imputed values are dropped — a warning for hedonic work on scraped listing data. All estimates are presented as listing-price capitalization gradients, not causal willingness-to-pay parameters." | |
| 156 | + ], | |
| 157 | + "findings": [ | |
| 158 | + { | |
| 159 | + "value": "788,842", | |
| 160 | + "label": "Zillow listings across 50 states + DC" | |
| 161 | + }, | |
| 162 | + { | |
| 163 | + "value": "0.833 vs 0.425–0.547", | |
| 164 | + "label": "XGBoost R²: random split vs 10-state geographic holdout" | |
| 165 | + }, | |
| 166 | + { | |
| 167 | + "value": "11 of 13", | |
| 168 | + "label": "variables where inter-quantile Wald tests reject coefficient equality" | |
| 169 | + }, | |
| 170 | + { | |
| 171 | + "value": "10×", | |
| 172 | + "label": "garage gradient at τ = 0.10 relative to τ = 0.90 (z = 28.9)" | |
| 173 | + }, | |
| 174 | + { | |
| 175 | + "value": "0.2745", | |
| 176 | + "label": "mean Moran's I of OLS residuals (all p < 0.001)" | |
| 177 | + }, | |
| 178 | + { | |
| 179 | + "value": "0.630 → 0.725", | |
| 180 | + "label": "OLS out-of-sample R² from region dummies to 886 ZIP3 fixed effects" | |
| 181 | + } | |
| 182 | + ], | |
| 183 | + "contributions": [ | |
| 184 | + { | |
| 185 | + "title": "Spatial leakage in hedonic evaluation quantified", | |
| 186 | + "description": "Three complementary designs — a 10-state geographic holdout, a 6-stage feature-ablation cascade, and a lat/lon augmentation experiment — measure how much random validation inflates machine-learning performance." | |
| 187 | + }, | |
| 188 | + { | |
| 189 | + "title": "Geographic features can hurt generalization", | |
| 190 | + "description": "Removing all geographic features improves geographic-holdout R² from 0.425 to 0.519, while adding raw coordinates boosts random R² but degrades out-of-region prediction — region dummies memorize price levels." | |
| 191 | + }, | |
| 192 | + { | |
| 193 | + "title": "Spatial granularity dominates OLS fit", | |
| 194 | + "description": "Moving from 4 Census-region dummies to state fixed effects to 886 ZIP3 fixed effects raises out-of-sample R² from 0.630 to 0.678 to 0.725 — a 9.5 point gain from geography alone." | |
| 195 | + }, | |
| 196 | + { | |
| 197 | + "title": "Distributional heterogeneity in attribute gradients", | |
| 198 | + "description": "The garage gradient is 10× larger at the bottom price decile than at the top, the pool premium only emerges above the median, and the age penalty concentrates in lower-priced homes." | |
| 199 | + }, | |
| 200 | + { | |
| 201 | + "title": "SHAP rankings are model-stable, not structural", | |
| 202 | + "description": "Spearman correlations of mean |SHAP| across XGBoost, LightGBM, and Random Forest range from 0.89 to 0.99, with the same six features on top — but these remain predictive decompositions, not implicit prices." | |
| 203 | + }, | |
| 204 | + { | |
| 205 | + "title": "Imputation sensitivity exposed", | |
| 206 | + "description": "The lot-size gradient triples (0.018 to 0.059) when state-median-imputed observations are dropped — a caution for hedonic research built on scraped listing data." | |
| 207 | + } | |
| 208 | + ], | |
| 209 | + "data": [ | |
| 210 | + { | |
| 211 | + "title": "Zillow active for-sale listings (2025–2026 snapshot)", | |
| 212 | + "description": "839,313 raw residential properties with 116 variables in a 1.6 GB DuckDB database, including price history, schools, and tax-history tables." | |
| 213 | + }, | |
| 214 | + { | |
| 215 | + "title": "Analytical sample", | |
| 216 | + "description": "788,842 listings after five sequential filters (price $10K–$10M, living area 200–20,000 sqft, 1–10 beds/baths, valid coordinates; 6.0% attrition), with 68 engineered columns including six interaction terms." | |
| 217 | + } | |
| 218 | + ], | |
| 219 | + "methodology": [ | |
| 220 | + { | |
| 221 | + "title": "Semi-log OLS with HC3 errors", | |
| 222 | + "description": "62 regressors on log listing price with fixed effects escalating from Census region to state to 886 ZIP3 areas; price-to-area elasticity of 0.63." | |
| 223 | + }, | |
| 224 | + { | |
| 225 | + "title": "Quantile regression", | |
| 226 | + "description": "Estimated at τ = 0.10 to 0.90 on a 150,000-observation subsample with a 10-seed stability check and inter-quantile Wald z-tests on coefficient differences." | |
| 227 | + }, | |
| 228 | + { | |
| 229 | + "title": "Gradient boosting benchmark suite", | |
| 230 | + "description": "XGBoost (1,000 trees, depth 8), LightGBM, Random Forest, Ridge, Lasso, and Elastic Net, evaluated under both random 80/20 and 10-state geographic-holdout validation." | |
| 231 | + }, | |
| 232 | + { | |
| 233 | + "title": "TreeSHAP interpretation", | |
| 234 | + "description": "SHAP values on 10,000 test observations, with cross-model Spearman rank-stability analysis across XGBoost, LightGBM, and Random Forest." | |
| 235 | + }, | |
| 236 | + { | |
| 237 | + "title": "Spatial diagnostics", | |
| 238 | + "description": "Moran's I on OLS residuals with row-standardized 8-nearest-neighbor weights, three independent 5,000-listing subsamples, and 999 permutations." | |
| 239 | + }, | |
| 240 | + { | |
| 241 | + "title": "Feature-ablation cascade", | |
| 242 | + "description": "Six nested feature sets from structural-only to the full specification isolate each block's predictive contribution; neighborhood scores alone add 17.6 R² points." | |
| 243 | + } | |
| 244 | + ], | |
| 245 | + "reproducibility": [ | |
| 246 | + "Complete research compendium: analysis code, LaTeX source, 11 publication-ready figures, and 13 machine-readable CSV tables mirroring every table in the paper.", | |
| 247 | + "Every script prints verification statistics against the stored result artifacts; the Moran's I pipeline reproduces stored values to four decimal places.", | |
| 248 | + "A systematic audit reconciled the manuscript against stored artifacts, fixing six internal inconsistencies and filling 13 placeholder table cells — with no scientific result altered.", | |
| 249 | + "Paths resolve relative to the repository root (WP3_ROOT-overridable); pinned dependencies validated 2026-08-05; the 60-page paper compiles with zero errors.", | |
| 250 | + "Data artifacts (up to 1.6 GB) exceed GitHub limits and are available on request, subject to Zillow's Terms of Service." | |
| 251 | + ], | |
| 252 | + "keywords": [ | |
| 253 | + "hedonic pricing", | |
| 254 | + "housing markets", | |
| 255 | + "machine learning", | |
| 256 | + "quantile regression", | |
| 257 | + "spatial leakage", | |
| 258 | + "XGBoost", | |
| 259 | + "SHAP", | |
| 260 | + "United States" | |
| 261 | + ] | |
| 262 | + }, | |
| 263 | + { | |
| 264 | + "slug": "wp5", | |
| 265 | + "repo": "https://github.com/spboucher-ai/wp5_uqo", | |
| 266 | + "hero": { | |
| 267 | + "headline": "Nearby Airbnb activity is priced into Quebec rents", | |
| 268 | + "subheadline": "Is short-term rental density associated with higher residential rents? Each additional Airbnb listing within 500 m corresponds to roughly +0.4% monthly rent." | |
| 269 | + }, | |
| 270 | + "abstract": [ | |
| 271 | + "This paper investigates the relationship between Airbnb short-term rental activity and residential rents in Quebec, Canada, using cross-sectional microdata on 3,456 cleaned Airbnb listings and 8,303 rental listings. For every rental unit, Airbnb exposure is measured within 250 m, 500 m, 1 km, and 2 km buffers via Haversine distances, and a hedonic pricing framework augmented with spatial econometric techniques quantifies the conditional association between nearby Airbnb presence and monthly rents.", | |
| 272 | + "Baseline estimates indicate that an additional Airbnb listing within 500 m is associated with a statistically significant rent increase of approximately 0.3–0.5%, controlling for dwelling characteristics, building type, and city fixed effects — about $6–10 per month at the median rent, the same order of magnitude as Berlin's quasi-experimental evidence. The per-listing association decays monotonically with distance, from 0.93% at 250 m to 0.06% at 2 km.", | |
| 273 | + "Quantile regressions show the association is strongest at the upper tail of the rent distribution (0.0047 at the 90th percentile, about 25% above OLS), and the coefficient survives spatial autoregressive and spatial error models estimated by GMM with only mild attenuation. The paper explicitly cautions that these cross-sectional associations should not be read as causal effects, and discusses implications for housing affordability and short-term rental regulation." | |
| 274 | + ], | |
| 275 | + "findings": [ | |
| 276 | + { | |
| 277 | + "value": "+0.4%", | |
| 278 | + "label": "monthly rent per additional Airbnb listing within 500 m (0.3–0.5% across specifications)" | |
| 279 | + }, | |
| 280 | + { | |
| 281 | + "value": "8,303 + 3,456", | |
| 282 | + "label": "cleaned rental listings and Airbnb listings across Quebec" | |
| 283 | + }, | |
| 284 | + { | |
| 285 | + "value": "0.93% → 0.06%", | |
| 286 | + "label": "monotonic spatial decay of the association from 250 m to 2 km" | |
| 287 | + }, | |
| 288 | + { | |
| 289 | + "value": "0.0047 at τ = 0.90", | |
| 290 | + "label": "quantile coefficient at the top of the rent distribution, ~25% above OLS" | |
| 291 | + }, | |
| 292 | + { | |
| 293 | + "value": "ρ̂ = 0.137***", | |
| 294 | + "label": "spatial lag parameter; coefficient survives SAR/SEM with mild attenuation (0.0034)" | |
| 295 | + }, | |
| 296 | + { | |
| 297 | + "value": "0.0038–0.0040", | |
| 298 | + "label": "leave-one-city-out coefficient range, including dropping Montreal" | |
| 299 | + } | |
| 300 | + ], | |
| 301 | + "contributions": [ | |
| 302 | + { | |
| 303 | + "title": "Canadian evidence on the Airbnb–rent link", | |
| 304 | + "description": "Fills a documented gap in Canadian and Quebec evidence on short-term rentals and housing costs, benchmarking magnitudes against Berlin, Los Angeles, and Boston quasi-experiments." | |
| 305 | + }, | |
| 306 | + { | |
| 307 | + "title": "Multi-radius spatial exposure measurement", | |
| 308 | + "description": "Six exposure metrics (count, density, mean price, entire-home share, mean rating, superhost share) computed at four Haversine buffer radii yield 24 exposure variables per rental listing." | |
| 309 | + }, | |
| 310 | + { | |
| 311 | + "title": "Distance-decay gradient documented", | |
| 312 | + "description": "The per-listing rent association falls monotonically from 0.93% at 250 m to 0.06% at 2 km, consistent with a genuinely local neighborhood-level channel." | |
| 313 | + }, | |
| 314 | + { | |
| 315 | + "title": "Robustness across estimators and samples", | |
| 316 | + "description": "The coefficient stays positive and significant at 1% in every check: SAR/SEM spatial models, city-clustered errors, ring decomposition, trimming, and leave-one-city-out including Montreal." | |
| 317 | + }, | |
| 318 | + { | |
| 319 | + "title": "Complementary Airbnb pricing model", | |
| 320 | + "description": "A hedonic model of nightly prices shows short-term rental pricing is driven by listing characteristics, with a superhost discount that contrasts with premia in prior work — discussed, not hidden." | |
| 321 | + }, | |
| 322 | + { | |
| 323 | + "title": "Candid identification discussion", | |
| 324 | + "description": "Endogeneity and identification limits are addressed head-on: cross-sectional associations are explicitly framed as conditional capitalization gradients, not causal effects, with seven stated limitations." | |
| 325 | + } | |
| 326 | + ], | |
| 327 | + "data": [ | |
| 328 | + { | |
| 329 | + "title": "Airbnb listings (Quebec)", | |
| 330 | + "description": "Roughly 5,000 scraped listings cleaned to 3,456, with nightly price, coordinates, property type, rating, review counts, and superhost status." | |
| 331 | + }, | |
| 332 | + { | |
| 333 | + "title": "Realtor.ca rental listings", | |
| 334 | + "description": "8,356 scraped listings cleaned to 8,303, with monthly rent, coordinates, bedrooms, bathrooms, building type, and unit size, covering the province of Quebec." | |
| 335 | + }, | |
| 336 | + { | |
| 337 | + "title": "Spatially merged exposure dataset", | |
| 338 | + "description": "For each rental, six Airbnb exposure metrics at four buffer radii (250 m to 2 km) computed via chunked vectorized Haversine distances, plus city-level aggregates for 153 cities." | |
| 339 | + } | |
| 340 | + ], | |
| 341 | + "methodology": [ | |
| 342 | + { | |
| 343 | + "title": "Hedonic rent regressions", | |
| 344 | + "description": "Log monthly rent on Airbnb exposure plus dwelling controls, building type, and city fixed effects (Models 1a–1e), estimated by OLS with HC1 robust errors." | |
| 345 | + }, | |
| 346 | + { | |
| 347 | + "title": "Spatial econometrics (SAR/SEM)", | |
| 348 | + "description": "Spatial lag and spatial error models with row-standardized KNN(5) weights, estimated by Kelejian–Prucha GMM via PySAL/spreg; ρ̂ = 0.137, λ̂ = 0.539." | |
| 349 | + }, | |
| 350 | + { | |
| 351 | + "title": "Quantile regression", | |
| 352 | + "description": "Estimated at τ ∈ {0.10, 0.25, 0.50, 0.75, 0.90} plus a fine grid, tracing the exposure gradient from 0.0037 to 0.0047 across the rent distribution." | |
| 353 | + }, | |
| 354 | + { | |
| 355 | + "title": "Machine-learning benchmark with SHAP", | |
| 356 | + "description": "OLS, LASSO, Elastic Net, Random Forest, and GBM under an 80/20 split (RF test R² = 0.71), with SHAP attribution confirming the exposure variable's role." | |
| 357 | + }, | |
| 358 | + { | |
| 359 | + "title": "Extended robustness programme", | |
| 360 | + "description": "Nine checks: alternative radii and exposures, subsamples, trimming, city-clustered standard errors, size control, log(1+count) form, ring decomposition, and leave-one-city-out." | |
| 361 | + } | |
| 362 | + ], | |
| 363 | + "reproducibility": [ | |
| 364 | + "Eleven-script numbered Python pipeline from raw data inspection to extended robustness; fully deterministic (seed 42), most steps run in under 15 seconds on Apple Silicon.", | |
| 365 | + "Committed processed parquet files make steps 04–11 and the entire paper reproducible without the raw scraped data, which cannot be redistributed.", | |
| 366 | + "Rebuilt under a strict no-result-changes policy: 3/3 merged parquets value-identical and 14 of 16 tables byte-identical to the originals, with residual float-level diffs documented.", | |
| 367 | + "All 32 references added in the scholarly upgrade verified via Crossref/JMLR DOIs, with per-reference justification; 56 references in total.", | |
| 368 | + "The 51-page LaTeX paper builds via latexmk with zero unresolved references; 13 LaTeX table fragments and 16 publication PDF figures regenerate from the pipeline." | |
| 369 | + ], | |
| 370 | + "keywords": [ | |
| 371 | + "Airbnb", | |
| 372 | + "short-term rentals", | |
| 373 | + "housing rents", | |
| 374 | + "hedonic pricing", | |
| 375 | + "spatial econometrics", | |
| 376 | + "quantile regression", | |
| 377 | + "housing affordability", | |
| 378 | + "Quebec" | |
| 379 | + ] | |
| 380 | + }, | |
| 381 | + { | |
| 382 | + "slug": "wp7", | |
| 383 | + "repo": "https://github.com/spboucher-ai/wp7_uqo", | |
| 384 | + "hero": { | |
| 385 | + "headline": "What 3.8 Billion Option Contracts Know About Tomorrow", | |
| 386 | + "subheadline": "Do option-implied moments predict returns and volatility across assets? Weekly returns yes, daily no — and implied volatility dominates every realized-volatility benchmark." | |
| 387 | + }, | |
| 388 | + "abstract": [ | |
| 389 | + "This paper asks five questions about the information content of equity option markets, answered with 3.83 billion option contracts on 11,077 underlyings (2010–2025) merged with 11.5 billion intraday OHLCV observations. The estimation panel covers 264,383 ticker-days across 69 tickers.", | |
| 390 | + "Implied moments predict cross-sectional returns at the weekly horizon (R² of 4.8–19.3%) but not daily; a long/short portfolio sorted on implied kurtosis earns a Sharpe ratio of 2.33 (t = 19.8). Adding the IV surface to a HAR model raises 1-day realized-volatility forecasting R² by +23.3%, robust across all nine subperiods, and IV Granger-causes RV for 100% of tickers.", | |
| 391 | + "The implied-minus-realized correlation ratio predicts market stress at 5–20 day horizons (t-statistics 3.91–8.30). The popular max-open-interest \"price magnet\" hypothesis is rejected (47.0% hit rate, below the 50% coin flip), and on the SPX surface a simple HAR-RV model beats random forests, gradient boosting, and the VIX out of sample." | |
| 392 | + ], | |
| 393 | + "findings": [ | |
| 394 | + { | |
| 395 | + "value": "3.83B", | |
| 396 | + "label": "Option contracts analyzed (11,077 underlyings, 2010–2025)" | |
| 397 | + }, | |
| 398 | + { | |
| 399 | + "value": "2.33", | |
| 400 | + "label": "Sharpe ratio of the implied-kurtosis long/short strategy (t = 19.8)" | |
| 401 | + }, | |
| 402 | + { | |
| 403 | + "value": "+23.3%", | |
| 404 | + "label": "Gain in 1-day RV forecasting R² from adding the IV surface to HAR" | |
| 405 | + }, | |
| 406 | + { | |
| 407 | + "value": "100%", | |
| 408 | + "label": "Share of tickers where IV Granger-causes realized volatility (F = 62.4)" | |
| 409 | + }, | |
| 410 | + { | |
| 411 | + "value": "73.8%", | |
| 412 | + "label": "RV forecast-error variance explained by IV shocks at 20 days (FEVD)" | |
| 413 | + }, | |
| 414 | + { | |
| 415 | + "value": "47.0%", | |
| 416 | + "label": "Max-OI \"price magnet\" hit rate — below 50%, hypothesis rejected" | |
| 417 | + } | |
| 418 | + ], | |
| 419 | + "contributions": [ | |
| 420 | + { | |
| 421 | + "title": "Weekly, not daily, return predictability", | |
| 422 | + "description": "Implied moments predict cross-sectional returns at the weekly horizon (R² 4.8–19.3%) but carry no daily signal; the kurtosis long/short portfolio delivers a 2.33 Sharpe ratio." | |
| 423 | + }, | |
| 424 | + { | |
| 425 | + "title": "IV surface dominates HAR and GARCH", | |
| 426 | + "description": "Augmenting HAR-RV with implied-volatility surface features improves 1-day forecasting R² by +23.3%, a gain that survives in all nine subperiods examined." | |
| 427 | + }, | |
| 428 | + { | |
| 429 | + "title": "Implied–realized correlation as a stress gauge", | |
| 430 | + "description": "The ratio of implied to realized correlation predicts market stress at 5–20 day horizons, with t-statistics ranging from 3.91 to 8.30." | |
| 431 | + }, | |
| 432 | + { | |
| 433 | + "title": "Price-magnet hypothesis rejected", | |
| 434 | + "description": "Prices gravitate toward maximum-open-interest strikes only 47.0% of the time — below chance — refuting a widespread piece of options-market folklore." | |
| 435 | + }, | |
| 436 | + { | |
| 437 | + "title": "Simple models beat ML on the SPX surface", | |
| 438 | + "description": "For SPX realized-volatility forecasting, HAR-RV beats random forests, gradient boosting, and the VIX out of sample; two-week ATM IV alone captures 50.8% of feature importance." | |
| 439 | + }, | |
| 440 | + { | |
| 441 | + "title": "Placebo-validated information content", | |
| 442 | + "description": "A placebo design confirms the results are not mechanical: shuffled predictors yield R² of 0.0008 against 0.053 for the actual implied moments." | |
| 443 | + } | |
| 444 | + ], | |
| 445 | + "data": [ | |
| 446 | + { | |
| 447 | + "title": "Equity options database", | |
| 448 | + "description": "3.83 billion option contracts across 11,077 underlyings, 2010–2025, stored in external DuckDB stores and distilled into five derived parquet files." | |
| 449 | + }, | |
| 450 | + { | |
| 451 | + "title": "Intraday OHLCV database", | |
| 452 | + "description": "11.5 billion intraday open-high-low-close-volume observations merged with the options data to construct realized-volatility measures." | |
| 453 | + }, | |
| 454 | + { | |
| 455 | + "title": "Estimation panel", | |
| 456 | + "description": "264,383 ticker-days over 69 tickers, 2010–2025 — the analysis sample behind all cross-sectional and time-series results." | |
| 457 | + } | |
| 458 | + ], | |
| 459 | + "methodology": [ | |
| 460 | + { | |
| 461 | + "title": "Fama-MacBeth cross-sectional regressions", | |
| 462 | + "description": "Panel regressions of daily and weekly returns on option-implied moments, with Newey-West, clustered-SE, and quantile robustness checks." | |
| 463 | + }, | |
| 464 | + { | |
| 465 | + "title": "HAR-RV and GARCH forecasting horse race", | |
| 466 | + "description": "Realized-volatility forecasts from HAR and GARCH benchmarks compared against IV-surface-augmented models across nine subperiods and rolling windows." | |
| 467 | + }, | |
| 468 | + { | |
| 469 | + "title": "Granger causality, VAR, IRF and FEVD", | |
| 470 | + "description": "Vector autoregressions establish that IV leads RV for every ticker; forecast-error variance decompositions attribute 73.8% of 20-day RV variance to IV shocks." | |
| 471 | + }, | |
| 472 | + { | |
| 473 | + "title": "Portfolio sorts and double sorts", | |
| 474 | + "description": "Single and double sorts on implied moments, including decile sorts and Spearman information coefficients, generate the long/short trading strategies." | |
| 475 | + }, | |
| 476 | + { | |
| 477 | + "title": "Machine learning on the SPX surface", | |
| 478 | + "description": "Random forests and gradient boosting trained on implied-volatility surface features, benchmarked against HAR-RV and the VIX out of sample." | |
| 479 | + }, | |
| 480 | + { | |
| 481 | + "title": "Extended robustness battery", | |
| 482 | + "description": "Winsorization and Newey-West lag sensitivity, leave-one-year-out stability, and a placebo test with shuffled predictors validate the headline findings." | |
| 483 | + } | |
| 484 | + ], | |
| 485 | + "reproducibility": [ | |
| 486 | + "Fully scripted pipeline: 13 numbered Python entry points (make pipeline | figures | paper) with a reusable src/wp7 package.", | |
| 487 | + "All 28 regenerable result files were re-run and verified on 2026-08-05: 10 byte-identical, 15 equal to floating-point noise, 1 explained deviation, 2 newly regenerated.", | |
| 488 | + "Every table in the 29-page paper derives from one of 41 shipped CSV result tables; raw-dependent steps skip gracefully when the DuckDB stores are absent.", | |
| 489 | + "Reproduction evidence (rerun logs and a CSV comparator) ships in _verify/, with a full audit trail in AUDIT.md and CHANGES.md.", | |
| 490 | + "LaTeX source is modular (one file per section, 45 BibTeX references, all cited) and builds with latexmk via make paper." | |
| 491 | + ], | |
| 492 | + "keywords": [ | |
| 493 | + "options-implied moments", | |
| 494 | + "implied volatility surface", | |
| 495 | + "realized volatility forecasting", | |
| 496 | + "HAR-RV", | |
| 497 | + "cross-sectional return predictability", | |
| 498 | + "Granger causality", | |
| 499 | + "portfolio sorts", | |
| 500 | + "machine learning" | |
| 501 | + ] | |
| 502 | + }, | |
| 503 | + { | |
| 504 | + "slug": "wp9", | |
| 505 | + "repo": "https://github.com/spboucher-ai/wp9_uqo", | |
| 506 | + "hero": { | |
| 507 | + "headline": "Location Is Worth More Than Every Wall Combined", | |
| 508 | + "subheadline": "What do structure and neighbourhood each contribute to Canadian home prices? Absorbing 1,153 neighbourhood fixed effects lifts explained variance from 46% to 77%." | |
| 509 | + }, | |
| 510 | + "abstract": [ | |
| 511 | + "A dwelling is the archetypal heterogeneous good, and its most important attribute — location — cannot be observed as a scalar. This paper estimates a semi-logarithmic hedonic price equation at national scale for Canada on 140,931 MLS listings (82,334 houses, 57,857 condos, nine provinces), absorbing 1,153 Forward Sortation Area fixed effects so that structural implicit prices are identified purely from within-neighbourhood variation.", | |
| 512 | + "The specification ladder is decisive: structural attributes alone explain 46.4% of log-price variance; adding province effects reaches 56.7%; the grand model with neighbourhood fixed effects reaches 76.7%. Location alone is worth roughly 30 percentage points of R² — more than every structural attribute combined. The living-area elasticity is 0.547, each full bathroom adds about 11%, and bedrooms conditional on area are worth approximately zero.", | |
| 513 | + "Out of sample, the model values held-out homes with a median absolute error of 15.8% (OOS R² = 0.764), competitive with commercial AVMs while remaining fully transparent. Neighbourhood premia span a factor of nine: the most expensive FSAs net of structure are all in Vancouver (+150–200% versus the national median); the cheapest sit in rural Saskatchewan, Manitoba, and Newfoundland (−60 to −67%)." | |
| 514 | + ], | |
| 515 | + "findings": [ | |
| 516 | + { | |
| 517 | + "value": "140,931", | |
| 518 | + "label": "MLS listings in the estimation sample, across 9 provinces" | |
| 519 | + }, | |
| 520 | + { | |
| 521 | + "value": "46% → 77%", | |
| 522 | + "label": "Explained log-price variance after absorbing 1,153 neighbourhood fixed effects" | |
| 523 | + }, | |
| 524 | + { | |
| 525 | + "value": "0.547", | |
| 526 | + "label": "Living-area elasticity in the grand model (cluster SE 0.009)" | |
| 527 | + }, | |
| 528 | + { | |
| 529 | + "value": "15.8%", | |
| 530 | + "label": "Median absolute out-of-sample valuation error (OOS R² = 0.764)" | |
| 531 | + }, | |
| 532 | + { | |
| 533 | + "value": "+11%", | |
| 534 | + "label": "Price premium per full bathroom (0.109 log points)" | |
| 535 | + }, | |
| 536 | + { | |
| 537 | + "value": "×9", | |
| 538 | + "label": "Span of neighbourhood premia between the most and least expensive FSAs" | |
| 539 | + } | |
| 540 | + ], | |
| 541 | + "contributions": [ | |
| 542 | + { | |
| 543 | + "title": "National-scale variance decomposition", | |
| 544 | + "description": "Quantifies structure versus location for the entire Canadian market: neighbourhood identity contributes about 30 percentage points of R², exceeding all structural attributes combined." | |
| 545 | + }, | |
| 546 | + { | |
| 547 | + "title": "High-dimensional fixed-effects hedonic model", | |
| 548 | + "description": "Absorbs 1,153 FSA intercepts via absorbing least squares, identifying implicit prices from within-neighbourhood variation with standard errors clustered by FSA." | |
| 549 | + }, | |
| 550 | + { | |
| 551 | + "title": "Transparent AVM-grade valuation accuracy", | |
| 552 | + "description": "On a held-out 20% sample the model achieves a 15.8% median absolute error with 59% of homes priced within ±20% — competitive with commercial automated valuation models." | |
| 553 | + }, | |
| 554 | + { | |
| 555 | + "title": "The textbook bedroom result, confirmed", | |
| 556 | + "description": "Conditional on floor area, bedroom count is worth approximately zero, while living-area elasticity of 0.547 and an 11% full-bathroom premium dominate structural pricing." | |
| 557 | + }, | |
| 558 | + { | |
| 559 | + "title": "Spatial diagnostics validate the decomposition", | |
| 560 | + "description": "Moran's I of residuals falls from 0.46 to 0.08 (−82%) once neighbourhood effects are absorbed, showing FSA intercepts capture nearly all spatial price structure." | |
| 561 | + }, | |
| 562 | + { | |
| 563 | + "title": "Urban gradient and neighbourhood ranking", | |
| 564 | + "description": "Location premia decline 8.5% per doubling of distance to the nine major metros; a ranked national map places Vancouver FSAs (V6S, V8E, V6T) at +150–200%." | |
| 565 | + } | |
| 566 | + ], | |
| 567 | + "data": [ | |
| 568 | + { | |
| 569 | + "title": "Canadian MLS listings snapshot", | |
| 570 | + "description": "De-duplicated DuckDB of 172,019 for-sale listings × 81 columns with list price, geocoded coordinates, postal code, and semi-structured building and lot attributes (747 MB, not redistributed)." | |
| 571 | + }, | |
| 572 | + { | |
| 573 | + "title": "Estimation sample", | |
| 574 | + "description": "140,931 filtered and trimmed listings (committed as a 6 MB parquet): median list price ≈ $639,888, median living area ≈ 135 m², 3 bedrooms, 2 full bathrooms." | |
| 575 | + } | |
| 576 | + ], | |
| 577 | + "methodology": [ | |
| 578 | + { | |
| 579 | + "title": "Specification ladder M1–M5", | |
| 580 | + "description": "Five nested hedonic models, from structural attributes only to the grand model with FSA fixed effects; the M3-to-M5 R² gap measures the value of resolving location at neighbourhood scale." | |
| 581 | + }, | |
| 582 | + { | |
| 583 | + "title": "Absorbing least squares", | |
| 584 | + "description": "linearmodels AbsorbingLS sweeps out 1,153 FSA intercepts without materializing dummies — numerically identical to full-dummy OLS — with FSA-clustered standard errors throughout." | |
| 585 | + }, | |
| 586 | + { | |
| 587 | + "title": "Duan smearing retransformation", | |
| 588 | + "description": "Level predictions from the log model use Duan's (1983) smearing estimator, avoiding any log-normality assumption in out-of-sample valuation." | |
| 589 | + }, | |
| 590 | + { | |
| 591 | + "title": "Out-of-sample and transferability validation", | |
| 592 | + "description": "Random 80/20 split restricted to FSAs seen in training, plus leave-one-province-out cross-validation with province-specific intercepts." | |
| 593 | + }, | |
| 594 | + { | |
| 595 | + "title": "Quantile and nonlinearity extensions", | |
| 596 | + "description": "Quantile hedonic regressions across τ = 0.1–0.9, a quadratic test of diminishing returns to floor space, and an urban price gradient in metro distance." | |
| 597 | + }, | |
| 598 | + { | |
| 599 | + "title": "Moran's I spatial diagnostics", | |
| 600 | + "description": "Row-standardized k-NN weights (k = 10) on a 15,000-listing sample with 199 permutations test residual spatial autocorrelation before and after absorbing FSA effects." | |
| 601 | + } | |
| 602 | + ], | |
| 603 | + "reproducibility": [ | |
| 604 | + "End-to-end five-script pipeline (build sample, core estimation, extended estimation, figures, tables) running in under 10 minutes on Apple Silicon.", | |
| 605 | + "Two results tiers keep the published record intact: results/reference/ holds the original outputs, results/reproduced/ the regenerated ones, switchable via a --results flag.", | |
| 606 | + "All six LaTeX tables consumed by the 26-page paper are verified numerically identical to the originally published versions.", | |
| 607 | + "The lost upstream cleaning code was reconstructed from the paper's data section, reproducing the published sample to within +0.86% and the full R² ladder to the second decimal; residual gaps are flagged in AUDIT.md, not hidden.", | |
| 608 | + "The committed 6 MB estimation parquet lets anyone run estimation, figures, and tables (steps 02–05) without the 747 MB raw DuckDB." | |
| 609 | + ], | |
| 610 | + "keywords": [ | |
| 611 | + "hedonic pricing", | |
| 612 | + "housing markets", | |
| 613 | + "neighbourhood fixed effects", | |
| 614 | + "Canada", | |
| 615 | + "MLS listings", | |
| 616 | + "automated valuation", | |
| 617 | + "spatial econometrics", | |
| 618 | + "Moran's I" | |
| 619 | + ] | |
| 620 | + }, | |
| 621 | + { | |
| 622 | + "slug": "wp10", | |
| 623 | + "repo": "https://github.com/spboucher-ai/wp10_uqo", | |
| 624 | + "hero": { | |
| 625 | + "headline": "Quebec's Cheapest Homes Pay 65% Too Much Tax", | |
| 626 | + "subheadline": "Are municipal assessments equitable? Matching 522,769 sales to the roll shows systematic regressivity — 99% of municipalities fail the IAAO uniformity standard." | |
| 627 | + }, | |
| 628 | + "abstract": [ | |
| 629 | + "Quebec taxes every dwelling in proportion to its assessed value, redrawn on a triennial roll that must by statute reflect market conditions at a single reference date. If assessments are regressive — cheap homes overvalued relative to expensive ones — the effective tax rate silently falls with wealth. Matching 522,769 residential sales (2021–2026) at the parcel level to the assessment roll in force at sale, this paper delivers the first province-wide audit of property-assessment equity in Canada.", | |
| 630 | + "Within the same municipality × roll × sale-year cell, the elasticity of the assessment ratio with respect to price is −0.34 under Cheng fixed-effects estimation, and remains −0.08 under Clapp's measurement-error-robust rank instrument — regressivity is real, not a statistical artifact. The quantile profile shows failure concentrated at the top: β falls from 0.87 at the 10th percentile to 0.49 at the 90th.", | |
| 631 | + "The consequences are stark. Ninety-nine percent of municipalities have a negative price-related bias, 95% fall below the IAAO vertical-equity band, and the median municipal COD of 26 far exceeds the IAAO ceiling of 15. The median dwelling in the bottom local price decile pays roughly 65% more property tax than uniform assessment would imply, while the top decile pays about 5% less. Montréal is the lone progressive large market, with PRB +0.07 in all six years." | |
| 632 | + ], | |
| 633 | + "findings": [ | |
| 634 | + { | |
| 635 | + "value": "522,769", | |
| 636 | + "label": "Sales matched to the assessment roll, across 625 municipalities" | |
| 637 | + }, | |
| 638 | + { | |
| 639 | + "value": "−0.34", | |
| 640 | + "label": "Elasticity of the assessment ratio w.r.t. price (Cheng fixed effects)" | |
| 641 | + }, | |
| 642 | + { | |
| 643 | + "value": "−0.08", | |
| 644 | + "label": "Elasticity under the measurement-error-robust Clapp rank IV" | |
| 645 | + }, | |
| 646 | + { | |
| 647 | + "value": "99%", | |
| 648 | + "label": "Municipalities failing the IAAO uniformity standard (PRB < 0)" | |
| 649 | + }, | |
| 650 | + { | |
| 651 | + "value": "+65%", | |
| 652 | + "label": "Excess property tax paid by the median bottom-decile dwelling" | |
| 653 | + }, | |
| 654 | + { | |
| 655 | + "value": "26", | |
| 656 | + "label": "Median municipal COD — versus the IAAO ceiling of 15" | |
| 657 | + } | |
| 658 | + ], | |
| 659 | + "contributions": [ | |
| 660 | + { | |
| 661 | + "title": "First province-wide equity audit in Canada", | |
| 662 | + "description": "Parcel-level matching of 522,769 sales to the triennial assessment rolls (median match distance 0.6 m) produces the first Canadian province-scale test of assessment equity." | |
| 663 | + }, | |
| 664 | + { | |
| 665 | + "title": "Regressivity survives measurement-error correction", | |
| 666 | + "description": "The Clapp rank IV, designed to purge the attenuation bias that inflates naive ratio studies, still finds γ = −0.083 — regressivity is genuine, not spurious." | |
| 667 | + }, | |
| 668 | + { | |
| 669 | + "title": "Failure is concentrated at the top", | |
| 670 | + "description": "Quantile regressions show β falling from 0.87 at the 10th percentile to 0.49 at the 90th: expensive homes are the ones assessed furthest below market." | |
| 671 | + }, | |
| 672 | + { | |
| 673 | + "title": "Near-universal IAAO standard failure", | |
| 674 | + "description": "99% of municipalities show negative price-related bias and 95% fall outside the IAAO vertical-equity band; the median COD of 26 breaches the uniformity ceiling of 15." | |
| 675 | + }, | |
| 676 | + { | |
| 677 | + "title": "Exact intra-municipal tax-shift calculation", | |
| 678 | + "description": "Under Quebec's exemption-free ad valorem rule, the bottom local price decile overpays about 65% while the top decile underpays about 5% — a silent redistribution." | |
| 679 | + }, | |
| 680 | + { | |
| 681 | + "title": "The Montréal exception", | |
| 682 | + "description": "Montréal is the only progressive large market, with a positive PRB of +0.07 in all six sample years, possibly reflecting borough-level composition." | |
| 683 | + }, | |
| 684 | + { | |
| 685 | + "title": "Subgroup anatomy of the gap", | |
| 686 | + "description": "Regressivity is strongest for plexes (γ = −0.49), single-family homes (−0.45), high land share (−0.50) and old dwellings (−0.43); condos are mildest at −0.12." | |
| 687 | + } | |
| 688 | + ], | |
| 689 | + "data": [ | |
| 690 | + { | |
| 691 | + "title": "Matched sales–roll snapshot", | |
| 692 | + "description": "745,119 residential transactions (Jan 2021 – Jul 2026) matched at the parcel level to the assessment roll in force at sale; every retained sale reproduces the roll value exactly." | |
| 693 | + }, | |
| 694 | + { | |
| 695 | + "title": "Assessment-roll attributes", | |
| 696 | + "description": "Total, land, and building assessed values, lot and floor areas, year built, unit count, CUBF use code, roll vintage, and the statutory market-condition date." | |
| 697 | + }, | |
| 698 | + { | |
| 699 | + "title": "Final estimation sample", | |
| 700 | + "description": "522,769 sales in 625 municipalities and 2,884 municipality × roll × sale-year cells, after residential filtering, match-quality screens, 1/99% ratio trimming, and a 20-sale cell minimum." | |
| 701 | + } | |
| 702 | + ], | |
| 703 | + "methodology": [ | |
| 704 | + { | |
| 705 | + "title": "IAAO ratio-study battery", | |
| 706 | + "description": "Median assessment ratio, COD, PRD, and PRB with percentile-bootstrap confidence intervals, computed for every municipality with at least 100 usable sales." | |
| 707 | + }, | |
| 708 | + { | |
| 709 | + "title": "Cheng (1974) log-log fixed effects", | |
| 710 | + "description": "ln AV regressed on ln SP with 2,884 absorbed market-timing cells (AbsorbingLS), standard errors clustered on 625 municipalities; γ = β − 1 measures vertical inequity." | |
| 711 | + }, | |
| 712 | + { | |
| 713 | + "title": "Clapp (1990) rank instrument", | |
| 714 | + "description": "A three-valued instrument built from within-cell rank agreement of ln AV and ln SP, estimated by 2SLS on demeaned data, provides the conservative measurement-error-robust bound." | |
| 715 | + }, | |
| 716 | + { | |
| 717 | + "title": "Quantile and heterogeneity regressions", | |
| 718 | + "description": "Quantile regressions on within-cell demeaned data (τ = 0.10–0.90) and separate fixed-effects estimates by dwelling class, age, land share, roll lag, market size, and sale year." | |
| 719 | + }, | |
| 720 | + { | |
| 721 | + "title": "Horizontal-inequity regressions", | |
| 722 | + "description": "The absolute deviation of the log ratio from its cell median is regressed on property characteristics to measure dispersion in assessment quality among comparable homes." | |
| 723 | + }, | |
| 724 | + { | |
| 725 | + "title": "Exact tax-shift computation", | |
| 726 | + "description": "Ratio relative to the cell median by within-cell price decile yields exact percentage over- and under-payment under Quebec's exemption-free ad valorem property tax." | |
| 727 | + } | |
| 728 | + ], | |
| 729 | + "reproducibility": [ | |
| 730 | + "Five-script end-to-end pipeline (sample, IAAO stats, regressions, figures, tables) runs in about 5 minutes on an Apple Silicon laptop with roughly 6 GB peak RAM.", | |
| 731 | + "A reusable src/wp10 package separates IAAO diagnostics (iaao.py), econometric models (models.py), sample construction, and a validated journal-calibre plot style.", | |
| 732 | + "Every CSV the scripts emit is committed under results/reproduced/, and 10 LaTeX tables plus 10 publication figures (300 dpi) feed the 29-page paper directly.", | |
| 733 | + "The paper builds with latexmk from modular LaTeX sections via a Makefile." | |
| 734 | + ], | |
| 735 | + "keywords": [ | |
| 736 | + "property tax", | |
| 737 | + "assessment equity", | |
| 738 | + "vertical inequity", | |
| 739 | + "IAAO ratio studies", | |
| 740 | + "Quebec", | |
| 741 | + "Clapp rank IV", | |
| 742 | + "fixed effects", | |
| 743 | + "housing valuation" | |
| 744 | + ] | |
| 745 | + }, | |
| 746 | + { | |
| 747 | + "slug": "chapter-1", | |
| 748 | + "repo": "https://github.com/spboucher-ai/phd-thesis", | |
| 749 | + "hero": { | |
| 750 | + "headline": "Speculators Dampen Macro News Shocks in Energy Futures", | |
| 751 | + "subheadline": "Does speculative trading amplify macroeconomic news in commodity futures? No — greater speculation dampens price drift, volatility, and spreads while improving liquidity." | |
| 752 | + }, | |
| 753 | + "abstract": [ | |
| 754 | + "Using five-minute futures data from 2007 to 2024 and 26 macroeconomic announcement releases, this essay asks whether speculative trading amplifies or dampens the impact of macro news on commodity futures. Speculation intensity is measured with an NLS proxy built from the CFTC disaggregated Commitments of Traders report, distinguishing money managers from swap dealers across energy (crude oil, natural gas) and metals (gold, silver, copper, palladium) contracts.", | |
| 755 | + "The evidence points to a stabilizing role for speculators: increased speculative trading dampens the impact of standardized macro surprises on price drift, volatility, and bid-ask spreads, improving liquidity and price discovery. The damping effect is stronger for procyclical commodities such as oil and natural gas than for safe havens like gold, and the beneficial effects are driven by money managers rather than swap dealers." | |
| 756 | + ], | |
| 757 | + "findings": [ | |
| 758 | + { | |
| 759 | + "value": "26", | |
| 760 | + "label": "Macroeconomic announcement releases studied" | |
| 761 | + }, | |
| 762 | + { | |
| 763 | + "value": "6", | |
| 764 | + "label": "Futures contracts (CL, NG, GC, SI, HG, PA)" | |
| 765 | + }, | |
| 766 | + { | |
| 767 | + "value": "5-min", | |
| 768 | + "label": "Sampling frequency of futures data, 2007–2024" | |
| 769 | + }, | |
| 770 | + { | |
| 771 | + "value": "14", | |
| 772 | + "label": "Tables of results in the article" | |
| 773 | + }, | |
| 774 | + { | |
| 775 | + "value": "6", | |
| 776 | + "label": "Figures documenting speculation and news effects" | |
| 777 | + } | |
| 778 | + ], | |
| 779 | + "contributions": [ | |
| 780 | + { | |
| 781 | + "title": "Speculation conditions macro news impact", | |
| 782 | + "description": "Shows that the intensity of speculative trading systematically conditions how commodity futures react to standardized macroeconomic surprises, rather than treating announcement effects as uniform." | |
| 783 | + }, | |
| 784 | + { | |
| 785 | + "title": "Evidence that speculators stabilize markets", | |
| 786 | + "description": "Increased speculative activity dampens the impact of macro surprises on price drift, volatility, and bid-ask spreads, countering the view that speculation amplifies commodity price shocks." | |
| 787 | + }, | |
| 788 | + { | |
| 789 | + "title": "Trader-type decomposition of effects", | |
| 790 | + "description": "Using the CFTC disaggregated Commitments of Traders data, the beneficial liquidity and price-discovery effects are attributed to money managers, not swap dealers." | |
| 791 | + }, | |
| 792 | + { | |
| 793 | + "title": "Procyclical versus safe-haven contrast", | |
| 794 | + "description": "Documents that the damping effect is stronger for procyclical commodities such as crude oil and natural gas than for safe havens like gold." | |
| 795 | + }, | |
| 796 | + { | |
| 797 | + "title": "NLS speculation proxy at announcement times", | |
| 798 | + "description": "Builds a nonlinear-least-squares speculation intensity proxy from disaggregated positioning data and interacts it with high-frequency announcement-window reactions in energy and metals futures." | |
| 799 | + } | |
| 800 | + ], | |
| 801 | + "data": [ | |
| 802 | + { | |
| 803 | + "title": "Five-minute commodity futures, 2007–2024", | |
| 804 | + "description": "High-frequency price data for crude oil (CL), natural gas (NG), gold (GC), silver (SI), copper (HG), and palladium (PA) futures." | |
| 805 | + }, | |
| 806 | + { | |
| 807 | + "title": "26 macroeconomic announcement series", | |
| 808 | + "description": "Scheduled U.S. macroeconomic releases converted into standardized surprises to measure announcement-window reactions in returns, volatility, and spreads." | |
| 809 | + }, | |
| 810 | + { | |
| 811 | + "title": "CFTC disaggregated Commitments of Traders", | |
| 812 | + "description": "Weekly positioning data separating money managers from swap dealers, used to construct the NLS speculation intensity proxy." | |
| 813 | + } | |
| 814 | + ], | |
| 815 | + "methodology": [ | |
| 816 | + { | |
| 817 | + "title": "WLS-EWMA event regressions", | |
| 818 | + "description": "Weighted least squares with exponentially weighted moving average variance estimates to measure announcement effects on high-frequency returns and volatility." | |
| 819 | + }, | |
| 820 | + { | |
| 821 | + "title": "GARCH volatility modelling", | |
| 822 | + "description": "GARCH-type specifications capture conditional volatility dynamics around macroeconomic announcement releases in energy and metals futures." | |
| 823 | + }, | |
| 824 | + { | |
| 825 | + "title": "NLS speculation proxy from COT data", | |
| 826 | + "description": "A nonlinear-least-squares proxy for speculation intensity built from CFTC disaggregated positions, interacted with standardized macro surprises." | |
| 827 | + }, | |
| 828 | + { | |
| 829 | + "title": "COVID and ZLB robustness appendices", | |
| 830 | + "description": "Dedicated appendices test the robustness of the results across the COVID-19 period and the zero-lower-bound monetary policy regime." | |
| 831 | + } | |
| 832 | + ], | |
| 833 | + "reproducibility": [ | |
| 834 | + "Full LaTeX source lives in phd_chap1_20260731/ as a frozen July 31, 2026 snapshot: a monolithic main.tex plus tables.tex (14 tables), figures.tex (6 figures), and master.bib.", | |
| 835 | + "The folder includes COVID and ZLB robustness appendices and the compiled main.pdf alongside standalone figure files (FIG_NLS, FIG_MSCT, FIG_WT, per-commodity plots).", | |
| 836 | + "The chapter is also integrated into the full thesis in these-ulaval/, where labels are prefixed ch1: and the bibliography is merged into a consolidated 271-key BibTeX file." | |
| 837 | + ], | |
| 838 | + "keywords": [ | |
| 839 | + "speculative trading", | |
| 840 | + "energy futures", | |
| 841 | + "macroeconomic announcements", | |
| 842 | + "high-frequency data", | |
| 843 | + "CFTC Commitments of Traders", | |
| 844 | + "liquidity", | |
| 845 | + "price discovery", | |
| 846 | + "volatility" | |
| 847 | + ] | |
| 848 | + }, | |
| 849 | + { | |
| 850 | + "slug": "chapter-2", | |
| 851 | + "repo": "https://github.com/spboucher-ai/phd-thesis", | |
| 852 | + "hero": { | |
| 853 | + "headline": "Intraday iNAV Reveals Jump-Driven ETF Volatility Transmission", | |
| 854 | + "subheadline": "How does volatility flow between commodity ETFs and their underlyings? The intraday indicative NAV shows transmission runs through jumps, not diffusion." | |
| 855 | + }, | |
| 856 | + "abstract": [ | |
| 857 | + "This essay asks how volatility flows between commodity ETFs and their underlying assets, and what the intraday indicative NAV (iNAV) reveals that daily data cannot. It builds a novel minute-level iNAV dataset for four single-commodity ETFs — gold (GLD), silver (SLV), oil (USO), and natural gas (UNG) — from roughly 45 million tick observations spanning 2010 to 2023.", | |
| 858 | + "Realized variance is decomposed into continuous and jump components and modelled with HAR-X and HAR-CJ-X regressions at 1, 5, and 30 minutes, plus a Bayesian VAR. The iNAV yields a sharper image of the ETF–underlying volatility relationship: transmission runs primarily through jumps, sampling frequency matters (1-minute estimates are up to twice as large as 30-minute ones), and precious metals show unidirectional transmission while energy is bidirectional and asymmetric." | |
| 859 | + ], | |
| 860 | + "findings": [ | |
| 861 | + { | |
| 862 | + "value": "≈45M", | |
| 863 | + "label": "Tick observations underlying the minute-level iNAV dataset, 2010–2023" | |
| 864 | + }, | |
| 865 | + { | |
| 866 | + "value": "4", | |
| 867 | + "label": "Single-commodity ETFs studied (GLD, SLV, USO, UNG)" | |
| 868 | + }, | |
| 869 | + { | |
| 870 | + "value": "2×", | |
| 871 | + "label": "1-minute transmission estimates up to twice as large as 30-minute ones" | |
| 872 | + }, | |
| 873 | + { | |
| 874 | + "value": "3", | |
| 875 | + "label": "Sampling frequencies compared: 1, 5, and 30 minutes" | |
| 876 | + }, | |
| 877 | + { | |
| 878 | + "value": "13", | |
| 879 | + "label": "Tables and 8 figures (realized volatility and IRF plots)" | |
| 880 | + } | |
| 881 | + ], | |
| 882 | + "contributions": [ | |
| 883 | + { | |
| 884 | + "title": "Novel minute-level iNAV dataset", | |
| 885 | + "description": "Constructs an original minute-frequency indicative NAV dataset for four single-commodity ETFs from roughly 45 million tick observations covering 2010 to 2023." | |
| 886 | + }, | |
| 887 | + { | |
| 888 | + "title": "Sharper view of ETF–underlying dynamics", | |
| 889 | + "description": "Shows the intraday iNAV gives a sharper image of the volatility relationship between ETFs and their underlying assets than daily data can provide." | |
| 890 | + }, | |
| 891 | + { | |
| 892 | + "title": "Jumps as the transmission channel", | |
| 893 | + "description": "Decomposing realized variance shows volatility transmission runs primarily through jump components rather than the continuous diffusion component." | |
| 894 | + }, | |
| 895 | + { | |
| 896 | + "title": "Sampling frequency matters for inference", | |
| 897 | + "description": "Comparing 1-, 5-, and 30-minute estimates shows 1-minute transmission coefficients can be up to twice as large as 30-minute ones." | |
| 898 | + }, | |
| 899 | + { | |
| 900 | + "title": "Asset-class contrast in transmission direction", | |
| 901 | + "description": "Precious metals show unidirectional iNAV-to-ETF transmission consistent with passive arbitrage, while energy ETFs display bidirectional and asymmetric volatility flows." | |
| 902 | + } | |
| 903 | + ], | |
| 904 | + "data": [ | |
| 905 | + { | |
| 906 | + "title": "Minute-level iNAV series, 2010–2023", | |
| 907 | + "description": "A novel intraday indicative NAV dataset for four single-commodity ETFs, built from approximately 45 million tick observations." | |
| 908 | + }, | |
| 909 | + { | |
| 910 | + "title": "Commodity ETF prices: GLD, SLV, USO, UNG", | |
| 911 | + "description": "High-frequency prices for the gold, silver, crude oil, and natural gas ETFs, matched to their underlying assets at 1-, 5-, and 30-minute frequencies." | |
| 912 | + } | |
| 913 | + ], | |
| 914 | + "methodology": [ | |
| 915 | + { | |
| 916 | + "title": "HAR-X and HAR-CJ-X models", | |
| 917 | + "description": "Heterogeneous autoregressive realized-volatility regressions with cross-market terms, estimated at 1-, 5-, and 30-minute sampling frequencies." | |
| 918 | + }, | |
| 919 | + { | |
| 920 | + "title": "Jump decomposition (Barndorff-Nielsen–Shephard)", | |
| 921 | + "description": "Realized variance is decomposed into continuous and jump components to identify which channel carries volatility between ETFs and underlyings." | |
| 922 | + }, | |
| 923 | + { | |
| 924 | + "title": "Minnesota-prior Bayesian VAR", | |
| 925 | + "description": "A Bayesian VAR with Minnesota prior traces impulse responses and the direction and asymmetry of volatility transmission between markets." | |
| 926 | + } | |
| 927 | + ], | |
| 928 | + "reproducibility": [ | |
| 929 | + "Full LaTeX source in phd_chap2_20260731/ is organized as main.tex plus a sections/ folder (introduction, data, methods, results, conclusion), with 13 tables, 8 figures, and master.bib.", | |
| 930 | + "The folder is a frozen July 31, 2026 submission snapshot for the Journal of Futures Markets and includes the compiled main.pdf.", | |
| 931 | + "The thesis-adapted version lives in these-ulaval/chapitre2/, with labels prefixed ch2: and references merged into the consolidated thesis bibliography." | |
| 932 | + ], | |
| 933 | + "keywords": [ | |
| 934 | + "commodity ETFs", | |
| 935 | + "indicative NAV", | |
| 936 | + "volatility transmission", | |
| 937 | + "realized volatility", | |
| 938 | + "jumps", | |
| 939 | + "HAR models", | |
| 940 | + "Bayesian VAR", | |
| 941 | + "high-frequency data" | |
| 942 | + ] | |
| 943 | + }, | |
| 944 | + { | |
| 945 | + "slug": "chapter-3", | |
| 946 | + "repo": "https://github.com/spboucher-ai/phd-thesis", | |
| 947 | + "hero": { | |
| 948 | + "headline": "Fed Tone Moves Prices, Novelty Moves Volatility", | |
| 949 | + "subheadline": "When the Fed speaks, what moves markets — what it says or how new it is? Tone drives directional returns; novelty drives volatility." | |
| 950 | + }, | |
| 951 | + "abstract": [ | |
| 952 | + "This essay asks what moves markets when the Federal Reserve speaks: the policy tone of the statement or its informational novelty. FOMC statements (217 releases, 2000–2025) are decomposed into hawkish/dovish tone and novelty using a dual-model NLP ensemble (MiniLM and BERT with TSDAE+MNRL fine-tuning and PCA-based reference selection), then linked to 1-minute futures data across seven contracts around 148 FOMC events from 2008 to 2025.", | |
| 953 | + "The two dimensions play distinct roles. Tone predicts directional returns: a one-standard-deviation dovish shift generates equity gains building to about +12 basis points within two hours. Novelty predicts volatility: the stance-by-novelty interaction on the VIX persists from 5 to 120 minutes (t = -5.06), and stance moves realized volatility in six of seven contracts at the 1% level, while pre-announcement placebo tests are null." | |
| 954 | + ], | |
| 955 | + "findings": [ | |
| 956 | + { | |
| 957 | + "value": "+12 bps", | |
| 958 | + "label": "Equity gain within two hours after a 1σ dovish tone shift" | |
| 959 | + }, | |
| 960 | + { | |
| 961 | + "value": "t = −5.06", | |
| 962 | + "label": "Stance × novelty interaction on VIX, persisting 5–120 minutes" | |
| 963 | + }, | |
| 964 | + { | |
| 965 | + "value": "6 of 7", | |
| 966 | + "label": "Contracts where stance moves realized volatility (p < 0.01)" | |
| 967 | + }, | |
| 968 | + { | |
| 969 | + "value": "148", | |
| 970 | + "label": "FOMC events with 1-minute futures data, 2008–2025" | |
| 971 | + }, | |
| 972 | + { | |
| 973 | + "value": "217", | |
| 974 | + "label": "FOMC statement releases scored for tone and novelty, 2000–2025" | |
| 975 | + }, | |
| 976 | + { | |
| 977 | + "value": "7", | |
| 978 | + "label": "Futures contracts studied: ES, VX, ZN, ZF, DX, CL, GC" | |
| 979 | + } | |
| 980 | + ], | |
| 981 | + "contributions": [ | |
| 982 | + { | |
| 983 | + "title": "Separating policy tone from novelty", | |
| 984 | + "description": "Decomposes FOMC statements into hawkish/dovish tone and informational novelty, showing the two dimensions have distinct, complementary effects on returns and volatility." | |
| 985 | + }, | |
| 986 | + { | |
| 987 | + "title": "Dual-model NLP ensemble for Fed text", | |
| 988 | + "description": "Combines MiniLM and BERT with TSDAE+MNRL fine-tuning and PCA-based reference selection to score 217 FOMC statements from 2000 to 2025." | |
| 989 | + }, | |
| 990 | + { | |
| 991 | + "title": "Tone predicts directional returns", | |
| 992 | + "description": "A one-standard-deviation dovish shift generates equity gains that build to roughly +12 basis points within two hours of the announcement." | |
| 993 | + }, | |
| 994 | + { | |
| 995 | + "title": "Novelty predicts volatility dynamics", | |
| 996 | + "description": "The stance-by-novelty interaction on the VIX persists from 5 to 120 minutes after the release, with a t-statistic of -5.06." | |
| 997 | + }, | |
| 998 | + { | |
| 999 | + "title": "Broad cross-market volatility evidence", | |
| 1000 | + "description": "Policy stance moves realized volatility in six of seven futures contracts at the 1% significance level, spanning equities, rates, currencies, and commodities." | |
| 1001 | + }, | |
| 1002 | + { | |
| 1003 | + "title": "Placebo-validated announcement effects", | |
| 1004 | + "description": "Pre-announcement placebo tests are null, confirming the estimated tone and novelty effects are driven by the FOMC announcements themselves." | |
| 1005 | + } | |
| 1006 | + ], | |
| 1007 | + "data": [ | |
| 1008 | + { | |
| 1009 | + "title": "FOMC statements, 2000–2025", | |
| 1010 | + "description": "217 statement releases scored for hawkish/dovish policy tone and informational novelty via the dual-model NLP ensemble." | |
| 1011 | + }, | |
| 1012 | + { | |
| 1013 | + "title": "1-minute futures data around 148 FOMC events", | |
| 1014 | + "description": "Minute-level prices from 2008 to 2025 for seven contracts: ES, VX, ZN, ZF, DX, CL, and GC." | |
| 1015 | + } | |
| 1016 | + ], | |
| 1017 | + "methodology": [ | |
| 1018 | + { | |
| 1019 | + "title": "NLP ensemble (MiniLM + BERT)", | |
| 1020 | + "description": "A dual-model text ensemble with TSDAE+MNRL fine-tuning and PCA-based reference selection extracts tone and novelty from FOMC statements." | |
| 1021 | + }, | |
| 1022 | + { | |
| 1023 | + "title": "Event regressions and minute-level panels", | |
| 1024 | + "description": "High-frequency event-window regressions and minute-level panel specifications link tone and novelty to returns and realized volatility across contracts." | |
| 1025 | + }, | |
| 1026 | + { | |
| 1027 | + "title": "Jordà local projections", | |
| 1028 | + "description": "Local projections trace the dynamic response of returns and volatility to tone and novelty shocks over the post-announcement window." | |
| 1029 | + }, | |
| 1030 | + { | |
| 1031 | + "title": "Placebo tests and five inference methods", | |
| 1032 | + "description": "Pre-announcement placebo windows and five alternative inference methods validate that the estimated effects are announcement-driven and statistically robust." | |
| 1033 | + } | |
| 1034 | + ], | |
| 1035 | + "reproducibility": [ | |
| 1036 | + "Full LaTeX source in PHD_chapitre3_theses_20260731/ is organized as chapitre3.tex plus sections/, tables/ (24 tables), 19 figures, a mathematical proofs appendix, and master.bib, with the compiled chapitre3.pdf included.", | |
| 1037 | + "The folder is a frozen July 31, 2026 snapshot of the March 6, 2026 manuscript; thesis adaptations live separately in these-ulaval/chapitre3/.", | |
| 1038 | + "In the assembled thesis, the chapter's proofs and extras appear as appendices A–B and its labels are prefixed ch3:, with references merged into the 271-key consolidated bibliography." | |
| 1039 | + ], | |
| 1040 | + "keywords": [ | |
| 1041 | + "FOMC announcements", | |
| 1042 | + "monetary policy tone", | |
| 1043 | + "informational novelty", | |
| 1044 | + "NLP", | |
| 1045 | + "high-frequency finance", | |
| 1046 | + "local projections", | |
| 1047 | + "realized volatility", | |
| 1048 | + "event study" | |
| 1049 | + ] | |
| 1050 | + } | |
| 1051 | +]; | |
added
lib/research.ts
+148 −0
@@ -0,0 +1,148 @@ | ||
| 1 | +/* | |
| 2 | + research.ts | |
| 3 | + spboucher.ai Web | |
| 4 | + Author: Simon-Pierre Boucher | |
| 5 | + Mail: contact@spboucher.ai | |
| 6 | +*/ | |
| 7 | + | |
| 8 | +export interface ResearchPaper { | |
| 9 | + slug: string; | |
| 10 | + kind: "wp" | "chapter"; | |
| 11 | + num: string; | |
| 12 | + pages?: string; | |
| 13 | + title: string; | |
| 14 | + description: string; | |
| 15 | + status?: string; | |
| 16 | + pdf: string; | |
| 17 | + source?: string; | |
| 18 | + repo?: string; | |
| 19 | +} | |
| 20 | + | |
| 21 | +export const uqoWorkingPapers: ResearchPaper[] = [ | |
| 22 | + { | |
| 23 | + slug: "wp2", | |
| 24 | + kind: "wp", | |
| 25 | + num: "UQO Working Paper No. 2", | |
| 26 | + pages: "53 pages", | |
| 27 | + title: | |
| 28 | + "Decoding Real Estate Descriptions: Semantic Embeddings and Hedonic Pricing of Residential Properties in Quebec", | |
| 29 | + description: | |
| 30 | + "Adds sentence-transformer embeddings of listing descriptions to hedonic models of 17,087 Quebec houses — lifting adjusted R² from 0.452 to 0.511 beyond structural attributes alone.", | |
| 31 | + pdf: "/papers/wp2_uqo.pdf", | |
| 32 | + repo: "https://github.com/spboucher-ai/wp2_uqo", | |
| 33 | + }, | |
| 34 | + { | |
| 35 | + slug: "wp3", | |
| 36 | + kind: "wp", | |
| 37 | + num: "UQO Working Paper No. 3", | |
| 38 | + pages: "60 pages", | |
| 39 | + title: | |
| 40 | + "Hedonic Housing Price Models for the United States: A Multi-Method Comparison of Parametric, Quantile, and Machine Learning Approaches", | |
| 41 | + description: | |
| 42 | + "OLS, quantile regression, and gradient-boosting (XGBoost + SHAP) approaches compared on 788,842 Zillow listings covering all 50 states and DC.", | |
| 43 | + pdf: "https://github.com/spboucher-ai/wp3-hedonic-housing-us/blob/main/paper/main.pdf", | |
| 44 | + repo: "https://github.com/spboucher-ai/wp3-hedonic-housing-us", | |
| 45 | + }, | |
| 46 | + { | |
| 47 | + slug: "wp5", | |
| 48 | + kind: "wp", | |
| 49 | + num: "UQO Working Paper No. 5", | |
| 50 | + pages: "51 pages", | |
| 51 | + title: | |
| 52 | + "Airbnb, Residential Rents, and Housing Market Pressure: A Hedonic and Spatial Econometric Analysis", | |
| 53 | + description: | |
| 54 | + "Hedonic, spatial, quantile, and machine-learning evidence from 8,303 Quebec rental listings and 3,456 Airbnb listings — each active Airbnb within 500 m is associated with roughly 0.4% higher asking rent.", | |
| 55 | + pdf: "/papers/wp5_uqo.pdf", | |
| 56 | + repo: "https://github.com/spboucher-ai/wp5_uqo", | |
| 57 | + }, | |
| 58 | + { | |
| 59 | + slug: "wp7", | |
| 60 | + kind: "wp", | |
| 61 | + num: "UQO Working Paper No. 7", | |
| 62 | + pages: "29 pages", | |
| 63 | + title: | |
| 64 | + "The Options-Implied Information Content for Cross-Asset Return and Volatility Prediction: Evidence from 3.8 Billion Option Contracts", | |
| 65 | + description: | |
| 66 | + "Options-implied moments forecast returns and volatility on a panel of 264,383 ticker-days (69 tickers, 2010–2025); a kurtosis long/short strategy delivers a Sharpe ratio of 2.33.", | |
| 67 | + pdf: "/papers/wp7_uqo.pdf", | |
| 68 | + repo: "https://github.com/spboucher-ai/wp7_uqo", | |
| 69 | + }, | |
| 70 | + { | |
| 71 | + slug: "wp9", | |
| 72 | + kind: "wp", | |
| 73 | + num: "UQO Working Paper No. 9", | |
| 74 | + pages: "26 pages", | |
| 75 | + title: | |
| 76 | + "A Grand Hedonic Model of the Canadian Housing Market: Decomposing the Value of Structure and Location", | |
| 77 | + description: | |
| 78 | + "140,931 MLS listings with 1,153 neighbourhood (FSA) fixed effects — location alone adds ~30 points of R² (46% → 77%), valuing held-out homes with a 15.8% median absolute error (OOS R² = 0.764).", | |
| 79 | + pdf: "/papers/wp9_uqo.pdf", | |
| 80 | + repo: "https://github.com/spboucher-ai/wp9_uqo", | |
| 81 | + }, | |
| 82 | + { | |
| 83 | + slug: "wp10", | |
| 84 | + kind: "wp", | |
| 85 | + num: "UQO Working Paper No. 10", | |
| 86 | + pages: "29 pages", | |
| 87 | + title: | |
| 88 | + "The Assessment Gap in Quebec: Vertical and Horizontal Inequity in Municipal Property Valuation", | |
| 89 | + description: | |
| 90 | + "First province-wide audit of property-assessment equity in Canada — 522,769 sales matched to the assessment rolls. 99% of municipalities fail the IAAO uniformity standard; the median dwelling in the bottom local price decile pays ~65% more property tax than uniform assessment would imply.", | |
| 91 | + pdf: "/papers/wp10_uqo.pdf", | |
| 92 | + repo: "https://github.com/spboucher-ai/wp10_uqo", | |
| 93 | + }, | |
| 94 | +]; | |
| 95 | + | |
| 96 | +export const thesisChapters: ResearchPaper[] = [ | |
| 97 | + { | |
| 98 | + slug: "chapter-1", | |
| 99 | + kind: "chapter", | |
| 100 | + num: "Chapter 1", | |
| 101 | + status: "Revised version for The Energy Journal", | |
| 102 | + title: | |
| 103 | + "Speculative Trading in Energy Markets: Evidence from Macroeconomic Surprises", | |
| 104 | + description: | |
| 105 | + "How speculative positioning conditions the reaction of energy futures to macroeconomic news surprises.", | |
| 106 | + pdf: "https://github.com/spboucher-ai/phd-thesis/blob/main/phd_chap1_20260731/main.pdf", | |
| 107 | + source: | |
| 108 | + "https://github.com/spboucher-ai/phd-thesis/tree/main/phd_chap1_20260731", | |
| 109 | + repo: "https://github.com/spboucher-ai/phd-thesis", | |
| 110 | + }, | |
| 111 | + { | |
| 112 | + slug: "chapter-2", | |
| 113 | + kind: "chapter", | |
| 114 | + num: "Chapter 2", | |
| 115 | + status: "Submission version, Journal of Futures Markets", | |
| 116 | + title: | |
| 117 | + "Seeing Through the ETF: Indicative NAV and Commodity Volatility Transmission", | |
| 118 | + description: | |
| 119 | + "Uses intraday indicative NAV to trace how volatility and jumps transmit between commodity ETFs and their underlying assets.", | |
| 120 | + pdf: "https://github.com/spboucher-ai/phd-thesis/blob/main/phd_chap2_20260731/main.pdf", | |
| 121 | + source: | |
| 122 | + "https://github.com/spboucher-ai/phd-thesis/tree/main/phd_chap2_20260731", | |
| 123 | + repo: "https://github.com/spboucher-ai/phd-thesis", | |
| 124 | + }, | |
| 125 | + { | |
| 126 | + slug: "chapter-3", | |
| 127 | + kind: "chapter", | |
| 128 | + num: "Chapter 3", | |
| 129 | + status: "Manuscript, 2026", | |
| 130 | + title: | |
| 131 | + "Returns and Volatility Around FOMC Announcements: A High-Frequency Analysis of Policy Tone and Novelty", | |
| 132 | + description: | |
| 133 | + "High-frequency evidence on how the tone and novelty of FOMC communication move returns and volatility across asset classes.", | |
| 134 | + pdf: "https://github.com/spboucher-ai/phd-thesis/blob/main/PHD_chapitre3_theses_20260731/chapitre3.pdf", | |
| 135 | + source: | |
| 136 | + "https://github.com/spboucher-ai/phd-thesis/tree/main/PHD_chapitre3_theses_20260731", | |
| 137 | + repo: "https://github.com/spboucher-ai/phd-thesis", | |
| 138 | + }, | |
| 139 | +]; | |
| 140 | + | |
| 141 | +export const allResearchPapers: ResearchPaper[] = [ | |
| 142 | + ...uqoWorkingPapers, | |
| 143 | + ...thesisChapters, | |
| 144 | +]; | |
| 145 | + | |
| 146 | +export function getResearchPaper(slug: string): ResearchPaper | undefined { | |
| 147 | + return allResearchPapers.find((p) => p.slug === slug); | |
| 148 | +} | |
modified
package-lock.json
+26 −0
@@ -11,6 +11,7 @@ | ||
| 11 | 11 | "class-variance-authority": "^0.7.1", |
| 12 | 12 | "clsx": "^2.1.1", |
| 13 | 13 | "framer-motion": "^12.0.0", |
| 14 | + "katex": "^0.18.2", | |
| 14 | 15 | "lucide-react": "^0.525.0", |
| 15 | 16 | "next": "^16.0.0", |
| 16 | 17 | "react": "^19.0.0", |
@@ -1140,6 +1141,15 @@ | ||
| 1140 | 1141 | "node": ">=6" |
| 1141 | 1142 | } |
| 1142 | 1143 | }, |
| 1144 | + "node_modules/commander": { | |
| 1145 | + "version": "8.3.0", | |
| 1146 | + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", | |
| 1147 | + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", | |
| 1148 | + "license": "MIT", | |
| 1149 | + "engines": { | |
| 1150 | + "node": ">= 12" | |
| 1151 | + } | |
| 1152 | + }, | |
| 1143 | 1153 | "node_modules/csstype": { |
| 1144 | 1154 | "version": "3.2.3", |
| 1145 | 1155 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", |
@@ -1215,6 +1225,22 @@ | ||
| 1215 | 1225 | "jiti": "lib/jiti-cli.mjs" |
| 1216 | 1226 | } |
| 1217 | 1227 | }, |
| 1228 | + "node_modules/katex": { | |
| 1229 | + "version": "0.18.2", | |
| 1230 | + "resolved": "https://registry.npmjs.org/katex/-/katex-0.18.2.tgz", | |
| 1231 | + "integrity": "sha512-3snve4y0SXTMequLim1FMiPvLGElySam0blN5xQZBqEY3lOJz1TnuaaiugnBZBqHzlSAGmFTYL7MCQkORuLKCA==", | |
| 1232 | + "funding": [ | |
| 1233 | + "https://opencollective.com/katex", | |
| 1234 | + "https://github.com/sponsors/katex" | |
| 1235 | + ], | |
| 1236 | + "license": "MIT", | |
| 1237 | + "dependencies": { | |
| 1238 | + "commander": "^8.3.0" | |
| 1239 | + }, | |
| 1240 | + "bin": { | |
| 1241 | + "katex": "cli.js" | |
| 1242 | + } | |
| 1243 | + }, | |
| 1218 | 1244 | "node_modules/lightningcss": { |
| 1219 | 1245 | "version": "1.32.0", |
| 1220 | 1246 | "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", |
modified
package.json
+1 −0
@@ -14,6 +14,7 @@ | ||
| 14 | 14 | "class-variance-authority": "^0.7.1", |
| 15 | 15 | "clsx": "^2.1.1", |
| 16 | 16 | "framer-motion": "^12.0.0", |
| 17 | + "katex": "^0.18.2", | |
| 17 | 18 | "lucide-react": "^0.525.0", |
| 18 | 19 | "next": "^16.0.0", |
| 19 | 20 | "react": "^19.0.0", |
added
public/cv/Simon-Pierre-Boucher-CV.pdf
+0 −0
Binary file not shown.
added
public/icons/coinexplorer.png
+0 −0
Binary file not shown.
added
public/icons/llmindex.png
+0 −0
Binary file not shown.
added
public/icons/lou-ka.png
+0 −0
Binary file not shown.
added
public/icons/qwhpi-platform.png
+0 −0
Binary file not shown.
added
public/icons/valoplex.png
+0 −0
Binary file not shown.
added
public/icons/vrai-prix.png
+0 −0
Binary file not shown.
added
public/papers/wp10_uqo.pdf
+0 −0
Binary file not shown.
added
public/profile/simon-pierre-boucher.jpeg
+0 −0
Binary file not shown.