Accept pasted Scattermind concept maps
This commit is contained in:
@@ -468,6 +468,55 @@ function objectFrom(value) {
|
||||
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
||||
}
|
||||
|
||||
function looksLikeRankPayload(value = {}) {
|
||||
return Boolean(
|
||||
value.schema
|
||||
|| value.featureSet
|
||||
|| value.conceptMap
|
||||
|| value.lenses
|
||||
|| value.reference_code
|
||||
|| value.referenceCode
|
||||
|| value.artifactId
|
||||
|| value.sourceArtifactId
|
||||
|| value.ideaText
|
||||
|| Array.isArray(value.features)
|
||||
|| Array.isArray(value.actions)
|
||||
|| Array.isArray(value.nextMoves)
|
||||
|| Array.isArray(value.candidates)
|
||||
);
|
||||
}
|
||||
|
||||
function parseEmbeddedRankPayload(value = '') {
|
||||
const text = cleanMultiline(value, 12000);
|
||||
if (!text.startsWith('{') || !text.endsWith('}')) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(text);
|
||||
return looksLikeRankPayload(parsed) ? parsed : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function expandEmbeddedRankPayload(body = {}) {
|
||||
const original = objectFrom(body);
|
||||
for (const key of ['payload', 'rankPayload', 'scattermindPayload', 'conceptMapJson', 'idea', 'ideaText', 'optionsText']) {
|
||||
const embedded = typeof original[key] === 'string'
|
||||
? parseEmbeddedRankPayload(original[key])
|
||||
: looksLikeRankPayload(original[key])
|
||||
? original[key]
|
||||
: null;
|
||||
if (!embedded) continue;
|
||||
const expanded = { ...original, ...embedded };
|
||||
if (key === 'idea' && !embedded.idea && !embedded.ideaText) expanded.idea = '';
|
||||
if (key === 'optionsText' && !embedded.optionsText) expanded.optionsText = '';
|
||||
if (original.mode && !embedded.mode) expanded.mode = original.mode;
|
||||
if (original.context && !embedded.context) expanded.context = original.context;
|
||||
expanded._embeddedPayloadSource = key;
|
||||
return expanded;
|
||||
}
|
||||
return original;
|
||||
}
|
||||
|
||||
function cleanProvenance(input = {}) {
|
||||
const featureSet = objectFrom(input.featureSet);
|
||||
const artifact = objectFrom(input.artifact || featureSet.artifact);
|
||||
@@ -519,10 +568,10 @@ function cleanDecisionContext(input = {}) {
|
||||
const artifact = objectFrom(input.artifact || featureSet.artifact);
|
||||
const conceptMap = objectFrom(input.conceptMap || featureSet.conceptMap || artifact.conceptMap);
|
||||
const conceptMapLenses = objectFrom(conceptMap.lenses || input.lenses || featureSet.lenses);
|
||||
const riskLens = objectFrom(conceptMapLenses.risk || conceptMapLenses.risks || conceptMapLenses.boundaries || conceptMapLenses.notYet);
|
||||
const audienceLens = objectFrom(conceptMapLenses.audience || conceptMapLenses.who || conceptMapLenses.customer || conceptMapLenses.users);
|
||||
const constraintsLens = objectFrom(conceptMapLenses.constraints || conceptMapLenses.boundaries || conceptMapLenses.scope);
|
||||
const assumptionsLens = objectFrom(conceptMapLenses.assumptions || conceptMapLenses.unknowns || conceptMapLenses.openQuestions);
|
||||
const riskLens = conceptMapLenses.risk || conceptMapLenses.risks || conceptMapLenses.boundaries || conceptMapLenses.notYet;
|
||||
const audienceLens = conceptMapLenses.audience || conceptMapLenses.who || conceptMapLenses.customer || conceptMapLenses.users;
|
||||
const constraintsLens = conceptMapLenses.constraints || conceptMapLenses.boundaries || conceptMapLenses.scope;
|
||||
const assumptionsLens = conceptMapLenses.assumptions || conceptMapLenses.unknowns || conceptMapLenses.openQuestions;
|
||||
const structuredContext = objectFrom(input.context);
|
||||
const contextSources = [
|
||||
input.decisionContext,
|
||||
@@ -1050,13 +1099,14 @@ function createHandoffContract({ ranked, provenance, decisionContext }) {
|
||||
}
|
||||
|
||||
app.post('/api/rank-feedback', (req, res) => {
|
||||
const idea = cleanMultiline(req.body?.idea || '', 3000);
|
||||
const context = cleanContextText(req.body?.context || '');
|
||||
const modeId = cleanText(req.body?.mode || 'progress', 40);
|
||||
const body = expandEmbeddedRankPayload(req.body || {});
|
||||
const idea = cleanMultiline(body?.idea || '', 3000);
|
||||
const context = cleanContextText(body?.context || '');
|
||||
const modeId = cleanText(body?.mode || 'progress', 40);
|
||||
const mode = judgementModes[modeId] || judgementModes.progress;
|
||||
const provenance = cleanProvenance(req.body || {});
|
||||
const decisionContext = cleanDecisionContext(req.body || {});
|
||||
let options = optionsFromBody(req.body || {});
|
||||
const provenance = cleanProvenance(body || {});
|
||||
const decisionContext = cleanDecisionContext(body || {});
|
||||
let options = optionsFromBody(body || {});
|
||||
if (options.length < 2) return res.status(400).json({ error: 'Paste at least two options, features, ideas, or next moves to rank.' });
|
||||
const bridgeContext = `${idea}\n${context}\n${provenance.snapshotTitle}\n${provenance.schema}\n${decisionContext.targetAudience}\n${decisionContext.constraints.join('\n')}\n${decisionContext.assumptions.join('\n')}`;
|
||||
options = options.map(option => ({ ...option, metrics: scoreOption(option, mode, bridgeContext, decisionContext) }))
|
||||
@@ -1081,7 +1131,7 @@ app.post('/api/rank-feedback', (req, res) => {
|
||||
res.json({
|
||||
ok: true,
|
||||
mode: { id: modeId in judgementModes ? modeId : 'progress', label: mode.label },
|
||||
input: { idea, context, optionCount: options.length, provenance, decisionContext },
|
||||
input: { idea, context, optionCount: options.length, provenance, decisionContext, embeddedPayloadSource: body._embeddedPayloadSource || '' },
|
||||
ranked: options,
|
||||
brief,
|
||||
rankConfidence: rankConfidenceFor(options),
|
||||
|
||||
Reference in New Issue
Block a user