textmachine/backend/internal/store/voice.go

74 lines
3.2 KiB
Go

package store
import "database/sql"
// voice.go: durable storage for the two D21 bank record types added by pack-19 (schema v13) — the
// per-character VOICE profile and the ordered ADDRESS pair. Dumb storage like glossary.go: this layer
// replaces and reads back, the pipeline owns validation, projection and matching.
//
// Both are BANK CONTENT: they are replaced in the same transaction as the glossary (ReplaceBank) and
// fold into the same memory_version, so a signature on either is a bank-only snapshot move and stays
// re-pinnable per unit (pipeline/repin.go) instead of re-buying an edit wave.
// VoiceProfile is one character's voice record for a chapter window (research/15 §1.5). The character is
// identified by the STABLE (Src, Sense) key of its glossary term — never by glossary.id, which is a fresh
// autoincrement on every replace. The list-valued columns are stored as JSON; membank owns the encoding.
type VoiceProfile struct {
Src string
Sense string
Register string
SelfRef string
AddressDefault string // informal|formal|"" — the T/V default outside the pair registry
LexiconMarkers string // JSON []string
NGLexicon string // JSON []string
Exemplars string // JSON []string
Brightness string
SinceCh int
UntilCh int
}
// AddressPair is one ORDERED (speaker → addressee) register record for a chapter window — one entry of
// the ты/вы transition journal (D7-Amendment-1). Register is ABSTRACT (informal|formal); which target
// surfaces realise it is target data, so a target without a T/V distinction simply matches nothing.
type AddressPair struct {
SpeakerSrc string
SpeakerSense string
AddresseeSrc string
AddresseeSense string
Register string
Form string
Closeness string
SinceCh int
UntilCh int
}
// VoiceProfilesForBook returns a book's voice profiles in a DETERMINISTIC order — the stable
// materialization the bank version hashes over.
func (s *Store) VoiceProfilesForBook(bookID string) ([]VoiceProfile, error) {
return queryAll(s.r, `
SELECT src, sense, register, self_ref, address_default,
lexicon_markers, ng_lexicon, exemplars, brightness, since_ch, until_ch
FROM voice_profiles WHERE book_id = ?
ORDER BY src, sense, since_ch, until_ch`,
func(rows *sql.Rows) (VoiceProfile, error) {
var v VoiceProfile
err := rows.Scan(&v.Src, &v.Sense, &v.Register, &v.SelfRef, &v.AddressDefault,
&v.LexiconMarkers, &v.NGLexicon, &v.Exemplars, &v.Brightness, &v.SinceCh, &v.UntilCh)
return v, err
}, bookID)
}
// AddressPairsForBook returns a book's address-register journal in a DETERMINISTIC order.
func (s *Store) AddressPairsForBook(bookID string) ([]AddressPair, error) {
return queryAll(s.r, `
SELECT speaker_src, speaker_sense, addressee_src, addressee_sense,
register, form, closeness, since_ch, until_ch
FROM address_pairs WHERE book_id = ?
ORDER BY speaker_src, speaker_sense, addressee_src, addressee_sense, since_ch, until_ch`,
func(rows *sql.Rows) (AddressPair, error) {
var p AddressPair
err := rows.Scan(&p.SpeakerSrc, &p.SpeakerSense, &p.AddresseeSrc, &p.AddresseeSense,
&p.Register, &p.Form, &p.Closeness, &p.SinceCh, &p.UntilCh)
return p, err
}, bookID)
}