chore: auto-commit (57 archivos)

- frontend/functions/core/format_datetime_short.md
- frontend/functions/core/format_datetime_short.test.ts
- frontend/functions/core/format_datetime_short.ts
- frontend/functions/core/format_duration.md
- frontend/functions/core/format_duration.test.ts
- frontend/functions/core/format_duration.ts
- frontend/functions/core/month_grid.md
- frontend/functions/core/month_grid.test.ts
- frontend/functions/core/month_grid.ts
- frontend/functions/core/string_hash_palette.md
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-09 03:41:58 +02:00
parent 4d5a5bd3ea
commit 8618aa1be3
58 changed files with 2923 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
package datascience
// DurationStats holds descriptive statistics for a set of durations in milliseconds.
type DurationStats struct {
N int `json:"n"`
AvgMs int64 `json:"avg_ms"`
P50Ms int64 `json:"p50_ms"`
P90Ms int64 `json:"p90_ms"`
P99Ms int64 `json:"p99_ms"`
}
+27
View File
@@ -0,0 +1,27 @@
package datascience
import "sort"
// DurationStatsFrom computes descriptive statistics for a slice of durations in milliseconds.
// It sorts a local copy so the original slice is not mutated.
// Returns a zero-value DurationStats when the input is empty.
func DurationStatsFrom(durations []int64) DurationStats {
n := len(durations)
if n == 0 {
return DurationStats{}
}
cp := make([]int64, n)
copy(cp, durations)
sort.Slice(cp, func(i, j int) bool { return cp[i] < cp[j] })
var sum int64
for _, d := range cp {
sum += d
}
return DurationStats{
N: n,
AvgMs: sum / int64(n),
P50Ms: Percentile(cp, 0.5),
P90Ms: Percentile(cp, 0.9),
P99Ms: Percentile(cp, 0.99),
}
}
+55
View File
@@ -0,0 +1,55 @@
---
name: duration_stats
kind: function
lang: go
domain: datascience
version: "1.0.0"
purity: pure
signature: "func DurationStatsFrom(durations []int64) DurationStats"
description: "Calcula estadisticas descriptivas (N, media, P50/P90/P99) de un slice de duraciones en milisegundos. Ordena una copia local sin mutar el input. Retorna DurationStats{} para slice vacio."
tags: [statistics, duration, percentile, metrics, int64]
uses_functions:
- percentile_int64_go_datascience
uses_types:
- DurationStats_go_datascience
returns:
- DurationStats_go_datascience
returns_optional: false
error_type: ""
imports:
- sort
params:
- name: durations
desc: "Slice de duraciones en milisegundos. No necesita estar ordenado; se ordena internamente sobre una copia."
output: "DurationStats con N, AvgMs, P50Ms, P90Ms y P99Ms calculados. DurationStats{} si el slice esta vacio."
tested: true
tests:
- "slice vacio retorna estadisticas cero"
- "un solo elemento produce estadisticas identicas"
- "cinco elementos calcula media y percentiles correctos"
- "input original no se muta"
- "diez elementos p90 usa idx truncado"
test_file_path: "functions/datascience/duration_stats_test.go"
file_path: "functions/datascience/duration_stats.go"
source_repo: "https://github.com/egutierrez/fn_registry/apps/kanban"
source_license: "private"
source_file: "apps/kanban/backend/metrics.go"
---
## Ejemplo
```go
durations := []int64{50, 10, 30, 40, 20}
stats := DurationStatsFrom(durations)
// stats.N = 5
// stats.AvgMs = 30
// stats.P50Ms = 30
// stats.P90Ms = 50
// stats.P99Ms = 50
```
## Notas
Ordena una copia con `sort.Slice` para no mutar el slice original.
Compone `Percentile` (`percentile_int64_go_datascience`) para los calculos de P50/P90/P99.
Extraido y generalizado desde `apps/kanban/backend/metrics.go:113-130`.
@@ -0,0 +1,69 @@
package datascience
import "testing"
func TestDurationStatsFrom(t *testing.T) {
t.Run("slice vacio retorna estadisticas cero", func(t *testing.T) {
got := DurationStatsFrom([]int64{})
if got.N != 0 || got.AvgMs != 0 || got.P50Ms != 0 || got.P90Ms != 0 || got.P99Ms != 0 {
t.Errorf("got %+v, want zero DurationStats", got)
}
})
t.Run("un solo elemento produce estadisticas identicas", func(t *testing.T) {
got := DurationStatsFrom([]int64{100})
if got.N != 1 || got.AvgMs != 100 || got.P50Ms != 100 || got.P90Ms != 100 || got.P99Ms != 100 {
t.Errorf("got %+v, want all=100", got)
}
})
t.Run("cinco elementos calcula media y percentiles correctos", func(t *testing.T) {
// sorted: [10,20,30,40,50], n=5
// P50: idx=int(4*0.5)=2 → 30
// P90: idx=int(4*0.9)=int(3.6)=3 → 40
// P99: idx=int(4*0.99)=int(3.96)=3 → 40
got := DurationStatsFrom([]int64{50, 10, 30, 40, 20})
if got.N != 5 {
t.Errorf("N: got %v, want 5", got.N)
}
if got.AvgMs != 30 {
t.Errorf("AvgMs: got %v, want 30", got.AvgMs)
}
if got.P50Ms != 30 {
t.Errorf("P50Ms: got %v, want 30", got.P50Ms)
}
if got.P90Ms != 40 {
t.Errorf("P90Ms: got %v, want 40", got.P90Ms)
}
if got.P99Ms != 40 {
t.Errorf("P99Ms: got %v, want 40", got.P99Ms)
}
})
t.Run("input original no se muta", func(t *testing.T) {
input := []int64{50, 10, 30}
_ = DurationStatsFrom(input)
if input[0] != 50 || input[1] != 10 || input[2] != 30 {
t.Errorf("input mutated: got %v", input)
}
})
t.Run("diez elementos p90 usa idx truncado", func(t *testing.T) {
// sorted: [10..100], n=10
// P50: idx=int(9*0.5)=4 → 50
// P90: idx=int(9*0.9)=int(8.1)=8 → 90
got := DurationStatsFrom([]int64{10, 20, 30, 40, 50, 60, 70, 80, 90, 100})
if got.N != 10 {
t.Errorf("N: got %v, want 10", got.N)
}
if got.AvgMs != 55 {
t.Errorf("AvgMs: got %v, want 55", got.AvgMs)
}
if got.P50Ms != 50 {
t.Errorf("P50Ms: got %v, want 50", got.P50Ms)
}
if got.P90Ms != 90 {
t.Errorf("P90Ms: got %v, want 90", got.P90Ms)
}
})
}
+19
View File
@@ -0,0 +1,19 @@
package datascience
// Percentile returns the value at percentile p (0.01.0) from a pre-sorted
// ascending slice of int64 values.
// Returns 0 for an empty slice.
// idx is computed as int(float64(len-1)*p), clamped to [0, len-1].
func Percentile(sorted []int64, p float64) int64 {
if len(sorted) == 0 {
return 0
}
idx := int(float64(len(sorted)-1) * p)
if idx < 0 {
idx = 0
}
if idx >= len(sorted) {
idx = len(sorted) - 1
}
return sorted[idx]
}
+52
View File
@@ -0,0 +1,52 @@
---
name: percentile_int64
kind: function
lang: go
domain: datascience
version: "1.0.0"
purity: pure
signature: "func Percentile(sorted []int64, p float64) int64"
description: "Calcula el percentil p (0.0-1.0) de un slice de int64 pre-ordenado ascendente. Retorna 0 para slice vacio. idx = int(float64(len-1)*p), clamped a [0, len-1]."
tags: [statistics, percentile, quantile, int64, sorted]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
params:
- name: sorted
desc: "Slice de int64 pre-ordenado en orden ascendente. No se reordena internamente."
- name: p
desc: "Percentil a calcular, en rango [0.0, 1.0]. 0.5 = mediana, 0.9 = P90, 0.99 = P99."
output: "El valor en la posicion del percentil p dentro del slice. Retorna 0 si el slice esta vacio."
tested: true
tests:
- "slice vacio retorna cero"
- "un solo elemento retorna ese elemento"
- "p0 retorna minimo"
- "p100 retorna maximo"
- "p50 retorna mediana de cinco elementos"
- "p90 de diez elementos usa idx int truncado"
- "p99 de slice pequeno usa idx truncado a cero"
test_file_path: "functions/datascience/percentile_int64_test.go"
file_path: "functions/datascience/percentile_int64.go"
source_repo: "https://github.com/egutierrez/fn_registry/apps/kanban"
source_license: "private"
source_file: "apps/kanban/backend/metrics.go"
---
## Ejemplo
```go
sorted := []int64{10, 20, 30, 40, 50}
p50 := Percentile(sorted, 0.5) // 30
p90 := Percentile(sorted, 0.9) // 50
p99 := Percentile(sorted, 0.99) // 50
```
## Notas
El input debe estar ordenado ascendente antes de llamar a esta funcion.
`DurationStatsFrom` se encarga de ordenar antes de llamarla.
Extraido de `apps/kanban/backend/metrics.go:99-111`.
@@ -0,0 +1,56 @@
package datascience
import "testing"
func TestPercentile(t *testing.T) {
t.Run("slice vacio retorna cero", func(t *testing.T) {
got := Percentile([]int64{}, 0.5)
if got != 0 {
t.Errorf("got %v, want 0", got)
}
})
t.Run("un solo elemento retorna ese elemento", func(t *testing.T) {
got := Percentile([]int64{42}, 0.5)
if got != 42 {
t.Errorf("got %v, want 42", got)
}
})
t.Run("p0 retorna minimo", func(t *testing.T) {
got := Percentile([]int64{10, 20, 30, 40, 50}, 0.0)
if got != 10 {
t.Errorf("got %v, want 10", got)
}
})
t.Run("p100 retorna maximo", func(t *testing.T) {
got := Percentile([]int64{10, 20, 30, 40, 50}, 1.0)
if got != 50 {
t.Errorf("got %v, want 50", got)
}
})
t.Run("p50 retorna mediana de cinco elementos", func(t *testing.T) {
got := Percentile([]int64{10, 20, 30, 40, 50}, 0.5)
if got != 30 {
t.Errorf("got %v, want 30", got)
}
})
t.Run("p90 de diez elementos usa idx int truncado", func(t *testing.T) {
// idx = int(9 * 0.9) = int(8.1) = 8 → sorted[8] = 9
got := Percentile([]int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 0.9)
if got != 9 {
t.Errorf("got %v, want 9", got)
}
})
t.Run("p99 de slice pequeno usa idx truncado a cero", func(t *testing.T) {
// idx = int(1 * 0.99) = int(0.99) = 0 → sorted[0] = 100
got := Percentile([]int64{100, 200}, 0.99)
if got != 100 {
t.Errorf("got %v, want 100", got)
}
})
}