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:
@@ -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"`
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package datascience
|
||||
|
||||
// Percentile returns the value at percentile p (0.0–1.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]
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user