внутренняя модель перестроена вокруг аудиозаписи
- audiorecords вместо transcribe_jobs: приложения (texts, structures, recognitions, record_events, topics) живут своими коллекциями, ссылки на исходник и на приведённую копию перестали переставляться - рубеж называет достигнутое, отказ стал признаком остановки с причиной, а сторожей стало двое: число отказов и время в рубеже - воркеры потеряли специализацию, их число задаётся [pipeline] workers, шаг выбирается по рубежу, а захват отдаёт идентификатор и признак захвата
This commit is contained in:
@@ -4,35 +4,83 @@ import (
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
|
||||
"git.vakhrushev.me/av/transcriber/internal/adapter/repo/pocketbase/migrations"
|
||||
"git.vakhrushev.me/av/transcriber/internal/entity"
|
||||
)
|
||||
|
||||
// BindPanelRules подчиняет правку задачи в панели тем же правилам перехода, что
|
||||
// и правку из кода.
|
||||
// BindPanelRules подчиняет правку записи в панели тем же правилам, что и правку
|
||||
// из кода.
|
||||
//
|
||||
// Панель — вход в задачу наравне с конвейером, а не окно просмотра: ради правки
|
||||
// она и покупалась, мёртвая задача оживляется сменой состояния. Но правка полем
|
||||
// идёт мимо кода, который чистит служебные поля прошлого состояния, и владелец,
|
||||
// «вернувший задачу в работу», получил бы задачу с прежним признаком захвата
|
||||
// (захвату она не выдастся до конца срока) и с числом попыток на пределе (умрёт
|
||||
// от первого же отказа). Узнать об этом ему неоткуда.
|
||||
// Панель — вход в запись наравне с конвейером, а не окно просмотра: ради правки
|
||||
// она и покупалась, остановленная запись возвращается в работу снятием признака.
|
||||
// Но правка полем идёт мимо кода, который чистит служебные поля, и владелец,
|
||||
// «вернувший запись в работу», получил бы запись с прежним признаком захвата
|
||||
// (захвату она не выдастся до конца срока), с числом отказов на пределе
|
||||
// (остановится от первого же отказа) и со старым временем входа в рубеж
|
||||
// (остановится снова первым же захватом по пределу простоя). Узнать об этом ему
|
||||
// неоткуда.
|
||||
//
|
||||
// Правило живёт **одним местом** — доменными `Resume` и `MoveToState`, — и хук
|
||||
// зовёт именно их, а не повторяет перечень служебных полей колонками. Повтор
|
||||
// перечня был бы вторым домом того же правила: новый сторож попал бы в домен и
|
||||
// не попал в панель, и владелец «вернул бы запись в работу», а она снова выпала
|
||||
// бы из выборки — молча.
|
||||
//
|
||||
// Хук стоит на правке **запросом**, а не на всяком сохранении записи. Модельное
|
||||
// событие не различает, кто пишет, и срабатывало бы на каждом переходе
|
||||
// конвейера: тогда задержка, поставленная шагом вместе со сменой состояния,
|
||||
// стиралась бы тем же сохранением, а число попыток мёртвой задачи — которое
|
||||
// переход хранит намеренно — приходило бы владельцу нулём.
|
||||
// конвейера: тогда пауза, поставленная шагом вместе со сменой рубежа, стиралась
|
||||
// бы тем же сохранением, а число отказов остановленной записи — которое
|
||||
// остановка хранит намеренно — приходило бы владельцу нулём.
|
||||
func BindPanelRules(app core.App) {
|
||||
app.OnRecordUpdateRequest(migrations.JobsCollection).BindFunc(func(e *core.RecordRequestEvent) error {
|
||||
app.OnRecordUpdateRequest(migrations.RecordsCollection).BindFunc(func(e *core.RecordRequestEvent) error {
|
||||
original := e.Record.Original()
|
||||
if original == nil || original.GetString("state") == e.Record.GetString("state") {
|
||||
if original == nil {
|
||||
return e.Next()
|
||||
}
|
||||
|
||||
e.Record.Set("acquisition_id", "")
|
||||
e.Record.Set("acquire_time", "")
|
||||
e.Record.Set("delay_time", "")
|
||||
e.Record.Set("attempts", 0)
|
||||
stateChanged := original.GetString("state") != e.Record.GetString("state")
|
||||
// Снятие признака остановки — то самое движение, ради которого признак и
|
||||
// заведён: запись возвращается в работу с сохранённого рубежа.
|
||||
resumed := !original.GetDateTime("halted_at").IsZero() &&
|
||||
e.Record.GetDateTime("halted_at").IsZero()
|
||||
|
||||
return e.Next()
|
||||
if !stateChanged && !resumed {
|
||||
return e.Next()
|
||||
}
|
||||
|
||||
// Запись читается уже с правкой человека: рубеж здесь тот, который он
|
||||
// выбрал, а признак остановки — тот, который он снял или оставил.
|
||||
record := recordToAudioRecord(e.Record)
|
||||
switch {
|
||||
case resumed:
|
||||
record.Resume()
|
||||
default:
|
||||
record.MoveToState(record.State)
|
||||
}
|
||||
applyOwnedByPipeline(e.Record, record)
|
||||
|
||||
if err := e.Next(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resumed {
|
||||
// Перезапуск виден в журнале событий с указанием, что его сделал
|
||||
// человек: иначе запись, вернувшаяся в работу, выглядела бы как
|
||||
// запись, которая туда и не уходила.
|
||||
//
|
||||
// Строка пишется **после** сохранения: событие о правке, которая не
|
||||
// прошла, соврало бы о состоянии записи. Отказ записи журнала саму
|
||||
// правку не отменяет — журнал никем не читается ради решения.
|
||||
event := &entity.RecordEvent{
|
||||
RecordID: e.Record.Id,
|
||||
Origin: entity.EventOriginHuman,
|
||||
Step: "resume",
|
||||
Outcome: entity.EventOutcomeResumed,
|
||||
}
|
||||
if err := NewRecordEventRepository(e.App).Append(event); err != nil {
|
||||
e.App.Logger().Error("Failed to log record resume", "error", err, "record_id", e.Record.Id)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user