@reddoorla/maintenance 0.46.0 → 0.47.0
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/cli/bin.js +111 -25
- package/dist/cli/bin.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +77 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/bin.js
CHANGED
|
@@ -607,7 +607,10 @@ var init_checklist = __esm({
|
|
|
607
607
|
label: "Interactions & Animations",
|
|
608
608
|
field: "Test: Interactions & Animations"
|
|
609
609
|
},
|
|
610
|
-
|
|
610
|
+
// `field` keeps its original Airtable column name ("…Verified After Updates") even though
|
|
611
|
+
// the client-facing label is now "Tested After Updates" — the column holds operator data,
|
|
612
|
+
// so renaming the label is display-only and avoids a live-base column migration.
|
|
613
|
+
{ key: "updates", label: "Tested After Updates", field: "Test: Verified After Updates" }
|
|
611
614
|
];
|
|
612
615
|
ALL_CHECKLIST_FIELDS = [...MAINTENANCE_CHECKLIST, ...TESTING_CHECKLIST].map(
|
|
613
616
|
(i) => i.field
|
|
@@ -701,7 +704,7 @@ async function createDraft(base, input) {
|
|
|
701
704
|
async function setDraftReady(base, recordId, ready) {
|
|
702
705
|
await base(REPORTS_TABLE).update([{ id: recordId, fields: { "Draft ready": ready } }]);
|
|
703
706
|
}
|
|
704
|
-
async function updateReportScores(base, recordId, scores, completedOn) {
|
|
707
|
+
async function updateReportScores(base, recordId, scores, completedOn, enrichment) {
|
|
705
708
|
const fields = {
|
|
706
709
|
"Lighthouse \u2014 Performance": scores.performance,
|
|
707
710
|
"Lighthouse \u2014 Accessibility": scores.accessibility,
|
|
@@ -709,6 +712,14 @@ async function updateReportScores(base, recordId, scores, completedOn) {
|
|
|
709
712
|
"Lighthouse \u2014 SEO": scores.seo
|
|
710
713
|
};
|
|
711
714
|
if (completedOn) fields["Completed on"] = ymd(completedOn);
|
|
715
|
+
if (enrichment?.gaUsersCurrent !== void 0)
|
|
716
|
+
fields["GA users (period)"] = enrichment.gaUsersCurrent;
|
|
717
|
+
if (enrichment?.gaUsersPrevious !== void 0)
|
|
718
|
+
fields["GA users (prev period)"] = enrichment.gaUsersPrevious;
|
|
719
|
+
if (enrichment?.searchFoundPage1 !== void 0)
|
|
720
|
+
fields["Search found page 1"] = enrichment.searchFoundPage1;
|
|
721
|
+
if (enrichment?.searchPosition !== void 0)
|
|
722
|
+
fields["Search position"] = enrichment.searchPosition;
|
|
712
723
|
await base(REPORTS_TABLE).update([{ id: recordId, fields }]);
|
|
713
724
|
}
|
|
714
725
|
async function listSendableReports(base) {
|
|
@@ -808,7 +819,7 @@ var init_copy = __esm({
|
|
|
808
819
|
"Links & Navigation",
|
|
809
820
|
"Form Functionality",
|
|
810
821
|
"Interactions & Animations",
|
|
811
|
-
"
|
|
822
|
+
"Tested After Updates"
|
|
812
823
|
],
|
|
813
824
|
notesHeader: "NOTES",
|
|
814
825
|
seoCta: "Contact us if you are interested in more in-depth data or have questions about SEO.",
|
|
@@ -828,6 +839,7 @@ var init_copy = __esm({
|
|
|
828
839
|
announceTestingLabel: "Full site testing",
|
|
829
840
|
announceMaintenanceLabel: "Routine maintenance",
|
|
830
841
|
announcePreviewLabel: "From your latest full site test:",
|
|
842
|
+
announceScoreNote: "These are independent Google Lighthouse scores, each out of 100 \u2014 higher is better.",
|
|
831
843
|
announceImprovementResend: "Your contact forms now deliver straight to your inbox through reliable infrastructure, so no inquiry slips through the cracks.",
|
|
832
844
|
announceImprovementSvelte5: "We've modernized your site to the latest framework \u2014 it's faster, more secure, and built to last.",
|
|
833
845
|
announceCadence: "After each one we'll send you a short report like this \u2014 there's nothing you need to do.",
|
|
@@ -1048,7 +1060,7 @@ function buildMjml(data) {
|
|
|
1048
1060
|
<mj-text color="#C00" font-size="44px" font-weight="400" padding-top="0px">${data.lighthouse.performance}</mj-text>
|
|
1049
1061
|
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="0px" padding-bottom="36px">Acceptable 50\u201389 // Ideal 90\u2013100</mj-text>
|
|
1050
1062
|
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
1051
|
-
<mj-text color="#C00" font-size="20px" font-weight="300" padding-top="25px">Readability</mj-text>
|
|
1063
|
+
<mj-text color="#C00" font-size="20px" font-weight="300" padding-top="25px">Readability (A11y)</mj-text>
|
|
1052
1064
|
<mj-text color="#C00" font-size="44px" font-weight="400" padding-top="0px">${data.lighthouse.accessibility}</mj-text>
|
|
1053
1065
|
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="0px" padding-bottom="36px">Acceptable 80\u201399 // Ideal 100</mj-text>
|
|
1054
1066
|
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
@@ -1169,6 +1181,22 @@ var init_template2 = __esm({
|
|
|
1169
1181
|
});
|
|
1170
1182
|
|
|
1171
1183
|
// src/reports/announcement-email/template.ts
|
|
1184
|
+
function fmtVisitors(n) {
|
|
1185
|
+
return n.toLocaleString("en-US");
|
|
1186
|
+
}
|
|
1187
|
+
function visitorTrend(cur, prev) {
|
|
1188
|
+
if (cur === void 0 || prev === void 0 || prev === 0) return null;
|
|
1189
|
+
const pct = Math.round((cur - prev) / prev * 100);
|
|
1190
|
+
if (pct > 0) return `\u25B2 ${pct}% vs the previous month`;
|
|
1191
|
+
if (pct < 0) return `\u25BC ${Math.abs(pct)}% vs the previous month`;
|
|
1192
|
+
return "No change vs the previous month";
|
|
1193
|
+
}
|
|
1194
|
+
function announcementSiteExtras(site) {
|
|
1195
|
+
return {
|
|
1196
|
+
cadence: { maintenance: site.maintenanceFreq, testing: site.testingFreq },
|
|
1197
|
+
improvements: { resendForms: true, svelte5: true }
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1172
1200
|
function buildAnnouncementMjml(data) {
|
|
1173
1201
|
const copy = data.copy ?? DEFAULT_COPY;
|
|
1174
1202
|
const previewText = "Your monthly report from Reddoor";
|
|
@@ -1203,8 +1231,10 @@ function buildAnnouncementMjml(data) {
|
|
|
1203
1231
|
<mj-text color="${RED2}" font-size="20px" font-weight="700" padding-top="36px">${escapeXml(copy.announceCadenceHeading)}</mj-text>
|
|
1204
1232
|
${cadenceBlocks.map(
|
|
1205
1233
|
(b) => `
|
|
1206
|
-
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="400" line-height="24px" padding-top="12px" padding-bottom="
|
|
1207
|
-
|
|
1234
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="400" line-height="24px" padding-top="12px" padding-bottom="2px">\u2022 ${escapeXml(b.line)}</mj-text>${b.checks.map(
|
|
1235
|
+
(c) => `
|
|
1236
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="14px" font-weight="300" line-height="22px" padding-top="1px" padding-bottom="0px" padding-left="16px">${escapeXml(c)} <img src="${CHECK_PNG2}" alt="\u2713" width="14" height="14" style="vertical-align:middle;display:inline-block;margin-left:2px;" /></mj-text>`
|
|
1237
|
+
).join("")}`
|
|
1208
1238
|
).join("")}
|
|
1209
1239
|
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="14px">${escapeXml(copy.announceCadence)}</mj-text>
|
|
1210
1240
|
</mj-column>
|
|
@@ -1214,6 +1244,23 @@ function buildAnnouncementMjml(data) {
|
|
|
1214
1244
|
<mj-text color="${RED2}" font-size="20px" font-weight="300" padding-top="25px">${label}</mj-text>
|
|
1215
1245
|
<mj-text color="${RED2}" font-size="44px" font-weight="400" padding-top="0px">${data.lighthouse[key]}</mj-text>`
|
|
1216
1246
|
).join("");
|
|
1247
|
+
const scoreNote = copy.announceScoreNote ? `
|
|
1248
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" font-style="italic" line-height="18px" padding-top="16px">${escapeXml(copy.announceScoreNote)}</mj-text>` : "";
|
|
1249
|
+
const trend = visitorTrend(data.gaUsersCurrent, data.gaUsersPrevious);
|
|
1250
|
+
const trafficRows = [];
|
|
1251
|
+
if (data.gaUsersCurrent !== void 0)
|
|
1252
|
+
trafficRows.push(`
|
|
1253
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="8px"><span style="color:${RED2};font-size:22px;font-weight:400;">${escapeXml(fmtVisitors(data.gaUsersCurrent))}</span> visitors in the last month${trend ? ` \u2014 ${escapeXml(trend)}` : ""}</mj-text>`);
|
|
1254
|
+
if (data.searchPosition !== void 0)
|
|
1255
|
+
trafficRows.push(`
|
|
1256
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="4px">Page 1 Google result (#${data.searchPosition}) for your brand search</mj-text>`);
|
|
1257
|
+
const trafficSection = trafficRows.length > 0 ? `
|
|
1258
|
+
<mj-section background-color="white">
|
|
1259
|
+
<mj-column>
|
|
1260
|
+
<mj-text color="${RED2}" font-size="20px" font-weight="700" padding-top="36px">TRAFFIC & SEARCH</mj-text>
|
|
1261
|
+
${trafficRows.join("")}
|
|
1262
|
+
</mj-column>
|
|
1263
|
+
</mj-section>` : "";
|
|
1217
1264
|
const contactRows = copy.contact.map(
|
|
1218
1265
|
(line) => `
|
|
1219
1266
|
<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" line-height="30px">${escapeXml(line)}</mj-text>`
|
|
@@ -1248,9 +1295,10 @@ function buildAnnouncementMjml(data) {
|
|
|
1248
1295
|
<mj-section background-color="#F4F4F4">
|
|
1249
1296
|
<mj-column>
|
|
1250
1297
|
<mj-text color="${RED2}" font-size="20px" font-weight="700" padding-top="55px">${escapeXml(copy.announcePreviewLabel)}</mj-text>
|
|
1251
|
-
${scoreRows}
|
|
1298
|
+
${scoreRows}${scoreNote}
|
|
1252
1299
|
</mj-column>
|
|
1253
1300
|
</mj-section>
|
|
1301
|
+
${trafficSection}
|
|
1254
1302
|
<mj-section background-color="white">
|
|
1255
1303
|
<mj-column>
|
|
1256
1304
|
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="36px">${escapeXml(copy.announceOpenDoor)}</mj-text>
|
|
@@ -1270,12 +1318,13 @@ function buildAnnouncementMjml(data) {
|
|
|
1270
1318
|
</mj-body>
|
|
1271
1319
|
</mjml>`;
|
|
1272
1320
|
}
|
|
1273
|
-
var FREQ_PHRASE, RED2, GREY2, SCORE_PREVIEW;
|
|
1321
|
+
var FREQ_PHRASE, RED2, GREY2, CHECK_PNG2, SCORE_PREVIEW;
|
|
1274
1322
|
var init_template3 = __esm({
|
|
1275
1323
|
"src/reports/announcement-email/template.ts"() {
|
|
1276
1324
|
"use strict";
|
|
1277
1325
|
init_copy();
|
|
1278
1326
|
init_template();
|
|
1327
|
+
init_assets();
|
|
1279
1328
|
FREQ_PHRASE = {
|
|
1280
1329
|
Monthly: "every month",
|
|
1281
1330
|
Quarterly: "every quarter",
|
|
@@ -1283,9 +1332,10 @@ var init_template3 = __esm({
|
|
|
1283
1332
|
};
|
|
1284
1333
|
RED2 = "#C00";
|
|
1285
1334
|
GREY2 = "#757575";
|
|
1335
|
+
CHECK_PNG2 = `cid:${CHECK_CID}`;
|
|
1286
1336
|
SCORE_PREVIEW = [
|
|
1287
1337
|
{ label: "Performance", key: "performance" },
|
|
1288
|
-
{ label: "Readability", key: "accessibility" },
|
|
1338
|
+
{ label: "Readability (A11y)", key: "accessibility" },
|
|
1289
1339
|
{ label: "Best Practices", key: "bestPractices" },
|
|
1290
1340
|
{ label: "Site Structure", key: "seo" }
|
|
1291
1341
|
];
|
|
@@ -1951,7 +2001,12 @@ async function sendOne(client, base, site, report) {
|
|
|
1951
2001
|
headerImageCid: cidName,
|
|
1952
2002
|
headerWidth: header.displayWidth,
|
|
1953
2003
|
headerHeight: header.displayHeight,
|
|
1954
|
-
headerBgColor: header.placeholderColor
|
|
2004
|
+
headerBgColor: header.placeholderColor,
|
|
2005
|
+
// Announcement-only: re-derive cadence + improvements from the site row so the SENT email
|
|
2006
|
+
// keeps its WHAT TO EXPECT section + improvement callouts. Without this the send-time
|
|
2007
|
+
// re-render drops them entirely (they're not stored on the Reports row). Ignored by the
|
|
2008
|
+
// other report templates.
|
|
2009
|
+
...report.reportType === "Announcement" ? announcementSiteExtras(site) : {}
|
|
1955
2010
|
});
|
|
1956
2011
|
const reportDate = report.completedOn ? new Date(report.completedOn) : /* @__PURE__ */ new Date();
|
|
1957
2012
|
const subject = report.subjectOverride ?? `${site.name} \u2014 ${monthYear(reportDate)} ${report.reportType} Report`;
|
|
@@ -2037,6 +2092,7 @@ var init_orchestrate = __esm({
|
|
|
2037
2092
|
init_attachments();
|
|
2038
2093
|
init_render();
|
|
2039
2094
|
init_copy();
|
|
2095
|
+
init_template3();
|
|
2040
2096
|
init_assets();
|
|
2041
2097
|
init_header_image();
|
|
2042
2098
|
init_resend();
|
|
@@ -5776,6 +5832,7 @@ import { JWT as JWT2 } from "google-auth-library";
|
|
|
5776
5832
|
var WEBMASTERS_READONLY = "https://www.googleapis.com/auth/webmasters.readonly";
|
|
5777
5833
|
var SC_BASE = "https://searchconsole.googleapis.com/webmasters/v3";
|
|
5778
5834
|
var PAGE_1_MAX_POSITION = 10;
|
|
5835
|
+
var BRAND_QUERY_ROW_LIMIT = 100;
|
|
5779
5836
|
function bareHost(s) {
|
|
5780
5837
|
return s.trim().replace(/^sc-domain:/i, "").replace(/^https?:\/\//i, "").split("/")[0].replace(/^www\./i, "").toLowerCase();
|
|
5781
5838
|
}
|
|
@@ -5789,6 +5846,25 @@ function resolvePropertyCandidates(entries, host) {
|
|
|
5789
5846
|
function ymd3(d) {
|
|
5790
5847
|
return d.toISOString().slice(0, 10);
|
|
5791
5848
|
}
|
|
5849
|
+
function pickBrandQuery(rows) {
|
|
5850
|
+
let best;
|
|
5851
|
+
for (const r of rows) {
|
|
5852
|
+
if (typeof r.position !== "number") continue;
|
|
5853
|
+
const impressions = r.impressions ?? 0;
|
|
5854
|
+
if (best === void 0 || impressions > best.impressions || impressions === best.impressions && r.position < best.position) {
|
|
5855
|
+
best = { position: r.position, impressions };
|
|
5856
|
+
}
|
|
5857
|
+
}
|
|
5858
|
+
return best?.position;
|
|
5859
|
+
}
|
|
5860
|
+
function selectBrandPosition(rows, exactQuery) {
|
|
5861
|
+
const want = exactQuery.toLowerCase();
|
|
5862
|
+
const exact = rows.find(
|
|
5863
|
+
(r) => typeof r.position === "number" && (r.keys?.[0] ?? "").toLowerCase() === want
|
|
5864
|
+
);
|
|
5865
|
+
if (exact) return exact.position;
|
|
5866
|
+
return pickBrandQuery(rows);
|
|
5867
|
+
}
|
|
5792
5868
|
async function fetchSearchPresence(q, periodStart, periodEnd) {
|
|
5793
5869
|
const key = JSON.parse(readFileSync4(q.keyPath, "utf8"));
|
|
5794
5870
|
const jwt = new JWT2({
|
|
@@ -5820,14 +5896,14 @@ async function fetchSearchPresence(q, periodStart, periodEnd) {
|
|
|
5820
5896
|
dimensionFilterGroups: [
|
|
5821
5897
|
{
|
|
5822
5898
|
filters: [
|
|
5823
|
-
{ dimension: "query", operator: "
|
|
5899
|
+
{ dimension: "query", operator: "contains", expression: q.query.toLowerCase() }
|
|
5824
5900
|
]
|
|
5825
5901
|
}
|
|
5826
5902
|
],
|
|
5827
|
-
rowLimit:
|
|
5903
|
+
rowLimit: BRAND_QUERY_ROW_LIMIT
|
|
5828
5904
|
}
|
|
5829
5905
|
});
|
|
5830
|
-
const pos = res.data.rows
|
|
5906
|
+
const pos = selectBrandPosition(res.data.rows ?? [], q.query.toLowerCase());
|
|
5831
5907
|
if (typeof pos === "number") {
|
|
5832
5908
|
return { foundOnPage1: pos <= PAGE_1_MAX_POSITION, position: Math.max(1, Math.round(pos)) };
|
|
5833
5909
|
}
|
|
@@ -6419,6 +6495,7 @@ init_reports();
|
|
|
6419
6495
|
init_attachments();
|
|
6420
6496
|
init_render();
|
|
6421
6497
|
init_copy();
|
|
6498
|
+
init_template3();
|
|
6422
6499
|
async function announce(deps) {
|
|
6423
6500
|
const base = deps?.base ?? openBase(readAirtableConfig());
|
|
6424
6501
|
const now = deps?.now ?? /* @__PURE__ */ new Date();
|
|
@@ -6437,15 +6514,24 @@ async function announce(deps) {
|
|
|
6437
6514
|
results.push({ site: w.name, status: "skipped-no-scores" });
|
|
6438
6515
|
continue;
|
|
6439
6516
|
}
|
|
6517
|
+
const periodEnd = now;
|
|
6518
|
+
const periodStart = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1e3);
|
|
6519
|
+
const gaUsers = (await fetchGaUsers(w, periodStart, periodEnd)).value;
|
|
6520
|
+
const search = (await fetchSearch(w, periodStart, periodEnd)).value;
|
|
6521
|
+
const enrichment = {
|
|
6522
|
+
...gaUsers ? { gaUsersCurrent: gaUsers.current, gaUsersPrevious: gaUsers.previous } : {},
|
|
6523
|
+
...search ? { searchFoundPage1: search.foundOnPage1 } : {},
|
|
6524
|
+
...search?.foundOnPage1 && search.position !== null ? { searchPosition: search.position } : {}
|
|
6525
|
+
};
|
|
6440
6526
|
let report;
|
|
6441
6527
|
let statusKind;
|
|
6442
6528
|
const existing = await findReportByPeriod(base, w.id, "Announcement", period);
|
|
6443
6529
|
if (existing) {
|
|
6444
|
-
await updateReportScores(base, existing.id, scores, now);
|
|
6530
|
+
await updateReportScores(base, existing.id, scores, now, enrichment);
|
|
6445
6531
|
report = existing;
|
|
6446
6532
|
statusKind = "reused";
|
|
6447
6533
|
} else {
|
|
6448
|
-
report = await createDraft(base, draftInputFor2(w, scores, now, period));
|
|
6534
|
+
report = await createDraft(base, draftInputFor2(w, scores, now, period, enrichment));
|
|
6449
6535
|
statusKind = "drafted";
|
|
6450
6536
|
}
|
|
6451
6537
|
const slug = siteSlug(w.name);
|
|
@@ -6455,18 +6541,17 @@ async function announce(deps) {
|
|
|
6455
6541
|
reportType: "Announcement",
|
|
6456
6542
|
completedOn: now,
|
|
6457
6543
|
lighthouse: scores,
|
|
6544
|
+
gaUsersCurrent: gaUsers?.current,
|
|
6545
|
+
gaUsersPrevious: gaUsers?.previous,
|
|
6546
|
+
searchPosition: search?.foundOnPage1 ? search.position ?? void 0 : void 0,
|
|
6458
6547
|
lastTestedDate: null,
|
|
6459
6548
|
commentary: null,
|
|
6460
6549
|
copy: resolveCopy(w),
|
|
6461
6550
|
headerImageCid: `${slug}-header`,
|
|
6462
|
-
//
|
|
6463
|
-
//
|
|
6464
|
-
//
|
|
6465
|
-
|
|
6466
|
-
// Default-on fleet-wide for v1: both recent-improvement callouts render for every
|
|
6467
|
-
// site. Operator review (the draft never auto-sends) is the relevance backstop;
|
|
6468
|
-
// per-site conditioning of these flags is a future lever, not a v1 requirement.
|
|
6469
|
-
improvements: { resendForms: true, svelte5: true }
|
|
6551
|
+
// cadence (the client's go-forward pace, "None" omitted) + default-on improvement
|
|
6552
|
+
// callouts. Shared with the send re-render via announcementSiteExtras so the sent
|
|
6553
|
+
// email matches this reviewed preview.
|
|
6554
|
+
...announcementSiteExtras(w)
|
|
6470
6555
|
});
|
|
6471
6556
|
try {
|
|
6472
6557
|
await uploadAttachment(
|
|
@@ -6505,7 +6590,7 @@ function scoresFromRow(w) {
|
|
|
6505
6590
|
seo: w.seoScore
|
|
6506
6591
|
};
|
|
6507
6592
|
}
|
|
6508
|
-
function draftInputFor2(w, scores, now, period) {
|
|
6593
|
+
function draftInputFor2(w, scores, now, period, enrichment) {
|
|
6509
6594
|
return {
|
|
6510
6595
|
reportId: `${w.name} \u2014 Announcement \u2014 ${now.toISOString().slice(0, 10)}`,
|
|
6511
6596
|
siteId: w.id,
|
|
@@ -6516,7 +6601,8 @@ function draftInputFor2(w, scores, now, period) {
|
|
|
6516
6601
|
completedOn: now,
|
|
6517
6602
|
lighthouse: scores,
|
|
6518
6603
|
lastTestedDate: null,
|
|
6519
|
-
subjectOverride: `Your testing & maintenance schedule for ${w.name}
|
|
6604
|
+
subjectOverride: `Your testing & maintenance schedule for ${w.name}`,
|
|
6605
|
+
...enrichment
|
|
6520
6606
|
};
|
|
6521
6607
|
}
|
|
6522
6608
|
|