@forwardimpact/libsyntheticrender 0.1.1
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/LICENSE +201 -0
- package/format.js +84 -0
- package/index.js +7 -0
- package/package.json +37 -0
- package/render/dataset-renderers.js +187 -0
- package/render/enricher.js +384 -0
- package/render/html.js +458 -0
- package/render/industry-data.js +434 -0
- package/render/link-assigner.js +350 -0
- package/render/markdown.js +126 -0
- package/render/pathway.js +124 -0
- package/render/raw.js +465 -0
- package/render/renderer.js +122 -0
- package/render/validate-links.js +329 -0
- package/templates/article.html +31 -0
- package/templates/blog-post.html +34 -0
- package/templates/blog.html +27 -0
- package/templates/briefing.md +20 -0
- package/templates/comments.html +15 -0
- package/templates/courses.html +37 -0
- package/templates/departments.html +29 -0
- package/templates/drugs.html +23 -0
- package/templates/events.html +38 -0
- package/templates/faq.html +22 -0
- package/templates/howto.html +8 -0
- package/templates/leadership.html +8 -0
- package/templates/ontology.md +34 -0
- package/templates/page.html +12 -0
- package/templates/platforms.html +23 -0
- package/templates/project-note.md +19 -0
- package/templates/projects.html +42 -0
- package/templates/readme.md +28 -0
- package/templates/reviews.html +19 -0
- package/templates/roles.html +11 -0
- package/templates/skill-reflection.md +18 -0
- package/templates/weekly.md +18 -0
- package/test/dataset-renderers.test.js +214 -0
- package/test/validate.test.js +396 -0
- package/validate.js +535 -0
package/render/raw.js
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raw Document Renderer — generates individual JSON/YAML documents
|
|
3
|
+
* destined for Supabase Storage or local output.
|
|
4
|
+
*
|
|
5
|
+
* Produces: GitHub webhooks, GetDX API payloads, people YAML,
|
|
6
|
+
* roster YAML, and teams YAML.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import YAML from "yaml";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Render raw documents from entities.
|
|
13
|
+
* @param {object} entities
|
|
14
|
+
* @param {Map<string,string>} [proseMap] - Optional prose map for comment text
|
|
15
|
+
* @returns {Map<string,string>} storage-path → content
|
|
16
|
+
*/
|
|
17
|
+
export function renderRawDocuments(entities, proseMap) {
|
|
18
|
+
const files = new Map();
|
|
19
|
+
|
|
20
|
+
renderGitHubWebhooks(entities, files);
|
|
21
|
+
renderGetDXPayloads(entities, files);
|
|
22
|
+
renderGetDXInitiatives(entities, files);
|
|
23
|
+
renderGetDXScorecards(entities, files);
|
|
24
|
+
renderGetDXComments(entities, files, proseMap);
|
|
25
|
+
renderRosterSnapshots(entities, files);
|
|
26
|
+
renderSummitYAML(entities, files);
|
|
27
|
+
renderPeopleYAML(entities, files);
|
|
28
|
+
|
|
29
|
+
return files;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Render activity files (roster + teams) from entities.
|
|
34
|
+
* @param {object} entities
|
|
35
|
+
* @returns {Map<string,string>} path → YAML content
|
|
36
|
+
*/
|
|
37
|
+
export function renderActivityFiles(entities) {
|
|
38
|
+
const files = new Map();
|
|
39
|
+
files.set("roster.yaml", renderRoster(entities));
|
|
40
|
+
files.set("teams.yaml", renderTeams(entities));
|
|
41
|
+
return files;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Render GitHub webhook JSON payloads.
|
|
46
|
+
* @param {object} entities
|
|
47
|
+
* @param {Map<string,string>} files
|
|
48
|
+
*/
|
|
49
|
+
function renderGitHubWebhooks(entities, files) {
|
|
50
|
+
if (!entities.activity?.webhooks) return;
|
|
51
|
+
|
|
52
|
+
for (const webhook of entities.activity.webhooks) {
|
|
53
|
+
const path = `github/${webhook.delivery_id}.json`;
|
|
54
|
+
files.set(path, JSON.stringify(webhook, null, 2));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Index file for all webhooks
|
|
58
|
+
const index = entities.activity.webhooks.map((w) => ({
|
|
59
|
+
id: w.delivery_id,
|
|
60
|
+
type: w.event_type,
|
|
61
|
+
repo: w.payload?.repository?.full_name,
|
|
62
|
+
actor: w.payload?.sender?.login,
|
|
63
|
+
created_at: w.occurred_at,
|
|
64
|
+
}));
|
|
65
|
+
files.set("github/index.json", JSON.stringify(index, null, 2));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Render GetDX API payloads.
|
|
70
|
+
* @param {object} entities
|
|
71
|
+
* @param {Map<string,string>} files
|
|
72
|
+
*/
|
|
73
|
+
function renderGetDXPayloads(entities, files) {
|
|
74
|
+
// teams.list
|
|
75
|
+
if (entities.activity?.activityTeams) {
|
|
76
|
+
const teamsList = entities.activity.activityTeams.map((t) => ({
|
|
77
|
+
id: t.getdx_team_id,
|
|
78
|
+
name: t.name,
|
|
79
|
+
parent_id: t.parent_id || null,
|
|
80
|
+
parent: t.is_parent || false,
|
|
81
|
+
manager_id: t.manager_id || null,
|
|
82
|
+
contributors: t.contributors || 0,
|
|
83
|
+
last_changed_at: t.last_changed_at || null,
|
|
84
|
+
reference_id: t.reference_id || null,
|
|
85
|
+
ancestors: t.ancestors || [],
|
|
86
|
+
}));
|
|
87
|
+
files.set(
|
|
88
|
+
"getdx/teams.list.json",
|
|
89
|
+
JSON.stringify({ ok: true, teams: teamsList }, null, 2),
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// snapshots.list
|
|
94
|
+
if (entities.activity?.snapshots) {
|
|
95
|
+
const snapshotsList = entities.activity.snapshots.map((s) => ({
|
|
96
|
+
id: s.snapshot_id,
|
|
97
|
+
account_id: s.account_id,
|
|
98
|
+
last_result_change_at: s.last_result_change_at,
|
|
99
|
+
scheduled_for: s.scheduled_for,
|
|
100
|
+
completed_at: s.completed_at,
|
|
101
|
+
completed_count: s.completed_count,
|
|
102
|
+
deleted_at: s.deleted_at || null,
|
|
103
|
+
total_count: s.total_count,
|
|
104
|
+
}));
|
|
105
|
+
files.set(
|
|
106
|
+
"getdx/snapshots.list.json",
|
|
107
|
+
JSON.stringify({ ok: true, snapshots: snapshotsList }, null, 2),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// snapshots.info — one per snapshot with scores
|
|
112
|
+
if (entities.activity?.snapshots && entities.activity?.scores) {
|
|
113
|
+
const scoresBySnapshot = new Map();
|
|
114
|
+
for (const score of entities.activity.scores) {
|
|
115
|
+
const key = score.snapshot_id;
|
|
116
|
+
if (!scoresBySnapshot.has(key)) scoresBySnapshot.set(key, []);
|
|
117
|
+
scoresBySnapshot.get(key).push(score);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
for (const [snapshotId, scores] of scoresBySnapshot) {
|
|
121
|
+
const teamScores = scores.map((s) => ({
|
|
122
|
+
snapshot_team: {
|
|
123
|
+
id: s.snapshot_team_id,
|
|
124
|
+
name: s.team_name,
|
|
125
|
+
team_id: s.getdx_team_id,
|
|
126
|
+
parent: s.is_parent || false,
|
|
127
|
+
parent_id: s.parent_id || null,
|
|
128
|
+
ancestors: s.ancestors || [],
|
|
129
|
+
},
|
|
130
|
+
item_id: s.item_id,
|
|
131
|
+
item_type: s.item_type,
|
|
132
|
+
item_name: s.item_name,
|
|
133
|
+
response_count: s.response_count,
|
|
134
|
+
score: s.score,
|
|
135
|
+
contributor_count: s.contributor_count,
|
|
136
|
+
vs_prev: s.vs_prev,
|
|
137
|
+
vs_org: s.vs_org,
|
|
138
|
+
vs_50th: s.vs_50th,
|
|
139
|
+
vs_75th: s.vs_75th,
|
|
140
|
+
vs_90th: s.vs_90th,
|
|
141
|
+
}));
|
|
142
|
+
|
|
143
|
+
files.set(
|
|
144
|
+
`getdx/snapshots/${snapshotId}.json`,
|
|
145
|
+
JSON.stringify(
|
|
146
|
+
{ ok: true, snapshot: { team_scores: teamScores } },
|
|
147
|
+
null,
|
|
148
|
+
2,
|
|
149
|
+
),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// evidence
|
|
155
|
+
if (entities.activity?.evidence) {
|
|
156
|
+
files.set(
|
|
157
|
+
"getdx/evidence.json",
|
|
158
|
+
JSON.stringify({ evidence: entities.activity.evidence }, null, 2),
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Render GetDX initiatives API payloads.
|
|
165
|
+
* @param {object} entities
|
|
166
|
+
* @param {Map<string,string>} files
|
|
167
|
+
*/
|
|
168
|
+
function renderGetDXInitiatives(entities, files) {
|
|
169
|
+
if (!entities.activity?.initiatives) return;
|
|
170
|
+
|
|
171
|
+
const initiatives = entities.activity.initiatives.map((init) => ({
|
|
172
|
+
id: init.id,
|
|
173
|
+
name: init.name,
|
|
174
|
+
description: init.description,
|
|
175
|
+
scorecard_id: init.scorecard_id,
|
|
176
|
+
scorecard_name: init.scorecard_name,
|
|
177
|
+
priority: init.priority,
|
|
178
|
+
published: init.published,
|
|
179
|
+
complete_by: init.complete_by,
|
|
180
|
+
percentage_complete: init.percentage_complete,
|
|
181
|
+
passed_checks: init.passed_checks,
|
|
182
|
+
total_checks: init.total_checks,
|
|
183
|
+
remaining_dev_days: init.remaining_dev_days,
|
|
184
|
+
owner: init.owner,
|
|
185
|
+
tags: init.tags,
|
|
186
|
+
}));
|
|
187
|
+
|
|
188
|
+
files.set(
|
|
189
|
+
"getdx/initiatives.list.json",
|
|
190
|
+
JSON.stringify({ ok: true, initiatives }, null, 2),
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
for (const init of initiatives) {
|
|
194
|
+
files.set(
|
|
195
|
+
`getdx/initiatives/${init.id}.json`,
|
|
196
|
+
JSON.stringify({ ok: true, initiative: init }, null, 2),
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Render GetDX scorecards API payloads.
|
|
203
|
+
* @param {object} entities
|
|
204
|
+
* @param {Map<string,string>} files
|
|
205
|
+
*/
|
|
206
|
+
function renderGetDXScorecards(entities, files) {
|
|
207
|
+
if (!entities.activity?.scorecards) return;
|
|
208
|
+
|
|
209
|
+
const scorecards = entities.activity.scorecards.map((sc) => ({
|
|
210
|
+
id: sc.id,
|
|
211
|
+
name: sc.name,
|
|
212
|
+
description: sc.description,
|
|
213
|
+
type: sc.type,
|
|
214
|
+
published: sc.published,
|
|
215
|
+
checks: sc.checks,
|
|
216
|
+
levels: sc.levels,
|
|
217
|
+
tags: sc.tags,
|
|
218
|
+
entity_filter_type: "entity_types",
|
|
219
|
+
entity_filter_sql: null,
|
|
220
|
+
entity_filter_type_ids: [],
|
|
221
|
+
editors: [],
|
|
222
|
+
admins: [],
|
|
223
|
+
sql_errors: [],
|
|
224
|
+
empty_level_label: "Not assessed",
|
|
225
|
+
empty_level_color: "#9ca3af",
|
|
226
|
+
}));
|
|
227
|
+
|
|
228
|
+
files.set(
|
|
229
|
+
"getdx/scorecards.list.json",
|
|
230
|
+
JSON.stringify({ ok: true, scorecards }, null, 2),
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
for (const sc of scorecards) {
|
|
234
|
+
files.set(
|
|
235
|
+
`getdx/scorecards/${sc.id}.json`,
|
|
236
|
+
JSON.stringify({ ok: true, scorecard: sc }, null, 2),
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Render GetDX snapshot comments API payloads.
|
|
243
|
+
* Uses LLM-generated prose from the prose map when available,
|
|
244
|
+
* falls back to placeholder text.
|
|
245
|
+
* @param {object} entities
|
|
246
|
+
* @param {Map<string,string>} files
|
|
247
|
+
* @param {Map<string,string>} [proseMap]
|
|
248
|
+
*/
|
|
249
|
+
function renderGetDXComments(entities, files, proseMap) {
|
|
250
|
+
if (!entities.activity?.commentKeys) return;
|
|
251
|
+
|
|
252
|
+
// Group comments by snapshot
|
|
253
|
+
const bySnapshot = new Map();
|
|
254
|
+
for (const ck of entities.activity.commentKeys) {
|
|
255
|
+
if (!bySnapshot.has(ck.snapshot_id)) bySnapshot.set(ck.snapshot_id, []);
|
|
256
|
+
bySnapshot.get(ck.snapshot_id).push(ck);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
for (const [snapshotId, keys] of bySnapshot) {
|
|
260
|
+
const comments = keys.map((ck) => {
|
|
261
|
+
// Look up LLM-generated prose
|
|
262
|
+
const proseKey = `snapshot_comment_${ck.snapshot_id}_${ck.email.replace(/[@.]/g, "_")}`;
|
|
263
|
+
let text = null;
|
|
264
|
+
if (proseMap) {
|
|
265
|
+
for (const [k, v] of proseMap) {
|
|
266
|
+
if (k.includes(proseKey) || proseKey.includes(k)) {
|
|
267
|
+
text = v;
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// Note: if text is still null, prose generation was not run for this key.
|
|
273
|
+
// The prose map uses hashed keys, so fallback iteration is not feasible.
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
snapshot_id: ck.snapshot_id,
|
|
277
|
+
email: ck.email,
|
|
278
|
+
text:
|
|
279
|
+
text ||
|
|
280
|
+
`[${ck.driver_name} — ${ck.trajectory}] Comment pending prose generation.`,
|
|
281
|
+
timestamp: ck.timestamp,
|
|
282
|
+
team_id: ck.team_id,
|
|
283
|
+
};
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
files.set(
|
|
287
|
+
`getdx/snapshots/${snapshotId}/comments.json`,
|
|
288
|
+
JSON.stringify({ ok: true, comments }, null, 2),
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Render quarterly roster snapshots for Summit trajectory.
|
|
295
|
+
* @param {object} entities
|
|
296
|
+
* @param {Map<string,string>} files
|
|
297
|
+
*/
|
|
298
|
+
function renderRosterSnapshots(entities, files) {
|
|
299
|
+
if (!entities.activity?.rosterSnapshots) return;
|
|
300
|
+
|
|
301
|
+
const snapshots = entities.activity.rosterSnapshots.map((rs) => ({
|
|
302
|
+
quarter: rs.quarter,
|
|
303
|
+
members: rs.members,
|
|
304
|
+
changes: rs.changes,
|
|
305
|
+
roster: rs.roster,
|
|
306
|
+
}));
|
|
307
|
+
|
|
308
|
+
files.set(
|
|
309
|
+
"activity/roster-snapshots.json",
|
|
310
|
+
JSON.stringify({ roster_snapshots: snapshots }, null, 2),
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
for (const rs of snapshots) {
|
|
314
|
+
files.set(
|
|
315
|
+
`activity/roster-snapshots/${rs.quarter}.yaml`,
|
|
316
|
+
YAML.stringify(
|
|
317
|
+
{
|
|
318
|
+
quarter: rs.quarter,
|
|
319
|
+
members: rs.members,
|
|
320
|
+
changes: rs.changes,
|
|
321
|
+
roster: rs.roster,
|
|
322
|
+
},
|
|
323
|
+
{ lineWidth: 120 },
|
|
324
|
+
),
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Render summit.yaml with project teams and allocation.
|
|
331
|
+
* @param {object} entities
|
|
332
|
+
* @param {Map<string,string>} files
|
|
333
|
+
*/
|
|
334
|
+
function renderSummitYAML(entities, files) {
|
|
335
|
+
if (!entities.activity?.projectTeams) return;
|
|
336
|
+
|
|
337
|
+
const teams = {};
|
|
338
|
+
for (const pt of entities.activity.projectTeams) {
|
|
339
|
+
teams[pt.id] = pt.members.map((m) => ({
|
|
340
|
+
name: m.name,
|
|
341
|
+
email: m.email,
|
|
342
|
+
job: m.job,
|
|
343
|
+
...(m.allocation !== 1.0 ? { allocation: m.allocation } : {}),
|
|
344
|
+
}));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Also include reporting teams from roster
|
|
348
|
+
const reportingTeams = {};
|
|
349
|
+
if (entities.activity?.roster) {
|
|
350
|
+
const teamMap = new Map();
|
|
351
|
+
for (const person of entities.activity.roster) {
|
|
352
|
+
if (!teamMap.has(person.team_id)) teamMap.set(person.team_id, []);
|
|
353
|
+
teamMap.get(person.team_id).push({
|
|
354
|
+
name: person.name,
|
|
355
|
+
email: person.email,
|
|
356
|
+
job: {
|
|
357
|
+
discipline: person.discipline,
|
|
358
|
+
level: person.level,
|
|
359
|
+
...(person.track ? { track: person.track } : {}),
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
for (const [teamId, members] of teamMap) {
|
|
364
|
+
reportingTeams[teamId] = members;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const summitData = { teams: reportingTeams, projects: teams };
|
|
369
|
+
files.set(
|
|
370
|
+
"activity/summit.yaml",
|
|
371
|
+
YAML.stringify(summitData, { lineWidth: 120 }),
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Render individual people YAML files.
|
|
377
|
+
* @param {object} entities
|
|
378
|
+
* @param {Map<string,string>} files
|
|
379
|
+
*/
|
|
380
|
+
function renderPeopleYAML(entities, files) {
|
|
381
|
+
for (const person of entities.people) {
|
|
382
|
+
const team = entities.teams.find((t) => t.id === person.team_id);
|
|
383
|
+
const dept = entities.departments.find((d) => d.id === person.department);
|
|
384
|
+
|
|
385
|
+
const data = {
|
|
386
|
+
id: person.id,
|
|
387
|
+
name: person.name,
|
|
388
|
+
email: person.email,
|
|
389
|
+
github: person.github,
|
|
390
|
+
iri: person.iri,
|
|
391
|
+
discipline: person.discipline,
|
|
392
|
+
level: person.level,
|
|
393
|
+
team: { id: team?.id, name: team?.name },
|
|
394
|
+
department: { id: dept?.id, name: dept?.name },
|
|
395
|
+
hire_date: person.hire_date,
|
|
396
|
+
is_manager: person.is_manager || false,
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
files.set(
|
|
400
|
+
`people/${person.id}.yaml`,
|
|
401
|
+
YAML.stringify(data, { lineWidth: 120 }),
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// People index
|
|
406
|
+
const index = entities.people.map((p) => ({
|
|
407
|
+
id: p.id,
|
|
408
|
+
name: p.name,
|
|
409
|
+
team: p.team_id,
|
|
410
|
+
level: p.level,
|
|
411
|
+
}));
|
|
412
|
+
files.set("people/index.json", JSON.stringify(index, null, 2));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Render roster YAML for GetDX integration.
|
|
417
|
+
* @param {object} entities
|
|
418
|
+
* @returns {string}
|
|
419
|
+
*/
|
|
420
|
+
function renderRoster(entities) {
|
|
421
|
+
const roster = entities.people.map((person) => ({
|
|
422
|
+
id: person.id,
|
|
423
|
+
name: person.name,
|
|
424
|
+
email: person.email,
|
|
425
|
+
github: person.github,
|
|
426
|
+
team: person.team_id,
|
|
427
|
+
department: person.department,
|
|
428
|
+
discipline: person.discipline,
|
|
429
|
+
level: person.level,
|
|
430
|
+
hire_date: person.hire_date,
|
|
431
|
+
is_manager: person.is_manager || false,
|
|
432
|
+
}));
|
|
433
|
+
|
|
434
|
+
return YAML.stringify({ roster }, { lineWidth: 120 });
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Render teams YAML.
|
|
439
|
+
* @param {object} entities
|
|
440
|
+
* @returns {string}
|
|
441
|
+
*/
|
|
442
|
+
function renderTeams(entities) {
|
|
443
|
+
const teams = entities.teams.map((team) => {
|
|
444
|
+
const dept = entities.departments.find((d) => d.id === team.department);
|
|
445
|
+
const members = entities.people.filter((p) => p.team_id === team.id);
|
|
446
|
+
const manager = members.find((m) => m.is_manager);
|
|
447
|
+
|
|
448
|
+
return {
|
|
449
|
+
id: team.id,
|
|
450
|
+
name: team.name,
|
|
451
|
+
department: team.department,
|
|
452
|
+
department_name: dept?.name || "",
|
|
453
|
+
manager: manager?.name || "",
|
|
454
|
+
manager_email: manager?.email || "",
|
|
455
|
+
size: team.size,
|
|
456
|
+
members: members.map((m) => ({
|
|
457
|
+
name: m.name,
|
|
458
|
+
email: m.email,
|
|
459
|
+
level: m.level,
|
|
460
|
+
})),
|
|
461
|
+
};
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
return YAML.stringify({ teams }, { lineWidth: 120 });
|
|
465
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renderer — wraps all render functions behind a single class with DI.
|
|
3
|
+
*
|
|
4
|
+
* @module libuniverse/render/renderer
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { dirname, join } from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
import { TemplateLoader } from "@forwardimpact/libtemplate/loader";
|
|
10
|
+
import { renderHTML, renderREADME, renderONTOLOGY } from "./html.js";
|
|
11
|
+
import { renderRawDocuments, renderActivityFiles } from "./raw.js";
|
|
12
|
+
import { renderPathway } from "./pathway.js";
|
|
13
|
+
import { renderMarkdown } from "./markdown.js";
|
|
14
|
+
import { enrichDocuments } from "./enricher.js";
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Renderer class that delegates to individual render modules.
|
|
20
|
+
*/
|
|
21
|
+
export class Renderer {
|
|
22
|
+
/**
|
|
23
|
+
* @param {import('@forwardimpact/libtemplate/loader').TemplateLoader} templateLoader - Template loader
|
|
24
|
+
* @param {object} logger - Logger instance
|
|
25
|
+
*/
|
|
26
|
+
constructor(templateLoader, logger) {
|
|
27
|
+
if (!templateLoader) throw new Error("templateLoader is required");
|
|
28
|
+
if (!logger) throw new Error("logger is required");
|
|
29
|
+
this.templateLoader = templateLoader;
|
|
30
|
+
this.logger = logger;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Render HTML microdata files from entities and prose.
|
|
35
|
+
* @param {object} entities
|
|
36
|
+
* @param {Map<string,string>} prose
|
|
37
|
+
* @returns {{ files: Map<string,string>, linked: object }}
|
|
38
|
+
*/
|
|
39
|
+
renderHtml(entities, prose) {
|
|
40
|
+
return renderHTML(entities, prose, this.templateLoader);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Render organization README.
|
|
45
|
+
* @param {object} entities
|
|
46
|
+
* @param {Map<string,string>} prose
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
renderReadme(entities, prose) {
|
|
50
|
+
return renderREADME(entities, prose, this.templateLoader);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Render ONTOLOGY.md with entity IRIs.
|
|
55
|
+
* @param {object} entities
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
renderOntology(entities) {
|
|
59
|
+
return renderONTOLOGY(entities, this.templateLoader);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Render Markdown files for Basecamp personas.
|
|
64
|
+
* @param {object} entities
|
|
65
|
+
* @param {Map<string,string>} prose
|
|
66
|
+
* @returns {Map<string,string>}
|
|
67
|
+
*/
|
|
68
|
+
renderMarkdown(entities, prose) {
|
|
69
|
+
return renderMarkdown(entities, prose, this.templateLoader);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Render raw documents from entities.
|
|
74
|
+
* @param {object} entities
|
|
75
|
+
* @param {Map<string,string>} [proseMap] - Optional prose map for comment text
|
|
76
|
+
* @returns {Map<string,string>}
|
|
77
|
+
*/
|
|
78
|
+
renderRaw(entities, proseMap) {
|
|
79
|
+
return renderRawDocuments(entities, proseMap);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Render activity files (roster + teams) from entities.
|
|
84
|
+
* @param {object} entities
|
|
85
|
+
* @returns {Map<string,string>}
|
|
86
|
+
*/
|
|
87
|
+
renderActivity(entities) {
|
|
88
|
+
return renderActivityFiles(entities);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Render pathway YAML files from generated entity data.
|
|
93
|
+
* @param {object} pathwayData
|
|
94
|
+
* @returns {Map<string,string>}
|
|
95
|
+
*/
|
|
96
|
+
renderPathway(pathwayData) {
|
|
97
|
+
return renderPathway(pathwayData);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Enrich HTML documents with LLM-generated prose.
|
|
102
|
+
* @param {Map<string,string>} htmlFiles
|
|
103
|
+
* @param {object} linked - LinkedEntities
|
|
104
|
+
* @param {import('../engine/prose.js').ProseEngine} proseEngine
|
|
105
|
+
* @param {string} domain
|
|
106
|
+
* @returns {Promise<Map<string,string>>}
|
|
107
|
+
*/
|
|
108
|
+
async enrichHtml(htmlFiles, linked, proseEngine, domain) {
|
|
109
|
+
return enrichDocuments(htmlFiles, linked, proseEngine, domain, this.logger);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Creates a Renderer with real dependencies wired.
|
|
115
|
+
* @param {object} logger - Logger instance
|
|
116
|
+
* @returns {Renderer}
|
|
117
|
+
*/
|
|
118
|
+
export function createRenderer(logger) {
|
|
119
|
+
const templateDir = join(__dirname, "..", "templates");
|
|
120
|
+
const templateLoader = new TemplateLoader(templateDir);
|
|
121
|
+
return new Renderer(templateLoader, logger);
|
|
122
|
+
}
|