primitives.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. package feed
  2. import (
  3. "math"
  4. "sort"
  5. "time"
  6. )
  7. type ForumPost struct {
  8. Title string
  9. DiscussionUrl string
  10. TargetUrl string
  11. TargetUrlDomain string
  12. ThumbnailUrl string
  13. CommentCount int
  14. Score int
  15. Engagement float64
  16. TimePosted time.Time
  17. }
  18. type ForumPosts []ForumPost
  19. type Calendar struct {
  20. CurrentDay int
  21. CurrentWeekNumber int
  22. CurrentMonthName string
  23. CurrentYear int
  24. Days []int
  25. }
  26. type Weather struct {
  27. Temperature int
  28. ApparentTemperature int
  29. WeatherCode int
  30. CurrentColumn int
  31. SunriseColumn int
  32. SunsetColumn int
  33. Columns []weatherColumn
  34. }
  35. type AppRelease struct {
  36. Name string
  37. Version string
  38. NotesUrl string
  39. TimeReleased time.Time
  40. Downvotes int
  41. }
  42. type AppReleases []AppRelease
  43. type ChangeWatch struct {
  44. Name string
  45. Url string
  46. LastChanged time.Time
  47. }
  48. type ChangeWatches []ChangeWatch
  49. type Video struct {
  50. ThumbnailUrl string
  51. Title string
  52. Url string
  53. Author string
  54. AuthorUrl string
  55. TimePosted time.Time
  56. }
  57. type Videos []Video
  58. var currencyToSymbol = map[string]string{
  59. "USD": "$",
  60. "EUR": "€",
  61. "JPY": "¥",
  62. "CAD": "C$",
  63. "AUD": "A$",
  64. "GBP": "£",
  65. "CHF": "Fr",
  66. "NZD": "N$",
  67. "INR": "₹",
  68. "BRL": "R$",
  69. "RUB": "₽",
  70. "TRY": "₺",
  71. "ZAR": "R",
  72. "CNY": "¥",
  73. "KRW": "₩",
  74. "HKD": "HK$",
  75. "SGD": "S$",
  76. "SEK": "kr",
  77. "NOK": "kr",
  78. "DKK": "kr",
  79. "PLN": "zł",
  80. "PHP": "₱",
  81. }
  82. type Stock struct {
  83. Name string
  84. Symbol string
  85. Currency string
  86. Price float64
  87. PercentChange float64
  88. SvgChartPoints string
  89. }
  90. type Stocks []Stock
  91. func (t Stocks) SortByAbsChange() {
  92. sort.Slice(t, func(i, j int) bool {
  93. return math.Abs(t[i].PercentChange) > math.Abs(t[j].PercentChange)
  94. })
  95. }
  96. var weatherCodeTable = map[int]string{
  97. 0: "Clear Sky",
  98. 1: "Mainly Clear",
  99. 2: "Partly Cloudy",
  100. 3: "Overcast",
  101. 45: "Fog",
  102. 48: "Rime Fog",
  103. 51: "Drizzle",
  104. 53: "Drizzle",
  105. 55: "Drizzle",
  106. 56: "Drizzle",
  107. 57: "Drizzle",
  108. 61: "Rain",
  109. 63: "Moderate Rain",
  110. 65: "Heavy Rain",
  111. 66: "Freezing Rain",
  112. 67: "Freezing Rain",
  113. 71: "Snow",
  114. 73: "Moderate Snow",
  115. 75: "Heavy Snow",
  116. 77: "Snow Grains",
  117. 80: "Rain",
  118. 81: "Moderate Rain",
  119. 82: "Heavy Rain",
  120. 85: "Snow",
  121. 86: "Snow",
  122. 95: "Thunderstorm",
  123. 96: "Thunderstorm",
  124. 99: "Thunderstorm",
  125. }
  126. func (w *Weather) WeatherCodeAsString() string {
  127. if weatherCode, ok := weatherCodeTable[w.WeatherCode]; ok {
  128. return weatherCode
  129. }
  130. return ""
  131. }
  132. const depreciatePostsOlderThanHours = 7
  133. const maxDepreciation = 0.9
  134. const maxDepreciationAfterHours = 24
  135. func (p ForumPosts) CalculateEngagement() {
  136. var totalComments int
  137. var totalScore int
  138. for i := range p {
  139. totalComments += p[i].CommentCount
  140. totalScore += p[i].Score
  141. }
  142. numberOfPosts := float64(len(p))
  143. averageComments := float64(totalComments) / numberOfPosts
  144. averageScore := float64(totalScore) / numberOfPosts
  145. for i := range p {
  146. p[i].Engagement = (float64(p[i].CommentCount)/averageComments + float64(p[i].Score)/averageScore) / 2
  147. elapsed := time.Since(p[i].TimePosted)
  148. if elapsed < time.Hour*depreciatePostsOlderThanHours {
  149. continue
  150. }
  151. p[i].Engagement *= 1.0 - (math.Max(elapsed.Hours()-depreciatePostsOlderThanHours, maxDepreciationAfterHours)/maxDepreciationAfterHours)*maxDepreciation
  152. }
  153. }
  154. func (p ForumPosts) SortByEngagement() {
  155. sort.Slice(p, func(i, j int) bool {
  156. return p[i].Engagement > p[j].Engagement
  157. })
  158. }
  159. func (s *ForumPost) HasTargetUrl() bool {
  160. return s.TargetUrl != ""
  161. }
  162. func (p ForumPosts) FilterPostedBefore(postedBefore time.Duration) []ForumPost {
  163. recent := make([]ForumPost, 0, len(p))
  164. for i := range p {
  165. if time.Since(p[i].TimePosted) < postedBefore {
  166. recent = append(recent, p[i])
  167. }
  168. }
  169. return recent
  170. }
  171. func (r AppReleases) SortByNewest() AppReleases {
  172. sort.Slice(r, func(i, j int) bool {
  173. return r[i].TimeReleased.After(r[j].TimeReleased)
  174. })
  175. return r
  176. }
  177. func (r ChangeWatches) SortByNewest() ChangeWatches {
  178. sort.Slice(r, func(i, j int) bool {
  179. return r[i].LastChanged.After(r[j].LastChanged)
  180. })
  181. return r
  182. }
  183. func (v Videos) SortByNewest() Videos {
  184. sort.Slice(v, func(i, j int) bool {
  185. return v[i].TimePosted.After(v[j].TimePosted)
  186. })
  187. return v
  188. }