Simplify Stage 8 syndication and derive Advisor Stage 6 complexity

- Stage 8: replace 3x3 complexity x EAN grid with single syndicationType
  dropdown -- Salsify Prep Only (4d), Syndication PDP (5d), Syndication
  Non-PDP (3d). Advisor maps contentType to PDP/Non-PDP only; Salsify
  Prep Only is not surfaced in the Advisor (team decision).
- Stage 6 (Advisor): derive complexity from staticWorkType / videoWorkType
  / needsHTML toggles via deriveProductionComplexity helper. Precedence
  bespoke > creation > complex > simple, max across enabled toggles.
  HTML-only falls back to complex (placeholder, may revisit).
- Bump config.json cache-bust to 2026050801.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Phil Dore 2026-05-08 13:24:02 +01:00
parent 5e301b68c1
commit 586cb57155
4 changed files with 63 additions and 65 deletions

View file

@ -359,39 +359,17 @@
"stage8": {
"label": "8. Syndication",
"tooltip": "Syndication complexity and EAN count determine the timeline. Complex syndication (Brand Store) can take 2-4 weeks.",
"tooltip": "Syndication type determines the base days. Salsify Prep Only = 4d, Syndication PDP = 5d, Syndication Non-PDP = 3d.",
"fields": {
"eanVolume": {
"label": "Estimated Number of EANs",
"tooltip": "Number of EANs impacts the Syndication task timeline the most.",
"syndicationType": {
"label": "Syndication Type",
"tooltip": "Type of syndication work required — drives the base days.",
"options": [
{ "value": "1_5", "label": "1 - 5 EANs", "impactPercent": 0 },
{ "value": "5_10", "label": "5 - 10 EANs", "impactPercent": 50 },
{ "value": "10_15", "label": "10 - 15 EANs", "impactPercent": 100 }
{ "value": "salsify_prep_only", "label": "Salsify Prep Only", "days": 4, "definition": "Prepping PDP content in Salsify, completing any necessary workflows or manual asset selections." },
{ "value": "syndication_pdp", "label": "Syndication PDP", "days": 5, "definition": "Prepping PDP content in Salsify, extracting information from Salsify through channels, loading into SharePoint or a retailer portal and sending any necessary communications." },
{ "value": "syndication_non_pdp", "label": "Syndication Non-PDP", "days": 3, "definition": "Sharing non-PDP information directly with a retailer." }
],
"default": "1_5"
},
"complexity": {
"label": "Syndication Complexity",
"tooltip": "Complexity of the syndication work required.",
"options": [
{ "value": "simple", "label": "Simple", "definition": "Salsify Enrichment (adding PDP, Copy) + Standard Syndication (downloading assets, Salsify channel excel, SharePoint, email distribution). Caveat: Assuming all info is available and Salsify channels are set up." },
{ "value": "mid", "label": "Mid", "definition": "Standard Syndication plus: ASIN creation, BTF content creation/maintenance (e.g., Amazon Comparison Table), Virtual Bundle creation/maintenance (Amazon only)." },
{ "value": "complex", "label": "Complex", "definition": "Bespoke tasks for Amazon: Brand Store Creation or Brand Store Maintenance. Takes 2-4 weeks." }
],
"default": "simple"
},
"crossReferenceTable": {
"_comment": "Syndication days by complexity x EAN volume. Key format: complexity_eanVolume",
"simple_1_5": 3,
"simple_5_10": 5,
"simple_10_15": 7,
"mid_1_5": 4,
"mid_5_10": 6,
"mid_10_15": 8,
"complex_1_5": 5,
"complex_5_10": 7,
"complex_10_15": 9
"default": "syndication_pdp"
}
}
}

View file

@ -567,15 +567,11 @@
<div class="stage-fields grid gap-4 sm:grid-cols-2">
<div>
<label class="block text-sm font-medium mb-1 tooltip-trigger relative">
Estimated No. of EANs
Syndication Type
<span class="ml-1 inline-flex items-center justify-center w-4 h-4 rounded-full bg-gray-200 dark:bg-gray-600 text-xs cursor-help">i</span>
<span class="tooltip-content hidden absolute bottom-full left-0 mb-1 px-3 py-2 bg-gray-900 text-white text-xs rounded-lg shadow-lg w-56 z-10">Number of EANs impacts the Syndication task timeline the most.</span>
<span class="tooltip-content hidden absolute bottom-full left-0 mb-1 px-3 py-2 bg-gray-900 text-white text-xs rounded-lg shadow-lg w-64 z-10">Salsify Prep Only = 4d, Syndication PDP = 5d, Syndication Non-PDP = 3d.</span>
</label>
<select id="stage8EanVolume" class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm focus:ring-2 focus:ring-brand-500 outline-none"></select>
</div>
<div>
<label class="block text-sm font-medium mb-1">Syndication Complexity</label>
<select id="stage8Complexity" class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm focus:ring-2 focus:ring-brand-500 outline-none"></select>
<select id="stage8SyndicationType" class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm focus:ring-2 focus:ring-brand-500 outline-none"></select>
</div>
<div>
<label class="block text-sm font-medium mb-1">Syndication Base Days</label>

View file

@ -64,7 +64,7 @@ document.addEventListener('DOMContentLoaded', async () => {
async function loadConfig() {
try {
const res = await fetch('config.json?v=2026050601');
const res = await fetch('config.json?v=2026050801');
CONFIG = await res.json();
} catch (e) {
console.error('Failed to load config.json:', e);
@ -228,6 +228,30 @@ function determineBriefType() {
return 'country_pull_simple';
}
// ---- Derive Stage 6 production complexity from Advisor toggles ----
// Precedence: bespoke > creation > complex > simple — take the max across enabled toggles.
// HTML has no sub-type, so when HTML is the ONLY production toggle it falls back to 'complex'
// (team decision 2026-05-08; revisit if HTML needs its own production track).
function deriveProductionComplexity({ needsStatic, needsVideo, needsHTML, staticWorkType, videoWorkType }) {
const order = ['simple', 'complex', 'creation', 'bespoke'];
const rank = { simple: 0, complex: 1, creation: 2, bespoke: 3 };
let level = -1;
if (needsStatic) {
const map = { simple: 'simple', adaptation: 'complex', creation: 'creation' };
level = Math.max(level, rank[map[staticWorkType] || 'simple']);
}
if (needsVideo) {
const map = { adapting: 'complex', new_asset: 'creation' };
level = Math.max(level, rank[map[videoWorkType] || 'complex']);
}
if (needsHTML && level < 0) {
level = rank.complex;
}
return level >= 0 ? order[level] : 'simple';
}
// ---- Business Day Arithmetic ----
function addBusinessDays(startDate, days) {
const result = new Date(startDate);
@ -316,12 +340,19 @@ function calculateAndRender(overrideBriefId) {
matrix[4] = needsTranslation; // Translation (Asset)
matrix[7] = needsSyndication; // Syndication
// Urgent Brief scenario: eventing + 030 assets + static resizing/cropping only
// Production toggles + work-type radios — needed for Stage 6 complexity derivation
const contentType = document.getElementById('contentType').value;
const needsStatic = document.getElementById('needsStatic').checked;
const needsVideo = document.getElementById('needsVideo').checked;
const needsHTML = document.getElementById('needsHTML').checked;
const staticWorkType = document.querySelector('input[name="staticWorkType"]:checked')?.value;
const videoWorkType = document.querySelector('input[name="videoWorkType"]:checked')?.value;
// Urgent Brief scenario: eventing + 030 assets + static resizing/cropping only
const isUrgentScenario = briefId === 'urgent_brief' &&
document.getElementById('contentType').value === 'eventing' &&
contentType === 'eventing' &&
assetVolume === '0_50' &&
document.getElementById('needsStatic').checked &&
needsStatic &&
staticWorkType === 'simple';
// Combined feedback days from production toggles (static/video/html)
@ -377,7 +408,7 @@ function calculateAndRender(overrideBriefId) {
if (isUrgentScenario) {
return { wip: 1, feedback: fb.static, revisions: 0, rounds: 0 };
}
const complexity = cfg.stage6.fields.complexity.default;
const complexity = deriveProductionComplexity({ needsStatic, needsVideo, needsHTML, staticWorkType, videoWorkType });
const key = complexity + '_' + assetVolume;
const table = cfg.stage6.fields.crossReferenceTable;
const prodFeedback = productionFeedback > 0 ? productionFeedback : cfg.stage6.fields.marketApprovalDays.default;
@ -396,13 +427,12 @@ function calculateAndRender(overrideBriefId) {
stages.push(s7);
currentDate = s7.end;
// Stage 8: Syndication
// Stage 8: Syndication — Advisor only chooses PDP vs Non-PDP from contentType.
// Salsify Prep Only is not surfaced here (team decision 2026-05-08).
const s8 = calcStage(7, matrix[7], currentDate, () => {
const complexity = cfg.stage8.fields.complexity.default;
const eanVolume = cfg.stage8.fields.eanVolume.default;
const key = complexity + '_' + eanVolume;
const table = cfg.stage8.fields.crossReferenceTable;
return { wip: table[key] || 0, feedback: 0, revisions: 0, rounds: 0 };
const typeKey = contentType === 'pdp' ? 'syndication_pdp' : 'syndication_non_pdp';
const opt = cfg.stage8.fields.syndicationType.options.find(o => o.value === typeKey);
return { wip: opt ? opt.days : 0, feedback: 0, revisions: 0, rounds: 0 };
});
stages.push(s8);

View file

@ -72,7 +72,7 @@ document.addEventListener('DOMContentLoaded', async () => {
async function loadConfig() {
try {
const res = await fetch('config.json?v=2026050601');
const res = await fetch('config.json?v=2026050801');
CONFIG = await res.json();
populateBriefTypes();
populateStageDropdowns();
@ -111,8 +111,8 @@ function populateStageDropdowns() {
populateSelect('stage6AssetVolume', cfg.stage6.fields.assetVolume.options, 'value', 'label');
// Stage 8
populateSelect('stage8EanVolume', cfg.stage8.fields.eanVolume.options, 'value', 'label');
populateSelect('stage8Complexity', cfg.stage8.fields.complexity.options, 'value', 'label');
populateSelect('stage8SyndicationType', cfg.stage8.fields.syndicationType.options, 'value', 'label');
document.getElementById('stage8SyndicationType').value = cfg.stage8.fields.syndicationType.default;
}
function populateSelect(id, options, valueKey, labelKey) {
@ -245,10 +245,8 @@ function bindEvents() {
});
});
// Syndication complexity/EAN change -> update base days
['stage8Complexity', 'stage8EanVolume'].forEach(id => {
document.getElementById(id).addEventListener('change', updateSyndicationBaseDays);
});
// Syndication type change -> update base days
document.getElementById('stage8SyndicationType').addEventListener('change', updateSyndicationBaseDays);
}
// ---- Brief Type Change ----
@ -461,11 +459,9 @@ function updateOperaDays() {
}
function updateSyndicationBaseDays() {
const complexity = document.getElementById('stage8Complexity').value;
const eanVolume = document.getElementById('stage8EanVolume').value;
const key = complexity + '_' + eanVolume;
const table = CONFIG.stageConfigs.stage8.fields.crossReferenceTable;
const days = table[key];
const value = document.getElementById('stage8SyndicationType').value;
const opt = CONFIG.stageConfigs.stage8.fields.syndicationType.options.find(o => o.value === value);
const days = opt ? opt.days : undefined;
document.getElementById('stage8BaseDays').textContent = days !== undefined ? days + ' days' : '--';
}
@ -607,11 +603,9 @@ function calculateSLA() {
// Stage 8: Syndication
results.push(calcGenericStage(7, activeStages[7], currentDate, () => {
const complexity = document.getElementById('stage8Complexity').value;
const eanVolume = document.getElementById('stage8EanVolume').value;
const key = complexity + '_' + eanVolume;
const table = cfg.stage8.fields.crossReferenceTable;
const wip = table[key] || 0;
const value = document.getElementById('stage8SyndicationType').value;
const opt = cfg.stage8.fields.syndicationType.options.find(o => o.value === value);
const wip = opt ? opt.days : 0;
return { handover: 0, wip, feedback: 0, revisions: 0, rounds: 0, noFeedback: true };
}));