131 lines
4.9 KiB
Go
131 lines
4.9 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"textmachine/platform/internal/ingest"
|
|
"textmachine/platform/internal/pgstore"
|
|
)
|
|
|
|
// project.go: the read model as the contract's shapes.
|
|
//
|
|
// Everything here is a TRANSLATION and not a forwarding. Three vocabularies meet at this line — the
|
|
// engine's, the platform's own and the contract's — and the rule is the same for all three: what
|
|
// leaves is the contract's, and a value this build cannot map leaves as `null` rather than as a word
|
|
// a generated client's union does not contain.
|
|
|
|
func projectBook(b pgstore.Book) wireBook {
|
|
out := wireBook{
|
|
ID: b.ID, Revision: b.Revision, Title: b.Title,
|
|
SourceLang: b.SourceLang, TargetLang: b.TargetLang,
|
|
Status: b.Status,
|
|
StructureVersion: b.StructureVersion,
|
|
ShapeEpoch: b.ShapeEpoch,
|
|
ChapterCount: b.ChapterCount,
|
|
ChaptersDone: b.ChaptersDone,
|
|
AddedAt: b.AddedAt,
|
|
NoteCount: b.NoteCount,
|
|
}
|
|
// `null` while the book is still arriving: the size is counted as the bytes go past, so a book
|
|
// that is still on the wire has no size yet — which is a different fact from "zero characters".
|
|
//
|
|
// The ENGINE's figure wins whenever there is one, and the flag beside it says which was used. The
|
|
// two are not interchangeable and the difference is not a rounding: for an EPUB the intake's
|
|
// number counts the runes of a ZIP archive (books.counter). Until a manifest has been read there
|
|
// is nothing better to say, so the approximation is served and honestly labelled rather than
|
|
// withheld — a screen with no size at all is worse than one that says its size is approximate.
|
|
if b.Status != "uploading" {
|
|
count := b.CharacterCount
|
|
if b.SourceChars != nil {
|
|
count, out.CharacterCountExact = *b.SourceChars, true
|
|
}
|
|
out.CharacterCount = &count
|
|
}
|
|
if b.Structure != "" {
|
|
structure := b.Structure
|
|
out.Structure = &structure
|
|
}
|
|
if reason := ingest.ContractRejectReason(b.RejectReason); reason != "" {
|
|
out.RejectReason = &reason
|
|
}
|
|
return out
|
|
}
|
|
|
|
func projectRun(r pgstore.Run) wireRun {
|
|
out := wireRun{
|
|
ID: r.ID, BookID: r.BookID, Revision: r.Revision, Status: r.Status,
|
|
// ⚠ The wire name is the product's and the column's is the engine flag's (`--verify-bank`).
|
|
// They are deliberately different words for the same bit: what the user chose is "stop so I
|
|
// can look at the terms", and the flag is how that reaches the engine.
|
|
StopForSigning: r.VerifyBank,
|
|
OrderedChapters: r.OrderedChapters,
|
|
OrderedUnits: r.OrderedUnits,
|
|
DeliveredChapters: r.DeliveredChapters,
|
|
TermConsistencyFunded: r.BondFunded,
|
|
// The user's own click, which no `status` answers: a stop asked for mid-translation can meet
|
|
// the run reaching the bank signature, and `awaiting_bank` then offers to continue on a click
|
|
// that meant "stop". REQUIRED and never omitted — no stop is `false`, and an absent field
|
|
// would be a second way of writing the same fact.
|
|
StopRequested: r.StopRequested,
|
|
Progress: wireProgress{
|
|
Done: r.Progress.Done, Total: r.Progress.Total, Stage: r.Progress.Stage,
|
|
ETASeconds: r.Progress.ETASeconds,
|
|
},
|
|
StartedAt: r.StartedAt, FinishedAt: r.FinishedAt,
|
|
}
|
|
if reason := ingest.ContractPausedReason(r.PausedReason); reason != "" {
|
|
out.PausedReason = &reason
|
|
}
|
|
if r.FailureReason != "" {
|
|
out.FailureReason = &r.FailureReason
|
|
}
|
|
return out
|
|
}
|
|
|
|
func projectChapter(c pgstore.Chapter) wireChapter {
|
|
return wireChapter{
|
|
ID: c.ID, Number: c.Number,
|
|
// ⚠ ALWAYS `null` today, and that is the contract being obeyed rather than a hole. The engine's
|
|
// manifest carries a `heading`, and it is a deterministic render of the pair's heading rule
|
|
// («Глава N») — the engine's own comment forbids presenting it as a label carried by the book,
|
|
// and the canon forbids a deployment to put a rendered ordinal in this field. A client with no
|
|
// label renders its own ordinal, in the language of ITS interface, which the server does not
|
|
// know. Real labels need a producer that does not exist yet (engine backlog row 160).
|
|
Heading: nil,
|
|
UnitsTotal: c.UnitsTotal,
|
|
UnitsDone: c.UnitsDone,
|
|
NoteCount: c.NoteCount,
|
|
}
|
|
}
|
|
|
|
func projectUnit(u pgstore.Unit) wireUnit {
|
|
out := wireUnit{
|
|
ID: u.ID, Source: u.Source, Target: u.Target, State: u.State,
|
|
Notes: make([]wireNote, 0, len(u.Notes)),
|
|
}
|
|
for _, n := range u.Notes {
|
|
out.Notes = append(out.Notes, projectNote(n))
|
|
}
|
|
return out
|
|
}
|
|
|
|
func projectNote(n pgstore.Note) wireNote {
|
|
code, severity := ingest.NoteCode(n.Reason)
|
|
out := wireNote{
|
|
ID: n.ID, CreatedAt: n.CreatedAt, Severity: severity, Code: code, ChapterID: n.ChapterID,
|
|
}
|
|
if n.UnitID != "" {
|
|
out.UnitID = &n.UnitID
|
|
}
|
|
return out
|
|
}
|
|
|
|
func projectTerm(t pgstore.BankTerm) wireTerm {
|
|
out := wireTerm{
|
|
ID: t.ID, Src: t.Src, Dst: t.Dst, Status: t.Status, Origin: t.Origin, Sense: t.Sense,
|
|
SinceChapter: t.Since, UntilChapter: t.Until,
|
|
}
|
|
if t.Kind != "" {
|
|
kind := t.Kind
|
|
out.Kind = &kind
|
|
}
|
|
return out
|
|
}
|