20 // |
20 // |
21 // Get records and display the average: |
21 // Get records and display the average: |
22 // % gobm65 --average |
22 // % gobm65 --average |
23 // ... display more statistics: |
23 // ... display more statistics: |
24 // % gobm65 --stats |
24 // % gobm65 --stats |
|
25 // ... also display World Health Organization classification: |
|
26 // % gobm65 --stats --class |
25 // |
27 // |
26 // Display the latest 3 records with the average: |
28 // Display the latest 3 records with the average: |
27 // % gobm65 -l 3 --average |
29 // % gobm65 -l 3 --average |
28 // Display all records since a specific date: |
30 // Display all records since a specific date: |
29 // % gobm65 --since "2016-06-01" |
31 // % gobm65 --since "2016-06-01" |
79 Year int |
81 Year int |
80 } |
82 } |
81 |
83 |
82 type simpleTime struct { |
84 type simpleTime struct { |
83 hour, minute int |
85 hour, minute int |
|
86 } |
|
87 |
|
88 // World Heath Organization blood pressure classification |
|
89 const ( |
|
90 BPOptimal = iota // < 120,80: Optimal |
|
91 BPNormal // < 130,85: Normal |
|
92 BPHighNormal // < 140,90: High-Normal |
|
93 BPMildHypertension // < 160,100: Mild Hypertension |
|
94 BPModerateHypertension // < 180,110: Moderate Hypertension |
|
95 BPSevereHypertension // >=180,110: Severe Hypertension |
|
96 ) |
|
97 |
|
98 // WHOPressureClassification contains the World Health Organization blood |
|
99 // pressure categories |
|
100 var WHOPressureClassification = []string{ |
|
101 "Optimal", |
|
102 "Normal", |
|
103 "High-Normal", |
|
104 "Mild Hypertension", |
|
105 "Moderate Hypertension", |
|
106 "Severe Hypertension", |
84 } |
107 } |
85 |
108 |
86 func getData(s io.ReadWriteCloser, buf []byte, size int) (int, error) { |
109 func getData(s io.ReadWriteCloser, buf []byte, size int) (int, error) { |
87 t := 0 |
110 t := 0 |
88 b := buf |
111 b := buf |
421 dev.Pulse = int(sumPul / float64(len(items))) |
444 dev.Pulse = int(sumPul / float64(len(items))) |
422 |
445 |
423 return dev, nil |
446 return dev, nil |
424 } |
447 } |
425 |
448 |
|
449 func (m measurement) WHOClass() int { |
|
450 switch { |
|
451 case m.Systolic < 120 && m.Diastolic < 80: |
|
452 return BPOptimal |
|
453 case m.Systolic < 130 && m.Diastolic < 85: |
|
454 return BPNormal |
|
455 case m.Systolic < 140 && m.Diastolic < 90: |
|
456 return BPHighNormal |
|
457 case m.Systolic < 160 && m.Diastolic < 100: |
|
458 return BPMildHypertension |
|
459 case m.Systolic < 180 && m.Diastolic < 110: |
|
460 return BPModerateHypertension |
|
461 } |
|
462 return BPSevereHypertension |
|
463 } |
|
464 |
|
465 func (m measurement) WHOClassString() string { |
|
466 return WHOPressureClassification[m.WHOClass()] |
|
467 } |
|
468 |
|
469 func displayWHOClassStats(items []measurement) { |
|
470 sum := 0 |
|
471 classes := make(map[int]int) |
|
472 for _, m := range items { |
|
473 s := m.WHOClass() |
|
474 classes[s]++ |
|
475 sum += s |
|
476 } |
|
477 |
|
478 avg := float64(sum) / float64(len(items)) |
|
479 fmt.Printf("Average WHO classification: %s (%.2f)\n", |
|
480 WHOPressureClassification[int(0.5+avg)], avg) |
|
481 |
|
482 for c := range WHOPressureClassification { |
|
483 fmt.Printf(" . %21s: %3d (%d%%)\n", |
|
484 WHOPressureClassification[c], classes[c], |
|
485 classes[c]*100/len(items)) |
|
486 } |
|
487 } |
|
488 |
426 func main() { |
489 func main() { |
427 inFile := flag.String([]string{"-input-file", "i"}, "", "Input JSON file") |
490 inFile := flag.String([]string{"-input-file", "i"}, "", "Input JSON file") |
428 outFile := flag.String([]string{"-output-file", "o"}, "", "Output JSON file") |
491 outFile := flag.String([]string{"-output-file", "o"}, "", "Output JSON file") |
429 limit := flag.Uint([]string{"-limit", "l"}, 0, "Limit number of items to N first") |
492 limit := flag.Uint([]string{"-limit", "l"}, 0, "Limit number of items to N first") |
430 toDate := flag.String([]string{"-to-date"}, "", |
493 toDate := flag.String([]string{"-to-date"}, "", |
432 fromDate := flag.String([]string{"-from-date", "-since"}, "", |
495 fromDate := flag.String([]string{"-from-date", "-since"}, "", |
433 "Filter records from date (YYYY-mm-dd HH:MM:SS)") |
496 "Filter records from date (YYYY-mm-dd HH:MM:SS)") |
434 format := flag.String([]string{"-format", "f"}, "", "Output format (csv, json)") |
497 format := flag.String([]string{"-format", "f"}, "", "Output format (csv, json)") |
435 avg := flag.Bool([]string{"-average", "a"}, false, "Compute average") |
498 avg := flag.Bool([]string{"-average", "a"}, false, "Compute average") |
436 stats := flag.Bool([]string{"-stats"}, false, "Compute statistics") |
499 stats := flag.Bool([]string{"-stats"}, false, "Compute statistics") |
|
500 whoClass := flag.Bool([]string{"-class", "c"}, false, "Display WHO classification") |
437 merge := flag.Bool([]string{"-merge", "m"}, false, |
501 merge := flag.Bool([]string{"-merge", "m"}, false, |
438 "Try to merge input JSON file with fetched data") |
502 "Try to merge input JSON file with fetched data") |
439 device := flag.String([]string{"-device", "d"}, "/dev/ttyUSB0", "Serial device") |
503 device := flag.String([]string{"-device", "d"}, "/dev/ttyUSB0", "Serial device") |
440 fromTime := flag.String([]string{"-from-time"}, "", "Select records after time (HH:MM)") |
504 fromTime := flag.String([]string{"-from-time"}, "", "Select records after time (HH:MM)") |
441 toTime := flag.String([]string{"-to-time"}, "", "Select records bofore time (HH:MM)") |
505 toTime := flag.String([]string{"-to-time"}, "", "Select records bofore time (HH:MM)") |
579 |
643 |
580 // Done with filtering |
644 // Done with filtering |
581 |
645 |
582 if *format == "csv" { |
646 if *format == "csv" { |
583 for i, data := range items { |
647 for i, data := range items { |
584 fmt.Printf("%d;%x;%d-%02d-%02d %02d:%02d;%d;%d;%d\n", |
648 fmt.Printf("%d;%x;%d-%02d-%02d %02d:%02d;%d;%d;%d", |
585 i+1, data.Header, |
649 i+1, data.Header, |
586 data.Year, data.Month, data.Day, |
650 data.Year, data.Month, data.Day, |
587 data.Hour, data.Minute, |
651 data.Hour, data.Minute, |
588 data.Systolic, data.Diastolic, data.Pulse) |
652 data.Systolic, data.Diastolic, data.Pulse) |
|
653 if *whoClass { |
|
654 fmt.Printf(";%s", data.WHOClassString()) |
|
655 } |
|
656 fmt.Println() |
589 } |
657 } |
590 } |
658 } |
591 |
659 |
592 if *stats { |
660 if *stats { |
593 *avg = true |
661 *avg = true |
596 if *avg && len(items) > 0 { |
664 if *avg && len(items) > 0 { |
597 avgMeasure, err := average(items) |
665 avgMeasure, err := average(items) |
598 if err != nil { |
666 if err != nil { |
599 log.Println("Error:", err) |
667 log.Println("Error:", err) |
600 } else { |
668 } else { |
601 fmt.Printf("Average: %d;%d;%d\n", avgMeasure.Systolic, |
669 fmt.Printf("Average: %d;%d;%d", avgMeasure.Systolic, |
602 avgMeasure.Diastolic, avgMeasure.Pulse) |
670 avgMeasure.Diastolic, avgMeasure.Pulse) |
|
671 if *whoClass { |
|
672 fmt.Printf(" [%s]", avgMeasure.WHOClassString()) |
|
673 } |
|
674 fmt.Println() |
603 } |
675 } |
604 } |
676 } |
605 |
677 |
606 if *stats && len(items) > 1 { |
678 if *stats && len(items) > 1 { |
607 d, err := stdDeviation(items) |
679 d, err := stdDeviation(items) |
622 if *stats && len(items) > 0 { |
694 if *stats && len(items) > 0 { |
623 m, err := median(items) |
695 m, err := median(items) |
624 if err != nil { |
696 if err != nil { |
625 log.Println("Error:", err) |
697 log.Println("Error:", err) |
626 } else { |
698 } else { |
627 fmt.Printf("Median values: %d;%d;%d\n", |
699 fmt.Printf("Median values: %d;%d;%d", |
628 m.Systolic, m.Diastolic, m.Pulse) |
700 m.Systolic, m.Diastolic, m.Pulse) |
|
701 if *whoClass { |
|
702 fmt.Printf(" [%s]", m.WHOClassString()) |
|
703 } |
|
704 fmt.Println() |
|
705 } |
|
706 |
|
707 if *whoClass { |
|
708 displayWHOClassStats(items) |
629 } |
709 } |
630 } |
710 } |
631 |
711 |
632 if *format == "json" || *outFile != "" { |
712 if *format == "json" || *outFile != "" { |
633 rawJSON, err := json.MarshalIndent(items, "", " ") |
713 rawJSON, err := json.MarshalIndent(items, "", " ") |