Pipeline completo de publicação no LinkedIn: evaluator → redator → editor → art → director → publisher - Seed com 37 posts em _sugestoes.md - Sorteio de formato com N=3 bloqueados (format-history) - Reciclagem mensal de posts com rotação de formato - Revisão via Telegram com chat livre (Gemini 2.5 Flash) - Publicação via LinkedIn API (OAuth2) - Makefile com targets para Windows/Linux/ARM64 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
89 lines
1.8 KiB
Go
89 lines
1.8 KiB
Go
package formats
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"time"
|
|
)
|
|
|
|
// FormatPesos defines selection weights for each format.
|
|
var FormatPesos = map[string]int{
|
|
"como": 5,
|
|
"erro": 2,
|
|
"porque": 2,
|
|
"checklist": 1,
|
|
"comparacao": 1,
|
|
"bastidor": 1,
|
|
}
|
|
|
|
// SortearFormato picks a weighted random format, excluding bloqueados.
|
|
// Uses a fresh rand source seeded with time.Now().UnixNano() per call.
|
|
func SortearFormato(bloqueados []string) string {
|
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
blocked := make(map[string]bool, len(bloqueados))
|
|
for _, b := range bloqueados {
|
|
blocked[b] = true
|
|
}
|
|
|
|
type item struct {
|
|
formato string
|
|
peso int
|
|
}
|
|
|
|
var pool []item
|
|
total := 0
|
|
for formato, peso := range FormatPesos {
|
|
if !blocked[formato] {
|
|
pool = append(pool, item{formato, peso})
|
|
total += peso
|
|
}
|
|
}
|
|
|
|
// Fallback: unblock all when every format is blocked
|
|
if total == 0 {
|
|
for formato, peso := range FormatPesos {
|
|
pool = append(pool, item{formato, peso})
|
|
total += peso
|
|
}
|
|
}
|
|
|
|
pick := r.Intn(total)
|
|
acc := 0
|
|
for _, it := range pool {
|
|
acc += it.peso
|
|
if pick < acc {
|
|
return it.formato
|
|
}
|
|
}
|
|
return pool[0].formato
|
|
}
|
|
|
|
// ValidarFormato returns an error if f is not one of the 6 valid formats.
|
|
func ValidarFormato(f string) error {
|
|
if _, ok := FormatPesos[f]; ok {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("formato %q inválido — válidos: como, erro, porque, checklist, comparacao, bastidor", f)
|
|
}
|
|
|
|
// FormatLabel returns the human-readable label for a format.
|
|
func FormatLabel(f string) string {
|
|
switch f {
|
|
case "como":
|
|
return "Como fazer"
|
|
case "erro":
|
|
return "Erro clássico"
|
|
case "porque":
|
|
return "Por que"
|
|
case "checklist":
|
|
return "Checklist"
|
|
case "comparacao":
|
|
return "Comparação"
|
|
case "bastidor":
|
|
return "Bastidor"
|
|
default:
|
|
return f
|
|
}
|
|
}
|