Add UI console and spare forecast naming

This commit is contained in:
2026-02-06 00:01:52 +03:00
parent 5af1462645
commit ee46a093d3
21 changed files with 1573 additions and 207 deletions

View File

@@ -36,7 +36,7 @@ type FirmwareRisk struct {
FailureRate *float64
}
type ZipForecast struct {
type SpareForecast struct {
LotID *int64
LotCode *string
ComponentCount int64
@@ -154,21 +154,21 @@ func (r *Repository) ListFirmwareRisk(ctx context.Context, start, end time.Time)
return items, nil
}
func (r *Repository) ForecastZIP(ctx context.Context, start, end time.Time, horizonDays int, multiplier float64) ([]ZipForecast, error) {
func (r *Repository) ForecastSpare(ctx context.Context, start, end time.Time, horizonDays int, multiplier float64) ([]SpareForecast, error) {
metrics, err := r.ListLotMetrics(ctx, start, end)
if err != nil {
return nil, err
}
horizonYears := float64(horizonDays) / 365
items := make([]ZipForecast, 0, len(metrics))
items := make([]SpareForecast, 0, len(metrics))
for _, metric := range metrics {
expected := metric.AFR * float64(metric.ComponentCount) * horizonYears
if expected < 0 {
expected = 0
}
spares := int64(math.Ceil(expected * multiplier))
items = append(items, ZipForecast{
items = append(items, SpareForecast{
LotID: metric.LotID,
LotCode: metric.LotCode,
ComponentCount: metric.ComponentCount,

View File

@@ -67,3 +67,51 @@ func (r *FailureRepository) Upsert(ctx context.Context, tx *sql.Tx, event domain
}
return id, nil
}
func (r *FailureRepository) ListAll(ctx context.Context, limit int) ([]domain.FailureEvent, error) {
if limit <= 0 {
limit = 200
}
rows, err := r.db.QueryContext(ctx,
`SELECT id, source, external_id, component_id, asset_id, failure_type, failure_time, details, confidence, created_at
FROM failure_events
ORDER BY failure_time DESC, id DESC
LIMIT ?`,
limit,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := make([]domain.FailureEvent, 0)
for rows.Next() {
var event domain.FailureEvent
var assetID sql.NullInt64
var details sql.NullString
var confidence sql.NullFloat64
if err := rows.Scan(&event.ID, &event.Source, &event.ExternalID, &event.ComponentID, &assetID, &event.FailureType, &event.FailureTime, &details, &confidence, &event.CreatedAt); err != nil {
return nil, err
}
if assetID.Valid {
value := assetID.Int64
event.AssetID = &value
}
if details.Valid {
value := details.String
event.Details = &value
}
if confidence.Valid {
value := confidence.Float64
event.Confidence = &value
}
items = append(items, event)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

View File

@@ -123,3 +123,50 @@ func (r *TicketRepository) ListByAsset(ctx context.Context, assetID int64) ([]do
}
return items, nil
}
func (r *TicketRepository) ListAll(ctx context.Context, limit int) ([]domain.Ticket, error) {
if limit <= 0 {
limit = 200
}
rows, err := r.db.QueryContext(ctx,
`SELECT t.id, t.source, t.external_id, t.title, t.status, t.opened_at, t.closed_at, t.url, t.created_at, t.updated_at
FROM tickets t
ORDER BY t.updated_at DESC, t.id DESC
LIMIT ?`,
limit,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := make([]domain.Ticket, 0)
for rows.Next() {
var ticket domain.Ticket
var openedAt sql.NullTime
var closedAt sql.NullTime
var url sql.NullString
if err := rows.Scan(&ticket.ID, &ticket.Source, &ticket.ExternalID, &ticket.Title, &ticket.Status, &openedAt, &closedAt, &url, &ticket.CreatedAt, &ticket.UpdatedAt); err != nil {
return nil, err
}
if openedAt.Valid {
value := openedAt.Time
ticket.OpenedAt = &value
}
if closedAt.Valid {
value := closedAt.Time
ticket.ClosedAt = &value
}
if url.Valid {
value := url.String
ticket.URL = &value
}
items = append(items, ticket)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}