chore: release v0.26.3
This commit is contained in:
@@ -137,6 +137,13 @@ const gapThreshold = 30 * time.Minute
|
||||
const rateMaxHours = 7 * 24
|
||||
|
||||
// Stats is the whole dashboard payload.
|
||||
// entityFirst is the first contact ever made with one DXCC entity.
|
||||
type entityFirst struct {
|
||||
at time.Time
|
||||
call string
|
||||
country string
|
||||
}
|
||||
|
||||
type Stats struct {
|
||||
// Headline figures.
|
||||
Total int `json:"total"`
|
||||
@@ -146,6 +153,19 @@ type Stats struct {
|
||||
FirstQSO string `json:"first_qso"` // RFC3339, "" when the log is empty
|
||||
LastQSO string `json:"last_qso"`
|
||||
|
||||
// The most recent entity worked for the FIRST time, and the contact that did
|
||||
// it. "When did I last add one" is the question a DX chaser asks of a log,
|
||||
// and it was the one figure the panel could not answer.
|
||||
//
|
||||
// Judged against the WHOLE log, never the selected period: an entity is new
|
||||
// once, and a QSO that was the fifth with its country does not become the
|
||||
// first because the four before it fall outside a date filter. Only the
|
||||
// period filter decides whether that contact is SHOWN.
|
||||
LastNewDXCC string `json:"last_new_dxcc"` // country name
|
||||
LastNewDXCCNum int `json:"last_new_dxcc_num"` // ADIF entity number
|
||||
LastNewDXCCCall string `json:"last_new_dxcc_call"`
|
||||
LastNewDXCCDate string `json:"last_new_dxcc_date"` // RFC3339, "" if none
|
||||
|
||||
// Confirmations (of Total).
|
||||
ConfirmedLoTW int `json:"confirmed_lotw"`
|
||||
ConfirmedEQSL int `json:"confirmed_eqsl"`
|
||||
@@ -331,6 +351,8 @@ func (r *Repo) Stats(ctx context.Context, from, to time.Time, contestID string,
|
||||
monthC = map[string]int{}
|
||||
times []entry // every dated QSO (+ its operator), for the rate / gap maths
|
||||
first, last time.Time
|
||||
// The earliest contact with each entity, for "last new DXCC".
|
||||
firstByEntity = map[int]entityFirst{}
|
||||
)
|
||||
|
||||
for rows.Next() {
|
||||
@@ -346,6 +368,22 @@ func (r *Repo) Stats(ctx context.Context, from, to time.Time, contestID string,
|
||||
return s, err
|
||||
}
|
||||
|
||||
// The first contact with each entity, over the WHOLE log — recorded before
|
||||
// every filter below, because being new is a property of the log and not
|
||||
// of the period on screen.
|
||||
if dxcc.Valid && dxcc.Int64 > 0 {
|
||||
if t := parseTimeLoose(dateStr.String).UTC(); !t.IsZero() {
|
||||
n := int(dxcc.Int64)
|
||||
if cur, seen := firstByEntity[n]; !seen || t.Before(cur.at) {
|
||||
firstByEntity[n] = entityFirst{
|
||||
at: t,
|
||||
call: strings.ToUpper(strings.TrimSpace(call.String)),
|
||||
country: strings.TrimSpace(country.String),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Contest filter first — same reasoning as the window below: a QSO that
|
||||
// isn't in this contest must not reach ANY bucket.
|
||||
if contestID != "" && strings.ToUpper(strings.TrimSpace(contestID2.String)) != contestID {
|
||||
@@ -459,6 +497,16 @@ func (r *Repo) Stats(ctx context.Context, from, to time.Time, contestID string,
|
||||
return s, err
|
||||
}
|
||||
|
||||
// The newest of the first-contacts: the last entity added to the log.
|
||||
for num, f := range firstByEntity {
|
||||
if f.at.After(parseTimeLoose(s.LastNewDXCCDate)) {
|
||||
s.LastNewDXCC = f.country
|
||||
s.LastNewDXCCNum = num
|
||||
s.LastNewDXCCCall = f.call
|
||||
s.LastNewDXCCDate = f.at.Format(time.RFC3339)
|
||||
}
|
||||
}
|
||||
|
||||
s.UniqueCalls = len(calls)
|
||||
s.Entities = len(entities)
|
||||
s.Continents = len(contC)
|
||||
|
||||
Reference in New Issue
Block a user