fix(input): correct lon mapping

This commit is contained in:
gili8420 2026-08-04 13:40:50 +09:00
parent 19557f3a62
commit 84d1664b29
20 changed files with 1197 additions and 137 deletions

View file

@ -161,6 +161,57 @@ func (m *Manager) SelectFor(t time.Time, lat, lng float64) weather.WindField {
return nil
}
// FieldFor returns the global dataset whose epoch is epoch, loading it from
// storage on demand when it is stored but not currently active.
//
// Being stored is enough. A caller asking for a specific run — an archival
// forecast, a reproducible comparison — must not have to make it the service's
// active dataset first, and asking for it must not repoint the service: Load
// appends, and Active keeps returning the first global it finds.
//
// Errors when no stored dataset carries that epoch, so a request naming a
// dataset the service cannot serve is refused rather than quietly answered from
// a different one.
//
// ponytail: loaded datasets are never evicted, so a long-lived service asked for
// many distinct epochs accumulates one mmap and fd each. Add LRU eviction here
// if that becomes real; the files are mmap-backed, so the cost is address space
// and descriptors, not resident memory.
func (m *Manager) FieldFor(ctx context.Context, epoch time.Time) (weather.WindField, error) {
if f := m.activeFieldAt(epoch); f != nil {
return f, nil
}
stored, err := m.store.List()
if err != nil {
return nil, fmt.Errorf("list stored datasets: %w", err)
}
for _, id := range stored {
if !id.Subset.IsGlobal() || !id.Epoch.Equal(epoch) {
continue
}
if err := m.Load(ctx, id); err != nil {
return nil, fmt.Errorf("load %s: %w", id.Filename(), err)
}
if f := m.activeFieldAt(epoch); f != nil {
return f, nil
}
}
return nil, fmt.Errorf("dataset %s is not stored", epoch.UTC().Format(time.RFC3339))
}
// activeFieldAt returns the loaded global field with exactly this epoch, or nil.
func (m *Manager) activeFieldAt(epoch time.Time) weather.WindField {
m.activeMu.RLock()
defer m.activeMu.RUnlock()
for _, d := range m.active {
if d.ID.Subset.IsGlobal() && d.ID.Epoch.Equal(epoch) {
return d.Field
}
}
return nil
}
// LoadedDatasets returns snapshots of every currently-loaded dataset.
func (m *Manager) LoadedDatasets() []LoadedDatasetInfo {
m.activeMu.RLock()
@ -309,36 +360,48 @@ func (m *Manager) Load(ctx context.Context, id DatasetID) error {
//
// Returns the JobID started, or empty string when nothing was scheduled.
func (m *Manager) Refresh(ctx context.Context, freshnessTTL time.Duration) (string, error) {
if a := m.activeGlobal(); a != nil && time.Since(a.ID.Epoch) < freshnessTTL {
return "", nil
}
if datasets, err := m.store.List(); err == nil {
for _, id := range datasets {
if !id.Subset.IsGlobal() {
continue
}
if time.Since(id.Epoch) > freshnessTTL {
continue
}
if a := m.activeGlobal(); a != nil && a.ID.Equals(id) {
return "", nil
}
if err := m.Load(ctx, id); err == nil {
return "", nil
// Get something usable loaded first, whatever its age.
//
// freshnessTTL answers one question only: go fetch something newer? It must
// never decide whether data already on disk may be read. While one check
// served both, a stored run older than the TTL was skipped even when it was
// the only dataset present — active stayed empty, every prediction answered
// "no dataset loaded" with gigabytes of usable wind on disk, and with no
// reachable origin that state was permanent.
if m.activeGlobal() == nil {
if stored, err := m.store.List(); err == nil {
for _, id := range stored { // newest first
if !id.Subset.IsGlobal() {
continue
}
if err := m.Load(ctx, id); err == nil {
break
}
}
}
}
active := m.activeGlobal()
if active != nil && time.Since(active.ID.Epoch) < freshnessTTL {
return "", nil
}
latest, err := m.src.LatestEpoch(ctx)
if err != nil {
return "", fmt.Errorf("latest epoch: %w", err)
}
id := DatasetID{Epoch: latest}
if a := m.activeGlobal(); a != nil && !latest.After(a.ID.Epoch) {
if active != nil && !latest.After(active.ID.Epoch) {
return "", nil
}
// Another replica sharing this volume may have committed it already.
id := DatasetID{Epoch: latest}
if m.store.Exists(id) {
if err := m.Load(ctx, id); err == nil {
return "", nil
}
}
jobID := m.Download(id)
go m.loadAfterCompletion(jobID, id)
return jobID, nil