primitives.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  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. Tags []string
  18. }
  19. type ForumPosts []ForumPost
  20. type Calendar struct {
  21. CurrentDay int
  22. CurrentWeekNumber int
  23. CurrentMonthName string
  24. CurrentYear int
  25. Days []int
  26. }
  27. type Weather struct {
  28. Temperature int
  29. ApparentTemperature int
  30. WeatherCode int
  31. CurrentColumn int
  32. SunriseColumn int
  33. SunsetColumn int
  34. Columns []weatherColumn
  35. }
  36. type AppRelease struct {
  37. Name string
  38. Version string
  39. NotesUrl string
  40. TimeReleased time.Time
  41. Downvotes int
  42. }
  43. type AppReleases []AppRelease
  44. type Video struct {
  45. ThumbnailUrl string
  46. Title string
  47. Url string
  48. Author string
  49. AuthorUrl string
  50. TimePosted time.Time
  51. }
  52. type Videos []Video
  53. var currencyToSymbol = map[string]string{
  54. "USD": "$",
  55. "EUR": "€",
  56. "JPY": "¥",
  57. "CAD": "C$",
  58. "AUD": "A$",
  59. "GBP": "£",
  60. "CHF": "Fr",
  61. "NZD": "N$",
  62. "INR": "₹",
  63. "BRL": "R$",
  64. "RUB": "₽",
  65. "TRY": "₺",
  66. "ZAR": "R",
  67. "CNY": "¥",
  68. "KRW": "₩",
  69. "HKD": "HK$",
  70. "SGD": "S$",
  71. "SEK": "kr",
  72. "NOK": "kr",
  73. "DKK": "kr",
  74. "PLN": "zł",
  75. "PHP": "₱",
  76. }
  77. type MarketRequest struct {
  78. Name string `yaml:"name"`
  79. Symbol string `yaml:"symbol"`
  80. ChartLink string `yaml:"chart-link"`
  81. SymbolLink string `yaml:"symbol-link"`
  82. }
  83. type Market struct {
  84. MarketRequest
  85. Currency string `yaml:"-"`
  86. Price float64 `yaml:"-"`
  87. PercentChange float64 `yaml:"-"`
  88. SvgChartPoints string `yaml:"-"`
  89. }
  90. type Markets []Market
  91. func (t Markets) 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 (v Videos) SortByNewest() Videos {
  178. sort.Slice(v, func(i, j int) bool {
  179. return v[i].TimePosted.After(v[j].TimePosted)
  180. })
  181. return v
  182. }