Template pipeline: JS→Python -arvomuunnokset korjattu

Ongelma: generoiduissa Python-tiedostoissa JS-booleanit (false/true)
päätyvät sellaisenaan Python-koodiin, jossa ne eivät ole valideja.
Lisäksi datetime-importit puuttuivat kun LLM antoi extra_imports-kentässä
pelkän "datetime"-merkkijonon eikä kokonaista import-lausetta.

Korjaukset:
- pyLiteral(): muuntaa JS-arvot Python-literaaleiksi (false→False jne.)
- pyJsonLiteral(): testidatan serialisointi Python-dict-muodossa
- tmplSchemas: datetime-importit tunnistetaan automaattisesti kentistä
- tmplModels + tmplSchemas: oletusarvot pyLiteral()-funktion kautta
- tmplTests: JSON.stringify korvattu pyJsonLiteral():lla
- Validaattori: tunnistaa nyt datetime-import-puutteet ja JS-booleanit

Testattu: molemmat aiemmin rikkinäiset speksit generoivat nyt toimivan
koodin — 6/6 pytest-testiä läpi molemmilla.
This commit is contained in:
2026-04-13 12:43:39 +03:00
parent 68c7195d54
commit b88a741f85
3 changed files with 55 additions and 5 deletions

View File

@@ -193,6 +193,19 @@ import Settings from "../components/Settings.astro";
if (fname === 'schemas.py') {
if (/:\s*date\b/.test(code) && !/from datetime import/.test(code))
issues.push('ISSUE: schemas.py: käyttää date-tyyppiä mutta "from datetime import date" puuttuu');
if (/:\s*datetime\b/.test(code) && !/from datetime import/.test(code))
issues.push('ISSUE: schemas.py: käyttää datetime-tyyppiä mutta "from datetime import datetime" puuttuu');
}
// 4b. Python-syntaksi: JS-booleanit (false/true ilman isoa alkukirjainta)
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (/^\s*#/.test(line) || /^\s*$/.test(line)) continue;
// Etsi false/true joita ei ole merkkijonon sisällä ja jotka eivät ole osa isompaa sanaa
if (/(?<!["\w])false(?![\w"])/.test(line))
issues.push(`ISSUE: ${fname}:${i+1}: "false" ei ole Python — pitäisi olla "False"`);
if (/(?<!["\w])true(?![\w"])/.test(line))
issues.push(`ISSUE: ${fname}:${i+1}: "true" ei ole Python — pitäisi olla "True"`);
}
// 5. test_main.py: ei saa uudelleenmääritellä appia tai modeleita
@@ -1121,6 +1134,29 @@ OUTPUT FORMAT:
// === Template Pipeline — rakennuspalaset ===
// JS-arvo → Python-literaali: false→False, true→True, "teksti"→'"teksti"'
function pyLiteral(val) {
if (val === true) return 'True';
if (val === false) return 'False';
if (val === null || val === undefined) return 'None';
if (typeof val === 'string') return `"${val}"`;
return String(val);
}
// JSON-merkkijono jossa booleanit on Python-muodossa: {"a": True, "b": False}
function pyJsonLiteral(obj) {
const parts = Object.entries(obj).map(([k, v]) => {
let pyVal;
if (v === true) pyVal = 'True';
else if (v === false) pyVal = 'False';
else if (v === null) pyVal = 'None';
else if (typeof v === 'string') pyVal = `"${v}"`;
else pyVal = String(v);
return `"${k}":${pyVal}`;
});
return '{' + parts.join(',') + '}';
}
const SPEC_SYSTEM = `You are a software architect who designs database schemas for Python web applications.
THINK STEP BY STEP before outputting JSON:
@@ -1179,7 +1215,7 @@ Blog → Author: name,email,bio(Text|None) / Post: title, content(Text), author_
for (const f of e.fields) {
let parts = [`Column(${f.sa_type}`];
if (!f.nullable) parts.push('nullable=False');
if (f.default !== null && f.default !== undefined) parts.push(typeof f.default === 'string' ? `default="${f.default}"` : `default=${f.default}`);
if (f.default !== null && f.default !== undefined) parts.push(`default=${pyLiteral(f.default)}`);
code += ` ${f.name} = ${parts.join(', ')})\n`;
}
code += '\n';
@@ -1189,13 +1225,27 @@ Blog → Author: name,email,bio(Text|None) / Post: title, content(Text), author_
}
function tmplSchemas(spec) {
// Tunnista tarvittavat datetime-importit kenttätyypeistä
const dtTypes = new Set();
for (const e of spec.entities) for (const f of e.fields) {
if (/\bdate\b/i.test(f.py_type) && !/datetime/.test(f.py_type)) dtTypes.add('date');
if (/\bdatetime\b/i.test(f.py_type)) dtTypes.add('datetime');
}
let code = 'from pydantic import BaseModel\n';
for (const imp of (spec.extra_imports || [])) code += imp + '\n';
if (dtTypes.size > 0) code += `from datetime import ${[...dtTypes].sort().join(', ')}\n`;
// extra_imports: suodata pois pelkät nimet kuten "datetime" (jo käsitelty yllä)
for (const imp of (spec.extra_imports || [])) {
if (/^(date|datetime)$/.test(imp.trim())) continue; // käsitelty jo
if (/^from\s/.test(imp) || /^import\s/.test(imp)) code += imp + '\n';
}
code += '\n';
for (const e of spec.entities) {
code += `class ${e.name}Create(BaseModel):\n`;
for (const f of e.fields) {
if (f.default !== null && f.default !== undefined) code += ` ${f.name}: ${f.py_type} = ${typeof f.default === 'string' ? '"'+f.default+'"' : f.default}\n`;
if (f.default !== null && f.default !== undefined) code += ` ${f.name}: ${f.py_type} = ${pyLiteral(f.default)}\n`;
else if (f.nullable && f.py_type.includes('None')) code += ` ${f.name}: ${f.py_type} = None\n`;
else code += ` ${f.name}: ${f.py_type}\n`;
}
@@ -1253,11 +1303,11 @@ Blog → Author: name,email,bio(Text|None) / Post: title, content(Text), author_
else if (f.py_type.includes('bool')) testData[f.name] = true;
else if (f.py_type.includes('date')) testData[f.name] = '2024-01-15';
}
const td = JSON.stringify(testData);
const td = pyJsonLiteral(testData);
const firstStr = e.fields.find(f => f.py_type.includes('str') && f.name !== 'status');
const updateData = {...testData};
if (firstStr) updateData[firstStr.name] = `Updated ${firstStr.name}`;
const ud = JSON.stringify(updateData);
const ud = pyJsonLiteral(updateData);
code += `def test_create_${lo}():\n response = client.post('/${tb}/', json=${td})\n assert response.status_code == 201\n assert 'id' in response.json()\n\n`;
code += `def test_list_${lo}s():\n client.post('/${tb}/', json=${td})\n response = client.get('/${tb}/')\n assert response.status_code == 200\n assert len(response.json()) >= 1\n\n`;

Binary file not shown.

Binary file not shown.