aeorank 3.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -197,6 +197,12 @@ interface RawDataSummary {
197
197
  crawl_discovered: number;
198
198
  crawl_fetched: number;
199
199
  crawl_skipped: number;
200
+ citation_ready_sentences: number;
201
+ answer_first_ratio: number;
202
+ evidence_citations_avg: number;
203
+ entity_disambiguation_ratio: number;
204
+ extraction_friction_avg: number;
205
+ image_figure_ratio: number;
200
206
  }
201
207
  /**
202
208
  * Fetches all site data in parallel with HTTPS/HTTP fallback.
package/dist/index.d.ts CHANGED
@@ -197,6 +197,12 @@ interface RawDataSummary {
197
197
  crawl_discovered: number;
198
198
  crawl_fetched: number;
199
199
  crawl_skipped: number;
200
+ citation_ready_sentences: number;
201
+ answer_first_ratio: number;
202
+ evidence_citations_avg: number;
203
+ entity_disambiguation_ratio: number;
204
+ extraction_friction_avg: number;
205
+ image_figure_ratio: number;
200
206
  }
201
207
  /**
202
208
  * Fetches all site data in parallel with HTTPS/HTTP fallback.
package/dist/index.js CHANGED
@@ -1824,7 +1824,83 @@ function extractRawDataSummary(data) {
1824
1824
  // Full-crawl stats
1825
1825
  crawl_discovered: data.crawlStats?.discovered ?? 0,
1826
1826
  crawl_fetched: data.crawlStats?.fetched ?? 0,
1827
- crawl_skipped: data.crawlStats?.skipped ?? 0
1827
+ crawl_skipped: data.crawlStats?.skipped ?? 0,
1828
+ // V2 criteria fields
1829
+ citation_ready_sentences: (() => {
1830
+ const combinedText = text + " " + (data.blogSample?.map((p) => p.text.replace(/<[^>]*>/g, " ")).join(" ") || "");
1831
+ return (combinedText.match(/\b\w+\s+(is\s+(?:a|an)\s|refers\s+to|defined\s+as)\b/gi) || []).length;
1832
+ })(),
1833
+ answer_first_ratio: (() => {
1834
+ const pages = [html, ...data.blogSample?.map((p) => p.text) || []];
1835
+ let answerFirst = 0;
1836
+ for (const pageHtml of pages) {
1837
+ const bodyMatch = pageHtml.match(/<body[^>]*>([\s\S]*)/i);
1838
+ const bodyHtml = bodyMatch ? bodyMatch[1] : pageHtml;
1839
+ const earlyParas = bodyHtml.match(/<p[^>]*>([\s\S]*?)<\/p>/gi)?.slice(0, 5) || [];
1840
+ for (const p of earlyParas) {
1841
+ const pText = p.replace(/<[^>]*>/g, "").trim();
1842
+ const wc = pText.split(/\s+/).length;
1843
+ if (wc >= 40 && wc <= 80) {
1844
+ answerFirst++;
1845
+ break;
1846
+ }
1847
+ }
1848
+ }
1849
+ return pages.length > 0 ? Math.round(answerFirst / pages.length * 100) : 0;
1850
+ })(),
1851
+ evidence_citations_avg: (() => {
1852
+ const allHtml = html + "\n" + (data.blogSample?.map((p) => p.text).join("\n") || "");
1853
+ const paragraphs = allHtml.match(/<p[^>]*>[\s\S]*?<\/p>/gi) || [];
1854
+ let citations = 0;
1855
+ const domainLower = data.domain.replace(/^www\./, "").toLowerCase();
1856
+ for (const p of paragraphs) {
1857
+ const links = p.match(/<a[^>]*href=["'](https?:\/\/[^"']+)["'][^>]*>/gi) || [];
1858
+ for (const link of links) {
1859
+ const href = link.match(/href=["'](https?:\/\/[^"']+)["']/i);
1860
+ if (href) {
1861
+ try {
1862
+ const ld = new URL(href[1]).hostname.replace(/^www\./, "").toLowerCase();
1863
+ if (ld !== domainLower) citations++;
1864
+ } catch {
1865
+ }
1866
+ }
1867
+ }
1868
+ }
1869
+ const pageCount = Math.max(1, 1 + (data.blogSample?.length ?? 0));
1870
+ return Math.round(citations / pageCount * 10) / 10;
1871
+ })(),
1872
+ entity_disambiguation_ratio: (() => {
1873
+ const pages = [html, ...data.blogSample?.map((p) => p.text) || []];
1874
+ let defined = 0;
1875
+ for (const pageHtml of pages) {
1876
+ const h1Match = pageHtml.match(/<h1[^>]*>([\s\S]*?)<\/h1>/i);
1877
+ if (!h1Match) continue;
1878
+ const h1Text = h1Match[1].replace(/<[^>]*>/g, "").trim();
1879
+ const h1Words = h1Text.split(/\s+/).filter((w) => w.length > 3);
1880
+ const primaryNoun = h1Words.sort((a, b) => b.length - a.length)[0] || "";
1881
+ if (!primaryNoun) continue;
1882
+ const pageText = pageHtml.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").slice(0, 500);
1883
+ if (new RegExp(`\\b${primaryNoun.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b[^.]*\\b(is|refers|defined|means)\\b`, "i").test(pageText)) {
1884
+ defined++;
1885
+ }
1886
+ }
1887
+ return pages.length > 0 ? Math.round(defined / pages.length * 100) : 0;
1888
+ })(),
1889
+ extraction_friction_avg: (() => {
1890
+ const combinedText = text + " " + (data.blogSample?.map((p) => p.text.replace(/<[^>]*>/g, " ")).join(" ") || "");
1891
+ const sentences = combinedText.split(/[.!?]+/).filter((s) => s.trim().length > 5);
1892
+ if (sentences.length === 0) return 0;
1893
+ const totalWords = sentences.reduce((sum, s) => sum + s.trim().split(/\s+/).length, 0);
1894
+ return Math.round(totalWords / sentences.length * 10) / 10;
1895
+ })(),
1896
+ image_figure_ratio: (() => {
1897
+ const combinedHtml = html + "\n" + (data.blogSample?.map((p) => p.text).join("\n") || "");
1898
+ const allImages = combinedHtml.match(/<img\s[^>]*>/gi) || [];
1899
+ if (allImages.length === 0) return 0;
1900
+ const figureBlocks = combinedHtml.match(/<figure[\s\S]*?<\/figure>/gi) || [];
1901
+ const figuresWithCaption = figureBlocks.filter((f) => /<figcaption/i.test(f));
1902
+ return Math.round(figuresWithCaption.length / allImages.length * 100);
1903
+ })()
1828
1904
  };
1829
1905
  }
1830
1906
  function getPageTopicText(html) {
@@ -4555,8 +4631,8 @@ function extractLinksWithAnchors(html, sourceUrl, domain) {
4555
4631
  if (href.startsWith("//")) {
4556
4632
  fullUrl = `https:${href}`;
4557
4633
  } else if (href.startsWith("/")) {
4558
- if (href === "/" || href.startsWith("/#")) continue;
4559
- fullUrl = `https://${domain}${href}`;
4634
+ if (href.startsWith("/#")) continue;
4635
+ fullUrl = href === "/" ? `https://${domain}` : `https://${domain}${href}`;
4560
4636
  } else if (href.startsWith("http")) {
4561
4637
  fullUrl = href;
4562
4638
  } else if (href.startsWith("#") || href.startsWith("?") || href.startsWith("mailto:") || href.startsWith("tel:") || href.startsWith("javascript:")) {
@@ -4570,7 +4646,7 @@ function extractLinksWithAnchors(html, sourceUrl, domain) {
4570
4646
  if (linkDomain !== cleanDomain) continue;
4571
4647
  parsed.hash = "";
4572
4648
  const path = parsed.pathname;
4573
- if (path === "/" || path === "") continue;
4649
+ if (path === "") continue;
4574
4650
  if (RESOURCE_EXTENSIONS.test(path)) continue;
4575
4651
  if (SKIP_PATH_PATTERNS.test(path)) continue;
4576
4652
  const normalized = normalizeUrl(fullUrl);
@@ -6090,6 +6166,22 @@ var CSS = `
6090
6166
  .criterion-score { font-size: 14px; font-weight: 700; min-width: 32px; text-align: center; }
6091
6167
  .criterion-name { font-size: 13px; flex: 1; }
6092
6168
  .criterion-status { font-size: 11px; color: #666; background: #f0f0f0; padding: 2px 8px; border-radius: 10px; white-space: nowrap; }
6169
+ .criterion-pillar { display: block; font-size: 10px; color: #999; font-weight: 400; margin-top: 2px; }
6170
+ .pillar-grid { background: #fff; border: 1px solid #e0e0e0; border-radius: 8px; padding: 16px 20px; margin-bottom: 32px; }
6171
+ .pillar-row { display: flex; align-items: center; gap: 12px; padding: 8px 0; }
6172
+ .pillar-row + .pillar-row { border-top: 1px solid #f0f0f0; }
6173
+ .pillar-name { font-size: 14px; font-weight: 500; min-width: 180px; }
6174
+ .pillar-weight { font-size: 11px; color: #999; font-weight: 400; }
6175
+ .pillar-bar { flex: 1; height: 10px; background: #e0e0e0; border-radius: 5px; overflow: hidden; }
6176
+ .pillar-bar-fill { height: 100%; border-radius: 5px; transition: width 0.3s; }
6177
+ .pillar-score { font-size: 16px; font-weight: 700; min-width: 36px; text-align: right; }
6178
+ .top-fixes { background: #fff; border: 1px solid #e0e0e0; border-radius: 8px; padding: 16px 20px; margin-bottom: 32px; }
6179
+ .fix-item { display: flex; align-items: center; gap: 12px; padding: 10px 0; }
6180
+ .fix-item + .fix-item { border-top: 1px solid #f0f0f0; }
6181
+ .fix-num { font-size: 16px; font-weight: 700; color: #FF5722; min-width: 24px; }
6182
+ .fix-name { flex: 1; font-size: 14px; font-weight: 500; }
6183
+ .fix-impact { font-size: 13px; font-weight: 600; color: #4CAF50; }
6184
+ .fix-effort { font-size: 12px; color: #999; }
6093
6185
  table { width: 100%; border-collapse: collapse; background: #fff; border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; margin-bottom: 32px; }
6094
6186
  th { background: #f5f5f5; text-align: left; padding: 10px 14px; font-size: 13px; font-weight: 600; color: #555; border-bottom: 1px solid #e0e0e0; }
6095
6187
  td { padding: 10px 14px; font-size: 13px; border-bottom: 1px solid #f0f0f0; }
@@ -6135,10 +6227,11 @@ function generateHtmlReport(result) {
6135
6227
  const scorecardCards = result.scorecard.map((item) => {
6136
6228
  const color = criterionColor(item.score);
6137
6229
  const width = item.score * 10;
6230
+ const pillarTag = item.pillar ? `<span class="criterion-pillar">${escapeHtml(item.pillar)}</span>` : "";
6138
6231
  return `<div class="criterion-card">
6139
6232
  <div class="criterion-bar"><div class="criterion-bar-fill" style="width:${width}%;background:${color}"></div></div>
6140
6233
  <span class="criterion-score" style="color:${color}">${item.score}/10</span>
6141
- <span class="criterion-name">${escapeHtml(item.criterion)}</span>
6234
+ <span class="criterion-name">${escapeHtml(item.clientName || item.criterion)}${pillarTag}</span>
6142
6235
  <span class="criterion-status">${escapeHtml(item.status)}</span>
6143
6236
  </div>`;
6144
6237
  }).join("\n");
@@ -6190,11 +6283,40 @@ function generateHtmlReport(result) {
6190
6283
 
6191
6284
  <div class="verdict">${escapeHtml(result.verdict)}</div>
6192
6285
 
6193
- <h2 class="section-title">Scorecard (26 Criteria)</h2>
6286
+ <h2 class="section-title">Scorecard (34 Criteria)</h2>
6194
6287
  <div class="scorecard-grid">
6195
6288
  ${scorecardCards}
6196
6289
  </div>
6197
6290
 
6291
+ ${result.pillarScores ? `
6292
+ <h2 class="section-title">Pillar Scores</h2>
6293
+ <div class="pillar-grid">
6294
+ ${[
6295
+ { name: "Answer Readiness", score: result.pillarScores.answerReadiness, weight: "~40%" },
6296
+ { name: "Content Structure", score: result.pillarScores.contentStructure, weight: "~25%" },
6297
+ { name: "Trust & Authority", score: result.pillarScores.trustAuthority, weight: "~15%" },
6298
+ { name: "Technical Foundation", score: result.pillarScores.technicalFoundation, weight: "~10%" },
6299
+ { name: "AI Discovery", score: result.pillarScores.aiDiscovery, weight: "~10%" }
6300
+ ].map((p) => `<div class="pillar-row">
6301
+ <span class="pillar-name">${p.name} <span class="pillar-weight">${p.weight}</span></span>
6302
+ <div class="pillar-bar"><div class="pillar-bar-fill" style="width:${p.score}%;background:${scoreColor(p.score)}"></div></div>
6303
+ <span class="pillar-score" style="color:${scoreColor(p.score)}">${p.score}</span>
6304
+ </div>`).join("\n")}
6305
+ </div>
6306
+ ` : ""}
6307
+
6308
+ ${result.topFixes && result.topFixes.length > 0 ? `
6309
+ <h2 class="section-title">Top Fixes</h2>
6310
+ <div class="top-fixes">
6311
+ ${result.topFixes.map((f, i) => `<div class="fix-item">
6312
+ <span class="fix-num">${i + 1}</span>
6313
+ <span class="fix-name">${escapeHtml(f.fix)}</span>
6314
+ <span class="fix-impact">${escapeHtml(f.impact)}</span>
6315
+ <span class="fix-effort">${escapeHtml(f.effort)} effort</span>
6316
+ </div>`).join("\n")}
6317
+ </div>
6318
+ ` : ""}
6319
+
6198
6320
  ${result.opportunities.length > 0 ? `
6199
6321
  <h2 class="section-title">Opportunities (${result.opportunities.length})</h2>
6200
6322
  <table>