house-lights 1.6.0 → 1.9.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/package.json +1 -1
- package/skills/cast-list/assets/map.html +42 -38
- package/skills/cast-list/assets/playbill.html +127 -0
- package/skills/cast-list/assets/plot.html +114 -0
- package/skills/cast-list/examples/avaliacoes/evaluate-bodega-azul-frete-2026-10-30.csv +5 -0
- package/skills/cast-list/examples/avaliacoes/evaluate-bodega-azul-pedidos-2026-10-30.csv +5 -0
- package/skills/cast-list/references/format.md +1 -1
- package/skills/cast-list/references/keys.md +3 -1
- package/skills/cast-list/references/playbill.md +65 -0
- package/skills/cast-list/scripts/build.mjs +43 -13
- package/skills/cast-list/scripts/engine.mjs +102 -100
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<meta name="generator" content="cast-list (engine v0.259.0)">
|
|
7
|
+
<meta name="robots" content="noindex">
|
|
8
|
+
<title>cast-list</title>
|
|
9
|
+
<style>/* The RH of the cast: the map's tokens, so the two pages look like one. */
|
|
10
|
+
.cl {
|
|
11
|
+
--bg: #f6f6f3; --card: #ffffff; --ink: #1d2330; --muted: #5f6878; --line: #e2e4e8; --soft: #f3f4f1;
|
|
12
|
+
--has: #2e7d55; --off: #c8442c; --off-bg: #fde8e3; --said: #b7791f;
|
|
13
|
+
--platform: #2e7d55; --platform-bg: #e3f3ea; --specialist: #3b5bdb; --specialist-bg: #e8edff; --orchestrator: #7b4fc9; --orchestrator-bg: #efe8fb; --type: #7b4fc9; --type-bg: #efe8fb;
|
|
14
|
+
--amber: #8a5a00; --amber-bg: #fff6dc; --amber-line: #f0dca4; --focus: #2f5bea; --active: #1d2330; --on-active: #fff;
|
|
15
|
+
font: 14px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; color: var(--ink); background: var(--bg);
|
|
16
|
+
min-height: 100vh; box-sizing: border-box; padding: 24px 16px 40px;
|
|
17
|
+
}
|
|
18
|
+
@media (prefers-color-scheme: dark) {
|
|
19
|
+
.cl:not(.light) {
|
|
20
|
+
--bg: #14171c; --card: #1d2129; --ink: #e8ebf0; --muted: #9aa3b2; --line: #313744; --soft: #23272f;
|
|
21
|
+
--has: #6fcf9a; --off: #ff8a70; --off-bg: #3a221d; --said: #f2c46b;
|
|
22
|
+
--platform: #6fcf9a; --platform-bg: #1c3328; --specialist: #9db0ff; --specialist-bg: #232c4a; --orchestrator: #c6a8ff; --orchestrator-bg: #2e2445; --type: #c6a8ff; --type-bg: #2e2445;
|
|
23
|
+
--amber: #f2c46b; --amber-bg: #2f2716; --amber-line: #5a4a22; --focus: #8fb0ff; --active: #e8ebf0; --on-active: #14171c;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
body { margin: 0; }
|
|
27
|
+
.cl *, .cl *::before, .cl *::after { box-sizing: border-box; }
|
|
28
|
+
.cl button { font: inherit; color: inherit; cursor: pointer; }
|
|
29
|
+
.cl :focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
|
|
30
|
+
.cl-wrap { max-width: 1280px; margin: 0 auto; display: flex; flex-direction: column; gap: 16px; }
|
|
31
|
+
.cl-title { margin: 0; font-size: 24px; line-height: 1.2; }
|
|
32
|
+
.cl-sub { margin: 4px 0 0; color: var(--muted); font-size: 13px; }
|
|
33
|
+
.cl-private { margin: 6px 0 0; font-size: 12px; color: var(--off); }
|
|
34
|
+
.cl-card { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 16px; }
|
|
35
|
+
.cl-card h2 { margin: 0 0 10px; font-size: 15px; }
|
|
36
|
+
.pb-staff { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 10px; }
|
|
37
|
+
.pb-count { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px; }
|
|
38
|
+
.pb-count b { display: block; font-size: 26px; line-height: 1.1; }
|
|
39
|
+
.pb-count span { font-size: 13px; color: var(--muted); }
|
|
40
|
+
.pb-count.warn b { color: var(--amber); } .pb-count.bad b { color: var(--off); } .pb-count.good b { color: var(--has); }
|
|
41
|
+
.pb-missing { margin: 0; font-size: 13px; color: var(--muted); }
|
|
42
|
+
.pb-body { display: grid; grid-template-columns: minmax(260px, 340px) 1fr; gap: 16px; align-items: start; }
|
|
43
|
+
@media (max-width: 860px) { .pb-body { grid-template-columns: 1fr; } }
|
|
44
|
+
.pb-side { display: flex; flex-direction: column; gap: 16px; }
|
|
45
|
+
.pb-org { display: flex; flex-direction: column; gap: 6px; }
|
|
46
|
+
.pb-node { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 4px 8px; align-items: center; width: 100%; text-align: left; padding: 7px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--card); }
|
|
47
|
+
.pb-node.manager { background: var(--active); color: var(--on-active); border-color: var(--active); }
|
|
48
|
+
.pb-node.ghost { border-style: dashed; color: var(--muted); cursor: default; }
|
|
49
|
+
.pb-node[aria-pressed="true"] { border-color: var(--focus); box-shadow: 0 0 0 1px var(--focus); }
|
|
50
|
+
.pb-also { color: var(--type); font-size: 11px; white-space: nowrap; }
|
|
51
|
+
.pb-type { margin: 8px 0 2px; font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--type); }
|
|
52
|
+
.pb-tag { font-size: 11px; padding: 1px 8px; border-radius: 999px; border: 1px solid currentColor; white-space: nowrap; }
|
|
53
|
+
.pb-tag.ok { color: var(--has); } .pb-tag.pdi, .pb-tag.noOwner { color: var(--off); } .pb-tag.hiring, .pb-tag.neverPublished, .pb-tag.noEval { color: var(--amber); }
|
|
54
|
+
.pb-posts { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }
|
|
55
|
+
.pb-posts button { padding: 4px 10px; border-radius: 999px; border: 1px solid var(--line); background: var(--card); font-size: 12px; }
|
|
56
|
+
.pb-posts button[aria-pressed="true"] { background: var(--active); color: var(--on-active); border-color: var(--active); }
|
|
57
|
+
.pb-req { list-style: none; margin: 0; padding: 0; }
|
|
58
|
+
.pb-req li { padding: 6px 0; border-bottom: 1px dashed var(--line); }
|
|
59
|
+
.pb-req li::before { content: "✓ "; color: var(--has); }
|
|
60
|
+
.pb-req li.proposal::before { content: "? "; color: var(--amber); }
|
|
61
|
+
.pb-note { margin: 8px 0 0; font-size: 12px; color: var(--muted); }
|
|
62
|
+
.pb-resume { display: flex; flex-direction: column; gap: 14px; }
|
|
63
|
+
.pb-head { display: flex; justify-content: space-between; gap: 12px; align-items: flex-start; border-bottom: 2px solid var(--ink); padding-bottom: 12px; }
|
|
64
|
+
.pb-head h2 { margin: 0; font-size: 22px; }
|
|
65
|
+
.pb-head p { margin: 2px 0 0; color: var(--muted); }
|
|
66
|
+
.pb-seal { font-weight: 800; letter-spacing: .08em; font-size: 12px; padding: 4px 10px; border: 2px solid currentColor; border-radius: 6px; transform: rotate(-3deg); white-space: nowrap; }
|
|
67
|
+
.pb-seal.ok { color: var(--has); } .pb-seal.pdi { color: var(--off); }
|
|
68
|
+
.pb-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 4px 16px; margin: 0; font-size: 13px; }
|
|
69
|
+
.pb-facts div { display: flex; gap: 6px; } .pb-facts dt { color: var(--muted); } .pb-facts dd { margin: 0; font-weight: 600; }
|
|
70
|
+
.pb-section h3 { margin: 0 0 6px; font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
|
|
71
|
+
.pb-section ul { margin: 0; padding-left: 18px; }
|
|
72
|
+
.pb-chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
73
|
+
.pb-chip { font-size: 12px; padding: 3px 9px; border-radius: 6px; background: var(--soft); border: 1px solid var(--line); }
|
|
74
|
+
.pb-src { font-size: 12px; color: var(--muted); font-weight: 400; }
|
|
75
|
+
.pb-perf { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
|
|
76
|
+
.pb-perf div { background: var(--soft); border-radius: 10px; padding: 10px 12px; }
|
|
77
|
+
.pb-perf b { display: block; font-size: 20px; } .pb-perf span { font-size: 12px; color: var(--muted); }
|
|
78
|
+
.pb-perf .bad b { color: var(--off); }
|
|
79
|
+
.pb-pdi { list-style: none; margin: 0; padding: 0; counter-reset: pdi; }
|
|
80
|
+
.pb-pdi li { counter-increment: pdi; display: grid; grid-template-columns: 26px 1fr; gap: 8px; padding: 7px 0; border-bottom: 1px solid var(--line); }
|
|
81
|
+
.pb-pdi li::before { content: counter(pdi); width: 22px; height: 22px; border-radius: 50%; background: var(--ink); color: var(--bg); display: grid; place-items: center; font-size: 12px; font-weight: 700; }
|
|
82
|
+
.pb-pdi li.cleanup::before { background: var(--muted); }
|
|
83
|
+
.pb-pdi small { display: block; color: var(--muted); font-size: 12px; }
|
|
84
|
+
.pb-empty { margin: 0; color: var(--muted); font-size: 13px; }
|
|
85
|
+
.cl-foot { margin: 8px 0 0; font-size: 12px; color: var(--muted); text-align: center; }
|
|
86
|
+
/* the two views of a résumé: as a résumé (the paper one) and as a sheet */
|
|
87
|
+
.pb-views { display: flex; gap: 6px; justify-content: flex-end; }
|
|
88
|
+
.pb-views button { padding: 3px 10px; border-radius: 999px; border: 1px solid var(--line); background: var(--card); font-size: 12px; }
|
|
89
|
+
.pb-views button[aria-pressed="true"] { background: var(--active); color: var(--on-active); border-color: var(--active); }
|
|
90
|
+
.pb-cv { --paper: #fffdf8; --chip-bg: #f1ece1; background: var(--paper); border-radius: 16px; padding: 22px 22px 18px; box-shadow: 0 1px 0 var(--line); }
|
|
91
|
+
@media (prefers-color-scheme: dark) { .cl:not(.light) .pb-cv { --paper: var(--card); --chip-bg: var(--soft); } }
|
|
92
|
+
.pb-cv .pb-head { align-items: center; gap: 14px; }
|
|
93
|
+
.pb-cv .pb-who { flex: 1; min-width: 0; }
|
|
94
|
+
.pb-av { width: 64px; height: 64px; border-radius: 50%; background: var(--chip-bg); color: var(--ink); display: grid; place-items: center; flex: none; }
|
|
95
|
+
.pb-cv h2, .pb-cv .pb-perf b { font-family: Georgia, "Times New Roman", serif; }
|
|
96
|
+
.pb-cv .pb-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); font-size: 12.5px; }
|
|
97
|
+
.pb-cv .pb-chip { background: var(--chip-bg); border-color: transparent; border-radius: 8px; }
|
|
98
|
+
.pb-cv .pb-perf div { background: var(--chip-bg); }
|
|
99
|
+
.pb-cv .pb-seal { font-family: Georgia, "Times New Roman", serif; transform: rotate(-4deg); }
|
|
100
|
+
@media (max-width: 520px) { .pb-cv .pb-facts { grid-template-columns: 1fr; } .pb-av { width: 48px; height: 48px; } }
|
|
101
|
+
</style>
|
|
102
|
+
</head>
|
|
103
|
+
<body>
|
|
104
|
+
<div class="cl" id="app"></div>
|
|
105
|
+
<script type="application/json" id="cast">{{CAST}}</script>
|
|
106
|
+
<script>(()=>{var C={en:{title:"The cast\u2019s HR",sub:"The same cast, as people: staff board, org chart, post, r\xE9sum\xE9 and development plan. Everything comes from the files, the telemetry and the team\u2019s spreadsheets.",privacy:"This file holds the client\u2019s data: keep it in the client\u2019s environment.",staff:{cast:"on the staff",hiring:"being hired",noOwner:"with no owner",neverPublished:"never published",noEval:"with no evaluation",pdi:"with a development plan",ok:"in good standing"},tag:{hiring:"being hired",noOwner:"no owner",neverPublished:"never published",noEval:"no evaluation",pdi:"plan",ok:"in good standing"},missing:{inventory:"the admin center\u2019s inventory (owner, environment, published)",team:"the team\u2019s spreadsheet (team, goal)",telemetry:"the export of Application Insights (performance)"},more:"More with",org:"Org chart",also:e=>`also ${e.join(", ")}`,client:"Customer",clientSub:"who is served",manager:"manager",ghost:"called, not in the folder",noType:"no type",post:"The post",postNote:"The post is the accepted pattern of the role and type, and the profile\u2019s rules. A newcomer is \xABhired\xBB when they meet it.",proposal:"proposal \u2014 no decision yet",manager_:"Manager",team:"Team",owner:"Owner",created:"Hired",published:"Last published",schema:"Badge",env:"Unit",nobody:"nobody calls it",summary:"Summary",tools:"Tools and access",conduct:"Languages and conduct",goal:"Goal",noGoal:"no goal declared",perf:e=>`Performance \xB7 ${e} days`,starts:"topic starts",errors:"errors logged",notFired:"did not fire",measured:"goal measured",noLine:"no line in the telemetry",noPerf:"The performance comes with the export of Application Insights (--telemetry).",certs:"Certifications",certsNone:"No evaluation in the folder of --evals.",passed:(e,t)=>`${e} of ${t} passed`,access:"Access only it has in its type",views:"How to see it",cv:"R\xE9sum\xE9",sheet:"Sheet",pdi:"Development plan",nothing:"Nothing to develop.",sources:"Sources",seal:{ok:"IN GOOD STANDING",pdi:"PLAN"},orchestrator:"Orchestrator",specialist:"Specialist",map:"\u2190 Map",foot:"cast-list \xB7 no model: what is here is what the files and tables say."},pt:{title:"RH do elenco",sub:"O mesmo elenco, visto como pessoas: quadro de pessoal, organograma, vaga, curr\xEDculo e plano de desenvolvimento. Tudo sai dos arquivos, da telemetria e das planilhas do time.",privacy:"Este arquivo tem dados do cliente: deixe-o no ambiente dele.",staff:{cast:"no quadro",hiring:"em admiss\xE3o",noOwner:"sem respons\xE1vel",neverPublished:"nunca publicado",noEval:"sem avalia\xE7\xE3o",pdi:"em PDI",ok:"em dia"},tag:{hiring:"em admiss\xE3o",noOwner:"sem respons\xE1vel",neverPublished:"nunca publicado",noEval:"sem avalia\xE7\xE3o",pdi:"PDI",ok:"em dia"},missing:{inventory:"o invent\xE1rio do admin center (respons\xE1vel, ambiente, publica\xE7\xE3o)",team:"a planilha do time (equipe, meta)",telemetry:"a exporta\xE7\xE3o do Application Insights (desempenho)"},more:"Mais com",org:"Organograma",also:e=>`tamb\xE9m ${e.join(", ")}`,client:"Cliente",clientSub:"quem \xE9 atendido",manager:"gestor",ghost:"chamado, fora da pasta",noType:"sem tipo",post:"A vaga",postNote:"A vaga \xE9 o padr\xE3o aceito do papel e do tipo, mais as regras do perfil. Quem chega \xE9 \xABcontratado\xBB quando cumpre a vaga.",proposal:"proposta \u2014 sem decis\xE3o ainda",manager_:"Gestor",team:"Equipe",owner:"Respons\xE1vel",created:"Admiss\xE3o",published:"\xDAltima publica\xE7\xE3o",schema:"Matr\xEDcula",env:"Lota\xE7\xE3o",nobody:"ningu\xE9m chama",summary:"Resumo",tools:"Ferramentas e acessos",conduct:"Idiomas e conduta",goal:"Meta",noGoal:"sem meta declarada",perf:e=>`Desempenho \xB7 ${e} dias`,starts:"in\xEDcios de t\xF3pico",errors:"erros registrados",notFired:"n\xE3o disparou",measured:"meta medida",noLine:"nenhuma linha na telemetria",noPerf:"O desempenho aparece com a exporta\xE7\xE3o do Application Insights (--telemetry).",certs:"Certifica\xE7\xF5es",certsNone:"Nenhuma avalia\xE7\xE3o na pasta do --evals.",passed:(e,t)=>`${e} de ${t} aprovados`,access:"Acessos que s\xF3 ele tem no tipo",views:"Como ver",cv:"Curr\xEDculo",sheet:"Ficha",pdi:"Plano de desenvolvimento",nothing:"Nada a desenvolver.",sources:"Fontes",seal:{ok:"EM DIA",pdi:"PDI"},orchestrator:"Orquestrador",specialist:"Especialista",map:"\u2190 Mapa",foot:"cast-list \xB7 sem modelo: o que est\xE1 aqui \xE9 o que os arquivos e as planilhas dizem."}},h=JSON.parse(document.getElementById("cast").textContent),{playbill:i,lang:d}=h,a=C[d];document.documentElement.lang=d==="pt"?"pt-BR":"en";document.title=`${a.title} \xB7 ${i.name??h.id}`;var s=e=>String(e??"").replace(/[&<>"']/g,t=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[t]),b=document.getElementById("app"),y=[...i.org.orchestrators,...i.org.types.flatMap(e=>e.people)].map(e=>e.name),w=y.length>1?y.reduce((e,t)=>{let o=0;for(;o<e.length&&e[o]===t[o];)o++;return e.slice(0,o)}):"",v=/[-–·:]\s*$/.test(w)?w:"",u=e=>v&&e.startsWith(v)?e.slice(v.length):e,E=e=>[...i.org.orchestrators,...i.org.types.flatMap(t=>t.people)].find(t=>t.id===e)?.situation??[],S=decodeURIComponent(location.hash.slice(1)),g=(i.resumes.find(e=>e.id===S)??i.resumes.find(e=>e.seal==="pdi"&&e.role==="specialist")??i.resumes[0])?.id,q=i.posts[0]?.scope,f="cv",M={cast:"",hiring:"warn",noOwner:"bad",neverPublished:"warn",noEval:"warn",pdi:"bad",ok:"good"};function L(){let e=i.staff.map(o=>`<div class="pb-count ${M[o.key]}"><b>${o.people.length}</b><span>${s(a.staff[o.key])}${o.key!=="cast"&&o.people.length?` \u2014 ${s(o.people.map(n=>u(n.name)).join(", "))}`:""}</span></div>`).join(""),t=i.missing.length?`<p class="pb-missing">${s(a.more)}: ${i.missing.map(o=>s(a.missing[o])).join(" \xB7 ")}</p>`:"";return`<section class="pb-staff" aria-label="${s(a.title)}">${e}</section>${t}`}var I=e=>e.filter(t=>t!=="ok"||e.length===1).map(t=>`<span class="pb-tag ${t}">${s(a.tag[t])}</span>`).join(" "),D=(e,t,o)=>`<button class="pb-node" data-who="${s(e)}" aria-pressed="${e===g}"><span>${s(u(t))}${o?.length?` <small class="pb-also">${s(a.also(o))}</small>`:""}</span>${I(E(e))}</button>`;function F(){let e=i.org.orchestrators.map(n=>`<button class="pb-node manager" data-who="${s(n.id)}" aria-pressed="${n.id===g}"><span>${s(u(n.name))}<br><small>${s(a.manager)}</small></span>${I(n.situation)}</button>`).join(""),t=i.org.types.map(n=>`<p class="pb-type">${s(n.name||a.noType)}</p>${n.people.map(r=>D(r.id,r.name,r.alsoIn)).join("")}`).join(""),o=i.org.ghosts.map(n=>`<div class="pb-node ghost"><span>${s(u(n.name))}</span><span class="pb-tag hiring">${s(a.ghost)}</span></div>`).join("");return`<section class="cl-card"><h2>${s(a.org)}</h2><div class="pb-org"><div class="pb-node ghost" style="justify-content:center"><span>${s(a.client)} \xB7 <small>${s(a.clientSub)}</small></span></div>${e}${t}${o}</div></section>`}function x(){let e=i.posts.find(n=>n.scope===q)??i.posts[0];if(!e)return"";let t=i.posts.map(n=>`<button data-post="${s(n.scope)}" aria-pressed="${n.scope===e.scope}">${s(n.name[d])}</button>`).join(""),o=e.requirements.map(n=>`<li class="${n.proposal?"proposal":""}">${s(n[d])}${n.proposal?` <span class="pb-src">(${s(a.proposal)})</span>`:""}</li>`).join("");return`<section class="cl-card"><h2>${s(a.post)}</h2><div class="pb-posts">${t}</div><ul class="pb-req">${o}</ul><p class="pb-note">${s(a.postNote)}</p></section>`}var j=e=>e?`${s(e.value)} <span class="pb-src">(${s(e.source)})</span>`:"\u2014",m=(e,t)=>`<div><dt>${s(e)}</dt><dd>${t}</dd></div>`,G=e=>e?s(e.value):"\u2014",B='<svg viewBox="0 0 40 40" width="34" height="34" aria-hidden="true"><rect x="9" y="13" width="22" height="17" rx="5" fill="none" stroke="currentColor" stroke-width="2.2"/><line x1="20" y1="13" x2="20" y2="8" stroke="currentColor" stroke-width="2.2"/><circle cx="20" cy="7" r="2" fill="currentColor"/><circle cx="16" cy="21" r="2" fill="currentColor"/><circle cx="24" cy="21" r="2" fill="currentColor"/><path d="M16 26 q4 2.5 8 0" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',c=(e,t)=>`<div class="pb-section"><h3>${s(e)}</h3>${t}</div>`,k=(e,t=!1)=>e.length?`<div class="pb-chips">${e.map(o=>`<span class="pb-chip${t?" pb-quote":""}">${s(o[d])}</span>`).join("")}</div>`:'<p class="pb-empty">\u2014</p>';function H(e){let t=e.role==="orchestrator"?a.orchestrator:a.specialist,o=[e.title??"",`${t}${e.types.length?` \xB7 ${e.types.join(" \xB7 ")}`:""}`].filter(Boolean),n=f==="cv",r=n?G:j,O=[m(a.manager_,e.manager?s(e.manager):e.role==="orchestrator"?"\u2014":s(a.nobody)),m(a.team,r(e.team)),m(a.owner,r(e.owner)),m(a.created,r(e.created)),m(a.published,r(e.published)),m(a.schema,s(e.schemaName??"\u2014")),m(a.env,r(e.environment))].join(""),p=e.performance,R=p?p.silent?`<p class="pb-empty">${s(a.noLine)}</p>`:`<div class="pb-perf"><div><b>${p.starts}</b><span>${s(a.starts)}</span></div><div class="${p.notFired.length?"bad":""}"><b>${p.errors}</b><span>${s(a.errors)}${p.notFired.length?` \u2014 ${s(p.notFired.join(", "))} ${s(a.notFired)}`:""}</span></div><div><b>\u2014</b><span>${s(a.measured)}</span></div></div>`:`<p class="pb-empty">${s(a.noPerf)}</p>`,T=e.pdi.length?`<ol class="pb-pdi">${e.pdi.map(l=>`<li class="${l.group}"><span>${s(l[d])}<small>${s(l.why[d])}</small></span></li>`).join("")}</ol>`:`<p class="pb-empty">${s(a.nothing)}</p>`,A=n?`<div class="pb-head"><span class="pb-av">${B}</span><div class="pb-who"><h2>${s(e.name)}</h2>${e.title?`<p class="pb-quote">${s(e.title)}</p>`:""}<p>${s(o[o.length-1])}</p></div><span class="pb-seal ${e.seal}">${s(a.seal[e.seal])}</span></div>`:`<div class="pb-head"><div><h2>${s(e.name)}</h2>${e.title?`<p class="pb-quote">${s(e.title)}</p>`:""}<p>${s(o[o.length-1])}</p></div><span class="pb-seal ${e.seal}">${s(a.seal[e.seal])}</span></div>`,P=`<div class="pb-views" role="group" aria-label="${s(a.views)}">${["cv","sheet"].map(l=>`<button data-view="${l}" aria-pressed="${l===f}">${s(a[l])}</button>`).join("")}</div>`;return`<article class="cl-card pb-resume${n?" pb-cv":""}" aria-live="polite">
|
|
107
|
+
${P}
|
|
108
|
+
${A}
|
|
109
|
+
<dl class="pb-facts">${O}</dl>
|
|
110
|
+
${e.summary.length?c(a.summary,`<ul class="pb-quote">${e.summary.map(l=>`<li>${s(l)}</li>`).join("")}</ul>`):""}
|
|
111
|
+
${c(a.tools,k(e.tools))}
|
|
112
|
+
${c(a.conduct,k(e.conduct,!0))}
|
|
113
|
+
${c(a.goal,e.goal?`<p>${j(e.goal)}</p>`:`<p class="pb-empty">${s(a.noGoal)}</p>`)}
|
|
114
|
+
${c(p?a.perf(p.days):a.perf(0).replace(/ · .*/,""),R)}
|
|
115
|
+
${e.certifications?c(a.certs,e.certifications.length?`<ul>${e.certifications.map(l=>`<li><b>${s(a.passed(l.passed,l.total))}</b>${l.date?` \xB7 ${s(l.date)}`:""} <span class="pb-src">(${s(l.methods.map($=>`${$.name} ${$.passed}/${$.total}`).join(" \xB7 "))} \u2014 ${s(l.file)})</span></li>`).join("")}</ul>`:`<p class="pb-empty">${s(a.certsNone)}</p>`):""}
|
|
116
|
+
${e.access.length?c(a.access,`<ul>${e.access.map(l=>`<li>${s(l[d])}</li>`).join("")}</ul>`):""}
|
|
117
|
+
${c(a.pdi,T)}
|
|
118
|
+
<p class="pb-note">${s(a.sources)}: ${s(e.sources.join(", "))}</p>
|
|
119
|
+
</article>`}function N(){let e=i.resumes.find(t=>t.id===g)??i.resumes[0];b.innerHTML=`<div class="cl-wrap">
|
|
120
|
+
<header>${h.map?`<p class="cl-sub"><a href="${s(h.map)}">${s(a.map)}</a></p>`:""}<h1 class="cl-title">${s(a.title)} \xB7 ${s(i.name??h.id)}</h1><p class="cl-sub">${s(a.sub)}</p><p class="cl-private">${s(a.privacy)}</p></header>
|
|
121
|
+
${L()}
|
|
122
|
+
<div class="pb-body"><div class="pb-side">${F()}${x()}</div>${e?H(e):""}</div>
|
|
123
|
+
<p class="cl-foot">${s(a.foot)}</p>
|
|
124
|
+
</div>`}b.addEventListener("click",e=>{let t=e.target.closest("button");if(t){if(t.dataset.who)g=t.dataset.who;else if(t.dataset.post)q=t.dataset.post;else if(t.dataset.view)f=t.dataset.view;else return;N(),t.dataset.who&&b.querySelector(".pb-resume")?.scrollIntoView({block:"nearest"})}});N();})();
|
|
125
|
+
</script>
|
|
126
|
+
</body>
|
|
127
|
+
</html>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<meta name="generator" content="cast-list (engine v0.259.0)">
|
|
7
|
+
<meta name="robots" content="noindex">
|
|
8
|
+
<title>cast-list</title>
|
|
9
|
+
<style>/* The stage of the cast: a light plot. Light by day, the dark of a theatre when the system is dark. */
|
|
10
|
+
.cl {
|
|
11
|
+
--bg: #f4f3ef; --stage: #fbfaf6; --stage-edge: #ece9e1; --card: #ffffff; --ink: #1d2330; --muted: #5f6878; --line: #dcdfe5;
|
|
12
|
+
--warm: #b86e00; --warm-soft: #f6e3c2; --beam: #e9b65e; --ok: #1f7a4d; --bad: #c0392b; --amber: #9a6200; --sys: #2f5bea; --ghost: #8a8f9c;
|
|
13
|
+
--node: #ffffff; --node-line: #c9ced8; --manager: #1d2330; --on-manager: #ffffff; --focus: #2f5bea; --dark: #10131a;
|
|
14
|
+
font: 14px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; color: var(--ink); background: var(--bg);
|
|
15
|
+
min-height: 100vh; box-sizing: border-box; padding: 20px 16px 36px;
|
|
16
|
+
}
|
|
17
|
+
@media (prefers-color-scheme: dark) {
|
|
18
|
+
.cl:not(.light) {
|
|
19
|
+
--bg: #07090f; --stage: #0d1220; --stage-edge: #080b14; --card: #0e1322; --ink: #e9e4d8; --muted: #9a9fb2; --line: #22293d;
|
|
20
|
+
--warm: #ffd88a; --warm-soft: #3a3020; --beam: #ffd88a; --ok: #4fd18b; --bad: #ff6a5f; --amber: #ffb340; --sys: #7aa2ff; --ghost: #6f7590;
|
|
21
|
+
--node: #121a2e; --node-line: #2e3858; --manager: #f3e7c9; --on-manager: #14110a; --focus: #8fb0ff; --dark: #03050a;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
body { margin: 0; }
|
|
25
|
+
.cl *, .cl *::before, .cl *::after { box-sizing: border-box; }
|
|
26
|
+
.cl button { font: inherit; color: inherit; cursor: pointer; }
|
|
27
|
+
.cl :focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
|
|
28
|
+
.cl a { color: var(--sys); }
|
|
29
|
+
.pl-head { max-width: 1480px; margin: 0 auto 10px; }
|
|
30
|
+
.pl-head h1 { margin: 0; font-size: 22px; } .pl-head h1 span { color: var(--warm); }
|
|
31
|
+
.pl-head p { margin: 2px 0 0; color: var(--muted); font-size: 13px; }
|
|
32
|
+
.pl-head .pl-private { color: var(--bad); font-size: 12px; }
|
|
33
|
+
.pl-wrap { max-width: 1480px; margin: 0 auto; display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 14px; align-items: start; }
|
|
34
|
+
@media (max-width: 1000px) { .pl-wrap { grid-template-columns: 1fr; } }
|
|
35
|
+
.pl-stage { position: relative; border: 1px solid var(--line); border-radius: 16px; overflow: hidden; background: radial-gradient(1100px 600px at 40% 40%, var(--stage) 0%, var(--stage) 55%, var(--stage-edge) 100%); }
|
|
36
|
+
.pl-stage svg { display: block; width: 100%; height: auto; }
|
|
37
|
+
.pl-panel { display: flex; flex-direction: column; gap: 12px; }
|
|
38
|
+
.pl-box { background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 14px; }
|
|
39
|
+
.pl-box h2 { margin: 0 0 8px; font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); font-weight: 650; }
|
|
40
|
+
.pl-chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
41
|
+
.pl-chips button, .pl-scenes button { background: var(--node); border: 1px solid var(--node-line); border-radius: 99px; padding: 5px 11px; font-size: 13px; }
|
|
42
|
+
.pl-chips button[aria-pressed="true"] { background: var(--warm); color: var(--bg); border-color: var(--warm); }
|
|
43
|
+
.pl-scenes { display: flex; flex-direction: column; gap: 6px; }
|
|
44
|
+
.pl-scenes button { border-radius: 10px; text-align: left; }
|
|
45
|
+
.pl-scenes button[aria-pressed="true"] { border-color: var(--warm); box-shadow: 0 0 0 1px var(--warm); }
|
|
46
|
+
.pl-cap { margin: 10px 0 0; font-size: 13px; min-height: 40px; }
|
|
47
|
+
.pl-note { margin: 0; color: var(--muted); font-size: 12px; }
|
|
48
|
+
.pl-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 12px; color: var(--muted); }
|
|
49
|
+
.pl-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 5px; vertical-align: -1px; }
|
|
50
|
+
.pl-card .pl-name { font-size: 16px; font-weight: 650; margin: 0; }
|
|
51
|
+
.pl-card .pl-role { margin: 0 0 8px; color: var(--muted); font-size: 12px; }
|
|
52
|
+
.pl-dna { display: flex; height: 14px; border-radius: 4px; overflow: hidden; gap: 2px; margin: 4px 0 4px; }
|
|
53
|
+
.pl-dna span { display: block; min-width: 3px; }
|
|
54
|
+
.pl-dna .own { background: var(--node-line); } .pl-dna .shared { background: var(--sys); } .pl-dna .official { background: var(--ok); }
|
|
55
|
+
.pl-dna .variant { background: var(--amber); } .pl-dna .marker { background: var(--warm); }
|
|
56
|
+
.pl-checks { list-style: none; margin: 8px 0 0; padding: 0; font-size: 13px; }
|
|
57
|
+
.pl-checks li { padding: 3px 0; border-bottom: 1px dashed var(--line); }
|
|
58
|
+
.pl-checks .ok::before { content: "✓ "; color: var(--ok); } .pl-checks .off::before { content: "✕ "; color: var(--bad); } .pl-checks .decide::before { content: "? "; color: var(--amber); }
|
|
59
|
+
.pl-checks .off { color: var(--bad); } .pl-checks .decide { color: var(--amber); }
|
|
60
|
+
.pl-narr { margin: 0; background: var(--card); border-top: 1px solid var(--line); padding: 10px 14px; font-size: 15px; min-height: 64px; }
|
|
61
|
+
.pl-narr.idle { display: flex; align-items: center; }
|
|
62
|
+
.pl-narr.warn { border-top: 2px solid var(--bad); }
|
|
63
|
+
.pl-narr .pl-step { color: var(--warm); font-weight: 650; margin-right: 6px; }
|
|
64
|
+
.pl-narr q { display: block; margin-top: 4px; color: var(--muted); font-size: 13px; }
|
|
65
|
+
.pl-narr .pl-keys { display: block; margin-top: 4px; color: var(--muted); font-size: 11px; }
|
|
66
|
+
/* the stage's own marks */
|
|
67
|
+
.pl-group { font-size: 11px; letter-spacing: .14em; text-transform: uppercase; fill: var(--muted); }
|
|
68
|
+
.pl-band { fill: none; stroke: var(--line); stroke-dasharray: 2 4; }
|
|
69
|
+
.pl-edge { fill: none; stroke: var(--node-line); stroke-width: 1.4; }
|
|
70
|
+
.pl-edge.traffic { stroke-width: 1.8; }
|
|
71
|
+
.pl-edge.bad { stroke: var(--bad); stroke-width: 2.6; } .pl-edge.amber { stroke: var(--amber); } .pl-edge.ghost { stroke: var(--ghost); }
|
|
72
|
+
.pl-edge.dashed { stroke-dasharray: 5 5; }
|
|
73
|
+
.pl-elabel { font-size: 11px; fill: var(--muted); paint-order: stroke; stroke: var(--stage); stroke-width: 4px; }
|
|
74
|
+
.pl-elabel.bad { fill: var(--bad); } .pl-elabel.amber { fill: var(--amber); }
|
|
75
|
+
.pl-part { fill: var(--beam); } .pl-part.structure { opacity: .45; }
|
|
76
|
+
.pl-node { cursor: pointer; }
|
|
77
|
+
.pl-node rect { fill: var(--node); stroke: var(--node-line); stroke-width: 1.2; }
|
|
78
|
+
.pl-node text { font-size: 13px; fill: var(--ink); pointer-events: none; }
|
|
79
|
+
.pl-node .pl-sub { font-size: 10.5px; fill: var(--muted); }
|
|
80
|
+
.pl-node.orchestrator rect { fill: var(--manager); stroke: var(--manager); } .pl-node.orchestrator text { fill: var(--on-manager); }
|
|
81
|
+
.pl-node.system rect { stroke: var(--sys); }
|
|
82
|
+
.pl-node.ghost rect, .pl-node.unused rect { stroke: var(--ghost); stroke-dasharray: 4 3; }
|
|
83
|
+
.pl-node.ghost text, .pl-node.unused text { fill: var(--muted); }
|
|
84
|
+
.pl-node:focus { outline: none; } .pl-node:focus-visible rect { stroke: var(--focus); stroke-width: 2.4; }
|
|
85
|
+
.pl-svg .pl-node text.pl-vol { font-size: 11px; fill: var(--warm); font-weight: 650; } .pl-svg .pl-node text.pl-vol.silent { fill: var(--bad); }
|
|
86
|
+
/* the lenses */
|
|
87
|
+
.pl-svg.lensing .pl-node { opacity: .35; }
|
|
88
|
+
.pl-svg.lensing .pl-node.l-ok, .pl-svg.lensing .pl-node.l-amber, .pl-svg.lensing .pl-node.l-bad { opacity: 1; }
|
|
89
|
+
.pl-node.l-ok rect { stroke: var(--ok); stroke-width: 3; } .pl-node.l-amber rect { stroke: var(--amber); stroke-width: 3; }
|
|
90
|
+
.pl-node.l-bad rect { stroke: var(--bad); stroke-width: 3.2; animation: pl-pulse 1.1s ease-in-out infinite; }
|
|
91
|
+
.pl-node.warn rect { stroke: var(--bad); stroke-width: 3.2; animation: pl-pulse .8s ease-in-out infinite; }
|
|
92
|
+
@keyframes pl-pulse { 50% { stroke-opacity: .25; } }
|
|
93
|
+
.pl-dark { fill: var(--dark); pointer-events: none; transition: opacity .45s; }
|
|
94
|
+
@media (prefers-reduced-motion: reduce) { .pl-node.l-bad rect, .pl-node.warn rect { animation: none; } .pl-dark { transition: none; } }
|
|
95
|
+
.pl-foot { max-width: 1480px; margin: 10px auto 0; font-size: 12px; color: var(--muted); text-align: center; }
|
|
96
|
+
</style>
|
|
97
|
+
</head>
|
|
98
|
+
<body>
|
|
99
|
+
<div class="cl" id="app"></div>
|
|
100
|
+
<script type="application/json" id="cast">{{CAST}}</script>
|
|
101
|
+
<script>(()=>{var H={en:{title:"Stage",sub:"The cast on stage: who talks to whom, the traffic each one gets, the patterns as lenses, and a follow spot that walks a call.",privacy:"This file holds the client\u2019s data: keep it in the client\u2019s environment.",client:"Customer",lenses:"Lenses \u2014 light a pattern",lensHint:"Pick a lens: who follows the pattern lights green, who is off blinks red.",scenes:"Follow spot \u2014 walk a call",narrIdle:"Pick a call under \xABFollow spot\xBB to walk it step by step here.",actor:"Actor",actorHint:"Click someone on stage.",resume:"See the r\xE9sum\xE9 \u2192",map:"\u2190 Map",vol:(e,n)=>`${e.toLocaleString("en")} topic starts / ${n} days`,silent:"no telemetry",noTraffic:"The traffic shows with the export of Application Insights (--telemetry).",legend:["traffic (topic starts of who receives it)","off a pattern","to decide","not in the folder / unused"],structure:"Inner arrows (flows, systems) are structure, not measured traffic.",step:(e,n)=>`step ${e}/${n}`,keys:"\u2190 \u2192 to move \xB7 space plays all \xB7 Esc leaves",orchestrator:"Orchestrator",specialist:"Specialist",fingerprint:"Fingerprint of its instructions",foot:"cast-list \xB7 no model: what is here is what the files and tables say."},pt:{title:"Palco",sub:"O elenco no palco: quem fala com quem, o tr\xE1fego de cada um, os padr\xF5es como lentes, e um canh\xE3o que segue uma chamada.",privacy:"Este arquivo tem dados do cliente: deixe-o no ambiente dele.",client:"Cliente",lenses:"Lentes \u2014 acenda um padr\xE3o",lensHint:"Escolha uma lente: quem segue o padr\xE3o acende em verde, quem foge pisca em vermelho.",scenes:"Canh\xE3o seguidor \u2014 siga uma chamada",narrIdle:"Escolha uma chamada em \xABCanh\xE3o seguidor\xBB para segui-la passo a passo aqui.",actor:"Ator",actorHint:"Clique em algu\xE9m no palco.",resume:"Ver curr\xEDculo \u2192",map:"\u2190 Mapa",vol:(e,n)=>`${e.toLocaleString("pt-BR")} in\xEDcios de t\xF3pico / ${n} dias`,silent:"sem telemetria",noTraffic:"O tr\xE1fego aparece com a exporta\xE7\xE3o do Application Insights (--telemetry).",legend:["tr\xE1fego (in\xEDcios de t\xF3pico de quem recebe)","fora do padr\xE3o","a decidir","fora da pasta / sem uso"],structure:"As setas internas (fluxos, sistemas) s\xE3o estrutura, n\xE3o tr\xE1fego medido.",step:(e,n)=>`passo ${e}/${n}`,keys:"\u2190 \u2192 avan\xE7am e voltam \xB7 espa\xE7o toca tudo \xB7 Esc sai",orchestrator:"Orquestrador",specialist:"Especialista",fingerprint:"Impress\xE3o digital das instru\xE7\xF5es",foot:"cast-list \xB7 sem modelo: o que est\xE1 aqui \xE9 o que os arquivos e as planilhas dizem."}},y=JSON.parse(document.getElementById("cast").textContent),{stage:o,lang:h}=y,l=H[h];document.documentElement.lang=h==="pt"?"pt-BR":"en";document.title=`${l.title} \xB7 ${o.name??y.id}`;var s=e=>String(e??"").replace(/[&<>"']/g,n=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[n]),v=document.getElementById("app"),N=matchMedia("(prefers-reduced-motion: reduce)").matches,m=168,k=48,x=e=>o.nodes.find(n=>n.id===e),I=e=>e.kind==="orchestrator"||e.kind==="specialist",b,d,$=0,L,A;function V(e){let n=x(e.from),i=x(e.to),r=i.x>=n.x,c=n.x+(r?m/2:-m/2),p=i.x+(r?-m/2:m/2),a=(c+p)/2;return{d:`M${c},${n.y} C${a},${n.y} ${a},${i.y} ${p},${i.y}`,mx:a,my:(n.y+i.y)/2,x2:p,y2:i.y}}var B=e=>e.kind==="client"?l.client:e.labels?.[h]??e.label,w=(e,n=24)=>e.length>n?`${e.slice(0,n-1)}\u2026`:e,j=o.nodes.filter(I),G=j.length>1?j.map(e=>e.label).reduce((e,n)=>{let i=0;for(;i<e.length&&e[i]===n[i];)i++;return e.slice(0,i)}):"",C=/[-–·:]\s*$/.test(G)?G:"",O=e=>(I(e)||e.kind==="ghost")&&C&&e.label.startsWith(C)?e.label.slice(C.length):B(e);function P(){let e=o.lenses.find(t=>t.id===b),n=t=>{let u=["pl-node",t.kind,t.unused?"unused":""];return e&&u.push(e.bad.includes(t.id)?"l-bad":e.amber.includes(t.id)?"l-amber":e.ok.includes(t.id)?"l-ok":""),d&&d.steps[$]?.node===t.id&&d.steps[$].warn&&u.push("warn"),u.filter(Boolean).join(" ")},i=o.groups.map(t=>`<rect class="pl-band" x="${x(o.nodes.find(u=>u.kind==="specialist").id).x-m/2-18}" y="${t.y1}" width="${m+36}" height="${t.y2-t.y1}" rx="12"/><text class="pl-group" x="${x(o.nodes.find(u=>u.kind==="specialist").id).x-m/2-10}" y="${t.y1-6}">${s(t.label)}</text>`).join(""),r=o.edges.map((t,u)=>{let f=V(t),g=t.tone??"",M=t.kind==="traffic"&&x(t.to).kind!=="ghost",F=t.label?M?`<text class="pl-elabel ${g}" x="${f.x2-10}" y="${f.y2-9}" text-anchor="end">${s(w(t.label[h],56))}</text>`:`<text class="pl-elabel ${g}" x="${f.mx}" y="${f.my-6}" text-anchor="middle">${s(w(t.label[h],40))}</text>`:"";return`<path id="e${u}" class="pl-edge ${t.kind} ${g} ${t.dashed?"dashed":""}" d="${f.d}"/>${F}`}).join(""),c=o.nodes.map(t=>{let u=I(t)&&o.hasTelemetry?`<text class="pl-vol ${t.silent?"silent":""}" x="${m/2}" y="${k+14}" text-anchor="middle">${s(t.silent?l.silent:t.volume!==void 0?l.vol(t.volume,o.days??0):"")}</text>`:"",f=I(t)?` tabindex="0" role="button" aria-label="${s(t.label)}"`:"";return`<g class="${n(t)}" data-node="${s(t.id)}" transform="translate(${t.x-m/2},${t.y-k/2})"${f}>${u}<rect width="${m}" height="${k}" rx="${t.kind==="client"?k/2:10}"/><text x="${m/2}" y="21" text-anchor="middle">${s(w(O(t),t.kind==="ghost"?30:22))}</text><text class="pl-sub" x="${m/2}" y="37" text-anchor="middle">${s(w(t.sub[h],30))}</text></g>`}).join(""),p=d?.steps[$],a=p?x(p.node):void 0;return`<svg class="pl-svg ${e?"lensing":""}" viewBox="0 0 ${o.width} ${o.height}" role="img" aria-label="${s(l.title)}">
|
|
102
|
+
<defs><radialGradient id="pl-spot" r="0.5"><stop offset="0" stop-color="#000"/><stop offset="0.6" stop-color="#000"/><stop offset="1" stop-color="#000" stop-opacity="0"/></radialGradient>
|
|
103
|
+
<mask id="pl-mask"><rect width="${o.width}" height="${o.height}" fill="#fff"/>${a?`<ellipse cx="${a.x}" cy="${a.y}" rx="${m/2+34}" ry="${k/2+30}" fill="url(#pl-spot)"/>`:""}</mask></defs>
|
|
104
|
+
<g>${i}</g><g>${r}</g><g id="pl-parts"></g><g>${c}</g>
|
|
105
|
+
<rect class="pl-dark" width="${o.width}" height="${o.height}" mask="url(#pl-mask)" opacity="${d?.72:0}"/>
|
|
106
|
+
</svg>`}function D(){let e=o.lenses.find(a=>a.id===b),n=`<section class="pl-box"><h2>${s(l.lenses)}</h2><div class="pl-chips">${o.lenses.map(a=>`<button data-lens="${s(a.id)}" aria-pressed="${a.id===b}">${s(w(a.name[h],28))}</button>`).join("")}</div><p class="pl-cap">${e?s(e.caption[h]):`<span class="pl-note">${s(l.lensHint)}</span>`}</p></section>`,i=o.scenes.length?`<section class="pl-box"><h2>${s(l.scenes)}</h2><div class="pl-scenes">${o.scenes.map(a=>`<button data-scene="${s(a.id)}" aria-pressed="${a.id===d?.id}">\u25B6 ${s(a.title[h])}</button>`).join("")}</div></section>`:"",r=o.actors.find(a=>a.id===L),c=r?`<section class="pl-box pl-card" aria-live="polite"><h2>${s(l.actor)}</h2><p class="pl-name">${s(r.name)}</p><p class="pl-role">${s(r.role==="orchestrator"?l.orchestrator:l.specialist)}${r.types.length?` \xB7 ${s(r.types.join(" \xB7 "))}`:""}${o.hasTelemetry?` \xB7 ${s(r.silent?l.silent:r.volume!==void 0?l.vol(r.volume,o.days??0):"")}`:""}</p>
|
|
107
|
+
${r.fingerprint.length?`<p class="pl-note">${s(l.fingerprint)}</p><div class="pl-dna">${r.fingerprint.map(a=>`<span class="${a.kind}" style="flex:${a.size}" title="${s(a.label??"")}"></span>`).join("")}</div>`:""}
|
|
108
|
+
<ul class="pl-checks">${r.checks.map(a=>`<li class="${a.state}">${s(a[h])}</li>`).join("")}${r.access.map(a=>`<li class="decide">${s(a[h])}</li>`).join("")}</ul>
|
|
109
|
+
${y.playbill?`<p class="pl-note" style="margin-top:8px"><a href="${s(y.playbill)}#${encodeURIComponent(r.id)}">${s(l.resume)}</a></p>`:""}</section>`:`<section class="pl-box pl-card"><h2>${s(l.actor)}</h2><p class="pl-note">${s(l.actorHint)}</p></section>`,p=`<section class="pl-box"><div class="pl-legend">${[["var(--beam)",l.legend[0]],["var(--bad)",l.legend[1]],["var(--amber)",l.legend[2]],["var(--ghost)",l.legend[3]]].map(([a,t])=>`<span><i class="pl-dot" style="background:${a}"></i>${s(t)}</span>`).join("")}</div><p class="pl-note" style="margin-top:8px">${s(o.hasTelemetry?l.structure:l.noTraffic)}</p></section>`;return n+i+c+p}function R(){let e=d?.steps[$];return!d||!e?`<div class="pl-narr idle"><span class="pl-note">${s(l.narrIdle)}</span></div>`:`<div class="pl-narr ${e.warn?"warn":""}" role="status"><span class="pl-step">${s(l.step($+1,d.steps.length))}</span>${s(e.text[h])}${e.quote?`<q class="pb-quote">${s(e.quote)}</q>`:""}<span class="pl-keys">${s(l.keys)}</span></div>`}function E(){v.innerHTML=`<header class="pl-head">${y.map?`<p><a href="${s(y.map)}">${s(l.map)}</a></p>`:""}<h1>${s(l.title)} \xB7 <span>${s(o.name??y.id)}</span></h1><p>${s(l.sub)}</p><p class="pl-private">${s(l.privacy)}</p></header>
|
|
110
|
+
<div class="pl-wrap"><div class="pl-stage">${P()}${R()}</div><aside class="pl-panel">${D()}</aside></div>
|
|
111
|
+
<p class="pl-foot">${s(l.foot)}</p>`,z()}var q;function z(){q&&cancelAnimationFrame(q);let e=v.querySelector("#pl-parts");if(!e||!o.hasTelemetry)return;let n=[];o.edges.forEach((c,p)=>{let a=v.querySelector(`#e${p}`);if(!a||c.dashed)return;let t=c.kind==="traffic"?c.volume?Math.max(1,Math.min(8,Math.round(Math.log10(c.volume+1)*2.2))):0:1,u=a.getTotalLength();for(let f=0;f<t;f++){let g=document.createElementNS("http://www.w3.org/2000/svg","circle");g.setAttribute("r",c.kind==="traffic"?"3":"2.2"),g.setAttribute("class",`pl-part ${c.kind}`),e.appendChild(g),n.push({el:g,path:a,len:u,phase:f/t,speed:c.kind==="traffic"?11e-5:6e-5})}});let i=c=>{for(let p of n){let a=p.path.getPointAtLength(((p.phase+c*p.speed)%1+1)%1*p.len);p.el.setAttribute("cx",a.x.toFixed(1)),p.el.setAttribute("cy",a.y.toFixed(1))}};if(N)return i(0);let r=c=>{i(c),q=requestAnimationFrame(r)};q=requestAnimationFrame(r)}function S(){A&&clearInterval(A),A=void 0}function T(e){d&&($=Math.max(0,Math.min(d.steps.length-1,e)),E())}v.addEventListener("click",e=>{let n=e.target,i=n.closest("button"),r=n.closest("[data-node]");if(i?.dataset.lens)b=b===i.dataset.lens?void 0:i.dataset.lens,b&&(S(),d=void 0);else if(i?.dataset.scene)S(),d=d?.id===i.dataset.scene?void 0:o.scenes.find(c=>c.id===i.dataset.scene),$=0,d&&(b=void 0);else if(r&&o.actors.some(c=>c.id===r.dataset.node))L=r.dataset.node;else return;E()});v.addEventListener("keydown",e=>{let n=e.target.closest?.("[data-node]");n&&(e.key==="Enter"||e.key===" ")&&o.actors.some(i=>i.id===n.dataset.node)&&(e.preventDefault(),L=n.dataset.node,E(),v.querySelector(`[data-node="${CSS.escape(L)}"]`)?.focus())});document.addEventListener("keydown",e=>{if(d)if(e.key==="ArrowRight")T($+1);else if(e.key==="ArrowLeft")T($-1);else if(e.key==="Escape")S(),d=void 0,E();else if(e.key===" "&&!e.target.closest?.("[data-node]"))e.preventDefault(),S(),A=window.setInterval(()=>d&&$<d.steps.length-1?T($+1):S(),1800);else return});E();})();
|
|
112
|
+
</script>
|
|
113
|
+
</body>
|
|
114
|
+
</html>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
question,expectedResponse,actualResponse,testMethodType_1,result_1,passingScore_1,explanation_1,testMethodType_2,result_2,passingScore_2,explanation_2
|
|
2
|
+
Quanto custa o frete para o Nordeste?,"Conforme a tabela de frete","O frete para o Nordeste sai a partir da tabela por faixa de CEP.",GeneralQuality,Pass,,Seems relevant,CompareMeaning,Pass,70,Same meaning
|
|
3
|
+
O frete grátis vale para qualquer valor?,"Acima de 199 reais","Vale para compras acima de 199 reais.",GeneralQuality,Pass,,Seems relevant,CompareMeaning,Pass,70,Same meaning
|
|
4
|
+
Em quantos dias chega em Manaus?,"Até 12 dias úteis","Chega em poucos dias.",GeneralQuality,Fail,,Seems incomplete,CompareMeaning,Fail,70,Different meaning
|
|
5
|
+
Quero falar com uma pessoa,"Encaminhar para uma pessoa","Posso te ajudar com o frete por aqui.",GeneralQuality,Pass,,Seems relevant,CompareMeaning,Fail,70,Question not answered
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
question,expectedResponse,actualResponse,testMethodType_1,result_1,passingScore_1,explanation_1
|
|
2
|
+
Onde está meu pedido 1234?,,"O pedido 1234 saiu para entrega hoje. Prazo: até amanhã.",GeneralQuality,Pass,,Seems relevant; Seems complete
|
|
3
|
+
Posso pagar com boleto?,,"Sim: boleto à vista, com aprovação em até 2 dias úteis.",GeneralQuality,Pass,,Seems relevant; Seems complete
|
|
4
|
+
Quanto tempo leva o processamento?,,"O processamento leva até 1 dia útil depois do pagamento.",GeneralQuality,Pass,,Seems relevant
|
|
5
|
+
Quero falar com uma pessoa,,"Vou te passar para alguém da equipe agora.",GeneralQuality,Pass,,Seems relevant
|
|
@@ -40,7 +40,7 @@ decisions: # what the team decided about a pattern
|
|
|
40
40
|
`--declared <table.csv|.xlsx>` (more than once is fine) reads a table the team already has — the admin center's
|
|
41
41
|
inventory download, the team's list — and proposes a `declared:` line per agent and column, with the source
|
|
42
42
|
`<file> › line <n>` (`<file> › <sheet> › line <n>` for an .xlsx). The agent's column is found by its values; the key is
|
|
43
|
-
the column's title in lower case with `-` (`Linha de base` → `linha-de-base`; `Dono`/`Owner` → `owner`, `KPI`/`Indicador`
|
|
43
|
+
the column's title in lower case with `-` (`Linha de base` → `linha-de-base`; `Dono`/`Equipe`/`Squad` → `team`, the inventory's `Owner` → `owner`, `KPI`/`Indicador`
|
|
44
44
|
→ `kpi`). Listed with keys (`d1`…), written with `--take`, like the workbook below. Two tables saying the same key of
|
|
45
45
|
the same agent: the later one holds.
|
|
46
46
|
|
|
@@ -80,6 +80,7 @@ Every fact cast-list finds is a key. The keys below have a name; any other key k
|
|
|
80
80
|
| `instructions:present` | Has instructions | Tem instruções |
|
|
81
81
|
| `node:LogCustomTelemetryEvent` | Logs telemetry | Registra telemetria |
|
|
82
82
|
| `node:CSATQuestion` | Asks for satisfaction (CSAT) | Pergunta a satisfação (CSAT) |
|
|
83
|
+
| `eval:run` | Passed through an evaluation | Passou por uma avaliação |
|
|
83
84
|
| `orphan:topic:<value>` | Topic nobody calls <value> | Tópico que ninguém chama <valor> |
|
|
84
85
|
| `orphan:action:<value>` | Tool nobody can choose <value> | Ferramenta que ninguém escolhe <valor> |
|
|
85
86
|
| `orphan:variable:<value>` | Variable nobody uses <value> | Variável que ninguém usa <valor> |
|
|
@@ -109,7 +110,8 @@ Every fact cast-list finds is a key. The keys below have a name; any other key k
|
|
|
109
110
|
| Key | Name | Nome |
|
|
110
111
|
|---|---|---|
|
|
111
112
|
| `declared:kpi` | KPI declared | KPI declarado |
|
|
112
|
-
| `declared:
|
|
113
|
+
| `declared:team` | Team declared | Equipe declarada |
|
|
114
|
+
| `declared:owner` | Owner declared | Responsável declarado |
|
|
113
115
|
| `declared:<value>` | Declared: <value> | Declarado: <valor> |
|
|
114
116
|
|
|
115
117
|
## The Microsoft patterns
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# The RH of the cast (--playbill)
|
|
2
|
+
|
|
3
|
+
Generated from `dev/cast-list/src/`. The same cast, as people. Nothing is guessed: a field nothing says is left empty.
|
|
4
|
+
|
|
5
|
+
## The staff board
|
|
6
|
+
|
|
7
|
+
| Situation | When |
|
|
8
|
+
|---|---|
|
|
9
|
+
| on the staff | in the folder |
|
|
10
|
+
| being hired | called by the orchestrator and not in the folder, or in an environment (`declared:environment`) not the one most are in |
|
|
11
|
+
| with no owner | no `declared:owner` (the inventory's Owner) — only with the inventory |
|
|
12
|
+
| never published | a row in the inventory and no `declared:last-published` — only with the inventory |
|
|
13
|
+
| with no evaluation | no export of the Evaluate tab names it — only with `--evals` |
|
|
14
|
+
| with a development plan | one item of the plan or more that is not cleanup |
|
|
15
|
+
| in good standing | no item, or only cleanup |
|
|
16
|
+
|
|
17
|
+
## The résumé
|
|
18
|
+
|
|
19
|
+
The title is the first sentence of the instructions, as written; the summary, their first list (at most 5). The team is
|
|
20
|
+
`declared:team` (the team spreadsheet's «Dono»), the owner `declared:owner`, the goal `declared:kpi`. The performance
|
|
21
|
+
comes from `--telemetry`: topic starts (`TopicStart`), errors logged (the events of its YAML that fired) and those that
|
|
22
|
+
did not fire. The goal is never measured from the telemetry.
|
|
23
|
+
|
|
24
|
+
## Certifications (--evals)
|
|
25
|
+
|
|
26
|
+
`--evals <folder>` reads the exports of Copilot Studio's Evaluate tab, «Evaluate <name> <date>.csv»: who is named by
|
|
27
|
+
the file, a row passes when every `result_N` says Pass, and each method (`testMethodType_N`) is counted. Only the
|
|
28
|
+
counts are kept: `question`, `expectedResponse`, `actualResponse` and `explanation_N` are the client's
|
|
29
|
+
conversation and never reach an output. Who has one gets the fact `eval:run`, so a rule `has: "eval:*"` asks every
|
|
30
|
+
one of a role for it. A file that names nobody in the folder, or has no `result_1`, is told and left out.
|
|
31
|
+
|
|
32
|
+
## The review of access
|
|
33
|
+
|
|
34
|
+
Within each type of two or more, a connector, an MCP server or what a flow reaches that only one has is asked about on
|
|
35
|
+
the résumé and on the stage's card: does the post need it? It becomes a cleanup item of the plan only when the team
|
|
36
|
+
decides so — a decision `rejected` on that key, in the type's scope (`type:<id>`).
|
|
37
|
+
|
|
38
|
+
## The post
|
|
39
|
+
|
|
40
|
+
The accepted patterns of the role or type and the profile's rules (`has`); with no decision yet, the patterns someone is
|
|
41
|
+
off, as a proposal. A fact with no words below is «Has «…»».
|
|
42
|
+
|
|
43
|
+
| Key | Requirement (en) | Requirement (pt) |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `trigger:OnEscalate` | Hands over to a person (escalation) | Sabe encaminhar para uma pessoa (escalonamento) |
|
|
46
|
+
| `called-with:history` | Gets the conversation history from the orchestrator | Recebe do gestor o histórico da conversa |
|
|
47
|
+
| `node:LogCustomTelemetryEvent` | Logs its own errors (telemetry) | Registra os próprios erros (telemetria) |
|
|
48
|
+
| `declared:kpi` | Has a goal (KPI) | Tem uma meta (KPI) |
|
|
49
|
+
| `declared:owner` | Has an owner | Tem um responsável |
|
|
50
|
+
| `declared:team` | Has a team | Tem uma equipe |
|
|
51
|
+
|
|
52
|
+
## The development plan
|
|
53
|
+
|
|
54
|
+
What «to look at» says about one alone, written as an action, one item per fact, in this order:
|
|
55
|
+
|
|
56
|
+
1. **contradictions** — what was declared against the YAML; a rule that confronts the text with the setup.
|
|
57
|
+
2. **contract** — the inputs the orchestrator does not pass in one call (one item), a short routing description, a flow that returns nothing; a call to one not in the folder is the orchestrator’s.
|
|
58
|
+
3. **off** — off a pattern, one item per fact, a variation of an official block included.
|
|
59
|
+
4. **rules** — a rule of the profile not met.
|
|
60
|
+
5. **text** — a marker set twice in one.
|
|
61
|
+
6. **telemetry** — an event spelt another way, a field missing, an event that did not fire, no line in the telemetry, no telemetry.
|
|
62
|
+
7. **cleanup** — what nobody uses or is switched off — it does not put anyone in a plan alone.
|
|
63
|
+
|
|
64
|
+
Not in anyone's plan: what only one has (innovation or debt is the team's call), a type with one specialist, a marker
|
|
65
|
+
set in different ways across the cast, what was not understood, and what a decision rejected.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// (.xlsx) and the cast file (what people said, what was decided). No model, no install, no network: Node 18 or later.
|
|
5
5
|
//
|
|
6
6
|
// node build.mjs <agents-folder> --check [--profile <p.cast.yaml>] [--lang en|pt] only compare: nothing is written
|
|
7
|
-
// node build.mjs <agents-folder> [--profile <p.cast.yaml>] [--html] [--xlsx] [--lesson] [--all] [--lang en|pt] [--out <folder>]
|
|
7
|
+
// node build.mjs <agents-folder> [--profile <p.cast.yaml>] [--html] [--xlsx] [--lesson] [--playbill] [--plot] [--all] [--lang en|pt] [--out <folder>]
|
|
8
8
|
// node build.mjs <agents-folder> --facts [--out <folder>] only the facts, for a model: <id>.facts.json
|
|
9
9
|
// node build.mjs <agents-folder> --from <edited.xlsx> [--take <key,key…|all>] [--out <folder>]
|
|
10
10
|
// what was filled in the workbook (Declared, the Decision column): listed with a key each,
|
|
@@ -12,27 +12,30 @@
|
|
|
12
12
|
// node build.mjs <agents-folder> --declared <table.csv|.xlsx> [--declared <another>] [--agent-column <title>] [--take <key,…|all>]
|
|
13
13
|
// what a spreadsheet says about the agents (the team's, the admin center's inventory): each
|
|
14
14
|
// column becomes a declared fact, listed with a key, and with --take written to the cast file
|
|
15
|
+
// … --evals <folder> the exports of Copilot Studio's Evaluate tab («Evaluate <agent> <date>.csv»): who
|
|
16
|
+
// passed how many, by method — the counts only, never what was asked or answered
|
|
15
17
|
// … --telemetry <export.csv|.xlsx> [--telemetry-days <n>] what fired, from Application Insights (references/telemetry.md):
|
|
16
18
|
// the events in the YAML that did not fire, and who has no line at all
|
|
17
19
|
//
|
|
18
|
-
// With no --html/--xlsx/--lesson it makes the map. Files go in --out (or beside the folder), named by the folder:
|
|
19
|
-
// <id>.html, <id>.xlsx, <id>.lesson.html (a chalk-talk lesson),
|
|
20
|
+
// With no --html/--xlsx/--lesson/--playbill/--plot it makes the map. Files go in --out (or beside the folder), named by the folder:
|
|
21
|
+
// <id>.html, <id>.xlsx, <id>.lesson.html (a chalk-talk lesson), <id>.playbill.html (the RH of the cast), <id>.plot.html (the stage) and
|
|
22
|
+
// <id>.cast.yaml, kept up to date (read again on every
|
|
20
23
|
// run). Exit codes: 0 done (what to look at is printed, and is not an error); 1 the profile or the cast file breaks its
|
|
21
24
|
// format (each problem with its line), or no agent in the folder; 2 bad arguments; 3 made, but the lesson's reviewer
|
|
22
25
|
// found something to fix. The outputs hold the client's data: keep them in the client's environment. The lesson's
|
|
23
26
|
// review uses Chrome, Chromium or Edge when there is one.
|
|
24
|
-
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
|
|
27
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
|
|
25
28
|
import { basename, dirname, join, resolve } from 'node:path'
|
|
26
29
|
import { fileURLToPath } from 'node:url'
|
|
27
30
|
import { runPage } from './browser.mjs'
|
|
28
|
-
import { agentsIn, applyChanges, compare, declaredFrom, DEFAULT_PROFILE, extractAgent, firedFrom, fromSheets, groupLooks, mergeDeclared, knownKinds, labelOf, lessonOf, problemText, readCast, readCsv, readProfile, reviewCast, validateScene, workbookFile, workbookOf, writeCast } from './engine.mjs'
|
|
31
|
+
import { agentsIn, applyChanges, compare, declaredFrom, DEFAULT_PROFILE, evalsFrom, extractAgent, firedFrom, fromSheets, groupLooks, playbillOf, stageOf, mergeDeclared, knownKinds, labelOf, lessonOf, problemText, readCast, readCsv, readProfile, reviewCast, validateScene, withEvals, workbookFile, workbookOf, writeCast } from './engine.mjs'
|
|
29
32
|
import { readXlsx } from './read-xlsx.mjs'
|
|
30
33
|
import { fill, inline } from './page.mjs'
|
|
31
34
|
|
|
32
35
|
const here = dirname(fileURLToPath(import.meta.url))
|
|
33
36
|
const argv = process.argv.slice(2)
|
|
34
|
-
const valued = new Set(['--lang', '--out', '--profile', '--from', '--take', '--declared', '--agent-column', '--telemetry', '--telemetry-days'])
|
|
35
|
-
const flags = new Set(['--check', '--html', '--xlsx', '--lesson', '--all', '--facts', ...valued])
|
|
37
|
+
const valued = new Set(['--lang', '--out', '--profile', '--from', '--take', '--declared', '--agent-column', '--telemetry', '--telemetry-days', '--evals'])
|
|
38
|
+
const flags = new Set(['--check', '--html', '--xlsx', '--lesson', '--playbill', '--plot', '--all', '--facts', ...valued])
|
|
36
39
|
const option = (name) => {
|
|
37
40
|
const at = argv.indexOf(name)
|
|
38
41
|
return at >= 0 ? argv[at + 1] : undefined
|
|
@@ -40,11 +43,11 @@ const option = (name) => {
|
|
|
40
43
|
const options = (name) => argv.flatMap((a, i) => (a === name && argv[i + 1] ? [argv[i + 1]] : []))
|
|
41
44
|
const args = argv.filter((a, i) => !a.startsWith('--') && !valued.has(argv[i - 1]))
|
|
42
45
|
const usage = `usage: node build.mjs <agents-folder> --check [--profile <p.cast.yaml>] [--lang en|pt]
|
|
43
|
-
node build.mjs <agents-folder> [--profile <p.cast.yaml>] [--html] [--xlsx] [--lesson] [--all] [--lang en|pt] [--out <folder>]
|
|
46
|
+
node build.mjs <agents-folder> [--profile <p.cast.yaml>] [--html] [--xlsx] [--lesson] [--playbill] [--plot] [--all] [--lang en|pt] [--out <folder>]
|
|
44
47
|
node build.mjs <agents-folder> --facts [--out <folder>]
|
|
45
48
|
node build.mjs <agents-folder> --from <edited.xlsx> [--take <key,key…|all>] [--out <folder>]
|
|
46
49
|
node build.mjs <agents-folder> --declared <table.csv|.xlsx> [--agent-column <title>] [--take <key,key…|all>] [--out <folder>]
|
|
47
|
-
… [--telemetry <export.csv|.xlsx>] [--telemetry-days <n>]`
|
|
50
|
+
… [--telemetry <export.csv|.xlsx>] [--telemetry-days <n>] [--evals <folder>]`
|
|
48
51
|
const lang = option('--lang') ?? 'en'
|
|
49
52
|
const fail = (msg) => {
|
|
50
53
|
console.error(`${msg}\n${usage}`)
|
|
@@ -58,6 +61,7 @@ for (const v of valued) if (argv.includes(v) && !option(v)) fail(`✗ ${v} needs
|
|
|
58
61
|
if (argv.includes('--take') && !argv.includes('--from') && !argv.includes('--declared')) fail('✗ --take goes with --from or --declared')
|
|
59
62
|
if (argv.includes('--from') && argv.includes('--declared')) fail('✗ --from or --declared, one at a time')
|
|
60
63
|
for (const f of [...options('--declared'), ...options('--telemetry')]) if (!existsSync(f) || !/\.(csv|xlsx)$/i.test(f)) fail(`✗ ${f}: a .csv or an .xlsx that is there`)
|
|
64
|
+
if (option('--evals') && !(existsSync(option('--evals')) && statSync(option('--evals')).isDirectory())) fail(`✗ ${option('--evals')}: a folder with the exports of the Evaluate tab`)
|
|
61
65
|
const days = Number(option('--telemetry-days') ?? 30)
|
|
62
66
|
if (!Number.isInteger(days) || days < 1) fail('✗ --telemetry-days is a whole number of days')
|
|
63
67
|
|
|
@@ -75,6 +79,8 @@ const T = {
|
|
|
75
79
|
noEvent: (f, cols) => `✗ ${f}: no column of the event («evento», «event» or «name»; columns: ${cols.join(', ')})`,
|
|
76
80
|
unmatched: (n, f, list) => `• ${f}: ${n} ${n === 1 ? 'row names' : 'rows name'} nobody in the folder: ${list}`, line: 'line',
|
|
77
81
|
telemetry: (f, n, d) => `✓ telemetry ${f}: ${n} ${n === 1 ? 'row' : 'rows'} of the folder's agents, ${d} days`,
|
|
82
|
+
evals: (d, n, a) => `✓ evals ${d}: ${n} ${n === 1 ? 'evaluation' : 'evaluations'} of ${a} in the folder (counts only)`,
|
|
83
|
+
evalUnknown: (f, why) => `• evals ${f}: ${why === 'who' ? 'its name says nobody in the folder («Evaluate <name> <date>.csv»)' : 'no result_1 column: not an export of the Evaluate tab'}`,
|
|
78
84
|
},
|
|
79
85
|
pt: {
|
|
80
86
|
found: (n, o, s) => `✓ ${n} no elenco: ${o} ${o === 1 ? 'orquestrador' : 'orquestradores'}, ${s} ${s === 1 ? 'especialista' : 'especialistas'}`,
|
|
@@ -89,6 +95,8 @@ const T = {
|
|
|
89
95
|
noEvent: (f, cols) => `✗ ${f}: nenhuma coluna do evento («evento», «event» ou «name»; colunas: ${cols.join(', ')})`,
|
|
90
96
|
unmatched: (n, f, list) => `• ${f}: ${n} ${n === 1 ? 'linha não cita' : 'linhas não citam'} ninguém da pasta: ${list}`, line: 'linha',
|
|
91
97
|
telemetry: (f, n, d) => `✓ telemetria ${f}: ${n} ${n === 1 ? 'linha' : 'linhas'} dos agentes da pasta, ${d} dias`,
|
|
98
|
+
evals: (d, n, a) => `✓ avaliações ${d}: ${n} ${n === 1 ? 'avaliação' : 'avaliações'} de ${a} da pasta (só as contagens)`,
|
|
99
|
+
evalUnknown: (f, why) => `• avaliações ${f}: ${why === 'who' ? 'o nome não cita ninguém da pasta («Evaluate <nome> <data>.csv»)' : 'sem a coluna result_1: não é uma exportação da aba Evaluate'}`,
|
|
92
100
|
},
|
|
93
101
|
}[lang]
|
|
94
102
|
|
|
@@ -199,7 +207,17 @@ if (option('--telemetry')) {
|
|
|
199
207
|
}
|
|
200
208
|
if (!fired) tableProblem(file, problem)
|
|
201
209
|
}
|
|
202
|
-
|
|
210
|
+
// the evaluations of the Evaluate tab: the counts only, and eval:run for who has one (a rule «has: eval:*»)
|
|
211
|
+
let evalSets
|
|
212
|
+
if (option('--evals')) {
|
|
213
|
+
const dir = option('--evals')
|
|
214
|
+
const names = readdirSync(dir).filter((f) => /\.csv$/i.test(f)).sort()
|
|
215
|
+
const ev = evalsFrom(names.map((name) => ({ name: `${basename(dir)}/${name}`, table: readCsv(readFileSync(join(dir, name), 'utf8'), name) })), found)
|
|
216
|
+
evalSets = ev.sets
|
|
217
|
+
console.log(T.evals(dir, ev.sets.length, new Set(ev.sets.map((s) => s.agent)).size))
|
|
218
|
+
for (const u of ev.unknown) console.log(T.evalUnknown(u.file, u.why))
|
|
219
|
+
}
|
|
220
|
+
const result = compare(evalSets ? withEvals(found, evalSets) : found, profile, cast, fired)
|
|
203
221
|
const looks = reviewCast(result)
|
|
204
222
|
const roles = Object.values(result.roles)
|
|
205
223
|
console.log(T.found(found.length, roles.filter((r) => r === 'orchestrator').length, roles.filter((r) => r === 'specialist').length))
|
|
@@ -213,8 +231,8 @@ else console.log(T.nothing)
|
|
|
213
231
|
if (argv.includes('--check')) process.exit(0)
|
|
214
232
|
|
|
215
233
|
const all = argv.includes('--all')
|
|
216
|
-
const want = { html: all || argv.includes('--html'), xlsx: all || argv.includes('--xlsx'), lesson: all || argv.includes('--lesson') }
|
|
217
|
-
if (!want.xlsx && !want.lesson) want.html = true
|
|
234
|
+
const want = { html: all || argv.includes('--html'), xlsx: all || argv.includes('--xlsx'), lesson: all || argv.includes('--lesson'), playbill: all || argv.includes('--playbill'), plot: all || argv.includes('--plot') }
|
|
235
|
+
if (!want.xlsx && !want.lesson && !want.playbill && !want.plot) want.html = true
|
|
218
236
|
mkdirSync(out, { recursive: true })
|
|
219
237
|
// the cast file keeps this run's roles and blocks of text (so a block keeps its id across small edits), under what
|
|
220
238
|
// people wrote in it; the types a person gave an agent stay, the ones the profile works out are not written
|
|
@@ -226,9 +244,21 @@ console.log(`✓ cast ${castFile}`)
|
|
|
226
244
|
if (want.html) {
|
|
227
245
|
const page = join(out, `${id}.html`)
|
|
228
246
|
const map = readFileSync(join(here, '../assets/map.html'), 'utf8')
|
|
229
|
-
writeFileSync(page, fill(map, { CAST: inline({ id, lang, result, looks, cast: kept, castYaml, focusAreas: profile.focusAreas, threshold: profile.threshold }) }))
|
|
247
|
+
writeFileSync(page, fill(map, { CAST: inline({ id, lang, result, looks, cast: kept, castYaml, focusAreas: profile.focusAreas, threshold: profile.threshold, ...(want.playbill ? { playbill: `${id}.playbill.html` } : {}), ...(want.plot ? { plot: `${id}.plot.html` } : {}) }) }))
|
|
230
248
|
console.log(`✓ map ${page}`)
|
|
231
249
|
}
|
|
250
|
+
if (want.playbill) {
|
|
251
|
+
const page = join(out, `${id}.playbill.html`)
|
|
252
|
+
const playbill = playbillOf(result, looks, found, kept, profile.rules, fired, evalSets)
|
|
253
|
+
writeFileSync(page, fill(readFileSync(join(here, '../assets/playbill.html'), 'utf8'), { CAST: inline({ id, lang, playbill, ...(want.html ? { map: `${id}.html` } : {}) }) }))
|
|
254
|
+
console.log(`✓ rh ${page}`)
|
|
255
|
+
}
|
|
256
|
+
if (want.plot) {
|
|
257
|
+
const page = join(out, `${id}.plot.html`)
|
|
258
|
+
const stage = stageOf(result, looks, found, fired)
|
|
259
|
+
writeFileSync(page, fill(readFileSync(join(here, '../assets/plot.html'), 'utf8'), { CAST: inline({ id, lang, stage, ...(want.html ? { map: `${id}.html` } : {}), ...(want.playbill ? { playbill: `${id}.playbill.html` } : {}) }) }))
|
|
260
|
+
console.log(`✓ stage ${page}`)
|
|
261
|
+
}
|
|
232
262
|
if (want.xlsx) {
|
|
233
263
|
const book = join(out, `${id}.xlsx`)
|
|
234
264
|
writeFileSync(book, workbookFile(workbookOf(result, looks, kept, lang)))
|