77 "fmt"
88 "log"
99 "os"
10+ "time"
1011
1112 "health-receiver/internal/health"
1213 "health-receiver/internal/storage"
@@ -16,6 +17,8 @@ func main() {
1617 schema := flag .String ("schema" , "health" , "tenant schema" )
1718 lang := flag .String ("lang" , "en" , "briefing language" )
1819 date := flag .String ("date" , "" , "specific local date YYYY-MM-DD; defaults to latest briefing" )
20+ from := flag .String ("from" , "" , "start local date YYYY-MM-DD for inclusive sweep" )
21+ to := flag .String ("to" , "" , "end local date YYYY-MM-DD for inclusive sweep" )
1922 days := flag .Int ("days" , 0 , "retrospective sweep over recent daily_scores dates" )
2023 noCheckin := flag .Bool ("no-checkin" , false , "ignore subjective check-in evidence" )
2124 flag .Parse ()
@@ -31,6 +34,11 @@ func main() {
3134 return
3235 }
3336
37+ if * from != "" || * to != "" {
38+ runRange (db , * schema , * from , * to , ! * noCheckin )
39+ return
40+ }
41+
3442 if * date != "" {
3543 printDate (db , * schema , * date , ! * noCheckin )
3644 return
@@ -111,6 +119,55 @@ func runSweep(db *storage.DB, schema string, days int, includeCheckin bool) {
111119 printJSON (map [string ]any {"schema" : schema , "days" : days , "counts" : counts , "top_hits" : hits })
112120}
113121
122+ func runRange (db * storage.DB , schema , from , to string , includeCheckin bool ) {
123+ if from == "" || to == "" {
124+ log .Fatal ("--from and --to must be provided together" )
125+ }
126+ if _ , err := time .Parse ("2006-01-02" , from ); err != nil {
127+ log .Fatalf ("invalid --from date: %v" , err )
128+ }
129+ if _ , err := time .Parse ("2006-01-02" , to ); err != nil {
130+ log .Fatalf ("invalid --to date: %v" , err )
131+ }
132+ rows , err := db .QueryReadOnly ("SELECT date FROM daily_scores WHERE date >= $1 AND date <= $2 ORDER BY date" , from , to )
133+ if err != nil {
134+ log .Fatal (err )
135+ }
136+ type day struct {
137+ Date string `json:"date"`
138+ Confidence string `json:"confidence"`
139+ Pattern string `json:"pattern,omitempty"`
140+ Signals []string `json:"signals,omitempty"`
141+ }
142+ out := struct {
143+ Schema string `json:"schema"`
144+ From string `json:"from"`
145+ To string `json:"to"`
146+ Counts map [string ]int `json:"counts"`
147+ Days []day `json:"days"`
148+ }{Schema : schema , From : from , To : to , Counts : map [string ]int {"none" : 0 , "low" : 0 , "moderate" : 0 , "high" : 0 }}
149+ for _ , row := range rows {
150+ date , _ := row ["date" ].(string )
151+ if date == "" {
152+ continue
153+ }
154+ var checkin * health.SubjectiveCheckinSummary
155+ if includeCheckin {
156+ checkin = checkinForDate (db , date )
157+ }
158+ ill := health .ComputeIllnessSuspicion (db .BuildIllnessEvidenceInput (date , checkin ))
159+ out .Counts [ill .Confidence ]++
160+ d := day {Date : date , Confidence : ill .Confidence , Pattern : ill .Pattern }
161+ for _ , sig := range ill .Signals {
162+ if sig .Status == "ok" && sig .Strength != "weak" && (sig .Contributes || sig .Metric == "sustained_hr_load" ) {
163+ d .Signals = append (d .Signals , sig .Metric + ":" + sig .Strength )
164+ }
165+ }
166+ out .Days = append (out .Days , d )
167+ }
168+ printJSON (out )
169+ }
170+
114171func checkinForDate (db * storage.DB , date string ) * health.SubjectiveCheckinSummary {
115172 row , err := db .GetTodayCheckin (date , storage .CheckinSourceTelegram )
116173 if err != nil || row == nil {
0 commit comments