fix(blog): extract article from QA, status badge ready/step X/10, calibration v6 Flexoptix balance

This commit is contained in:
Rene Fichtmueller 2026-03-31 23:52:56 +02:00
parent bf34096d48
commit e406262a7a
3 changed files with 165 additions and 24 deletions

View File

@ -493,12 +493,14 @@ CALIBRATION FAILS (auto-reject — fix before returning):
STRONG: "You're about to spend $400,000 on optics. Here's how to accidentally turn it into a $2M problem." STRONG: "You're about to spend $400,000 on optics. Here's how to accidentally turn it into a $2M problem."
If the hook lacks a concrete number or consequence, strengthen it. If the hook lacks a concrete number or consequence, strengthen it.
For each issue: CRITICAL OUTPUT RULE:
- Quote the problematic text Return ONLY the fixed article text. NO review commentary. NO numbered issue lists. NO "Critical Review" section. NO "HARD FAIL CHECKS" header. NO markdown review structure.
- Explain what's wrong
- Provide the corrected version
Return the COMPLETE fixed article. The output must START DIRECTLY with the article hook (first sentence of the article).
The output must END with the final sentence of the article.
Nothing before the article. Nothing after the article.
If you find issues, fix them silently in the article itself. Do not list them.
Article: Article:
{{ARTICLE}}`; {{ARTICLE}}`;
@ -710,6 +712,43 @@ KEY ELEMENTS OF THIS STYLE:
- Ending is open, not prescriptive - Ending is open, not prescriptive
- Tone: been there, done that, not afraid to say so - Tone: been there, done that, not afraid to say so
STYLE B GOLD EXAMPLE 2 (10/10 validated, 2026-03-31 OEM vs Compatible)
Topic: OEM vs compatible optics comparison. Calm, balanced, Flexoptix voice. THIS is the target for comparison/analysis articles.
"You're about to sign a purchase order for 400G optics. On paper, the numbers look straightforward: fewer ports, higher density, lower cost per bit. The decision between OEM and compatible transceivers often appears to be a simple trade-off between cost and perceived risk. In practice, it is neither.
Most production issues are not caused by the optics themselves. They emerge at the intersection of optics, cabling, firmware, and operational processes. This is where assumptions made during design are tested against real-world conditions.
One of the most underestimated factors is connector quality. In high-density environments, particularly with MPO-based links, even minor contamination can have a measurable impact. A single impaired fiber end-face in a multi-fiber connector can increase insertion loss enough to reduce the available margin. The resulting behaviour is often intermittent rather than binary: rising CRC counters, occasional link flaps, or performance degradation that is difficult to reproduce in a lab environment.
Cabling transitions introduce another layer of complexity. Moving from 100G SR4 on multimode fiber to 400G DR4 or FR4 on single-mode fiber changes not only the optics, but also the tolerances of the system. Multimode deployments are generally more forgiving, while single-mode environments operate with tighter loss budgets.
From a system perspective, vendor dependency is often discussed in terms of support and compatibility. OEM optics provide a controlled and validated environment. Compatible optics introduce flexibility in sourcing and cost, but require a structured validation approach. The practical difference is not in the hardware itself, but in where responsibility is placed. With OEM optics, much of the validation is handled by the vendor. With compatible optics, that responsibility shifts towards the operator.
To understand the technical boundaries, it is useful to look at the optical budget. For a typical DR4-class transceiver:
TX_min = -2.9 dBm
RX_sensitivity = -7.7 dBm
Available optical budget = 4.8 dB
This budget must accommodate fiber attenuation, connector losses, and any additional impairments. In a short-reach single-mode scenario:
Fiber loss = 0.5 km × 0.22 dB/km = 0.11 dB
Connector loss 0.20.35 dB per mated pair
Even with a small number of connections, the remaining margin can decrease quickly if connectors are not properly cleaned or if additional patching is introduced.
High-speed optics do not typically fail because of their specifications. They fail when real-world conditions reduce the margin that those specifications assume. Designing with that in mind and validating accordingly is what separates stable deployments from those that require continuous intervention."
KEY ELEMENTS OF THIS SECOND STYLE B EXAMPLE:
- Calm, authoritative not angry or fear-inducing
- Compatible optics framed as "responsibility shifts to operator" not "risky"
- Technical math shown correctly: TX_min not TX_max, dBm separate from Watts
- Connector loss: 0.2-0.35 dB per mated pair (not 0.6 dB)
- No scenario stacking one clear thread from design assumptions to production reality
- Ending reframes the whole topic without telling reader what to do
- No bullet lists, no section headers, no numbered points
WRONG PATTERNS (both styles never produce): WRONG PATTERNS (both styles never produce):
"Thoroughly Test Your PoE Budget:" (PoE = wrong context, checklist = wrong format) "Thoroughly Test Your PoE Budget:" (PoE = wrong context, checklist = wrong format)
"QSFP-DD DR4 (Direct Attach)" (DR4 Direct Attach DAC is Direct Attach Copper) "QSFP-DD DR4 (Direct Attach)" (DR4 Direct Attach DAC is Direct Attach Copper)
@ -735,6 +774,31 @@ WRONG PATTERNS (both styles — never produce):
Fiber loss: "500m × 0.22 dB/km = 1.1 dB" off by factor 10. Correct: 0.5 km × 0.22 = 0.11 dB. Fiber loss: "500m × 0.22 dB/km = 1.1 dB" off by factor 10. Correct: 0.5 km × 0.22 = 0.11 dB.
"A multi-thousand-dollar problem" without a breakdown cite the numbers: engineer hours × rate + SLA penalty + customer escalation. "A multi-thousand-dollar problem" without a breakdown cite the numbers: engineer hours × rate + SLA penalty + customer escalation.
400ZR reach stated as "80-120km" without qualification 400ZR is standardized to 80km; beyond that depends on OSNR, amplification, vendor implementation. 400ZR reach stated as "80-120km" without qualification 400ZR is standardized to 80km; beyond that depends on OSNR, amplification, vendor implementation.
"TX power: +3.0 dBm/lane (4 lanes = 12W)" dBm and Watts are NOT addable. TX power per lane stays in dBm. Module power consumption (12W) is a separate electrical spec. Never convert dBm to Watts via lane count.
"TX Power Min - RX Sensitivity Max = 3.0 dBm - (-7.7 dBm) = 10.7 dB" using TX_max (+3.0) instead of TX_min (-2.9). Correct: TX_min - RX_sensitivity = (-2.9) - (-7.7) = 4.8 dB available budget.
"Connector loss: 0.6 dB per connector" too high. Correct: 0.2-0.35 dB per mated pair for a clean, properly terminated connector.
"100G-SR4 → up to 8×10G from a single 100G port" or similar density breakout math remove or mark explicitly as theoretical maximum. This is marketing language in a technical article.
"compatible optics are a gamble" or any framing that makes compatible optics sound inherently unreliable this is not Flexoptix voice. Correct framing: "compatible optics shift responsibility from vendor to operator."
Stacking 5-6 worst-case scenarios in a row this reads as AI-constructed, not field experience. Max 2-3 scenarios, each deeper, not more.
FLEXOPTIX BALANCE RULES (critical this is a Flexoptix blog, not an OEM vendor blog):
- Never frame compatible optics as "a gamble" or "ticking time bomb"
- Never imply OEM = safe and compatible = risky without nuance
- CORRECT FRAMING: "OEM optics provide validated integration. Compatible optics require the operator to own the validation process. Both can be stable — the difference is where responsibility sits."
- The blog should make a senior engineer nod, not make a purchasing manager scared
- Tone: calm, factual, slightly provocative never fear-mongering
- A Flexoptix article about compatible vs OEM should make the reader trust compatible optics MORE, not less, when deployed correctly
POWER / LOSS BUDGET PRECISION (always apply):
- TX power per lane: stated in dBm (e.g., -2.9 to +3.0 dBm)
- Module power consumption: stated in Watts (e.g., ~12W for 400G DR4) SEPARATE spec, not derived from dBm
- Available optical budget = TX_min - RX_sensitivity (both in dBm, result in positive dB)
Example: (-2.9 dBm) - (-7.7 dBm) = 4.8 dB
- Link loss = fiber_loss + connector_loss
Fiber: distance_km × attenuation_dB_per_km
Connector: 0.2-0.35 dB per mated pair (clean)
- Margin = Available budget - Link loss (must be 3 dB)
- Received power = TX_power - link_loss (verify > RX_sensitivity)
--- END GOLD STANDARD --- --- END GOLD STANDARD ---
`; `;

View File

@ -1083,8 +1083,34 @@ async function runLlmPipeline(
} }
stepsCompleted = 10; stepsCompleted = 10;
// Final article // Extract only the article from STEP9 output (QA returns review + fixed article)
const draftContent = `# ${title}\n\n${step9.text}`; // Look for "COMPLETE FIXED ARTICLE" marker and take everything after it
let finalArticleText = step9.text;
const articleMarkers = [
"### COMPLETE FIXED ARTICLE",
"## COMPLETE FIXED ARTICLE",
"COMPLETE FIXED ARTICLE",
"---\n\n**You're",
"---\n\nYou're",
];
for (const marker of articleMarkers) {
const idx = step9.text.indexOf(marker);
if (idx !== -1) {
// Skip past the marker line itself
const afterMarker = step9.text.slice(idx + marker.length).trimStart();
// Strip leading --- separator if present
finalArticleText = afterMarker.replace(/^---\s*\n/, "").trimStart();
break;
}
}
// Strip any remaining markdown review headers (### lines) from the article
finalArticleText = finalArticleText
.split("\n")
.filter(line => !line.match(/^#{1,4}\s+(Critical Review|HARD FAIL|QUALITY CHECKS|CALIBRATION FAILS)/))
.join("\n")
.trim();
const draftContent = `# ${title}\n\n${finalArticleText}`;
const wordCount = draftContent.split(/\s+/).length; const wordCount = draftContent.split(/\s+/).length;
const finalIssues = validateArticle(draftContent); const finalIssues = validateArticle(draftContent);

View File

@ -2278,29 +2278,44 @@ function generateBlog(topic, speed) {
body: JSON.stringify(body) body: JSON.stringify(body)
}).then(function(r) { return r.json(); }).then(function(data) { }).then(function(r) { return r.json(); }).then(function(data) {
if (data.success) { if (data.success) {
var msg = data.draft.title + ' — ' + data.draft.word_count + ' words'; showToast('⚙️ Generating…', data.draft.title + ' — pipeline running (~10 min)');
if (data.draft.llm_enhancing) msg += ' (LLM enhancing in background...)';
showToast('Draft generated', msg);
if (data.draft.llm_enhancing) {
pollBlogLlm(data.draft.id, 0);
}
} else showToast('Failed', data.error || 'Unknown error', true);
loadBlogDrafts(); loadBlogDrafts();
// Always poll progress — pipeline runs async
pollBlogLlm(data.draft.id, 0);
} else showToast('Failed', data.error || 'Unknown error', true);
}).catch(function(err) { showToast('Network error', err.message, true); }); }).catch(function(err) { showToast('Network error', err.message, true); });
} }
function pollBlogLlm(id, attempt) { function pollBlogLlm(id, attempt) {
if (attempt > 30) return; if (attempt > 60) return; // max 10 min (60 × 10s)
setTimeout(function() { setTimeout(function() {
api('/api/blog/' + id + '/progress').then(function(p) {
if (!p.running) {
// Pipeline done — update badge to "ready" and reload list
var badge = document.querySelector('.ri[data-blog-id="' + id + '"] .blog-status-badge');
if (badge) {
badge.className = 'b b-green blog-status-badge';
badge.textContent = 'ready';
}
api('/api/blog/' + id).then(function(data) { api('/api/blog/' + id).then(function(data) {
if (data.draft && data.draft.generated_by && data.draft.generated_by.includes('llm')) { if (data.draft) {
showToast('LLM Enhanced', data.draft.title + ' — ' + data.draft.word_count + ' words'); showToast('✅ Blog ready', data.draft.title + ' — ' + data.draft.word_count + ' words');
loadBlogDrafts();
} else {
pollBlogLlm(id, attempt + 1);
} }
}).catch(function() {}); }).catch(function() {});
}, 10000); loadBlogDrafts();
} else {
// Still running — update badge with step info
var badge = document.querySelector('.ri[data-blog-id="' + id + '"] .blog-status-badge');
if (badge) {
badge.className = 'b b-yellow blog-status-badge';
badge.textContent = 'step ' + p.step + '/10';
}
pollBlogLlm(id, attempt + 1);
}
}).catch(function() {
pollBlogLlm(id, attempt + 1);
});
}, 8000);
} }
// Hot topics loaded dynamically via hot-topics.js // Hot topics loaded dynamically via hot-topics.js
@ -2309,15 +2324,44 @@ function pollBlogLlm(id, attempt) {
async function loadBlogDrafts() { async function loadBlogDrafts() {
var data = await api('/api/blog'); var data = await api('/api/blog');
buildDOM(el('blog-list'), (data.drafts || []).map(function(d) { // Check which drafts are still generating (in-progress pipelines)
var sc = d.status === 'published' ? 'b-green' : d.status === 'review' ? 'b-yellow' : 'b-blue'; var drafts = data.drafts || [];
// Fetch progress for all drafts in parallel to know which are still running
var progressMap = {};
await Promise.all(drafts.map(function(d) {
return api('/api/blog/' + d.id + '/progress').then(function(p) {
progressMap[d.id] = p;
}).catch(function() {});
}));
buildDOM(el('blog-list'), drafts.map(function(d) {
var p = progressMap[d.id] || {};
var isRunning = p.running === true;
// Status badge: generating → step X/10, done → ready, published → published, review → review
var statusLabel, statusClass;
if (isRunning) {
statusLabel = p.step ? 'step ' + p.step + '/10' : 'generating…';
statusClass = 'b-yellow';
} else if (d.status === 'published') {
statusLabel = 'published';
statusClass = 'b-green';
} else if (d.status === 'review') {
statusLabel = 'review';
statusClass = 'b-yellow';
} else if (d.pipeline_steps_completed >= 10 || (d.generated_by || '').includes('fo-blog')) {
statusLabel = 'ready ✓';
statusClass = 'b-green';
} else {
statusLabel = 'draft';
statusClass = 'b-blue';
}
var gen = (d.generated_by || '').replace('tip-blog-engine-', ''); var gen = (d.generated_by || '').replace('tip-blog-engine-', '');
var gc = gen === 'llm' ? 'b-green' : gen === 'template-fallback' ? 'b-yellow' : 'b-neutral'; var gc = gen.includes('fo-blog') ? 'b-green' : gen === 'template-fallback' ? 'b-yellow' : 'b-neutral';
return '<div class="ri" data-blog-id="' + esc(d.id) + '" data-blog-title="' + esc(d.title || '') + '" onclick="openBlogDetail(\'' + esc(d.id) + '\')">' return '<div class="ri" data-blog-id="' + esc(d.id) + '" data-blog-title="' + esc(d.title || '') + '" onclick="openBlogDetail(\'' + esc(d.id) + '\')">'
+ '<div style="display:flex;justify-content:space-between;align-items:center">' + '<div style="display:flex;justify-content:space-between;align-items:center">'
+ '<div class="ri-title">' + esc(d.title) + '</div>' + '<div class="ri-title">' + esc(d.title) + '</div>'
+ '<div style="display:flex;align-items:center;gap:8px">' + '<div style="display:flex;align-items:center;gap:8px">'
+ '<span class="b ' + sc + '">' + esc(d.status) + '</span>' + '<span class="b ' + statusClass + ' blog-status-badge">' + statusLabel + '</span>'
+ '<span class="blog-del-btn" data-blog-id="' + esc(d.id) + '" data-blog-title="' + esc(d.title || '') + '" title="Delete" style="color:#c1121f;cursor:pointer;font-size:0.9rem;padding:2px 6px;border-radius:4px" onclick="event.stopPropagation();blogDeleteClick(this)"></span>' + '<span class="blog-del-btn" data-blog-id="' + esc(d.id) + '" data-blog-title="' + esc(d.title || '') + '" title="Delete" style="color:#c1121f;cursor:pointer;font-size:0.9rem;padding:2px 6px;border-radius:4px" onclick="event.stopPropagation();blogDeleteClick(this)"></span>'
+ '</div>' + '</div>'
+ '</div>' + '</div>'
@ -2329,6 +2373,13 @@ async function loadBlogDrafts() {
+ '<span>' + new Date(d.created_at).toLocaleDateString() + '</span>' + '<span>' + new Date(d.created_at).toLocaleDateString() + '</span>'
+ '</div></div>'; + '</div></div>';
}).join('') || '<div class="loading">No drafts yet &mdash; click a card above to generate</div>'); }).join('') || '<div class="loading">No drafts yet &mdash; click a card above to generate</div>');
// Auto-start polling for any drafts currently running
drafts.forEach(function(d) {
if (progressMap[d.id] && progressMap[d.id].running) {
pollBlogLlm(d.id, 0);
}
});
} }
async function openBlogDetail(id) { async function openBlogDetail(id) {