Skip to content

Instantly share code, notes, and snippets.

@aNNiMON
Last active November 12, 2020 10:33
Show Gist options
  • Save aNNiMON/bb8782d10ab199bb5a76fb936fb066b8 to your computer and use it in GitHub Desktop.
Save aNNiMON/bb8782d10ab199bb5a76fb936fb066b8 to your computer and use it in GitHub Desktop.
Daily statistics per region in Ukraine

covid-tool

Daily statistics per region in Ukraine

covid.json
config.json
own-modules
{
"dataFile":"covid.json",
"sourceChannel":"-1001280273449",
"botToken":"1234567890:AABBCCDDEE"
}
use ["std", "http", "json", "files", "functional", "types", "regex", "collections"]
// https://www.convertcsv.com/json-to-csv.htm
include "own-modules/telegram-bot/TelegramBot.own"
include "FileOperator.own"
files = new FileOperator()
config = files.readJson("config.json")
covidData = files.readJson(config.dataFile)
covidDataObj = linkedHashMap()
for row : covidData {
covidDataObj[row.date] = row
}
bot = new TelegramBot(config.botToken)
rows = stream(bot.getUpdatesSync())
.filter(def(u) = (u.message.text ?? false))
.filter(def(u) = (u.message.forward_from_chat.id ?? 0) == config.sourceChannel)
.map(def(u) = u.message.text)
.filter(def(text) = text.contains("Дані з тимчасово"))
.map(::extractData)
.toArray()
// Add new results
for row : rows {
covidDataObj[row.date] = row
}
covidData = arrayValues(covidDataObj)
files.writeJson(config.dataFile, covidData)
def matcher(regexStr, text) = regex(regexStr, Pattern.U).matcher(text)
def extractData(text) {
row = linkedHashMap()
onelinetext = text.replace(toChar(10), " ").replace(toChar(13), "").lower
m = matcher("([\d ]+) нов.*?станом на (\d+) ([а-яіїє]+) 202", onelinetext)
if m.find() {
row.date = match (m.group(3)) {
case "січня": "01."
case "лютого": "02."
case "березня": "03."
case "квітня": "04."
case "травня": "05."
case "червня": "06."
case "липня": "07."
case "серпня": "08."
case "вересня": "09."
case "жовтня": "10."
case "листопада": "11."
case "грудня": "12."
} + sprintf("%02d", parseInt(m.group(2)))
row.daily = parseInt(m.group(1).replace(" ", ""))
}
m = matcher("захворіло – ([\d ]+) ос", text)
if m.find() {
row.all = parseInt(m.group(1).replace(" ", ""))
}
places = ["Вінницька", "Волинська", "Дніпропетровська",
"Донецька", "Житомирська", "Закарпатська",
"Запорізька", "Івано-Франківська", "Кіровоградська",
"м. Київ", "Київська", "Львівська", "Луганська",
"Миколаївська", "Одеська", "Полтавська",
"Рівненська", "Сумська", "Тернопільська",
"Харківська", "Херсонська", "Хмельницька",
"Чернівецька", "Черкаська", "Чернігівська"]
for place : places {
m = matcher(place + "(.*?)(область)?\s*[-–—]\s*(\d+ ?\d*)\s*вип", text)
row[place] = m.find() ? parseInt(replace(m.group(3), " ", "")) : 0
}
return row
}
class FileOperator {
def readJson(file) {
f = fopen(file, "r")
data = jsondecode(readText(f))
fclose(f)
return data
}
def writeJson(file, data) {
f = fopen(file, "w")
writeText(f, jsonencode(data))
flush(f)
fclose(f)
}
}
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>COVID-19 Украина</title>
<link href="styles.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-apexcharts"></script>
</head>
<body>
<div id="app">
<div id="chart" class="container">
<apexchart type="line" :options="chartOptions" :series="series"></apexchart>
</div>
<div class="container">
<div class="flex-row">
<h3>Данные по дням</h3>
<label>
<input type="checkbox" v-model="showDiffs">
Показать изменения
</label>
</div>
<table class="table">
<tr>
<th v-for="column in dailyTableColumns" v-key="column">{{ column }}</th>
</tr>
<tr v-for="row in dailyRows" v-key="row[0]">
<td v-for="(column, index) in row" v-key="index" v-bind:class="column.class">{{ column.value }}</td>
</tr>
</table>
</div>
</div>
<script>
// Find top maximal values and mark them for highlight
function highlightTop(arr) {
for (let i = 0; i < arr.length; i++) {
// Skip 2 values (`date`, `daily`) and find max values, then take top 3
let top = arr[i].slice(2).map(it => it.value).sort((a, b) => b - a).splice(0, 3);
arr[i].forEach(row => {
for (let topIndex = 0; topIndex < top.length; topIndex++) {
if (row.value == top[topIndex]) {
row.class = 'highlight-' + (topIndex + 1);
}
}
});
}
}
const input = <?= file_get_contents("covid.json") ?>;
// All columns except `date` and `all`
let columns = ['daily'].concat(Object.keys(input[0])
.filter(it => !['all', 'daily', 'date'].includes(it))
.sort((a, b) => a.localeCompare(b)));
// X-Axis: dates + mapping for columns
let dates = [];
let mapping = {};
input.forEach(item => {
dates.push(item.date);
columns.forEach(column => {
mapping[column] = mapping[column] || [];
mapping[column].push(item[column]);
});
});
// Populate series for chart
let series = [];
for (let [key, value] of Object.entries(mapping)) {
series.push({name: key, data: value});
}
// Populate data for daily table
let dailyTableData = {rows: [], diffs: []};
let yesterdayData = (new Array(columns.length + 1)).fill(({value: 0}));
for (let i = 0; i < dates.length; i++) {
// `date`, column1, column2, ..., columnN
let row1 = [];
row1.push({value: dates[i], class: ''});
columns.forEach( column => row1.push({value: mapping[column][i], class: ''}) );
dailyTableData.rows.push(row1);
// Calculate diffs
let row2 = row1.map(it => ({value: it.value, class: ''}));
for (let j = 1; j < row1.length; j++) {
row2[j].value -= yesterdayData[j].value;
yesterdayData[j] = {value: row1[j].value};
if (row2[j].value > 0) {
row2[j].value = '+' + row2[j].value;
}
if (i == 0) {
row2[j].class = 'ignored';
}
}
row2[1].class = 'ignored';
dailyTableData.diffs.push(row2);
}
highlightTop(dailyTableData.rows);
highlightTop(dailyTableData.diffs);
new Vue({
el: '#app',
components: {
apexchart: VueApexCharts,
},
data: {
showDiffs: false,
dailyTableColumns: ['Дата'].concat(columns),
dailyTableData,
series,
chartOptions: {
chart: {
width: '100%',
type: 'line',
zoom: {
enabled: true
},
animations: {
enabled: false,
},
},
responsive: [{
breakpoint: 6000,
options: {
chart: { height: 640 }
}
}],
legend: {
position: 'left'
},
colors: [
"#F3B415",
"#F27036",
"#663F59",
"#6A6E94",
"#4E88B4",
"#00A7C6",
"#18D8D8",
"#A9D794",
"#46AF78",
"#A93F55",
"#8C5E58",
"#2176FF",
"#33A1FD",
"#7A918D",
"#BAFF29"
],
stroke: {
curve: 'straight',
width: 2,
},
title: {
text: 'COVID-19 по областям Украины',
align: 'left'
},
xaxis: {
categories: dates,
},
noData: {
text: 'Загрузка...'
}
},
},
computed: {
dailyRows() {
return this.dailyTableData[this.showDiffs ? 'diffs' : 'rows']
},
}
});
</script>
</body>
</html>
[
{
"id": "18f1894447dfa72000a83011511a817c",
"name": "telegram-bot"
}
]
use ["std"]
if (ARGS.length >= 2 && ARGS[0] == "owm") {
use ["files", "json", "java"]
File = newClass("java.io.File")
runtime = newClass("java.lang.Runtime").getRuntime()
def loadModulesJson(path = "modules.json") {
f = fopen(path, "r")
modules = jsondecode(readText(f))
fclose(f)
return modules
}
def exec(cmd, dir = ".") = runtime.exec(cmd, null, new File(dir))
match (ARGS[1]) {
case "install": {
modulesDir = "own-modules"
if (!exists(modulesDir)) {
mkdir(modulesDir)
}
for module : loadModulesJson() {
print module.name
moduleDir = modulesDir + "/" + module.name
if (!exists(moduleDir)) {
mkdir(moduleDir)
cmd = "git clone https://gist.github.com/" + module.id + ".git " + module.name
exec(cmd, modulesDir)
println " installed"
} else {
exec("git pull origin master", moduleDir)
println " updated"
}
}
}
}
}
body{height:100vh;background:#f9f9f9}#chart,.chart-box{padding-top:20px;padding-left:10px;background:#fff;border:1px solid #ddd;box-shadow:0 22px 35px -16px rgba(0,0,0,.1)}.container{max-width:1020px;margin:5px auto;margin-bottom:2rem}.table{display:block;border-collapse:collapse;border-spacing:0;text-align:left;width:100%;overflow-x:auto;max-height:600px;padding-bottom:.75rem}.table th{font-size:.9em}.table tbody tr:nth-of-type(2n+1){background:#f5f7f8}.table tbody td:first-child{font-weight:700;min-width:50px;border-right:.05rem solid #e1e5eb}.table tbody tr:nth-of-type(2n),.table thead tr{background:#fff}.table td,.table th{border-bottom:.05rem solid #e1e5eb;padding:.3rem .3rem;min-width:45px}.table th{text-align: center;word-wrap:anywhere}.table td{text-align:right}select.flat-select{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:#008ffb url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'60px\' height=\'60px\'><polyline fill=\'white\' points=\'46.139,15.518 25.166,36.49 4.193,15.519\'/></svg>") no-repeat scroll right 2px top 9px/16px 16px;border:0 none;border-radius:3px;color:#fff;font-family:arial,tahoma;font-size:16px;font-weight:700;outline:0 none;height:33px;padding:5px 20px 5px 10px;text-align:center;text-indent:.01px;text-overflow:"";text-shadow:0 -1px 0 rgba(0,0,0,.25);transition:all .3s ease 0s;width:auto;-webkit-transition:.3s ease all;-moz-transition:.3s ease all;-ms-transition:.3s ease all;-o-transition:.3s ease all;transition:.3s ease all}select.flat-select:focus,select.flat-select:hover{border:0;outline:0}.apexcharts-canvas{margin:0 auto}.ignored{color:#949494;font-style:italic}.highlight-1{background:#ff7979;font-weight:700}.highlight-2{background:#f9b2b2}.highlight-3{background:#f7e5e5}.flex-row{display:flex;align-items:center;justify-content:space-between}
th:not([scope=row]){background:#f5f7f8;position:-webkit-sticky;position:sticky;top:0;z-index:2;}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment