Skip to content

Commit 0aec099

Browse files
author
psriraj3
committed
PDFs generation problem fixed
1 parent 39444bd commit 0aec099

10 files changed

Lines changed: 1472 additions & 472 deletions

File tree

dist/assets/index-CNIGOKbX.js

Lines changed: 287 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/assets/index-DUl6GAzZ.js

Lines changed: 0 additions & 321 deletions
This file was deleted.

dist/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
1010
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
1111
<title>FUNton</title>
12-
<script type="module" crossorigin src="/assets/index-DUl6GAzZ.js"></script>
12+
<script type="module" crossorigin src="/assets/index-CNIGOKbX.js"></script>
1313
<link rel="stylesheet" crossorigin href="/assets/index-DmekIFy3.css">
1414
</head>
1515
<body>

node_modules/.tmp/tsconfig.app.tsbuildinfo

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/generate.ts

Lines changed: 248 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,39 +10,222 @@ function makeSeed(): string {
1010
return `${Date.now()}_${Math.random().toString(16).slice(2)}`;
1111
}
1212

13-
export async function generateSet(params: {
13+
/** =========================
14+
* Robust question signature + answerKey normalization
15+
* ========================= */
16+
17+
function norm(s: string) {
18+
return s.trim().toLowerCase().replace(/\s+/g, " ");
19+
}
20+
21+
function getQuestionText(q: any): string {
22+
return String(q?.question ?? q?.prompt ?? q?.stem ?? q?.text ?? q?.q ?? "").trim();
23+
}
24+
25+
function getOptions(q: any): string[] {
26+
const opts = q?.options ?? q?.choices ?? q?.mcqOptions ?? q?.answers;
27+
return Array.isArray(opts) ? opts.map((x: any) => String(x)) : [];
28+
}
29+
30+
function getQuestionId(q: any): string | null {
31+
const v =
32+
q?.id ??
33+
q?.qid ??
34+
q?.questionId ??
35+
q?.uid ??
36+
q?.uuid ??
37+
q?.key ??
38+
q?.slug ??
39+
q?.meta?.id ??
40+
q?.meta?.qid ??
41+
q?.meta?.questionId;
42+
43+
if (typeof v === "string" && v.trim()) return v.trim();
44+
if (typeof v === "number" && Number.isFinite(v)) return String(v);
45+
return null;
46+
}
47+
48+
function getContextSnippet(set: any, q: any): string {
49+
const ctx =
50+
q?.context ??
51+
q?.passage ??
52+
q?.stimulus ??
53+
q?.story ??
54+
q?.reading ??
55+
q?.instructions ??
56+
q?.meta?.context ??
57+
set?.context ??
58+
set?.passage ??
59+
set?.stimulus ??
60+
set?.story ??
61+
set?.reading ??
62+
set?.instructions ??
63+
"";
64+
const s = String(ctx ?? "").trim();
65+
if (!s) return "";
66+
// keep signature stable but short
67+
return norm(s).slice(0, 180);
68+
}
69+
70+
function questionSignature(set: any, q: any): string {
71+
const id = getQuestionId(q);
72+
if (id) return `id:${id}`;
73+
74+
const t = norm(getQuestionText(q));
75+
const o = getOptions(q).map(norm).join("|");
76+
const c = getContextSnippet(set, q);
77+
return `t:${t}::o:${o}::c:${c}`;
78+
}
79+
80+
function normalizeAnswerKeyEntry(entry: any, questionId: string | null) {
81+
// Normalize everything into { questionId, answer, ...rest }
82+
const qid = questionId ?? "";
83+
84+
if (entry && typeof entry === "object" && !Array.isArray(entry)) {
85+
// Already has questionId
86+
if (typeof entry.questionId === "string" || typeof entry.questionId === "number") {
87+
return {
88+
...entry,
89+
questionId: String(entry.questionId),
90+
answer: entry.answer ?? entry.value ?? entry.correct ?? entry.solution ?? entry.expected ?? entry,
91+
};
92+
}
93+
94+
// Might be keyed differently
95+
const maybeId = entry.id ?? entry.qid ?? entry.questionID ?? entry.question_id;
96+
const maybeAnswer = entry.answer ?? entry.value ?? entry.correct ?? entry.solution ?? entry.expected;
97+
98+
return {
99+
...entry,
100+
questionId: String(maybeId ?? qid),
101+
answer: maybeAnswer ?? entry,
102+
};
103+
}
104+
105+
// primitive (string/number/etc.)
106+
return { questionId: String(qid), answer: entry };
107+
}
108+
109+
function buildAnswerKeyMap(answerKey: any[]): Map<string, any> {
110+
const m = new Map<string, any>();
111+
for (const k of answerKey) {
112+
if (k && typeof k === "object" && !Array.isArray(k) && (k.questionId != null || k.id != null || k.qid != null)) {
113+
const id = k.questionId ?? k.id ?? k.qid;
114+
if (id != null) m.set(String(id), k);
115+
}
116+
}
117+
return m;
118+
}
119+
120+
function dedupeSet(base: GeneratedSet, desiredCount: number): GeneratedSet {
121+
const qs: any[] = Array.isArray((base as any).questions) ? ((base as any).questions as any[]) : [];
122+
const ak: any[] = Array.isArray((base as any).answerKey) ? ((base as any).answerKey as any[]) : [];
123+
124+
const akMap = buildAnswerKeyMap(ak);
125+
const hasParallelIndexKey = ak.length === qs.length && akMap.size === 0;
126+
127+
const seen = new Set<string>();
128+
const outQ: any[] = [];
129+
const outAK: any[] = [];
130+
131+
for (let i = 0; i < qs.length; i++) {
132+
const q = qs[i];
133+
const sig = questionSignature(base, q);
134+
if (seen.has(sig)) continue;
135+
seen.add(sig);
136+
137+
outQ.push(q);
138+
139+
const qid = getQuestionId(q);
140+
const rawKey =
141+
(qid ? akMap.get(String(qid)) : undefined) ??
142+
(hasParallelIndexKey ? ak[i] : undefined);
143+
144+
outAK.push(normalizeAnswerKeyEntry(rawKey, qid));
145+
146+
if (outQ.length >= desiredCount) break;
147+
}
148+
149+
return {
150+
...base,
151+
questions: outQ as any,
152+
answerKey: outAK as any,
153+
};
154+
}
155+
156+
function mergeUnique(into: GeneratedSet, extra: GeneratedSet, desiredCount: number): GeneratedSet {
157+
const merged = {
158+
...into,
159+
questions: [...(into.questions as any[])],
160+
answerKey: [...(into.answerKey as any[])],
161+
} as GeneratedSet;
162+
163+
const seen = new Set<string>();
164+
for (const q of merged.questions as any[]) {
165+
seen.add(questionSignature(merged, q));
166+
}
167+
168+
const extraQs: any[] = Array.isArray((extra as any).questions) ? ((extra as any).questions as any[]) : [];
169+
const extraAk: any[] = Array.isArray((extra as any).answerKey) ? ((extra as any).answerKey as any[]) : [];
170+
const extraMap = buildAnswerKeyMap(extraAk);
171+
const hasParallelIndexKey = extraAk.length === extraQs.length && extraMap.size === 0;
172+
173+
for (let i = 0; i < extraQs.length; i++) {
174+
if ((merged.questions as any[]).length >= desiredCount) break;
175+
176+
const q = extraQs[i];
177+
const sig = questionSignature(extra, q);
178+
if (seen.has(sig)) continue;
179+
seen.add(sig);
180+
181+
(merged.questions as any[]).push(q);
182+
183+
const qid = getQuestionId(q);
184+
const rawKey =
185+
(qid ? extraMap.get(String(qid)) : undefined) ??
186+
(hasParallelIndexKey ? extraAk[i] : undefined);
187+
188+
(merged.answerKey as any[]).push(normalizeAnswerKeyEntry(rawKey, qid));
189+
}
190+
191+
return merged;
192+
}
193+
194+
/** =========================
195+
* Raw generator (same logic as before)
196+
* ========================= */
197+
198+
async function generateRaw(params: {
14199
subject: Subject;
15200
ukYear: number;
16201
difficulty: number;
17202
count: number;
18-
seed?: string;
203+
seed: string;
19204
topic?: MathsTopic;
20205
}): Promise<GeneratedSet> {
21-
const seed = params.seed ?? makeSeed();
22-
23206
// ✅ Maths stays on the maths-only generator
24207
if (params.subject === "maths") {
25208
return generateMathSet({
26209
ukYear: params.ukYear,
27210
difficulty: params.difficulty,
28211
count: params.count,
29-
seed,
212+
seed: params.seed,
30213
topic: params.topic,
31214
});
32215
}
33216

34-
// ✅ English uses the new procedural generator (no packs)
217+
// ✅ English uses the procedural generator (no packs)
35218
if (params.subject === "english") {
36219
return generateEnglishSet({
37220
ukYear: params.ukYear,
38221
difficulty: params.difficulty,
39222
count: params.count,
40-
seed,
223+
seed: params.seed,
41224
});
42225
}
43226

44-
// ✅ Everything else continues using packs (science, etc.)
45-
const rng = createRng(seed);
227+
// ✅ Everything else uses packs
228+
const rng = createRng(params.seed);
46229
const bank = await loadPack(params.subject, params.ukYear, rng);
47230

48231
const out = generateFromBank({
@@ -55,11 +238,66 @@ export async function generateSet(params: {
55238
});
56239

57240
return {
58-
seed,
241+
seed: params.seed,
59242
subject: params.subject,
60243
ukYear: params.ukYear,
61244
difficulty: params.difficulty,
62245
questions: out.questions,
63246
answerKey: out.answerKey,
64247
};
65248
}
249+
250+
/** =========================
251+
* Public API: generateSet (now unique)
252+
* ========================= */
253+
254+
export async function generateSet(params: {
255+
subject: Subject;
256+
ukYear: number;
257+
difficulty: number;
258+
count: number;
259+
seed?: string;
260+
topic?: MathsTopic;
261+
}): Promise<GeneratedSet> {
262+
const seed = params.seed ?? makeSeed();
263+
264+
// First pass
265+
let base = await generateRaw({
266+
subject: params.subject,
267+
ukYear: params.ukYear,
268+
difficulty: params.difficulty,
269+
count: params.count,
270+
seed,
271+
topic: params.topic,
272+
});
273+
274+
// Deduplicate
275+
base = dedupeSet(base, params.count);
276+
277+
// Top-up if needed (generate extra with seed variants)
278+
let attempt = 0;
279+
while ((base.questions as any[]).length < params.count && attempt < 6) {
280+
attempt++;
281+
const needed = params.count - (base.questions as any[]).length;
282+
283+
const extra = await generateRaw({
284+
subject: params.subject,
285+
ukYear: params.ukYear,
286+
difficulty: params.difficulty,
287+
count: Math.max(needed * 2, needed), // ask for a bit more to beat duplicates
288+
seed: `${seed}__topup${attempt}`,
289+
topic: params.topic,
290+
});
291+
292+
base = mergeUnique(base, dedupeSet(extra, extra.questions.length), params.count);
293+
}
294+
295+
// Keep the original seed in the final object (for UI consistency)
296+
return {
297+
...base,
298+
seed,
299+
subject: params.subject,
300+
ukYear: params.ukYear,
301+
difficulty: params.difficulty,
302+
};
303+
}

0 commit comments

Comments
 (0)