textmachine/eval/pkg7/minerharness/main.go

184 lines
5.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Command minerharness прогоняет БОЕВОЙ майнер банка (`internal/miner`) по произвольной книге,
// НЕ ТРОГАЯ репозиторий. Прецедент — пакет-6 (`labels/README.md`): отдельный Go-модуль ВНЕ репо с
// `module textmachine/backend/<имя>` и `replace textmachine/backend => <копия>/backend`; имя обязано
// начинаться с `textmachine/backend/`, иначе Go запретит импорт `internal/`.
//
// Отличие от пакета-6, сознательное: `replace` смотрит не в рабочее дерево, а в КОПИЮ бэкенда,
// извлечённую `git archive <commit>` — параллельно идёт бэкенд-сессия пака-20, её незакоммиченные
// правки (в т.ч. `internal/terminology`, `miner_emit.go`) меняли бы результат под руками. Пин к
// коммиту делает прогон воспроизводимым и снимает вопрос «чей это был код».
//
// Вход: JSONL от `eval/pkg7/split_book.py` ({chapter, chunk_idx, source}). Нормализацию делает
// сам харнесс тем же вызовом, что и прод (`text.NormalizeSourceKey`, pipeline/mining.go:57).
// Выход: TSV эмитированной дельты + сводка. Ноль сетевых вызовов, ноль моделей, $0.
package main
import (
"bufio"
"encoding/json"
"flag"
"fmt"
"os"
"sort"
"strings"
"textmachine/backend/internal/lang"
"textmachine/backend/internal/membank"
"textmachine/backend/internal/miner"
"textmachine/backend/internal/store"
"textmachine/backend/internal/text"
)
type row struct {
Chapter int `json:"chapter"`
ChunkIdx int `json:"chunk_idx"`
Source string `json:"source"`
}
func main() {
var (
in = flag.String("in", "", "JSONL чанков (chapter, chunk_idx, source)")
contrastP = flag.String("contrast", "", "контраст-корпус (jieba dict.txt)")
packRoot = flag.String("langpack", "", "корень langpack (configs/langpacks)")
srcLang = flag.String("src", "zh", "язык исходника для langpack")
tgtLang = flag.String("tgt", "ru", "целевой язык для langpack")
seedPath = flag.String("seed", "", "опциональный сид книги (YAML); пусто = майнить с нуля")
outTSV = flag.String("out", "", "куда писать TSV дельты (пусто = stdout)")
outYAML = flag.String("out-yaml", "", "опционально: карта подписи в форме DeltaYAML")
)
flag.Parse()
if *in == "" || *contrastP == "" || *packRoot == "" {
fmt.Fprintln(os.Stderr, "usage: minerharness -in chunks.jsonl -contrast dict.txt -langpack <root> [-src zh] [-seed seed.yaml] [-out out.tsv]")
os.Exit(2)
}
chunks, chars, chapters, err := readChunks(*in)
if err != nil {
fatal(err)
}
cf, err := os.Open(*contrastP)
if err != nil {
fatal(err)
}
defer cf.Close()
contrast, err := miner.LoadContrast(cf)
if err != nil {
fatal(err)
}
pack, err := lang.Load(*packRoot, *srcLang, *tgtLang)
if err != nil {
fatal(err)
}
var seed []store.GlossaryEntry
if *seedPath != "" {
seed, err = membank.LoadGlossarySeed(*seedPath)
if err != nil {
fatal(err)
}
}
mined := miner.MineBank(chunks, contrast, seed, nil, miner.FrozenConfig(), pack)
w := os.Stdout
if *outTSV != "" {
f, err := os.Create(*outTSV)
if err != nil {
fatal(err)
}
defer f.Close()
w = f
}
bw := bufio.NewWriter(w)
defer bw.Flush()
fmt.Fprintln(bw, "src\ttype\tfreq\tsince_ch\taliases\tevidence")
byType := map[string]int{}
byLen := map[int]int{}
for _, m := range mined {
byType[m.Type]++
byLen[len([]rune(m.Src))]++
fmt.Fprintf(bw, "%s\t%s\t%d\t%d\t%s\t%s\n",
m.Src, m.Type, m.Freq, m.SinceCh,
strings.Join(m.Aliases, "|"), strings.Join(m.Evidence, "|"))
}
bw.Flush()
if *outYAML != "" {
y, err := miner.DeltaYAML(mined, nil)
if err != nil {
fatal(err)
}
if err := os.WriteFile(*outYAML, []byte(y), 0o644); err != nil {
fatal(err)
}
}
fmt.Fprintf(os.Stderr, "chunks=%d chapters=%d chars=%d pack=%s emitted=%d\n",
len(chunks), chapters, chars, pack.Version(), len(mined))
fmt.Fprintf(os.Stderr, "by_type: %s\n", kvSorted(byType))
fmt.Fprintf(os.Stderr, "by_srclen: %s\n", kvIntSorted(byLen))
}
func readChunks(path string) (out []miner.Chunk, chars, chapters int, err error) {
f, err := os.Open(path)
if err != nil {
return nil, 0, 0, err
}
defer f.Close()
sc := bufio.NewScanner(f)
sc.Buffer(make([]byte, 0, 1<<20), 1<<26)
seen := map[int]bool{}
for sc.Scan() {
line := strings.TrimSpace(sc.Text())
if line == "" {
continue
}
var r row
if err := json.Unmarshal([]byte(line), &r); err != nil {
return nil, 0, 0, err
}
chars += len([]rune(r.Source))
seen[r.Chapter] = true
out = append(out, miner.Chunk{
Chapter: r.Chapter, ChunkIdx: r.ChunkIdx,
NSource: text.NormalizeSourceKey(r.Source),
})
}
return out, chars, len(seen), sc.Err()
}
func kvSorted(m map[string]int) string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
var b strings.Builder
for i, k := range keys {
if i > 0 {
b.WriteString(" ")
}
fmt.Fprintf(&b, "%s=%d", k, m[k])
}
return b.String()
}
func kvIntSorted(m map[int]int) string {
keys := make([]int, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Ints(keys)
var b strings.Builder
for i, k := range keys {
if i > 0 {
b.WriteString(" ")
}
fmt.Fprintf(&b, "%d=%d", k, m[k])
}
return b.String()
}
func fatal(err error) {
fmt.Fprintln(os.Stderr, "minerharness:", err)
os.Exit(1)
}