estela 0.1.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.
Files changed (186) hide show
  1. package/README.md +138 -0
  2. package/dist/billing/amortize.d.ts +44 -0
  3. package/dist/billing/amortize.d.ts.map +1 -0
  4. package/dist/billing/amortize.js +83 -0
  5. package/dist/billing/amortize.js.map +1 -0
  6. package/dist/billing/amortize.test.d.ts +2 -0
  7. package/dist/billing/amortize.test.d.ts.map +1 -0
  8. package/dist/billing/amortize.test.js +121 -0
  9. package/dist/billing/amortize.test.js.map +1 -0
  10. package/dist/billing/format.test.d.ts +2 -0
  11. package/dist/billing/format.test.d.ts.map +1 -0
  12. package/dist/billing/format.test.js +110 -0
  13. package/dist/billing/format.test.js.map +1 -0
  14. package/dist/billing/invoice.d.ts +52 -0
  15. package/dist/billing/invoice.d.ts.map +1 -0
  16. package/dist/billing/invoice.js +148 -0
  17. package/dist/billing/invoice.js.map +1 -0
  18. package/dist/billing/invoice.test.d.ts +2 -0
  19. package/dist/billing/invoice.test.d.ts.map +1 -0
  20. package/dist/billing/invoice.test.js +200 -0
  21. package/dist/billing/invoice.test.js.map +1 -0
  22. package/dist/billing/sessionize.d.ts +104 -0
  23. package/dist/billing/sessionize.d.ts.map +1 -0
  24. package/dist/billing/sessionize.js +0 -0
  25. package/dist/billing/sessionize.js.map +1 -0
  26. package/dist/billing/sessionize.test.d.ts +2 -0
  27. package/dist/billing/sessionize.test.d.ts.map +1 -0
  28. package/dist/billing/sessionize.test.js +67 -0
  29. package/dist/billing/sessionize.test.js.map +1 -0
  30. package/dist/cli.d.ts +4 -0
  31. package/dist/cli.d.ts.map +1 -0
  32. package/dist/cli.js +1091 -0
  33. package/dist/cli.js.map +1 -0
  34. package/dist/cloud/auth.d.ts +4 -0
  35. package/dist/cloud/auth.d.ts.map +1 -0
  36. package/dist/cloud/auth.js +98 -0
  37. package/dist/cloud/auth.js.map +1 -0
  38. package/dist/cloud/client.d.ts +12 -0
  39. package/dist/cloud/client.d.ts.map +1 -0
  40. package/dist/cloud/client.js +34 -0
  41. package/dist/cloud/client.js.map +1 -0
  42. package/dist/db/schema.d.ts +11 -0
  43. package/dist/db/schema.d.ts.map +1 -0
  44. package/dist/db/schema.js +424 -0
  45. package/dist/db/schema.js.map +1 -0
  46. package/dist/db/schema.test.d.ts +2 -0
  47. package/dist/db/schema.test.d.ts.map +1 -0
  48. package/dist/db/schema.test.js +335 -0
  49. package/dist/db/schema.test.js.map +1 -0
  50. package/dist/db/store.d.ts +113 -0
  51. package/dist/db/store.d.ts.map +1 -0
  52. package/dist/db/store.js +550 -0
  53. package/dist/db/store.js.map +1 -0
  54. package/dist/doctor.d.ts +24 -0
  55. package/dist/doctor.d.ts.map +1 -0
  56. package/dist/doctor.js +229 -0
  57. package/dist/doctor.js.map +1 -0
  58. package/dist/export/csv.d.ts +9 -0
  59. package/dist/export/csv.d.ts.map +1 -0
  60. package/dist/export/csv.js +81 -0
  61. package/dist/export/csv.js.map +1 -0
  62. package/dist/export/invoice-pdf.d.ts +21 -0
  63. package/dist/export/invoice-pdf.d.ts.map +1 -0
  64. package/dist/export/invoice-pdf.js +135 -0
  65. package/dist/export/invoice-pdf.js.map +1 -0
  66. package/dist/export/panel.d.ts +78 -0
  67. package/dist/export/panel.d.ts.map +1 -0
  68. package/dist/export/panel.js +693 -0
  69. package/dist/export/panel.js.map +1 -0
  70. package/dist/export/panel.test.d.ts +2 -0
  71. package/dist/export/panel.test.d.ts.map +1 -0
  72. package/dist/export/panel.test.js +273 -0
  73. package/dist/export/panel.test.js.map +1 -0
  74. package/dist/export/pdf.d.ts +32 -0
  75. package/dist/export/pdf.d.ts.map +1 -0
  76. package/dist/export/pdf.js +229 -0
  77. package/dist/export/pdf.js.map +1 -0
  78. package/dist/export/pdf.test.d.ts +2 -0
  79. package/dist/export/pdf.test.d.ts.map +1 -0
  80. package/dist/export/pdf.test.js +151 -0
  81. package/dist/export/pdf.test.js.map +1 -0
  82. package/dist/export/share.d.ts +42 -0
  83. package/dist/export/share.d.ts.map +1 -0
  84. package/dist/export/share.js +181 -0
  85. package/dist/export/share.js.map +1 -0
  86. package/dist/export/share.test.d.ts +2 -0
  87. package/dist/export/share.test.d.ts.map +1 -0
  88. package/dist/export/share.test.js +116 -0
  89. package/dist/export/share.test.js.map +1 -0
  90. package/dist/metrics/index.d.ts +101 -0
  91. package/dist/metrics/index.d.ts.map +1 -0
  92. package/dist/metrics/index.js +221 -0
  93. package/dist/metrics/index.js.map +1 -0
  94. package/dist/metrics/metrics.test.d.ts +2 -0
  95. package/dist/metrics/metrics.test.d.ts.map +1 -0
  96. package/dist/metrics/metrics.test.js +162 -0
  97. package/dist/metrics/metrics.test.js.map +1 -0
  98. package/dist/metrics/people.d.ts +46 -0
  99. package/dist/metrics/people.d.ts.map +1 -0
  100. package/dist/metrics/people.js +126 -0
  101. package/dist/metrics/people.js.map +1 -0
  102. package/dist/metrics/people.test.d.ts +2 -0
  103. package/dist/metrics/people.test.d.ts.map +1 -0
  104. package/dist/metrics/people.test.js +84 -0
  105. package/dist/metrics/people.test.js.map +1 -0
  106. package/dist/metrics/team.d.ts +41 -0
  107. package/dist/metrics/team.d.ts.map +1 -0
  108. package/dist/metrics/team.js +0 -0
  109. package/dist/metrics/team.js.map +1 -0
  110. package/dist/metrics/team.test.d.ts +2 -0
  111. package/dist/metrics/team.test.d.ts.map +1 -0
  112. package/dist/metrics/team.test.js +90 -0
  113. package/dist/metrics/team.test.js.map +1 -0
  114. package/dist/pricing/catalog.d.ts +39 -0
  115. package/dist/pricing/catalog.d.ts.map +1 -0
  116. package/dist/pricing/catalog.js +59 -0
  117. package/dist/pricing/catalog.js.map +1 -0
  118. package/dist/pricing/cost.d.ts +25 -0
  119. package/dist/pricing/cost.d.ts.map +1 -0
  120. package/dist/pricing/cost.js +50 -0
  121. package/dist/pricing/cost.js.map +1 -0
  122. package/dist/pricing/cost.test.d.ts +2 -0
  123. package/dist/pricing/cost.test.d.ts.map +1 -0
  124. package/dist/pricing/cost.test.js +74 -0
  125. package/dist/pricing/cost.test.js.map +1 -0
  126. package/dist/server.d.ts +15 -0
  127. package/dist/server.d.ts.map +1 -0
  128. package/dist/server.js +1141 -0
  129. package/dist/server.js.map +1 -0
  130. package/dist/setup.d.ts +70 -0
  131. package/dist/setup.d.ts.map +1 -0
  132. package/dist/setup.js +223 -0
  133. package/dist/setup.js.map +1 -0
  134. package/dist/setup.test.d.ts +2 -0
  135. package/dist/setup.test.d.ts.map +1 -0
  136. package/dist/setup.test.js +131 -0
  137. package/dist/setup.test.js.map +1 -0
  138. package/dist/watchers/claude.d.ts +34 -0
  139. package/dist/watchers/claude.d.ts.map +1 -0
  140. package/dist/watchers/claude.js +235 -0
  141. package/dist/watchers/claude.js.map +1 -0
  142. package/dist/watchers/claude.test.d.ts +2 -0
  143. package/dist/watchers/claude.test.d.ts.map +1 -0
  144. package/dist/watchers/claude.test.js +180 -0
  145. package/dist/watchers/claude.test.js.map +1 -0
  146. package/dist/watchers/git.d.ts +42 -0
  147. package/dist/watchers/git.d.ts.map +1 -0
  148. package/dist/watchers/git.js +218 -0
  149. package/dist/watchers/git.js.map +1 -0
  150. package/dist/watchers/git.test.d.ts +2 -0
  151. package/dist/watchers/git.test.d.ts.map +1 -0
  152. package/dist/watchers/git.test.js +73 -0
  153. package/dist/watchers/git.test.js.map +1 -0
  154. package/dist/watchers/identity.d.ts +34 -0
  155. package/dist/watchers/identity.d.ts.map +1 -0
  156. package/dist/watchers/identity.js +85 -0
  157. package/dist/watchers/identity.js.map +1 -0
  158. package/dist/watchers/identity.test.d.ts +2 -0
  159. package/dist/watchers/identity.test.d.ts.map +1 -0
  160. package/dist/watchers/identity.test.js +48 -0
  161. package/dist/watchers/identity.test.js.map +1 -0
  162. package/dist/web-ui.test.d.ts +2 -0
  163. package/dist/web-ui.test.d.ts.map +1 -0
  164. package/dist/web-ui.test.js +312 -0
  165. package/dist/web-ui.test.js.map +1 -0
  166. package/node_modules/@estela/shared/dist/index.d.ts +3 -0
  167. package/node_modules/@estela/shared/dist/index.d.ts.map +1 -0
  168. package/node_modules/@estela/shared/dist/index.js +19 -0
  169. package/node_modules/@estela/shared/dist/index.js.map +1 -0
  170. package/node_modules/@estela/shared/dist/money.d.ts +71 -0
  171. package/node_modules/@estela/shared/dist/money.d.ts.map +1 -0
  172. package/node_modules/@estela/shared/dist/money.js +171 -0
  173. package/node_modules/@estela/shared/dist/money.js.map +1 -0
  174. package/node_modules/@estela/shared/dist/types.d.ts +365 -0
  175. package/node_modules/@estela/shared/dist/types.d.ts.map +1 -0
  176. package/node_modules/@estela/shared/dist/types.js +24 -0
  177. package/node_modules/@estela/shared/dist/types.js.map +1 -0
  178. package/node_modules/@estela/shared/package.json +5 -0
  179. package/package.json +41 -0
  180. package/web/app.css +749 -0
  181. package/web/app.js +1257 -0
  182. package/web/apple-touch-icon.png +0 -0
  183. package/web/favicon.png +0 -0
  184. package/web/index.html +132 -0
  185. package/web/lib.js +94 -0
  186. package/web/logo-mark.png +0 -0
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ /**
3
+ * Quién es quién en un repositorio.
4
+ *
5
+ * Git no tiene identidades, tiene pares (nombre, correo) escritos a mano en
6
+ * cada máquina. En el repositorio de Accuro, una sola persona aparece así:
7
+ *
8
+ * Denys Fonseca <denys.fonseca@accuro.es> 79 commits
9
+ * Denys Fonseca <denys.fonseca@paipe.com> 134 commits
10
+ * dfonseca <denys.fonseca@accuro.es> 11 commits
11
+ *
12
+ * Agrupar por correo la parte en dos. Agrupar por nombre también. Una vista de
13
+ * equipo que enseñe a Denys tres veces no se la cree nadie, y con razón: si
14
+ * falla en lo que el lector puede comprobar de un vistazo, deja de creerse
15
+ * también lo que no puede comprobar.
16
+ *
17
+ * La regla: **dos identidades son la misma persona si comparten el correo, o si
18
+ * comparten un nombre que de verdad identifique a alguien**, y esa relación se
19
+ * propaga. Es el problema de las componentes conexas de un grafo, y resuelve el
20
+ * caso de arriba en un paso: `dfonseca` entra por el correo que comparte con
21
+ * `Denys Fonseca`, y arrastra consigo el segundo correo de esa persona.
22
+ *
23
+ * El matiz del nombre no es teórico. Unir por cualquier nombre fusionaba a dos
24
+ * compañeros distintos porque ambos habían commiteado alguna vez como "JD": un
25
+ * alias de dos letras hacía de puente entre `nx-jleon@` y `nx-jmoreira@`. Por
26
+ * eso solo une un nombre con apellido, y los alias sueltos se agrupan por
27
+ * correo, que es el vínculo fuerte.
28
+ *
29
+ * El sesgo es deliberado: ante la duda, partir antes que fundir. Una persona
30
+ * partida en dos filas se ve en pantalla y se entiende; dos personas fundidas
31
+ * atribuyen el trabajo de alguien a otro sin que nadie lo note.
32
+ */
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.mergeIdentities = mergeIdentities;
35
+ /** Une identidades de git que pertenecen a la misma persona. */
36
+ function mergeIdentities(rows) {
37
+ // Union-find sobre los índices de las filas.
38
+ const parent = rows.map((_, i) => i);
39
+ const find = (i) => {
40
+ while (parent[i] !== i) {
41
+ parent[i] = parent[parent[i]];
42
+ i = parent[i];
43
+ }
44
+ return i;
45
+ };
46
+ const union = (a, b) => {
47
+ const ra = find(a), rb = find(b);
48
+ if (ra !== rb)
49
+ parent[ra] = rb;
50
+ };
51
+ const byName = new Map();
52
+ const byEmail = new Map();
53
+ rows.forEach((row, i) => {
54
+ const name = row.name.trim().toLowerCase();
55
+ const email = row.email.trim().toLowerCase();
56
+ // Solo une por nombre si el nombre identifica a alguien. Un alias corto no
57
+ // lo hace, y fundir a dos personas es mucho peor que partir a una en dos:
58
+ // partirla se ve en pantalla y se entiende, fundirlas atribuye el trabajo
59
+ // de alguien a otra persona sin que nadie lo note.
60
+ //
61
+ // Pasó de verdad: "JD" se usó con nx-jleon@ y con nx-jmoreira@, y ese alias
62
+ // de dos letras unió a dos personas distintas en una sola fila.
63
+ if (isFullName(name)) {
64
+ const seen = byName.get(name);
65
+ if (seen !== undefined)
66
+ union(i, seen);
67
+ else
68
+ byName.set(name, i);
69
+ }
70
+ if (email) {
71
+ const seen = byEmail.get(email);
72
+ if (seen !== undefined)
73
+ union(i, seen);
74
+ else
75
+ byEmail.set(email, i);
76
+ }
77
+ });
78
+ const groups = new Map();
79
+ rows.forEach((row, i) => {
80
+ const root = find(i);
81
+ const bucket = groups.get(root);
82
+ if (bucket)
83
+ bucket.push(row);
84
+ else
85
+ groups.set(root, [row]);
86
+ });
87
+ return [...groups.values()]
88
+ .map((bucket) => ({
89
+ name: displayName(bucket),
90
+ emails: [...new Set(bucket.map((r) => r.email).filter(Boolean))].sort((a, b) => a.localeCompare(b)),
91
+ names: [...new Set(bucket.map((r) => r.name).filter(Boolean))].sort((a, b) => a.localeCompare(b)),
92
+ commits: bucket.reduce((sum, r) => sum + r.commits, 0),
93
+ }))
94
+ .sort((a, b) => b.commits - a.commits);
95
+ }
96
+ /**
97
+ * Si un nombre identifica a una persona o es solo el alias de una cuenta.
98
+ *
99
+ * El criterio es tener nombre y apellido. No es infalible, pero separa bien lo
100
+ * que hay en un historial de git real: "Denys Fonseca" identifica, "dfonseca" y
101
+ * "JD" no. Un alias suelto sigue uniéndose por correo, que es el vínculo
102
+ * fuerte, así que no se pierde nada por ser estricto aquí.
103
+ */
104
+ function isFullName(name) {
105
+ return name.includes(" ") && name.length >= 5;
106
+ }
107
+ /**
108
+ * El nombre que se enseña.
109
+ *
110
+ * Se prefiere el que parece un nombre de persona ("Denys Fonseca") al alias de
111
+ * la cuenta ("dfonseca"), porque quien lee el panel conoce a sus compañeros por
112
+ * el primero. A igualdad, manda el que más commits tiene.
113
+ */
114
+ function displayName(bucket) {
115
+ const candidates = bucket.filter((r) => r.name.trim());
116
+ if (candidates.length === 0)
117
+ return bucket[0]?.email ?? "desconocido";
118
+ return [...candidates].sort((a, b) => {
119
+ const humanA = a.name.includes(" ") ? 1 : 0;
120
+ const humanB = b.name.includes(" ") ? 1 : 0;
121
+ if (humanA !== humanB)
122
+ return humanB - humanA;
123
+ return b.commits - a.commits;
124
+ })[0].name;
125
+ }
126
+ //# sourceMappingURL=people.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"people.js","sourceRoot":"","sources":["../../src/metrics/people.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;;AAiBH,0CAmDC;AApDD,gEAAgE;AAChE,SAAgB,eAAe,CAAC,IAA0B;IACxD,6CAA6C;IAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,CAAC,CAAS,EAAU,EAAE;QACjC,OAAO,MAAM,CAAC,CAAC,CAAE,KAAK,CAAC,EAAE,CAAC;YAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAE,CAAE,CAAC;YAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;QAAC,CAAC;QAC7E,OAAO,CAAC,CAAC;IACX,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE;QACrC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACjC,IAAI,EAAE,KAAK,EAAE;YAAE,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC;IACjC,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE1C,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;QACtB,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAE7C,2EAA2E;QAC3E,0EAA0E;QAC1E,0EAA0E;QAC1E,mDAAmD;QACnD,EAAE;QACF,4EAA4E;QAC5E,gEAAgE;QAChE,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC9B,IAAI,IAAI,KAAK,SAAS;gBAAE,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;;gBAAM,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACnE,CAAC;QACD,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAChC,IAAI,IAAI,KAAK,SAAS;gBAAE,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;;gBAAM,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACrE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC9C,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;QACtB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACrB,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,MAAM;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;;YAAM,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;SACxB,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAChB,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC;QACzB,MAAM,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;QACnG,KAAK,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;QACjG,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;KACvD,CAAC,CAAC;SACF,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;AAChD,CAAC;AAED;;;;;;GAMG;AACH,SAAS,WAAW,CAAC,MAA4B;IAC/C,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACvD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,aAAa,CAAC;IAEtE,OAAO,CAAC,GAAG,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACnC,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5C,IAAI,MAAM,KAAK,MAAM;YAAE,OAAO,MAAM,GAAG,MAAM,CAAC;QAC9C,OAAO,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC;IAC/B,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=people.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"people.test.d.ts","sourceRoot":"","sources":["../../src/metrics/people.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_test_1 = require("node:test");
8
+ const people_js_1 = require("./people.js");
9
+ /** Los autores reales del repositorio de Accuro, tal cual salen de git. */
10
+ const ACCURO = [
11
+ { name: "Denys Fonseca", email: "denys.fonseca@accuro.es", commits: 79 },
12
+ { name: "Denys Fonseca", email: "denys.fonseca@paipe.com", commits: 134 },
13
+ { name: "dfonseca", email: "denys.fonseca@accuro.es", commits: 11 },
14
+ { name: "Juan Manuel Cruz Berjano", email: "juan.cruz@accuro.es", commits: 256 },
15
+ { name: "Juan Manuel Cruz Berjano", email: "juanmanuel.cruz@accuro.es", commits: 25 },
16
+ { name: "j-cruz4", email: "juan.cruz@accuro.es", commits: 1 },
17
+ { name: "Sergio Raigal Tendero", email: "sergio.raigal@accuro.es", commits: 523 },
18
+ { name: "apascual", email: "adrian.pascual@ext.accuro.es", commits: 69 },
19
+ ];
20
+ (0, node_test_1.test)("une a una persona repartida entre varios correos y alias", () => {
21
+ const people = (0, people_js_1.mergeIdentities)(ACCURO);
22
+ // Denys entra por dos vías a la vez: `dfonseca` comparte correo con una de
23
+ // sus filas, y esa fila comparte nombre con la otra. Sin propagar la
24
+ // relación, se quedaría fuera uno de los dos correos.
25
+ const denys = people.find((p) => p.name === "Denys Fonseca");
26
+ strict_1.default.ok(denys, "no se encontró a Denys");
27
+ strict_1.default.equal(denys.commits, 79 + 134 + 11);
28
+ strict_1.default.deepEqual(denys.emails, ["denys.fonseca@accuro.es", "denys.fonseca@paipe.com"]);
29
+ });
30
+ (0, node_test_1.test)("prefiere el nombre de persona al alias de la cuenta", () => {
31
+ const people = (0, people_js_1.mergeIdentities)(ACCURO);
32
+ // "Denys Fonseca" y no "dfonseca": quien lee el panel conoce a sus
33
+ // compañeros por su nombre, no por su usuario.
34
+ strict_1.default.ok(people.some((p) => p.name === "Denys Fonseca"));
35
+ strict_1.default.ok(!people.some((p) => p.name === "dfonseca"));
36
+ });
37
+ (0, node_test_1.test)("no funde a personas que no comparten nada", () => {
38
+ const people = (0, people_js_1.mergeIdentities)(ACCURO);
39
+ strict_1.default.equal(people.length, 4, people.map((p) => p.name).join(", "));
40
+ strict_1.default.ok(people.some((p) => p.name === "Sergio Raigal Tendero"));
41
+ strict_1.default.ok(people.some((p) => p.name === "apascual"));
42
+ });
43
+ (0, node_test_1.test)("un nombre vacío no une a media plantilla", () => {
44
+ // Commitear sin nombre configurado es común. Si el vacío uniera, todas esas
45
+ // personas se fundirían en una sola fila inventada.
46
+ const people = (0, people_js_1.mergeIdentities)([
47
+ { name: "", email: "ana@empresa.com", commits: 5 },
48
+ { name: "", email: "luis@empresa.com", commits: 3 },
49
+ ]);
50
+ strict_1.default.equal(people.length, 2);
51
+ });
52
+ (0, node_test_1.test)("ordena por volumen y suma bien", () => {
53
+ const people = (0, people_js_1.mergeIdentities)(ACCURO);
54
+ strict_1.default.equal(people[0].name, "Sergio Raigal Tendero");
55
+ strict_1.default.equal(people.reduce((s, p) => s + p.commits, 0), ACCURO.reduce((s, r) => s + r.commits, 0));
56
+ });
57
+ (0, node_test_1.test)("un alias corto no hace de puente entre dos personas", () => {
58
+ // El caso real: dos compañeros distintos habían commiteado alguna vez como
59
+ // "JD". Uniendo por cualquier nombre, ese alias de dos letras los fundía y
60
+ // uno se llevaba el trabajo del otro.
61
+ const people = (0, people_js_1.mergeIdentities)([
62
+ { name: "JD Leon", email: "nx-jleon@banco.com", commits: 475 },
63
+ { name: "JD", email: "nx-jleon@banco.com", commits: 21 },
64
+ { name: "JD", email: "nx-jmoreira@banco.com", commits: 2 },
65
+ { name: "JD Moreira", email: "nx-jmoreira@banco.com", commits: 64 },
66
+ ]);
67
+ strict_1.default.equal(people.length, 2, "son dos personas distintas");
68
+ const leon = people.find((p) => p.emails.includes("nx-jleon@banco.com"));
69
+ const moreira = people.find((p) => p.emails.includes("nx-jmoreira@banco.com"));
70
+ // Cada alias "JD" se agrupa por su correo, con quien le corresponde.
71
+ strict_1.default.equal(leon.commits, 475 + 21);
72
+ strict_1.default.equal(moreira.commits, 64 + 2);
73
+ strict_1.default.ok(!leon.emails.includes("nx-jmoreira@banco.com"));
74
+ });
75
+ (0, node_test_1.test)("un nombre con apellido sí une dos correos", () => {
76
+ // Lo contrario también tiene que seguir funcionando, o Denys vuelve a salir
77
+ // partido en dos filas.
78
+ const people = (0, people_js_1.mergeIdentities)([
79
+ { name: "Denys Fonseca", email: "d@accuro.es", commits: 79 },
80
+ { name: "Denys Fonseca", email: "d@paipe.com", commits: 134 },
81
+ ]);
82
+ strict_1.default.equal(people.length, 1);
83
+ });
84
+ //# sourceMappingURL=people.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"people.test.js","sourceRoot":"","sources":["../../src/metrics/people.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { CommitRecord, TimeEntry } from "@estela/shared";
2
+ /**
3
+ * El trabajo del equipo, sin que el equipo instale nada.
4
+ *
5
+ * Quien lleva el proyecto ya tiene los repositorios en su máquina, y en ellos
6
+ * está el rastro de todo el mundo. `sessionizeCommits()` —escrito para el
7
+ * desarrollador que programa sin agente— sirve igual para estimar las horas de
8
+ * un compañero que no usa Estela.
9
+ *
10
+ * De ahí la distinción que gobierna esta pantalla:
11
+ *
12
+ * - **Medido**: tus horas, deducidas de los transcripts del agente. Se sabe
13
+ * cuándo empezaste porque hay una marca de tiempo por cada turno.
14
+ * - **Estimado**: las de los demás, deducidas solo de sus commits. El rato
15
+ * anterior a cada tanda es una inferencia.
16
+ *
17
+ * Enseñar ambas cosas con el mismo aspecto sería mentir, y aquí la mentira
18
+ * tiene consecuencias: alguien podría usar "42h" para evaluar a una persona que
19
+ * nunca aceptó ser medida. Cada fila lleva su precisión, y la ordenación es por
20
+ * actividad reciente y nunca por horas — esto responde "dónde está el trabajo",
21
+ * no "quién rinde más".
22
+ */
23
+ export interface TeamMember {
24
+ readonly name: string;
25
+ readonly emails: readonly string[];
26
+ /** Si eres tú, sus horas están medidas y no estimadas. */
27
+ readonly isMe: boolean;
28
+ readonly seconds: number;
29
+ readonly measured: boolean;
30
+ readonly commits: number;
31
+ readonly branches: readonly string[];
32
+ readonly lastDay: string;
33
+ }
34
+ export interface TeamViewOptions {
35
+ /** Tus correos. Determina qué fila usa horas medidas. */
36
+ readonly myEmails: ReadonlySet<string>;
37
+ /** Tus imputaciones del periodo, ya medidas. */
38
+ readonly myEntries: readonly TimeEntry[];
39
+ }
40
+ export declare function teamView(commits: readonly CommitRecord[], options: TeamViewOptions): TeamMember[];
41
+ //# sourceMappingURL=team.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team.d.ts","sourceRoot":"","sources":["../../src/metrics/team.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAM9D;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACvC,gDAAgD;IAChD,QAAQ,CAAC,SAAS,EAAE,SAAS,SAAS,EAAE,CAAC;CAC1C;AAED,wBAAgB,QAAQ,CACtB,OAAO,EAAE,SAAS,YAAY,EAAE,EAAE,OAAO,EAAE,eAAe,GACzD,UAAU,EAAE,CA4Cd"}
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team.js","sourceRoot":"","sources":["../../src/metrics/team.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=team.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team.test.d.ts","sourceRoot":"","sources":["../../src/metrics/team.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_test_1 = require("node:test");
8
+ const team_js_1 = require("./team.js");
9
+ function commit(name, email, iso, branch = "main") {
10
+ return {
11
+ repoPath: "/repo", hash: `${name}-${iso}`, at: new Date(iso),
12
+ authorEmail: email, authorName: name, branch,
13
+ subject: "trabajo", linesAdded: 10, linesDeleted: 2, files: [],
14
+ };
15
+ }
16
+ function entry(seconds) {
17
+ return {
18
+ id: "te_1", projectId: "p", startedAt: new Date("2026-08-20T09:00:00Z"),
19
+ endedAt: new Date("2026-08-20T12:00:00Z"), seconds,
20
+ description: "medido", billable: true, invoiceId: null,
21
+ aiCost: { microUsd: 0 }, agentSeconds: seconds, commitHashes: [],
22
+ agents: ["claude-code"], source: "agent", kind: "development", branch: "main",
23
+ };
24
+ }
25
+ const COMMITS = [
26
+ commit("Yo", "yo@ejemplo.com", "2026-08-20T10:00:00Z"),
27
+ commit("Ana Ruiz", "ana@empresa.com", "2026-08-21T09:00:00Z", "feat/pagos"),
28
+ commit("Ana Ruiz", "ana@empresa.com", "2026-08-21T10:00:00Z", "feat/pagos"),
29
+ commit("aruiz", "ana@empresa.com", "2026-08-22T09:00:00Z", "feat/pagos"),
30
+ ];
31
+ (0, node_test_1.test)("las horas de un compañero salen de sus commits", () => {
32
+ const team = (0, team_js_1.teamView)(COMMITS, {
33
+ myEmails: new Set(["yo@ejemplo.com"]), myEntries: [],
34
+ });
35
+ const ana = team.find((p) => p.name === "Ana Ruiz");
36
+ strict_1.default.ok(ana, "no se encontró a Ana");
37
+ strict_1.default.equal(ana.commits, 3, "sus tres identidades deben unirse");
38
+ strict_1.default.ok(ana.seconds > 0, "sin horas, la fila no dice nada");
39
+ strict_1.default.equal(ana.measured, false, "las de un compañero son estimadas");
40
+ });
41
+ (0, node_test_1.test)("tus horas son las medidas, no una reestimación de tus commits", () => {
42
+ const team = (0, team_js_1.teamView)(COMMITS, {
43
+ myEmails: new Set(["yo@ejemplo.com"]), myEntries: [entry(7200)],
44
+ });
45
+ const yo = team.find((p) => p.isMe);
46
+ strict_1.default.ok(yo);
47
+ strict_1.default.equal(yo.seconds, 7200, "debe usar la imputación real");
48
+ strict_1.default.equal(yo.measured, true);
49
+ });
50
+ (0, node_test_1.test)("sin imputaciones tuyas, tu fila también queda estimada", () => {
51
+ // Si no se marcara, tus horas parecerían medidas sin serlo.
52
+ const team = (0, team_js_1.teamView)(COMMITS, {
53
+ myEmails: new Set(["yo@ejemplo.com"]), myEntries: [],
54
+ });
55
+ strict_1.default.equal(team.find((p) => p.isMe)?.measured, false);
56
+ });
57
+ (0, node_test_1.test)("no se ordena por horas: eso sería un ranking de rendimiento", () => {
58
+ const team = (0, team_js_1.teamView)(COMMITS, {
59
+ myEmails: new Set(["yo@ejemplo.com"]),
60
+ myEntries: [entry(360_000)], // muchísimas horas para mí
61
+ });
62
+ // Ana commiteó después, así que va primera pese a tener menos horas.
63
+ strict_1.default.equal(team[0].name, "Ana Ruiz");
64
+ });
65
+ (0, node_test_1.test)("recoge las ramas en las que anda cada persona", () => {
66
+ const team = (0, team_js_1.teamView)(COMMITS, {
67
+ myEmails: new Set(["yo@ejemplo.com"]), myEntries: [],
68
+ });
69
+ strict_1.default.deepEqual(team.find((p) => p.name === "Ana Ruiz")?.branches, ["feat/pagos"]);
70
+ });
71
+ (0, node_test_1.test)("tus varias identidades de git son una sola fila", () => {
72
+ // Commiteas con el correo de cada cliente y con el que te da GitHub. Nada las
73
+ // relaciona en git, así que salías dos veces y cada fila enseñaba tus horas
74
+ // completas: el mismo tiempo contado dos veces en pantalla.
75
+ const commits = [
76
+ commit("JDev Leon", "yo@cliente.com", "2026-08-20T10:00:00Z"),
77
+ commit("jdleonruiz", "1234+jd@users.noreply.github.com", "2026-08-21T10:00:00Z"),
78
+ commit("Ana Ruiz", "ana@empresa.com", "2026-08-22T10:00:00Z"),
79
+ ];
80
+ const team = (0, team_js_1.teamView)(commits, {
81
+ myEmails: new Set(["yo@cliente.com", "1234+jd@users.noreply.github.com"]),
82
+ myEntries: [entry(7200)],
83
+ });
84
+ const me = team.filter((p) => p.isMe);
85
+ strict_1.default.equal(me.length, 1, "debes salir una sola vez");
86
+ strict_1.default.equal(me[0].seconds, 7200, "tus horas se cuentan una vez");
87
+ strict_1.default.equal(me[0].commits, 2, "y arrastran los commits de ambas identidades");
88
+ strict_1.default.equal(team.length, 2, "Ana sigue siendo otra persona");
89
+ });
90
+ //# sourceMappingURL=team.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team.test.js","sourceRoot":"","sources":["../../src/metrics/team.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Catálogo de precios con fechas de vigencia.
3
+ *
4
+ * Los precios son **datos**, no constantes en el código. Motivo: recalcular el
5
+ * histórico. Si en marzo baja el precio de un modelo y en noviembre reconstruyes
6
+ * un informe de enero, el informe debe seguir dando lo mismo que dio en enero.
7
+ * Por eso cada entrada lleva `effectiveFrom` y el cálculo busca la vigente en la
8
+ * fecha del turno, nunca "la actual".
9
+ *
10
+ * Fuente: tarifas de la API de Anthropic (precio por millón de tokens).
11
+ * Multiplicadores de caché: lectura 0.1x sobre input; escritura 1.25x con TTL de
12
+ * 5 minutos y 2x con TTL de 1 hora.
13
+ */
14
+ export interface ModelPrice {
15
+ /** Prefijo de `model` con el que casa esta entrada. */
16
+ readonly modelPrefix: string;
17
+ readonly effectiveFrom: string;
18
+ /** USD por millón de tokens de entrada sin cachear. */
19
+ readonly inputPerMTok: number;
20
+ /** USD por millón de tokens de salida. */
21
+ readonly outputPerMTok: number;
22
+ readonly cacheReadMultiplier: number;
23
+ readonly cacheWrite5mMultiplier: number;
24
+ readonly cacheWrite1hMultiplier: number;
25
+ }
26
+ /**
27
+ * Ordenado por especificidad de prefijo (el más largo gana) y luego por fecha.
28
+ * `resolvePrice` se encarga del orden; aquí basta con que estén todos.
29
+ */
30
+ export declare const PRICE_CATALOG: readonly ModelPrice[];
31
+ /**
32
+ * Busca el precio vigente para un modelo en una fecha.
33
+ *
34
+ * Devuelve `null` en vez de adivinar cuando el modelo no está en el catálogo:
35
+ * un coste inventado en una factura es peor que un coste ausente, porque el
36
+ * ausente se ve y el inventado no.
37
+ */
38
+ export declare function resolvePrice(model: string, at: Date): ModelPrice | null;
39
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/pricing/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,UAAU;IACzB,uDAAuD;IACvD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,uDAAuD;IACvD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,0CAA0C;IAC1C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;CACzC;AAQD;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,UAAU,EAe9C,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,GAAG,UAAU,GAAG,IAAI,CAWvE"}
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ /**
3
+ * Catálogo de precios con fechas de vigencia.
4
+ *
5
+ * Los precios son **datos**, no constantes en el código. Motivo: recalcular el
6
+ * histórico. Si en marzo baja el precio de un modelo y en noviembre reconstruyes
7
+ * un informe de enero, el informe debe seguir dando lo mismo que dio en enero.
8
+ * Por eso cada entrada lleva `effectiveFrom` y el cálculo busca la vigente en la
9
+ * fecha del turno, nunca "la actual".
10
+ *
11
+ * Fuente: tarifas de la API de Anthropic (precio por millón de tokens).
12
+ * Multiplicadores de caché: lectura 0.1x sobre input; escritura 1.25x con TTL de
13
+ * 5 minutos y 2x con TTL de 1 hora.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.PRICE_CATALOG = void 0;
17
+ exports.resolvePrice = resolvePrice;
18
+ const STANDARD_CACHE = {
19
+ cacheReadMultiplier: 0.1,
20
+ cacheWrite5mMultiplier: 1.25,
21
+ cacheWrite1hMultiplier: 2.0,
22
+ };
23
+ /**
24
+ * Ordenado por especificidad de prefijo (el más largo gana) y luego por fecha.
25
+ * `resolvePrice` se encarga del orden; aquí basta con que estén todos.
26
+ */
27
+ exports.PRICE_CATALOG = [
28
+ // --- Anthropic ---
29
+ { modelPrefix: "claude-fable-5", effectiveFrom: "2020-01-01", inputPerMTok: 10, outputPerMTok: 50, ...STANDARD_CACHE },
30
+ { modelPrefix: "claude-mythos-5", effectiveFrom: "2020-01-01", inputPerMTok: 10, outputPerMTok: 50, ...STANDARD_CACHE },
31
+ { modelPrefix: "claude-opus-5", effectiveFrom: "2020-01-01", inputPerMTok: 5, outputPerMTok: 25, ...STANDARD_CACHE },
32
+ { modelPrefix: "claude-opus-4-8", effectiveFrom: "2020-01-01", inputPerMTok: 5, outputPerMTok: 25, ...STANDARD_CACHE },
33
+ { modelPrefix: "claude-opus-4-7", effectiveFrom: "2020-01-01", inputPerMTok: 5, outputPerMTok: 25, ...STANDARD_CACHE },
34
+ { modelPrefix: "claude-opus-4-6", effectiveFrom: "2020-01-01", inputPerMTok: 5, outputPerMTok: 25, ...STANDARD_CACHE },
35
+ { modelPrefix: "claude-sonnet-5", effectiveFrom: "2020-01-01", inputPerMTok: 2, outputPerMTok: 10, ...STANDARD_CACHE },
36
+ { modelPrefix: "claude-sonnet-4-6", effectiveFrom: "2020-01-01", inputPerMTok: 3, outputPerMTok: 15, ...STANDARD_CACHE },
37
+ { modelPrefix: "claude-haiku-4-5", effectiveFrom: "2020-01-01", inputPerMTok: 1, outputPerMTok: 5, ...STANDARD_CACHE },
38
+ // Familias antiguas: prefijo corto como red de seguridad.
39
+ { modelPrefix: "claude-opus", effectiveFrom: "2020-01-01", inputPerMTok: 5, outputPerMTok: 25, ...STANDARD_CACHE },
40
+ { modelPrefix: "claude-sonnet", effectiveFrom: "2020-01-01", inputPerMTok: 3, outputPerMTok: 15, ...STANDARD_CACHE },
41
+ { modelPrefix: "claude-haiku", effectiveFrom: "2020-01-01", inputPerMTok: 1, outputPerMTok: 5, ...STANDARD_CACHE },
42
+ ];
43
+ /**
44
+ * Busca el precio vigente para un modelo en una fecha.
45
+ *
46
+ * Devuelve `null` en vez de adivinar cuando el modelo no está en el catálogo:
47
+ * un coste inventado en una factura es peor que un coste ausente, porque el
48
+ * ausente se ve y el inventado no.
49
+ */
50
+ function resolvePrice(model, at) {
51
+ const id = model.toLowerCase();
52
+ const iso = at.toISOString();
53
+ const candidates = exports.PRICE_CATALOG
54
+ .filter((p) => id.startsWith(p.modelPrefix) && p.effectiveFrom <= iso)
55
+ .sort((a, b) => b.modelPrefix.length - a.modelPrefix.length ||
56
+ b.effectiveFrom.localeCompare(a.effectiveFrom));
57
+ return candidates[0] ?? null;
58
+ }
59
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/pricing/catalog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { AiCost, TokenUsage } from "@estela/shared";
2
+ /**
3
+ * Coste de un turno de agente, con la caché incluida.
4
+ *
5
+ * Medido sobre transcripts reales, la caché es la mayor parte del gasto: en una
6
+ * sesión típica los `input_tokens` sin cachear son unos cientos mientras que los
7
+ * `cache_read` son millones. Calcular `input * precio` produce un número que
8
+ * parece plausible y está varias veces por debajo del real.
9
+ *
10
+ * Se devuelve en micro-USD enteros para que sumar diez mil turnos no acumule
11
+ * error de coma flotante.
12
+ */
13
+ export declare function costOfTurn(usage: TokenUsage, model: string, at: Date): AiCost | null;
14
+ /** Desglose por concepto. Para explicar en la interfaz de dónde sale el número. */
15
+ export interface CostBreakdown {
16
+ readonly input: AiCost;
17
+ readonly output: AiCost;
18
+ readonly cacheRead: AiCost;
19
+ readonly cacheWrite: AiCost;
20
+ readonly total: AiCost;
21
+ /** Fracción del total que se va en caché. Suele rondar el 80%. */
22
+ readonly cacheShare: number;
23
+ }
24
+ export declare function breakdownOfTurn(usage: TokenUsage, model: string, at: Date): CostBreakdown | null;
25
+ //# sourceMappingURL=cost.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost.d.ts","sourceRoot":"","sources":["../../src/pricing/cost.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGzD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAapF;AAED,mFAAmF;AACnF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,GAAG,aAAa,GAAG,IAAI,CAuBhG"}
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.costOfTurn = costOfTurn;
4
+ exports.breakdownOfTurn = breakdownOfTurn;
5
+ const catalog_js_1 = require("./catalog.js");
6
+ /**
7
+ * Coste de un turno de agente, con la caché incluida.
8
+ *
9
+ * Medido sobre transcripts reales, la caché es la mayor parte del gasto: en una
10
+ * sesión típica los `input_tokens` sin cachear son unos cientos mientras que los
11
+ * `cache_read` son millones. Calcular `input * precio` produce un número que
12
+ * parece plausible y está varias veces por debajo del real.
13
+ *
14
+ * Se devuelve en micro-USD enteros para que sumar diez mil turnos no acumule
15
+ * error de coma flotante.
16
+ */
17
+ function costOfTurn(usage, model, at) {
18
+ const price = (0, catalog_js_1.resolvePrice)(model, at);
19
+ if (!price)
20
+ return null;
21
+ const perToken = price.inputPerMTok / 1_000_000;
22
+ const usd = usage.input * perToken +
23
+ usage.output * (price.outputPerMTok / 1_000_000) +
24
+ usage.cacheRead * perToken * price.cacheReadMultiplier +
25
+ usage.cacheWrite5m * perToken * price.cacheWrite5mMultiplier +
26
+ usage.cacheWrite1h * perToken * price.cacheWrite1hMultiplier;
27
+ return { microUsd: Math.round(usd * 1_000_000) };
28
+ }
29
+ function breakdownOfTurn(usage, model, at) {
30
+ const price = (0, catalog_js_1.resolvePrice)(model, at);
31
+ if (!price)
32
+ return null;
33
+ const perToken = price.inputPerMTok / 1_000_000;
34
+ const micro = (usd) => Math.round(usd * 1_000_000);
35
+ const input = micro(usage.input * perToken);
36
+ const output = micro(usage.output * (price.outputPerMTok / 1_000_000));
37
+ const cacheRead = micro(usage.cacheRead * perToken * price.cacheReadMultiplier);
38
+ const cacheWrite = micro(usage.cacheWrite5m * perToken * price.cacheWrite5mMultiplier +
39
+ usage.cacheWrite1h * perToken * price.cacheWrite1hMultiplier);
40
+ const total = input + output + cacheRead + cacheWrite;
41
+ return {
42
+ input: { microUsd: input },
43
+ output: { microUsd: output },
44
+ cacheRead: { microUsd: cacheRead },
45
+ cacheWrite: { microUsd: cacheWrite },
46
+ total: { microUsd: total },
47
+ cacheShare: total === 0 ? 0 : (cacheRead + cacheWrite) / total,
48
+ };
49
+ }
50
+ //# sourceMappingURL=cost.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost.js","sourceRoot":"","sources":["../../src/pricing/cost.ts"],"names":[],"mappings":";;AAcA,gCAaC;AAaD,0CAuBC;AA9DD,6CAA4C;AAE5C;;;;;;;;;;GAUG;AACH,SAAgB,UAAU,CAAC,KAAiB,EAAE,KAAa,EAAE,EAAQ;IACnE,MAAM,KAAK,GAAG,IAAA,yBAAY,EAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACtC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,GAAG,SAAS,CAAC;IAChD,MAAM,GAAG,GACP,KAAK,CAAC,KAAK,GAAG,QAAQ;QACtB,KAAK,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,aAAa,GAAG,SAAS,CAAC;QAChD,KAAK,CAAC,SAAS,GAAG,QAAQ,GAAG,KAAK,CAAC,mBAAmB;QACtD,KAAK,CAAC,YAAY,GAAG,QAAQ,GAAG,KAAK,CAAC,sBAAsB;QAC5D,KAAK,CAAC,YAAY,GAAG,QAAQ,GAAG,KAAK,CAAC,sBAAsB,CAAC;IAE/D,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,SAAS,CAAC,EAAE,CAAC;AACnD,CAAC;AAaD,SAAgB,eAAe,CAAC,KAAiB,EAAE,KAAa,EAAE,EAAQ;IACxE,MAAM,KAAK,GAAG,IAAA,yBAAY,EAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACtC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,GAAG,SAAS,CAAC;IAChD,MAAM,KAAK,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,SAAS,CAAC,CAAC;IAE3D,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,QAAQ,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,aAAa,GAAG,SAAS,CAAC,CAAC,CAAC;IACvE,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,QAAQ,GAAG,KAAK,CAAC,mBAAmB,CAAC,CAAC;IAChF,MAAM,UAAU,GAAG,KAAK,CACtB,KAAK,CAAC,YAAY,GAAG,QAAQ,GAAG,KAAK,CAAC,sBAAsB;QAC5D,KAAK,CAAC,YAAY,GAAG,QAAQ,GAAG,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAEhE,MAAM,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC;IACtD,OAAO;QACL,KAAK,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC1B,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;QAC5B,SAAS,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE;QAClC,UAAU,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE;QACpC,KAAK,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC1B,UAAU,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,UAAU,CAAC,GAAG,KAAK;KAC/D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cost.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost.test.d.ts","sourceRoot":"","sources":["../../src/pricing/cost.test.ts"],"names":[],"mappings":""}