initial commit

This commit is contained in:
2026-05-26 09:25:11 -05:00
commit ebbeac2231
15 changed files with 3478 additions and 0 deletions
+117
View File
@@ -0,0 +1,117 @@
const express = require('express');
const fs = require('fs');
const path = require('path');
const sass = require('sass');
const app = express();
const BASE = __dirname;
const DATA_FILE = path.join(BASE, 'data', 'responses.json');
const CSV_FILE = path.join(BASE, 'data', 'responses.csv');
// Compile SCSS on startup
try {
const result = sass.compile(path.join(BASE, 'src', 'styles', 'main.scss'), { style: 'compressed' });
fs.writeFileSync(path.join(BASE, 'public', 'css', 'main.css'), result.css);
console.log('SCSS compiled successfully');
} catch (err) {
console.error('SCSS compile error:', err.message);
}
// Ensure data files exist
if (!fs.existsSync(DATA_FILE)) fs.writeFileSync(DATA_FILE, JSON.stringify([], null, 2));
if (!fs.existsSync(CSV_FILE)) fs.writeFileSync(CSV_FILE, buildCSVHeader() + '\n');
// ──────────────────────────────────────────
// CSV helpers
// ──────────────────────────────────────────
function csvEscape(val) {
const s = String(val ?? '');
if (s.includes(',') || s.includes('"') || s.includes('\n')) {
return '"' + s.replace(/"/g, '""') + '"';
}
return s;
}
function buildCSVHeader() {
const cols = ['timestamp', 'country', 'age', 'gender'];
for (let i = 1; i <= 15; i++) cols.push(`case_${i}_likert`);
for (let i = 1; i <= 15; i++) cols.push(`case_${i}_order`);
for (let i = 1; i <= 15; i++) cols.push(`case_${i}_ms`);
cols.push('ahp_w_stability','ahp_w_revenue','ahp_w_capital','ahp_w_plan','ahp_w_social');
cols.push('ahp_lambda_max','ahp_CI','ahp_CR','ahp_cr_warning');
for (let i = 1; i <= 15; i++) cols.push(`match_case_${i}`);
cols.push('post_q1','post_q2','post_q3','post_q4','open_response');
return cols.join(',');
}
function buildCSVRow(payload) {
const fields = [];
fields.push(payload.meta.timestamp, csvEscape(payload.meta.country), csvEscape(payload.meta.age), csvEscape(payload.meta.gender));
// Case judgments ordered by case_id 1-15
const byCase = {};
(payload.intuitive_judgments || []).forEach(j => { byCase[j.case_id] = j; });
for (let i = 1; i <= 15; i++) fields.push(byCase[i]?.judgment ?? '');
for (let i = 1; i <= 15; i++) fields.push(byCase[i]?.presented_order ?? '');
for (let i = 1; i <= 15; i++) fields.push(byCase[i]?.response_time_ms ?? '');
// AHP
const ahp = payload.ahp || {};
(ahp.weights || [0,0,0,0,0]).forEach(w => fields.push(Number(w).toFixed(4)));
fields.push(Number(ahp.lambda_max).toFixed(4));
fields.push(Number(ahp.CI).toFixed(4));
fields.push(Number(ahp.CR).toFixed(4));
fields.push(ahp.cr_warning_shown ? 1 : 0);
// Match by case_id
const matchByCase = {};
(payload.comparison || []).forEach(c => { matchByCase[c.case_id] = c.match; });
for (let i = 1; i <= 15; i++) {
const m = matchByCase[i];
fields.push(m === true ? 1 : m === false ? 0 : '');
}
// Post survey
const ps = payload.post_survey || {};
fields.push(ps.q1 ?? '', ps.q2 ?? '', ps.q3 ?? '', ps.q4 ?? '');
fields.push(csvEscape(ps.open_response ?? ''));
return fields.join(',');
}
// ──────────────────────────────────────────
// Routes
// ──────────────────────────────────────────
app.use(express.json({ limit: '1mb' }));
app.use(express.static(path.join(BASE, 'public')));
app.post('/api/submit', (req, res) => {
try {
// Save JSON
const data = JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'));
const submission = { ...req.body, server_timestamp: Date.now() };
data.push(submission);
fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
// Append CSV row
const row = buildCSVRow(submission);
fs.appendFileSync(CSV_FILE, row + '\n');
res.json({ success: true, id: data.length });
} catch (err) {
console.error('Submit error:', err);
res.status(500).json({ error: err.message });
}
});
app.get('/api/responses', (req, res) => {
try {
res.json(JSON.parse(fs.readFileSync(DATA_FILE, 'utf8')));
} catch (err) {
res.status(500).json({ error: err.message });
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running at http://localhost:${PORT}`));