diff --git a/.gitignore b/.gitignore
index ea6a851..fc38adb 100755
--- a/.gitignore
+++ b/.gitignore
@@ -2,9 +2,20 @@
database.ini
settings.json
check.py
-step8-priv.py
-__pycache__/
data
Barbora
notebooks
first scrape tries
+personal_finance/fin/analysis
+personal_finance/fin/analysis/Manually polished
+personal_finance/income
+personal_finance/OG-CSV
+personal_finance/spending
+personal_notes/clickup_extract/old_output
+personal_notes/clickup_extract/source data
+personal_notes/discord_extract/categories
+src/personal_notes/discord_extract/.env.env
+.env
+Data-Engineering
+__pycache__
+
diff --git a/src/Archives/automation/plan.txt b/src/Archives/automation/plan.txt
new file mode 100755
index 0000000..e69de29
diff --git a/src/LoL-support/Database/__pycache__/config.cpython-311.pyc b/src/LoL-support/Database/__pycache__/config.cpython-311.pyc
new file mode 100755
index 0000000..73d7681
Binary files /dev/null and b/src/LoL-support/Database/__pycache__/config.cpython-311.pyc differ
diff --git a/src/LoL-support/Database/__pycache__/config.cpython-312.pyc b/src/LoL-support/Database/__pycache__/config.cpython-312.pyc
new file mode 100755
index 0000000..27a4159
Binary files /dev/null and b/src/LoL-support/Database/__pycache__/config.cpython-312.pyc differ
diff --git a/src/LoL-support/Database/config.py b/src/LoL-support/Database/config.py
old mode 100644
new mode 100755
diff --git a/src/LoL-support/Database/main.py b/src/LoL-support/Database/main.py
old mode 100644
new mode 100755
index 2d79050..6654d6f
--- a/src/LoL-support/Database/main.py
+++ b/src/LoL-support/Database/main.py
@@ -2,15 +2,6 @@
from config import config
# Connect to the PostgreSQL database
-"""conn = psycopg2.connect(
- host="127.0.0.1",
- port="5432",
- dbname="league",
- user="postgres",
- password="0809"
-)
-"""
-
def connect():
connection = None
try:
diff --git a/src/LoL-support/Leage_workspace.code-workspace b/src/LoL-support/Leage_workspace.code-workspace
new file mode 100755
index 0000000..407c760
--- /dev/null
+++ b/src/LoL-support/Leage_workspace.code-workspace
@@ -0,0 +1,8 @@
+{
+ "folders": [
+ {
+ "path": "../.."
+ }
+ ],
+ "settings": {}
+}
\ No newline at end of file
diff --git a/src/LoL-support/archive/adc_names.json b/src/LoL-support/archive/adc_names.json
old mode 100644
new mode 100755
diff --git a/src/LoL-support/archive/jungler_names.json b/src/LoL-support/archive/jungler_names.json
old mode 100644
new mode 100755
diff --git a/src/LoL-support/archive/mid_names.json b/src/LoL-support/archive/mid_names.json
old mode 100644
new mode 100755
diff --git a/src/LoL-support/archive/step5-regex.py b/src/LoL-support/archive/step5-regex.py
old mode 100644
new mode 100755
diff --git a/src/LoL-support/champion_names_test.json b/src/LoL-support/champion_names_test.json
old mode 100644
new mode 100755
diff --git a/src/LoL-support/counters_processed.json b/src/LoL-support/counters_processed.json
old mode 100644
new mode 100755
index 43dbd0d..ae49a71
--- a/src/LoL-support/counters_processed.json
+++ b/src/LoL-support/counters_processed.json
@@ -1,166 +1,86 @@
[
- {
- "champion_name": "Taric",
- "counters": "Soraka(55.26%), Rell(52.91%), Vel'Koz(52.75%), Janna(50.88%), Zilean(50.55%), Rakan(49.62%), Maokai(49.58%), Nami(49.42%), Xerath(49.28%), Senna(49.24%)"
- },
- {
- "champion_name": "Janna",
- "counters": "Senna(50.79%), Sona(50.1%), Soraka(50.01%), Seraphine(49.87%), Milio(49.71%), Neeko(49.67%), Rell(49.5%), Bard(49.44%), Rakan(49.42%), Blitzcrank(49.4%)"
- },
{
"champion_name": "Maokai",
- "counters": "Shaco(58.18%), Braum(54.8%), Sona(53.99%), Janna(52.72%), Renata Glasc(52.39%), Zyra(51.46%), Alistar(51.45%), Soraka(50.8%), Taric(50.42%), Neeko(50.29%)"
- },
- {
- "champion_name": "Bard",
- "counters": "Taric(52.2%), Janna(50.56%), Maokai(50.28%), Zilean(49.95%), Senna(49.94%), Blitzcrank(49.89%), Renata Glasc(49.52%), Soraka(49.45%), Rell(49.3%), Pyke(49.06%)"
- },
- {
- "champion_name": "Rakan",
- "counters": "Senna(52.31%), Bard(51.42%), Soraka(51.41%), Neeko(51.37%), Renata Glasc(51.09%), Sona(50.93%), Maokai(50.87%), Rell(50.69%), Zilean(50.64%), Janna(50.58%)"
- },
- {
- "champion_name": "Sona",
- "counters": "Taric(56.83%), Leona(55.04%), Seraphine(53.49%), Blitzcrank(52.56%), Senna(51.71%), Bard(51.4%), Nami(50.59%), Milio(50.43%), Zilean(50.26%), Pyke(50.19%)"
- },
- {
- "champion_name": "Senna",
- "counters": "Pyke(52.31%), Blitzcrank(52.25%), Xerath(51.66%), Maokai(51.62%), Zyra(51.1%), Taric(50.76%), Leona(50.22%), Bard(50.06%), Lux(49.9%), Rell(49.8%)"
+ "counters": "Taric(50.48%), Alistar(49.83%), Braum(49.49%), Leona(49.02%), Janna(48.98%), Renata Glasc(48.48%), Rell(47.97%), Shaco(47.18%), Camille(47.04%), Zyra(47.02%)"
},
{
- "champion_name": "Rell",
- "counters": "Poppy(53.79%), Seraphine(53.57%), Soraka(52.57%), Zilean(52.54%), Alistar(52.45%), Shaco(51.5%), Maokai(50.89%), Sona(50.75%), Bard(50.7%), Janna(50.5%)"
- },
- {
- "champion_name": "Neeko",
- "counters": "Vel'Koz(60.45%), Sona(53.26%), Bard(52.61%), Brand(52.38%), Taric(52.17%), Alistar(51%), Shaco(50.68%), Senna(50.42%), Renata Glasc(50.38%), Morgana(50.35%)"
+ "champion_name": "Janna",
+ "counters": "Camille(53.25%), Sona(51.89%), Blitzcrank(51.2%), Maokai(51.02%), Braum(49.67%), Pyke(49.38%), Renata Glasc(49.3%), Senna(49.25%), Ashe(49.07%), Nami(48.64%)"
},
{
- "champion_name": "Soraka",
- "counters": "Blitzcrank(54.17%), Neeko(51.31%), Sona(51.25%), Zyra(51.13%), Vel'Koz(51.05%), Bard(50.55%), Senna(50.38%), Yuumi(50.06%), Janna(49.99%), Pyke(49.98%)"
+ "champion_name": "Taric",
+ "counters": "Swain(57.63%), Sona(57.32%), Janna(54.88%), Nami(54.27%), Lux(53.54%), Seraphine(53.45%), Bard(52.97%), Soraka(52.32%), Thresh(52.21%), Braum(51.67%)"
},
{
"champion_name": "Blitzcrank",
- "counters": "Taric(56.85%), Rakan(52.88%), Leona(52.47%), Braum(52.28%), Renata Glasc(51.05%), Maokai(51.04%), Zyra(50.98%), Rell(50.78%), Alistar(50.73%), Zilean(50.7%)"
+ "counters": "Pantheon(54.85%), Leona(54.08%), Maokai(53.66%), Braum(53.61%), Rakan(53.26%), Taric(53.13%), Alistar(52.33%), Shaco(51.5%), Rell(50.9%), Zilean(50.57%)"
},
{
- "champion_name": "Zyra",
- "counters": "Sona(53.56%), Pyke(53.25%), Heimerdinger(53.11%), Bard(53.07%), Leona(51.91%), Janna(51.88%), Shaco(51.56%), Taric(51.43%), Rakan(50.98%), Nautilus(50.75%)"
+ "champion_name": "Camille",
+ "counters": "Taric(58.76%), Renata Glasc(58.12%), Braum(57.32%), Leona(54.48%), Brand(54.2%), Bard(53.76%), Zac(53.03%), Maokai(52.96%), Tahm Kench(52.94%), Zilean(52.78%)"
},
{
- "champion_name": "Pyke",
- "counters": "Rakan(53.38%), Maokai(52.78%), Neeko(52.1%), Rell(52.08%), Taric(51.82%), Janna(51.79%), Blitzcrank(51.44%), Alistar(50.94%), Bard(50.94%), Thresh(50.49%)"
+ "champion_name": "Zac",
+ "counters": "Pantheon(61.36%), Taric(58.82%), Zilean(58.49%), Rell(56.82%), Bard(56.55%), Morgana(55.93%), Janna(54.44%), Maokai(53.44%), Milio(53.25%), Thresh(53.18%)"
},
{
- "champion_name": "Zilean",
- "counters": "Janna(53.27%), Shaco(53.02%), Senna(51.99%), Neeko(51.67%), Maokai(51.41%), Xerath(51.07%), Milio(50.67%), Sylas(50.34%), Soraka(50.28%), Vel'Koz(50.23%)"
+ "champion_name": "Bard",
+ "counters": "Maokai(56.1%), Renata Glasc(53.77%), Janna(53.45%), Pyke(51.79%), Morgana(51.04%), Nami(50.92%), Pantheon(50.72%), Blitzcrank(50.43%), Brand(50.34%), Senna(50.21%)"
},
{
- "champion_name": "Braum",
- "counters": "Zilean(55.95%), Soraka(54.2%), Bard(53.76%), Rell(53.74%), Taric(53.44%), Sona(53.2%), Senna(53.06%), Rakan(52.88%), Janna(52.49%), Renata Glasc(52.31%)"
+ "champion_name": "Sona",
+ "counters": "Zilean(61.68%), Rell(59.62%), Leona(57.21%), Neeko(55.71%), Thresh(55.03%), Maokai(54.7%), Bard(53.59%), Braum(51.81%), Blitzcrank(51.79%), Seraphine(51.2%)"
},
{
- "champion_name": "Vel'Koz",
- "counters": "Pyke(55.14%), Zyra(54.76%), Seraphine(54.15%), Blitzcrank(53.89%), Janna(53.62%), Bard(53.22%), Sona(52.97%), Nami(52.1%), Maokai(51.85%), Renata Glasc(51.64%)"
+ "champion_name": "Zyra",
+ "counters": "Taric(54.73%), Leona(54.05%), Sona(53.31%), Maokai(52.98%), Renata Glasc(52.84%), Pyke(52.6%), Zac(52.59%), Janna(52.42%), Zilean(51.88%), Yuumi(51.63%)"
},
{
- "champion_name": "Thresh",
- "counters": "Taric(53.39%), Janna(52.43%), Brand(52.12%), Rakan(52.02%), Neeko(51.75%), Sona(51.47%), Maokai(51.34%), Vel'Koz(51.27%), Bard(51.24%), Renata Glasc(51.24%)"
+ "champion_name": "Nautilus",
+ "counters": "Taric(60.99%), Leona(57.54%), Braum(56.73%), Renata Glasc(55.96%), Alistar(55.65%), Rell(55.38%), Camille(54.74%), Pantheon(54.53%), Swain(54.35%), Maokai(53.72%)"
},
{
"champion_name": "Milio",
- "counters": "Taric(54.79%), Blitzcrank(53.66%), Braum(53.22%), Vel'Koz(53.08%), Senna(52.78%), Bard(52.75%), Neeko(52.17%), Rakan(51.91%), Soraka(51.52%), Thresh(51.33%)"
- },
- {
- "champion_name": "Alistar",
- "counters": "Sona(54.51%), Janna(54.04%), Swain(53.24%), Vel'Koz(52.78%), Senna(52.64%), Zilean(52.39%), Soraka(52.22%), Seraphine(51.97%), Xerath(51.86%), Milio(51.85%)"
- },
- {
- "champion_name": "Zac",
- "counters": "Poppy(67.39%), Maokai(57.14%), Neeko(56.72%), Swain(56%), Vel'Koz(54.1%), Taric(53.85%), Alistar(53.74%), Ashe(53.49%), Rakan(53.42%), Zilean(53.26%)"
+ "counters": "Blitzcrank(56.32%), Braum(55.45%), Maokai(54.37%), Camille(53.86%), Janna(53.43%), Thresh(53.37%), Zilean(53.04%), Tahm Kench(53.04%), Vel'Koz(52.98%), Leona(52.96%)"
},
{
- "champion_name": "Leona",
- "counters": "Taric(56.7%), Janna(54.1%), Bard(54.03%), Shaco(53.67%), Maokai(53.55%), Rell(53.22%), Soraka(53.11%), Lux(53.09%), Morgana(53.02%), Braum(51.72%)"
+ "champion_name": "Nami",
+ "counters": "Morgana(56.08%), Blitzcrank(55.61%), Maokai(55.57%), Vel'Koz(54.47%), Rakan(54.44%), Neeko(54.21%), Rell(53.9%), Nautilus(53.32%), Thresh(53.28%), Senna(53.17%)"
},
{
"champion_name": "Xerath",
- "counters": "Sylas(55%), Maokai(53.98%), Sona(53.77%), Janna(53.15%), Pyke(52.84%), Soraka(52.33%), Rakan(52.02%), Leona(51.76%), Zyra(51.72%), Nami(51.6%)"
+ "counters": "Blitzcrank(58.63%), Neeko(56.43%), Sona(56.36%), Maokai(56.26%), Pyke(56.15%), Leona(56.02%), Braum(55.06%), Rakan(54.83%), Nautilus(54.65%), Janna(53.86%)"
},
{
- "champion_name": "Nami",
- "counters": "Maokai(56.37%), Neeko(53.14%), Blitzcrank(53.03%), Rakan(52.58%), Zyra(52.29%), Soraka(51.92%), Milio(51.67%), Pyke(51.56%), Janna(51.44%), Bard(51.33%)"
+ "champion_name": "Swain",
+ "counters": "Zac(58.97%), Sona(58.67%), Neeko(57.41%), Nami(57.14%), Milio(56.44%), Pantheon(55.95%), Zyra(55.49%), Morgana(55.24%), Leona(55%), Camille(54.79%)"
},
{
- "champion_name": "Shaco",
- "counters": "Sona(60.95%), Swain(60.22%), Taric(59.68%), Heimerdinger(59.02%), Zac(56.14%), Janna(55.96%), Milio(54.49%), Pyke(54.23%), Rakan(53.2%), Bard(52.59%)"
+ "champion_name": "Lulu",
+ "counters": "Vel'Koz(57.35%), Blitzcrank(56.23%), Zilean(55.78%), Tahm Kench(55.43%), Renata Glasc(54.86%), Rell(54.7%), Janna(54.6%), Maokai(54.51%), Braum(53.79%), Ashe(53.65%)"
},
{
- "champion_name": "Heimerdinger",
- "counters": "Maokai(61.64%), Neeko(56.82%), Seraphine(55.95%), Xerath(54.86%), Bard(54.55%), Leona(54.4%), Rell(53.15%), Rakan(52.67%), Lulu(52.56%), Janna(52.56%)"
+ "champion_name": "Ashe",
+ "counters": "Camille(58.72%), Maokai(57.34%), Pyke(56.14%), Blitzcrank(55.88%), Nautilus(55.46%), Taric(55.41%), Vel'Koz(54.55%), Zyra(54.13%), Soraka(53.87%), Xerath(53.83%)"
},
{
"champion_name": "Sylas",
- "counters": "Sona(60%), Janna(57.91%), Vel'Koz(57.75%), Braum(57.52%), Morgana(57.5%), Taric(55.56%), Rell(54.55%), Rakan(53.89%), Blitzcrank(53.74%), Thresh(53.64%)"
- },
- {
- "champion_name": "Tahm Kench",
- "counters": "Taric(62.22%), Braum(61.11%), Bard(57.4%), Zyra(57.27%), Pyke(55.38%), Maokai(54.9%), Brand(54.67%), Lulu(54.55%), Renata Glasc(54.17%), Rakan(54.12%)"
- },
- {
- "champion_name": "Amumu",
- "counters": "Maokai(66.07%), Zyra(61.68%), Rell(58.9%), Rakan(58.55%), Janna(58.5%), Neeko(57.14%), Taric(56.1%), Braum(55.41%), Bard(54%), Leona(53.91%)"
- },
- {
- "champion_name": "Twitch",
- "counters": "Zilean(61.97%), Taric(60.47%), Leona(57.92%), Soraka(57.87%), Shaco(57.38%), Nami(57.14%), Rakan(56.34%), Sylas(55.56%), Brand(55.26%), Pyke(54.65%)"
- },
- {
- "champion_name": "Nautilus",
- "counters": "Taric(55.98%), Rell(55.66%), Renata Glasc(55.13%), Sylas(55.09%), Braum(54.08%), Rakan(54.06%), Vel'Koz(53.45%), Swain(53.09%), Neeko(52.97%), Alistar(52.87%)"
+ "counters": "Taric(70%), Shaco(70%), Pantheon(64.86%), Neeko(63.16%), Thresh(58.58%), Lulu(58.52%), Morgana(58.06%), Janna(57.66%), Leona(57.58%), Bard(57.45%)"
},
{
- "champion_name": "Ashe",
- "counters": "Pyke(56.98%), Sylas(54.95%), Blitzcrank(54.83%), Heimerdinger(54.8%), Rell(54.67%), Nautilus(54.35%), Bard(53.44%), Milio(53.1%), Rakan(52.69%), Brand(52.5%)"
+ "champion_name": "Neeko",
+ "counters": "Zilean(60.66%), Taric(58.7%), Zac(57.78%), Leona(57.76%), Pantheon(57.14%), Janna(56.74%), Blitzcrank(56.38%), Bard(55.85%), Camille(55.41%), Rell(55.19%)"
},
{
"champion_name": "Lux",
- "counters": "Blitzcrank(56.57%), Maokai(54.94%), Zyra(54.66%), Shaco(54.45%), Vel'Koz(54.1%), Sylas(53.87%), Zilean(53.51%), Pyke(53.5%), Xerath(52.75%), Nami(52.72%)"
- },
- {
- "champion_name": "Brand",
- "counters": "Sona(58.11%), Maokai(57.24%), Xerath(57.03%), Janna(56.31%), Pyke(56.01%), Zyra(55.13%), Leona(54.96%), Zilean(54.92%), Soraka(54.73%), Senna(54.19%)"
- },
- {
- "champion_name": "Seraphine",
- "counters": "Sylas(58.33%), Maokai(57.24%), Zilean(56.09%), Bard(55.97%), Pyke(54.69%), Soraka(53.72%), Blitzcrank(53.61%), Yuumi(53.6%), Milio(52.99%), Thresh(52.66%)"
- },
- {
- "champion_name": "Lulu",
- "counters": "Taric(56.84%), Zilean(54.43%), Neeko(54.04%), Senna(54.04%), Sona(53.78%), Zyra(53.7%), Maokai(53.46%), Blitzcrank(52.96%), Bard(52.89%), Thresh(52.88%)"
- },
- {
- "champion_name": "Morgana",
- "counters": "Zyra(56.73%), Senna(56.35%), Rell(56.29%), Janna(55.6%), Milio(55.2%), Vel'Koz(55.08%), Nami(54.67%), Taric(54.31%), Karma(54.24%), Maokai(53.2%)"
- },
- {
- "champion_name": "Karma",
- "counters": "Pyke(57.3%), Maokai(55.33%), Zyra(54.97%), Blitzcrank(54.92%), Rakan(54.52%), Janna(54.36%), Sona(54.33%), Nautilus(54.21%), Soraka(53.61%), Neeko(52.83%)"
- },
- {
- "champion_name": "Swain",
- "counters": "Zilean(57.77%), Sona(57.32%), Vel'Koz(57.25%), Janna(56.41%), Brand(55.87%), Yuumi(55.78%), Zyra(55.78%), Xerath(55.48%), Soraka(55.34%), Senna(55.32%)"
- },
- {
- "champion_name": "Pantheon",
- "counters": "Neeko(59.74%), Maokai(58.43%), Karma(57.22%), Bard(57.14%), Swain(56.25%), Rakan(55.92%), Brand(55.83%), Soraka(55.74%), Sona(55.56%), Janna(55.42%)"
+ "counters": "Camille(62.93%), Pantheon(60%), Pyke(58.92%), Bard(57.24%), Shaco(56.3%), Blitzcrank(55.42%), Maokai(55.39%), Leona(55.08%), Alistar(54.69%), Zilean(54.61%)"
},
{
"champion_name": "Yuumi",
- "counters": "Maokai(61.28%), Rell(59.85%), Taric(57.47%), Alistar(57.31%), Nautilus(56.64%), Rakan(55.78%), Pyke(55.71%), Leona(55.63%), Sylas(55.27%), Thresh(55.23%)"
+ "counters": "Rell(62.65%), Braum(58.82%), Blitzcrank(58.05%), Leona(57.77%), Pyke(57.61%), Taric(57.46%), Swain(56.85%), Rakan(56.72%), Maokai(56.12%), Janna(55.85%)"
},
{
"champion_name": "Hwei",
- "counters": "Taric(66.91%), Sona(66.55%), Blitzcrank(65.66%), Janna(65.3%), Brand(65.18%), Pyke(63.85%), Xerath(63.02%), Vel'Koz(62.71%), Morgana(62.31%), Milio(62.01%)"
+ "counters": "Camille(63.76%), Pyke(60.47%), Maokai(58.72%), Xerath(58.7%), Rakan(58.47%), Taric(58.43%), Swain(58.1%), Zilean(57.69%), Vel'Koz(57.69%), Janna(57.32%)"
}
]
\ No newline at end of file
diff --git a/src/LoL-support/data.json b/src/LoL-support/data.json
old mode 100644
new mode 100755
index 78e746e..8cdf817
--- a/src/LoL-support/data.json
+++ b/src/LoL-support/data.json
@@ -1,46 +1,46 @@
{
"div_elements": [
- "
",
- "",
- "",
- "",
- "5
S+
51.78%
12.8%
7.9%
133,770
",
- "",
- "7
S+
51.36%
11.0%
10.8%
114,521
",
- "",
- "",
- "",
- "11
S+
51.19%
8.8%
34.3%
92,387
",
- "",
- "13
S+
51.02%
7.6%
16.6%
79,272
",
- "",
- "",
- "",
- "",
- "18
S
50.23%
13.3%
5.1%
138,739
",
- "",
- "",
- "",
- "",
- "",
- "",
- "",
- "",
- "",
- "",
- "",
- "",
- "31
D
49.09%
10.9%
16.7%
114,188
",
- "",
- "",
- "",
- "",
- "",
- "",
- "",
- "",
- "",
- "",
- ""
+ "1
S+
53.96%
12.7%
29.6%
69,959
",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "11
S+
51.20%
8.5%
18.2%
46,810
",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "21
A
49.97%
11.0%
18.8%
60,568
",
+ "",
+ "",
+ "",
+ "25
D
49.05%
11.0%
10.6%
60,829
",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ ""
]
}
\ No newline at end of file
diff --git a/src/LoL-support/final_data.json b/src/LoL-support/final_data.json
old mode 100644
new mode 100755
index 019d059..b61a654
--- a/src/LoL-support/final_data.json
+++ b/src/LoL-support/final_data.json
@@ -1,1233 +1,934 @@
[
{
- "champion_name": "Taric",
- "pick_rate": "1.4%",
- "counters": {
- "Soraka": "55.26%",
- "Rell": "52.91%",
- "Vel'Koz": "52.75%",
- "Janna": "50.88%",
- "Zilean": "50.55%",
- "Rakan": "49.62%",
- "Maokai": "49.58%",
- "Nami": "49.42%",
- "Xerath": "49.28%",
- "Senna": "49.24%"
+ "champion_name": "Maokai",
+ "pick_rate": "12.7%",
+ "counters": {
+ "Taric": "50.48%",
+ "Alistar": "49.83%",
+ "Braum": "49.49%",
+ "Leona": "49.02%",
+ "Janna": "48.98%",
+ "Renata Glasc": "48.48%",
+ "Rell": "47.97%",
+ "Shaco": "47.18%",
+ "Camille": "47.04%",
+ "Zyra": "47.02%"
},
- "synergy": {
- "Karthus": "70.83%",
- "Yasuo": "68.91%",
- "Lee Sin": "68.18%",
- "Master Yi": "59.26%",
- "Kog'Maw": "58.82%",
- "Sivir": "58.62%",
- "Varus": "56.63%",
- "Swain": "56.45%",
- "Seraphine": "56.41%",
- "Kalista": "55.94%",
- "Ziggs": "55.00%",
- "Vayne": "54.01%",
- "Jinx": "52.38%",
- "Lucian": "51.91%",
- "Kai'Sa": "51.81%",
- "Ashe": "51.67%",
- "Miss Fortune": "50.94%",
- "Ezreal": "50.20%",
- "Twitch": "50.00%"
- }
+ "synergy": [
+ "no data"
+ ]
},
{
"champion_name": "Janna",
- "pick_rate": "9.6%",
- "counters": {
- "Senna": "50.79%",
- "Sona": "50.1%",
- "Soraka": "50.01%",
- "Seraphine": "49.87%",
- "Milio": "49.71%",
- "Neeko": "49.67%",
- "Rell": "49.5%",
- "Bard": "49.44%",
- "Rakan": "49.42%",
- "Blitzcrank": "49.4%"
- },
- "synergy": {
- "Seraphine": "58.66%",
- "Vayne": "55.43%",
- "Yasuo": "54.91%",
- "Ziggs": "54.85%",
- "Jinx": "53.96%",
- "Ashe": "53.85%",
- "Tristana": "53.63%",
- "Kalista": "53.47%",
- "Kog'Maw": "52.89%",
- "Karthus": "52.83%",
- "Jhin": "52.65%",
- "Ezreal": "52.58%",
- "Lucian": "52.51%",
- "Draven": "52.25%",
- "Twitch": "52.00%",
- "Samira": "51.88%",
- "Varus": "51.73%",
- "Zeri": "51.64%",
- "Caitlyn": "51.53%",
- "Kai'Sa": "50.95%"
+ "pick_rate": "10.7%",
+ "counters": {
+ "Camille": "53.25%",
+ "Sona": "51.89%",
+ "Blitzcrank": "51.2%",
+ "Maokai": "51.02%",
+ "Braum": "49.67%",
+ "Pyke": "49.38%",
+ "Renata Glasc": "49.3%",
+ "Senna": "49.25%",
+ "Ashe": "49.07%",
+ "Nami": "48.64%"
+ },
+ "synergy": {
+ "Nilah": "57.38%",
+ "Twitch": "57.02%",
+ "Senna": "56.68%",
+ "Seraphine": "55.59%",
+ "Miss Fortune": "54.71%",
+ "Zeri": "54.30%",
+ "Tristana": "54.22%",
+ "Draven": "54.22%",
+ "Twisted Fate": "53.57%",
+ "Samira": "53.57%",
+ "Jhin": "53.03%",
+ "Lucian": "52.84%",
+ "Karthus": "52.73%",
+ "Ezreal": "52.60%",
+ "Yasuo": "52.58%",
+ "Kalista": "52.35%",
+ "Vayne": "51.97%",
+ "Ashe": "51.80%",
+ "Sivir": "51.10%",
+ "Caitlyn": "51.05%",
+ "Smolder": "50.23%",
+ "Aphelios": "50.23%"
}
},
{
- "champion_name": "Maokai",
- "pick_rate": "1.6%",
+ "champion_name": "Taric",
+ "pick_rate": "1.5%",
"counters": {
- "Shaco": "58.18%",
- "Braum": "54.8%",
- "Sona": "53.99%",
- "Janna": "52.72%",
- "Renata Glasc": "52.39%",
- "Zyra": "51.46%",
- "Alistar": "51.45%",
- "Soraka": "50.8%",
- "Taric": "50.42%",
- "Neeko": "50.29%"
- },
- "synergy": {
- "Xayah": "60.00%",
- "Ziggs": "58.33%",
- "Karthus": "57.89%",
- "Kai'Sa": "53.95%",
- "Vayne": "53.51%",
- "Sivir": "52.63%",
- "Jhin": "52.46%",
- "Aphelios": "52.46%",
- "Draven": "52.38%",
- "Ezreal": "52.29%",
- "Samira": "51.81%",
- "Varus": "51.53%",
- "Zeri": "51.02%",
- "Twitch": "50.00%",
- "Ashe": "50.00%",
- "Hwei": "50.00%"
+ "Swain": "57.63%",
+ "Sona": "57.32%",
+ "Janna": "54.88%",
+ "Nami": "54.27%",
+ "Lux": "53.54%",
+ "Seraphine": "53.45%",
+ "Bard": "52.97%",
+ "Soraka": "52.32%",
+ "Thresh": "52.21%",
+ "Braum": "51.67%"
+ },
+ "synergy": {
+ "Vayne": "60.16%",
+ "Ashe": "58.46%",
+ "Yasuo": "58.33%",
+ "Twisted Fate": "57.89%",
+ "Pyke": "57.14%",
+ "Senna": "56.59%",
+ "Nilah": "56.32%",
+ "Kalista": "55.13%",
+ "Jinx": "54.39%",
+ "Aphelios": "53.57%",
+ "Seraphine": "53.13%",
+ "Zeri": "52.54%",
+ "Twitch": "52.05%",
+ "Smolder": "51.89%",
+ "Ezreal": "51.67%",
+ "Kai'Sa": "50.59%",
+ "Sivir": "50.00%",
+ "Lucian": "50.00%",
+ "Dr. Mundo": "50.00%",
+ "Xayah": "50.00%",
+ "Karthus": "50.00%"
}
},
{
- "champion_name": "Bard",
- "pick_rate": "6.1%",
- "counters": {
- "Taric": "52.2%",
- "Janna": "50.56%",
- "Maokai": "50.28%",
- "Zilean": "49.95%",
- "Senna": "49.94%",
- "Blitzcrank": "49.89%",
- "Renata Glasc": "49.52%",
- "Soraka": "49.45%",
- "Rell": "49.3%",
- "Pyke": "49.06%"
- },
- "synergy": {
- "Nilah": "59.77%",
- "Vayne": "55.15%",
- "Caitlyn": "55.12%",
- "Twitch": "55.06%",
- "Ziggs": "54.37%",
- "Tristana": "53.92%",
- "Karthus": "53.62%",
- "Seraphine": "53.05%",
- "Lucian": "52.97%",
- "Ezreal": "52.51%",
- "Ashe": "52.44%",
- "Jhin": "51.89%",
- "Swain": "51.69%",
- "Jinx": "51.34%",
- "Zeri": "50.76%"
- }
+ "champion_name": "Braum",
+ "pick_rate": "3.8%",
+ "counters": "no info",
+ "synergy": [
+ "no data"
+ ]
},
{
- "champion_name": "Rakan",
- "pick_rate": "12.8%",
- "counters": {
- "Senna": "52.31%",
- "Bard": "51.42%",
- "Soraka": "51.41%",
- "Neeko": "51.37%",
- "Renata Glasc": "51.09%",
- "Sona": "50.93%",
- "Maokai": "50.87%",
- "Rell": "50.69%",
- "Zilean": "50.64%",
- "Janna": "50.58%"
- },
- "synergy": {
- "Lucian": "56.02%",
- "Jinx": "54.48%",
- "Twitch": "54.35%",
- "Seraphine": "54.17%",
- "Ziggs": "54.08%",
- "Vayne": "54.03%",
- "Sivir": "53.73%",
- "Jhin": "52.40%",
- "Yasuo": "51.97%",
- "Karthus": "51.90%",
- "Ezreal": "51.77%",
- "Ashe": "51.55%",
- "Miss Fortune": "50.65%",
- "Nilah": "50.61%",
- "Varus": "50.35%",
- "Draven": "50.12%"
+ "champion_name": "Blitzcrank",
+ "pick_rate": "8.1%",
+ "counters": {
+ "Pantheon": "54.85%",
+ "Leona": "54.08%",
+ "Maokai": "53.66%",
+ "Braum": "53.61%",
+ "Rakan": "53.26%",
+ "Taric": "53.13%",
+ "Alistar": "52.33%",
+ "Shaco": "51.5%",
+ "Rell": "50.9%",
+ "Zilean": "50.57%"
+ },
+ "synergy": {
+ "Seraphine": "65.22%",
+ "Karthus": "57.78%",
+ "Draven": "56.57%",
+ "Vayne": "55.39%",
+ "Varus": "54.74%",
+ "Jinx": "54.30%",
+ "Twisted Fate": "54.17%",
+ "Tristana": "53.18%",
+ "Sivir": "53.06%",
+ "Senna": "52.85%",
+ "Aphelios": "52.14%",
+ "Kai'Sa": "51.65%",
+ "Lucian": "50.78%",
+ "Nilah": "50.67%",
+ "Twitch": "50.00%",
+ "Ashe": "50.00%",
+ "Zeri": "50.00%"
}
},
{
- "champion_name": "Sona",
- "pick_rate": "2.4%",
+ "champion_name": "Camille",
+ "pick_rate": "2.3%",
"counters": {
- "Taric": "56.83%",
- "Leona": "55.04%",
- "Seraphine": "53.49%",
- "Blitzcrank": "52.56%",
- "Senna": "51.71%",
- "Bard": "51.4%",
- "Nami": "50.59%",
- "Milio": "50.43%",
- "Zilean": "50.26%",
- "Pyke": "50.19%"
- },
- "synergy": {
- "Yasuo": "64.52%",
- "Tristana": "60.29%",
- "Ziggs": "58.51%",
- "Samira": "57.45%",
- "Sivir": "56.60%",
- "Vayne": "56.55%",
- "Nilah": "55.88%",
- "Kog'Maw": "53.49%",
- "Lucian": "53.04%",
- "Xayah": "52.90%",
- "Seraphine": "52.76%",
- "Ezreal": "52.71%",
- "Miss Fortune": "52.23%",
- "Varus": "51.05%",
- "Ashe": "50.93%",
- "Caitlyn": "50.81%",
- "Twitch": "50.21%"
+ "Taric": "58.76%",
+ "Renata Glasc": "58.12%",
+ "Braum": "57.32%",
+ "Leona": "54.48%",
+ "Brand": "54.2%",
+ "Bard": "53.76%",
+ "Zac": "53.03%",
+ "Maokai": "52.96%",
+ "Tahm Kench": "52.94%",
+ "Zilean": "52.78%"
+ },
+ "synergy": {
+ "Twitch": "62.67%",
+ "Sivir": "58.33%",
+ "Zeri": "56.47%",
+ "Tristana": "56.20%",
+ "Senna": "55.81%",
+ "Xayah": "55.26%",
+ "Aphelios": "53.62%",
+ "Nilah": "53.13%",
+ "Seraphine": "52.94%",
+ "Varus": "52.89%",
+ "Karthus": "52.63%",
+ "Ashe": "52.50%",
+ "Smolder": "52.34%",
+ "Jhin": "52.09%",
+ "Draven": "52.06%",
+ "Ziggs": "52.00%",
+ "Twisted Fate": "50.94%",
+ "Vayne": "50.92%",
+ "Lucian": "50.35%",
+ "Swain": "50.00%"
}
},
{
- "champion_name": "Senna",
- "pick_rate": "11.0%",
- "counters": {
- "Pyke": "52.31%",
- "Blitzcrank": "52.25%",
- "Xerath": "51.66%",
- "Maokai": "51.62%",
- "Zyra": "51.1%",
- "Taric": "50.76%",
- "Leona": "50.22%",
- "Bard": "50.06%",
- "Lux": "49.9%",
- "Rell": "49.8%"
- },
+ "champion_name": "Leona",
+ "pick_rate": "4.4%",
+ "counters": "no info",
"synergy": {
- "Kalista": "57.48%",
- "Nilah": "56.48%",
- "Karthus": "56.18%",
- "Zeri": "55.11%",
- "Seraphine": "55.06%",
- "Miss Fortune": "54.98%",
- "Cho'Gath": "54.69%",
- "Vayne": "54.60%",
- "Ashe": "54.54%",
- "Tahm Kench": "54.51%",
- "Yasuo": "54.49%",
- "Brand": "54.26%",
- "Twitch": "54.15%",
- "Swain": "53.87%",
- "Jhin": "52.87%",
- "Ziggs": "52.59%",
- "Sivir": "52.21%",
- "Ezreal": "51.36%",
- "Tristana": "51.36%",
- "Varus": "50.75%"
+ "Swain": "68.09%",
+ "Senna": "60.41%",
+ "Jinx": "59.02%",
+ "Seraphine": "58.90%",
+ "Ashe": "57.67%",
+ "Aphelios": "56.67%",
+ "Nilah": "56.52%",
+ "Vayne": "55.20%",
+ "Lucian": "54.63%",
+ "Smolder": "54.42%",
+ "Miss Fortune": "53.39%",
+ "Karthus": "53.06%",
+ "Xayah": "51.61%",
+ "Ezreal": "51.04%",
+ "Kalista": "50.59%",
+ "Twisted Fate": "50.31%"
}
},
{
"champion_name": "Rell",
- "pick_rate": "4.9%",
- "counters": {
- "Poppy": "53.79%",
- "Seraphine": "53.57%",
- "Soraka": "52.57%",
- "Zilean": "52.54%",
- "Alistar": "52.45%",
- "Shaco": "51.5%",
- "Maokai": "50.89%",
- "Sona": "50.75%",
- "Bard": "50.7%",
- "Janna": "50.5%"
- },
+ "pick_rate": "4.1%",
+ "counters": "no info",
"synergy": {
- "Ziggs": "61.31%",
- "Karthus": "60.18%",
- "Seraphine": "59.02%",
- "Tristana": "58.16%",
- "Lucian": "57.30%",
- "Twitch": "56.30%",
- "Miss Fortune": "54.44%",
- "Ashe": "53.88%",
- "Sivir": "53.49%",
- "Jinx": "52.83%",
- "Draven": "52.25%",
- "Xayah": "52.10%",
- "Ezreal": "52.08%",
- "Kai'Sa": "51.33%",
- "Samira": "51.15%",
- "Zeri": "50.35%",
- "Yasuo": "50.25%",
- "Aphelios": "50.21%"
+ "Xayah": "58.33%",
+ "Twisted Fate": "56.72%",
+ "Twitch": "56.48%",
+ "Nilah": "56.25%",
+ "Miss Fortune": "55.88%",
+ "Zeri": "55.45%",
+ "Senna": "55.08%",
+ "Vayne": "53.65%",
+ "Jinx": "53.40%",
+ "Kalista": "52.46%",
+ "Samira": "52.46%",
+ "Aphelios": "52.17%",
+ "Draven": "52.15%",
+ "Tristana": "51.90%",
+ "Karthus": "51.43%",
+ "Ezreal": "51.32%",
+ "Jhin": "50.43%",
+ "Yasuo": "50.00%"
}
},
{
- "champion_name": "Neeko",
- "pick_rate": "1.9%",
- "counters": {
- "Vel'Koz": "60.45%",
- "Sona": "53.26%",
- "Bard": "52.61%",
- "Brand": "52.38%",
- "Taric": "52.17%",
- "Alistar": "51%",
- "Shaco": "50.68%",
- "Senna": "50.42%",
- "Renata Glasc": "50.38%",
- "Morgana": "50.35%"
- },
- "synergy": {
- "Nilah": "77.78%",
- "Yasuo": "59.46%",
- "Xayah": "59.26%",
- "Hwei": "58.82%",
- "Lucian": "56.79%",
- "Vayne": "53.85%",
- "Twitch": "53.17%",
- "Kalista": "50.75%",
- "Ezreal": "50.72%",
- "Ashe": "50.29%",
- "Quinn": "50.00%",
- "Samira": "50.00%",
- "Varus": "50.00%"
- }
+ "champion_name": "Rakan",
+ "pick_rate": "7.7%",
+ "counters": "no info",
+ "synergy": [
+ "no data"
+ ]
},
{
- "champion_name": "Soraka",
- "pick_rate": "5.5%",
- "counters": {
- "Blitzcrank": "54.17%",
- "Neeko": "51.31%",
- "Sona": "51.25%",
- "Zyra": "51.13%",
- "Vel'Koz": "51.05%",
- "Bard": "50.55%",
- "Senna": "50.38%",
- "Yuumi": "50.06%",
- "Janna": "49.99%",
- "Pyke": "49.98%"
- },
+ "champion_name": "Zilean",
+ "pick_rate": "2.8%",
+ "counters": "no info",
"synergy": {
- "Yasuo": "63.54%",
- "Swain": "59.70%",
- "Samira": "58.41%",
- "Vayne": "57.03%",
- "Ashe": "53.73%",
- "Jinx": "53.47%",
- "Ziggs": "53.38%",
- "Jhin": "53.17%",
- "Tristana": "52.80%",
- "Lucian": "52.34%",
- "Miss Fortune": "51.96%",
- "Twitch": "51.63%",
- "Draven": "50.30%"
+ "Miss Fortune": "62.03%",
+ "Twisted Fate": "61.05%",
+ "Draven": "54.63%",
+ "Swain": "54.55%",
+ "Vayne": "54.47%",
+ "Ashe": "53.97%",
+ "Caitlyn": "53.13%",
+ "Seraphine": "52.44%",
+ "Smolder": "51.37%",
+ "Xayah": "51.22%",
+ "Sivir": "50.00%"
}
},
{
- "champion_name": "Blitzcrank",
- "pick_rate": "8.8%",
- "counters": {
- "Taric": "56.85%",
- "Rakan": "52.88%",
- "Leona": "52.47%",
- "Braum": "52.28%",
- "Renata Glasc": "51.05%",
- "Maokai": "51.04%",
- "Zyra": "50.98%",
- "Rell": "50.78%",
- "Alistar": "50.73%",
- "Zilean": "50.7%"
- },
+ "champion_name": "Pyke",
+ "pick_rate": "8.5%",
+ "counters": "no info",
"synergy": {
- "Nilah": "58.59%",
- "Xayah": "55.50%",
- "Draven": "54.17%",
- "Lucian": "53.85%",
- "Ashe": "53.42%",
- "Vayne": "52.91%",
- "Twitch": "52.80%",
- "Seraphine": "52.34%",
- "Jinx": "51.90%",
- "Karthus": "51.89%",
- "Varus": "51.87%",
- "Tristana": "51.72%",
- "Miss Fortune": "51.53%",
- "Zeri": "51.27%",
- "Aphelios": "50.95%",
- "Kalista": "50.84%",
- "Jhin": "50.74%",
- "Ezreal": "50.08%",
- "Caitlyn": "50.00%"
+ "Karthus": "60.47%",
+ "Swain": "58.06%",
+ "Senna": "57.89%",
+ "Ashe": "55.02%",
+ "Twitch": "54.55%",
+ "Vayne": "53.90%",
+ "Jinx": "53.67%",
+ "Yasuo": "53.66%",
+ "Seraphine": "53.19%",
+ "Twisted Fate": "53.07%",
+ "Nilah": "53.00%",
+ "Smolder": "51.63%",
+ "Zeri": "50.56%",
+ "Ezreal": "50.22%",
+ "Draven": "50.00%"
}
},
{
- "champion_name": "Zyra",
- "pick_rate": "3.8%",
+ "champion_name": "Zac",
+ "pick_rate": "1.0%",
"counters": {
- "Sona": "53.56%",
- "Pyke": "53.25%",
- "Heimerdinger": "53.11%",
- "Bard": "53.07%",
- "Leona": "51.91%",
- "Janna": "51.88%",
- "Shaco": "51.56%",
- "Taric": "51.43%",
- "Rakan": "50.98%",
- "Nautilus": "50.75%"
- },
- "synergy": {
- "Seraphine": "55.06%",
- "Vayne": "54.84%",
- "Tristana": "54.05%",
- "Twitch": "52.96%",
- "Samira": "51.98%",
- "Jhin": "51.51%",
- "Karthus": "51.16%",
- "Draven": "50.65%",
+ "Pantheon": "61.36%",
+ "Taric": "58.82%",
+ "Zilean": "58.49%",
+ "Rell": "56.82%",
+ "Bard": "56.55%",
+ "Morgana": "55.93%",
+ "Janna": "54.44%",
+ "Maokai": "53.44%",
+ "Milio": "53.25%",
+ "Thresh": "53.18%"
+ },
+ "synergy": {
+ "Aphelios": "83.33%",
+ "Caitlyn": "66.67%",
+ "Draven": "65.52%",
+ "Senna": "64.71%",
+ "Tristana": "61.54%",
+ "Twisted Fate": "61.29%",
+ "Zeri": "58.82%",
+ "Seraphine": "56.25%",
+ "Jinx": "55.00%",
+ "Ezreal": "52.38%",
+ "Smolder": "51.58%",
+ "Kalista": "51.52%",
"Xayah": "50.00%"
}
},
{
- "champion_name": "Pyke",
- "pick_rate": "7.6%",
- "counters": {
- "Rakan": "53.38%",
- "Maokai": "52.78%",
- "Neeko": "52.1%",
- "Rell": "52.08%",
- "Taric": "51.82%",
- "Janna": "51.79%",
- "Blitzcrank": "51.44%",
- "Alistar": "50.94%",
- "Bard": "50.94%",
- "Thresh": "50.49%"
- },
- "synergy": {
- "Karthus": "61.98%",
- "Nilah": "60.43%",
- "Yasuo": "57.30%",
- "Tristana": "55.96%",
- "Twitch": "55.08%",
- "Ashe": "54.27%",
- "Swain": "53.91%",
- "Ziggs": "53.28%",
- "Seraphine": "52.82%",
- "Vayne": "52.64%",
- "Varus": "51.58%",
- "Jhin": "51.51%",
- "Ezreal": "51.49%",
- "Hwei": "51.43%",
- "Draven": "50.95%"
- }
- },
- {
- "champion_name": "Zilean",
- "pick_rate": "2.5%",
- "counters": {
- "Janna": "53.27%",
- "Shaco": "53.02%",
- "Senna": "51.99%",
- "Neeko": "51.67%",
- "Maokai": "51.41%",
- "Xerath": "51.07%",
- "Milio": "50.67%",
- "Sylas": "50.34%",
- "Soraka": "50.28%",
- "Vel'Koz": "50.23%"
- },
+ "champion_name": "Alistar",
+ "pick_rate": "4.7%",
+ "counters": "no info",
"synergy": {
- "Swain": "58.97%",
- "Hwei": "55.56%",
- "Samira": "54.12%",
- "Kog'Maw": "54.05%",
- "Twitch": "53.80%",
- "Aphelios": "53.47%",
- "Vayne": "51.88%",
- "Caitlyn": "51.79%",
- "Seraphine": "51.67%",
- "Ezreal": "51.07%",
- "Ashe": "50.12%"
+ "Karthus": "64.86%",
+ "Seraphine": "62.86%",
+ "Zeri": "60.83%",
+ "Sivir": "59.26%",
+ "Jinx": "58.42%",
+ "Yasuo": "58.09%",
+ "Tristana": "55.84%",
+ "Senna": "53.27%",
+ "Twitch": "52.99%",
+ "Draven": "51.74%",
+ "Xayah": "51.25%",
+ "Ezreal": "50.91%",
+ "Twisted Fate": "50.75%",
+ "Samira": "50.00%"
}
},
{
- "champion_name": "Braum",
- "pick_rate": "3.6%",
+ "champion_name": "Bard",
+ "pick_rate": "5.7%",
"counters": {
- "Zilean": "55.95%",
- "Soraka": "54.2%",
- "Bard": "53.76%",
- "Rell": "53.74%",
- "Taric": "53.44%",
- "Sona": "53.2%",
- "Senna": "53.06%",
- "Rakan": "52.88%",
- "Janna": "52.49%",
- "Renata Glasc": "52.31%"
- },
- "synergy": {
- "Sivir": "60.82%",
- "Twitch": "59.55%",
- "Xayah": "54.40%",
- "Varus": "52.69%",
- "Kog'Maw": "52.03%",
- "Ashe": "51.92%",
- "Jinx": "51.32%",
- "Tristana": "50.94%",
- "Vayne": "50.83%",
- "Draven": "50.75%",
- "Ezreal": "50.35%"
+ "Maokai": "56.1%",
+ "Renata Glasc": "53.77%",
+ "Janna": "53.45%",
+ "Pyke": "51.79%",
+ "Morgana": "51.04%",
+ "Nami": "50.92%",
+ "Pantheon": "50.72%",
+ "Blitzcrank": "50.43%",
+ "Brand": "50.34%",
+ "Senna": "50.21%"
+ },
+ "synergy": {
+ "Yasuo": "60.34%",
+ "Seraphine": "59.53%",
+ "Senna": "59.42%",
+ "Ziggs": "57.58%",
+ "Caitlyn": "55.43%",
+ "Xayah": "54.26%",
+ "Hwei": "54.17%",
+ "Twisted Fate": "53.66%",
+ "Jinx": "52.43%",
+ "Twitch": "51.68%",
+ "Aphelios": "51.54%",
+ "Karthus": "51.47%",
+ "Samira": "50.60%",
+ "Vayne": "50.34%"
}
},
{
- "champion_name": "Renata Glasc",
- "pick_rate": "5.0%",
+ "champion_name": "Soraka",
+ "pick_rate": "5.4%",
"counters": "no info",
"synergy": [
"no data"
]
},
{
- "champion_name": "Vel'Koz",
- "pick_rate": "1.6%",
- "counters": {
- "Pyke": "55.14%",
- "Zyra": "54.76%",
- "Seraphine": "54.15%",
- "Blitzcrank": "53.89%",
- "Janna": "53.62%",
- "Bard": "53.22%",
- "Sona": "52.97%",
- "Nami": "52.1%",
- "Maokai": "51.85%",
- "Renata Glasc": "51.64%"
- },
- "synergy": {
- "Karthus": "61.90%",
- "Vayne": "55.34%",
- "Sivir": "54.72%",
- "Ashe": "54.44%",
- "Aphelios": "53.25%",
- "Seraphine": "52.38%",
- "Jhin": "51.92%",
- "Caitlyn": "51.43%",
- "Ezreal": "51.22%",
- "Twitch": "50.39%",
- "Varus": "50.37%",
- "Tristana": "50.00%",
- "Miss Fortune": "50.00%"
+ "champion_name": "Sona",
+ "pick_rate": "2.3%",
+ "counters": {
+ "Zilean": "61.68%",
+ "Rell": "59.62%",
+ "Leona": "57.21%",
+ "Neeko": "55.71%",
+ "Thresh": "55.03%",
+ "Maokai": "54.7%",
+ "Bard": "53.59%",
+ "Braum": "51.81%",
+ "Blitzcrank": "51.79%",
+ "Seraphine": "51.2%"
+ },
+ "synergy": {
+ "Nilah": "71.43%",
+ "Tristana": "63.16%",
+ "Twisted Fate": "56.19%",
+ "Jhin": "55.34%",
+ "Kai'Sa": "55.11%",
+ "Varus": "52.63%",
+ "Aphelios": "52.00%",
+ "Twitch": "51.46%",
+ "Vayne": "51.22%",
+ "Smolder": "50.72%",
+ "Lucian": "50.00%",
+ "Samira": "50.00%"
}
},
{
"champion_name": "Thresh",
- "pick_rate": "13.3%",
- "counters": {
- "Taric": "53.39%",
- "Janna": "52.43%",
- "Brand": "52.12%",
- "Rakan": "52.02%",
- "Neeko": "51.75%",
- "Sona": "51.47%",
- "Maokai": "51.34%",
- "Vel'Koz": "51.27%",
- "Bard": "51.24%",
- "Renata Glasc": "51.24%"
- },
+ "pick_rate": "9.4%",
+ "counters": "no info",
"synergy": {
- "Tristana": "55.29%",
- "Hwei": "54.01%",
- "Draven": "53.28%",
- "Jinx": "52.84%",
- "Aphelios": "51.39%",
- "Twitch": "51.28%",
- "Kalista": "51.17%",
- "Samira": "51.08%",
- "Vayne": "51.01%",
- "Karthus": "50.91%",
- "Lucian": "50.61%",
- "Ashe": "50.26%",
- "Yasuo": "50.24%"
+ "Yasuo": "62.82%",
+ "Senna": "58.33%",
+ "Xayah": "56.52%",
+ "Sivir": "56.25%",
+ "Seraphine": "55.74%",
+ "Kalista": "54.41%",
+ "Twitch": "52.68%",
+ "Miss Fortune": "52.44%",
+ "Jinx": "52.43%",
+ "Draven": "52.37%",
+ "Aphelios": "52.12%",
+ "Smolder": "51.38%",
+ "Tristana": "51.11%",
+ "Samira": "50.59%",
+ "Varus": "50.32%",
+ "Vayne": "50.15%",
+ "Kai'Sa": "50.05%"
}
},
{
- "champion_name": "Milio",
- "pick_rate": "6.7%",
- "counters": {
- "Taric": "54.79%",
- "Blitzcrank": "53.66%",
- "Braum": "53.22%",
- "Vel'Koz": "53.08%",
- "Senna": "52.78%",
- "Bard": "52.75%",
- "Neeko": "52.17%",
- "Rakan": "51.91%",
- "Soraka": "51.52%",
- "Thresh": "51.33%"
- },
- "synergy": {
- "Kog'Maw": "53.82%",
- "Xayah": "52.76%",
- "Lucian": "51.94%",
- "Zeri": "51.56%",
- "Vayne": "51.56%",
- "Kalista": "51.56%",
- "Ashe": "51.48%",
- "Draven": "50.69%",
- "Aphelios": "50.42%",
- "Twitch": "50.00%"
- }
+ "champion_name": "Renata Glasc",
+ "pick_rate": "2.4%",
+ "counters": "no info",
+ "synergy": [
+ "no data"
+ ]
},
{
- "champion_name": "Alistar",
- "pick_rate": "5.8%",
- "counters": {
- "Sona": "54.51%",
- "Janna": "54.04%",
- "Swain": "53.24%",
- "Vel'Koz": "52.78%",
- "Senna": "52.64%",
- "Zilean": "52.39%",
- "Soraka": "52.22%",
- "Seraphine": "51.97%",
- "Xerath": "51.86%",
- "Milio": "51.85%"
- },
- "synergy": {
- "Seraphine": "59.43%",
- "Ziggs": "57.86%",
- "Sivir": "55.20%",
- "Hwei": "54.65%",
- "Zeri": "53.37%",
- "Ashe": "52.55%",
- "Yasuo": "52.42%",
- "Vayne": "52.01%",
- "Twitch": "51.83%",
- "Xayah": "51.49%",
- "Jinx": "50.82%",
- "Jhin": "50.68%",
- "Ezreal": "50.58%",
- "Draven": "50.28%",
- "Miss Fortune": "50.20%"
+ "champion_name": "Zyra",
+ "pick_rate": "4.3%",
+ "counters": {
+ "Taric": "54.73%",
+ "Leona": "54.05%",
+ "Sona": "53.31%",
+ "Maokai": "52.98%",
+ "Renata Glasc": "52.84%",
+ "Pyke": "52.6%",
+ "Zac": "52.59%",
+ "Janna": "52.42%",
+ "Zilean": "51.88%",
+ "Yuumi": "51.63%"
+ },
+ "synergy": {
+ "Xayah": "66.07%",
+ "Sivir": "61.54%",
+ "Twisted Fate": "53.68%",
+ "Miss Fortune": "53.25%",
+ "Ezreal": "52.52%",
+ "Senna": "52.28%",
+ "Tristana": "50.94%",
+ "Vayne": "50.18%",
+ "Jinx": "50.00%"
}
},
{
- "champion_name": "Zac",
- "pick_rate": "0.7%",
- "counters": {
- "Poppy": "67.39%",
- "Maokai": "57.14%",
- "Neeko": "56.72%",
- "Swain": "56%",
- "Vel'Koz": "54.1%",
- "Taric": "53.85%",
- "Alistar": "53.74%",
- "Ashe": "53.49%",
- "Rakan": "53.42%",
- "Zilean": "53.26%"
- },
+ "champion_name": "Vel'Koz",
+ "pick_rate": "1.4%",
+ "counters": "no info",
"synergy": {
- "Ziggs": "72.73%",
- "Sivir": "71.43%",
- "Zeri": "70.00%",
- "Varus": "63.64%",
- "Yasuo": "62.07%",
- "Jhin": "59.32%",
- "Kalista": "57.14%",
- "Hwei": "57.14%",
- "Twitch": "55.32%",
- "Ezreal": "52.63%",
- "Miss Fortune": "52.38%",
- "Vayne": "50.75%",
- "Swain": "50.00%",
- "Tristana": "50.00%",
- "Caitlyn": "50.00%"
+ "Samira": "65.00%",
+ "Nilah": "60.00%",
+ "Hwei": "58.82%",
+ "Seraphine": "54.55%",
+ "Draven": "53.66%",
+ "Ashe": "53.52%",
+ "Tristana": "53.33%",
+ "Zeri": "53.13%",
+ "Senna": "51.90%",
+ "Kai'Sa": "51.02%",
+ "Jhin": "50.71%",
+ "Smolder": "50.22%",
+ "Sivir": "50.00%",
+ "Karthus": "50.00%"
}
},
{
- "champion_name": "Leona",
- "pick_rate": "4.8%",
- "counters": {
- "Taric": "56.7%",
- "Janna": "54.1%",
- "Bard": "54.03%",
- "Shaco": "53.67%",
- "Maokai": "53.55%",
- "Rell": "53.22%",
- "Soraka": "53.11%",
- "Lux": "53.09%",
- "Morgana": "53.02%",
- "Braum": "51.72%"
- },
- "synergy": {
- "Seraphine": "56.47%",
- "Xayah": "55.00%",
- "Zeri": "54.63%",
- "Ezreal": "54.35%",
- "Twitch": "54.27%",
- "Caitlyn": "53.68%",
- "Jhin": "53.21%",
- "Yasuo": "53.19%",
- "Lucian": "52.72%",
- "Miss Fortune": "51.92%",
- "Aphelios": "51.70%",
- "Tristana": "51.48%",
- "Kalista": "51.16%"
- }
+ "champion_name": "Senna",
+ "pick_rate": "11.0%",
+ "counters": "no info",
+ "synergy": [
+ "no data"
+ ]
},
{
- "champion_name": "Xerath",
- "pick_rate": "4.8%",
- "counters": {
- "Sylas": "55%",
- "Maokai": "53.98%",
- "Sona": "53.77%",
- "Janna": "53.15%",
- "Pyke": "52.84%",
- "Soraka": "52.33%",
- "Rakan": "52.02%",
- "Leona": "51.76%",
- "Zyra": "51.72%",
- "Nami": "51.6%"
- },
+ "champion_name": "Shaco",
+ "pick_rate": "2.6%",
+ "counters": "no info",
"synergy": {
- "Ziggs": "55.24%",
- "Jinx": "53.95%",
- "Vayne": "53.32%",
- "Samira": "52.63%",
- "Xayah": "52.57%",
- "Ashe": "52.33%",
- "Jhin": "51.81%",
- "Caitlyn": "51.12%",
- "Hwei": "50.88%",
- "Tristana": "50.70%",
- "Ezreal": "50.00%"
+ "Karthus": "76.92%",
+ "Twisted Fate": "62.64%",
+ "Tristana": "58.62%",
+ "Senna": "56.19%",
+ "Miss Fortune": "56.10%",
+ "Aphelios": "52.78%",
+ "Hwei": "52.63%",
+ "Ashe": "52.48%",
+ "Draven": "52.24%",
+ "Nilah": "50.00%",
+ "Caitlyn": "50.00%",
+ "Ziggs": "50.00%"
}
},
{
- "champion_name": "Nami",
- "pick_rate": "6.5%",
- "counters": {
- "Maokai": "56.37%",
- "Neeko": "53.14%",
- "Blitzcrank": "53.03%",
- "Rakan": "52.58%",
- "Zyra": "52.29%",
- "Soraka": "51.92%",
- "Milio": "51.67%",
- "Pyke": "51.56%",
- "Janna": "51.44%",
- "Bard": "51.33%"
- },
+ "champion_name": "Pantheon",
+ "pick_rate": "1.8%",
+ "counters": "no info",
"synergy": {
- "Swain": "59.52%",
- "Brand": "55.69%",
- "Karthus": "55.65%",
- "Twitch": "52.16%",
- "Ashe": "51.88%",
- "Seraphine": "51.43%",
- "Jhin": "51.32%",
- "Vayne": "51.29%",
- "Ezreal": "51.27%"
+ "Karthus": "66.67%",
+ "Twisted Fate": "58.82%",
+ "Miss Fortune": "56.60%",
+ "Senna": "56.59%",
+ "Jhin": "55.67%",
+ "Nilah": "54.55%",
+ "Ezreal": "54.37%",
+ "Swain": "53.85%",
+ "Lucian": "53.06%",
+ "Ashe": "52.38%",
+ "Smolder": "52.07%",
+ "Samira": "51.97%",
+ "Jinx": "51.72%"
}
},
{
- "champion_name": "Shaco",
+ "champion_name": "Tahm Kench",
"pick_rate": "1.1%",
- "counters": {
- "Sona": "60.95%",
- "Swain": "60.22%",
- "Taric": "59.68%",
- "Heimerdinger": "59.02%",
- "Zac": "56.14%",
- "Janna": "55.96%",
- "Milio": "54.49%",
- "Pyke": "54.23%",
- "Rakan": "53.2%",
- "Bard": "52.59%"
- },
+ "counters": "no info",
"synergy": {
- "Seraphine": "85.71%",
- "Miss Fortune": "58.33%",
- "Samira": "54.84%",
- "Caitlyn": "54.43%",
- "Jhin": "54.01%",
- "Varus": "52.43%",
- "Karthus": "50.00%"
+ "Kalista": "68.75%",
+ "Jinx": "65.00%",
+ "Nilah": "62.50%",
+ "Xayah": "62.50%",
+ "Twitch": "57.14%",
+ "Varus": "55.10%",
+ "Twisted Fate": "54.55%",
+ "Zeri": "53.85%",
+ "Senna": "52.51%",
+ "Seraphine": "50.00%"
}
},
{
- "champion_name": "Heimerdinger",
- "pick_rate": "1.0%",
+ "champion_name": "Nautilus",
+ "pick_rate": "11.0%",
"counters": {
- "Maokai": "61.64%",
- "Neeko": "56.82%",
- "Seraphine": "55.95%",
- "Xerath": "54.86%",
- "Bard": "54.55%",
- "Leona": "54.4%",
- "Rell": "53.15%",
- "Rakan": "52.67%",
- "Lulu": "52.56%",
- "Janna": "52.56%"
- },
- "synergy": {
- "Zeri": "68.00%",
- "Sivir": "57.89%",
- "Draven": "54.29%",
- "Jhin": "54.03%",
- "Vayne": "53.49%",
- "Miss Fortune": "52.78%",
- "Ashe": "50.34%",
- "Kalista": "50.00%",
- "Twitch": "50.00%"
+ "Taric": "60.99%",
+ "Leona": "57.54%",
+ "Braum": "56.73%",
+ "Renata Glasc": "55.96%",
+ "Alistar": "55.65%",
+ "Rell": "55.38%",
+ "Camille": "54.74%",
+ "Pantheon": "54.53%",
+ "Swain": "54.35%",
+ "Maokai": "53.72%"
+ },
+ "synergy": {
+ "Seraphine": "55.21%",
+ "Senna": "54.59%",
+ "Kalista": "52.55%",
+ "Xayah": "52.25%",
+ "Caitlyn": "51.40%",
+ "Karthus": "51.11%",
+ "Twitch": "50.96%",
+ "Lucian": "50.86%",
+ "Jhin": "50.64%",
+ "Draven": "50.62%",
+ "Vayne": "50.27%",
+ "Zeri": "50.21%",
+ "Samira": "50.09%",
+ "Nilah": "50.00%"
}
},
{
- "champion_name": "Sylas",
- "pick_rate": "1.0%",
- "counters": {
- "Sona": "60%",
- "Janna": "57.91%",
- "Vel'Koz": "57.75%",
- "Braum": "57.52%",
- "Morgana": "57.5%",
- "Taric": "55.56%",
- "Rell": "54.55%",
- "Rakan": "53.89%",
- "Blitzcrank": "53.74%",
- "Thresh": "53.64%"
+ "champion_name": "Milio",
+ "pick_rate": "8.1%",
+ "counters": {
+ "Blitzcrank": "56.32%",
+ "Braum": "55.45%",
+ "Maokai": "54.37%",
+ "Camille": "53.86%",
+ "Janna": "53.43%",
+ "Thresh": "53.37%",
+ "Zilean": "53.04%",
+ "Tahm Kench": "53.04%",
+ "Vel'Koz": "52.98%",
+ "Leona": "52.96%"
},
- "synergy": {
- "Karthus": "60.00%",
- "Aphelios": "58.06%",
- "Nilah": "57.89%",
- "Miss Fortune": "57.14%",
- "Kalista": "55.36%",
- "Lucian": "54.65%",
- "Seraphine": "54.55%",
- "Sivir": "52.94%",
- "Zeri": "52.63%",
- "Vayne": "52.53%",
- "Jhin": "52.38%",
- "Ezreal": "51.14%",
- "Twitch": "50.77%",
- "Yasuo": "50.00%"
- }
+ "synergy": [
+ "no data"
+ ]
},
{
- "champion_name": "Tahm Kench",
- "pick_rate": "0.6%",
+ "champion_name": "Nami",
+ "pick_rate": "5.5%",
"counters": {
- "Taric": "62.22%",
- "Braum": "61.11%",
- "Bard": "57.4%",
- "Zyra": "57.27%",
- "Pyke": "55.38%",
- "Maokai": "54.9%",
- "Brand": "54.67%",
- "Lulu": "54.55%",
- "Renata Glasc": "54.17%",
- "Rakan": "54.12%"
+ "Morgana": "56.08%",
+ "Blitzcrank": "55.61%",
+ "Maokai": "55.57%",
+ "Vel'Koz": "54.47%",
+ "Rakan": "54.44%",
+ "Neeko": "54.21%",
+ "Rell": "53.9%",
+ "Nautilus": "53.32%",
+ "Thresh": "53.28%",
+ "Senna": "53.17%"
},
- "synergy": {
- "Veigar": "80.00%",
- "Ziggs": "75.00%",
- "Karthus": "66.67%",
- "Seraphine": "58.33%",
- "Xayah": "58.33%",
- "Jhin": "55.56%",
- "Miss Fortune": "51.72%",
- "Twitch": "51.28%",
- "Kog'Maw": "50.00%",
- "Kalista": "50.00%",
- "Ashe": "50.00%",
- "Ezreal": "50.00%"
+ "synergy": [
+ "no data"
+ ]
+ },
+ {
+ "champion_name": "Xerath",
+ "pick_rate": "2.7%",
+ "counters": {
+ "Blitzcrank": "58.63%",
+ "Neeko": "56.43%",
+ "Sona": "56.36%",
+ "Maokai": "56.26%",
+ "Pyke": "56.15%",
+ "Leona": "56.02%",
+ "Braum": "55.06%",
+ "Rakan": "54.83%",
+ "Nautilus": "54.65%",
+ "Janna": "53.86%"
+ },
+ "synergy": {
+ "Samira": "70.00%",
+ "Twisted Fate": "61.36%",
+ "Caitlyn": "57.97%",
+ "Karthus": "55.56%",
+ "Ashe": "52.46%",
+ "Vayne": "50.55%",
+ "Zeri": "50.00%"
}
},
{
- "champion_name": "Amumu",
- "pick_rate": "0.6%",
- "counters": {
- "Maokai": "66.07%",
- "Zyra": "61.68%",
- "Rell": "58.9%",
- "Rakan": "58.55%",
- "Janna": "58.5%",
- "Neeko": "57.14%",
- "Taric": "56.1%",
- "Braum": "55.41%",
- "Bard": "54%",
- "Leona": "53.91%"
- },
+ "champion_name": "Brand",
+ "pick_rate": "1.9%",
+ "counters": "no info",
"synergy": {
- "Yasuo": "71.43%",
- "Ziggs": "66.67%",
- "Kalista": "64.15%",
- "Lucian": "60.00%",
- "Karthus": "60.00%",
- "Hwei": "57.14%",
- "Ezreal": "55.88%",
- "Nilah": "55.56%",
- "Draven": "55.41%",
- "Miss Fortune": "54.37%",
- "Ashe": "54.17%",
- "Jinx": "53.85%",
- "Kai'Sa": "53.60%",
- "Tristana": "52.63%",
- "Zeri": "50.00%",
- "Veigar": "50.00%"
+ "Samira": "59.09%",
+ "Yasuo": "57.89%",
+ "Twisted Fate": "57.14%",
+ "Xayah": "56.52%",
+ "Jinx": "55.88%",
+ "Jhin": "53.98%",
+ "Draven": "53.70%",
+ "Seraphine": "52.50%",
+ "Aphelios": "50.00%"
}
},
{
- "champion_name": "Twitch",
- "pick_rate": "0.8%",
+ "champion_name": "Swain",
+ "pick_rate": "1.4%",
"counters": {
- "Zilean": "61.97%",
- "Taric": "60.47%",
- "Leona": "57.92%",
- "Soraka": "57.87%",
- "Shaco": "57.38%",
+ "Zac": "58.97%",
+ "Sona": "58.67%",
+ "Neeko": "57.41%",
"Nami": "57.14%",
- "Rakan": "56.34%",
- "Sylas": "55.56%",
- "Brand": "55.26%",
- "Pyke": "54.65%"
+ "Milio": "56.44%",
+ "Pantheon": "55.95%",
+ "Zyra": "55.49%",
+ "Morgana": "55.24%",
+ "Leona": "55%",
+ "Camille": "54.79%"
},
- "synergy": {
- "Sivir": "70.97%",
- "Swain": "70.00%",
- "Seraphine": "63.64%",
- "Draven": "57.89%",
- "Cassiopeia": "53.85%",
- "Vayne": "53.54%",
- "Jhin": "53.40%",
- "Jinx": "52.27%",
- "Ziggs": "51.72%",
- "Zeri": "51.61%",
- "Nilah": "50.00%",
- "Kalista": "50.00%",
- "Hwei": "50.00%",
- "Tristana": "50.00%"
- }
+ "synergy": [
+ "no data"
+ ]
},
{
- "champion_name": "Nautilus",
- "pick_rate": "10.9%",
- "counters": {
- "Taric": "55.98%",
- "Rell": "55.66%",
- "Renata Glasc": "55.13%",
- "Sylas": "55.09%",
- "Braum": "54.08%",
- "Rakan": "54.06%",
- "Vel'Koz": "53.45%",
- "Swain": "53.09%",
- "Neeko": "52.97%",
- "Alistar": "52.87%"
+ "champion_name": "Lulu",
+ "pick_rate": "7.9%",
+ "counters": {
+ "Vel'Koz": "57.35%",
+ "Blitzcrank": "56.23%",
+ "Zilean": "55.78%",
+ "Tahm Kench": "55.43%",
+ "Renata Glasc": "54.86%",
+ "Rell": "54.7%",
+ "Janna": "54.6%",
+ "Maokai": "54.51%",
+ "Braum": "53.79%",
+ "Ashe": "53.65%"
},
- "synergy": {
- "Seraphine": "58.68%",
- "Ziggs": "55.43%",
- "Karthus": "55.21%",
- "Lucian": "51.71%",
- "Tristana": "51.50%",
- "Samira": "51.14%",
- "Kalista": "50.71%",
- "Jinx": "50.13%"
- }
+ "synergy": [
+ "no data"
+ ]
},
{
- "champion_name": "Ashe",
- "pick_rate": "5.7%",
- "counters": {
- "Pyke": "56.98%",
- "Sylas": "54.95%",
- "Blitzcrank": "54.83%",
- "Heimerdinger": "54.8%",
- "Rell": "54.67%",
- "Nautilus": "54.35%",
- "Bard": "53.44%",
- "Milio": "53.1%",
- "Rakan": "52.69%",
- "Brand": "52.5%"
- },
+ "champion_name": "Morgana",
+ "pick_rate": "2.8%",
+ "counters": "no info",
"synergy": {
- "Karthus": "56.83%",
- "Seraphine": "54.60%",
- "Jinx": "54.17%",
- "Nilah": "53.85%",
- "Twitch": "53.55%",
- "Ezreal": "51.69%",
- "Swain": "51.32%",
- "Ziggs": "50.79%",
- "Lucian": "50.00%",
- "Zeri": "50.00%"
+ "Xayah": "60.00%",
+ "Draven": "57.81%",
+ "Nilah": "54.55%",
+ "Samira": "51.61%",
+ "Sivir": "50.00%",
+ "Seraphine": "50.00%"
}
},
{
- "champion_name": "Lux",
- "pick_rate": "5.1%",
- "counters": {
- "Blitzcrank": "56.57%",
- "Maokai": "54.94%",
- "Zyra": "54.66%",
- "Shaco": "54.45%",
- "Vel'Koz": "54.1%",
- "Sylas": "53.87%",
- "Zilean": "53.51%",
- "Pyke": "53.5%",
- "Xerath": "52.75%",
- "Nami": "52.72%"
- },
+ "champion_name": "Heimerdinger",
+ "pick_rate": "0.5%",
+ "counters": "no info",
"synergy": {
- "Karthus": "59.46%",
- "Twitch": "53.57%",
- "Xayah": "52.17%",
- "Ashe": "51.05%",
- "Varus": "50.88%",
- "Tristana": "50.67%",
+ "Tristana": "80.00%",
+ "Aphelios": "80.00%",
+ "Draven": "64.29%",
+ "Twitch": "62.50%",
+ "Twisted Fate": "60.00%",
+ "Jhin": "55.56%",
+ "Varus": "53.85%",
+ "Senna": "51.28%",
+ "Miss Fortune": "50.00%",
"Jinx": "50.00%",
- "Zeri": "50.00%"
+ "Nilah": "50.00%"
}
},
{
- "champion_name": "Brand",
- "pick_rate": "2.0%",
+ "champion_name": "Ashe",
+ "pick_rate": "4.8%",
"counters": {
- "Sona": "58.11%",
- "Maokai": "57.24%",
- "Xerath": "57.03%",
- "Janna": "56.31%",
- "Pyke": "56.01%",
- "Zyra": "55.13%",
- "Leona": "54.96%",
- "Zilean": "54.92%",
- "Soraka": "54.73%",
- "Senna": "54.19%"
+ "Camille": "58.72%",
+ "Maokai": "57.34%",
+ "Pyke": "56.14%",
+ "Blitzcrank": "55.88%",
+ "Nautilus": "55.46%",
+ "Taric": "55.41%",
+ "Vel'Koz": "54.55%",
+ "Zyra": "54.13%",
+ "Soraka": "53.87%",
+ "Xerath": "53.83%"
},
"synergy": {
- "Seraphine": "57.69%",
- "Karthus": "55.17%",
- "Yasuo": "55.17%",
- "Kalista": "53.49%",
- "Tristana": "52.31%",
- "Xayah": "51.40%",
- "Varus": "50.17%"
+ "Jinx": "53.73%",
+ "Jhin": "53.36%",
+ "Karthus": "52.31%",
+ "Kalista": "51.90%",
+ "Twisted Fate": "51.81%",
+ "Zeri": "50.65%"
}
},
{
- "champion_name": "Seraphine",
- "pick_rate": "2.0%",
+ "champion_name": "Sylas",
+ "pick_rate": "0.6%",
"counters": {
- "Sylas": "58.33%",
- "Maokai": "57.24%",
- "Zilean": "56.09%",
- "Bard": "55.97%",
- "Pyke": "54.69%",
- "Soraka": "53.72%",
- "Blitzcrank": "53.61%",
- "Yuumi": "53.6%",
- "Milio": "52.99%",
- "Thresh": "52.66%"
+ "Taric": "70%",
+ "Shaco": "70%",
+ "Pantheon": "64.86%",
+ "Neeko": "63.16%",
+ "Thresh": "58.58%",
+ "Lulu": "58.52%",
+ "Morgana": "58.06%",
+ "Janna": "57.66%",
+ "Leona": "57.58%",
+ "Bard": "57.45%"
},
"synergy": {
- "Vayne": "59.62%",
- "Tristana": "59.38%",
- "Miss Fortune": "52.50%",
- "Ashe": "52.20%",
- "Jinx": "51.11%",
- "Draven": "50.94%"
+ "Miss Fortune": "75.00%",
+ "Zeri": "72.73%",
+ "Twitch": "68.75%",
+ "Kalista": "68.42%",
+ "Draven": "65.22%",
+ "Tristana": "61.54%",
+ "Senna": "60.00%",
+ "Aphelios": "60.00%",
+ "Jinx": "55.56%",
+ "Jhin": "50.00%"
}
},
{
- "champion_name": "Lulu",
- "pick_rate": "7.4%",
+ "champion_name": "Neeko",
+ "pick_rate": "1.6%",
"counters": {
- "Taric": "56.84%",
- "Zilean": "54.43%",
- "Neeko": "54.04%",
- "Senna": "54.04%",
- "Sona": "53.78%",
- "Zyra": "53.7%",
- "Maokai": "53.46%",
- "Blitzcrank": "52.96%",
- "Bard": "52.89%",
- "Thresh": "52.88%"
- },
- "synergy": {
- "Miss Fortune": "55.96%",
- "Sivir": "55.62%",
- "Tristana": "55.22%",
- "Ashe": "51.59%",
+ "Zilean": "60.66%",
+ "Taric": "58.7%",
+ "Zac": "57.78%",
+ "Leona": "57.76%",
+ "Pantheon": "57.14%",
+ "Janna": "56.74%",
+ "Blitzcrank": "56.38%",
+ "Bard": "55.85%",
+ "Camille": "55.41%",
+ "Rell": "55.19%"
+ },
+ "synergy": {
+ "Yasuo": "67.74%",
+ "Tristana": "66.67%",
+ "Twitch": "65.63%",
+ "Ashe": "60.87%",
+ "Senna": "59.22%",
+ "Nilah": "57.14%",
+ "Caitlyn": "54.55%",
"Draven": "51.52%",
- "Varus": "51.22%",
- "Twitch": "51.14%",
- "Jinx": "50.76%",
- "Kog'Maw": "50.00%",
- "Kalista": "50.00%"
+ "Twisted Fate": "50.94%",
+ "Vayne": "50.00%",
+ "Jinx": "50.00%",
+ "Miss Fortune": "50.00%"
}
},
{
- "champion_name": "Morgana",
- "pick_rate": "3.2%",
- "counters": {
- "Zyra": "56.73%",
- "Senna": "56.35%",
- "Rell": "56.29%",
- "Janna": "55.6%",
- "Milio": "55.2%",
- "Vel'Koz": "55.08%",
- "Nami": "54.67%",
- "Taric": "54.31%",
- "Karma": "54.24%",
- "Maokai": "53.2%"
- },
+ "champion_name": "Karma",
+ "pick_rate": "9.6%",
+ "counters": "no info",
"synergy": {
- "Karthus": "55.56%",
- "Nilah": "53.13%",
- "Miss Fortune": "52.07%",
- "Hwei": "51.72%",
- "Vayne": "51.31%",
- "Sivir": "50.91%",
- "Draven": "50.63%",
- "Tristana": "50.00%"
+ "Tristana": "54.41%",
+ "Ashe": "52.79%",
+ "Twisted Fate": "52.57%",
+ "Jinx": "52.21%",
+ "Miss Fortune": "51.67%",
+ "Kalista": "51.02%",
+ "Twitch": "50.19%",
+ "Caitlyn": "50.15%",
+ "Zeri": "50.00%"
}
},
{
- "champion_name": "Karma",
- "pick_rate": "7.6%",
- "counters": {
- "Pyke": "57.3%",
- "Maokai": "55.33%",
- "Zyra": "54.97%",
- "Blitzcrank": "54.92%",
- "Rakan": "54.52%",
- "Janna": "54.36%",
- "Sona": "54.33%",
- "Nautilus": "54.21%",
- "Soraka": "53.61%",
- "Neeko": "52.83%"
- },
- "synergy": {
- "Draven": "53.57%",
- "Vayne": "51.30%",
- "Jhin": "50.49%",
- "Ezreal": "50.43%",
- "Tristana": "50.42%"
+ "champion_name": "Lux",
+ "pick_rate": "3.9%",
+ "counters": {
+ "Camille": "62.93%",
+ "Pantheon": "60%",
+ "Pyke": "58.92%",
+ "Bard": "57.24%",
+ "Shaco": "56.3%",
+ "Blitzcrank": "55.42%",
+ "Maokai": "55.39%",
+ "Leona": "55.08%",
+ "Alistar": "54.69%",
+ "Zilean": "54.61%"
+ },
+ "synergy": {
+ "Ziggs": "57.14%",
+ "Twisted Fate": "56.82%",
+ "Samira": "52.63%",
+ "Senna": "52.05%",
+ "Ezreal": "50.65%",
+ "Nilah": "50.00%"
}
},
{
- "champion_name": "Swain",
- "pick_rate": "1.6%",
- "counters": {
- "Zilean": "57.77%",
- "Sona": "57.32%",
- "Vel'Koz": "57.25%",
- "Janna": "56.41%",
- "Brand": "55.87%",
- "Yuumi": "55.78%",
- "Zyra": "55.78%",
- "Xerath": "55.48%",
- "Soraka": "55.34%",
- "Senna": "55.32%"
- },
+ "champion_name": "Seraphine",
+ "pick_rate": "1.7%",
+ "counters": "no info",
"synergy": {
- "Tristana": "64.29%",
- "Karthus": "54.55%",
- "Vayne": "54.01%",
- "Jhin": "53.18%",
- "Twitch": "50.70%",
- "Nilah": "50.00%",
- "Ziggs": "50.00%"
+ "Lucian": "61.54%",
+ "Tristana": "61.54%",
+ "Vayne": "58.90%",
+ "Varus": "57.69%",
+ "Jinx": "56.25%",
+ "Miss Fortune": "55.32%",
+ "Senna": "51.51%",
+ "Ezreal": "50.83%",
+ "Samira": "50.00%",
+ "Karthus": "50.00%"
}
},
{
- "champion_name": "Pantheon",
- "pick_rate": "1.0%",
- "counters": {
- "Neeko": "59.74%",
- "Maokai": "58.43%",
- "Karma": "57.22%",
- "Bard": "57.14%",
- "Swain": "56.25%",
- "Rakan": "55.92%",
- "Brand": "55.83%",
- "Soraka": "55.74%",
- "Sona": "55.56%",
- "Janna": "55.42%"
- },
- "synergy": {
- "Miss Fortune": "63.41%",
- "Seraphine": "56.25%",
- "Yasuo": "54.55%",
- "Swain": "52.63%",
- "Varus": "52.05%",
- "Zeri": "50.00%"
+ "champion_name": "Yuumi",
+ "pick_rate": "4.4%",
+ "counters": {
+ "Rell": "62.65%",
+ "Braum": "58.82%",
+ "Blitzcrank": "58.05%",
+ "Leona": "57.77%",
+ "Pyke": "57.61%",
+ "Taric": "57.46%",
+ "Swain": "56.85%",
+ "Rakan": "56.72%",
+ "Maokai": "56.12%",
+ "Janna": "55.85%"
+ },
+ "synergy": {
+ "Rammus": "64.71%",
+ "Jinx": "55.32%",
+ "Vayne": "54.26%",
+ "Samira": "53.57%",
+ "Lucian": "53.52%",
+ "Tristana": "53.33%",
+ "Twitch": "53.31%",
+ "Jhin": "50.00%",
+ "Xayah": "50.00%"
}
},
{
- "champion_name": "Yuumi",
- "pick_rate": "4.9%",
- "counters": {
- "Maokai": "61.28%",
- "Rell": "59.85%",
- "Taric": "57.47%",
- "Alistar": "57.31%",
- "Nautilus": "56.64%",
- "Rakan": "55.78%",
- "Pyke": "55.71%",
- "Leona": "55.63%",
- "Sylas": "55.27%",
- "Thresh": "55.23%"
- },
+ "champion_name": "Twitch",
+ "pick_rate": "0.5%",
+ "counters": "no info",
"synergy": {
- "Seraphine": "66.67%",
- "Yasuo": "53.25%",
- "Tristana": "52.55%",
- "Vayne": "51.74%",
- "Zeri": "51.45%",
- "Twitch": "50.41%"
+ "Karma": "75.00%",
+ "Lucian": "66.67%",
+ "Cassiopeia": "66.67%",
+ "Janna": "66.67%",
+ "Aphelios": "66.67%",
+ "Jhin": "61.90%",
+ "Zeri": "60.87%",
+ "Varus": "60.00%",
+ "Kalista": "57.14%",
+ "Xayah": "53.85%",
+ "Ezreal": "53.52%",
+ "Vayne": "53.19%",
+ "Sivir": "50.00%",
+ "Jinx": "50.00%",
+ "Lulu": "50.00%"
}
},
{
"champion_name": "Hwei",
- "pick_rate": "2.1%",
- "counters": {
- "Taric": "66.91%",
- "Sona": "66.55%",
- "Blitzcrank": "65.66%",
- "Janna": "65.3%",
- "Brand": "65.18%",
- "Pyke": "63.85%",
- "Xerath": "63.02%",
- "Vel'Koz": "62.71%",
- "Morgana": "62.31%",
- "Milio": "62.01%"
- },
- "synergy": {
- "Nilah": "51.61%"
+ "pick_rate": "2.9%",
+ "counters": {
+ "Camille": "63.76%",
+ "Pyke": "60.47%",
+ "Maokai": "58.72%",
+ "Xerath": "58.7%",
+ "Rakan": "58.47%",
+ "Taric": "58.43%",
+ "Swain": "58.1%",
+ "Zilean": "57.69%",
+ "Vel'Koz": "57.69%",
+ "Janna": "57.32%"
+ },
+ "synergy": {
+ "Lucian": "57.14%",
+ "Tristana": "54.55%",
+ "Swain": "53.85%",
+ "Miss Fortune": "51.85%",
+ "Seraphine": "51.02%"
}
}
]
\ No newline at end of file
diff --git a/src/LoL-support/how-to-start.md b/src/LoL-support/how-to-start.md
old mode 100644
new mode 100755
diff --git a/src/LoL-support/lolchamps.sh b/src/LoL-support/lolchamps.sh
index 441b3b1..3f4b309 100755
--- a/src/LoL-support/lolchamps.sh
+++ b/src/LoL-support/lolchamps.sh
@@ -1,35 +1,37 @@
#!/bin/zsh
-#python step1-lolchamps.py
-#echo "Step 1 complete: Scraped targeted data from URL"
+python step1-lolchamps.py
echo "Step 1 Scrape targeted data from URL."
-echo "TEMPORALY DISABLED, Data already extracted"
+#echo "TEMPORALY DISABLED, Data already extracted"
+#TBD: show the date when was the last time it scraped,
python step2-lolchamps.py
echo "Step 2 complete: extracted Champion names and their Pick rates"
+#TBD: Winrates from diamond+
python step3-lolchamps.py
echo "Step 3 complete: made data two-dimensional with Pandas, just for fun"
-#python step4-lolchamps.py
+python step4-lolchamps.py
#enable only if list of champion names are lost
echo "Step 4 complete: saved champion names in a list"
-echo "TEMPORALY DISABLED, champ names already saved"
+#echo "TEMPORALY DISABLED, champ names already saved"
-#python step5-lolchamps.py
+python step5-lolchamps.py
echo "Step 5 in Beta mode: trying to iterate through champion names and scrape their counters."
-echo "DISABLED BECAUSE COUNTERS ARE ALREADY SCRAPED AND SAVED"
+#echo "DISABLED BECAUSE COUNTERS ARE ALREADY SCRAPED AND SAVED"
#scraped only best counters. but script is unable to click for 'more counters'
-#python step6-lolchamps.py
+python step6-lolchamps.py
echo "Step 6 in Beta mode: collecting synergies for each support champion"
-echo "DISABLED BECAUSE SYNERGIES ARE ALREADY SCRAPED AND SAVED"
+#echo "DISABLED BECAUSE SYNERGIES ARE ALREADY SCRAPED AND SAVED"
#EUW and KR versions. Atm EUW works only. Currently it only shows DUO synergy, but I want ADC-SUPP-JUNGLER Synergy
-#python step7-lolchamps.py
+python step7-lolchamps.py
echo "Step 7 complete: combining data into final_data"
-#python step8-db.py
+python step8-db.py
echo "Step 8 in beta mode: sending final_data to Postgres Server tables"
+#last extraction: 2024/03/04
chmod +x lolchamps.sh
\ No newline at end of file
diff --git a/src/LoL-support/pick_rates.json b/src/LoL-support/pick_rates.json
old mode 100644
new mode 100755
index b8e55ed..f9a9a85
--- a/src/LoL-support/pick_rates.json
+++ b/src/LoL-support/pick_rates.json
@@ -1,170 +1,170 @@
[
{
- "champion_name": "Taric",
- "pick_rate": "1.4%"
+ "champion_name": "Maokai",
+ "pick_rate": "12.7%"
},
{
"champion_name": "Janna",
- "pick_rate": "9.6%"
+ "pick_rate": "10.7%"
},
{
- "champion_name": "Maokai",
- "pick_rate": "1.6%"
+ "champion_name": "Taric",
+ "pick_rate": "1.5%"
},
{
- "champion_name": "Bard",
- "pick_rate": "6.1%"
+ "champion_name": "Braum",
+ "pick_rate": "3.8%"
},
{
- "champion_name": "Rakan",
- "pick_rate": "12.8%"
+ "champion_name": "Blitzcrank",
+ "pick_rate": "8.1%"
},
{
- "champion_name": "Sona",
- "pick_rate": "2.4%"
+ "champion_name": "Camille",
+ "pick_rate": "2.3%"
},
{
- "champion_name": "Senna",
- "pick_rate": "11.0%"
+ "champion_name": "Leona",
+ "pick_rate": "4.4%"
},
{
"champion_name": "Rell",
- "pick_rate": "4.9%"
- },
- {
- "champion_name": "Neeko",
- "pick_rate": "1.9%"
+ "pick_rate": "4.1%"
},
{
- "champion_name": "Soraka",
- "pick_rate": "5.5%"
+ "champion_name": "Rakan",
+ "pick_rate": "7.7%"
},
{
- "champion_name": "Blitzcrank",
- "pick_rate": "8.8%"
+ "champion_name": "Zilean",
+ "pick_rate": "2.8%"
},
{
- "champion_name": "Zyra",
- "pick_rate": "3.8%"
+ "champion_name": "Pyke",
+ "pick_rate": "8.5%"
},
{
- "champion_name": "Pyke",
- "pick_rate": "7.6%"
+ "champion_name": "Zac",
+ "pick_rate": "1.0%"
},
{
- "champion_name": "Zilean",
- "pick_rate": "2.5%"
+ "champion_name": "Alistar",
+ "pick_rate": "4.7%"
},
{
- "champion_name": "Braum",
- "pick_rate": "3.6%"
+ "champion_name": "Bard",
+ "pick_rate": "5.7%"
},
{
- "champion_name": "Renata Glasc",
- "pick_rate": "5.0%"
+ "champion_name": "Soraka",
+ "pick_rate": "5.4%"
},
{
- "champion_name": "Vel'Koz",
- "pick_rate": "1.6%"
+ "champion_name": "Sona",
+ "pick_rate": "2.3%"
},
{
"champion_name": "Thresh",
- "pick_rate": "13.3%"
+ "pick_rate": "9.4%"
},
{
- "champion_name": "Milio",
- "pick_rate": "6.7%"
+ "champion_name": "Renata Glasc",
+ "pick_rate": "2.4%"
},
{
- "champion_name": "Alistar",
- "pick_rate": "5.8%"
+ "champion_name": "Zyra",
+ "pick_rate": "4.3%"
},
{
- "champion_name": "Zac",
- "pick_rate": "0.7%"
+ "champion_name": "Vel'Koz",
+ "pick_rate": "1.4%"
},
{
- "champion_name": "Leona",
- "pick_rate": "4.8%"
+ "champion_name": "Senna",
+ "pick_rate": "11.0%"
},
{
- "champion_name": "Xerath",
- "pick_rate": "4.8%"
+ "champion_name": "Shaco",
+ "pick_rate": "2.6%"
},
{
- "champion_name": "Nami",
- "pick_rate": "6.5%"
+ "champion_name": "Pantheon",
+ "pick_rate": "1.8%"
},
{
- "champion_name": "Shaco",
+ "champion_name": "Tahm Kench",
"pick_rate": "1.1%"
},
{
- "champion_name": "Heimerdinger",
- "pick_rate": "1.0%"
+ "champion_name": "Nautilus",
+ "pick_rate": "11.0%"
},
{
- "champion_name": "Sylas",
- "pick_rate": "1.0%"
+ "champion_name": "Milio",
+ "pick_rate": "8.1%"
},
{
- "champion_name": "Tahm Kench",
- "pick_rate": "0.6%"
+ "champion_name": "Nami",
+ "pick_rate": "5.5%"
},
{
- "champion_name": "Amumu",
- "pick_rate": "0.6%"
+ "champion_name": "Xerath",
+ "pick_rate": "2.7%"
},
{
- "champion_name": "Twitch",
- "pick_rate": "0.8%"
+ "champion_name": "Brand",
+ "pick_rate": "1.9%"
},
{
- "champion_name": "Nautilus",
- "pick_rate": "10.9%"
+ "champion_name": "Swain",
+ "pick_rate": "1.4%"
},
{
- "champion_name": "Ashe",
- "pick_rate": "5.7%"
+ "champion_name": "Lulu",
+ "pick_rate": "7.9%"
},
{
- "champion_name": "Lux",
- "pick_rate": "5.1%"
+ "champion_name": "Morgana",
+ "pick_rate": "2.8%"
},
{
- "champion_name": "Brand",
- "pick_rate": "2.0%"
+ "champion_name": "Heimerdinger",
+ "pick_rate": "0.5%"
},
{
- "champion_name": "Seraphine",
- "pick_rate": "2.0%"
+ "champion_name": "Ashe",
+ "pick_rate": "4.8%"
},
{
- "champion_name": "Lulu",
- "pick_rate": "7.4%"
+ "champion_name": "Sylas",
+ "pick_rate": "0.6%"
},
{
- "champion_name": "Morgana",
- "pick_rate": "3.2%"
+ "champion_name": "Neeko",
+ "pick_rate": "1.6%"
},
{
"champion_name": "Karma",
- "pick_rate": "7.6%"
+ "pick_rate": "9.6%"
},
{
- "champion_name": "Swain",
- "pick_rate": "1.6%"
+ "champion_name": "Lux",
+ "pick_rate": "3.9%"
},
{
- "champion_name": "Pantheon",
- "pick_rate": "1.0%"
+ "champion_name": "Seraphine",
+ "pick_rate": "1.7%"
},
{
"champion_name": "Yuumi",
- "pick_rate": "4.9%"
+ "pick_rate": "4.4%"
+ },
+ {
+ "champion_name": "Twitch",
+ "pick_rate": "0.5%"
},
{
"champion_name": "Hwei",
- "pick_rate": "2.1%"
+ "pick_rate": "2.9%"
}
]
\ No newline at end of file
diff --git a/src/LoL-support/sorted_final_data.json b/src/LoL-support/sorted_final_data.json
old mode 100644
new mode 100755
index 7517365..1a22c5a
--- a/src/LoL-support/sorted_final_data.json
+++ b/src/LoL-support/sorted_final_data.json
@@ -1,1233 +1,934 @@
[
{
- "champion_name": "Thresh",
- "pick_rate": "13.3%",
- "counters": {
- "Taric": "53.39%",
- "Janna": "52.43%",
- "Brand": "52.12%",
- "Rakan": "52.02%",
- "Neeko": "51.75%",
- "Sona": "51.47%",
- "Maokai": "51.34%",
- "Vel'Koz": "51.27%",
- "Bard": "51.24%",
- "Renata Glasc": "51.24%"
- },
- "synergy": {
- "Tristana": "55.29%",
- "Hwei": "54.01%",
- "Draven": "53.28%",
- "Jinx": "52.84%",
- "Aphelios": "51.39%",
- "Twitch": "51.28%",
- "Kalista": "51.17%",
- "Samira": "51.08%",
- "Vayne": "51.01%",
- "Karthus": "50.91%",
- "Lucian": "50.61%",
- "Ashe": "50.26%",
- "Yasuo": "50.24%"
- }
- },
- {
- "champion_name": "Rakan",
- "pick_rate": "12.8%",
- "counters": {
- "Senna": "52.31%",
- "Bard": "51.42%",
- "Soraka": "51.41%",
- "Neeko": "51.37%",
- "Renata Glasc": "51.09%",
- "Sona": "50.93%",
- "Maokai": "50.87%",
- "Rell": "50.69%",
- "Zilean": "50.64%",
- "Janna": "50.58%"
+ "champion_name": "Maokai",
+ "pick_rate": "12.7%",
+ "counters": {
+ "Taric": "50.48%",
+ "Alistar": "49.83%",
+ "Braum": "49.49%",
+ "Leona": "49.02%",
+ "Janna": "48.98%",
+ "Renata Glasc": "48.48%",
+ "Rell": "47.97%",
+ "Shaco": "47.18%",
+ "Camille": "47.04%",
+ "Zyra": "47.02%"
},
- "synergy": {
- "Lucian": "56.02%",
- "Jinx": "54.48%",
- "Twitch": "54.35%",
- "Seraphine": "54.17%",
- "Ziggs": "54.08%",
- "Vayne": "54.03%",
- "Sivir": "53.73%",
- "Jhin": "52.40%",
- "Yasuo": "51.97%",
- "Karthus": "51.90%",
- "Ezreal": "51.77%",
- "Ashe": "51.55%",
- "Miss Fortune": "50.65%",
- "Nilah": "50.61%",
- "Varus": "50.35%",
- "Draven": "50.12%"
- }
+ "synergy": [
+ "no data"
+ ]
},
{
"champion_name": "Senna",
"pick_rate": "11.0%",
- "counters": {
- "Pyke": "52.31%",
- "Blitzcrank": "52.25%",
- "Xerath": "51.66%",
- "Maokai": "51.62%",
- "Zyra": "51.1%",
- "Taric": "50.76%",
- "Leona": "50.22%",
- "Bard": "50.06%",
- "Lux": "49.9%",
- "Rell": "49.8%"
- },
- "synergy": {
- "Kalista": "57.48%",
- "Nilah": "56.48%",
- "Karthus": "56.18%",
- "Zeri": "55.11%",
- "Seraphine": "55.06%",
- "Miss Fortune": "54.98%",
- "Cho'Gath": "54.69%",
- "Vayne": "54.60%",
- "Ashe": "54.54%",
- "Tahm Kench": "54.51%",
- "Yasuo": "54.49%",
- "Brand": "54.26%",
- "Twitch": "54.15%",
- "Swain": "53.87%",
- "Jhin": "52.87%",
- "Ziggs": "52.59%",
- "Sivir": "52.21%",
- "Ezreal": "51.36%",
- "Tristana": "51.36%",
- "Varus": "50.75%"
- }
+ "counters": "no info",
+ "synergy": [
+ "no data"
+ ]
},
{
"champion_name": "Nautilus",
- "pick_rate": "10.9%",
+ "pick_rate": "11.0%",
"counters": {
- "Taric": "55.98%",
- "Rell": "55.66%",
- "Renata Glasc": "55.13%",
- "Sylas": "55.09%",
- "Braum": "54.08%",
- "Rakan": "54.06%",
- "Vel'Koz": "53.45%",
- "Swain": "53.09%",
- "Neeko": "52.97%",
- "Alistar": "52.87%"
- },
- "synergy": {
- "Seraphine": "58.68%",
- "Ziggs": "55.43%",
- "Karthus": "55.21%",
- "Lucian": "51.71%",
- "Tristana": "51.50%",
- "Samira": "51.14%",
- "Kalista": "50.71%",
- "Jinx": "50.13%"
+ "Taric": "60.99%",
+ "Leona": "57.54%",
+ "Braum": "56.73%",
+ "Renata Glasc": "55.96%",
+ "Alistar": "55.65%",
+ "Rell": "55.38%",
+ "Camille": "54.74%",
+ "Pantheon": "54.53%",
+ "Swain": "54.35%",
+ "Maokai": "53.72%"
+ },
+ "synergy": {
+ "Seraphine": "55.21%",
+ "Senna": "54.59%",
+ "Kalista": "52.55%",
+ "Xayah": "52.25%",
+ "Caitlyn": "51.40%",
+ "Karthus": "51.11%",
+ "Twitch": "50.96%",
+ "Lucian": "50.86%",
+ "Jhin": "50.64%",
+ "Draven": "50.62%",
+ "Vayne": "50.27%",
+ "Zeri": "50.21%",
+ "Samira": "50.09%",
+ "Nilah": "50.00%"
}
},
{
"champion_name": "Janna",
- "pick_rate": "9.6%",
- "counters": {
- "Senna": "50.79%",
- "Sona": "50.1%",
- "Soraka": "50.01%",
- "Seraphine": "49.87%",
- "Milio": "49.71%",
- "Neeko": "49.67%",
- "Rell": "49.5%",
- "Bard": "49.44%",
- "Rakan": "49.42%",
- "Blitzcrank": "49.4%"
- },
- "synergy": {
- "Seraphine": "58.66%",
- "Vayne": "55.43%",
- "Yasuo": "54.91%",
- "Ziggs": "54.85%",
- "Jinx": "53.96%",
- "Ashe": "53.85%",
- "Tristana": "53.63%",
- "Kalista": "53.47%",
- "Kog'Maw": "52.89%",
- "Karthus": "52.83%",
- "Jhin": "52.65%",
- "Ezreal": "52.58%",
- "Lucian": "52.51%",
- "Draven": "52.25%",
- "Twitch": "52.00%",
- "Samira": "51.88%",
- "Varus": "51.73%",
- "Zeri": "51.64%",
- "Caitlyn": "51.53%",
- "Kai'Sa": "50.95%"
+ "pick_rate": "10.7%",
+ "counters": {
+ "Camille": "53.25%",
+ "Sona": "51.89%",
+ "Blitzcrank": "51.2%",
+ "Maokai": "51.02%",
+ "Braum": "49.67%",
+ "Pyke": "49.38%",
+ "Renata Glasc": "49.3%",
+ "Senna": "49.25%",
+ "Ashe": "49.07%",
+ "Nami": "48.64%"
+ },
+ "synergy": {
+ "Nilah": "57.38%",
+ "Twitch": "57.02%",
+ "Senna": "56.68%",
+ "Seraphine": "55.59%",
+ "Miss Fortune": "54.71%",
+ "Zeri": "54.30%",
+ "Tristana": "54.22%",
+ "Draven": "54.22%",
+ "Twisted Fate": "53.57%",
+ "Samira": "53.57%",
+ "Jhin": "53.03%",
+ "Lucian": "52.84%",
+ "Karthus": "52.73%",
+ "Ezreal": "52.60%",
+ "Yasuo": "52.58%",
+ "Kalista": "52.35%",
+ "Vayne": "51.97%",
+ "Ashe": "51.80%",
+ "Sivir": "51.10%",
+ "Caitlyn": "51.05%",
+ "Smolder": "50.23%",
+ "Aphelios": "50.23%"
}
},
{
- "champion_name": "Blitzcrank",
- "pick_rate": "8.8%",
- "counters": {
- "Taric": "56.85%",
- "Rakan": "52.88%",
- "Leona": "52.47%",
- "Braum": "52.28%",
- "Renata Glasc": "51.05%",
- "Maokai": "51.04%",
- "Zyra": "50.98%",
- "Rell": "50.78%",
- "Alistar": "50.73%",
- "Zilean": "50.7%"
- },
+ "champion_name": "Karma",
+ "pick_rate": "9.6%",
+ "counters": "no info",
"synergy": {
- "Nilah": "58.59%",
- "Xayah": "55.50%",
- "Draven": "54.17%",
- "Lucian": "53.85%",
- "Ashe": "53.42%",
- "Vayne": "52.91%",
- "Twitch": "52.80%",
- "Seraphine": "52.34%",
- "Jinx": "51.90%",
- "Karthus": "51.89%",
- "Varus": "51.87%",
- "Tristana": "51.72%",
- "Miss Fortune": "51.53%",
- "Zeri": "51.27%",
- "Aphelios": "50.95%",
- "Kalista": "50.84%",
- "Jhin": "50.74%",
- "Ezreal": "50.08%",
- "Caitlyn": "50.00%"
+ "Tristana": "54.41%",
+ "Ashe": "52.79%",
+ "Twisted Fate": "52.57%",
+ "Jinx": "52.21%",
+ "Miss Fortune": "51.67%",
+ "Kalista": "51.02%",
+ "Twitch": "50.19%",
+ "Caitlyn": "50.15%",
+ "Zeri": "50.00%"
}
},
{
- "champion_name": "Pyke",
- "pick_rate": "7.6%",
- "counters": {
- "Rakan": "53.38%",
- "Maokai": "52.78%",
- "Neeko": "52.1%",
- "Rell": "52.08%",
- "Taric": "51.82%",
- "Janna": "51.79%",
- "Blitzcrank": "51.44%",
- "Alistar": "50.94%",
- "Bard": "50.94%",
- "Thresh": "50.49%"
- },
+ "champion_name": "Thresh",
+ "pick_rate": "9.4%",
+ "counters": "no info",
"synergy": {
- "Karthus": "61.98%",
- "Nilah": "60.43%",
- "Yasuo": "57.30%",
- "Tristana": "55.96%",
- "Twitch": "55.08%",
- "Ashe": "54.27%",
- "Swain": "53.91%",
- "Ziggs": "53.28%",
- "Seraphine": "52.82%",
- "Vayne": "52.64%",
- "Varus": "51.58%",
- "Jhin": "51.51%",
- "Ezreal": "51.49%",
- "Hwei": "51.43%",
- "Draven": "50.95%"
+ "Yasuo": "62.82%",
+ "Senna": "58.33%",
+ "Xayah": "56.52%",
+ "Sivir": "56.25%",
+ "Seraphine": "55.74%",
+ "Kalista": "54.41%",
+ "Twitch": "52.68%",
+ "Miss Fortune": "52.44%",
+ "Jinx": "52.43%",
+ "Draven": "52.37%",
+ "Aphelios": "52.12%",
+ "Smolder": "51.38%",
+ "Tristana": "51.11%",
+ "Samira": "50.59%",
+ "Varus": "50.32%",
+ "Vayne": "50.15%",
+ "Kai'Sa": "50.05%"
}
},
{
- "champion_name": "Karma",
- "pick_rate": "7.6%",
- "counters": {
- "Pyke": "57.3%",
- "Maokai": "55.33%",
- "Zyra": "54.97%",
- "Blitzcrank": "54.92%",
- "Rakan": "54.52%",
- "Janna": "54.36%",
- "Sona": "54.33%",
- "Nautilus": "54.21%",
- "Soraka": "53.61%",
- "Neeko": "52.83%"
- },
+ "champion_name": "Pyke",
+ "pick_rate": "8.5%",
+ "counters": "no info",
"synergy": {
- "Draven": "53.57%",
- "Vayne": "51.30%",
- "Jhin": "50.49%",
- "Ezreal": "50.43%",
- "Tristana": "50.42%"
+ "Karthus": "60.47%",
+ "Swain": "58.06%",
+ "Senna": "57.89%",
+ "Ashe": "55.02%",
+ "Twitch": "54.55%",
+ "Vayne": "53.90%",
+ "Jinx": "53.67%",
+ "Yasuo": "53.66%",
+ "Seraphine": "53.19%",
+ "Twisted Fate": "53.07%",
+ "Nilah": "53.00%",
+ "Smolder": "51.63%",
+ "Zeri": "50.56%",
+ "Ezreal": "50.22%",
+ "Draven": "50.00%"
}
},
{
- "champion_name": "Lulu",
- "pick_rate": "7.4%",
- "counters": {
- "Taric": "56.84%",
- "Zilean": "54.43%",
- "Neeko": "54.04%",
- "Senna": "54.04%",
- "Sona": "53.78%",
- "Zyra": "53.7%",
- "Maokai": "53.46%",
- "Blitzcrank": "52.96%",
- "Bard": "52.89%",
- "Thresh": "52.88%"
- },
- "synergy": {
- "Miss Fortune": "55.96%",
- "Sivir": "55.62%",
- "Tristana": "55.22%",
- "Ashe": "51.59%",
- "Draven": "51.52%",
- "Varus": "51.22%",
- "Twitch": "51.14%",
- "Jinx": "50.76%",
- "Kog'Maw": "50.00%",
- "Kalista": "50.00%"
+ "champion_name": "Blitzcrank",
+ "pick_rate": "8.1%",
+ "counters": {
+ "Pantheon": "54.85%",
+ "Leona": "54.08%",
+ "Maokai": "53.66%",
+ "Braum": "53.61%",
+ "Rakan": "53.26%",
+ "Taric": "53.13%",
+ "Alistar": "52.33%",
+ "Shaco": "51.5%",
+ "Rell": "50.9%",
+ "Zilean": "50.57%"
+ },
+ "synergy": {
+ "Seraphine": "65.22%",
+ "Karthus": "57.78%",
+ "Draven": "56.57%",
+ "Vayne": "55.39%",
+ "Varus": "54.74%",
+ "Jinx": "54.30%",
+ "Twisted Fate": "54.17%",
+ "Tristana": "53.18%",
+ "Sivir": "53.06%",
+ "Senna": "52.85%",
+ "Aphelios": "52.14%",
+ "Kai'Sa": "51.65%",
+ "Lucian": "50.78%",
+ "Nilah": "50.67%",
+ "Twitch": "50.00%",
+ "Ashe": "50.00%",
+ "Zeri": "50.00%"
}
},
{
"champion_name": "Milio",
- "pick_rate": "6.7%",
- "counters": {
- "Taric": "54.79%",
- "Blitzcrank": "53.66%",
- "Braum": "53.22%",
- "Vel'Koz": "53.08%",
- "Senna": "52.78%",
- "Bard": "52.75%",
- "Neeko": "52.17%",
- "Rakan": "51.91%",
- "Soraka": "51.52%",
- "Thresh": "51.33%"
+ "pick_rate": "8.1%",
+ "counters": {
+ "Blitzcrank": "56.32%",
+ "Braum": "55.45%",
+ "Maokai": "54.37%",
+ "Camille": "53.86%",
+ "Janna": "53.43%",
+ "Thresh": "53.37%",
+ "Zilean": "53.04%",
+ "Tahm Kench": "53.04%",
+ "Vel'Koz": "52.98%",
+ "Leona": "52.96%"
},
- "synergy": {
- "Kog'Maw": "53.82%",
- "Xayah": "52.76%",
- "Lucian": "51.94%",
- "Zeri": "51.56%",
- "Vayne": "51.56%",
- "Kalista": "51.56%",
- "Ashe": "51.48%",
- "Draven": "50.69%",
- "Aphelios": "50.42%",
- "Twitch": "50.00%"
- }
- },
- {
- "champion_name": "Nami",
- "pick_rate": "6.5%",
- "counters": {
- "Maokai": "56.37%",
- "Neeko": "53.14%",
- "Blitzcrank": "53.03%",
- "Rakan": "52.58%",
- "Zyra": "52.29%",
- "Soraka": "51.92%",
- "Milio": "51.67%",
- "Pyke": "51.56%",
- "Janna": "51.44%",
- "Bard": "51.33%"
- },
- "synergy": {
- "Swain": "59.52%",
- "Brand": "55.69%",
- "Karthus": "55.65%",
- "Twitch": "52.16%",
- "Ashe": "51.88%",
- "Seraphine": "51.43%",
- "Jhin": "51.32%",
- "Vayne": "51.29%",
- "Ezreal": "51.27%"
- }
+ "synergy": [
+ "no data"
+ ]
},
{
- "champion_name": "Bard",
- "pick_rate": "6.1%",
- "counters": {
- "Taric": "52.2%",
- "Janna": "50.56%",
- "Maokai": "50.28%",
- "Zilean": "49.95%",
- "Senna": "49.94%",
- "Blitzcrank": "49.89%",
- "Renata Glasc": "49.52%",
- "Soraka": "49.45%",
- "Rell": "49.3%",
- "Pyke": "49.06%"
+ "champion_name": "Lulu",
+ "pick_rate": "7.9%",
+ "counters": {
+ "Vel'Koz": "57.35%",
+ "Blitzcrank": "56.23%",
+ "Zilean": "55.78%",
+ "Tahm Kench": "55.43%",
+ "Renata Glasc": "54.86%",
+ "Rell": "54.7%",
+ "Janna": "54.6%",
+ "Maokai": "54.51%",
+ "Braum": "53.79%",
+ "Ashe": "53.65%"
},
- "synergy": {
- "Nilah": "59.77%",
- "Vayne": "55.15%",
- "Caitlyn": "55.12%",
- "Twitch": "55.06%",
- "Ziggs": "54.37%",
- "Tristana": "53.92%",
- "Karthus": "53.62%",
- "Seraphine": "53.05%",
- "Lucian": "52.97%",
- "Ezreal": "52.51%",
- "Ashe": "52.44%",
- "Jhin": "51.89%",
- "Swain": "51.69%",
- "Jinx": "51.34%",
- "Zeri": "50.76%"
- }
+ "synergy": [
+ "no data"
+ ]
},
{
- "champion_name": "Alistar",
- "pick_rate": "5.8%",
- "counters": {
- "Sona": "54.51%",
- "Janna": "54.04%",
- "Swain": "53.24%",
- "Vel'Koz": "52.78%",
- "Senna": "52.64%",
- "Zilean": "52.39%",
- "Soraka": "52.22%",
- "Seraphine": "51.97%",
- "Xerath": "51.86%",
- "Milio": "51.85%"
- },
- "synergy": {
- "Seraphine": "59.43%",
- "Ziggs": "57.86%",
- "Sivir": "55.20%",
- "Hwei": "54.65%",
- "Zeri": "53.37%",
- "Ashe": "52.55%",
- "Yasuo": "52.42%",
- "Vayne": "52.01%",
- "Twitch": "51.83%",
- "Xayah": "51.49%",
- "Jinx": "50.82%",
- "Jhin": "50.68%",
- "Ezreal": "50.58%",
- "Draven": "50.28%",
- "Miss Fortune": "50.20%"
- }
+ "champion_name": "Rakan",
+ "pick_rate": "7.7%",
+ "counters": "no info",
+ "synergy": [
+ "no data"
+ ]
},
{
- "champion_name": "Ashe",
+ "champion_name": "Bard",
"pick_rate": "5.7%",
"counters": {
- "Pyke": "56.98%",
- "Sylas": "54.95%",
- "Blitzcrank": "54.83%",
- "Heimerdinger": "54.8%",
- "Rell": "54.67%",
- "Nautilus": "54.35%",
- "Bard": "53.44%",
- "Milio": "53.1%",
- "Rakan": "52.69%",
- "Brand": "52.5%"
- },
- "synergy": {
- "Karthus": "56.83%",
- "Seraphine": "54.60%",
- "Jinx": "54.17%",
- "Nilah": "53.85%",
- "Twitch": "53.55%",
- "Ezreal": "51.69%",
- "Swain": "51.32%",
- "Ziggs": "50.79%",
- "Lucian": "50.00%",
- "Zeri": "50.00%"
+ "Maokai": "56.1%",
+ "Renata Glasc": "53.77%",
+ "Janna": "53.45%",
+ "Pyke": "51.79%",
+ "Morgana": "51.04%",
+ "Nami": "50.92%",
+ "Pantheon": "50.72%",
+ "Blitzcrank": "50.43%",
+ "Brand": "50.34%",
+ "Senna": "50.21%"
+ },
+ "synergy": {
+ "Yasuo": "60.34%",
+ "Seraphine": "59.53%",
+ "Senna": "59.42%",
+ "Ziggs": "57.58%",
+ "Caitlyn": "55.43%",
+ "Xayah": "54.26%",
+ "Hwei": "54.17%",
+ "Twisted Fate": "53.66%",
+ "Jinx": "52.43%",
+ "Twitch": "51.68%",
+ "Aphelios": "51.54%",
+ "Karthus": "51.47%",
+ "Samira": "50.60%",
+ "Vayne": "50.34%"
}
},
{
- "champion_name": "Soraka",
+ "champion_name": "Nami",
"pick_rate": "5.5%",
"counters": {
- "Blitzcrank": "54.17%",
- "Neeko": "51.31%",
- "Sona": "51.25%",
- "Zyra": "51.13%",
- "Vel'Koz": "51.05%",
- "Bard": "50.55%",
- "Senna": "50.38%",
- "Yuumi": "50.06%",
- "Janna": "49.99%",
- "Pyke": "49.98%"
+ "Morgana": "56.08%",
+ "Blitzcrank": "55.61%",
+ "Maokai": "55.57%",
+ "Vel'Koz": "54.47%",
+ "Rakan": "54.44%",
+ "Neeko": "54.21%",
+ "Rell": "53.9%",
+ "Nautilus": "53.32%",
+ "Thresh": "53.28%",
+ "Senna": "53.17%"
},
- "synergy": {
- "Yasuo": "63.54%",
- "Swain": "59.70%",
- "Samira": "58.41%",
- "Vayne": "57.03%",
- "Ashe": "53.73%",
- "Jinx": "53.47%",
- "Ziggs": "53.38%",
- "Jhin": "53.17%",
- "Tristana": "52.80%",
- "Lucian": "52.34%",
- "Miss Fortune": "51.96%",
- "Twitch": "51.63%",
- "Draven": "50.30%"
- }
- },
- {
- "champion_name": "Lux",
- "pick_rate": "5.1%",
- "counters": {
- "Blitzcrank": "56.57%",
- "Maokai": "54.94%",
- "Zyra": "54.66%",
- "Shaco": "54.45%",
- "Vel'Koz": "54.1%",
- "Sylas": "53.87%",
- "Zilean": "53.51%",
- "Pyke": "53.5%",
- "Xerath": "52.75%",
- "Nami": "52.72%"
- },
- "synergy": {
- "Karthus": "59.46%",
- "Twitch": "53.57%",
- "Xayah": "52.17%",
- "Ashe": "51.05%",
- "Varus": "50.88%",
- "Tristana": "50.67%",
- "Jinx": "50.00%",
- "Zeri": "50.00%"
- }
+ "synergy": [
+ "no data"
+ ]
},
{
- "champion_name": "Renata Glasc",
- "pick_rate": "5.0%",
+ "champion_name": "Soraka",
+ "pick_rate": "5.4%",
"counters": "no info",
"synergy": [
"no data"
]
},
{
- "champion_name": "Rell",
- "pick_rate": "4.9%",
+ "champion_name": "Ashe",
+ "pick_rate": "4.8%",
"counters": {
- "Poppy": "53.79%",
- "Seraphine": "53.57%",
- "Soraka": "52.57%",
- "Zilean": "52.54%",
- "Alistar": "52.45%",
- "Shaco": "51.5%",
- "Maokai": "50.89%",
- "Sona": "50.75%",
- "Bard": "50.7%",
- "Janna": "50.5%"
+ "Camille": "58.72%",
+ "Maokai": "57.34%",
+ "Pyke": "56.14%",
+ "Blitzcrank": "55.88%",
+ "Nautilus": "55.46%",
+ "Taric": "55.41%",
+ "Vel'Koz": "54.55%",
+ "Zyra": "54.13%",
+ "Soraka": "53.87%",
+ "Xerath": "53.83%"
},
"synergy": {
- "Ziggs": "61.31%",
- "Karthus": "60.18%",
- "Seraphine": "59.02%",
- "Tristana": "58.16%",
- "Lucian": "57.30%",
- "Twitch": "56.30%",
- "Miss Fortune": "54.44%",
- "Ashe": "53.88%",
- "Sivir": "53.49%",
- "Jinx": "52.83%",
- "Draven": "52.25%",
- "Xayah": "52.10%",
- "Ezreal": "52.08%",
- "Kai'Sa": "51.33%",
- "Samira": "51.15%",
- "Zeri": "50.35%",
- "Yasuo": "50.25%",
- "Aphelios": "50.21%"
+ "Jinx": "53.73%",
+ "Jhin": "53.36%",
+ "Karthus": "52.31%",
+ "Kalista": "51.90%",
+ "Twisted Fate": "51.81%",
+ "Zeri": "50.65%"
}
},
{
- "champion_name": "Yuumi",
- "pick_rate": "4.9%",
- "counters": {
- "Maokai": "61.28%",
- "Rell": "59.85%",
- "Taric": "57.47%",
- "Alistar": "57.31%",
- "Nautilus": "56.64%",
- "Rakan": "55.78%",
- "Pyke": "55.71%",
- "Leona": "55.63%",
- "Sylas": "55.27%",
- "Thresh": "55.23%"
- },
+ "champion_name": "Alistar",
+ "pick_rate": "4.7%",
+ "counters": "no info",
"synergy": {
- "Seraphine": "66.67%",
- "Yasuo": "53.25%",
- "Tristana": "52.55%",
- "Vayne": "51.74%",
- "Zeri": "51.45%",
- "Twitch": "50.41%"
+ "Karthus": "64.86%",
+ "Seraphine": "62.86%",
+ "Zeri": "60.83%",
+ "Sivir": "59.26%",
+ "Jinx": "58.42%",
+ "Yasuo": "58.09%",
+ "Tristana": "55.84%",
+ "Senna": "53.27%",
+ "Twitch": "52.99%",
+ "Draven": "51.74%",
+ "Xayah": "51.25%",
+ "Ezreal": "50.91%",
+ "Twisted Fate": "50.75%",
+ "Samira": "50.00%"
}
},
{
"champion_name": "Leona",
- "pick_rate": "4.8%",
- "counters": {
- "Taric": "56.7%",
- "Janna": "54.1%",
- "Bard": "54.03%",
- "Shaco": "53.67%",
- "Maokai": "53.55%",
- "Rell": "53.22%",
- "Soraka": "53.11%",
- "Lux": "53.09%",
- "Morgana": "53.02%",
- "Braum": "51.72%"
- },
+ "pick_rate": "4.4%",
+ "counters": "no info",
"synergy": {
- "Seraphine": "56.47%",
- "Xayah": "55.00%",
- "Zeri": "54.63%",
- "Ezreal": "54.35%",
- "Twitch": "54.27%",
- "Caitlyn": "53.68%",
- "Jhin": "53.21%",
- "Yasuo": "53.19%",
- "Lucian": "52.72%",
- "Miss Fortune": "51.92%",
- "Aphelios": "51.70%",
- "Tristana": "51.48%",
- "Kalista": "51.16%"
+ "Swain": "68.09%",
+ "Senna": "60.41%",
+ "Jinx": "59.02%",
+ "Seraphine": "58.90%",
+ "Ashe": "57.67%",
+ "Aphelios": "56.67%",
+ "Nilah": "56.52%",
+ "Vayne": "55.20%",
+ "Lucian": "54.63%",
+ "Smolder": "54.42%",
+ "Miss Fortune": "53.39%",
+ "Karthus": "53.06%",
+ "Xayah": "51.61%",
+ "Ezreal": "51.04%",
+ "Kalista": "50.59%",
+ "Twisted Fate": "50.31%"
}
},
{
- "champion_name": "Xerath",
- "pick_rate": "4.8%",
- "counters": {
- "Sylas": "55%",
- "Maokai": "53.98%",
- "Sona": "53.77%",
- "Janna": "53.15%",
- "Pyke": "52.84%",
- "Soraka": "52.33%",
- "Rakan": "52.02%",
- "Leona": "51.76%",
- "Zyra": "51.72%",
- "Nami": "51.6%"
- },
- "synergy": {
- "Ziggs": "55.24%",
- "Jinx": "53.95%",
- "Vayne": "53.32%",
- "Samira": "52.63%",
- "Xayah": "52.57%",
- "Ashe": "52.33%",
- "Jhin": "51.81%",
- "Caitlyn": "51.12%",
- "Hwei": "50.88%",
- "Tristana": "50.70%",
- "Ezreal": "50.00%"
+ "champion_name": "Yuumi",
+ "pick_rate": "4.4%",
+ "counters": {
+ "Rell": "62.65%",
+ "Braum": "58.82%",
+ "Blitzcrank": "58.05%",
+ "Leona": "57.77%",
+ "Pyke": "57.61%",
+ "Taric": "57.46%",
+ "Swain": "56.85%",
+ "Rakan": "56.72%",
+ "Maokai": "56.12%",
+ "Janna": "55.85%"
+ },
+ "synergy": {
+ "Rammus": "64.71%",
+ "Jinx": "55.32%",
+ "Vayne": "54.26%",
+ "Samira": "53.57%",
+ "Lucian": "53.52%",
+ "Tristana": "53.33%",
+ "Twitch": "53.31%",
+ "Jhin": "50.00%",
+ "Xayah": "50.00%"
}
},
{
"champion_name": "Zyra",
- "pick_rate": "3.8%",
- "counters": {
- "Sona": "53.56%",
- "Pyke": "53.25%",
- "Heimerdinger": "53.11%",
- "Bard": "53.07%",
- "Leona": "51.91%",
- "Janna": "51.88%",
- "Shaco": "51.56%",
- "Taric": "51.43%",
- "Rakan": "50.98%",
- "Nautilus": "50.75%"
- },
- "synergy": {
- "Seraphine": "55.06%",
- "Vayne": "54.84%",
- "Tristana": "54.05%",
- "Twitch": "52.96%",
- "Samira": "51.98%",
- "Jhin": "51.51%",
- "Karthus": "51.16%",
- "Draven": "50.65%",
- "Xayah": "50.00%"
+ "pick_rate": "4.3%",
+ "counters": {
+ "Taric": "54.73%",
+ "Leona": "54.05%",
+ "Sona": "53.31%",
+ "Maokai": "52.98%",
+ "Renata Glasc": "52.84%",
+ "Pyke": "52.6%",
+ "Zac": "52.59%",
+ "Janna": "52.42%",
+ "Zilean": "51.88%",
+ "Yuumi": "51.63%"
+ },
+ "synergy": {
+ "Xayah": "66.07%",
+ "Sivir": "61.54%",
+ "Twisted Fate": "53.68%",
+ "Miss Fortune": "53.25%",
+ "Ezreal": "52.52%",
+ "Senna": "52.28%",
+ "Tristana": "50.94%",
+ "Vayne": "50.18%",
+ "Jinx": "50.00%"
}
},
{
- "champion_name": "Braum",
- "pick_rate": "3.6%",
- "counters": {
- "Zilean": "55.95%",
- "Soraka": "54.2%",
- "Bard": "53.76%",
- "Rell": "53.74%",
- "Taric": "53.44%",
- "Sona": "53.2%",
- "Senna": "53.06%",
- "Rakan": "52.88%",
- "Janna": "52.49%",
- "Renata Glasc": "52.31%"
- },
+ "champion_name": "Rell",
+ "pick_rate": "4.1%",
+ "counters": "no info",
"synergy": {
- "Sivir": "60.82%",
- "Twitch": "59.55%",
- "Xayah": "54.40%",
- "Varus": "52.69%",
- "Kog'Maw": "52.03%",
- "Ashe": "51.92%",
- "Jinx": "51.32%",
- "Tristana": "50.94%",
- "Vayne": "50.83%",
- "Draven": "50.75%",
- "Ezreal": "50.35%"
+ "Xayah": "58.33%",
+ "Twisted Fate": "56.72%",
+ "Twitch": "56.48%",
+ "Nilah": "56.25%",
+ "Miss Fortune": "55.88%",
+ "Zeri": "55.45%",
+ "Senna": "55.08%",
+ "Vayne": "53.65%",
+ "Jinx": "53.40%",
+ "Kalista": "52.46%",
+ "Samira": "52.46%",
+ "Aphelios": "52.17%",
+ "Draven": "52.15%",
+ "Tristana": "51.90%",
+ "Karthus": "51.43%",
+ "Ezreal": "51.32%",
+ "Jhin": "50.43%",
+ "Yasuo": "50.00%"
}
},
{
- "champion_name": "Morgana",
- "pick_rate": "3.2%",
+ "champion_name": "Lux",
+ "pick_rate": "3.9%",
+ "counters": {
+ "Camille": "62.93%",
+ "Pantheon": "60%",
+ "Pyke": "58.92%",
+ "Bard": "57.24%",
+ "Shaco": "56.3%",
+ "Blitzcrank": "55.42%",
+ "Maokai": "55.39%",
+ "Leona": "55.08%",
+ "Alistar": "54.69%",
+ "Zilean": "54.61%"
+ },
+ "synergy": {
+ "Ziggs": "57.14%",
+ "Twisted Fate": "56.82%",
+ "Samira": "52.63%",
+ "Senna": "52.05%",
+ "Ezreal": "50.65%",
+ "Nilah": "50.00%"
+ }
+ },
+ {
+ "champion_name": "Braum",
+ "pick_rate": "3.8%",
+ "counters": "no info",
+ "synergy": [
+ "no data"
+ ]
+ },
+ {
+ "champion_name": "Hwei",
+ "pick_rate": "2.9%",
"counters": {
- "Zyra": "56.73%",
- "Senna": "56.35%",
- "Rell": "56.29%",
- "Janna": "55.6%",
- "Milio": "55.2%",
- "Vel'Koz": "55.08%",
- "Nami": "54.67%",
- "Taric": "54.31%",
- "Karma": "54.24%",
- "Maokai": "53.2%"
+ "Camille": "63.76%",
+ "Pyke": "60.47%",
+ "Maokai": "58.72%",
+ "Xerath": "58.7%",
+ "Rakan": "58.47%",
+ "Taric": "58.43%",
+ "Swain": "58.1%",
+ "Zilean": "57.69%",
+ "Vel'Koz": "57.69%",
+ "Janna": "57.32%"
},
"synergy": {
- "Karthus": "55.56%",
- "Nilah": "53.13%",
- "Miss Fortune": "52.07%",
- "Hwei": "51.72%",
- "Vayne": "51.31%",
- "Sivir": "50.91%",
- "Draven": "50.63%",
- "Tristana": "50.00%"
+ "Lucian": "57.14%",
+ "Tristana": "54.55%",
+ "Swain": "53.85%",
+ "Miss Fortune": "51.85%",
+ "Seraphine": "51.02%"
}
},
{
"champion_name": "Zilean",
- "pick_rate": "2.5%",
- "counters": {
- "Janna": "53.27%",
- "Shaco": "53.02%",
- "Senna": "51.99%",
- "Neeko": "51.67%",
- "Maokai": "51.41%",
- "Xerath": "51.07%",
- "Milio": "50.67%",
- "Sylas": "50.34%",
- "Soraka": "50.28%",
- "Vel'Koz": "50.23%"
- },
+ "pick_rate": "2.8%",
+ "counters": "no info",
"synergy": {
- "Swain": "58.97%",
- "Hwei": "55.56%",
- "Samira": "54.12%",
- "Kog'Maw": "54.05%",
- "Twitch": "53.80%",
- "Aphelios": "53.47%",
- "Vayne": "51.88%",
- "Caitlyn": "51.79%",
- "Seraphine": "51.67%",
- "Ezreal": "51.07%",
- "Ashe": "50.12%"
+ "Miss Fortune": "62.03%",
+ "Twisted Fate": "61.05%",
+ "Draven": "54.63%",
+ "Swain": "54.55%",
+ "Vayne": "54.47%",
+ "Ashe": "53.97%",
+ "Caitlyn": "53.13%",
+ "Seraphine": "52.44%",
+ "Smolder": "51.37%",
+ "Xayah": "51.22%",
+ "Sivir": "50.00%"
}
},
{
- "champion_name": "Sona",
- "pick_rate": "2.4%",
- "counters": {
- "Taric": "56.83%",
- "Leona": "55.04%",
- "Seraphine": "53.49%",
- "Blitzcrank": "52.56%",
- "Senna": "51.71%",
- "Bard": "51.4%",
- "Nami": "50.59%",
- "Milio": "50.43%",
- "Zilean": "50.26%",
- "Pyke": "50.19%"
- },
+ "champion_name": "Morgana",
+ "pick_rate": "2.8%",
+ "counters": "no info",
"synergy": {
- "Yasuo": "64.52%",
- "Tristana": "60.29%",
- "Ziggs": "58.51%",
- "Samira": "57.45%",
- "Sivir": "56.60%",
- "Vayne": "56.55%",
- "Nilah": "55.88%",
- "Kog'Maw": "53.49%",
- "Lucian": "53.04%",
- "Xayah": "52.90%",
- "Seraphine": "52.76%",
- "Ezreal": "52.71%",
- "Miss Fortune": "52.23%",
- "Varus": "51.05%",
- "Ashe": "50.93%",
- "Caitlyn": "50.81%",
- "Twitch": "50.21%"
+ "Xayah": "60.00%",
+ "Draven": "57.81%",
+ "Nilah": "54.55%",
+ "Samira": "51.61%",
+ "Sivir": "50.00%",
+ "Seraphine": "50.00%"
}
},
{
- "champion_name": "Hwei",
- "pick_rate": "2.1%",
- "counters": {
- "Taric": "66.91%",
- "Sona": "66.55%",
- "Blitzcrank": "65.66%",
- "Janna": "65.3%",
- "Brand": "65.18%",
- "Pyke": "63.85%",
- "Xerath": "63.02%",
- "Vel'Koz": "62.71%",
- "Morgana": "62.31%",
- "Milio": "62.01%"
- },
- "synergy": {
- "Nilah": "51.61%"
+ "champion_name": "Xerath",
+ "pick_rate": "2.7%",
+ "counters": {
+ "Blitzcrank": "58.63%",
+ "Neeko": "56.43%",
+ "Sona": "56.36%",
+ "Maokai": "56.26%",
+ "Pyke": "56.15%",
+ "Leona": "56.02%",
+ "Braum": "55.06%",
+ "Rakan": "54.83%",
+ "Nautilus": "54.65%",
+ "Janna": "53.86%"
+ },
+ "synergy": {
+ "Samira": "70.00%",
+ "Twisted Fate": "61.36%",
+ "Caitlyn": "57.97%",
+ "Karthus": "55.56%",
+ "Ashe": "52.46%",
+ "Vayne": "50.55%",
+ "Zeri": "50.00%"
}
},
{
- "champion_name": "Brand",
- "pick_rate": "2.0%",
- "counters": {
- "Sona": "58.11%",
- "Maokai": "57.24%",
- "Xerath": "57.03%",
- "Janna": "56.31%",
- "Pyke": "56.01%",
- "Zyra": "55.13%",
- "Leona": "54.96%",
- "Zilean": "54.92%",
- "Soraka": "54.73%",
- "Senna": "54.19%"
- },
+ "champion_name": "Shaco",
+ "pick_rate": "2.6%",
+ "counters": "no info",
"synergy": {
- "Seraphine": "57.69%",
- "Karthus": "55.17%",
- "Yasuo": "55.17%",
- "Kalista": "53.49%",
- "Tristana": "52.31%",
- "Xayah": "51.40%",
- "Varus": "50.17%"
+ "Karthus": "76.92%",
+ "Twisted Fate": "62.64%",
+ "Tristana": "58.62%",
+ "Senna": "56.19%",
+ "Miss Fortune": "56.10%",
+ "Aphelios": "52.78%",
+ "Hwei": "52.63%",
+ "Ashe": "52.48%",
+ "Draven": "52.24%",
+ "Nilah": "50.00%",
+ "Caitlyn": "50.00%",
+ "Ziggs": "50.00%"
}
},
{
- "champion_name": "Seraphine",
- "pick_rate": "2.0%",
+ "champion_name": "Renata Glasc",
+ "pick_rate": "2.4%",
+ "counters": "no info",
+ "synergy": [
+ "no data"
+ ]
+ },
+ {
+ "champion_name": "Camille",
+ "pick_rate": "2.3%",
"counters": {
- "Sylas": "58.33%",
- "Maokai": "57.24%",
- "Zilean": "56.09%",
- "Bard": "55.97%",
- "Pyke": "54.69%",
- "Soraka": "53.72%",
- "Blitzcrank": "53.61%",
- "Yuumi": "53.6%",
- "Milio": "52.99%",
- "Thresh": "52.66%"
- },
- "synergy": {
- "Vayne": "59.62%",
- "Tristana": "59.38%",
- "Miss Fortune": "52.50%",
- "Ashe": "52.20%",
- "Jinx": "51.11%",
- "Draven": "50.94%"
+ "Taric": "58.76%",
+ "Renata Glasc": "58.12%",
+ "Braum": "57.32%",
+ "Leona": "54.48%",
+ "Brand": "54.2%",
+ "Bard": "53.76%",
+ "Zac": "53.03%",
+ "Maokai": "52.96%",
+ "Tahm Kench": "52.94%",
+ "Zilean": "52.78%"
+ },
+ "synergy": {
+ "Twitch": "62.67%",
+ "Sivir": "58.33%",
+ "Zeri": "56.47%",
+ "Tristana": "56.20%",
+ "Senna": "55.81%",
+ "Xayah": "55.26%",
+ "Aphelios": "53.62%",
+ "Nilah": "53.13%",
+ "Seraphine": "52.94%",
+ "Varus": "52.89%",
+ "Karthus": "52.63%",
+ "Ashe": "52.50%",
+ "Smolder": "52.34%",
+ "Jhin": "52.09%",
+ "Draven": "52.06%",
+ "Ziggs": "52.00%",
+ "Twisted Fate": "50.94%",
+ "Vayne": "50.92%",
+ "Lucian": "50.35%",
+ "Swain": "50.00%"
}
},
{
- "champion_name": "Neeko",
+ "champion_name": "Sona",
+ "pick_rate": "2.3%",
+ "counters": {
+ "Zilean": "61.68%",
+ "Rell": "59.62%",
+ "Leona": "57.21%",
+ "Neeko": "55.71%",
+ "Thresh": "55.03%",
+ "Maokai": "54.7%",
+ "Bard": "53.59%",
+ "Braum": "51.81%",
+ "Blitzcrank": "51.79%",
+ "Seraphine": "51.2%"
+ },
+ "synergy": {
+ "Nilah": "71.43%",
+ "Tristana": "63.16%",
+ "Twisted Fate": "56.19%",
+ "Jhin": "55.34%",
+ "Kai'Sa": "55.11%",
+ "Varus": "52.63%",
+ "Aphelios": "52.00%",
+ "Twitch": "51.46%",
+ "Vayne": "51.22%",
+ "Smolder": "50.72%",
+ "Lucian": "50.00%",
+ "Samira": "50.00%"
+ }
+ },
+ {
+ "champion_name": "Brand",
"pick_rate": "1.9%",
- "counters": {
- "Vel'Koz": "60.45%",
- "Sona": "53.26%",
- "Bard": "52.61%",
- "Brand": "52.38%",
- "Taric": "52.17%",
- "Alistar": "51%",
- "Shaco": "50.68%",
- "Senna": "50.42%",
- "Renata Glasc": "50.38%",
- "Morgana": "50.35%"
- },
+ "counters": "no info",
"synergy": {
- "Nilah": "77.78%",
- "Yasuo": "59.46%",
- "Xayah": "59.26%",
- "Hwei": "58.82%",
- "Lucian": "56.79%",
- "Vayne": "53.85%",
- "Twitch": "53.17%",
- "Kalista": "50.75%",
- "Ezreal": "50.72%",
- "Ashe": "50.29%",
- "Quinn": "50.00%",
- "Samira": "50.00%",
- "Varus": "50.00%"
+ "Samira": "59.09%",
+ "Yasuo": "57.89%",
+ "Twisted Fate": "57.14%",
+ "Xayah": "56.52%",
+ "Jinx": "55.88%",
+ "Jhin": "53.98%",
+ "Draven": "53.70%",
+ "Seraphine": "52.50%",
+ "Aphelios": "50.00%"
}
},
{
- "champion_name": "Maokai",
- "pick_rate": "1.6%",
- "counters": {
- "Shaco": "58.18%",
- "Braum": "54.8%",
- "Sona": "53.99%",
- "Janna": "52.72%",
- "Renata Glasc": "52.39%",
- "Zyra": "51.46%",
- "Alistar": "51.45%",
- "Soraka": "50.8%",
- "Taric": "50.42%",
- "Neeko": "50.29%"
- },
+ "champion_name": "Pantheon",
+ "pick_rate": "1.8%",
+ "counters": "no info",
"synergy": {
- "Xayah": "60.00%",
- "Ziggs": "58.33%",
- "Karthus": "57.89%",
- "Kai'Sa": "53.95%",
- "Vayne": "53.51%",
- "Sivir": "52.63%",
- "Jhin": "52.46%",
- "Aphelios": "52.46%",
- "Draven": "52.38%",
- "Ezreal": "52.29%",
- "Samira": "51.81%",
- "Varus": "51.53%",
- "Zeri": "51.02%",
- "Twitch": "50.00%",
- "Ashe": "50.00%",
- "Hwei": "50.00%"
+ "Karthus": "66.67%",
+ "Twisted Fate": "58.82%",
+ "Miss Fortune": "56.60%",
+ "Senna": "56.59%",
+ "Jhin": "55.67%",
+ "Nilah": "54.55%",
+ "Ezreal": "54.37%",
+ "Swain": "53.85%",
+ "Lucian": "53.06%",
+ "Ashe": "52.38%",
+ "Smolder": "52.07%",
+ "Samira": "51.97%",
+ "Jinx": "51.72%"
}
},
{
- "champion_name": "Vel'Koz",
- "pick_rate": "1.6%",
- "counters": {
- "Pyke": "55.14%",
- "Zyra": "54.76%",
- "Seraphine": "54.15%",
- "Blitzcrank": "53.89%",
- "Janna": "53.62%",
- "Bard": "53.22%",
- "Sona": "52.97%",
- "Nami": "52.1%",
- "Maokai": "51.85%",
- "Renata Glasc": "51.64%"
- },
+ "champion_name": "Seraphine",
+ "pick_rate": "1.7%",
+ "counters": "no info",
"synergy": {
- "Karthus": "61.90%",
- "Vayne": "55.34%",
- "Sivir": "54.72%",
- "Ashe": "54.44%",
- "Aphelios": "53.25%",
- "Seraphine": "52.38%",
- "Jhin": "51.92%",
- "Caitlyn": "51.43%",
- "Ezreal": "51.22%",
- "Twitch": "50.39%",
- "Varus": "50.37%",
- "Tristana": "50.00%",
- "Miss Fortune": "50.00%"
+ "Lucian": "61.54%",
+ "Tristana": "61.54%",
+ "Vayne": "58.90%",
+ "Varus": "57.69%",
+ "Jinx": "56.25%",
+ "Miss Fortune": "55.32%",
+ "Senna": "51.51%",
+ "Ezreal": "50.83%",
+ "Samira": "50.00%",
+ "Karthus": "50.00%"
}
},
{
- "champion_name": "Swain",
+ "champion_name": "Neeko",
"pick_rate": "1.6%",
"counters": {
- "Zilean": "57.77%",
- "Sona": "57.32%",
- "Vel'Koz": "57.25%",
- "Janna": "56.41%",
- "Brand": "55.87%",
- "Yuumi": "55.78%",
- "Zyra": "55.78%",
- "Xerath": "55.48%",
- "Soraka": "55.34%",
- "Senna": "55.32%"
- },
- "synergy": {
- "Tristana": "64.29%",
- "Karthus": "54.55%",
- "Vayne": "54.01%",
- "Jhin": "53.18%",
- "Twitch": "50.70%",
- "Nilah": "50.00%",
- "Ziggs": "50.00%"
+ "Zilean": "60.66%",
+ "Taric": "58.7%",
+ "Zac": "57.78%",
+ "Leona": "57.76%",
+ "Pantheon": "57.14%",
+ "Janna": "56.74%",
+ "Blitzcrank": "56.38%",
+ "Bard": "55.85%",
+ "Camille": "55.41%",
+ "Rell": "55.19%"
+ },
+ "synergy": {
+ "Yasuo": "67.74%",
+ "Tristana": "66.67%",
+ "Twitch": "65.63%",
+ "Ashe": "60.87%",
+ "Senna": "59.22%",
+ "Nilah": "57.14%",
+ "Caitlyn": "54.55%",
+ "Draven": "51.52%",
+ "Twisted Fate": "50.94%",
+ "Vayne": "50.00%",
+ "Jinx": "50.00%",
+ "Miss Fortune": "50.00%"
}
},
{
"champion_name": "Taric",
- "pick_rate": "1.4%",
+ "pick_rate": "1.5%",
"counters": {
- "Soraka": "55.26%",
- "Rell": "52.91%",
- "Vel'Koz": "52.75%",
- "Janna": "50.88%",
- "Zilean": "50.55%",
- "Rakan": "49.62%",
- "Maokai": "49.58%",
- "Nami": "49.42%",
- "Xerath": "49.28%",
- "Senna": "49.24%"
- },
- "synergy": {
- "Karthus": "70.83%",
- "Yasuo": "68.91%",
- "Lee Sin": "68.18%",
- "Master Yi": "59.26%",
- "Kog'Maw": "58.82%",
- "Sivir": "58.62%",
- "Varus": "56.63%",
- "Swain": "56.45%",
- "Seraphine": "56.41%",
- "Kalista": "55.94%",
- "Ziggs": "55.00%",
- "Vayne": "54.01%",
- "Jinx": "52.38%",
- "Lucian": "51.91%",
- "Kai'Sa": "51.81%",
- "Ashe": "51.67%",
- "Miss Fortune": "50.94%",
- "Ezreal": "50.20%",
- "Twitch": "50.00%"
+ "Swain": "57.63%",
+ "Sona": "57.32%",
+ "Janna": "54.88%",
+ "Nami": "54.27%",
+ "Lux": "53.54%",
+ "Seraphine": "53.45%",
+ "Bard": "52.97%",
+ "Soraka": "52.32%",
+ "Thresh": "52.21%",
+ "Braum": "51.67%"
+ },
+ "synergy": {
+ "Vayne": "60.16%",
+ "Ashe": "58.46%",
+ "Yasuo": "58.33%",
+ "Twisted Fate": "57.89%",
+ "Pyke": "57.14%",
+ "Senna": "56.59%",
+ "Nilah": "56.32%",
+ "Kalista": "55.13%",
+ "Jinx": "54.39%",
+ "Aphelios": "53.57%",
+ "Seraphine": "53.13%",
+ "Zeri": "52.54%",
+ "Twitch": "52.05%",
+ "Smolder": "51.89%",
+ "Ezreal": "51.67%",
+ "Kai'Sa": "50.59%",
+ "Sivir": "50.00%",
+ "Lucian": "50.00%",
+ "Dr. Mundo": "50.00%",
+ "Xayah": "50.00%",
+ "Karthus": "50.00%"
}
},
{
- "champion_name": "Shaco",
- "pick_rate": "1.1%",
- "counters": {
- "Sona": "60.95%",
- "Swain": "60.22%",
- "Taric": "59.68%",
- "Heimerdinger": "59.02%",
- "Zac": "56.14%",
- "Janna": "55.96%",
- "Milio": "54.49%",
- "Pyke": "54.23%",
- "Rakan": "53.2%",
- "Bard": "52.59%"
- },
+ "champion_name": "Vel'Koz",
+ "pick_rate": "1.4%",
+ "counters": "no info",
"synergy": {
- "Seraphine": "85.71%",
- "Miss Fortune": "58.33%",
- "Samira": "54.84%",
- "Caitlyn": "54.43%",
- "Jhin": "54.01%",
- "Varus": "52.43%",
+ "Samira": "65.00%",
+ "Nilah": "60.00%",
+ "Hwei": "58.82%",
+ "Seraphine": "54.55%",
+ "Draven": "53.66%",
+ "Ashe": "53.52%",
+ "Tristana": "53.33%",
+ "Zeri": "53.13%",
+ "Senna": "51.90%",
+ "Kai'Sa": "51.02%",
+ "Jhin": "50.71%",
+ "Smolder": "50.22%",
+ "Sivir": "50.00%",
"Karthus": "50.00%"
}
},
{
- "champion_name": "Heimerdinger",
- "pick_rate": "1.0%",
+ "champion_name": "Swain",
+ "pick_rate": "1.4%",
"counters": {
- "Maokai": "61.64%",
- "Neeko": "56.82%",
- "Seraphine": "55.95%",
- "Xerath": "54.86%",
- "Bard": "54.55%",
- "Leona": "54.4%",
- "Rell": "53.15%",
- "Rakan": "52.67%",
- "Lulu": "52.56%",
- "Janna": "52.56%"
+ "Zac": "58.97%",
+ "Sona": "58.67%",
+ "Neeko": "57.41%",
+ "Nami": "57.14%",
+ "Milio": "56.44%",
+ "Pantheon": "55.95%",
+ "Zyra": "55.49%",
+ "Morgana": "55.24%",
+ "Leona": "55%",
+ "Camille": "54.79%"
},
- "synergy": {
- "Zeri": "68.00%",
- "Sivir": "57.89%",
- "Draven": "54.29%",
- "Jhin": "54.03%",
- "Vayne": "53.49%",
- "Miss Fortune": "52.78%",
- "Ashe": "50.34%",
- "Kalista": "50.00%",
- "Twitch": "50.00%"
- }
+ "synergy": [
+ "no data"
+ ]
},
{
- "champion_name": "Sylas",
- "pick_rate": "1.0%",
- "counters": {
- "Sona": "60%",
- "Janna": "57.91%",
- "Vel'Koz": "57.75%",
- "Braum": "57.52%",
- "Morgana": "57.5%",
- "Taric": "55.56%",
- "Rell": "54.55%",
- "Rakan": "53.89%",
- "Blitzcrank": "53.74%",
- "Thresh": "53.64%"
- },
+ "champion_name": "Tahm Kench",
+ "pick_rate": "1.1%",
+ "counters": "no info",
"synergy": {
- "Karthus": "60.00%",
- "Aphelios": "58.06%",
- "Nilah": "57.89%",
- "Miss Fortune": "57.14%",
- "Kalista": "55.36%",
- "Lucian": "54.65%",
- "Seraphine": "54.55%",
- "Sivir": "52.94%",
- "Zeri": "52.63%",
- "Vayne": "52.53%",
- "Jhin": "52.38%",
- "Ezreal": "51.14%",
- "Twitch": "50.77%",
- "Yasuo": "50.00%"
+ "Kalista": "68.75%",
+ "Jinx": "65.00%",
+ "Nilah": "62.50%",
+ "Xayah": "62.50%",
+ "Twitch": "57.14%",
+ "Varus": "55.10%",
+ "Twisted Fate": "54.55%",
+ "Zeri": "53.85%",
+ "Senna": "52.51%",
+ "Seraphine": "50.00%"
}
},
{
- "champion_name": "Pantheon",
+ "champion_name": "Zac",
"pick_rate": "1.0%",
"counters": {
- "Neeko": "59.74%",
- "Maokai": "58.43%",
- "Karma": "57.22%",
- "Bard": "57.14%",
- "Swain": "56.25%",
- "Rakan": "55.92%",
- "Brand": "55.83%",
- "Soraka": "55.74%",
- "Sona": "55.56%",
- "Janna": "55.42%"
- },
- "synergy": {
- "Miss Fortune": "63.41%",
+ "Pantheon": "61.36%",
+ "Taric": "58.82%",
+ "Zilean": "58.49%",
+ "Rell": "56.82%",
+ "Bard": "56.55%",
+ "Morgana": "55.93%",
+ "Janna": "54.44%",
+ "Maokai": "53.44%",
+ "Milio": "53.25%",
+ "Thresh": "53.18%"
+ },
+ "synergy": {
+ "Aphelios": "83.33%",
+ "Caitlyn": "66.67%",
+ "Draven": "65.52%",
+ "Senna": "64.71%",
+ "Tristana": "61.54%",
+ "Twisted Fate": "61.29%",
+ "Zeri": "58.82%",
"Seraphine": "56.25%",
- "Yasuo": "54.55%",
- "Swain": "52.63%",
- "Varus": "52.05%",
- "Zeri": "50.00%"
- }
- },
- {
- "champion_name": "Twitch",
- "pick_rate": "0.8%",
- "counters": {
- "Zilean": "61.97%",
- "Taric": "60.47%",
- "Leona": "57.92%",
- "Soraka": "57.87%",
- "Shaco": "57.38%",
- "Nami": "57.14%",
- "Rakan": "56.34%",
- "Sylas": "55.56%",
- "Brand": "55.26%",
- "Pyke": "54.65%"
- },
- "synergy": {
- "Sivir": "70.97%",
- "Swain": "70.00%",
- "Seraphine": "63.64%",
- "Draven": "57.89%",
- "Cassiopeia": "53.85%",
- "Vayne": "53.54%",
- "Jhin": "53.40%",
- "Jinx": "52.27%",
- "Ziggs": "51.72%",
- "Zeri": "51.61%",
- "Nilah": "50.00%",
- "Kalista": "50.00%",
- "Hwei": "50.00%",
- "Tristana": "50.00%"
+ "Jinx": "55.00%",
+ "Ezreal": "52.38%",
+ "Smolder": "51.58%",
+ "Kalista": "51.52%",
+ "Xayah": "50.00%"
}
},
{
- "champion_name": "Zac",
- "pick_rate": "0.7%",
+ "champion_name": "Sylas",
+ "pick_rate": "0.6%",
"counters": {
- "Poppy": "67.39%",
- "Maokai": "57.14%",
- "Neeko": "56.72%",
- "Swain": "56%",
- "Vel'Koz": "54.1%",
- "Taric": "53.85%",
- "Alistar": "53.74%",
- "Ashe": "53.49%",
- "Rakan": "53.42%",
- "Zilean": "53.26%"
+ "Taric": "70%",
+ "Shaco": "70%",
+ "Pantheon": "64.86%",
+ "Neeko": "63.16%",
+ "Thresh": "58.58%",
+ "Lulu": "58.52%",
+ "Morgana": "58.06%",
+ "Janna": "57.66%",
+ "Leona": "57.58%",
+ "Bard": "57.45%"
},
"synergy": {
- "Ziggs": "72.73%",
- "Sivir": "71.43%",
- "Zeri": "70.00%",
- "Varus": "63.64%",
- "Yasuo": "62.07%",
- "Jhin": "59.32%",
- "Kalista": "57.14%",
- "Hwei": "57.14%",
- "Twitch": "55.32%",
- "Ezreal": "52.63%",
- "Miss Fortune": "52.38%",
- "Vayne": "50.75%",
- "Swain": "50.00%",
- "Tristana": "50.00%",
- "Caitlyn": "50.00%"
+ "Miss Fortune": "75.00%",
+ "Zeri": "72.73%",
+ "Twitch": "68.75%",
+ "Kalista": "68.42%",
+ "Draven": "65.22%",
+ "Tristana": "61.54%",
+ "Senna": "60.00%",
+ "Aphelios": "60.00%",
+ "Jinx": "55.56%",
+ "Jhin": "50.00%"
}
},
{
- "champion_name": "Tahm Kench",
- "pick_rate": "0.6%",
- "counters": {
- "Taric": "62.22%",
- "Braum": "61.11%",
- "Bard": "57.4%",
- "Zyra": "57.27%",
- "Pyke": "55.38%",
- "Maokai": "54.9%",
- "Brand": "54.67%",
- "Lulu": "54.55%",
- "Renata Glasc": "54.17%",
- "Rakan": "54.12%"
- },
+ "champion_name": "Heimerdinger",
+ "pick_rate": "0.5%",
+ "counters": "no info",
"synergy": {
- "Veigar": "80.00%",
- "Ziggs": "75.00%",
- "Karthus": "66.67%",
- "Seraphine": "58.33%",
- "Xayah": "58.33%",
+ "Tristana": "80.00%",
+ "Aphelios": "80.00%",
+ "Draven": "64.29%",
+ "Twitch": "62.50%",
+ "Twisted Fate": "60.00%",
"Jhin": "55.56%",
- "Miss Fortune": "51.72%",
- "Twitch": "51.28%",
- "Kog'Maw": "50.00%",
- "Kalista": "50.00%",
- "Ashe": "50.00%",
- "Ezreal": "50.00%"
+ "Varus": "53.85%",
+ "Senna": "51.28%",
+ "Miss Fortune": "50.00%",
+ "Jinx": "50.00%",
+ "Nilah": "50.00%"
}
},
{
- "champion_name": "Amumu",
- "pick_rate": "0.6%",
- "counters": {
- "Maokai": "66.07%",
- "Zyra": "61.68%",
- "Rell": "58.9%",
- "Rakan": "58.55%",
- "Janna": "58.5%",
- "Neeko": "57.14%",
- "Taric": "56.1%",
- "Braum": "55.41%",
- "Bard": "54%",
- "Leona": "53.91%"
- },
+ "champion_name": "Twitch",
+ "pick_rate": "0.5%",
+ "counters": "no info",
"synergy": {
- "Yasuo": "71.43%",
- "Ziggs": "66.67%",
- "Kalista": "64.15%",
- "Lucian": "60.00%",
- "Karthus": "60.00%",
- "Hwei": "57.14%",
- "Ezreal": "55.88%",
- "Nilah": "55.56%",
- "Draven": "55.41%",
- "Miss Fortune": "54.37%",
- "Ashe": "54.17%",
- "Jinx": "53.85%",
- "Kai'Sa": "53.60%",
- "Tristana": "52.63%",
- "Zeri": "50.00%",
- "Veigar": "50.00%"
+ "Karma": "75.00%",
+ "Lucian": "66.67%",
+ "Cassiopeia": "66.67%",
+ "Janna": "66.67%",
+ "Aphelios": "66.67%",
+ "Jhin": "61.90%",
+ "Zeri": "60.87%",
+ "Varus": "60.00%",
+ "Kalista": "57.14%",
+ "Xayah": "53.85%",
+ "Ezreal": "53.52%",
+ "Vayne": "53.19%",
+ "Sivir": "50.00%",
+ "Jinx": "50.00%",
+ "Lulu": "50.00%"
}
}
]
\ No newline at end of file
diff --git a/src/LoL-support/step1-lolchamps.py b/src/LoL-support/step1-lolchamps.py
old mode 100644
new mode 100755
diff --git a/src/LoL-support/step2-lolchamps.py b/src/LoL-support/step2-lolchamps.py
old mode 100644
new mode 100755
diff --git a/src/LoL-support/step3-lolchamps.py b/src/LoL-support/step3-lolchamps.py
old mode 100644
new mode 100755
diff --git a/src/LoL-support/step4-lolchamps.py b/src/LoL-support/step4-lolchamps.py
old mode 100644
new mode 100755
diff --git a/src/LoL-support/step5-lolchamps.py b/src/LoL-support/step5-lolchamps.py
old mode 100644
new mode 100755
diff --git a/src/LoL-support/step6-lolchamps.py b/src/LoL-support/step6-lolchamps.py
old mode 100644
new mode 100755
diff --git a/src/LoL-support/step7-lolchamps.py b/src/LoL-support/step7-lolchamps.py
old mode 100644
new mode 100755
diff --git a/src/LoL-support/step8-db.py b/src/LoL-support/step8-db.py
old mode 100644
new mode 100755
diff --git a/src/LoL-support/support_combined.json b/src/LoL-support/support_combined.json
old mode 100644
new mode 100755
index 4e4f71b..3c678d9
--- a/src/LoL-support/support_combined.json
+++ b/src/LoL-support/support_combined.json
@@ -1,663 +1,443 @@
[
{
- "champion_name": "Taric",
- "pick_rate": "1.4%",
+ "champion_name": "Maokai",
+ "pick_rate": "12.7%",
"counters": {
- "Soraka": "55.26%",
- "Rell": "52.91%",
- "Vel'Koz": "52.75%",
- "Janna": "50.88%",
- "Zilean": "50.55%",
- "Rakan": "49.62%",
- "Maokai": "49.58%",
- "Nami": "49.42%",
- "Xerath": "49.28%",
- "Senna": "49.24%"
+ "Taric": "50.48%",
+ "Alistar": "49.83%",
+ "Braum": "49.49%",
+ "Leona": "49.02%",
+ "Janna": "48.98%",
+ "Renata Glasc": "48.48%",
+ "Rell": "47.97%",
+ "Shaco": "47.18%",
+ "Camille": "47.04%",
+ "Zyra": "47.02%"
}
},
{
"champion_name": "Janna",
- "pick_rate": "9.6%",
+ "pick_rate": "10.7%",
"counters": {
- "Senna": "50.79%",
- "Sona": "50.1%",
- "Soraka": "50.01%",
- "Seraphine": "49.87%",
- "Milio": "49.71%",
- "Neeko": "49.67%",
- "Rell": "49.5%",
- "Bard": "49.44%",
- "Rakan": "49.42%",
- "Blitzcrank": "49.4%"
+ "Camille": "53.25%",
+ "Sona": "51.89%",
+ "Blitzcrank": "51.2%",
+ "Maokai": "51.02%",
+ "Braum": "49.67%",
+ "Pyke": "49.38%",
+ "Renata Glasc": "49.3%",
+ "Senna": "49.25%",
+ "Ashe": "49.07%",
+ "Nami": "48.64%"
}
},
{
- "champion_name": "Maokai",
- "pick_rate": "1.6%",
+ "champion_name": "Taric",
+ "pick_rate": "1.5%",
"counters": {
- "Shaco": "58.18%",
- "Braum": "54.8%",
- "Sona": "53.99%",
- "Janna": "52.72%",
- "Renata Glasc": "52.39%",
- "Zyra": "51.46%",
- "Alistar": "51.45%",
- "Soraka": "50.8%",
- "Taric": "50.42%",
- "Neeko": "50.29%"
+ "Swain": "57.63%",
+ "Sona": "57.32%",
+ "Janna": "54.88%",
+ "Nami": "54.27%",
+ "Lux": "53.54%",
+ "Seraphine": "53.45%",
+ "Bard": "52.97%",
+ "Soraka": "52.32%",
+ "Thresh": "52.21%",
+ "Braum": "51.67%"
}
},
{
- "champion_name": "Bard",
- "pick_rate": "6.1%",
- "counters": {
- "Taric": "52.2%",
- "Janna": "50.56%",
- "Maokai": "50.28%",
- "Zilean": "49.95%",
- "Senna": "49.94%",
- "Blitzcrank": "49.89%",
- "Renata Glasc": "49.52%",
- "Soraka": "49.45%",
- "Rell": "49.3%",
- "Pyke": "49.06%"
- }
+ "champion_name": "Braum",
+ "pick_rate": "3.8%",
+ "counters": "no info"
},
{
- "champion_name": "Rakan",
- "pick_rate": "12.8%",
- "counters": {
- "Senna": "52.31%",
- "Bard": "51.42%",
- "Soraka": "51.41%",
- "Neeko": "51.37%",
- "Renata Glasc": "51.09%",
- "Sona": "50.93%",
- "Maokai": "50.87%",
- "Rell": "50.69%",
- "Zilean": "50.64%",
- "Janna": "50.58%"
+ "champion_name": "Blitzcrank",
+ "pick_rate": "8.1%",
+ "counters": {
+ "Pantheon": "54.85%",
+ "Leona": "54.08%",
+ "Maokai": "53.66%",
+ "Braum": "53.61%",
+ "Rakan": "53.26%",
+ "Taric": "53.13%",
+ "Alistar": "52.33%",
+ "Shaco": "51.5%",
+ "Rell": "50.9%",
+ "Zilean": "50.57%"
}
},
{
- "champion_name": "Sona",
- "pick_rate": "2.4%",
+ "champion_name": "Camille",
+ "pick_rate": "2.3%",
"counters": {
- "Taric": "56.83%",
- "Leona": "55.04%",
- "Seraphine": "53.49%",
- "Blitzcrank": "52.56%",
- "Senna": "51.71%",
- "Bard": "51.4%",
- "Nami": "50.59%",
- "Milio": "50.43%",
- "Zilean": "50.26%",
- "Pyke": "50.19%"
+ "Taric": "58.76%",
+ "Renata Glasc": "58.12%",
+ "Braum": "57.32%",
+ "Leona": "54.48%",
+ "Brand": "54.2%",
+ "Bard": "53.76%",
+ "Zac": "53.03%",
+ "Maokai": "52.96%",
+ "Tahm Kench": "52.94%",
+ "Zilean": "52.78%"
}
},
{
- "champion_name": "Senna",
- "pick_rate": "11.0%",
- "counters": {
- "Pyke": "52.31%",
- "Blitzcrank": "52.25%",
- "Xerath": "51.66%",
- "Maokai": "51.62%",
- "Zyra": "51.1%",
- "Taric": "50.76%",
- "Leona": "50.22%",
- "Bard": "50.06%",
- "Lux": "49.9%",
- "Rell": "49.8%"
- }
+ "champion_name": "Leona",
+ "pick_rate": "4.4%",
+ "counters": "no info"
},
{
"champion_name": "Rell",
- "pick_rate": "4.9%",
- "counters": {
- "Poppy": "53.79%",
- "Seraphine": "53.57%",
- "Soraka": "52.57%",
- "Zilean": "52.54%",
- "Alistar": "52.45%",
- "Shaco": "51.5%",
- "Maokai": "50.89%",
- "Sona": "50.75%",
- "Bard": "50.7%",
- "Janna": "50.5%"
- }
- },
- {
- "champion_name": "Neeko",
- "pick_rate": "1.9%",
- "counters": {
- "Vel'Koz": "60.45%",
- "Sona": "53.26%",
- "Bard": "52.61%",
- "Brand": "52.38%",
- "Taric": "52.17%",
- "Alistar": "51%",
- "Shaco": "50.68%",
- "Senna": "50.42%",
- "Renata Glasc": "50.38%",
- "Morgana": "50.35%"
- }
+ "pick_rate": "4.1%",
+ "counters": "no info"
},
{
- "champion_name": "Soraka",
- "pick_rate": "5.5%",
- "counters": {
- "Blitzcrank": "54.17%",
- "Neeko": "51.31%",
- "Sona": "51.25%",
- "Zyra": "51.13%",
- "Vel'Koz": "51.05%",
- "Bard": "50.55%",
- "Senna": "50.38%",
- "Yuumi": "50.06%",
- "Janna": "49.99%",
- "Pyke": "49.98%"
- }
+ "champion_name": "Rakan",
+ "pick_rate": "7.7%",
+ "counters": "no info"
},
{
- "champion_name": "Blitzcrank",
- "pick_rate": "8.8%",
- "counters": {
- "Taric": "56.85%",
- "Rakan": "52.88%",
- "Leona": "52.47%",
- "Braum": "52.28%",
- "Renata Glasc": "51.05%",
- "Maokai": "51.04%",
- "Zyra": "50.98%",
- "Rell": "50.78%",
- "Alistar": "50.73%",
- "Zilean": "50.7%"
- }
+ "champion_name": "Zilean",
+ "pick_rate": "2.8%",
+ "counters": "no info"
},
{
- "champion_name": "Zyra",
- "pick_rate": "3.8%",
- "counters": {
- "Sona": "53.56%",
- "Pyke": "53.25%",
- "Heimerdinger": "53.11%",
- "Bard": "53.07%",
- "Leona": "51.91%",
- "Janna": "51.88%",
- "Shaco": "51.56%",
- "Taric": "51.43%",
- "Rakan": "50.98%",
- "Nautilus": "50.75%"
- }
+ "champion_name": "Pyke",
+ "pick_rate": "8.5%",
+ "counters": "no info"
},
{
- "champion_name": "Pyke",
- "pick_rate": "7.6%",
+ "champion_name": "Zac",
+ "pick_rate": "1.0%",
"counters": {
- "Rakan": "53.38%",
- "Maokai": "52.78%",
- "Neeko": "52.1%",
- "Rell": "52.08%",
- "Taric": "51.82%",
- "Janna": "51.79%",
- "Blitzcrank": "51.44%",
- "Alistar": "50.94%",
- "Bard": "50.94%",
- "Thresh": "50.49%"
+ "Pantheon": "61.36%",
+ "Taric": "58.82%",
+ "Zilean": "58.49%",
+ "Rell": "56.82%",
+ "Bard": "56.55%",
+ "Morgana": "55.93%",
+ "Janna": "54.44%",
+ "Maokai": "53.44%",
+ "Milio": "53.25%",
+ "Thresh": "53.18%"
}
},
{
- "champion_name": "Zilean",
- "pick_rate": "2.5%",
- "counters": {
- "Janna": "53.27%",
- "Shaco": "53.02%",
- "Senna": "51.99%",
- "Neeko": "51.67%",
- "Maokai": "51.41%",
- "Xerath": "51.07%",
- "Milio": "50.67%",
- "Sylas": "50.34%",
- "Soraka": "50.28%",
- "Vel'Koz": "50.23%"
- }
+ "champion_name": "Alistar",
+ "pick_rate": "4.7%",
+ "counters": "no info"
},
{
- "champion_name": "Braum",
- "pick_rate": "3.6%",
+ "champion_name": "Bard",
+ "pick_rate": "5.7%",
"counters": {
- "Zilean": "55.95%",
- "Soraka": "54.2%",
- "Bard": "53.76%",
- "Rell": "53.74%",
- "Taric": "53.44%",
- "Sona": "53.2%",
- "Senna": "53.06%",
- "Rakan": "52.88%",
- "Janna": "52.49%",
- "Renata Glasc": "52.31%"
+ "Maokai": "56.1%",
+ "Renata Glasc": "53.77%",
+ "Janna": "53.45%",
+ "Pyke": "51.79%",
+ "Morgana": "51.04%",
+ "Nami": "50.92%",
+ "Pantheon": "50.72%",
+ "Blitzcrank": "50.43%",
+ "Brand": "50.34%",
+ "Senna": "50.21%"
}
},
{
- "champion_name": "Renata Glasc",
- "pick_rate": "5.0%",
+ "champion_name": "Soraka",
+ "pick_rate": "5.4%",
"counters": "no info"
},
{
- "champion_name": "Vel'Koz",
- "pick_rate": "1.6%",
+ "champion_name": "Sona",
+ "pick_rate": "2.3%",
"counters": {
- "Pyke": "55.14%",
- "Zyra": "54.76%",
- "Seraphine": "54.15%",
- "Blitzcrank": "53.89%",
- "Janna": "53.62%",
- "Bard": "53.22%",
- "Sona": "52.97%",
- "Nami": "52.1%",
- "Maokai": "51.85%",
- "Renata Glasc": "51.64%"
+ "Zilean": "61.68%",
+ "Rell": "59.62%",
+ "Leona": "57.21%",
+ "Neeko": "55.71%",
+ "Thresh": "55.03%",
+ "Maokai": "54.7%",
+ "Bard": "53.59%",
+ "Braum": "51.81%",
+ "Blitzcrank": "51.79%",
+ "Seraphine": "51.2%"
}
},
{
"champion_name": "Thresh",
- "pick_rate": "13.3%",
- "counters": {
- "Taric": "53.39%",
- "Janna": "52.43%",
- "Brand": "52.12%",
- "Rakan": "52.02%",
- "Neeko": "51.75%",
- "Sona": "51.47%",
- "Maokai": "51.34%",
- "Vel'Koz": "51.27%",
- "Bard": "51.24%",
- "Renata Glasc": "51.24%"
- }
+ "pick_rate": "9.4%",
+ "counters": "no info"
},
{
- "champion_name": "Milio",
- "pick_rate": "6.7%",
- "counters": {
- "Taric": "54.79%",
- "Blitzcrank": "53.66%",
- "Braum": "53.22%",
- "Vel'Koz": "53.08%",
- "Senna": "52.78%",
- "Bard": "52.75%",
- "Neeko": "52.17%",
- "Rakan": "51.91%",
- "Soraka": "51.52%",
- "Thresh": "51.33%"
- }
+ "champion_name": "Renata Glasc",
+ "pick_rate": "2.4%",
+ "counters": "no info"
},
{
- "champion_name": "Alistar",
- "pick_rate": "5.8%",
+ "champion_name": "Zyra",
+ "pick_rate": "4.3%",
"counters": {
- "Sona": "54.51%",
- "Janna": "54.04%",
- "Swain": "53.24%",
- "Vel'Koz": "52.78%",
- "Senna": "52.64%",
- "Zilean": "52.39%",
- "Soraka": "52.22%",
- "Seraphine": "51.97%",
- "Xerath": "51.86%",
- "Milio": "51.85%"
+ "Taric": "54.73%",
+ "Leona": "54.05%",
+ "Sona": "53.31%",
+ "Maokai": "52.98%",
+ "Renata Glasc": "52.84%",
+ "Pyke": "52.6%",
+ "Zac": "52.59%",
+ "Janna": "52.42%",
+ "Zilean": "51.88%",
+ "Yuumi": "51.63%"
}
},
{
- "champion_name": "Zac",
- "pick_rate": "0.7%",
- "counters": {
- "Poppy": "67.39%",
- "Maokai": "57.14%",
- "Neeko": "56.72%",
- "Swain": "56%",
- "Vel'Koz": "54.1%",
- "Taric": "53.85%",
- "Alistar": "53.74%",
- "Ashe": "53.49%",
- "Rakan": "53.42%",
- "Zilean": "53.26%"
- }
+ "champion_name": "Vel'Koz",
+ "pick_rate": "1.4%",
+ "counters": "no info"
},
{
- "champion_name": "Leona",
- "pick_rate": "4.8%",
- "counters": {
- "Taric": "56.7%",
- "Janna": "54.1%",
- "Bard": "54.03%",
- "Shaco": "53.67%",
- "Maokai": "53.55%",
- "Rell": "53.22%",
- "Soraka": "53.11%",
- "Lux": "53.09%",
- "Morgana": "53.02%",
- "Braum": "51.72%"
- }
+ "champion_name": "Senna",
+ "pick_rate": "11.0%",
+ "counters": "no info"
},
{
- "champion_name": "Xerath",
- "pick_rate": "4.8%",
- "counters": {
- "Sylas": "55%",
- "Maokai": "53.98%",
- "Sona": "53.77%",
- "Janna": "53.15%",
- "Pyke": "52.84%",
- "Soraka": "52.33%",
- "Rakan": "52.02%",
- "Leona": "51.76%",
- "Zyra": "51.72%",
- "Nami": "51.6%"
- }
+ "champion_name": "Shaco",
+ "pick_rate": "2.6%",
+ "counters": "no info"
},
{
- "champion_name": "Nami",
- "pick_rate": "6.5%",
- "counters": {
- "Maokai": "56.37%",
- "Neeko": "53.14%",
- "Blitzcrank": "53.03%",
- "Rakan": "52.58%",
- "Zyra": "52.29%",
- "Soraka": "51.92%",
- "Milio": "51.67%",
- "Pyke": "51.56%",
- "Janna": "51.44%",
- "Bard": "51.33%"
- }
+ "champion_name": "Pantheon",
+ "pick_rate": "1.8%",
+ "counters": "no info"
},
{
- "champion_name": "Shaco",
+ "champion_name": "Tahm Kench",
"pick_rate": "1.1%",
- "counters": {
- "Sona": "60.95%",
- "Swain": "60.22%",
- "Taric": "59.68%",
- "Heimerdinger": "59.02%",
- "Zac": "56.14%",
- "Janna": "55.96%",
- "Milio": "54.49%",
- "Pyke": "54.23%",
- "Rakan": "53.2%",
- "Bard": "52.59%"
- }
+ "counters": "no info"
},
{
- "champion_name": "Heimerdinger",
- "pick_rate": "1.0%",
+ "champion_name": "Nautilus",
+ "pick_rate": "11.0%",
"counters": {
- "Maokai": "61.64%",
- "Neeko": "56.82%",
- "Seraphine": "55.95%",
- "Xerath": "54.86%",
- "Bard": "54.55%",
- "Leona": "54.4%",
- "Rell": "53.15%",
- "Rakan": "52.67%",
- "Lulu": "52.56%",
- "Janna": "52.56%"
+ "Taric": "60.99%",
+ "Leona": "57.54%",
+ "Braum": "56.73%",
+ "Renata Glasc": "55.96%",
+ "Alistar": "55.65%",
+ "Rell": "55.38%",
+ "Camille": "54.74%",
+ "Pantheon": "54.53%",
+ "Swain": "54.35%",
+ "Maokai": "53.72%"
}
},
{
- "champion_name": "Sylas",
- "pick_rate": "1.0%",
+ "champion_name": "Milio",
+ "pick_rate": "8.1%",
"counters": {
- "Sona": "60%",
- "Janna": "57.91%",
- "Vel'Koz": "57.75%",
- "Braum": "57.52%",
- "Morgana": "57.5%",
- "Taric": "55.56%",
- "Rell": "54.55%",
- "Rakan": "53.89%",
- "Blitzcrank": "53.74%",
- "Thresh": "53.64%"
+ "Blitzcrank": "56.32%",
+ "Braum": "55.45%",
+ "Maokai": "54.37%",
+ "Camille": "53.86%",
+ "Janna": "53.43%",
+ "Thresh": "53.37%",
+ "Zilean": "53.04%",
+ "Tahm Kench": "53.04%",
+ "Vel'Koz": "52.98%",
+ "Leona": "52.96%"
}
},
{
- "champion_name": "Tahm Kench",
- "pick_rate": "0.6%",
+ "champion_name": "Nami",
+ "pick_rate": "5.5%",
"counters": {
- "Taric": "62.22%",
- "Braum": "61.11%",
- "Bard": "57.4%",
- "Zyra": "57.27%",
- "Pyke": "55.38%",
- "Maokai": "54.9%",
- "Brand": "54.67%",
- "Lulu": "54.55%",
- "Renata Glasc": "54.17%",
- "Rakan": "54.12%"
+ "Morgana": "56.08%",
+ "Blitzcrank": "55.61%",
+ "Maokai": "55.57%",
+ "Vel'Koz": "54.47%",
+ "Rakan": "54.44%",
+ "Neeko": "54.21%",
+ "Rell": "53.9%",
+ "Nautilus": "53.32%",
+ "Thresh": "53.28%",
+ "Senna": "53.17%"
}
},
{
- "champion_name": "Amumu",
- "pick_rate": "0.6%",
+ "champion_name": "Xerath",
+ "pick_rate": "2.7%",
"counters": {
- "Maokai": "66.07%",
- "Zyra": "61.68%",
- "Rell": "58.9%",
- "Rakan": "58.55%",
- "Janna": "58.5%",
- "Neeko": "57.14%",
- "Taric": "56.1%",
- "Braum": "55.41%",
- "Bard": "54%",
- "Leona": "53.91%"
+ "Blitzcrank": "58.63%",
+ "Neeko": "56.43%",
+ "Sona": "56.36%",
+ "Maokai": "56.26%",
+ "Pyke": "56.15%",
+ "Leona": "56.02%",
+ "Braum": "55.06%",
+ "Rakan": "54.83%",
+ "Nautilus": "54.65%",
+ "Janna": "53.86%"
}
},
{
- "champion_name": "Twitch",
- "pick_rate": "0.8%",
- "counters": {
- "Zilean": "61.97%",
- "Taric": "60.47%",
- "Leona": "57.92%",
- "Soraka": "57.87%",
- "Shaco": "57.38%",
- "Nami": "57.14%",
- "Rakan": "56.34%",
- "Sylas": "55.56%",
- "Brand": "55.26%",
- "Pyke": "54.65%"
- }
+ "champion_name": "Brand",
+ "pick_rate": "1.9%",
+ "counters": "no info"
},
{
- "champion_name": "Nautilus",
- "pick_rate": "10.9%",
+ "champion_name": "Swain",
+ "pick_rate": "1.4%",
"counters": {
- "Taric": "55.98%",
- "Rell": "55.66%",
- "Renata Glasc": "55.13%",
- "Sylas": "55.09%",
- "Braum": "54.08%",
- "Rakan": "54.06%",
- "Vel'Koz": "53.45%",
- "Swain": "53.09%",
- "Neeko": "52.97%",
- "Alistar": "52.87%"
+ "Zac": "58.97%",
+ "Sona": "58.67%",
+ "Neeko": "57.41%",
+ "Nami": "57.14%",
+ "Milio": "56.44%",
+ "Pantheon": "55.95%",
+ "Zyra": "55.49%",
+ "Morgana": "55.24%",
+ "Leona": "55%",
+ "Camille": "54.79%"
}
},
{
- "champion_name": "Ashe",
- "pick_rate": "5.7%",
+ "champion_name": "Lulu",
+ "pick_rate": "7.9%",
"counters": {
- "Pyke": "56.98%",
- "Sylas": "54.95%",
- "Blitzcrank": "54.83%",
- "Heimerdinger": "54.8%",
- "Rell": "54.67%",
- "Nautilus": "54.35%",
- "Bard": "53.44%",
- "Milio": "53.1%",
- "Rakan": "52.69%",
- "Brand": "52.5%"
+ "Vel'Koz": "57.35%",
+ "Blitzcrank": "56.23%",
+ "Zilean": "55.78%",
+ "Tahm Kench": "55.43%",
+ "Renata Glasc": "54.86%",
+ "Rell": "54.7%",
+ "Janna": "54.6%",
+ "Maokai": "54.51%",
+ "Braum": "53.79%",
+ "Ashe": "53.65%"
}
},
{
- "champion_name": "Lux",
- "pick_rate": "5.1%",
- "counters": {
- "Blitzcrank": "56.57%",
- "Maokai": "54.94%",
- "Zyra": "54.66%",
- "Shaco": "54.45%",
- "Vel'Koz": "54.1%",
- "Sylas": "53.87%",
- "Zilean": "53.51%",
- "Pyke": "53.5%",
- "Xerath": "52.75%",
- "Nami": "52.72%"
- }
+ "champion_name": "Morgana",
+ "pick_rate": "2.8%",
+ "counters": "no info"
},
{
- "champion_name": "Brand",
- "pick_rate": "2.0%",
- "counters": {
- "Sona": "58.11%",
- "Maokai": "57.24%",
- "Xerath": "57.03%",
- "Janna": "56.31%",
- "Pyke": "56.01%",
- "Zyra": "55.13%",
- "Leona": "54.96%",
- "Zilean": "54.92%",
- "Soraka": "54.73%",
- "Senna": "54.19%"
- }
+ "champion_name": "Heimerdinger",
+ "pick_rate": "0.5%",
+ "counters": "no info"
},
{
- "champion_name": "Seraphine",
- "pick_rate": "2.0%",
+ "champion_name": "Ashe",
+ "pick_rate": "4.8%",
"counters": {
- "Sylas": "58.33%",
- "Maokai": "57.24%",
- "Zilean": "56.09%",
- "Bard": "55.97%",
- "Pyke": "54.69%",
- "Soraka": "53.72%",
- "Blitzcrank": "53.61%",
- "Yuumi": "53.6%",
- "Milio": "52.99%",
- "Thresh": "52.66%"
+ "Camille": "58.72%",
+ "Maokai": "57.34%",
+ "Pyke": "56.14%",
+ "Blitzcrank": "55.88%",
+ "Nautilus": "55.46%",
+ "Taric": "55.41%",
+ "Vel'Koz": "54.55%",
+ "Zyra": "54.13%",
+ "Soraka": "53.87%",
+ "Xerath": "53.83%"
}
},
{
- "champion_name": "Lulu",
- "pick_rate": "7.4%",
+ "champion_name": "Sylas",
+ "pick_rate": "0.6%",
"counters": {
- "Taric": "56.84%",
- "Zilean": "54.43%",
- "Neeko": "54.04%",
- "Senna": "54.04%",
- "Sona": "53.78%",
- "Zyra": "53.7%",
- "Maokai": "53.46%",
- "Blitzcrank": "52.96%",
- "Bard": "52.89%",
- "Thresh": "52.88%"
+ "Taric": "70%",
+ "Shaco": "70%",
+ "Pantheon": "64.86%",
+ "Neeko": "63.16%",
+ "Thresh": "58.58%",
+ "Lulu": "58.52%",
+ "Morgana": "58.06%",
+ "Janna": "57.66%",
+ "Leona": "57.58%",
+ "Bard": "57.45%"
}
},
{
- "champion_name": "Morgana",
- "pick_rate": "3.2%",
+ "champion_name": "Neeko",
+ "pick_rate": "1.6%",
"counters": {
- "Zyra": "56.73%",
- "Senna": "56.35%",
- "Rell": "56.29%",
- "Janna": "55.6%",
- "Milio": "55.2%",
- "Vel'Koz": "55.08%",
- "Nami": "54.67%",
- "Taric": "54.31%",
- "Karma": "54.24%",
- "Maokai": "53.2%"
+ "Zilean": "60.66%",
+ "Taric": "58.7%",
+ "Zac": "57.78%",
+ "Leona": "57.76%",
+ "Pantheon": "57.14%",
+ "Janna": "56.74%",
+ "Blitzcrank": "56.38%",
+ "Bard": "55.85%",
+ "Camille": "55.41%",
+ "Rell": "55.19%"
}
},
{
"champion_name": "Karma",
- "pick_rate": "7.6%",
- "counters": {
- "Pyke": "57.3%",
- "Maokai": "55.33%",
- "Zyra": "54.97%",
- "Blitzcrank": "54.92%",
- "Rakan": "54.52%",
- "Janna": "54.36%",
- "Sona": "54.33%",
- "Nautilus": "54.21%",
- "Soraka": "53.61%",
- "Neeko": "52.83%"
- }
+ "pick_rate": "9.6%",
+ "counters": "no info"
},
{
- "champion_name": "Swain",
- "pick_rate": "1.6%",
+ "champion_name": "Lux",
+ "pick_rate": "3.9%",
"counters": {
- "Zilean": "57.77%",
- "Sona": "57.32%",
- "Vel'Koz": "57.25%",
- "Janna": "56.41%",
- "Brand": "55.87%",
- "Yuumi": "55.78%",
- "Zyra": "55.78%",
- "Xerath": "55.48%",
- "Soraka": "55.34%",
- "Senna": "55.32%"
+ "Camille": "62.93%",
+ "Pantheon": "60%",
+ "Pyke": "58.92%",
+ "Bard": "57.24%",
+ "Shaco": "56.3%",
+ "Blitzcrank": "55.42%",
+ "Maokai": "55.39%",
+ "Leona": "55.08%",
+ "Alistar": "54.69%",
+ "Zilean": "54.61%"
}
},
{
- "champion_name": "Pantheon",
- "pick_rate": "1.0%",
- "counters": {
- "Neeko": "59.74%",
- "Maokai": "58.43%",
- "Karma": "57.22%",
- "Bard": "57.14%",
- "Swain": "56.25%",
- "Rakan": "55.92%",
- "Brand": "55.83%",
- "Soraka": "55.74%",
- "Sona": "55.56%",
- "Janna": "55.42%"
- }
+ "champion_name": "Seraphine",
+ "pick_rate": "1.7%",
+ "counters": "no info"
},
{
"champion_name": "Yuumi",
- "pick_rate": "4.9%",
+ "pick_rate": "4.4%",
"counters": {
- "Maokai": "61.28%",
- "Rell": "59.85%",
- "Taric": "57.47%",
- "Alistar": "57.31%",
- "Nautilus": "56.64%",
- "Rakan": "55.78%",
- "Pyke": "55.71%",
- "Leona": "55.63%",
- "Sylas": "55.27%",
- "Thresh": "55.23%"
+ "Rell": "62.65%",
+ "Braum": "58.82%",
+ "Blitzcrank": "58.05%",
+ "Leona": "57.77%",
+ "Pyke": "57.61%",
+ "Taric": "57.46%",
+ "Swain": "56.85%",
+ "Rakan": "56.72%",
+ "Maokai": "56.12%",
+ "Janna": "55.85%"
}
},
+ {
+ "champion_name": "Twitch",
+ "pick_rate": "0.5%",
+ "counters": "no info"
+ },
{
"champion_name": "Hwei",
- "pick_rate": "2.1%",
- "counters": {
- "Taric": "66.91%",
- "Sona": "66.55%",
- "Blitzcrank": "65.66%",
- "Janna": "65.3%",
- "Brand": "65.18%",
- "Pyke": "63.85%",
- "Xerath": "63.02%",
- "Vel'Koz": "62.71%",
- "Morgana": "62.31%",
- "Milio": "62.01%"
+ "pick_rate": "2.9%",
+ "counters": {
+ "Camille": "63.76%",
+ "Pyke": "60.47%",
+ "Maokai": "58.72%",
+ "Xerath": "58.7%",
+ "Rakan": "58.47%",
+ "Taric": "58.43%",
+ "Swain": "58.1%",
+ "Zilean": "57.69%",
+ "Vel'Koz": "57.69%",
+ "Janna": "57.32%"
}
}
]
\ No newline at end of file
diff --git a/src/LoL-support/support_names.json b/src/LoL-support/support_names.json
old mode 100644
new mode 100755
index bafadeb..6655ed7
--- a/src/LoL-support/support_names.json
+++ b/src/LoL-support/support_names.json
@@ -1 +1 @@
-["Taric", "Janna", "Maokai", "Bard", "Rakan", "Sona", "Senna", "Rell", "Neeko", "Soraka", "Blitzcrank", "Zyra", "Pyke", "Zilean", "Braum", "Renata", "Vel'Koz", "Thresh", "Milio", "Alistar", "Zac", "Leona", "Xerath", "Nami", "Shaco", "Heimerdinger", "Sylas", "Tahm Kench", "Amumu", "Twitch", "Nautilus", "Ashe", "Lux", "Brand", "Seraphine", "Lulu", "Morgana", "Karma", "Swain", "Pantheon", "Yuumi", "Hwei"]
\ No newline at end of file
+["Maokai", "Janna", "Taric", "Braum", "Blitzcrank", "Camille", "Leona", "Rell", "Rakan", "Zilean", "Pyke", "Zac", "Alistar", "Bard", "Soraka", "Sona", "Thresh", "Renata Glasc", "Zyra", "Vel'Koz", "Senna", "Shaco", "Pantheon", "Tahm Kench", "Nautilus", "Milio", "Nami", "Xerath", "Brand", "Swain", "Lulu", "Morgana", "Heimerdinger", "Ashe", "Sylas", "Neeko", "Karma", "Lux", "Seraphine", "Yuumi", "Twitch", "Hwei"]
\ No newline at end of file
diff --git a/src/LoL-support/synergies_list.json b/src/LoL-support/synergies_list.json
old mode 100644
new mode 100755
index 391b717..3d7539f
--- a/src/LoL-support/synergies_list.json
+++ b/src/LoL-support/synergies_list.json
@@ -1,692 +1,559 @@
[
{
- "champion_name": "Taric",
+ "champion_name": "Janna",
"synergy": {
- "Karthus": "70.83%",
- "Yasuo": "68.91%",
- "Lee Sin": "68.18%",
- "Master Yi": "59.26%",
- "Kog'Maw": "58.82%",
- "Sivir": "58.62%",
- "Varus": "56.63%",
- "Swain": "56.45%",
- "Seraphine": "56.41%",
- "Kalista": "55.94%",
- "Ziggs": "55.00%",
- "Vayne": "54.01%",
- "Jinx": "52.38%",
- "Lucian": "51.91%",
- "Kai'Sa": "51.81%",
- "Ashe": "51.67%",
- "Miss Fortune": "50.94%",
- "Ezreal": "50.20%",
- "Twitch": "50.00%"
+ "Nilah": "57.38%",
+ "Twitch": "57.02%",
+ "Senna": "56.68%",
+ "Seraphine": "55.59%",
+ "Miss Fortune": "54.71%",
+ "Zeri": "54.30%",
+ "Tristana": "54.22%",
+ "Draven": "54.22%",
+ "Twisted Fate": "53.57%",
+ "Samira": "53.57%",
+ "Jhin": "53.03%",
+ "Lucian": "52.84%",
+ "Karthus": "52.73%",
+ "Ezreal": "52.60%",
+ "Yasuo": "52.58%",
+ "Kalista": "52.35%",
+ "Vayne": "51.97%",
+ "Ashe": "51.80%",
+ "Sivir": "51.10%",
+ "Caitlyn": "51.05%",
+ "Smolder": "50.23%",
+ "Aphelios": "50.23%"
}
},
{
- "champion_name": "Janna",
+ "champion_name": "Taric",
"synergy": {
- "Seraphine": "58.66%",
- "Vayne": "55.43%",
- "Yasuo": "54.91%",
- "Ziggs": "54.85%",
- "Jinx": "53.96%",
- "Ashe": "53.85%",
- "Tristana": "53.63%",
- "Kalista": "53.47%",
- "Kog'Maw": "52.89%",
- "Karthus": "52.83%",
- "Jhin": "52.65%",
- "Ezreal": "52.58%",
- "Lucian": "52.51%",
- "Draven": "52.25%",
- "Twitch": "52.00%",
- "Samira": "51.88%",
- "Varus": "51.73%",
- "Zeri": "51.64%",
- "Caitlyn": "51.53%",
- "Kai'Sa": "50.95%"
+ "Vayne": "60.16%",
+ "Ashe": "58.46%",
+ "Yasuo": "58.33%",
+ "Twisted Fate": "57.89%",
+ "Pyke": "57.14%",
+ "Senna": "56.59%",
+ "Nilah": "56.32%",
+ "Kalista": "55.13%",
+ "Jinx": "54.39%",
+ "Aphelios": "53.57%",
+ "Seraphine": "53.13%",
+ "Zeri": "52.54%",
+ "Twitch": "52.05%",
+ "Smolder": "51.89%",
+ "Ezreal": "51.67%",
+ "Kai'Sa": "50.59%",
+ "Sivir": "50.00%",
+ "Lucian": "50.00%",
+ "Dr. Mundo": "50.00%",
+ "Xayah": "50.00%",
+ "Karthus": "50.00%"
}
},
{
- "champion_name": "Maokai",
+ "champion_name": "Blitzcrank",
"synergy": {
- "Xayah": "60.00%",
- "Ziggs": "58.33%",
- "Karthus": "57.89%",
- "Kai'Sa": "53.95%",
- "Vayne": "53.51%",
- "Sivir": "52.63%",
- "Jhin": "52.46%",
- "Aphelios": "52.46%",
- "Draven": "52.38%",
- "Ezreal": "52.29%",
- "Samira": "51.81%",
- "Varus": "51.53%",
- "Zeri": "51.02%",
+ "Seraphine": "65.22%",
+ "Karthus": "57.78%",
+ "Draven": "56.57%",
+ "Vayne": "55.39%",
+ "Varus": "54.74%",
+ "Jinx": "54.30%",
+ "Twisted Fate": "54.17%",
+ "Tristana": "53.18%",
+ "Sivir": "53.06%",
+ "Senna": "52.85%",
+ "Aphelios": "52.14%",
+ "Kai'Sa": "51.65%",
+ "Lucian": "50.78%",
+ "Nilah": "50.67%",
"Twitch": "50.00%",
"Ashe": "50.00%",
- "Hwei": "50.00%"
+ "Zeri": "50.00%"
}
},
{
- "champion_name": "Bard",
+ "champion_name": "Camille",
"synergy": {
- "Nilah": "59.77%",
- "Vayne": "55.15%",
- "Caitlyn": "55.12%",
- "Twitch": "55.06%",
- "Ziggs": "54.37%",
- "Tristana": "53.92%",
- "Karthus": "53.62%",
- "Seraphine": "53.05%",
- "Lucian": "52.97%",
- "Ezreal": "52.51%",
- "Ashe": "52.44%",
- "Jhin": "51.89%",
- "Swain": "51.69%",
- "Jinx": "51.34%",
- "Zeri": "50.76%"
- }
- },
- {
- "champion_name": "Rakan",
- "synergy": {
- "Lucian": "56.02%",
- "Jinx": "54.48%",
- "Twitch": "54.35%",
- "Seraphine": "54.17%",
- "Ziggs": "54.08%",
- "Vayne": "54.03%",
- "Sivir": "53.73%",
- "Jhin": "52.40%",
- "Yasuo": "51.97%",
- "Karthus": "51.90%",
- "Ezreal": "51.77%",
- "Ashe": "51.55%",
- "Miss Fortune": "50.65%",
- "Nilah": "50.61%",
- "Varus": "50.35%",
- "Draven": "50.12%"
+ "Twitch": "62.67%",
+ "Sivir": "58.33%",
+ "Zeri": "56.47%",
+ "Tristana": "56.20%",
+ "Senna": "55.81%",
+ "Xayah": "55.26%",
+ "Aphelios": "53.62%",
+ "Nilah": "53.13%",
+ "Seraphine": "52.94%",
+ "Varus": "52.89%",
+ "Karthus": "52.63%",
+ "Ashe": "52.50%",
+ "Smolder": "52.34%",
+ "Jhin": "52.09%",
+ "Draven": "52.06%",
+ "Ziggs": "52.00%",
+ "Twisted Fate": "50.94%",
+ "Vayne": "50.92%",
+ "Lucian": "50.35%",
+ "Swain": "50.00%"
}
},
{
- "champion_name": "Sona",
+ "champion_name": "Leona",
"synergy": {
- "Yasuo": "64.52%",
- "Tristana": "60.29%",
- "Ziggs": "58.51%",
- "Samira": "57.45%",
- "Sivir": "56.60%",
- "Vayne": "56.55%",
- "Nilah": "55.88%",
- "Kog'Maw": "53.49%",
- "Lucian": "53.04%",
- "Xayah": "52.90%",
- "Seraphine": "52.76%",
- "Ezreal": "52.71%",
- "Miss Fortune": "52.23%",
- "Varus": "51.05%",
- "Ashe": "50.93%",
- "Caitlyn": "50.81%",
- "Twitch": "50.21%"
- }
- },
- {
- "champion_name": "Senna",
- "synergy": {
- "Kalista": "57.48%",
- "Nilah": "56.48%",
- "Karthus": "56.18%",
- "Zeri": "55.11%",
- "Seraphine": "55.06%",
- "Miss Fortune": "54.98%",
- "Cho'Gath": "54.69%",
- "Vayne": "54.60%",
- "Ashe": "54.54%",
- "Tahm Kench": "54.51%",
- "Yasuo": "54.49%",
- "Brand": "54.26%",
- "Twitch": "54.15%",
- "Swain": "53.87%",
- "Jhin": "52.87%",
- "Ziggs": "52.59%",
- "Sivir": "52.21%",
- "Ezreal": "51.36%",
- "Tristana": "51.36%",
- "Varus": "50.75%"
+ "Swain": "68.09%",
+ "Senna": "60.41%",
+ "Jinx": "59.02%",
+ "Seraphine": "58.90%",
+ "Ashe": "57.67%",
+ "Aphelios": "56.67%",
+ "Nilah": "56.52%",
+ "Vayne": "55.20%",
+ "Lucian": "54.63%",
+ "Smolder": "54.42%",
+ "Miss Fortune": "53.39%",
+ "Karthus": "53.06%",
+ "Xayah": "51.61%",
+ "Ezreal": "51.04%",
+ "Kalista": "50.59%",
+ "Twisted Fate": "50.31%"
}
},
{
"champion_name": "Rell",
"synergy": {
- "Ziggs": "61.31%",
- "Karthus": "60.18%",
- "Seraphine": "59.02%",
- "Tristana": "58.16%",
- "Lucian": "57.30%",
- "Twitch": "56.30%",
- "Miss Fortune": "54.44%",
- "Ashe": "53.88%",
- "Sivir": "53.49%",
- "Jinx": "52.83%",
- "Draven": "52.25%",
- "Xayah": "52.10%",
- "Ezreal": "52.08%",
- "Kai'Sa": "51.33%",
- "Samira": "51.15%",
- "Zeri": "50.35%",
- "Yasuo": "50.25%",
- "Aphelios": "50.21%"
- }
- },
- {
- "champion_name": "Neeko",
- "synergy": {
- "Nilah": "77.78%",
- "Yasuo": "59.46%",
- "Xayah": "59.26%",
- "Hwei": "58.82%",
- "Lucian": "56.79%",
- "Vayne": "53.85%",
- "Twitch": "53.17%",
- "Kalista": "50.75%",
- "Ezreal": "50.72%",
- "Ashe": "50.29%",
- "Quinn": "50.00%",
- "Samira": "50.00%",
- "Varus": "50.00%"
- }
- },
- {
- "champion_name": "Soraka",
- "synergy": {
- "Yasuo": "63.54%",
- "Swain": "59.70%",
- "Samira": "58.41%",
- "Vayne": "57.03%",
- "Ashe": "53.73%",
- "Jinx": "53.47%",
- "Ziggs": "53.38%",
- "Jhin": "53.17%",
- "Tristana": "52.80%",
- "Lucian": "52.34%",
- "Miss Fortune": "51.96%",
- "Twitch": "51.63%",
- "Draven": "50.30%"
- }
- },
- {
- "champion_name": "Blitzcrank",
- "synergy": {
- "Nilah": "58.59%",
- "Xayah": "55.50%",
- "Draven": "54.17%",
- "Lucian": "53.85%",
- "Ashe": "53.42%",
- "Vayne": "52.91%",
- "Twitch": "52.80%",
- "Seraphine": "52.34%",
- "Jinx": "51.90%",
- "Karthus": "51.89%",
- "Varus": "51.87%",
- "Tristana": "51.72%",
- "Miss Fortune": "51.53%",
- "Zeri": "51.27%",
- "Aphelios": "50.95%",
- "Kalista": "50.84%",
- "Jhin": "50.74%",
- "Ezreal": "50.08%",
- "Caitlyn": "50.00%"
- }
- },
- {
- "champion_name": "Zyra",
- "synergy": {
- "Seraphine": "55.06%",
- "Vayne": "54.84%",
- "Tristana": "54.05%",
- "Twitch": "52.96%",
- "Samira": "51.98%",
- "Jhin": "51.51%",
- "Karthus": "51.16%",
- "Draven": "50.65%",
- "Xayah": "50.00%"
- }
- },
- {
- "champion_name": "Pyke",
- "synergy": {
- "Karthus": "61.98%",
- "Nilah": "60.43%",
- "Yasuo": "57.30%",
- "Tristana": "55.96%",
- "Twitch": "55.08%",
- "Ashe": "54.27%",
- "Swain": "53.91%",
- "Ziggs": "53.28%",
- "Seraphine": "52.82%",
- "Vayne": "52.64%",
- "Varus": "51.58%",
- "Jhin": "51.51%",
- "Ezreal": "51.49%",
- "Hwei": "51.43%",
- "Draven": "50.95%"
+ "Xayah": "58.33%",
+ "Twisted Fate": "56.72%",
+ "Twitch": "56.48%",
+ "Nilah": "56.25%",
+ "Miss Fortune": "55.88%",
+ "Zeri": "55.45%",
+ "Senna": "55.08%",
+ "Vayne": "53.65%",
+ "Jinx": "53.40%",
+ "Kalista": "52.46%",
+ "Samira": "52.46%",
+ "Aphelios": "52.17%",
+ "Draven": "52.15%",
+ "Tristana": "51.90%",
+ "Karthus": "51.43%",
+ "Ezreal": "51.32%",
+ "Jhin": "50.43%",
+ "Yasuo": "50.00%"
}
},
{
"champion_name": "Zilean",
"synergy": {
- "Swain": "58.97%",
- "Hwei": "55.56%",
- "Samira": "54.12%",
- "Kog'Maw": "54.05%",
- "Twitch": "53.80%",
- "Aphelios": "53.47%",
- "Vayne": "51.88%",
- "Caitlyn": "51.79%",
- "Seraphine": "51.67%",
- "Ezreal": "51.07%",
- "Ashe": "50.12%"
- }
- },
- {
- "champion_name": "Braum",
- "synergy": {
- "Sivir": "60.82%",
- "Twitch": "59.55%",
- "Xayah": "54.40%",
- "Varus": "52.69%",
- "Kog'Maw": "52.03%",
- "Ashe": "51.92%",
- "Jinx": "51.32%",
- "Tristana": "50.94%",
- "Vayne": "50.83%",
- "Draven": "50.75%",
- "Ezreal": "50.35%"
- }
- },
- {
- "champion_name": "Vel'Koz",
- "synergy": {
- "Karthus": "61.90%",
- "Vayne": "55.34%",
- "Sivir": "54.72%",
- "Ashe": "54.44%",
- "Aphelios": "53.25%",
- "Seraphine": "52.38%",
- "Jhin": "51.92%",
- "Caitlyn": "51.43%",
- "Ezreal": "51.22%",
- "Twitch": "50.39%",
- "Varus": "50.37%",
- "Tristana": "50.00%",
- "Miss Fortune": "50.00%"
+ "Miss Fortune": "62.03%",
+ "Twisted Fate": "61.05%",
+ "Draven": "54.63%",
+ "Swain": "54.55%",
+ "Vayne": "54.47%",
+ "Ashe": "53.97%",
+ "Caitlyn": "53.13%",
+ "Seraphine": "52.44%",
+ "Smolder": "51.37%",
+ "Xayah": "51.22%",
+ "Sivir": "50.00%"
}
},
{
- "champion_name": "Thresh",
+ "champion_name": "Pyke",
"synergy": {
- "Tristana": "55.29%",
- "Hwei": "54.01%",
- "Draven": "53.28%",
- "Jinx": "52.84%",
- "Aphelios": "51.39%",
- "Twitch": "51.28%",
- "Kalista": "51.17%",
- "Samira": "51.08%",
- "Vayne": "51.01%",
- "Karthus": "50.91%",
- "Lucian": "50.61%",
- "Ashe": "50.26%",
- "Yasuo": "50.24%"
+ "Karthus": "60.47%",
+ "Swain": "58.06%",
+ "Senna": "57.89%",
+ "Ashe": "55.02%",
+ "Twitch": "54.55%",
+ "Vayne": "53.90%",
+ "Jinx": "53.67%",
+ "Yasuo": "53.66%",
+ "Seraphine": "53.19%",
+ "Twisted Fate": "53.07%",
+ "Nilah": "53.00%",
+ "Smolder": "51.63%",
+ "Zeri": "50.56%",
+ "Ezreal": "50.22%",
+ "Draven": "50.00%"
}
},
{
- "champion_name": "Milio",
+ "champion_name": "Zac",
"synergy": {
- "Kog'Maw": "53.82%",
- "Xayah": "52.76%",
- "Lucian": "51.94%",
- "Zeri": "51.56%",
- "Vayne": "51.56%",
- "Kalista": "51.56%",
- "Ashe": "51.48%",
- "Draven": "50.69%",
- "Aphelios": "50.42%",
- "Twitch": "50.00%"
+ "Aphelios": "83.33%",
+ "Caitlyn": "66.67%",
+ "Draven": "65.52%",
+ "Senna": "64.71%",
+ "Tristana": "61.54%",
+ "Twisted Fate": "61.29%",
+ "Zeri": "58.82%",
+ "Seraphine": "56.25%",
+ "Jinx": "55.00%",
+ "Ezreal": "52.38%",
+ "Smolder": "51.58%",
+ "Kalista": "51.52%",
+ "Xayah": "50.00%"
}
},
{
"champion_name": "Alistar",
"synergy": {
- "Seraphine": "59.43%",
- "Ziggs": "57.86%",
- "Sivir": "55.20%",
- "Hwei": "54.65%",
- "Zeri": "53.37%",
- "Ashe": "52.55%",
- "Yasuo": "52.42%",
- "Vayne": "52.01%",
- "Twitch": "51.83%",
- "Xayah": "51.49%",
- "Jinx": "50.82%",
- "Jhin": "50.68%",
- "Ezreal": "50.58%",
- "Draven": "50.28%",
- "Miss Fortune": "50.20%"
+ "Karthus": "64.86%",
+ "Seraphine": "62.86%",
+ "Zeri": "60.83%",
+ "Sivir": "59.26%",
+ "Jinx": "58.42%",
+ "Yasuo": "58.09%",
+ "Tristana": "55.84%",
+ "Senna": "53.27%",
+ "Twitch": "52.99%",
+ "Draven": "51.74%",
+ "Xayah": "51.25%",
+ "Ezreal": "50.91%",
+ "Twisted Fate": "50.75%",
+ "Samira": "50.00%"
}
},
{
- "champion_name": "Zac",
+ "champion_name": "Bard",
"synergy": {
- "Ziggs": "72.73%",
- "Sivir": "71.43%",
- "Zeri": "70.00%",
- "Varus": "63.64%",
- "Yasuo": "62.07%",
- "Jhin": "59.32%",
- "Kalista": "57.14%",
- "Hwei": "57.14%",
- "Twitch": "55.32%",
- "Ezreal": "52.63%",
- "Miss Fortune": "52.38%",
- "Vayne": "50.75%",
- "Swain": "50.00%",
- "Tristana": "50.00%",
- "Caitlyn": "50.00%"
+ "Yasuo": "60.34%",
+ "Seraphine": "59.53%",
+ "Senna": "59.42%",
+ "Ziggs": "57.58%",
+ "Caitlyn": "55.43%",
+ "Xayah": "54.26%",
+ "Hwei": "54.17%",
+ "Twisted Fate": "53.66%",
+ "Jinx": "52.43%",
+ "Twitch": "51.68%",
+ "Aphelios": "51.54%",
+ "Karthus": "51.47%",
+ "Samira": "50.60%",
+ "Vayne": "50.34%"
}
},
{
- "champion_name": "Leona",
+ "champion_name": "Sona",
"synergy": {
- "Seraphine": "56.47%",
- "Xayah": "55.00%",
- "Zeri": "54.63%",
- "Ezreal": "54.35%",
- "Twitch": "54.27%",
- "Caitlyn": "53.68%",
- "Jhin": "53.21%",
- "Yasuo": "53.19%",
- "Lucian": "52.72%",
- "Miss Fortune": "51.92%",
- "Aphelios": "51.70%",
- "Tristana": "51.48%",
- "Kalista": "51.16%"
+ "Nilah": "71.43%",
+ "Tristana": "63.16%",
+ "Twisted Fate": "56.19%",
+ "Jhin": "55.34%",
+ "Kai'Sa": "55.11%",
+ "Varus": "52.63%",
+ "Aphelios": "52.00%",
+ "Twitch": "51.46%",
+ "Vayne": "51.22%",
+ "Smolder": "50.72%",
+ "Lucian": "50.00%",
+ "Samira": "50.00%"
}
},
{
- "champion_name": "Xerath",
+ "champion_name": "Thresh",
"synergy": {
- "Ziggs": "55.24%",
- "Jinx": "53.95%",
- "Vayne": "53.32%",
- "Samira": "52.63%",
- "Xayah": "52.57%",
- "Ashe": "52.33%",
- "Jhin": "51.81%",
- "Caitlyn": "51.12%",
- "Hwei": "50.88%",
- "Tristana": "50.70%",
- "Ezreal": "50.00%"
+ "Yasuo": "62.82%",
+ "Senna": "58.33%",
+ "Xayah": "56.52%",
+ "Sivir": "56.25%",
+ "Seraphine": "55.74%",
+ "Kalista": "54.41%",
+ "Twitch": "52.68%",
+ "Miss Fortune": "52.44%",
+ "Jinx": "52.43%",
+ "Draven": "52.37%",
+ "Aphelios": "52.12%",
+ "Smolder": "51.38%",
+ "Tristana": "51.11%",
+ "Samira": "50.59%",
+ "Varus": "50.32%",
+ "Vayne": "50.15%",
+ "Kai'Sa": "50.05%"
}
},
{
- "champion_name": "Nami",
+ "champion_name": "Zyra",
"synergy": {
- "Swain": "59.52%",
- "Brand": "55.69%",
- "Karthus": "55.65%",
- "Twitch": "52.16%",
- "Ashe": "51.88%",
- "Seraphine": "51.43%",
- "Jhin": "51.32%",
- "Vayne": "51.29%",
- "Ezreal": "51.27%"
+ "Xayah": "66.07%",
+ "Sivir": "61.54%",
+ "Twisted Fate": "53.68%",
+ "Miss Fortune": "53.25%",
+ "Ezreal": "52.52%",
+ "Senna": "52.28%",
+ "Tristana": "50.94%",
+ "Vayne": "50.18%",
+ "Jinx": "50.00%"
}
},
{
- "champion_name": "Shaco",
+ "champion_name": "Vel'Koz",
"synergy": {
- "Seraphine": "85.71%",
- "Miss Fortune": "58.33%",
- "Samira": "54.84%",
- "Caitlyn": "54.43%",
- "Jhin": "54.01%",
- "Varus": "52.43%",
+ "Samira": "65.00%",
+ "Nilah": "60.00%",
+ "Hwei": "58.82%",
+ "Seraphine": "54.55%",
+ "Draven": "53.66%",
+ "Ashe": "53.52%",
+ "Tristana": "53.33%",
+ "Zeri": "53.13%",
+ "Senna": "51.90%",
+ "Kai'Sa": "51.02%",
+ "Jhin": "50.71%",
+ "Smolder": "50.22%",
+ "Sivir": "50.00%",
"Karthus": "50.00%"
}
},
{
- "champion_name": "Heimerdinger",
+ "champion_name": "Shaco",
"synergy": {
- "Zeri": "68.00%",
- "Sivir": "57.89%",
- "Draven": "54.29%",
- "Jhin": "54.03%",
- "Vayne": "53.49%",
- "Miss Fortune": "52.78%",
- "Ashe": "50.34%",
- "Kalista": "50.00%",
- "Twitch": "50.00%"
+ "Karthus": "76.92%",
+ "Twisted Fate": "62.64%",
+ "Tristana": "58.62%",
+ "Senna": "56.19%",
+ "Miss Fortune": "56.10%",
+ "Aphelios": "52.78%",
+ "Hwei": "52.63%",
+ "Ashe": "52.48%",
+ "Draven": "52.24%",
+ "Nilah": "50.00%",
+ "Caitlyn": "50.00%",
+ "Ziggs": "50.00%"
}
},
{
- "champion_name": "Sylas",
+ "champion_name": "Pantheon",
"synergy": {
- "Karthus": "60.00%",
- "Aphelios": "58.06%",
- "Nilah": "57.89%",
- "Miss Fortune": "57.14%",
- "Kalista": "55.36%",
- "Lucian": "54.65%",
- "Seraphine": "54.55%",
- "Sivir": "52.94%",
- "Zeri": "52.63%",
- "Vayne": "52.53%",
- "Jhin": "52.38%",
- "Ezreal": "51.14%",
- "Twitch": "50.77%",
- "Yasuo": "50.00%"
+ "Karthus": "66.67%",
+ "Twisted Fate": "58.82%",
+ "Miss Fortune": "56.60%",
+ "Senna": "56.59%",
+ "Jhin": "55.67%",
+ "Nilah": "54.55%",
+ "Ezreal": "54.37%",
+ "Swain": "53.85%",
+ "Lucian": "53.06%",
+ "Ashe": "52.38%",
+ "Smolder": "52.07%",
+ "Samira": "51.97%",
+ "Jinx": "51.72%"
}
},
{
"champion_name": "Tahm Kench",
"synergy": {
- "Veigar": "80.00%",
- "Ziggs": "75.00%",
- "Karthus": "66.67%",
- "Seraphine": "58.33%",
- "Xayah": "58.33%",
- "Jhin": "55.56%",
- "Miss Fortune": "51.72%",
- "Twitch": "51.28%",
- "Kog'Maw": "50.00%",
- "Kalista": "50.00%",
- "Ashe": "50.00%",
- "Ezreal": "50.00%"
+ "Kalista": "68.75%",
+ "Jinx": "65.00%",
+ "Nilah": "62.50%",
+ "Xayah": "62.50%",
+ "Twitch": "57.14%",
+ "Varus": "55.10%",
+ "Twisted Fate": "54.55%",
+ "Zeri": "53.85%",
+ "Senna": "52.51%",
+ "Seraphine": "50.00%"
}
},
{
- "champion_name": "Amumu",
- "synergy": {
- "Yasuo": "71.43%",
- "Ziggs": "66.67%",
- "Kalista": "64.15%",
- "Lucian": "60.00%",
- "Karthus": "60.00%",
- "Hwei": "57.14%",
- "Ezreal": "55.88%",
- "Nilah": "55.56%",
- "Draven": "55.41%",
- "Miss Fortune": "54.37%",
- "Ashe": "54.17%",
- "Jinx": "53.85%",
- "Kai'Sa": "53.60%",
- "Tristana": "52.63%",
- "Zeri": "50.00%",
- "Veigar": "50.00%"
+ "champion_name": "Nautilus",
+ "synergy": {
+ "Seraphine": "55.21%",
+ "Senna": "54.59%",
+ "Kalista": "52.55%",
+ "Xayah": "52.25%",
+ "Caitlyn": "51.40%",
+ "Karthus": "51.11%",
+ "Twitch": "50.96%",
+ "Lucian": "50.86%",
+ "Jhin": "50.64%",
+ "Draven": "50.62%",
+ "Vayne": "50.27%",
+ "Zeri": "50.21%",
+ "Samira": "50.09%",
+ "Nilah": "50.00%"
}
},
{
- "champion_name": "Twitch",
+ "champion_name": "Xerath",
"synergy": {
- "Sivir": "70.97%",
- "Swain": "70.00%",
- "Seraphine": "63.64%",
- "Draven": "57.89%",
- "Cassiopeia": "53.85%",
- "Vayne": "53.54%",
- "Jhin": "53.40%",
- "Jinx": "52.27%",
- "Ziggs": "51.72%",
- "Zeri": "51.61%",
- "Nilah": "50.00%",
- "Kalista": "50.00%",
- "Hwei": "50.00%",
- "Tristana": "50.00%"
+ "Samira": "70.00%",
+ "Twisted Fate": "61.36%",
+ "Caitlyn": "57.97%",
+ "Karthus": "55.56%",
+ "Ashe": "52.46%",
+ "Vayne": "50.55%",
+ "Zeri": "50.00%"
}
},
{
- "champion_name": "Nautilus",
+ "champion_name": "Brand",
"synergy": {
- "Seraphine": "58.68%",
- "Ziggs": "55.43%",
- "Karthus": "55.21%",
- "Lucian": "51.71%",
- "Tristana": "51.50%",
- "Samira": "51.14%",
- "Kalista": "50.71%",
- "Jinx": "50.13%"
+ "Samira": "59.09%",
+ "Yasuo": "57.89%",
+ "Twisted Fate": "57.14%",
+ "Xayah": "56.52%",
+ "Jinx": "55.88%",
+ "Jhin": "53.98%",
+ "Draven": "53.70%",
+ "Seraphine": "52.50%",
+ "Aphelios": "50.00%"
}
},
{
- "champion_name": "Ashe",
+ "champion_name": "Morgana",
"synergy": {
- "Karthus": "56.83%",
- "Seraphine": "54.60%",
- "Jinx": "54.17%",
- "Nilah": "53.85%",
- "Twitch": "53.55%",
- "Ezreal": "51.69%",
- "Swain": "51.32%",
- "Ziggs": "50.79%",
- "Lucian": "50.00%",
- "Zeri": "50.00%"
+ "Xayah": "60.00%",
+ "Draven": "57.81%",
+ "Nilah": "54.55%",
+ "Samira": "51.61%",
+ "Sivir": "50.00%",
+ "Seraphine": "50.00%"
}
},
{
- "champion_name": "Lux",
+ "champion_name": "Heimerdinger",
"synergy": {
- "Karthus": "59.46%",
- "Twitch": "53.57%",
- "Xayah": "52.17%",
- "Ashe": "51.05%",
- "Varus": "50.88%",
- "Tristana": "50.67%",
+ "Tristana": "80.00%",
+ "Aphelios": "80.00%",
+ "Draven": "64.29%",
+ "Twitch": "62.50%",
+ "Twisted Fate": "60.00%",
+ "Jhin": "55.56%",
+ "Varus": "53.85%",
+ "Senna": "51.28%",
+ "Miss Fortune": "50.00%",
"Jinx": "50.00%",
- "Zeri": "50.00%"
+ "Nilah": "50.00%"
}
},
{
- "champion_name": "Brand",
+ "champion_name": "Ashe",
"synergy": {
- "Seraphine": "57.69%",
- "Karthus": "55.17%",
- "Yasuo": "55.17%",
- "Kalista": "53.49%",
- "Tristana": "52.31%",
- "Xayah": "51.40%",
- "Varus": "50.17%"
+ "Jinx": "53.73%",
+ "Jhin": "53.36%",
+ "Karthus": "52.31%",
+ "Kalista": "51.90%",
+ "Twisted Fate": "51.81%",
+ "Zeri": "50.65%"
}
},
{
- "champion_name": "Seraphine",
+ "champion_name": "Sylas",
"synergy": {
- "Vayne": "59.62%",
- "Tristana": "59.38%",
- "Miss Fortune": "52.50%",
- "Ashe": "52.20%",
- "Jinx": "51.11%",
- "Draven": "50.94%"
+ "Miss Fortune": "75.00%",
+ "Zeri": "72.73%",
+ "Twitch": "68.75%",
+ "Kalista": "68.42%",
+ "Draven": "65.22%",
+ "Tristana": "61.54%",
+ "Senna": "60.00%",
+ "Aphelios": "60.00%",
+ "Jinx": "55.56%",
+ "Jhin": "50.00%"
}
},
{
- "champion_name": "Lulu",
+ "champion_name": "Neeko",
"synergy": {
- "Miss Fortune": "55.96%",
- "Sivir": "55.62%",
- "Tristana": "55.22%",
- "Ashe": "51.59%",
+ "Yasuo": "67.74%",
+ "Tristana": "66.67%",
+ "Twitch": "65.63%",
+ "Ashe": "60.87%",
+ "Senna": "59.22%",
+ "Nilah": "57.14%",
+ "Caitlyn": "54.55%",
"Draven": "51.52%",
- "Varus": "51.22%",
- "Twitch": "51.14%",
- "Jinx": "50.76%",
- "Kog'Maw": "50.00%",
- "Kalista": "50.00%"
+ "Twisted Fate": "50.94%",
+ "Vayne": "50.00%",
+ "Jinx": "50.00%",
+ "Miss Fortune": "50.00%"
}
},
{
- "champion_name": "Morgana",
+ "champion_name": "Karma",
"synergy": {
- "Karthus": "55.56%",
- "Nilah": "53.13%",
- "Miss Fortune": "52.07%",
- "Hwei": "51.72%",
- "Vayne": "51.31%",
- "Sivir": "50.91%",
- "Draven": "50.63%",
- "Tristana": "50.00%"
+ "Tristana": "54.41%",
+ "Ashe": "52.79%",
+ "Twisted Fate": "52.57%",
+ "Jinx": "52.21%",
+ "Miss Fortune": "51.67%",
+ "Kalista": "51.02%",
+ "Twitch": "50.19%",
+ "Caitlyn": "50.15%",
+ "Zeri": "50.00%"
}
},
{
- "champion_name": "Karma",
+ "champion_name": "Lux",
"synergy": {
- "Draven": "53.57%",
- "Vayne": "51.30%",
- "Jhin": "50.49%",
- "Ezreal": "50.43%",
- "Tristana": "50.42%"
+ "Ziggs": "57.14%",
+ "Twisted Fate": "56.82%",
+ "Samira": "52.63%",
+ "Senna": "52.05%",
+ "Ezreal": "50.65%",
+ "Nilah": "50.00%"
}
},
{
- "champion_name": "Swain",
+ "champion_name": "Seraphine",
"synergy": {
- "Tristana": "64.29%",
- "Karthus": "54.55%",
- "Vayne": "54.01%",
- "Jhin": "53.18%",
- "Twitch": "50.70%",
- "Nilah": "50.00%",
- "Ziggs": "50.00%"
+ "Lucian": "61.54%",
+ "Tristana": "61.54%",
+ "Vayne": "58.90%",
+ "Varus": "57.69%",
+ "Jinx": "56.25%",
+ "Miss Fortune": "55.32%",
+ "Senna": "51.51%",
+ "Ezreal": "50.83%",
+ "Samira": "50.00%",
+ "Karthus": "50.00%"
}
},
{
- "champion_name": "Pantheon",
+ "champion_name": "Yuumi",
"synergy": {
- "Miss Fortune": "63.41%",
- "Seraphine": "56.25%",
- "Yasuo": "54.55%",
- "Swain": "52.63%",
- "Varus": "52.05%",
- "Zeri": "50.00%"
+ "Rammus": "64.71%",
+ "Jinx": "55.32%",
+ "Vayne": "54.26%",
+ "Samira": "53.57%",
+ "Lucian": "53.52%",
+ "Tristana": "53.33%",
+ "Twitch": "53.31%",
+ "Jhin": "50.00%",
+ "Xayah": "50.00%"
}
},
{
- "champion_name": "Yuumi",
+ "champion_name": "Twitch",
"synergy": {
- "Seraphine": "66.67%",
- "Yasuo": "53.25%",
- "Tristana": "52.55%",
- "Vayne": "51.74%",
- "Zeri": "51.45%",
- "Twitch": "50.41%"
+ "Karma": "75.00%",
+ "Lucian": "66.67%",
+ "Cassiopeia": "66.67%",
+ "Janna": "66.67%",
+ "Aphelios": "66.67%",
+ "Jhin": "61.90%",
+ "Zeri": "60.87%",
+ "Varus": "60.00%",
+ "Kalista": "57.14%",
+ "Xayah": "53.85%",
+ "Ezreal": "53.52%",
+ "Vayne": "53.19%",
+ "Sivir": "50.00%",
+ "Jinx": "50.00%",
+ "Lulu": "50.00%"
}
},
{
"champion_name": "Hwei",
"synergy": {
- "Nilah": "51.61%"
+ "Lucian": "57.14%",
+ "Tristana": "54.55%",
+ "Swain": "53.85%",
+ "Miss Fortune": "51.85%",
+ "Seraphine": "51.02%"
}
}
]
\ No newline at end of file
diff --git a/src/personal_finance/.txt b/src/personal_finance/.txt
new file mode 100755
index 0000000..e69de29
diff --git a/src/personal_finance/fin/scrape.py b/src/personal_finance/fin/scrape.py
new file mode 100755
index 0000000..6998d00
--- /dev/null
+++ b/src/personal_finance/fin/scrape.py
@@ -0,0 +1,232 @@
+import pandas as pd
+import os
+from io import StringIO
+import json
+import openpyxl
+
+#simplified working version
+
+# Define the directory containing the CSV files
+directory = '/home/eikov/fin/OG-CSV'
+
+# Define the pattern for file names
+file_pattern = '{}{}.csv' # {} will be replaced by the file number and +/- for the file type
+
+# Initialize empty dictionaries to store dataframes for each month
+spending_dfs = {}
+income_dfs = {}
+
+# Define the filters
+filter1 = "KOVALIŪNAS EINARAS"
+filter2 = "Einaras Kovaliūnas" # Add your second filter value here
+
+# Function to extract first 4 words from a string
+def extract_first_4_words(text):
+ if pd.isna(text): # Check if the value is NaN
+ return "" # Return an empty string if NaN
+ words = text.split()[:4]
+ return ' '.join(words)
+
+
+# Iterate over the file numbers from 1 to 12
+for file_num in range(1, 13):
+ month = str(file_num)
+ spending_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"])
+ income_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"])
+ for file_type in ['+', '-']:
+ filename = file_pattern.format(file_num, file_type)
+ filepath = os.path.join(directory, filename)
+ if os.path.exists(filepath):
+ print(f"Processing file: {filename}")
+ # Initialize an empty list to store the valid lines of the CSV file
+ valid_lines = []
+ skipped_rows = []
+
+ # Read the CSV file line by line
+ with open(filepath, 'r', encoding='utf-8') as file:
+ for i, line in enumerate(file):
+ if i < 2: # Skip the first two lines (first row and header row)
+ continue
+ try:
+ # Attempt to parse the line as CSV
+ pd.read_csv(StringIO(line), header=None, sep=';') # Use header=None and specify separator as ';'
+ valid_lines.append(line) # If successful, add the line to the list of valid lines
+ except pd.errors.ParserError:
+ print(f"Skipped line {i+1} in file {filename} due to ParserError: {line.strip()}")
+ skipped_rows.append((i+1, line.strip())) # Store the line number and the content of the skipped row
+
+ # Read the valid lines into a DataFrame, skipping the header line
+ bank_data = None
+ try:
+ bank_data = pd.read_csv(StringIO(''.join(valid_lines)), header=None, sep=';')
+ except pd.errors.ParserError:
+ print(f"Encountered an error while parsing file {filename}. Skipping problematic rows.")
+ if not valid_lines:
+ print("No valid data read due to errors.")
+ continue
+
+ # If data was successfully read, extract specific columns and apply filter
+ if bank_data is not None:
+ bank_data = bank_data[[1, 3, 4, 9]] # Extract only desired columns
+
+ # Replace empty values with NaN
+ bank_data.replace('-', pd.NA, inplace=True)
+
+ # Rename columns
+ bank_data.columns = ["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"]
+
+ # Apply filters
+ bank_data = bank_data[(bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] != filter1) &
+ (bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] != filter2)]
+
+ # Filter out rows that are the same as column headers or contain all empty values
+ bank_data = bank_data[~bank_data.apply(lambda row: all(row == ["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"]), axis=1)]
+
+ # Extract first 4 words from specified columns
+ bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] = bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"].apply(extract_first_4_words)
+ bank_data["MOKÄ–JIMO PASKIRTIS"] = bank_data["MOKÄ–JIMO PASKIRTIS"].apply(extract_first_4_words)
+
+ # Append the DataFrame to the appropriate dictionary based on file type
+ if file_type == '+':
+ income_dfs[month] = bank_data
+ elif file_type == '-':
+ spending_dfs[month] = bank_data
+ else:
+ print(f"No valid data read from file {filename}.")
+
+ # Display the DataFrame
+ if file_type == '+':
+ print(f"\nIncome DataFrame for month {month}:")
+ print(income_dfs[month])
+ elif file_type == '-':
+ print(f"\nSpending DataFrame for month {month}:")
+ print(spending_dfs[month])
+
+ # Specify the folder path
+ output_folder_income = "/home/eikov/fin/income"
+ output_folder_spending = "/home/eikov/fin/spendings"
+
+ # Create the folder if it doesn't exist
+ os.makedirs(output_folder_income, exist_ok=True)
+ os.makedirs(output_folder_spending, exist_ok=True)
+
+ # Save DataFrames to JSON files with UTF-8 encoding
+ income_json_filename = os.path.join( output_folder_income, f"income_month_{month}.json")
+ spending_json_filename = os.path.join(output_folder_spending, f"spending_month_{month}.json")
+
+ # Save DataFrames to Excel files
+ income_excel_filename = os.path.join( output_folder_income, f"income_month_{month}.xlsx")
+ spending_excel_filename = os.path.join(output_folder_spending, f"spending_month_{month}.xlsx")
+
+ # Drop rows with NaN values
+ income_dfs[month].dropna(inplace=True)
+ spending_dfs[month].dropna(inplace=True)
+
+ # Convert DataFrames to JSON strings
+ income_json_str = income_dfs[month].to_json(orient='records', lines=True, default_handler=str)
+ spending_json_str = spending_dfs[month].to_json(orient='records', lines=True, default_handler=str)
+ """
+ # Write JSON strings to files with UTF-8 encoding
+ with open(income_json_filename, 'w+', encoding='utf-8') as income_file:
+ for line in income_json_str.split('\n'):
+ if line.strip(): # Check if line is not empty
+ income_file.write(line.strip() + '\n')
+
+ with open(spending_json_filename, 'w+', encoding='utf-8') as spending_file:
+ for line in spending_json_str.split('\n'):
+ if line.strip(): # Check if line is not empty
+ spending_file.write(line.strip() + '\n')
+ """
+ # Save DataFrames to Excel files
+ income_dfs[month].to_excel(income_excel_filename, index=False)
+ spending_dfs[month].to_excel(spending_excel_filename, index=False)
+
+ print(f"DataFrames saved to JSON files: {income_json_filename}, {spending_json_filename}")
+ print(f"DataFrames saved to Excel files: {income_excel_filename}, {spending_excel_filename}")
+
+ # Display skipped rows
+ if skipped_rows:
+ print(f"\nSkipped rows in file {filename}:")
+ for row_num, row_content in skipped_rows:
+ print(f"Row {row_num}: {row_content}")
+ else:
+ print(f"File {filename} not found. Skipping...")
+
+print("All files processed.")
+
+
+
+#Merge spending seperate xlsx files to one
+directory = '/home/eikov/fin/spendings'
+output_directory = '/home/eikov/fin/analysis'
+
+# List all files in the directory
+files = os.listdir(directory)
+
+# Filter out only the Excel files (files ending with .xlsx)
+excel_files = [file for file in files if file.endswith('.xlsx')]
+
+# Create a new workbook
+merged_workbook = openpyxl.Workbook()
+
+# Loop through each file and add it as a new sheet to the workbook
+for i, file_name in enumerate(excel_files):
+ # Load the workbook from file
+ workbook = openpyxl.load_workbook(os.path.join(directory, file_name))
+
+ # Get the active sheet from the loaded workbook
+ sheet = workbook.active
+
+ # Copy the active sheet to the merged workbook
+ if i == 0:
+ # For the first sheet, use the default sheet name 'Sheet'
+ merged_sheet = merged_workbook.active
+ else:
+ # For subsequent sheets, create a new sheet with the file name as the sheet name
+ merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}")
+
+ # Copy data from the original sheet to the merged sheet
+ for row in sheet.iter_rows(values_only=True):
+ merged_sheet.append(row)
+
+# Save the merged workbook to the output directory
+output_filename = os.path.join(output_directory, "merged_spending_data.xlsx")
+merged_workbook.save(output_filename)
+
+
+#Merge income seperate xlsx files to one
+directory = '/home/eikov/fin/income'
+output_directory = '/home/eikov/fin/analysis'
+
+# List all files in the directory
+files = os.listdir(directory)
+
+# Filter out only the Excel files (files ending with .xlsx)
+excel_files = [file for file in files if file.endswith('.xlsx')]
+
+# Create a new workbook
+merged_workbook = openpyxl.Workbook()
+
+# Loop through each file and add it as a new sheet to the workbook
+for i, file_name in enumerate(excel_files):
+ # Load the workbook from file
+ workbook = openpyxl.load_workbook(os.path.join(directory, file_name))
+
+ # Get the active sheet from the loaded workbook
+ sheet = workbook.active
+
+ # Copy the active sheet to the merged workbook
+ if i == 0:
+ # For the first sheet, use the default sheet name 'Sheet'
+ merged_sheet = merged_workbook.active
+ else:
+ # For subsequent sheets, create a new sheet with the file name as the sheet name
+ merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}")
+
+ # Copy data from the original sheet to the merged sheet
+ for row in sheet.iter_rows(values_only=True):
+ merged_sheet.append(row)
+
+# Save the merged workbook to the output directory
+output_filename = os.path.join(output_directory, "merged_income_data.xlsx")
+merged_workbook.save(output_filename)
\ No newline at end of file
diff --git a/src/personal_finance/pandamat1.0.py b/src/personal_finance/pandamat1.0.py
new file mode 100644
index 0000000..da52511
--- /dev/null
+++ b/src/personal_finance/pandamat1.0.py
@@ -0,0 +1,28 @@
+import pandas as pd
+import matplotlib.pyplot as plt
+
+# Load the Totals sheet
+file_path = '/home/eikov/fin/analysis/Manually-polished/2025/Galutinis_spendings.xlsx' # change filename
+df = pd.read_excel(file_path, sheet_name='Totals')
+
+# Ensure SUMA is numeric (clean up bad values)
+df['SUMA'] = pd.to_numeric(df['SUMA'], errors='coerce') # convert non-numeric to NaN
+df = df.dropna(subset=['SUMA']) # drop rows where SUMA couldn't be converted
+
+# Group by Category (or Subcategory)
+grouped = df.groupby('Category')['SUMA'].sum().sort_values(ascending=False)
+
+# Optional: filter out zero or very small values
+grouped = grouped[grouped > 0]
+
+# Plot
+plt.figure(figsize=(8, 8))
+grouped.plot.pie(autopct='%1.1f%%', startangle=90)
+plt.title('Spending Distribution by Category')
+plt.ylabel('')
+plt.tight_layout()
+
+# Save pie chart
+output_path = '/home/eikov/fin/analysis/spending_pie_by_category.png'
+plt.savefig(output_path)
+print(f"Saved pie chart to: {output_path}")
diff --git a/src/personal_finance/pandamat1.1.py b/src/personal_finance/pandamat1.1.py
new file mode 100644
index 0000000..1042f4c
--- /dev/null
+++ b/src/personal_finance/pandamat1.1.py
@@ -0,0 +1,43 @@
+import pandas as pd
+import matplotlib.pyplot as plt
+from openpyxl import load_workbook
+from openpyxl.drawing.image import Image as OpenpyxlImage
+
+#basic
+
+# File paths
+excel_file = '/home/eikov/fin/analysis/Manually-polished/2025/Galutinis_spendings.xlsx' # Replace with actual file name
+chart_image_path = '/home/eikov/fin/analysis/Manually-polished/2025/spending_pie_by_category.png'
+
+# 1. Load data from Totals sheet
+df = pd.read_excel(excel_file, sheet_name='Totals')
+
+# 2. Clean and group
+df['SUMA'] = pd.to_numeric(df['SUMA'], errors='coerce')
+df = df.dropna(subset=['SUMA'])
+grouped = df.groupby('Category')['SUMA'].sum().sort_values(ascending=False)
+grouped = grouped[grouped > 0]
+
+# 3. Create and save pie chart
+plt.figure(figsize=(8, 8))
+grouped.plot.pie(autopct='%1.1f%%', startangle=90)
+plt.title('Spending Distribution by Category')
+plt.ylabel('')
+plt.tight_layout()
+plt.savefig(chart_image_path)
+plt.close()
+
+# 4. Insert image into a new sheet in the Excel file
+workbook = load_workbook(excel_file)
+if 'Analysis' in workbook.sheetnames:
+ del workbook['Analysis'] # remove if it already exists (optional)
+sheet = workbook.create_sheet('Analysis')
+
+# Insert image
+img = OpenpyxlImage(chart_image_path)
+img.anchor = 'A1' # Position in the sheet
+sheet.add_image(img)
+
+# Save workbook
+workbook.save(excel_file)
+print(f"✅ Pie chart inserted into 'Analysis' sheet of: {excel_file}")
diff --git a/src/personal_finance/pandamat1.2.py b/src/personal_finance/pandamat1.2.py
new file mode 100644
index 0000000..37dca65
--- /dev/null
+++ b/src/personal_finance/pandamat1.2.py
@@ -0,0 +1,84 @@
+import pandas as pd
+import matplotlib.pyplot as plt
+from openpyxl import load_workbook
+from openpyxl.drawing.image import Image as OpenpyxlImage
+
+#ok
+
+# File paths
+excel_file = '/home/eikov/fin/analysis/Manually-polished/2025/Galutinis_spendings.xlsx' # Replace with actual file name
+chart_image_path = '/home/eikov/fin/analysis/Manually-polished/2025/spending_pie_by_category.png'
+
+# 1. Load data from Totals sheet
+df = pd.read_excel(excel_file, sheet_name='Totals')
+
+# 2. Clean and group
+df['SUMA'] = pd.to_numeric(df['SUMA'], errors='coerce')
+df = df.dropna(subset=['SUMA'])
+grouped = df.groupby('Category')['SUMA'].sum().sort_values(ascending=False)
+grouped = grouped[grouped > 0]
+
+
+
+
+# 3. Create and save pie chart
+# Group by Category and Subcategory
+df['SUMA'] = pd.to_numeric(df['SUMA'], errors='coerce')
+df = df.dropna(subset=['SUMA'])
+grouped = df.groupby(['Category', 'Subcategory'])['SUMA'].sum().sort_values(ascending=False)
+
+# Filter small values if chart gets too crowded (optional)
+grouped = grouped[grouped > 0]
+
+# Build custom labels: "Category: Subcategory"
+labels = [f"{cat}: {sub}" for cat, sub in grouped.index]
+
+# Plot with smaller font
+plt.figure(figsize=(10, 10))
+patches, texts, autotexts = plt.pie(
+ grouped.values,
+ labels=labels,
+ autopct='%1.1f%%',
+ startangle=90,
+ textprops={'fontsize': 8}
+)
+plt.title('Spending Breakdown by Category and Subcategory', fontsize=12)
+plt.tight_layout()
+
+# stacking small text
+plt.figure(figsize=(10, 10))
+patches, texts, autotexts = plt.pie(
+ grouped.values,
+ labels=None, # Don't show labels on the pie
+ autopct='%1.1f%%',
+ startangle=90,
+ textprops={'fontsize': 8}
+)
+
+# stacking small text: Add legend outside the pie
+plt.legend(patches, labels, loc='center left', bbox_to_anchor=(1, 0.5), fontsize=8)
+plt.title('Spending Breakdown by Category and Subcategory', fontsize=12)
+plt.tight_layout()
+
+
+
+
+# Save image
+chart_image_path = '/home/eikov/fin/analysis/spending_pie_by_category_sub.png'
+plt.savefig(chart_image_path)
+plt.close()
+
+# 4. Insert image into a new sheet in the Excel file
+workbook = load_workbook(excel_file)
+if 'Analysis' in workbook.sheetnames:
+ del workbook['Analysis'] # remove if it already exists (optional)
+sheet = workbook.create_sheet('Analysis')
+
+# Insert image
+img = OpenpyxlImage(chart_image_path)
+img.anchor = 'A1' # Position in the sheet
+sheet.add_image(img)
+
+# Save workbook
+workbook.save(excel_file)
+print(f"✅ Pie chart inserted into 'Analysis' sheet of: {excel_file}")
diff --git a/src/personal_finance/pandamat1.3.py b/src/personal_finance/pandamat1.3.py
new file mode 100644
index 0000000..550fd46
--- /dev/null
+++ b/src/personal_finance/pandamat1.3.py
@@ -0,0 +1,73 @@
+import pandas as pd
+import matplotlib.pyplot as plt
+from openpyxl import load_workbook
+from openpyxl.drawing.image import Image as ExcelImage
+
+#good
+
+# === Load data ===
+excel_file = "/home/eikov/fin/analysis/Manually-polished/2025/Galutinis_spendings.xlsx" # <-- Replace with your actual file
+sheet_name = "Totals"
+
+df = pd.read_excel(excel_file, sheet_name=sheet_name)
+
+# === Group by Category and Subcategory ===
+grouped = df.groupby(['Category', 'Subcategory'], as_index=False)['SUMA'].sum()
+
+# Remove rows with zero or negative sums (optional)
+grouped = grouped[grouped['SUMA'] > 0]
+
+# Sort by SUMA descending
+grouped = grouped.sort_values(by='SUMA', ascending=False)
+
+# === Create pie chart ===
+fig, ax = plt.subplots(figsize=(8, 8))
+
+# Pie chart data
+sizes = grouped['SUMA']
+labels_raw = grouped[['Category', 'Subcategory']].agg(' – '.join, axis=1)
+
+# Plot pie and get returned patches
+patches, texts, autotexts = ax.pie(
+ sizes,
+ autopct='%1.1f%%',
+ startangle=90
+)
+
+# Create legend with formatted labels including %
+total = sizes.sum()
+percentages = (sizes / total * 100).round(1)
+legend_labels = [f"{p:.1f}% – {cat}" for p, cat in zip(percentages, labels_raw)]
+
+# Sort legend entries by percentages
+sorted_indices = percentages.argsort()[::-1]
+sorted_labels = [legend_labels[i] for i in sorted_indices]
+sorted_patches = [patches[i] for i in sorted_indices]
+
+# Display legend
+ax.legend(sorted_patches, sorted_labels, loc="center left", bbox_to_anchor=(1, 0.5), fontsize='small')
+
+# Save figure
+chart_path = "output_chart.png"
+plt.tight_layout()
+plt.savefig(chart_path)
+plt.close()
+
+# === Insert chart into Excel in sheet "Analysis" ===
+wb = load_workbook(excel_file)
+
+# Create or get the "Analysis" sheet
+if "Analysis" in wb.sheetnames:
+ ws = wb["Analysis"]
+else:
+ ws = wb.create_sheet("Analysis")
+
+# Add image to worksheet
+img = ExcelImage(chart_path)
+img.anchor = "A1"
+ws.add_image(img)
+
+# Save updated Excel file
+wb.save(excel_file)
+
+print("Chart generated and embedded into the 'Analysis' sheet.")
diff --git a/src/personal_finance/pandamat1.4.py b/src/personal_finance/pandamat1.4.py
new file mode 100644
index 0000000..394e1dc
--- /dev/null
+++ b/src/personal_finance/pandamat1.4.py
@@ -0,0 +1,84 @@
+import pandas as pd
+import matplotlib.pyplot as plt
+from openpyxl import load_workbook
+from openpyxl.drawing.image import Image as ExcelImage
+
+#so far the best one
+
+# === Load data ===
+excel_file = "/home/eikov/fin/analysis/Manually-polished/2025/Galutinis_spendings.xlsx" # <-- Replace with your actual file
+sheet_name = "Totals"
+
+df = pd.read_excel(excel_file, sheet_name=sheet_name)
+
+# === Group by Category and Subcategory ===
+grouped = df.groupby(['Category', 'Subcategory'], as_index=False)['SUMA'].sum()
+
+# Remove zero or negative values
+grouped = grouped[grouped['SUMA'] > 0]
+
+# Sort by SUMA descending
+grouped = grouped.sort_values(by='SUMA', ascending=False)
+
+# === Create pie chart ===
+fig, ax = plt.subplots(figsize=(8, 8))
+
+# Pie data
+sizes = grouped['SUMA']
+labels_raw = grouped[['Category', 'Subcategory']].agg(' – '.join, axis=1)
+
+# Pie chart
+patches, texts, autotexts = ax.pie(
+ sizes,
+ autopct='%1.1f%%',
+ startangle=90
+)
+
+# Generate custom labels with % + numeric sum + category info
+total = sizes.sum()
+percentages = (sizes / total * 100).round(1)
+sums_formatted = sizes.round(2)
+
+legend_labels = [
+ f"{p:.1f}% – {s:.2f} – {label}"
+ for p, s, label in zip(percentages, sums_formatted, labels_raw)
+]
+
+# Sort legend entries by % descending
+sorted_indices = percentages.argsort()[::-1]
+sorted_labels = [legend_labels[i] for i in sorted_indices]
+sorted_patches = [patches[i] for i in sorted_indices]
+
+# Add legend
+ax.legend(
+ sorted_patches,
+ sorted_labels,
+ loc="center left",
+ bbox_to_anchor=(1, 0.5),
+ fontsize='small'
+)
+
+# Save the pie chart
+chart_path = "output_chart.png"
+plt.tight_layout()
+plt.savefig(chart_path)
+plt.close()
+
+# === Insert chart into Excel ===
+wb = load_workbook(excel_file)
+
+# Create or get the "Analysis" sheet
+if "Analysis" in wb.sheetnames:
+ ws = wb["Analysis"]
+else:
+ ws = wb.create_sheet("Analysis")
+
+# Add image
+img = ExcelImage(chart_path)
+img.anchor = "A1"
+ws.add_image(img)
+
+# Save updated Excel file
+wb.save(excel_file)
+
+print("Chart with enhanced legend saved and embedded into 'Analysis' sheet.")
diff --git a/src/personal_finance/run_script.sh b/src/personal_finance/run_script.sh
new file mode 100755
index 0000000..1fab98f
--- /dev/null
+++ b/src/personal_finance/run_script.sh
@@ -0,0 +1,15 @@
+#!/bin/zsh
+
+# activate the virtual environment, because that's where all modules are installed, not in system python
+source ~/scraping_env/bin/activate
+
+# Navigate to the directory where your Python script is located
+cd /home/eikov/fin
+
+# Run your Python script
+python3 scrape.py #or latest version like v2.3.py
+
+# optional: for better analysis create visual charts with pandas, mathplotlib
+python3 pandamat.py
+
+chmod +x run_script.sh
diff --git a/src/personal_finance/scheme.txt b/src/personal_finance/scheme.txt
new file mode 100755
index 0000000..77f3d4a
--- /dev/null
+++ b/src/personal_finance/scheme.txt
@@ -0,0 +1,17 @@
+step1: download seb monthly csv israsus
+step2: rename them by month and add +(if parsing incomes) or -(if parsing spendings)
+step3: enjoy clean data in xlsx , showing Date/Sum/Moketojas/Paskirtis
+step4: edit summed-strings-spendings.xlsx to assign each row sub-category and category, rename to a) cat-income.xlsx b) cat-spendings.xlsx then rerun .py again
+step5: run pandamat.py for visuals, charts, analysis
+
+Scheme
+Step 0 DONE: Download monthly banko israsus
+FUNCTIONALITY 1 DONE: Extract wanted columns: "DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS" .... and clean data to make it more neat, understandable. f
+ilter-out own transactions
+
+TBD:
+FUNCTIONALITY 2: add new column "Category"
+FUNCTIONALITY 3: Excel calculations, analysis, pie chart and projections
+FUNCTIONALITY 4 DONE: Extract incomes and clean data: filter-out own transactions
+Extra: Calculate avg of 3 months fixed costs spending
+Extra: Compare this realistic report with realistic numbers to my manual "personal finance" balance sheet
diff --git a/src/personal_finance/v1.7.py b/src/personal_finance/v1.7.py
new file mode 100755
index 0000000..42f7430
--- /dev/null
+++ b/src/personal_finance/v1.7.py
@@ -0,0 +1,143 @@
+import pandas as pd
+import os
+from io import StringIO
+import json
+
+# Define the directory containing the CSV files
+directory = 'OGCSV/'
+
+# Define the pattern for file names
+file_pattern = '{}{}.csv' # {} will be replaced by the file number and +/- for the file type
+
+# Initialize empty dictionaries to store dataframes for each month
+spending_dfs = {}
+income_dfs = {}
+
+# Define the filters
+filter1 = "KOVALIŪNAS EINARAS"
+filter2 = "Einaras Kovaliūnas" # Add your second filter value here
+
+# Function to extract first 4 words from a string
+def extract_first_4_words(text):
+ if pd.isna(text): # Check if the value is NaN
+ return "" # Return an empty string if NaN
+ words = text.split()[:4]
+ return ' '.join(words)
+
+
+# Iterate over the file numbers from 1 to 12
+for file_num in range(1, 13):
+ month = str(file_num)
+ spending_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"])
+ income_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"])
+ for file_type in ['+', '-']:
+ filename = file_pattern.format(file_num, file_type)
+ filepath = os.path.join(directory, filename)
+ if os.path.exists(filepath):
+ print(f"Processing file: {filename}")
+ # Initialize an empty list to store the valid lines of the CSV file
+ valid_lines = []
+ skipped_rows = []
+
+ # Read the CSV file line by line
+ with open(filepath, 'r', encoding='utf-8') as file:
+ for i, line in enumerate(file):
+ if i < 2: # Skip the first two lines (first row and header row)
+ continue
+ try:
+ # Attempt to parse the line as CSV
+ pd.read_csv(StringIO(line), header=None, sep=';') # Use header=None and specify separator as ';'
+ valid_lines.append(line) # If successful, add the line to the list of valid lines
+ except pd.errors.ParserError:
+ print(f"Skipped line {i+1} in file {filename} due to ParserError: {line.strip()}")
+ skipped_rows.append((i+1, line.strip())) # Store the line number and the content of the skipped row
+
+ # Read the valid lines into a DataFrame, skipping the header line
+ bank_data = None
+ try:
+ bank_data = pd.read_csv(StringIO(''.join(valid_lines)), header=None, sep=';')
+ except pd.errors.ParserError:
+ print(f"Encountered an error while parsing file {filename}. Skipping problematic rows.")
+ if not valid_lines:
+ print("No valid data read due to errors.")
+ continue
+
+ # If data was successfully read, extract specific columns and apply filter
+ if bank_data is not None:
+ bank_data = bank_data[[1, 3, 4, 9]] # Extract only desired columns
+
+ # Replace empty values with NaN
+ bank_data.replace('-', pd.NA, inplace=True)
+
+ # Rename columns
+ bank_data.columns = ["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"]
+
+ # Apply filters
+ bank_data = bank_data[(bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] != filter1) &
+ (bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] != filter2)]
+
+ # Filter out rows that are the same as column headers or contain all empty values
+ bank_data = bank_data[~bank_data.apply(lambda row: all(row == ["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"]), axis=1)]
+
+ # Extract first 4 words from specified columns
+ bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] = bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"].apply(extract_first_4_words)
+ bank_data["MOKÄ–JIMO PASKIRTIS"] = bank_data["MOKÄ–JIMO PASKIRTIS"].apply(extract_first_4_words)
+
+ # Append the DataFrame to the appropriate dictionary based on file type
+ if file_type == '+':
+ income_dfs[month] = bank_data
+ elif file_type == '-':
+ spending_dfs[month] = bank_data
+ else:
+ print(f"No valid data read from file {filename}.")
+
+ # Display the DataFrame
+ if file_type == '+':
+ print(f"\nIncome DataFrame for month {month}:")
+ print(income_dfs[month])
+ elif file_type == '-':
+ print(f"\nSpending DataFrame for month {month}:")
+ print(spending_dfs[month])
+ # Save DataFrames to JSON files with UTF-8 encoding
+ income_json_filename = f"income_month_{month}.json"
+ spending_json_filename = f"spending_month_{month}.json"
+
+ # Save DataFrames to Excel files
+ income_excel_filename = f"income_month_{month}.xlsx"
+ spending_excel_filename = f"spending_month_{month}.xlsx"
+
+ # Drop rows with NaN values
+ income_dfs[month].dropna(inplace=True)
+ spending_dfs[month].dropna(inplace=True)
+
+ # Convert DataFrames to JSON strings
+ income_json_str = income_dfs[month].to_json(orient='records', lines=True, default_handler=str)
+ spending_json_str = spending_dfs[month].to_json(orient='records', lines=True, default_handler=str)
+
+ # Write JSON strings to files with UTF-8 encoding
+ with open(income_json_filename, 'w', encoding='utf-8') as income_file:
+ for line in income_json_str.split('\n'):
+ if line.strip(): # Check if line is not empty
+ income_file.write(line.strip() + '\n')
+
+ with open(spending_json_filename, 'w', encoding='utf-8') as spending_file:
+ for line in spending_json_str.split('\n'):
+ if line.strip(): # Check if line is not empty
+ spending_file.write(line.strip() + '\n')
+
+ # Save DataFrames to Excel files
+ income_dfs[month].to_excel(income_excel_filename, index=False)
+ spending_dfs[month].to_excel(spending_excel_filename, index=False)
+
+ print(f"DataFrames saved to JSON files: {income_json_filename}, {spending_json_filename}")
+ print(f"DataFrames saved to Excel files: {income_excel_filename}, {spending_excel_filename}")
+
+ # Display skipped rows
+ if skipped_rows:
+ print(f"\nSkipped rows in file {filename}:")
+ for row_num, row_content in skipped_rows:
+ print(f"Row {row_num}: {row_content}")
+ else:
+ print(f"File {filename} not found. Skipping...")
+
+print("All files processed.")
diff --git a/src/personal_finance/v1.8.py b/src/personal_finance/v1.8.py
new file mode 100755
index 0000000..9584e84
--- /dev/null
+++ b/src/personal_finance/v1.8.py
@@ -0,0 +1,152 @@
+import pandas as pd
+import os
+from io import StringIO
+import json
+
+# Define the directory containing the CSV files
+directory = '/home/eikov/fin/OG-CSV'
+
+# Define the pattern for file names
+file_pattern = '{}{}.csv' # {} will be replaced by the file number and +/- for the file type
+
+# Initialize empty dictionaries to store dataframes for each month
+spending_dfs = {}
+income_dfs = {}
+
+# Define the filters
+filter1 = "KOVALIŪNAS EINARAS"
+filter2 = "Einaras Kovaliūnas" # Add your second filter value here
+
+# Function to extract first 4 words from a string
+def extract_first_4_words(text):
+ if pd.isna(text): # Check if the value is NaN
+ return "" # Return an empty string if NaN
+ words = text.split()[:4]
+ return ' '.join(words)
+
+
+# Iterate over the file numbers from 1 to 12
+for file_num in range(1, 13):
+ month = str(file_num)
+ spending_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"])
+ income_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"])
+ for file_type in ['+', '-']:
+ filename = file_pattern.format(file_num, file_type)
+ filepath = os.path.join(directory, filename)
+ if os.path.exists(filepath):
+ print(f"Processing file: {filename}")
+ # Initialize an empty list to store the valid lines of the CSV file
+ valid_lines = []
+ skipped_rows = []
+
+ # Read the CSV file line by line
+ with open(filepath, 'r', encoding='utf-8') as file:
+ for i, line in enumerate(file):
+ if i < 2: # Skip the first two lines (first row and header row)
+ continue
+ try:
+ # Attempt to parse the line as CSV
+ pd.read_csv(StringIO(line), header=None, sep=';') # Use header=None and specify separator as ';'
+ valid_lines.append(line) # If successful, add the line to the list of valid lines
+ except pd.errors.ParserError:
+ print(f"Skipped line {i+1} in file {filename} due to ParserError: {line.strip()}")
+ skipped_rows.append((i+1, line.strip())) # Store the line number and the content of the skipped row
+
+ # Read the valid lines into a DataFrame, skipping the header line
+ bank_data = None
+ try:
+ bank_data = pd.read_csv(StringIO(''.join(valid_lines)), header=None, sep=';')
+ except pd.errors.ParserError:
+ print(f"Encountered an error while parsing file {filename}. Skipping problematic rows.")
+ if not valid_lines:
+ print("No valid data read due to errors.")
+ continue
+
+ # If data was successfully read, extract specific columns and apply filter
+ if bank_data is not None:
+ bank_data = bank_data[[1, 3, 4, 9]] # Extract only desired columns
+
+ # Replace empty values with NaN
+ bank_data.replace('-', pd.NA, inplace=True)
+
+ # Rename columns
+ bank_data.columns = ["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"]
+
+ # Apply filters
+ bank_data = bank_data[(bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] != filter1) &
+ (bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] != filter2)]
+
+ # Filter out rows that are the same as column headers or contain all empty values
+ bank_data = bank_data[~bank_data.apply(lambda row: all(row == ["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"]), axis=1)]
+
+ # Extract first 4 words from specified columns
+ bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] = bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"].apply(extract_first_4_words)
+ bank_data["MOKÄ–JIMO PASKIRTIS"] = bank_data["MOKÄ–JIMO PASKIRTIS"].apply(extract_first_4_words)
+
+ # Append the DataFrame to the appropriate dictionary based on file type
+ if file_type == '+':
+ income_dfs[month] = bank_data
+ elif file_type == '-':
+ spending_dfs[month] = bank_data
+ else:
+ print(f"No valid data read from file {filename}.")
+
+ # Display the DataFrame
+ if file_type == '+':
+ print(f"\nIncome DataFrame for month {month}:")
+ print(income_dfs[month])
+ elif file_type == '-':
+ print(f"\nSpending DataFrame for month {month}:")
+ print(spending_dfs[month])
+
+ # Specify the folder path
+ output_folder_income = "/home/eikov/fin/income"
+ output_folder_spending = "/home/eikov/fin/spendings"
+
+ # Create the folder if it doesn't exist
+ os.makedirs(output_folder_income, exist_ok=True)
+ os.makedirs(output_folder_spending, exist_ok=True)
+
+ # Save DataFrames to JSON files with UTF-8 encoding
+ income_json_filename = os.path.join( output_folder_income, f"income_month_{month}.json")
+ spending_json_filename = os.path.join(output_folder_spending, f"spending_month_{month}.json")
+
+ # Save DataFrames to Excel files
+ income_excel_filename = os.path.join( output_folder_income, f"income_month_{month}.xlsx")
+ spending_excel_filename = os.path.join(output_folder_spending, f"spending_month_{month}.xlsx")
+
+ # Drop rows with NaN values
+ income_dfs[month].dropna(inplace=True)
+ spending_dfs[month].dropna(inplace=True)
+
+ # Convert DataFrames to JSON strings
+ income_json_str = income_dfs[month].to_json(orient='records', lines=True, default_handler=str)
+ spending_json_str = spending_dfs[month].to_json(orient='records', lines=True, default_handler=str)
+ """
+ # Write JSON strings to files with UTF-8 encoding
+ with open(income_json_filename, 'w+', encoding='utf-8') as income_file:
+ for line in income_json_str.split('\n'):
+ if line.strip(): # Check if line is not empty
+ income_file.write(line.strip() + '\n')
+
+ with open(spending_json_filename, 'w+', encoding='utf-8') as spending_file:
+ for line in spending_json_str.split('\n'):
+ if line.strip(): # Check if line is not empty
+ spending_file.write(line.strip() + '\n')
+ """
+ # Save DataFrames to Excel files
+ income_dfs[month].to_excel(income_excel_filename, index=False)
+ spending_dfs[month].to_excel(spending_excel_filename, index=False)
+
+ print(f"DataFrames saved to JSON files: {income_json_filename}, {spending_json_filename}")
+ print(f"DataFrames saved to Excel files: {income_excel_filename}, {spending_excel_filename}")
+
+ # Display skipped rows
+ if skipped_rows:
+ print(f"\nSkipped rows in file {filename}:")
+ for row_num, row_content in skipped_rows:
+ print(f"Row {row_num}: {row_content}")
+ else:
+ print(f"File {filename} not found. Skipping...")
+
+print("All files processed.")
diff --git a/src/personal_finance/v1.9.py b/src/personal_finance/v1.9.py
new file mode 100755
index 0000000..6318e8b
--- /dev/null
+++ b/src/personal_finance/v1.9.py
@@ -0,0 +1,230 @@
+import pandas as pd
+import os
+from io import StringIO
+import json
+import openpyxl
+
+# Define the directory containing the CSV files
+directory = '/home/eikov/fin/OG-CSV'
+
+# Define the pattern for file names
+file_pattern = '{}{}.csv' # {} will be replaced by the file number and +/- for the file type
+
+# Initialize empty dictionaries to store dataframes for each month
+spending_dfs = {}
+income_dfs = {}
+
+# Define the filters
+filter1 = "KOVALIŪNAS EINARAS"
+filter2 = "Einaras Kovaliūnas" # Add your second filter value here
+
+# Function to extract first 4 words from a string
+def extract_first_4_words(text):
+ if pd.isna(text): # Check if the value is NaN
+ return "" # Return an empty string if NaN
+ words = text.split()[:4]
+ return ' '.join(words)
+
+
+# Iterate over the file numbers from 1 to 12
+for file_num in range(1, 13):
+ month = str(file_num)
+ spending_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"])
+ income_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"])
+ for file_type in ['+', '-']:
+ filename = file_pattern.format(file_num, file_type)
+ filepath = os.path.join(directory, filename)
+ if os.path.exists(filepath):
+ print(f"Processing file: {filename}")
+ # Initialize an empty list to store the valid lines of the CSV file
+ valid_lines = []
+ skipped_rows = []
+
+ # Read the CSV file line by line
+ with open(filepath, 'r', encoding='utf-8') as file:
+ for i, line in enumerate(file):
+ if i < 2: # Skip the first two lines (first row and header row)
+ continue
+ try:
+ # Attempt to parse the line as CSV
+ pd.read_csv(StringIO(line), header=None, sep=';') # Use header=None and specify separator as ';'
+ valid_lines.append(line) # If successful, add the line to the list of valid lines
+ except pd.errors.ParserError:
+ print(f"Skipped line {i+1} in file {filename} due to ParserError: {line.strip()}")
+ skipped_rows.append((i+1, line.strip())) # Store the line number and the content of the skipped row
+
+ # Read the valid lines into a DataFrame, skipping the header line
+ bank_data = None
+ try:
+ bank_data = pd.read_csv(StringIO(''.join(valid_lines)), header=None, sep=';')
+ except pd.errors.ParserError:
+ print(f"Encountered an error while parsing file {filename}. Skipping problematic rows.")
+ if not valid_lines:
+ print("No valid data read due to errors.")
+ continue
+
+ # If data was successfully read, extract specific columns and apply filter
+ if bank_data is not None:
+ bank_data = bank_data[[1, 3, 4, 9]] # Extract only desired columns
+
+ # Replace empty values with NaN
+ bank_data.replace('-', pd.NA, inplace=True)
+
+ # Rename columns
+ bank_data.columns = ["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"]
+
+ # Apply filters
+ bank_data = bank_data[(bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] != filter1) &
+ (bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] != filter2)]
+
+ # Filter out rows that are the same as column headers or contain all empty values
+ bank_data = bank_data[~bank_data.apply(lambda row: all(row == ["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"]), axis=1)]
+
+ # Extract first 4 words from specified columns
+ bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] = bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"].apply(extract_first_4_words)
+ bank_data["MOKÄ–JIMO PASKIRTIS"] = bank_data["MOKÄ–JIMO PASKIRTIS"].apply(extract_first_4_words)
+
+ # Append the DataFrame to the appropriate dictionary based on file type
+ if file_type == '+':
+ income_dfs[month] = bank_data
+ elif file_type == '-':
+ spending_dfs[month] = bank_data
+ else:
+ print(f"No valid data read from file {filename}.")
+
+ # Display the DataFrame
+ if file_type == '+':
+ print(f"\nIncome DataFrame for month {month}:")
+ print(income_dfs[month])
+ elif file_type == '-':
+ print(f"\nSpending DataFrame for month {month}:")
+ print(spending_dfs[month])
+
+ # Specify the folder path
+ output_folder_income = "/home/eikov/fin/income"
+ output_folder_spending = "/home/eikov/fin/spendings"
+
+ # Create the folder if it doesn't exist
+ os.makedirs(output_folder_income, exist_ok=True)
+ os.makedirs(output_folder_spending, exist_ok=True)
+
+ # Save DataFrames to JSON files with UTF-8 encoding
+ income_json_filename = os.path.join( output_folder_income, f"income_month_{month}.json")
+ spending_json_filename = os.path.join(output_folder_spending, f"spending_month_{month}.json")
+
+ # Save DataFrames to Excel files
+ income_excel_filename = os.path.join( output_folder_income, f"income_month_{month}.xlsx")
+ spending_excel_filename = os.path.join(output_folder_spending, f"spending_month_{month}.xlsx")
+
+ # Drop rows with NaN values
+ income_dfs[month].dropna(inplace=True)
+ spending_dfs[month].dropna(inplace=True)
+
+ # Convert DataFrames to JSON strings
+ income_json_str = income_dfs[month].to_json(orient='records', lines=True, default_handler=str)
+ spending_json_str = spending_dfs[month].to_json(orient='records', lines=True, default_handler=str)
+ """
+ # Write JSON strings to files with UTF-8 encoding
+ with open(income_json_filename, 'w+', encoding='utf-8') as income_file:
+ for line in income_json_str.split('\n'):
+ if line.strip(): # Check if line is not empty
+ income_file.write(line.strip() + '\n')
+
+ with open(spending_json_filename, 'w+', encoding='utf-8') as spending_file:
+ for line in spending_json_str.split('\n'):
+ if line.strip(): # Check if line is not empty
+ spending_file.write(line.strip() + '\n')
+ """
+ # Save DataFrames to Excel files
+ income_dfs[month].to_excel(income_excel_filename, index=False)
+ spending_dfs[month].to_excel(spending_excel_filename, index=False)
+
+ print(f"DataFrames saved to JSON files: {income_json_filename}, {spending_json_filename}")
+ print(f"DataFrames saved to Excel files: {income_excel_filename}, {spending_excel_filename}")
+
+ # Display skipped rows
+ if skipped_rows:
+ print(f"\nSkipped rows in file {filename}:")
+ for row_num, row_content in skipped_rows:
+ print(f"Row {row_num}: {row_content}")
+ else:
+ print(f"File {filename} not found. Skipping...")
+
+print("All files processed.")
+
+
+
+#Merge spending seperate xlsx files to one
+directory = '/home/eikov/fin/spendings'
+output_directory = '/home/eikov/fin/analysis'
+
+# List all files in the directory
+files = os.listdir(directory)
+
+# Filter out only the Excel files (files ending with .xlsx)
+excel_files = [file for file in files if file.endswith('.xlsx')]
+
+# Create a new workbook
+merged_workbook = openpyxl.Workbook()
+
+# Loop through each file and add it as a new sheet to the workbook
+for i, file_name in enumerate(excel_files):
+ # Load the workbook from file
+ workbook = openpyxl.load_workbook(os.path.join(directory, file_name))
+
+ # Get the active sheet from the loaded workbook
+ sheet = workbook.active
+
+ # Copy the active sheet to the merged workbook
+ if i == 0:
+ # For the first sheet, use the default sheet name 'Sheet'
+ merged_sheet = merged_workbook.active
+ else:
+ # For subsequent sheets, create a new sheet with the file name as the sheet name
+ merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}")
+
+ # Copy data from the original sheet to the merged sheet
+ for row in sheet.iter_rows(values_only=True):
+ merged_sheet.append(row)
+
+# Save the merged workbook to the output directory
+output_filename = os.path.join(output_directory, "merged_spending_data.xlsx")
+merged_workbook.save(output_filename)
+
+
+#Merge income seperate xlsx files to one
+directory = '/home/eikov/fin/income'
+output_directory = '/home/eikov/fin/analysis'
+
+# List all files in the directory
+files = os.listdir(directory)
+
+# Filter out only the Excel files (files ending with .xlsx)
+excel_files = [file for file in files if file.endswith('.xlsx')]
+
+# Create a new workbook
+merged_workbook = openpyxl.Workbook()
+
+# Loop through each file and add it as a new sheet to the workbook
+for i, file_name in enumerate(excel_files):
+ # Load the workbook from file
+ workbook = openpyxl.load_workbook(os.path.join(directory, file_name))
+
+ # Get the active sheet from the loaded workbook
+ sheet = workbook.active
+
+ # Copy the active sheet to the merged workbook
+ if i == 0:
+ # For the first sheet, use the default sheet name 'Sheet'
+ merged_sheet = merged_workbook.active
+ else:
+ # For subsequent sheets, create a new sheet with the file name as the sheet name
+ merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}")
+
+ # Copy data from the original sheet to the merged sheet
+ for row in sheet.iter_rows(values_only=True):
+ merged_sheet.append(row)
+
+# Save the merged workbook to the output directory
+output_filename = os.path.join(output_directory, "merged_income_data.xlsx")
+merged_workbook.save(output_filename)
\ No newline at end of file
diff --git a/src/personal_finance/v2.0.py b/src/personal_finance/v2.0.py
new file mode 100755
index 0000000..ac32c08
--- /dev/null
+++ b/src/personal_finance/v2.0.py
@@ -0,0 +1,309 @@
+import pandas as pd
+import os
+from io import StringIO
+import json
+import openpyxl
+
+# Define the directory containing the CSV files
+directory = '/home/eikov/fin/OG-CSV'
+
+# Define the pattern for file names
+file_pattern = '{}{}.csv' # {} will be replaced by the file number and +/- for the file type
+
+# Initialize empty dictionaries to store dataframes for each month
+spending_dfs = {}
+income_dfs = {}
+
+# Define the filters
+filter1 = "KOVALIŪNAS EINARAS"
+filter2 = "Einaras Kovaliūnas" # Add your second filter value here
+
+# Function to extract first 4 words from a string
+def extract_first_4_words(text):
+ if pd.isna(text): # Check if the value is NaN
+ return "" # Return an empty string if NaN
+ words = text.split()[:4]
+ return ' '.join(words)
+
+
+# Iterate over the file numbers from 1 to 12
+for file_num in range(1, 13):
+ month = str(file_num)
+ spending_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"])
+ income_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"])
+ for file_type in ['+', '-']:
+ filename = file_pattern.format(file_num, file_type)
+ filepath = os.path.join(directory, filename)
+ if os.path.exists(filepath):
+ print(f"Processing file: {filename}")
+ # Initialize an empty list to store the valid lines of the CSV file
+ valid_lines = []
+ skipped_rows = []
+
+ # Read the CSV file line by line
+ with open(filepath, 'r', encoding='utf-8') as file:
+ for i, line in enumerate(file):
+ if i < 2: # Skip the first two lines (first row and header row)
+ continue
+ try:
+ # Attempt to parse the line as CSV
+ pd.read_csv(StringIO(line), header=None, sep=';') # Use header=None and specify separator as ';'
+ valid_lines.append(line) # If successful, add the line to the list of valid lines
+ except pd.errors.ParserError:
+ print(f"Skipped line {i+1} in file {filename} due to ParserError: {line.strip()}")
+ skipped_rows.append((i+1, line.strip())) # Store the line number and the content of the skipped row
+
+ # Read the valid lines into a DataFrame, skipping the header line
+ bank_data = None
+ try:
+ bank_data = pd.read_csv(StringIO(''.join(valid_lines)), header=None, sep=';')
+ except pd.errors.ParserError:
+ print(f"Encountered an error while parsing file {filename}. Skipping problematic rows.")
+ if not valid_lines:
+ print("No valid data read due to errors.")
+ continue
+
+ # If data was successfully read, extract specific columns and apply filter
+ if bank_data is not None:
+ bank_data = bank_data[[1, 3, 4, 9]] # Extract only desired columns
+
+ # Replace empty values with NaN
+ bank_data.replace('-', pd.NA, inplace=True)
+
+ # Rename columns
+ bank_data.columns = ["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"]
+
+ # Apply filters
+ bank_data = bank_data[(bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] != filter1) &
+ (bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] != filter2)]
+
+ # Filter out rows that are the same as column headers or contain all empty values
+ bank_data = bank_data[~bank_data.apply(lambda row: all(row == ["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"]), axis=1)]
+
+ # Extract first 4 words from specified columns
+ bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] = bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"].apply(extract_first_4_words)
+ bank_data["MOKÄ–JIMO PASKIRTIS"] = bank_data["MOKÄ–JIMO PASKIRTIS"].apply(extract_first_4_words)
+
+ # Append the DataFrame to the appropriate dictionary based on file type
+ if file_type == '+':
+ income_dfs[month] = bank_data
+ elif file_type == '-':
+ spending_dfs[month] = bank_data
+ else:
+ print(f"No valid data read from file {filename}.")
+
+ # Display the DataFrame
+ if file_type == '+':
+ print(f"\nIncome DataFrame for month {month}:")
+ print(income_dfs[month])
+ elif file_type == '-':
+ print(f"\nSpending DataFrame for month {month}:")
+ print(spending_dfs[month])
+
+ # Specify the folder path
+ output_folder_income = "/home/eikov/fin/income"
+ output_folder_spending = "/home/eikov/fin/spendings"
+
+ # Create the folder if it doesn't exist
+ os.makedirs(output_folder_income, exist_ok=True)
+ os.makedirs(output_folder_spending, exist_ok=True)
+
+ # Save DataFrames to JSON files with UTF-8 encoding
+ income_json_filename = os.path.join( output_folder_income, f"income_month_{month}.json")
+ spending_json_filename = os.path.join(output_folder_spending, f"spending_month_{month}.json")
+
+ # Save DataFrames to Excel files
+ income_excel_filename = os.path.join( output_folder_income, f"income_month_{month}.xlsx")
+ spending_excel_filename = os.path.join(output_folder_spending, f"spending_month_{month}.xlsx")
+
+ # Drop rows with NaN values
+ income_dfs[month].dropna(inplace=True)
+ spending_dfs[month].dropna(inplace=True)
+
+ # Convert DataFrames to JSON strings
+ income_json_str = income_dfs[month].to_json(orient='records', lines=True, default_handler=str)
+ spending_json_str = spending_dfs[month].to_json(orient='records', lines=True, default_handler=str)
+ """
+ # Write JSON strings to files with UTF-8 encoding
+ with open(income_json_filename, 'w+', encoding='utf-8') as income_file:
+ for line in income_json_str.split('\n'):
+ if line.strip(): # Check if line is not empty
+ income_file.write(line.strip() + '\n')
+
+ with open(spending_json_filename, 'w+', encoding='utf-8') as spending_file:
+ for line in spending_json_str.split('\n'):
+ if line.strip(): # Check if line is not empty
+ spending_file.write(line.strip() + '\n')
+ """
+ # Save DataFrames to Excel files
+ income_dfs[month].to_excel(income_excel_filename, index=False)
+ spending_dfs[month].to_excel(spending_excel_filename, index=False)
+
+ print(f"DataFrames saved to JSON files: {income_json_filename}, {spending_json_filename}")
+ print(f"DataFrames saved to Excel files: {income_excel_filename}, {spending_excel_filename}")
+
+ # Display skipped rows
+ if skipped_rows:
+ print(f"\nSkipped rows in file {filename}:")
+ for row_num, row_content in skipped_rows:
+ print(f"Row {row_num}: {row_content}")
+ else:
+ print(f"File {filename} not found. Skipping...")
+
+print("All files processed.")
+
+
+
+
+
+
+
+
+#Merge spending seperate xlsx files to one
+directory = '/home/eikov/fin/spendings'
+output_directory = '/home/eikov/fin/analysis'
+
+# List all files in the directory
+files = os.listdir(directory)
+
+# Filter out only the Excel files (files ending with .xlsx)
+excel_files = [file for file in files if file.endswith('.xlsx')]
+
+# Create a new workbook
+merged_workbook = openpyxl.Workbook()
+
+# Loop through each file and add it as a new sheet to the workbook
+for i, file_name in enumerate(excel_files):
+ # Load the workbook from file
+ workbook = openpyxl.load_workbook(os.path.join(directory, file_name))
+
+ # Get the active sheet from the loaded workbook
+ sheet = workbook.active
+
+ # Copy the active sheet to the merged workbook
+ if i == 0:
+ # For the first sheet, use the default sheet name 'Sheet'
+ merged_sheet = merged_workbook.active
+ else:
+ # For subsequent sheets, create a new sheet with the file name as the sheet name
+ merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}")
+
+ # Copy data from the original sheet to the merged sheet
+ for row in sheet.iter_rows(values_only=True):
+ merged_sheet.append(row)
+
+# Save the merged workbook to the output directory
+output_filename = os.path.join(output_directory, "merged_spending_data.xlsx")
+merged_workbook.save(output_filename)
+
+
+#Merge income seperate xlsx files to one
+directory = '/home/eikov/fin/income'
+output_directory = '/home/eikov/fin/analysis'
+
+# List all files in the directory
+files = os.listdir(directory)
+
+# Filter out only the Excel files (files ending with .xlsx)
+excel_files = [file for file in files if file.endswith('.xlsx')]
+
+# Create a new workbook
+merged_workbook = openpyxl.Workbook()
+
+# Loop through each file and add it as a new sheet to the workbook
+for i, file_name in enumerate(excel_files):
+ # Load the workbook from file
+ workbook = openpyxl.load_workbook(os.path.join(directory, file_name))
+
+ # Get the active sheet from the loaded workbook
+ sheet = workbook.active
+
+ # Copy the active sheet to the merged workbook
+ if i == 0:
+ # For the first sheet, use the default sheet name 'Sheet'
+ merged_sheet = merged_workbook.active
+ else:
+ # For subsequent sheets, create a new sheet with the file name as the sheet name
+ merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}")
+
+ # Copy data from the original sheet to the merged sheet
+ for row in sheet.iter_rows(values_only=True):
+ merged_sheet.append(row)
+
+# Save the merged workbook to the output directory
+output_filename = os.path.join(output_directory, "merged_income_data.xlsx")
+merged_workbook.save(output_filename)
+
+
+
+
+
+
+
+
+
+
+# finding unique strings in INCOME and categorise it
+# Path to the directory containing the file
+output_directory = '/home/eikov/fin/analysis/'
+# Specify the filename
+filename = 'merged_income_data.xlsx'
+# Construct the full path
+file_path = f'{output_directory}/{filename}'
+
+# Initialize an empty set to store unique values
+unique_values = set()
+
+# Read the Excel file
+xls = pd.ExcelFile(file_path)
+
+# Iterate over each sheet in the Excel file
+for sheet_name in xls.sheet_names:
+ # Read the sheet into a DataFrame
+ df = pd.read_excel(xls, sheet_name)
+ # Check the number of columns in the DataFrame
+ if df.shape[1] < 4: # If the number of columns is less than 4
+ print(f"Skipping sheet '{sheet_name}' because it has less than 4 columns.")
+ continue # Skip to the next sheet
+ # Extract unique values from column 3 (Python uses zero-based indexing, so the third column is indexed as 2)
+ unique_values.update(df.iloc[:, 2].unique())
+
+# Create a DataFrame with unique values and save it to 'categories.xlsx'
+unique_df = pd.DataFrame({'Unique Values': list(unique_values)})
+unique_df.to_excel(f'{output_directory}/categories_income.xlsx', index=False)
+
+
+
+
+
+
+
+
+# finding unique strings in SPENDINGS and categorise it
+# Path to the directory containing the file
+output_directory2 = '/home/eikov/fin/analysis/'
+# Specify the filename
+filename2 = 'merged_spending_data.xlsx'
+# Construct the full path
+file_path2 = f'{output_directory2}/{filename2}'
+
+# Initialize an empty set to store unique values
+unique_values2 = set()
+
+# Read the Excel file
+xls = pd.ExcelFile(file_path2)
+
+# Iterate over each sheet in the Excel file
+for sheet_name in xls.sheet_names:
+ # Read the sheet into a DataFrame
+ df = pd.read_excel(xls, sheet_name)
+ # Check the number of columns in the DataFrame
+ if df.shape[1] < 4: # If the number of columns is less than 4
+ print(f"Skipping sheet '{sheet_name}' because it has less than 4 columns.")
+ continue # Skip to the next sheet
+ # Extract unique values from column 3 (Python uses zero-based indexing, so the third column is indexed as 2)
+ unique_values2.update(df.iloc[:, 2].unique())
+
+# Create a DataFrame with unique values and save it to 'categories.xlsx'
+unique_df = pd.DataFrame({'Unique Values': list(unique_values2)})
+unique_df.to_excel(f'{output_directory}/categories_spendings.xlsx', index=False)
diff --git a/src/personal_finance/v2.1.py b/src/personal_finance/v2.1.py
new file mode 100755
index 0000000..f922e87
--- /dev/null
+++ b/src/personal_finance/v2.1.py
@@ -0,0 +1,393 @@
+import pandas as pd
+import os
+from io import StringIO
+import json
+import openpyxl
+
+"""
+This code is designed to process bank statement files and organize the data into separate DataFrames for income and spending transactions. It performs the following tasks:
+
+1. Reads bank statement files from a specified directory.
+2. Extracts relevant information from each file, such as transaction date, amount, payer/payee name, and purpose.
+3. Applies data cleaning and preprocessing steps, including removing unnecessary columns and extracting the first two words from payer/payee names and payment purposes.
+4. Organizes the data into separate DataFrames for income and spending transactions, grouped by month.
+5. Saves the DataFrames as JSON and Excel files in separate directories for income and spending.
+6. Merges separate monthly Excel files into single "merged_income_data.xlsx" and "merged_spending_data.xlsx" files.
+7. Extracts unique payer/payee names from the merged files and saves them as "categories_income.xlsx" and "categories_spendings.xlsx".
+8. Allows manual categorization of payer/payee names by creating "cat-income.xlsx" and "cat-spendings.xlsx" files.
+9. Merges the categorized data and analysis sheets into final "Galutinis_Income.xlsx" and "Galutinis_spendings.xlsx" files.
+
+The code is designed to be modular and extensible, allowing for easy integration of additional data processing or analysis steps as needed.
+"""
+
+"""
+#next: in Line 229
+modify Excel file: at the end of the entries in column "SUMA" make a formula to add sum all values of that column in order to calculate total income/spending.
+or if it's not possible then simply add sum function to B80 cell.
+
+#next: in 8: create DF for each found excel spending(-) file(ignore the last row with total sum) and join into one DF.
+now iterate each unique value and sum all "SUMA" values. if that's not possible, convert DF to set and then sum key values.
+"""
+
+
+# Define the directory containing the CSV files
+directory = '/home/eikov/fin/OG-CSV'
+
+# Define the pattern for file names
+file_pattern = '{}{}.csv' # {} will be replaced by the file number and +/- for the file type
+
+# Initialize empty dictionaries to store dataframes for each month
+spending_dfs = {}
+income_dfs = {}
+
+# Define the filters
+filter1 = "KOVALIŪNAS EINARAS"
+filter2 = "Einaras Kovaliūnas" # Add your second filter value here
+
+
+# Function to extract first 2 words from a string
+def extract_first_2_words(text):
+ if pd.isna(text): # Check if the value is NaN
+ return "" # Return an empty string if NaN
+ words = text.split()[:2]
+ return ' '.join(words)
+
+
+
+# Iterate over the file numbers from 1 to 12
+for file_num in range(1, 13):
+ month = str(file_num)
+ spending_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"])
+ income_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"])
+ for file_type in ['+', '-']:
+ filename = file_pattern.format(file_num, file_type)
+ filepath = os.path.join(directory, filename)
+ if os.path.exists(filepath):
+ print(f"Processing file: {filename}")
+ # Initialize an empty list to store the valid lines of the CSV file
+ valid_lines = []
+ skipped_rows = []
+
+ # Read the CSV file line by line
+ with open(filepath, 'r', encoding='utf-8') as file:
+ for i, line in enumerate(file):
+ if i < 2: # Skip the first two lines (first row and header row)
+ continue
+ try:
+ # Attempt to parse the line as CSV
+ pd.read_csv(StringIO(line), header=None, sep=';') # Use header=None and specify separator as ';'
+ valid_lines.append(line) # If successful, add the line to the list of valid lines
+ except pd.errors.ParserError:
+ print(f"Skipped line {i+1} in file {filename} due to ParserError: {line.strip()}")
+ skipped_rows.append((i+1, line.strip())) # Store the line number and the content of the skipped row
+
+ # Read the valid lines into a DataFrame, skipping the header line
+ bank_data = None
+ try:
+ bank_data = pd.read_csv(StringIO(''.join(valid_lines)), header=None, sep=';')
+ except pd.errors.ParserError:
+ print(f"Encountered an error while parsing file {filename}. Skipping problematic rows.")
+ if not valid_lines:
+ print("No valid data read due to errors.")
+ continue
+
+ # If data was successfully read, extract specific columns and apply filter
+ if bank_data is not None:
+ bank_data = bank_data[[1, 3, 4, 9]] # Extract only desired columns
+
+ # Replace empty values with NaN
+ bank_data.replace('-', pd.NA, inplace=True)
+
+ # Rename columns
+ bank_data.columns = ["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"]
+
+ # Apply filters
+ bank_data = bank_data[(bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] != filter1) &
+ (bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] != filter2)]
+
+ # Filter out rows that are the same as column headers or contain all empty values
+ bank_data = bank_data[~bank_data.apply(lambda row: all(row == ["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"]), axis=1)]
+
+ # Extract first 4 words from specified columns
+ bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] = bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"].apply(extract_first_2_words)
+ bank_data["MOKÄ–JIMO PASKIRTIS"] = bank_data["MOKÄ–JIMO PASKIRTIS"].apply(extract_first_2_words)
+
+ # Append the DataFrame to the appropriate dictionary based on file type
+ if file_type == '+':
+ income_dfs[month] = bank_data
+ elif file_type == '-':
+ spending_dfs[month] = bank_data
+ else:
+ print(f"No valid data read from file {filename}.")
+
+ # Display the DataFrame
+ if file_type == '+':
+ print(f"\nIncome DataFrame for month {month}:")
+ print(income_dfs[month])
+ elif file_type == '-':
+ print(f"\nSpending DataFrame for month {month}:")
+ print(spending_dfs[month])
+
+ # Specify the folder path
+ output_folder_income = "/home/eikov/fin/income"
+ output_folder_spending = "/home/eikov/fin/spendings"
+
+ # Create the folder if it doesn't exist
+ os.makedirs(output_folder_income, exist_ok=True)
+ os.makedirs(output_folder_spending, exist_ok=True)
+
+ # Save DataFrames to JSON files
+ income_json_filename = os.path.join( output_folder_income, f"income_month_{month}.json")
+ spending_json_filename = os.path.join(output_folder_spending, f"spending_month_{month}.json")
+
+ # Save DataFrames to Excel files
+ income_excel_filename = os.path.join( output_folder_income, f"income_month_{month}.xlsx")
+ spending_excel_filename = os.path.join(output_folder_spending, f"spending_month_{month}.xlsx")
+
+ # Drop rows with NaN values
+ income_dfs[month].dropna(inplace=True)
+ spending_dfs[month].dropna(inplace=True)
+
+ # Convert DataFrames to JSON strings
+ income_json_str = income_dfs[month].to_json(orient='records', lines=True, default_handler=str)
+ spending_json_str = spending_dfs[month].to_json(orient='records', lines=True, default_handler=str)
+
+ # Save DataFrames to Excel files
+ income_dfs[month].to_excel(income_excel_filename, index=False)
+ spending_dfs[month].to_excel(spending_excel_filename, index=False)
+
+ print(f"DataFrames saved to JSON files: {income_json_filename}, {spending_json_filename}")
+ print(f"DataFrames saved to Excel files: {income_excel_filename}, {spending_excel_filename}")
+
+ # Display skipped rows
+ if skipped_rows:
+ print(f"\nSkipped rows in file {filename}:")
+ for row_num, row_content in skipped_rows:
+ print(f"Row {row_num}: {row_content}")
+ else:
+ print(f"File {filename} not found. Skipping...")
+
+print("All files processed.")
+
+
+
+
+
+
+
+
+#Merge spending seperate xlsx files to one
+directory = '/home/eikov/fin/spendings'
+output_directory = '/home/eikov/fin/analysis'
+
+# List all files in the directory
+files = os.listdir(directory)
+
+# Filter out only the Excel files (files ending with .xlsx)
+excel_files = [file for file in files if file.endswith('.xlsx')]
+
+# Create a new workbook
+merged_workbook = openpyxl.Workbook()
+
+# Loop through each file and add it as a new sheet to the workbook
+for i, file_name in enumerate(excel_files):
+ # Load the workbook from file
+ workbook = openpyxl.load_workbook(os.path.join(directory, file_name))
+
+ # Get the active sheet from the loaded workbook
+ sheet = workbook.active
+
+ # Copy the active sheet to the merged workbook
+ if i == 0:
+ # For the first sheet, use the default sheet name 'Sheet'
+ merged_sheet = merged_workbook.active
+ else:
+ # For subsequent sheets, create a new sheet with the file name as the sheet name
+ merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}")
+
+ # Copy data from the original sheet to the merged sheet
+ for row in sheet.iter_rows(values_only=True):
+ merged_sheet.append(row)
+
+# Save the merged workbook to the output directory
+output_filename = os.path.join(output_directory, "merged_spending_data.xlsx")
+merged_workbook.save(output_filename)
+
+
+#Merge income seperate xlsx files to one
+directory = '/home/eikov/fin/income'
+output_directory = '/home/eikov/fin/analysis'
+
+# List all files in the directory
+files = os.listdir(directory)
+
+# Filter out only the Excel files (files ending with .xlsx)
+excel_files = [file for file in files if file.endswith('.xlsx')]
+
+# Create a new workbook
+merged_workbook = openpyxl.Workbook()
+
+# Loop through each file and add it as a new sheet to the workbook
+#additionally add at the end of the entries in column "SUMA" make a formula to add sum all values of that column in order to calculate total income/spending.
+#or if it's not possible then simply add sum function(=SUM(B1:B79)) to B80 cell in each sheet
+for i, file_name in enumerate(excel_files):
+ # Load the workbook from file
+ workbook = openpyxl.load_workbook(os.path.join(directory, file_name))
+
+ # Get the active sheet from the loaded workbook
+ sheet = workbook.active
+
+ # Copy the active sheet to the merged workbook
+ if i == 0:
+ # For the first sheet, use the default sheet name 'Sheet'
+ merged_sheet = merged_workbook.active
+ else:
+ # For subsequent sheets, create a new sheet with the file name as the sheet name
+ merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}")
+
+ # Copy data from the original sheet to the merged sheet
+ for row in sheet.iter_rows(values_only=True):
+ merged_sheet.append(row)
+
+# Save the merged workbook to the output directory
+output_filename = os.path.join(output_directory, "merged_income_data.xlsx")
+merged_workbook.save(output_filename)
+
+
+
+
+
+
+
+
+
+
+# finding unique strings in INCOME and categorise it
+# Path to the directory containing the file
+output_directory = '/home/eikov/fin/analysis/'
+# Specify the filename
+filename = 'merged_income_data.xlsx'
+# Construct the full path
+file_path = f'{output_directory}/{filename}'
+
+# Initialize an empty set to store unique values
+unique_values = set()
+
+# Read the Excel file
+xls = pd.ExcelFile(file_path)
+
+# Iterate over each sheet in the Excel file
+for sheet_name in xls.sheet_names:
+ # Read the sheet into a DataFrame
+ df = pd.read_excel(xls, sheet_name)
+ # Check the number of columns in the DataFrame
+ if df.shape[1] < 4: # If the number of columns is less than 4
+ print(f"Skipping sheet '{sheet_name}' because it has less than 4 columns.")
+ continue # Skip to the next sheet
+ # Extract unique values from column 3 (Python uses zero-based indexing, so the third column is indexed as 2)
+ unique_values.update(df.iloc[:, 2].unique())
+
+# Create a DataFrame with unique values and save it to 'categories.xlsx'
+unique_df = pd.DataFrame({'Unique Values': list(unique_values)})
+unique_df.to_excel(f'{output_directory}/categories_income.xlsx', index=False)
+
+
+
+
+
+
+
+
+# finding unique strings in SPENDINGS and categorise it
+# Path to the directory containing the file
+output_directory2 = '/home/eikov/fin/analysis/'
+# Specify the filename
+filename2 = 'merged_spending_data.xlsx'
+# Construct the full path
+file_path2 = f'{output_directory2}/{filename2}'
+
+# Initialize an empty set to store unique values
+unique_values2 = set()
+
+# Read the Excel file
+xls = pd.ExcelFile(file_path2)
+
+# Iterate over each sheet in the Excel file
+for sheet_name in xls.sheet_names:
+ # Read the sheet into a DataFrame
+ df = pd.read_excel(xls, sheet_name)
+ # Check the number of columns in the DataFrame
+ if df.shape[1] < 4: # If the number of columns is less than 4
+ print(f"Skipping sheet '{sheet_name}' because it has less than 4 columns.")
+ continue # Skip to the next sheet
+ # Extract unique values from column 3 (Python uses zero-based indexing, so the third column is indexed as 2)
+ unique_values2.update(df.iloc[:, 2].unique())
+
+# Create a DataFrame with unique values and save it to 'categories.xlsx'
+unique_df = pd.DataFrame({'Unique Values': list(unique_values2)})
+unique_df.to_excel(f'{output_directory}/categories_spendings.xlsx', index=False)
+
+
+
+
+
+
+
+"""
+#turiu svarius spendings ir income israsus: merged_spending_data.xlsx ir merged_income_data.xlsx
+taipogi unikaliu moketoju/gaveju sarasus is pajamu ir islaidu. rankiniu budu priskiriu kiekvienam gavejui/moketojui
+kategorijas ir issaugau kaip cat-income.xlsx ir cat-spendings.xlsx.
+priedo, manually sukuriau Analysis_income.xlsx ir Analysis_spending.xlsx, kad script'as neoverwritintu mano kategoriju ir analiziu darbo.
+belieka prijungti sheets, kad viskas susije su income butu vienam xlsx faile, ir viskas susije su spendings butu antram xlsx faile.
+"""
+#INCOME
+def merge_additional_sheets(base_file, additional_files, output_file):
+ # Create the output directory if it doesn't exist
+ output_directory_analysis = '/home/eikov/fin/analysis'
+ os.makedirs(output_directory_analysis, exist_ok=True)
+
+ # Create a Pandas ExcelWriter object
+ with pd.ExcelWriter(f'{output_directory_analysis}/{output_file}', engine='xlsxwriter') as writer:
+ # Write the base file to the output file
+ base_df = pd.read_excel(f'{output_directory_analysis}/{base_file}', sheet_name=None)
+ for sheet_name, df in base_df.items():
+ df.to_excel(writer, sheet_name=sheet_name, index=False)
+
+ # Write additional files to the output file as new sheets
+ for file in additional_files:
+ additional_df = pd.read_excel(f'{output_directory_analysis}/{file}', sheet_name=None)
+ for sheet_name, df in additional_df.items():
+ df.to_excel(writer, sheet_name=f'{sheet_name}', index=False)
+
+# List of additional files to merge as new sheets
+additional_files = ['cat-income.xlsx', 'Analysis_income.xlsx']
+
+# Merge additional sheets with merged_income_data.xlsx
+merge_additional_sheets('merged_income_data.xlsx', additional_files, 'Galutinis_Income.xlsx')
+
+
+#SPENDINGS
+def merge_additional_sheets(base_file, additional_files, output_file):
+ # Create the output directory if it doesn't exist
+ output_directory_analysis_s = '/home/eikov/fin/analysis'
+ os.makedirs(output_directory_analysis_s, exist_ok=True)
+
+ # Create a Pandas ExcelWriter object
+ with pd.ExcelWriter(f'{output_directory_analysis_s}/{output_file}', engine='xlsxwriter') as writer:
+ # Write the base file to the output file
+ base_df = pd.read_excel(f'{output_directory_analysis_s}/{base_file}', sheet_name=None)
+ for sheet_name, df in base_df.items():
+ df.to_excel(writer, sheet_name=sheet_name, index=False)
+
+ # Write additional files to the output file as new sheets
+ for file in additional_files:
+ additional_df = pd.read_excel(f'{output_directory_analysis_s}/{file}', sheet_name=None)
+ for sheet_name, df in additional_df.items():
+ df.to_excel(writer, sheet_name=f'{sheet_name}', index=False)
+
+# List of additional files to merge as new sheets
+additional_files = ['cat-spendings.xlsx', 'Analysis_spending.xlsx']
+
+# Merge additional sheets with merged_income_data.xlsx
+merge_additional_sheets('merged_spending_data.xlsx', additional_files, 'Galutinis_spendings.xlsx')
+
diff --git a/src/personal_finance/v2.2.py b/src/personal_finance/v2.2.py
new file mode 100755
index 0000000..3295fd6
--- /dev/null
+++ b/src/personal_finance/v2.2.py
@@ -0,0 +1,434 @@
+import pandas as pd
+import os
+from io import StringIO
+import json
+import openpyxl
+
+#more advanced than scrape.py but not fully working yet:
+# =sum() shows 0
+# galutinis spendings looks, but income does not merge Categories and Analysis_Income into galutinis_income.xlsx
+
+
+
+"""
+This code is designed to process bank statement files and organize the data into separate DataFrames for income and spending transactions. It performs the following tasks:
+
+1. Reads bank statement files from a specified directory.
+2. Extracts relevant information from each file, such as transaction date, amount, payer/payee name, and purpose.
+3. Applies data cleaning and preprocessing steps, including removing unnecessary columns and extracting the first two words from payer/payee names and payment purposes.
+4. Organizes the data into separate DataFrames for income and spending transactions, grouped by month.
+5. Saves the DataFrames as JSON and Excel files in separate directories for income and spending.
+6. Merges separate monthly Excel files into single "merged_income_data.xlsx" and "merged_spending_data.xlsx" files.
+7. Extracts unique payer/payee names from the merged files and saves them as "unique-strings-income.xlsx" and "unique-strings-spendings.xlsx".
+8. Allows manual categorization of payer/payee names by creating "cat-income.xlsx" and "cat-spendings.xlsx" files.
+9. Merges the categorized data and analysis sheets into final "Galutinis_Income.xlsx" and "Galutinis_spendings.xlsx" files.
+
+The code is designed to be modular and extensible, allowing for easy integration of additional data processing or analysis steps as needed.
+"""
+
+"""
+#next: in 8: create DF for each found excel spending(-) file(ignore the last row with total sum) and join into one DF.
+now iterate each unique value and sum all "SUMA" values. if that's not possible, convert DF to set and then sum key values.
+"""
+
+
+# Define the directory containing the CSV files
+directory = '/home/eikov/fin/OG-CSV'
+
+# Define the pattern for file names
+file_pattern = '{}{}.csv' # {} will be replaced by the file number and +/- for the file type
+
+# Initialize empty dictionaries to store dataframes for each month
+spending_dfs = {}
+income_dfs = {}
+
+# Define the filters
+filter1 = "KOVALIŪNAS EINARAS"
+filter2 = "Einaras Kovaliūnas" # Add your second filter value here
+
+
+# Function to extract first 2 words from a string
+def extract_first_2_words(text):
+ if pd.isna(text): # Check if the value is NaN
+ return "" # Return an empty string if NaN
+ words = text.split()[:2]
+ return ' '.join(words)
+
+
+
+# Iterate over the file numbers from 1 to 12
+for file_num in range(1, 13):
+ month = str(file_num)
+ spending_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"])
+ income_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"])
+ for file_type in ['+', '-']:
+ filename = file_pattern.format(file_num, file_type)
+ filepath = os.path.join(directory, filename)
+ if os.path.exists(filepath):
+ print(f"Processing file: {filename}")
+ # Initialize an empty list to store the valid lines of the CSV file
+ valid_lines = []
+ skipped_rows = []
+
+ # Read the CSV file line by line
+ with open(filepath, 'r', encoding='utf-8') as file:
+ for i, line in enumerate(file):
+ if i < 2: # Skip the first two lines (first row and header row)
+ continue
+ try:
+ # Attempt to parse the line as CSV
+ pd.read_csv(StringIO(line), header=None, sep=';') # Use header=None and specify separator as ';'
+ valid_lines.append(line) # If successful, add the line to the list of valid lines
+ except pd.errors.ParserError:
+ print(f"Skipped line {i+1} in file {filename} due to ParserError: {line.strip()}")
+ skipped_rows.append((i+1, line.strip())) # Store the line number and the content of the skipped row
+
+ # Read the valid lines into a DataFrame, skipping the header line
+ bank_data = None
+ try:
+ bank_data = pd.read_csv(StringIO(''.join(valid_lines)), header=None, sep=';')
+ except pd.errors.ParserError:
+ print(f"Encountered an error while parsing file {filename}. Skipping problematic rows.")
+ if not valid_lines:
+ print("No valid data read due to errors.")
+ continue
+
+ # If data was successfully read, extract specific columns and apply filter
+ if bank_data is not None:
+ bank_data = bank_data[[1, 3, 4, 9]] # Extract only desired columns
+
+ # Replace empty values with NaN
+ bank_data.replace('-', pd.NA, inplace=True)
+
+ # Rename columns
+ bank_data.columns = ["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"]
+
+ # Apply filters
+ bank_data = bank_data[(bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] != filter1) &
+ (bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] != filter2)]
+
+ # Filter out rows that are the same as column headers or contain all empty values
+ bank_data = bank_data[~bank_data.apply(lambda row: all(row == ["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"]), axis=1)]
+
+ # Extract first 4 words from specified columns
+ bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] = bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"].apply(extract_first_2_words)
+ bank_data["MOKÄ–JIMO PASKIRTIS"] = bank_data["MOKÄ–JIMO PASKIRTIS"].apply(extract_first_2_words)
+
+ # Replace commas with periods in the "SUMA" column
+ bank_data["SUMA"] = bank_data["SUMA"].str.replace(',', '.')
+
+ # Convert the values in the "SUMA" column to numeric data type
+ bank_data["SUMA"] = pd.to_numeric(bank_data["SUMA"], errors='coerce')
+ # Now the "SUMA" column contains numeric values
+
+
+ # Append the DataFrame to the appropriate dictionary based on file type
+ if file_type == '+':
+ income_dfs[month] = bank_data
+ elif file_type == '-':
+ spending_dfs[month] = bank_data
+ else:
+ print(f"No valid data read from file {filename}.")
+
+ # Display the DataFrame
+ if file_type == '+':
+ print(f"\nIncome DataFrame for month {month}:")
+ print(income_dfs[month])
+ elif file_type == '-':
+ print(f"\nSpending DataFrame for month {month}:")
+ print(spending_dfs[month])
+
+ # Specify the folder path
+ output_folder_income = "/home/eikov/fin/income"
+ output_folder_spending = "/home/eikov/fin/spendings"
+
+ # Create the folder if it doesn't exist
+ os.makedirs(output_folder_income, exist_ok=True)
+ os.makedirs(output_folder_spending, exist_ok=True)
+
+ # Save DataFrames to JSON files
+ income_json_filename = os.path.join( output_folder_income, f"income_month_{month}.json")
+ spending_json_filename = os.path.join(output_folder_spending, f"spending_month_{month}.json")
+
+ # Save DataFrames to Excel files
+ income_excel_filename = os.path.join( output_folder_income, f"income_month_{month}.xlsx")
+ spending_excel_filename = os.path.join(output_folder_spending, f"spending_month_{month}.xlsx")
+
+ # Drop rows with NaN values
+ income_dfs[month].dropna(inplace=True)
+ spending_dfs[month].dropna(inplace=True)
+
+ # Convert DataFrames to JSON strings
+ income_json_str = income_dfs[month].to_json(orient='records', lines=True, default_handler=str)
+ spending_json_str = spending_dfs[month].to_json(orient='records', lines=True, default_handler=str)
+
+ # Save DataFrames to Excel files
+ income_dfs[month].to_excel(income_excel_filename, index=False)
+ spending_dfs[month].to_excel(spending_excel_filename, index=False)
+
+ print(f"DataFrames saved to JSON files: {income_json_filename}, {spending_json_filename}")
+ print(f"DataFrames saved to Excel files: {income_excel_filename}, {spending_excel_filename}")
+
+ # Display skipped rows
+ if skipped_rows:
+ print(f"\nSkipped rows in file {filename}:")
+ for row_num, row_content in skipped_rows:
+ print(f"Row {row_num}: {row_content}")
+ else:
+ print(f"File {filename} not found. Skipping...")
+
+print("All files processed.")
+
+
+
+
+
+
+
+
+#Merge spending seperate xlsx files to one
+directory = '/home/eikov/fin/spendings'
+output_directory = '/home/eikov/fin/analysis'
+
+# List all files in the directory
+files = os.listdir(directory)
+
+# Filter out only the Excel files (files ending with .xlsx)
+excel_files = [file for file in files if file.endswith('.xlsx')]
+
+# Create a new workbook
+merged_workbook = openpyxl.Workbook()
+
+# Loop through each file and add it as a new sheet to the workbook
+for i, file_name in enumerate(excel_files):
+ # Load the workbook from file
+ workbook = openpyxl.load_workbook(os.path.join(directory, file_name))
+
+ # Get the active sheet from the loaded workbook
+ sheet = workbook.active
+
+ # Copy the active sheet to the merged workbook
+ if i == 0:
+ # For the first sheet, use the default sheet name 'Sheet'
+ merged_sheet = merged_workbook.active
+ else:
+ # For subsequent sheets, create a new sheet with the file name as the sheet name
+ merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}")
+
+ # Copy data from the original sheet to the merged sheet
+ for row in sheet.iter_rows(values_only=True):
+ merged_sheet.append(row)
+
+ # Add total sum formula(B2:B100) at the end of the last row
+ last_row = merged_sheet.max_row
+ formula_str = f'=SUM(B2:B100)'
+ merged_sheet[f'A{last_row + 1}'] = formula_str
+
+
+# Save the merged workbook to the output directory
+output_filename = os.path.join(output_directory, "merged_spending_data.xlsx")
+merged_workbook.save(output_filename)
+
+
+#Merge income seperate xlsx files to one
+directory = '/home/eikov/fin/income'
+output_directory = '/home/eikov/fin/analysis'
+
+# List all files in the directory
+files = os.listdir(directory)
+
+# Filter out only the Excel files (files ending with .xlsx)
+excel_files = [file for file in files if file.endswith('.xlsx')]
+
+# Create a new workbook
+merged_workbook = openpyxl.Workbook()
+
+# Loop through each file and add it as a new sheet to the workbook
+for i, file_name in enumerate(excel_files):
+ # Load the workbook from file
+ workbook = openpyxl.load_workbook(os.path.join(directory, file_name))
+
+ # Get the active sheet from the loaded workbook
+ sheet = workbook.active
+
+ # Copy the active sheet to the merged workbook
+ if i == 0:
+ # For the first sheet, use the default sheet name 'Sheet'
+ merged_sheet = merged_workbook.active
+ else:
+ # For subsequent sheets, create a new sheet with the file name as the sheet name
+ merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}")
+
+ # Copy data from the original sheet to the merged sheet
+ for row in sheet.iter_rows(values_only=True):
+ merged_sheet.append(row)
+
+ # Add total sum formula(B2:B100) at the end of the last row
+ last_row = merged_sheet.max_row
+ formula_str = f'=SUM(B2:B100)'
+ merged_sheet[f'A{last_row + 1}'] = formula_str
+
+
+ # Save the merged workbook to the output directory
+ output_filename = os.path.join(output_directory, "merged_income_data.xlsx")
+ merged_workbook.save(output_filename)
+
+
+
+
+
+
+
+
+
+
+
+# finding unique strings in INCOME and categorise it
+# Path to the directory containing the file
+output_directory = '/home/eikov/fin/analysis/'
+# Specify the filename
+filename = 'merged_income_data.xlsx'
+# Construct the full path
+file_path = f'{output_directory}/{filename}'
+
+# Initialize an empty set to store unique values
+unique_values = set()
+
+# Read the Excel file
+xls = pd.ExcelFile(file_path)
+
+# Iterate over each sheet in the Excel file
+for sheet_name in xls.sheet_names:
+ # Read the sheet into a DataFrame
+ df = pd.read_excel(xls, sheet_name)
+ # Check the number of columns in the DataFrame
+ if df.shape[1] < 4: # If the number of columns is less than 4
+ print(f"Skipping sheet '{sheet_name}' because it has less than 4 columns.")
+ continue # Skip to the next sheet
+ # Extract unique values from column 3 (Python uses zero-based indexing, so the third column is indexed as 2)
+ unique_values.update(df.iloc[:, 2].unique())
+
+# Create a DataFrame with unique values and save it to 'unique-strings-income.xlsx'
+unique_df = pd.DataFrame({'Unique Values': list(unique_values)})
+unique_df.to_excel(f'{output_directory}/unique-strings-income.xlsx', index=False)
+
+
+
+
+
+
+
+
+# finding unique strings in SPENDINGS and categorise it
+# Path to the directory containing the file
+output_directory2 = '/home/eikov/fin/analysis/'
+# Specify the filename
+filename2 = 'merged_spending_data.xlsx'
+# Construct the full path
+file_path2 = f'{output_directory2}/{filename2}'
+
+# Initialize an empty set to store unique values
+unique_values2 = set()
+
+# Read the Excel file
+xls = pd.ExcelFile(file_path2)
+
+# Iterate over each sheet in the Excel file
+for sheet_name in xls.sheet_names:
+ # Read the sheet into a DataFrame
+ df = pd.read_excel(xls, sheet_name)
+ # Check the number of columns in the DataFrame
+ if df.shape[1] < 4: # If the number of columns is less than 4
+ print(f"Skipping sheet '{sheet_name}' because it has less than 4 columns.")
+ continue # Skip to the next sheet
+ # Extract unique values from column 3 (Python uses zero-based indexing, so the third column is indexed as 2)
+ unique_values2.update(df.iloc[:, 2].unique())
+
+# Create a DataFrame with unique values and save it to 'unique-strings-spendings.'
+unique_df = pd.DataFrame({'Unique Values': list(unique_values2)})
+unique_df.to_excel(f'{output_directory}/unique-strings-spendings.xlsx', index=False)
+
+
+
+
+
+"""
+1) AUTO: turiu svarius spendings ir income israsus: merged_spending_data.xlsx ir merged_income_data.xlsx
+2) MANUAL: turiu unikaliu moketoju/gaveju sarasus is pajamu ir islaidu[unique-strings-.xlsx]. rankiniu budu priskiriu kiekvienam gavejui/moketojui
+kategorijas ir issaugau kaip cat-income.xlsx ir cat-spendings.xlsx.
+3) MANUAL: manually sukuriau Analysis_income.xlsx ir Analysis_spending.xlsx, kad script'as neoverwritintu mano kategoriju ir analiziu darbo.
+4) AUTO: belieka prijungti sheets, kad viskas susije su income butu vienam xlsx faile, ir viskas susije su spendings butu antram xlsx faile.
+
+ # Save the merged workbook to the output directory
+ output_filename = os.path.join(output_directory, "merged_income_data.xlsx")
+ merged_workbook.save(output_filename)
+"""
+
+#INCOME
+def merge_additional_sheets(base_file, additional_files, output_file):
+ # Create the output directory if it doesn't exist
+ output_directory_analysis = '/home/eikov/fin/analysis'
+ os.makedirs(output_directory_analysis, exist_ok=True)
+
+ # Create a Pandas ExcelWriter object
+ with pd.ExcelWriter(f'{output_directory_analysis}/{output_file}', engine='xlsxwriter') as writer:
+ # Write the base file to the output file
+ base_df = pd.read_excel(f'{output_directory_analysis}/{base_file}', sheet_name=None)
+ for sheet_name, df in base_df.items():
+ df.to_excel(writer, sheet_name=sheet_name, index=False)
+ # Add total sum formula to the end of the last row
+ last_row = df.shape[0] + 1
+ formula_str = f'=SUM(B2:B{last_row})'
+ writer.sheets[sheet_name].write(f'A{last_row + 1}', formula_str)
+
+ # Write additional files to the output file as new sheets
+ for file in additional_files:
+ additional_df = pd.read_excel(f'{output_directory_analysis}/{file}', sheet_name=None)
+ for sheet_name, df in additional_df.items():
+ df.to_excel(writer, sheet_name=f'{sheet_name}', index=False)
+ # Add total sum formula to the end of the last row
+ if file not in additional_files:
+ last_row = df.shape[0] + 1
+ formula_str = f'=SUM(B2:B{last_row})'
+ writer.sheets[sheet_name].write(f'A{last_row + 1}', formula_str)
+
+# List of additional files to merge as new sheets.
+additional_files = ['cat-income.xlsx', 'Analysis_income.xlsx']
+
+# Merge additional sheets with merged_income_data.xlsx
+merge_additional_sheets('merged_income_data.xlsx', additional_files, 'Galutinis_Income.xlsx')
+
+
+#SPENDINGS
+def merge_additional_sheets(base_file, additional_files, output_file):
+ # Create the output directory if it doesn't exist
+ output_directory_analysis = '/home/eikov/fin/analysis'
+ os.makedirs(output_directory_analysis, exist_ok=True)
+
+ # Create a Pandas ExcelWriter object
+ with pd.ExcelWriter(f'{output_directory_analysis}/{output_file}', engine='xlsxwriter') as writer:
+ # Write the base file to the output file
+ base_df = pd.read_excel(f'{output_directory_analysis}/{base_file}', sheet_name=None)
+ for sheet_name, df in base_df.items():
+ df.to_excel(writer, sheet_name=sheet_name, index=False)
+ # Add total sum formula to the end of the last row
+ last_row = df.shape[0] + 1
+ formula_str = f'=SUM(B2:B{last_row})'
+ writer.sheets[sheet_name].write(f'A{last_row + 1}', formula_str)
+
+ # Write additional files to the output file as new sheets
+ for file in additional_files:
+ additional_df = pd.read_excel(f'{output_directory_analysis}/{file}', sheet_name=None)
+ for sheet_name, df in additional_df.items():
+ df.to_excel(writer, sheet_name=f'{sheet_name}', index=False)
+ # Add total sum formula to the end of the last row
+ if file not in additional_files:
+ last_row = df.shape[0] + 1
+ formula_str = f'=SUM(B2:B{last_row})'
+ writer.sheets[sheet_name].write(f'A{last_row + 1}', formula_str)
+
+# List of additional files to merge as new sheets
+additional_files = ['cat-spendings.xlsx', 'Analysis_spendings.xlsx']
+
+# Merge additional sheets with merged_income_data.xlsx
+merge_additional_sheets('merged_spending_data.xlsx', additional_files, 'Galutinis_spendings.xlsx')
diff --git a/src/personal_finance/v2.3.py b/src/personal_finance/v2.3.py
new file mode 100755
index 0000000..e74be07
--- /dev/null
+++ b/src/personal_finance/v2.3.py
@@ -0,0 +1,474 @@
+import pandas as pd
+import os
+from io import StringIO
+import json
+import openpyxl
+
+#more advanced than scrape.py but not fully working yet:
+# =sum() shows 0
+# galutinis spendings looks, but income does not merge Categories and Analysis_Income into galutinis_income.xlsx
+
+#TBD:
+# a script scraped all entries and then gave me unique string values in .xlsx format.
+# 1) I want to see the sum each unique string . What's the best approach to do this, in pandas before writing to .xlsx or somehow edit it after saving?
+# 2) Then as a second [B] column I assigned each unique string value a sub-category and [C] column as Category. to do analysis on different Sheet named [Analysis]
+
+
+"""
+This code is designed to process bank statement files and organize the data into separate DataFrames for income and spending transactions. It performs the following tasks:
+
+1. Reads bank statement files from a specified directory.
+2. Extracts relevant information from each file, such as transaction date, amount, payer/payee name, and purpose.
+3. Applies data cleaning and preprocessing steps, including removing unnecessary columns and extracting the first two words from payer/payee names and payment purposes.
+4. Organizes the data into separate DataFrames for income and spending transactions, grouped by month.
+5. Saves the DataFrames as JSON and Excel files in separate directories for income and spending.
+6. Merges separate monthly Excel files into single "merged_income_data.xlsx" and "merged_spending_data.xlsx" files.
+7. Extracts unique payer/payee names from the merged files and saves them as "unique-strings-income.xlsx" and "unique-strings-spendings.xlsx".
+8. Allows manual categorization of payer/payee names by creating "cat-income.xlsx" and "cat-spendings.xlsx" files.
+9. Merges the categorized data and analysis sheets into final "Galutinis_Income.xlsx" and "Galutinis_spendings.xlsx" files.
+
+The code is designed to be modular and extensible, allowing for easy integration of additional data processing or analysis steps as needed.
+"""
+
+"""
+#next: in 8: create DF for each found excel spending(-) file(ignore the last row with total sum) and join into one DF.
+now iterate each unique value and sum all "SUMA" values. if that's not possible, convert DF to set and then sum key values.
+"""
+
+
+# Define the directory containing the CSV files
+directory = '/home/eikov/fin/OG-CSV'
+
+# Define the pattern for file names
+file_pattern = '{}{}.csv' # {} will be replaced by the file number and +/- for the file type
+
+# Initialize empty dictionaries to store dataframes for each month
+spending_dfs = {}
+income_dfs = {}
+
+# Define the filters
+filter1 = "KOVALIŪNAS EINARAS"
+filter2 = "Einaras Kovaliūnas" # Add your second filter value here
+
+
+# Function to extract first 2 words from a string
+def extract_first_2_words(text):
+ if pd.isna(text): # Check if the value is NaN
+ return "" # Return an empty string if NaN
+ words = text.split()[:2]
+ return ' '.join(words)
+
+
+
+# Iterate over the file numbers from 1 to 12
+for file_num in range(1, 13):
+ month = str(file_num)
+ spending_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"])
+ income_dfs[month] = pd.DataFrame(columns=["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"])
+ for file_type in ['+', '-']:
+ filename = file_pattern.format(file_num, file_type)
+ filepath = os.path.join(directory, filename)
+ if os.path.exists(filepath):
+ print(f"Processing file: {filename}")
+ # Initialize an empty list to store the valid lines of the CSV file
+ valid_lines = []
+ skipped_rows = []
+
+ # Read the CSV file line by line
+ with open(filepath, 'r', encoding='utf-8') as file:
+ for i, line in enumerate(file):
+ if i < 2: # Skip the first two lines (first row and header row)
+ continue
+ try:
+ # Attempt to parse the line as CSV
+ pd.read_csv(StringIO(line), header=None, sep=';') # Use header=None and specify separator as ';'
+ valid_lines.append(line) # If successful, add the line to the list of valid lines
+ except pd.errors.ParserError:
+ print(f"Skipped line {i+1} in file {filename} due to ParserError: {line.strip()}")
+ skipped_rows.append((i+1, line.strip())) # Store the line number and the content of the skipped row
+
+ # Read the valid lines into a DataFrame, skipping the header line
+ bank_data = None
+ try:
+ bank_data = pd.read_csv(StringIO(''.join(valid_lines)), header=None, sep=';')
+ except pd.errors.ParserError:
+ print(f"Encountered an error while parsing file {filename}. Skipping problematic rows.")
+ if not valid_lines:
+ print("No valid data read due to errors.")
+ continue
+
+ # If data was successfully read, extract specific columns and apply filter
+ if bank_data is not None:
+ bank_data = bank_data[[1, 3, 4, 9]] # Extract only desired columns
+
+ # Replace empty values with NaN
+ bank_data.replace('-', pd.NA, inplace=True)
+
+ # Rename columns
+ bank_data.columns = ["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"]
+
+ # Apply filters
+ bank_data = bank_data[(bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] != filter1) &
+ (bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] != filter2)]
+
+ # Filter out rows that are the same as column headers or contain all empty values
+ bank_data = bank_data[~bank_data.apply(lambda row: all(row == ["DATA", "SUMA", "MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS", "MOKÄ–JIMO PASKIRTIS"]), axis=1)]
+
+ # Extract first 4 words from specified columns
+ bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"] = bank_data["MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS"].apply(extract_first_2_words)
+ bank_data["MOKÄ–JIMO PASKIRTIS"] = bank_data["MOKÄ–JIMO PASKIRTIS"].apply(extract_first_2_words)
+
+ # Replace commas with periods in the "SUMA" column
+ bank_data["SUMA"] = bank_data["SUMA"].str.replace(',', '.')
+
+ # Convert the values in the "SUMA" column to numeric data type
+ bank_data["SUMA"] = pd.to_numeric(bank_data["SUMA"], errors='coerce')
+ # Now the "SUMA" column contains numeric values
+
+
+ # Append the DataFrame to the appropriate dictionary based on file type
+ if file_type == '+':
+ income_dfs[month] = bank_data
+ elif file_type == '-':
+ spending_dfs[month] = bank_data
+ else:
+ print(f"No valid data read from file {filename}.")
+
+ # Display the DataFrame
+ if file_type == '+':
+ print(f"\nIncome DataFrame for month {month}:")
+ print(income_dfs[month])
+ elif file_type == '-':
+ print(f"\nSpending DataFrame for month {month}:")
+ print(spending_dfs[month])
+
+ # Specify the folder path
+ output_folder_income = "/home/eikov/fin/income"
+ output_folder_spending = "/home/eikov/fin/spendings"
+
+ # Create the folder if it doesn't exist
+ os.makedirs(output_folder_income, exist_ok=True)
+ os.makedirs(output_folder_spending, exist_ok=True)
+
+ # Save DataFrames to JSON files
+ income_json_filename = os.path.join( output_folder_income, f"income_month_{month}.json")
+ spending_json_filename = os.path.join(output_folder_spending, f"spending_month_{month}.json")
+
+ # Save DataFrames to Excel files
+ income_excel_filename = os.path.join( output_folder_income, f"income_month_{month}.xlsx")
+ spending_excel_filename = os.path.join(output_folder_spending, f"spending_month_{month}.xlsx")
+
+ # Drop rows with NaN values
+ income_dfs[month].dropna(inplace=True)
+ spending_dfs[month].dropna(inplace=True)
+
+ # Convert DataFrames to JSON strings
+ income_json_str = income_dfs[month].to_json(orient='records', lines=True, default_handler=str)
+ spending_json_str = spending_dfs[month].to_json(orient='records', lines=True, default_handler=str)
+
+ # Save DataFrames to Excel files
+ income_dfs[month].to_excel(income_excel_filename, index=False)
+ spending_dfs[month].to_excel(spending_excel_filename, index=False)
+
+ print(f"DataFrames saved to JSON files: {income_json_filename}, {spending_json_filename}")
+ print(f"DataFrames saved to Excel files: {income_excel_filename}, {spending_excel_filename}")
+
+ # Display skipped rows
+ if skipped_rows:
+ print(f"\nSkipped rows in file {filename}:")
+ for row_num, row_content in skipped_rows:
+ print(f"Row {row_num}: {row_content}")
+ else:
+ print(f"File {filename} not found. Skipping...")
+
+print("All files processed.")
+
+
+
+
+
+
+
+
+#Merge spending seperate xlsx files to one
+directory = '/home/eikov/fin/spendings'
+output_directory = '/home/eikov/fin/analysis'
+
+# List all files in the directory
+files = os.listdir(directory)
+
+# Filter out only the Excel files (files ending with .xlsx)
+excel_files = [file for file in files if file.endswith('.xlsx')]
+
+# Create a new workbook
+merged_workbook = openpyxl.Workbook()
+
+# Loop through each file and add it as a new sheet to the workbook
+for i, file_name in enumerate(excel_files):
+ workbook = openpyxl.load_workbook(os.path.join(directory, file_name))
+ sheet = workbook.active
+
+ if i == 0:
+ merged_sheet = merged_workbook.active
+ else:
+ merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}")
+
+ for row in sheet.iter_rows(values_only=True):
+ if not any(row): # skip empty rows
+ continue
+ merged_sheet.append(row)
+
+ # Add total formula
+ data_start_row = 2
+ data_end_row = merged_sheet.max_row
+ formula_str = f'=SUM(B{data_start_row}:B{data_end_row})'
+ merged_sheet[f'A{data_end_row + 1}'] = 'TOTAL'
+ merged_sheet[f'B{data_end_row + 1}'] = formula_str
+
+
+# Save the merged workbook to the output directory
+output_filename = os.path.join(output_directory, "merged_spending_data.xlsx")
+merged_workbook.save(output_filename)
+
+
+#Merge income seperate xlsx files to one
+directory = '/home/eikov/fin/income'
+output_directory = '/home/eikov/fin/analysis'
+
+# List all files in the directory
+files = os.listdir(directory)
+
+# Filter out only the Excel files (files ending with .xlsx)
+excel_files = [file for file in files if file.endswith('.xlsx')]
+
+# Create a new workbook
+merged_workbook = openpyxl.Workbook()
+
+# Loop through each file and add it as a new sheet to the workbook
+for i, file_name in enumerate(excel_files):
+ # Load the workbook from file
+ workbook = openpyxl.load_workbook(os.path.join(directory, file_name))
+
+ # Get the active sheet from the loaded workbook
+ sheet = workbook.active
+
+ # Copy the active sheet to the merged workbook
+ if i == 0:
+ # For the first sheet, use the default sheet name 'Sheet'
+ merged_sheet = merged_workbook.active
+ else:
+ # For subsequent sheets, create a new sheet with the file name as the sheet name
+ merged_sheet = merged_workbook.create_sheet(title=f"Sheet{i+1}")
+
+ # Copy data from the original sheet to the merged sheet
+ for row in sheet.iter_rows(values_only=True):
+ merged_sheet.append(row)
+
+ # Add total sum formula(B2:B100) at the end of the last row
+ last_row = merged_sheet.max_row
+ formula_str = f'=SUM(B2:B100)'
+ merged_sheet[f'A{last_row + 1}'] = formula_str
+
+
+ # Save the merged workbook to the output directory
+ output_filename = os.path.join(output_directory, "merged_income_data.xlsx")
+ merged_workbook.save(output_filename)
+
+
+
+
+
+
+
+
+
+
+
+# finding unique strings in SPENDINGS and categorise it
+# Path to the directory containing the file
+output_directory2 = '/home/eikov/fin/analysis'
+filename2 = 'merged_income_data.xlsx'
+file_path2 = f'{output_directory2}/{filename2}'
+
+# Read Excel file
+xls = pd.ExcelFile(file_path2)
+
+# Create empty DataFrame to collect values
+all_data = pd.DataFrame(columns=['SUMA', 'MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS'])
+
+# Iterate through each sheet
+for sheet_name in xls.sheet_names:
+ df = pd.read_excel(xls, sheet_name)
+
+ if df.shape[1] < 4:
+ print(f"Skipping sheet '{sheet_name}' because it has less than 4 columns.")
+ continue
+
+ # Extract 'SUMA' and 'STRING' columns by index — adjust if needed
+ amounts = df.iloc[:, 1] # this is column B, the 'SUMA'
+ labels = df.iloc[:, 2] # this is column C, the 'MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS'
+
+
+ # Combine into one DataFrame
+ temp_df = pd.DataFrame({'SUMA': amounts, 'MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS': labels})
+
+ # Drop rows where either SUMA or STRING is missing
+ temp_df.dropna(subset=['SUMA', 'MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS'], inplace=True)
+
+ # 🛠Clean and convert SUMA properly
+ temp_df['SUMA'] = (
+ temp_df['SUMA']
+ .astype(str)
+ .str.replace(r'[^\d,.-]', '', regex=True)
+ .str.replace(',', '.', regex=False)
+ .astype(float)
+ )
+
+
+ all_data = pd.concat([all_data, temp_df], ignore_index=True)
+
+# Group and sum
+grouped = all_data.groupby('MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS', as_index=False)['SUMA'].sum()
+
+# Save results
+grouped.to_excel(f'{output_directory2}/summed-strings-income.xlsx', index=False)
+
+
+
+
+
+
+
+
+# finding unique strings in SPENDINGS and categorise it
+# Path to the directory containing the file
+output_directory2 = '/home/eikov/fin/analysis'
+filename2 = 'merged_spending_data.xlsx'
+file_path2 = f'{output_directory2}/{filename2}'
+
+# Read Excel file
+xls = pd.ExcelFile(file_path2)
+
+# Create empty DataFrame to collect values
+all_data = pd.DataFrame(columns=['SUMA', 'MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS'])
+
+# Iterate through each sheet
+for sheet_name in xls.sheet_names:
+ df = pd.read_excel(xls, sheet_name)
+
+ if df.shape[1] < 4:
+ print(f"Skipping sheet '{sheet_name}' because it has less than 4 columns.")
+ continue
+
+ # Extract 'SUMA' and 'STRING' columns by index — adjust if needed
+ amounts = df.iloc[:, 1] # this is column B, the 'SUMA'
+ labels = df.iloc[:, 2] # this is column C, the 'MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS'
+
+
+ # Combine into one DataFrame
+ temp_df = pd.DataFrame({'SUMA': amounts, 'MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS': labels})
+
+ # Drop rows where either SUMA or STRING is missing
+ temp_df.dropna(subset=['SUMA', 'MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS'], inplace=True)
+
+ # 🛠Clean and convert SUMA properly
+ temp_df['SUMA'] = (
+ temp_df['SUMA']
+ .astype(str)
+ .str.replace(r'[^\d,.-]', '', regex=True)
+ .str.replace(',', '.', regex=False)
+ .astype(float)
+ )
+
+ all_data = pd.concat([all_data, temp_df], ignore_index=True)
+
+# Group and sum
+grouped = all_data.groupby('MOKÄ–TOJO ARBA GAVÄ–JO PAVADINIMAS', as_index=False)['SUMA'].sum()
+
+# Save results
+grouped.to_excel(f'{output_directory2}/summed-strings-spendings.xlsx', index=False)
+
+
+
+
+
+"""
+1) AUTO: turiu svarius spendings ir income israsus: merged_spending_data.xlsx ir merged_income_data.xlsx
+2) MANUAL: turiu unikaliu moketoju/gaveju sarasus is pajamu ir islaidu[summed-strings-income/spendings.xlsx]. rankiniu budu priskiriu kiekvienam gavejui/moketojui
+kategorijas ir issaugau kaip cat-income.xlsx ir cat-spendings.xlsx.
+3) MANUAL: manually sukuriau Analysis_income.xlsx ir Analysis_spending.xlsx, kad script'as neoverwritintu mano kategoriju ir analiziu darbo.
+4) AUTO: belieka prijungti sheets, kad viskas susije su income butu vienam xlsx faile, ir viskas susije su spendings butu antram xlsx faile.
+
+ # Save the merged workbook to the output directory
+ output_filename = os.path.join(output_directory, "merged_income_data.xlsx")
+ merged_workbook.save(output_filename)
+"""
+
+#INCOME
+def merge_additional_sheets(base_file, additional_files, output_file):
+ # Create the output directory if it doesn't exist
+ output_directory_analysis = '/home/eikov/fin/analysis'
+ os.makedirs(output_directory_analysis, exist_ok=True)
+
+ # Create a Pandas ExcelWriter object
+ with pd.ExcelWriter(f'{output_directory_analysis}/{output_file}', engine='xlsxwriter') as writer:
+ # Write the base file to the output file
+ base_df = pd.read_excel(f'{output_directory_analysis}/{base_file}', sheet_name=None)
+ for sheet_name, df in base_df.items():
+ df.to_excel(writer, sheet_name=sheet_name, index=False)
+ # Add total sum formula to the end of the last row
+ last_row = df.shape[0] + 1
+ formula_str = f'=SUM(B2:B{last_row})'
+ writer.sheets[sheet_name].write(f'A{last_row + 1}', formula_str)
+
+ # Write additional files to the output file as new sheets
+ for file in additional_files:
+ additional_df = pd.read_excel(f'{output_directory_analysis}/{file}', sheet_name=None)
+ for sheet_name, df in additional_df.items():
+ df.to_excel(writer, sheet_name=f'{sheet_name}', index=False)
+ # Add total sum formula to the end of the last row
+ if file not in additional_files:
+ last_row = df.shape[0] + 1
+ formula_str = f'=SUM(B2:B{last_row})'
+ writer.sheets[sheet_name].write(f'A{last_row + 1}', formula_str)
+
+# List of additional files to merge as new sheets.
+additional_files = ['cat-income.xlsx', 'Analysis_income.xlsx']
+
+# Merge additional sheets with merged_income_data.xlsx
+merge_additional_sheets('merged_income_data.xlsx', additional_files, 'Galutinis_Income.xlsx')
+
+
+#SPENDINGS
+def merge_additional_sheets(base_file, additional_files, output_file):
+ # Create the output directory if it doesn't exist
+ output_directory_analysis = '/home/eikov/fin/analysis'
+ os.makedirs(output_directory_analysis, exist_ok=True)
+
+ # Create a Pandas ExcelWriter object
+ with pd.ExcelWriter(f'{output_directory_analysis}/{output_file}', engine='xlsxwriter') as writer:
+ # Write the base file to the output file
+ base_df = pd.read_excel(f'{output_directory_analysis}/{base_file}', sheet_name=None)
+ for sheet_name, df in base_df.items():
+ df.to_excel(writer, sheet_name=sheet_name, index=False)
+ # Add total sum formula to the end of the last row
+ last_row = df.shape[0] + 1
+ formula_str = f'=SUM(B2:B{last_row})'
+ writer.sheets[sheet_name].write(f'A{last_row + 1}', formula_str)
+
+ # Write additional files to the output file as new sheets
+ for file in additional_files:
+ additional_df = pd.read_excel(f'{output_directory_analysis}/{file}', sheet_name=None)
+ for sheet_name, df in additional_df.items():
+ df.to_excel(writer, sheet_name=f'{sheet_name}', index=False)
+ # Add total sum formula to the end of the last row
+ if file not in additional_files:
+ last_row = df.shape[0] + 1
+ formula_str = f'=SUM(B2:B{last_row})'
+ writer.sheets[sheet_name].write(f'A{last_row + 1}', formula_str)
+
+# List of additional files to merge as new sheets
+additional_files = ['cat-spendings.xlsx', 'Analysis_spendings.xlsx']
+
+# Merge additional sheets with merged_income_data.xlsx
+merge_additional_sheets('merged_spending_data.xlsx', additional_files, 'Galutinis_spendings.xlsx')
diff --git a/src/personal_notes/clickup_extract/plan.txt b/src/personal_notes/clickup_extract/plan.txt
new file mode 100755
index 0000000..14aac82
--- /dev/null
+++ b/src/personal_notes/clickup_extract/plan.txt
@@ -0,0 +1,11 @@
+extractinu data is clickup, palieku tik:
+1) project management category ~30lines
+2) data programming ~40 lines
+3) climate and life ~15lines
+4) to-do
+
+should be fine for free version. if not, leave only 1) and 2)
+3) and 4) to Joplin
+
+
+
diff --git a/src/personal_notes/clickup_extract/tempy.py b/src/personal_notes/clickup_extract/tempy.py
new file mode 100755
index 0000000..c1bc655
--- /dev/null
+++ b/src/personal_notes/clickup_extract/tempy.py
@@ -0,0 +1,104 @@
+import os
+import pandas as pd
+import re
+
+# Specify the CSV file path
+csv_file = 'clickup10-25.csv' # Replace with the path to your CSV file
+df = pd.read_csv(csv_file)
+
+# Strip whitespace from column names immediately after loading
+df.columns = df.columns.str.strip()
+
+# Columns to delete (excluding "Task ID" and "Parent ID")
+columns_to_delete = ['Attachments', 'Date Created', 'Date Created Text', 'Due Date', 'Due Date Text',
+ 'Start Date', 'Start Date Text', 'Assignees', 'Folder Name', 'Time Estimated',
+ 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent',
+ 'Time Spent Text', 'Rolled Up Time', 'Rolled Up Time Text', 'Priority', 'Status']
+
+# Drop specified columns
+df = df.drop(columns=columns_to_delete)
+
+# Sort the DataFrame by the "Space Name" column
+df_sorted = df.sort_values(by='Space Name')
+
+# Get unique values in the "List Name" column
+unique_values = df_sorted['List Name'].unique()
+
+# Define output directory
+output_dir = 'output'
+os.makedirs(output_dir, exist_ok=True)
+
+# Define a maximum length for folder and file names (adjust as needed)
+MAX_NAME_LENGTH = 100
+
+# Utility function to sanitize names
+def sanitize_name(name):
+ # Remove invalid characters and truncate
+ sanitized = re.sub(r'[<>:"/\\|?*]', '', str(name))
+ return sanitized[:MAX_NAME_LENGTH] # Truncate if necessary
+
+# Process each unique List Name
+for value in unique_values:
+ sanitized_list_name = sanitize_name(value) # Sanitize List Name
+ list_folder = os.path.join(output_dir, sanitized_list_name)
+ os.makedirs(list_folder, exist_ok=True)
+
+ # Filter DataFrame based on the current List Name value
+ filtered_df = df[df['List Name'] == value]
+
+ # Drop columns 'Space Name' and 'List Name'
+ filtered_df = filtered_df.drop(columns=['Space Name', 'List Name'])
+
+ # Prepare a list to hold sorted rows
+ sorted_rows = []
+
+ # Define function to add tasks and their children
+ def add_task_with_children(task_id):
+ task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist()
+ for row in task_rows:
+ sorted_rows.append(row)
+ child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique()
+ for child_task in child_tasks:
+ add_task_with_children(child_task)
+
+ # Find top-level tasks (no parent) and create folders for each
+ top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()]
+
+ for _, task_row in top_level_tasks.iterrows():
+ task_id = task_row['Task ID']
+ task_name = task_row['Task Name']
+
+ # Sanitize the task name for folder and file naming
+ sanitized_task_name = sanitize_name(task_name) # Sanitize Task Name
+
+ # Create folder for the top-level task using the sanitized name
+ task_folder = os.path.join(list_folder, sanitized_task_name)
+ os.makedirs(task_folder, exist_ok=True)
+
+ # Collect children of the top-level task
+ sorted_rows.clear()
+ add_task_with_children(task_id)
+
+ # Convert to DataFrame
+ sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns)
+
+ # Name the text file based on the parent task's name
+ txt_output_file = os.path.join(task_folder, f'{sanitized_task_name}.txt')
+
+ # Check for filename length and truncate if necessary
+ if len(txt_output_file) > 255:
+ txt_output_file = os.path.join(task_folder, f'{sanitized_task_name[:255]}.txt')
+
+ txt_columns_to_exclude = ['Task ID', 'Parent ID']
+ sorted_df_txt = sorted_df.drop(columns=txt_columns_to_exclude)
+ sorted_df_txt = sorted_df_txt.replace(['NaN', '[]'], '')
+
+ # Write to the text file
+ with open(txt_output_file, 'w', encoding='utf-8') as f:
+ for _, row in sorted_df_txt.iterrows():
+ cleaned_row = [f"(({val}))" if pd.notna(val) and col == 'Task Content' else str(val) if pd.notna(val) else ''
+ for col, val in zip(sorted_df_txt.columns, row.values)]
+ f.write('\t'.join(cleaned_row) + '\n')
+ f.write("----------------------------------------------------\n") # Add separator line after each child's content
+
+print(f'Processed {len(df)} rows and saved them in the "{output_dir}" directory with the specified folder structure.')
diff --git a/src/personal_notes/clickup_extract/v1clean.py b/src/personal_notes/clickup_extract/v1clean.py
new file mode 100755
index 0000000..4dec753
--- /dev/null
+++ b/src/personal_notes/clickup_extract/v1clean.py
@@ -0,0 +1,40 @@
+import os
+import pandas as pd
+
+#preparing clickup exported CSV file for cleaning unnecessary columns
+# Read the CSV file into a DataFrame
+csv_file = 'clickup2.csv' # Replace 'your_file.csv' with the path to your CSV file
+df = pd.read_csv(csv_file)
+
+# Specify the columns you want to delete
+columns_to_delete = ['Task ID', 'Attachments', 'Date Created', 'Date Created Text','Due Date', 'Due Date Text','Start Date', 'Start Date Text','Assignees', 'Folder Name', 'Time Estimated',
+ 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent', 'Time Spent', 'Time Spent Text',
+ 'Rolled Up Time', 'Rolled Up Time Text',
+ 'Parent ID', 'Tags', 'Priority', 'Status']
+# Replace with the names of columns you want to delete
+
+# Drop the specified columns from the DataFrame
+df = df.drop(columns=columns_to_delete)
+
+# Sort the DataFrame by the "Space Name" column
+df_sorted = df.sort_values(by='Space Name')
+
+# Get all the unique values in the "List Name" column
+unique_values = df_sorted['List Name'].unique()
+# Print or use the unique values as needed
+print(unique_values)
+
+# Write the modified DataFrame back to a CSV file
+output_dir = 'output'
+os.makedirs(output_dir, exist_ok=True)
+for value in unique_values:
+ # Sanitize the string to replace problematic characters
+ sanitized_value = value.replace('/', '_') # Replace '/' with '_'
+ output_file = os.path.join(output_dir, f'{sanitized_value}.csv')
+ # Filter the DataFrame based on the current value
+ filtered_df = df[df['List Name'] == value]
+ # Drop additional columns from the filtered DataFrame
+ additional_columns_to_drop = ['Space Name', 'List Name'] # Replace with the names of columns you want to drop
+ filtered_df = filtered_df.drop(columns=additional_columns_to_drop)
+ # Write the filtered DataFrame to a CSV file
+ filtered_df.to_csv(output_file, index=False)
diff --git a/src/personal_notes/clickup_extract/v2sorted_messy.py b/src/personal_notes/clickup_extract/v2sorted_messy.py
new file mode 100755
index 0000000..ff20c3e
--- /dev/null
+++ b/src/personal_notes/clickup_extract/v2sorted_messy.py
@@ -0,0 +1,74 @@
+import os
+import pandas as pd
+
+# Preparing clickup exported CSV file for cleaning unnecessary columns
+csv_file = 'clickup2.csv' # Replace with the path to your CSV file
+df = pd.read_csv(csv_file)
+
+# Specify the columns you want to delete (excluding "Task ID" and "Parent ID")
+columns_to_delete = ['Attachments', 'Date Created', 'Date Created Text', 'Due Date', 'Due Date Text',
+ 'Start Date', 'Start Date Text', 'Assignees', 'Folder Name', 'Time Estimated',
+ 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent',
+ 'Time Spent Text', 'Rolled Up Time', 'Rolled Up Time Text', 'Priority', 'Status']
+
+# Drop the specified columns from the DataFrame
+df = df.drop(columns=columns_to_delete)
+
+# Sort the DataFrame by the "Space Name" column
+df_sorted = df.sort_values(by='Space Name')
+
+# Get all the unique values in the "List Name" column
+unique_values = df_sorted['List Name'].unique()
+
+# Print or use the unique values as needed
+print(unique_values)
+
+# Write the modified DataFrame back to a CSV and TXT files
+output_dir = 'output'
+os.makedirs(output_dir, exist_ok=True)
+
+for value in unique_values:
+ # Sanitize the string to replace problematic characters
+ sanitized_value = value.replace('/', '_') # Replace '/' with '_'
+ csv_output_file = os.path.join(output_dir, f'{sanitized_value}.csv')
+ txt_output_file = os.path.join(output_dir, f'{sanitized_value}.txt')
+
+ # Filter the DataFrame based on the current value
+ filtered_df = df[df['List Name'] == value]
+
+ # Drop additional columns from the filtered DataFrame
+ additional_columns_to_drop = ['Space Name', 'List Name'] # Replace with the names of columns you want to drop
+ filtered_df = filtered_df.drop(columns=additional_columns_to_drop)
+
+ # Create a list to hold the final sorted rows
+ sorted_rows = []
+
+ # Function to add tasks and their children
+ def add_task_with_children(task_id):
+ task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist()
+ for row in task_rows:
+ sorted_rows.append(row)
+ sorted_rows.append(['---'] * len(row)) # Add separator row
+ child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique()
+ for child_task in child_tasks:
+ add_task_with_children(child_task)
+
+ # Find all tasks that do not have a parent (top-level tasks)
+ top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()]['Task ID'].unique()
+
+ # Add top-level tasks and their children to the sorted rows
+ for task_id in top_level_tasks:
+ add_task_with_children(task_id)
+
+ # Convert the list of sorted rows back to a DataFrame
+ sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns)
+
+ # Write the sorted DataFrame to a CSV file
+ sorted_df.to_csv(csv_output_file, index=False)
+
+ # Write the sorted DataFrame to a TXT file
+ with open(txt_output_file, 'w', encoding='utf-8') as f:
+ for index, row in sorted_df.iterrows():
+ f.write('\t'.join(map(str, row.values)) + '\n')
+ if all(cell == '---' for cell in row.values):
+ f.write('\n') # Add an extra newline after separator rows
diff --git a/src/personal_notes/clickup_extract/v3.0clean_sorted.py b/src/personal_notes/clickup_extract/v3.0clean_sorted.py
new file mode 100755
index 0000000..29f276b
--- /dev/null
+++ b/src/personal_notes/clickup_extract/v3.0clean_sorted.py
@@ -0,0 +1,99 @@
+import os
+import pandas as pd
+import re
+
+#output looksl ike: unnecessary folders, hard to read data.
+
+
+# Preparing clickup exported CSV file for cleaning unnecessary columns
+csv_file = 'clickup2.csv' # Replace with the path to your CSV file
+df = pd.read_csv(csv_file)
+
+# Specify the columns you want to delete (excluding "Task ID" and "Parent ID")
+columns_to_delete = ['Attachments', 'Date Created', 'Date Created Text', 'Due Date', 'Due Date Text',
+ 'Start Date', 'Start Date Text', 'Assignees', 'Folder Name', 'Time Estimated',
+ 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent',
+ 'Time Spent Text', 'Rolled Up Time', 'Rolled Up Time Text', 'Priority', 'Status']
+
+# Drop the specified columns from the DataFrame
+df = df.drop(columns=columns_to_delete)
+
+# Sort the DataFrame by the "Space Name" column
+df_sorted = df.sort_values(by='Space Name')
+
+# Get all unique values in the "List Name" column
+unique_values = df_sorted['List Name'].unique()
+
+# Print or use the unique values as needed
+print(unique_values)
+
+# Find all unique tags
+all_tags = set()
+for tags in df_sorted['Tags'].dropna():
+ all_tags.update(tags.split(','))
+
+# Write the modified DataFrame back to CSV and TXT files
+output_dir = 'output'
+os.makedirs(output_dir, exist_ok=True)
+
+for value in unique_values:
+ # Sanitize the string to replace problematic characters
+ sanitized_value = value.replace('/', '_') # Replace '/' with '_'
+ csv_output_file = os.path.join(output_dir, f'{sanitized_value}.csv')
+ txt_output_file = os.path.join(output_dir, f'{sanitized_value}.txt')
+
+ # Filter the DataFrame based on the current value
+ filtered_df = df[df['List Name'] == value]
+
+ # Drop additional columns from the filtered DataFrame
+ additional_columns_to_drop = ['Space Name', 'List Name'] # Replace with the names of columns you want to drop
+ filtered_df = filtered_df.drop(columns=additional_columns_to_drop)
+
+ # Create a list to hold the final sorted rows
+ sorted_rows = []
+
+ # Function to add tasks and their children
+ def add_task_with_children(task_id):
+ task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist()
+ for row in task_rows:
+ sorted_rows.append(row)
+ child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique()
+ for child_task in child_tasks:
+ add_task_with_children(child_task)
+
+ # Find all tasks that do not have a parent (top-level tasks)
+ top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()]['Task ID'].unique()
+
+ # Add top-level tasks and their children to the sorted rows
+ for task_id in top_level_tasks:
+ add_task_with_children(task_id)
+
+ # Convert the list of sorted rows back to a DataFrame
+ sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns)
+
+ # Write the sorted DataFrame to a CSV file
+ sorted_df.to_csv(csv_output_file, index=False)
+
+ # Write the sorted DataFrame to a TXT file
+ with open(txt_output_file, 'w', encoding='utf-8') as f:
+ for index, row in sorted_df.iterrows():
+ f.write('\t'.join(map(str, row.values)) + '\n')
+
+ # Write the DataFrame to tag-specific folders
+ for tag in all_tags:
+ # Escape special characters in the tag
+ escaped_tag = re.escape(tag.strip())
+ tag_filtered_df = sorted_df[sorted_df['Tags'].str.contains(escaped_tag, na=False)]
+ if not tag_filtered_df.empty:
+ tag_dir = os.path.join(output_dir, tag.strip())
+ os.makedirs(tag_dir, exist_ok=True)
+ tag_csv_output_file = os.path.join(tag_dir, f'{sanitized_value}.csv')
+ tag_txt_output_file = os.path.join(tag_dir, f'{sanitized_value}.txt')
+
+ tag_filtered_df.to_csv(tag_csv_output_file, index=False)
+
+ with open(tag_txt_output_file, 'w', encoding='utf-8') as f:
+ for index, row in tag_filtered_df.iterrows():
+ f.write('\t'.join(map(str, row.values)) + '\n')
+
+print(f'Processed {len(df)} rows and saved them as .csv and .txt files in the "{output_dir}" directory and tag-specific subdirectories.')
diff --git a/src/personal_notes/clickup_extract/v3.1.py b/src/personal_notes/clickup_extract/v3.1.py
new file mode 100755
index 0000000..68fb915
--- /dev/null
+++ b/src/personal_notes/clickup_extract/v3.1.py
@@ -0,0 +1,110 @@
+import os
+import pandas as pd
+import re
+
+#output looks like: with unnecessary folders by the tags, clean data.
+
+
+# Preparing clickup exported CSV file for cleaning unnecessary columns
+csv_file = 'clickup2.csv' # Replace with the path to your CSV file
+df = pd.read_csv(csv_file)
+
+# Specify the columns you want to delete (excluding "Task ID" and "Parent ID")
+columns_to_delete = ['Attachments', 'Date Created', 'Date Created Text', 'Due Date', 'Due Date Text',
+ 'Start Date', 'Start Date Text', 'Assignees', 'Folder Name', 'Time Estimated',
+ 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent',
+ 'Time Spent Text', 'Rolled Up Time', 'Rolled Up Time Text', 'Priority', 'Status']
+
+# Drop the specified columns from the DataFrame
+df = df.drop(columns=columns_to_delete)
+
+# Sort the DataFrame by the "Space Name" column
+df_sorted = df.sort_values(by='Space Name')
+
+# Get all unique values in the "List Name" column
+unique_values = df_sorted['List Name'].unique()
+
+# Print or use the unique values as needed
+print(unique_values)
+
+# Find all unique tags
+all_tags = set()
+for tags in df_sorted['Tags'].dropna():
+ all_tags.update(tags.split(','))
+
+# Write the modified DataFrame back to CSV and TXT files
+output_dir = 'output'
+os.makedirs(output_dir, exist_ok=True)
+
+for value in unique_values:
+ # Sanitize the string to replace problematic characters
+ sanitized_value = value.replace('/', '_') # Replace '/' with '_'
+ csv_output_file = os.path.join(output_dir, f'{sanitized_value}.csv')
+ txt_output_file = os.path.join(output_dir, f'{sanitized_value}.txt')
+
+ # Filter the DataFrame based on the current value
+ filtered_df = df[df['List Name'] == value]
+
+ # Drop additional columns from the filtered DataFrame
+ additional_columns_to_drop = ['Space Name', 'List Name'] # Replace with the names of columns you want to drop
+ filtered_df = filtered_df.drop(columns=additional_columns_to_drop)
+
+ # Create a list to hold the final sorted rows
+ sorted_rows = []
+
+ # Function to add tasks and their children
+ def add_task_with_children(task_id):
+ task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist()
+ for row in task_rows:
+ sorted_rows.append(row)
+ child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique()
+ for child_task in child_tasks:
+ add_task_with_children(child_task)
+
+ # Find all tasks that do not have a parent (top-level tasks)
+ top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()]['Task ID'].unique()
+
+ # Add top-level tasks and their children to the sorted rows
+ for task_id in top_level_tasks:
+ add_task_with_children(task_id)
+
+ # Convert the list of sorted rows back to a DataFrame
+ sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns)
+
+ # Write the sorted DataFrame to a CSV file
+ sorted_df.to_csv(csv_output_file, index=False)
+
+ # Write the sorted DataFrame to a TXT file without "Task ID" and "Parent ID" columns and removing NaN and []
+ txt_columns_to_exclude = ['Task ID', 'Parent ID']
+ sorted_df_txt = sorted_df.drop(columns=txt_columns_to_exclude)
+ sorted_df_txt = sorted_df_txt.replace(['NaN', '[]'], '')
+
+ with open(txt_output_file, 'w', encoding='utf-8') as f:
+ for index, row in sorted_df_txt.iterrows():
+ cleaned_row = [f"(({value}))" if pd.notna(value) and col == 'Task Content' else str(value) if pd.notna(value) else ''
+ for col, value in zip(sorted_df_txt.columns, row.values)]
+ f.write('\t'.join(cleaned_row) + '\n')
+
+ # Write the DataFrame to tag-specific folders
+ for tag in all_tags:
+ # Escape special characters in the tag
+ escaped_tag = re.escape(tag.strip())
+ tag_filtered_df = sorted_df[sorted_df['Tags'].str.contains(escaped_tag, na=False)]
+ if not tag_filtered_df.empty:
+ tag_dir = os.path.join(output_dir, tag.strip())
+ os.makedirs(tag_dir, exist_ok=True)
+ tag_csv_output_file = os.path.join(tag_dir, f'{sanitized_value}.csv')
+ tag_txt_output_file = os.path.join(tag_dir, f'{sanitized_value}.txt')
+
+ tag_filtered_df.to_csv(tag_csv_output_file, index=False)
+
+ tag_filtered_df_txt = tag_filtered_df.drop(columns=txt_columns_to_exclude)
+ tag_filtered_df_txt = tag_filtered_df_txt.replace(['NaN', '[]'], '')
+
+ with open(tag_txt_output_file, 'w', encoding='utf-8') as f:
+ for index, row in tag_filtered_df_txt.iterrows():
+ cleaned_row = [f"(({value}))" if pd.notna(value) and col == 'Task Content' else str(value) if pd.notna(value) else ''
+ for col, value in zip(tag_filtered_df_txt.columns, row.values)]
+ f.write('\t'.join(cleaned_row) + '\n')
+
+print(f'Processed {len(df)} rows and saved them as .csv and .txt files in the "{output_dir}" directory and tag-specific subdirectories.')
diff --git a/src/personal_notes/clickup_extract/v3.2.py b/src/personal_notes/clickup_extract/v3.2.py
new file mode 100755
index 0000000..776ea18
--- /dev/null
+++ b/src/personal_notes/clickup_extract/v3.2.py
@@ -0,0 +1,84 @@
+import os
+import pandas as pd
+import re
+
+#output looks like: clean data.
+# NEXT TBD improvement: create a folder for each parent ID, and txt file for each children that has attached parent ID
+
+
+# Preparing clickup exported CSV file for cleaning unnecessary columns
+csv_file = 'clickup10-25.csv' # Replace with the path to your CSV file
+df = pd.read_csv(csv_file)
+
+# Specify the columns you want to delete (excluding "Task ID" and "Parent ID")
+columns_to_delete = ['Attachments', 'Date Created', 'Date Created Text', 'Due Date', 'Due Date Text',
+ 'Start Date', 'Start Date Text', 'Assignees', 'Folder Name', 'Time Estimated',
+ 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent',
+ 'Time Spent Text', 'Rolled Up Time', 'Rolled Up Time Text', 'Priority', 'Status']
+
+# Drop the specified columns from the DataFrame
+df = df.drop(columns=columns_to_delete)
+
+# Sort the DataFrame by the "Space Name" column
+df_sorted = df.sort_values(by='Space Name')
+
+# Get all unique values in the "List Name" column
+unique_values = df_sorted['List Name'].unique()
+
+# Print or use the unique values as needed
+print(unique_values)
+
+# Write the modified DataFrame back to CSV and TXT files
+output_dir = 'output'
+os.makedirs(output_dir, exist_ok=True)
+
+for value in unique_values:
+ # Sanitize the string to replace problematic characters
+ sanitized_value = value.replace('/', '_') # Replace '/' with '_'
+ csv_output_file = os.path.join(output_dir, f'{sanitized_value}.csv')
+ txt_output_file = os.path.join(output_dir, f'{sanitized_value}.txt')
+
+ # Filter the DataFrame based on the current value
+ filtered_df = df[df['List Name'] == value]
+
+ # Drop additional columns from the filtered DataFrame
+ additional_columns_to_drop = ['Space Name', 'List Name'] # Replace with the names of columns you want to drop
+ filtered_df = filtered_df.drop(columns=additional_columns_to_drop)
+
+ # Create a list to hold the final sorted rows
+ sorted_rows = []
+
+ # Function to add tasks and their children
+ def add_task_with_children(task_id):
+ task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist()
+ for row in task_rows:
+ sorted_rows.append(row)
+ child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique()
+ for child_task in child_tasks:
+ add_task_with_children(child_task)
+
+ # Find all tasks that do not have a parent (top-level tasks)
+ top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()]['Task ID'].unique()
+
+ # Add top-level tasks and their children to the sorted rows
+ for task_id in top_level_tasks:
+ add_task_with_children(task_id)
+
+ # Convert the list of sorted rows back to a DataFrame
+ sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns)
+
+ # Write the sorted DataFrame to a CSV file
+ sorted_df.to_csv(csv_output_file, index=False)
+
+ # Write the sorted DataFrame to a TXT file without "Task ID" and "Parent ID" columns and removing NaN and []
+ txt_columns_to_exclude = ['Task ID', 'Parent ID']
+ sorted_df_txt = sorted_df.drop(columns=txt_columns_to_exclude)
+ sorted_df_txt = sorted_df_txt.replace(['NaN', '[]'], '')
+
+ with open(txt_output_file, 'w', encoding='utf-8') as f:
+ for index, row in sorted_df_txt.iterrows():
+ cleaned_row = [f"(({value}))" if pd.notna(value) and col == 'Task Content' else str(value) if pd.notna(value) else ''
+ for col, value in zip(sorted_df_txt.columns, row.values)]
+ f.write('\t'.join(cleaned_row) + '\n')
+
+print(f'Processed {len(df)} rows and saved them as .csv and .txt files in the "{output_dir}" directory.')
diff --git a/src/personal_notes/clickup_extract/v3.3.py b/src/personal_notes/clickup_extract/v3.3.py
new file mode 100755
index 0000000..de6697b
--- /dev/null
+++ b/src/personal_notes/clickup_extract/v3.3.py
@@ -0,0 +1,109 @@
+import os
+import pandas as pd
+import re
+
+#!!!!!!!!!!!!!!!!!!! QUITE neat. for the v3.4 version solve this problem: \n in txt files
+
+# Specify the CSV file path
+csv_file = 'clickup10-25.csv' # Replace with the path to your CSV file
+df = pd.read_csv(csv_file)
+
+# Strip whitespace from column names immediately after loading
+df.columns = df.columns.str.strip()
+
+# Columns to delete (excluding "Task ID" and "Parent ID")
+columns_to_delete = ['Attachments', 'Date Created', 'Date Created Text', 'Due Date', 'Due Date Text',
+ 'Start Date', 'Start Date Text', 'Assignees', 'Folder Name', 'Time Estimated',
+ 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent',
+ 'Time Spent Text', 'Rolled Up Time', 'Rolled Up Time Text', 'Priority', 'Status']
+
+# Drop specified columns
+df = df.drop(columns=columns_to_delete)
+
+# Sort the DataFrame by the "Space Name" column
+df_sorted = df.sort_values(by='Space Name')
+
+# Get unique values in the "List Name" column
+unique_values = df_sorted['List Name'].unique()
+
+# Define output directory
+output_dir = 'output'
+os.makedirs(output_dir, exist_ok=True)
+
+# Define a maximum length for folder and file names (adjust as needed)
+MAX_NAME_LENGTH = 100
+
+# Utility function to sanitize names
+def sanitize_name(name):
+ # Remove invalid characters and truncate
+ sanitized = re.sub(r'[<>:"/\\|?*]', '', str(name))
+ return sanitized[:MAX_NAME_LENGTH] # Truncate if necessary
+
+# Process each unique List Name
+for value in unique_values:
+ sanitized_list_name = sanitize_name(value) # Sanitize List Name
+ list_folder = os.path.join(output_dir, sanitized_list_name)
+ os.makedirs(list_folder, exist_ok=True)
+
+ # Filter DataFrame based on the current List Name value
+ filtered_df = df[df['List Name'] == value]
+
+ # Drop columns 'Space Name' and 'List Name'
+ filtered_df = filtered_df.drop(columns=['Space Name', 'List Name'])
+
+ # Prepare a list to hold sorted rows
+ sorted_rows = []
+
+ # Define function to add tasks and their children
+ def add_task_with_children(task_id):
+ task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist()
+ for row in task_rows:
+ sorted_rows.append(row)
+ child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique()
+ for child_task in child_tasks:
+ add_task_with_children(child_task)
+
+ # Find top-level tasks (no parent) and create folders for each
+ top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()]
+
+ for _, task_row in top_level_tasks.iterrows():
+ task_id = task_row['Task ID']
+ task_name = task_row['Task Name']
+
+ # Sanitize the task name for folder and file naming
+ sanitized_task_name = sanitize_name(task_name) # Sanitize Task Name
+
+ # Create folder for the top-level task using the sanitized name
+ task_folder = os.path.join(list_folder, sanitized_task_name)
+ os.makedirs(task_folder, exist_ok=True)
+
+ # Collect children of the top-level task
+ sorted_rows.clear()
+ add_task_with_children(task_id)
+
+ # Convert to DataFrame
+ sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns)
+
+ # Name the text file based on the parent task's name
+ txt_output_file = os.path.join(task_folder, f'{sanitized_task_name}.txt')
+
+ # Check for filename length and truncate if necessary
+ if len(txt_output_file) > 255:
+ txt_output_file = os.path.join(task_folder, f'{sanitized_task_name[:255]}.txt')
+
+ txt_columns_to_exclude = ['Task ID', 'Parent ID']
+ sorted_df_txt = sorted_df.drop(columns=txt_columns_to_exclude)
+ sorted_df_txt = sorted_df_txt.replace(['NaN', '[]'], '')
+
+ # Write to the text file
+ with open(txt_output_file, 'w', encoding='utf-8') as f:
+ for _, row in sorted_df_txt.iterrows():
+ cleaned_row = [f"(({val}))" if pd.notna(val) and col == 'Task Content' else str(val) if pd.notna(val) else ''
+ for col, val in zip(sorted_df_txt.columns, row.values)]
+ f.write('\t'.join(cleaned_row) + '\n')
+ f.write(" \n") # Add separator line after each child's content
+ f.write(" \n") # Add separator line after each child's content
+ f.write(" \n") # Add separator line after each child's content
+ f.write(" \n") # Add separator line after each child's content
+
+print(f'Processed {len(df)} rows and saved them in the "{output_dir}" directory with the specified folder structure.')
diff --git a/src/personal_notes/clickup_extract/v3.4.0.py b/src/personal_notes/clickup_extract/v3.4.0.py
new file mode 100755
index 0000000..c8b3ddb
--- /dev/null
+++ b/src/personal_notes/clickup_extract/v3.4.0.py
@@ -0,0 +1,117 @@
+import os
+import pandas as pd
+import re
+import glob
+
+# Specify the CSV file path
+csv_file = 'clickup10-25.csv' # Replace with the path to your CSV file
+df = pd.read_csv(csv_file)
+
+# Strip whitespace from column names immediately after loading
+df.columns = df.columns.str.strip()
+
+# Columns to delete (excluding "Task ID" and "Parent ID")
+columns_to_delete = ['Attachments', 'Date Created', 'Date Created Text', 'Due Date', 'Due Date Text',
+ 'Start Date', 'Start Date Text', 'Assignees', 'Folder Name', 'Time Estimated',
+ 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent',
+ 'Time Spent Text', 'Rolled Up Time', 'Rolled Up Time Text', 'Priority', 'Status']
+
+# Drop specified columns
+df = df.drop(columns=columns_to_delete)
+
+# Sort the DataFrame by the "Space Name" column
+df_sorted = df.sort_values(by='Space Name')
+
+# Get unique values in the "List Name" column
+unique_values = df_sorted['List Name'].unique()
+
+# Define output directory
+output_dir = 'output'
+os.makedirs(output_dir, exist_ok=True)
+
+# Define a maximum length for folder and file names (adjust as needed)
+MAX_NAME_LENGTH = 100
+
+# Utility function to sanitize names
+def sanitize_name(name):
+ # Remove invalid characters and truncate
+ sanitized = re.sub(r'[<>:"/\\|?*]', '', str(name))
+ return sanitized[:MAX_NAME_LENGTH] # Truncate if necessary
+
+# Process each unique List Name
+for value in unique_values:
+ sanitized_list_name = sanitize_name(value) # Sanitize List Name
+ list_folder = os.path.join(output_dir, sanitized_list_name)
+ os.makedirs(list_folder, exist_ok=True)
+
+ # Filter DataFrame based on the current List Name value
+ filtered_df = df[df['List Name'] == value]
+
+ # Drop columns 'Space Name' and 'List Name'
+ filtered_df = filtered_df.drop(columns=['Space Name', 'List Name'])
+
+ # Prepare a list to hold sorted rows
+ sorted_rows = []
+
+ # Define function to add tasks and their children
+ def add_task_with_children(task_id):
+ task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist()
+ for row in task_rows:
+ sorted_rows.append(row)
+ child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique()
+ for child_task in child_tasks:
+ add_task_with_children(child_task)
+
+ # Find top-level tasks (no parent) and create folders for each
+ top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()]
+
+ for _, task_row in top_level_tasks.iterrows():
+ task_id = task_row['Task ID']
+ task_name = task_row['Task Name']
+
+ # Sanitize the task name for folder and file naming
+ sanitized_task_name = sanitize_name(task_name) # Sanitize Task Name
+
+ # Create folder for the top-level task using the sanitized name
+ task_folder = os.path.join(list_folder, sanitized_task_name)
+ os.makedirs(task_folder, exist_ok=True)
+
+ # Collect children of the top-level task
+ sorted_rows.clear()
+ add_task_with_children(task_id)
+
+ # Convert to DataFrame
+ sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns)
+
+ # Name the text file based on the parent task's name
+ txt_output_file = os.path.join(task_folder, f'{sanitized_task_name}.txt')
+
+ # Check for filename length and truncate if necessary
+ if len(txt_output_file) > 255:
+ txt_output_file = os.path.join(task_folder, f'{sanitized_task_name[:255]}.txt')
+
+ txt_columns_to_exclude = ['Task ID', 'Parent ID']
+ sorted_df_txt = sorted_df.drop(columns=txt_columns_to_exclude)
+
+ # Clean up specific values without regex
+ sorted_df_txt = sorted_df_txt.replace(['NaN', '[]'], '')
+
+ # Remove any newline characters or multiple consecutive newlines that may still be present in each column
+ sorted_df_txt = sorted_df_txt.apply(
+ lambda col: col.map(lambda x: re.sub(r'\n+', ' ', str(x)) if pd.notna(x) else x)
+ )
+
+
+ # Write to the text file
+ with open(txt_output_file, 'w', encoding='utf-8') as f:
+ for _, row in sorted_df_txt.iterrows():
+ cleaned_row = [f"(({val}))" if pd.notna(val) and col == 'Task Content' else str(val) if pd.notna(val) else ''
+ for col, val in zip(sorted_df_txt.columns, row.values)]
+ f.write('\t'.join(cleaned_row) + '\n')
+
+ # Add separator line after each child's content for clarity
+ f.write(" \n") # Add separator line after each child's content
+ f.write(" \n") # Add separator line after each child's content
+ f.write(" \n") # Add separator line after each child's content
+ f.write(" \n") # Add separator line after each child's content
+print(f'Processed {len(df)} rows and saved them in the "{output_dir}" directory with the specified folder structure.')
diff --git a/src/personal_notes/clickup_extract/v3.4.1.ALPHA.py b/src/personal_notes/clickup_extract/v3.4.1.ALPHA.py
new file mode 100755
index 0000000..c6f54e2
--- /dev/null
+++ b/src/personal_notes/clickup_extract/v3.4.1.ALPHA.py
@@ -0,0 +1,119 @@
+import os
+import pandas as pd
+import re
+import glob
+
+# Specify the CSV file path
+csv_file = 'clickup10-25.csv' # Replace with the path to your CSV file
+df = pd.read_csv(csv_file)
+
+# Strip whitespace from column names immediately after loading
+df.columns = df.columns.str.strip()
+
+# Columns to delete (excluding "Task ID" and "Parent ID")
+columns_to_delete = ['Attachments', 'Date Created', 'Date Created Text', 'Due Date', 'Due Date Text',
+ 'Start Date', 'Start Date Text', 'Assignees', 'Folder Name', 'Time Estimated',
+ 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent',
+ 'Time Spent Text', 'Rolled Up Time', 'Rolled Up Time Text', 'Priority', 'Status']
+
+# Drop specified columns
+df = df.drop(columns=columns_to_delete)
+
+# Sort the DataFrame by the "Space Name" column
+df_sorted = df.sort_values(by='Space Name')
+
+# Get unique values in the "List Name" column
+unique_values = df_sorted['List Name'].unique()
+
+# Define output directory
+output_dir = 'output'
+os.makedirs(output_dir, exist_ok=True)
+
+# Define a maximum length for folder and file names (adjust as needed)
+MAX_NAME_LENGTH = 100
+
+# Utility function to sanitize names
+def sanitize_name(name):
+ # Remove invalid characters and truncate
+ sanitized = re.sub(r'[<>:"/\\|?*]', '', str(name)).strip() # Remove invalid chars and leading/trailing whitespace
+ sanitized = sanitized.rstrip('.') # Remove trailing dots for cross-platform compatibility
+ return sanitized[:MAX_NAME_LENGTH] # Truncate if necessary
+
+
+# Process each unique List Name
+for value in unique_values:
+ sanitized_list_name = sanitize_name(value) # Sanitize List Name
+ list_folder = os.path.join(output_dir, sanitized_list_name)
+ os.makedirs(list_folder, exist_ok=True)
+
+ # Filter DataFrame based on the current List Name value
+ filtered_df = df[df['List Name'] == value]
+
+ # Drop columns 'Space Name' and 'List Name'
+ filtered_df = filtered_df.drop(columns=['Space Name', 'List Name'])
+
+ # Prepare a list to hold sorted rows
+ sorted_rows = []
+
+ # Define function to add tasks and their children
+ def add_task_with_children(task_id):
+ task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist()
+ for row in task_rows:
+ sorted_rows.append(row)
+ child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique()
+ for child_task in child_tasks:
+ add_task_with_children(child_task)
+
+ # Find top-level tasks (no parent) and create folders for each
+ top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()]
+
+ for _, task_row in top_level_tasks.iterrows():
+ task_id = task_row['Task ID']
+ task_name = task_row['Task Name']
+
+ # Sanitize the task name for folder and file naming
+ sanitized_task_name = sanitize_name(task_name) # Sanitize Task Name
+
+ # Create folder for the top-level task using the sanitized name
+ task_folder = os.path.join(list_folder, sanitized_task_name)
+ os.makedirs(task_folder, exist_ok=True)
+
+ # Collect children of the top-level task
+ sorted_rows.clear()
+ add_task_with_children(task_id)
+
+ # Convert to DataFrame
+ sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns)
+
+ # Name the text file based on the parent task's name
+ txt_output_file = os.path.join(task_folder, f'{sanitized_task_name}.txt')
+
+ # Check for filename length and truncate if necessary
+ if len(txt_output_file) > 255:
+ txt_output_file = os.path.join(task_folder, f'{sanitized_task_name[:255]}.txt')
+
+ txt_columns_to_exclude = ['Task ID', 'Parent ID']
+ sorted_df_txt = sorted_df.drop(columns=txt_columns_to_exclude)
+
+ # Clean up specific values without regex
+ sorted_df_txt = sorted_df_txt.replace(['NaN', '[]'], '')
+
+ # Remove any newline characters or multiple consecutive newlines that may still be present in each column
+ sorted_df_txt = sorted_df_txt.apply(
+ lambda col: col.map(lambda x: re.sub(r'\n+', ' ', str(x)) if pd.notna(x) else x)
+ )
+
+
+ # Write to the text file
+ with open(txt_output_file, 'w', encoding='utf-8') as f:
+ for _, row in sorted_df_txt.iterrows():
+ cleaned_row = [f"(({val}))" if pd.notna(val) and col == 'Task Content' else str(val) if pd.notna(val) else ''
+ for col, val in zip(sorted_df_txt.columns, row.values)]
+ f.write('\t'.join(cleaned_row) + '\n')
+
+ # Add separator line after each child's content for clarity
+ f.write(" \n") # Add separator line after each child's content
+ f.write(" \n") # Add separator line after each child's content
+ f.write(" \n") # Add separator line after each child's content
+ f.write(" \n") # Add separator line after each child's content
+print(f'Processed {len(df)} rows and saved them in the "{output_dir}" directory with the specified folder structure.')
diff --git a/src/personal_notes/clickup_extract/v4.1.1.ALPHA.py b/src/personal_notes/clickup_extract/v4.1.1.ALPHA.py
new file mode 100755
index 0000000..df125dd
--- /dev/null
+++ b/src/personal_notes/clickup_extract/v4.1.1.ALPHA.py
@@ -0,0 +1,118 @@
+import os
+import pandas as pd
+import re
+import glob
+
+#TBD: radau neatitikima: sub_task folderiuose esantys txt failai turi ne tik sau priklausanti turini, bet ir tarsi i juos appendintas is kitu sub_tasku turinys
+
+# Specify the CSV file path
+csv_file = 'clickup10-25.csv' # Replace with the path to your CSV file
+df = pd.read_csv(csv_file)
+
+# Strip whitespace from column names immediately after loading
+df.columns = df.columns.str.strip()
+
+# Columns to delete (excluding "Task ID" and "Parent ID")
+columns_to_delete = ['Attachments', 'Date Created', 'Date Created Text', 'Due Date', 'Due Date Text',
+ 'Start Date', 'Start Date Text', 'Assignees', 'Folder Name', 'Time Estimated',
+ 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent',
+ 'Time Spent Text', 'Rolled Up Time', 'Rolled Up Time Text', 'Priority', 'Status']
+
+# Drop specified columns
+df = df.drop(columns=columns_to_delete)
+
+# Sort the DataFrame by the "Space Name" column
+df_sorted = df.sort_values(by='Space Name')
+
+# Get unique values in the "List Name" column
+unique_values = df_sorted['List Name'].unique()
+
+# Define output directory
+output_dir = 'output'
+os.makedirs(output_dir, exist_ok=True)
+
+# Define a maximum length for folder and file names (adjust as needed)
+MAX_NAME_LENGTH = 100
+
+# Utility function to sanitize names
+def sanitize_name(name):
+ # Remove invalid characters and truncate
+ sanitized = re.sub(r'[<>:"/\\|?*]', '', str(name)).strip() # Remove invalid chars and leading/trailing whitespace
+ sanitized = sanitized.rstrip('.') # Remove trailing dots for cross-platform compatibility
+ sanitized = sanitized.replace('.', '') # Remove all periods for compatibility
+ return sanitized[:MAX_NAME_LENGTH] # Truncate if necessary
+
+
+# Process each unique List Name
+for value in unique_values:
+ sanitized_list_name = sanitize_name(value) # Sanitize List Name
+ list_folder = os.path.join(output_dir, sanitized_list_name)
+ os.makedirs(list_folder, exist_ok=True)
+
+ # Filter DataFrame based on the current List Name value
+ filtered_df = df[df['List Name'] == value]
+
+ # Drop columns 'Space Name' and 'List Name'
+ filtered_df = filtered_df.drop(columns=['Space Name', 'List Name'])
+
+ # Prepare a list to hold sorted rows
+ sorted_rows = []
+
+ # Define function to add tasks and their children
+ def add_task_with_children(task_id, parent_folder, is_child=False):
+ task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist()
+ for row in task_rows:
+ sorted_rows.append(row)
+ task_name = row[filtered_df.columns.get_loc("Task Name")]
+ sanitized_task_name = sanitize_name(task_name) # Sanitize Task Name
+
+ # If it's a child task, create a subfolder; if it's top-level, use the parent_folder directly
+ if is_child:
+ subtask_folder = os.path.join(parent_folder, sanitized_task_name)
+ os.makedirs(subtask_folder, exist_ok=True)
+ target_folder = subtask_folder
+ else:
+ target_folder = parent_folder
+
+ # Collect children of the current task
+ child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique()
+ for child_task in child_tasks:
+ add_task_with_children(child_task, target_folder, is_child=True)
+
+ # Create and write to the text file inside the target folder
+ sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns)
+ txt_output_file = os.path.join(target_folder, f'{sanitized_task_name}.txt')
+
+ # Exclude specific columns and clean the content
+ txt_columns_to_exclude = ['Task ID', 'Parent ID']
+ sorted_df_txt = sorted_df.drop(columns=txt_columns_to_exclude)
+ sorted_df_txt = sorted_df_txt.replace(['NaN', '[]'], '')
+ sorted_df_txt = sorted_df_txt.apply(
+ lambda col: col.map(lambda x: re.sub(r'\n+', ' ', str(x)) if pd.notna(x) else x)
+ )
+
+ with open(txt_output_file, 'w', encoding='utf-8') as f:
+ for _, row in sorted_df_txt.iterrows():
+ cleaned_row = [f"(({val}))" if pd.notna(val) and col == 'Task Content' else str(val) if pd.notna(val) else ''
+ for col, val in zip(sorted_df_txt.columns, row.values)]
+ f.write('\t'.join(cleaned_row) + '\n')
+ # Separator lines for clarity
+ f.write(" \n") # Add separator line for clarity
+
+ # Find top-level tasks (no parent) and create folders for each
+ top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()]
+ for _, task_row in top_level_tasks.iterrows():
+ task_id = task_row['Task ID']
+ task_name = task_row['Task Name']
+
+ # Sanitize and create folder for the top-level task
+ sanitized_task_name = sanitize_name(task_name)
+ task_folder = os.path.join(list_folder, sanitized_task_name)
+ os.makedirs(task_folder, exist_ok=True)
+
+ # Add tasks with the new folder structure
+ sorted_rows.clear()
+ add_task_with_children(task_id, task_folder)
+
+print(f'Processed {len(df)} rows and saved them in the "{output_dir}" directory with the specified folder structure.')
+
diff --git a/src/personal_notes/clickup_extract/v4.1.2.FINAL.py b/src/personal_notes/clickup_extract/v4.1.2.FINAL.py
new file mode 100755
index 0000000..d30a0d7
--- /dev/null
+++ b/src/personal_notes/clickup_extract/v4.1.2.FINAL.py
@@ -0,0 +1,119 @@
+import os
+import pandas as pd
+import re
+import glob
+
+#TBD: radau neatitikima: sub_task folderiuose esantys txt failai turi ne tik sau priklausanti turini, bet ir tarsi i juos appendintas is kitu sub_tasku turinys
+
+# Specify the CSV file path
+csv_file = 'clickup10-25.csv' # Replace with the path to your CSV file
+df = pd.read_csv(csv_file)
+
+# Strip whitespace from column names immediately after loading
+df.columns = df.columns.str.strip()
+
+# Columns to delete (excluding "Task ID" and "Parent ID")
+columns_to_delete = ['Attachments', 'Date Created', 'Date Created Text', 'Due Date', 'Due Date Text',
+ 'Start Date', 'Start Date Text', 'Assignees', 'Folder Name', 'Time Estimated',
+ 'Time Estimated Text', 'Checklists', 'Comments', 'Assigned Comments', 'Time Spent',
+ 'Time Spent Text', 'Rolled Up Time', 'Rolled Up Time Text', 'Priority', 'Status']
+
+# Drop specified columns
+df = df.drop(columns=columns_to_delete)
+
+# Sort the DataFrame by the "Space Name" column
+df_sorted = df.sort_values(by='Space Name')
+
+# Get unique values in the "List Name" column
+unique_values = df_sorted['List Name'].unique()
+
+# Define output directory
+output_dir = 'output'
+os.makedirs(output_dir, exist_ok=True)
+
+# Define a maximum length for folder and file names (adjust as needed)
+MAX_NAME_LENGTH = 100
+
+# Utility function to sanitize names
+def sanitize_name(name):
+ # Remove invalid characters and truncate
+ sanitized = re.sub(r'[<>:"/\\|?*]', '', str(name)).strip() # Remove invalid chars and leading/trailing whitespace
+ sanitized = sanitized.rstrip('.') # Remove trailing dots for cross-platform compatibility
+ sanitized = sanitized.replace('.', '') # Remove all periods for compatibility
+ return sanitized[:MAX_NAME_LENGTH] # Truncate if necessary
+
+
+# Process each unique List Name
+for value in unique_values:
+ sanitized_list_name = sanitize_name(value) # Sanitize List Name
+ list_folder = os.path.join(output_dir, sanitized_list_name)
+ os.makedirs(list_folder, exist_ok=True)
+
+ # Filter DataFrame based on the current List Name value
+ filtered_df = df[df['List Name'] == value]
+
+ # Drop columns 'Space Name' and 'List Name'
+ filtered_df = filtered_df.drop(columns=['Space Name', 'List Name'])
+
+ # Define function to add tasks and their children
+ def add_task_with_children(task_id, parent_folder, is_child=False):
+ # Clear sorted_rows to isolate data for each task or subtask
+ sorted_rows = []
+
+ # Collect rows for the current task
+ task_rows = filtered_df[filtered_df['Task ID'] == task_id].values.tolist()
+ for row in task_rows:
+ sorted_rows.append(row)
+ task_name = row[filtered_df.columns.get_loc("Task Name")]
+ sanitized_task_name = sanitize_name(task_name) # Sanitize Task Name
+
+ # If it's a child task, create a subfolder; if it's top-level, use the parent_folder directly
+ if is_child:
+ subtask_folder = os.path.join(parent_folder, sanitized_task_name)
+ os.makedirs(subtask_folder, exist_ok=True)
+ target_folder = subtask_folder
+ else:
+ target_folder = parent_folder
+
+ # Collect children of the current task
+ child_tasks = filtered_df[filtered_df['Parent ID'] == task_id]['Task ID'].unique()
+ for child_task in child_tasks:
+ add_task_with_children(child_task, target_folder, is_child=True)
+
+ # Convert to DataFrame
+ sorted_df = pd.DataFrame(sorted_rows, columns=filtered_df.columns)
+ txt_output_file = os.path.join(target_folder, f'{sanitized_task_name}.txt')
+
+ # Exclude specific columns and clean the content
+ txt_columns_to_exclude = ['Task ID', 'Parent ID']
+ sorted_df_txt = sorted_df.drop(columns=txt_columns_to_exclude)
+ sorted_df_txt = sorted_df_txt.replace(['NaN', '[]'], '')
+ sorted_df_txt = sorted_df_txt.apply(
+ lambda col: col.map(lambda x: re.sub(r'\n+', ' ', str(x)) if pd.notna(x) else x)
+ )
+
+ # Write data for the current task/subtask to its respective text file
+ with open(txt_output_file, 'w', encoding='utf-8') as f:
+ for _, row in sorted_df_txt.iterrows():
+ cleaned_row = [f"(({val}))" if pd.notna(val) and col == 'Task Content' else str(val) if pd.notna(val) else ''
+ for col, val in zip(sorted_df_txt.columns, row.values)]
+ f.write('\t'.join(cleaned_row) + '\n')
+ # Separator lines for clarity
+ f.write(" \n") # Add separator line for clarity
+
+ # Find top-level tasks (no parent) and create folders for each
+ top_level_tasks = filtered_df[filtered_df['Parent ID'].isna()]
+ for _, task_row in top_level_tasks.iterrows():
+ task_id = task_row['Task ID']
+ task_name = task_row['Task Name']
+
+ # Sanitize and create folder for the top-level task
+ sanitized_task_name = sanitize_name(task_name)
+ task_folder = os.path.join(list_folder, sanitized_task_name)
+ os.makedirs(task_folder, exist_ok=True)
+
+ # Add tasks with the new folder structure
+ add_task_with_children(task_id, task_folder)
+
+print(f'Processed {len(df)} rows and saved them in the "{output_dir}" directory with the specified folder structure.')
+
diff --git a/src/personal_notes/discord_extract/__pycache__/json.cpython-311.pyc b/src/personal_notes/discord_extract/__pycache__/json.cpython-311.pyc
new file mode 100755
index 0000000..b6d1902
Binary files /dev/null and b/src/personal_notes/discord_extract/__pycache__/json.cpython-311.pyc differ
diff --git a/src/personal_notes/discord_extract/__pycache__/responses.cpython-311.pyc b/src/personal_notes/discord_extract/__pycache__/responses.cpython-311.pyc
new file mode 100755
index 0000000..55fc46c
Binary files /dev/null and b/src/personal_notes/discord_extract/__pycache__/responses.cpython-311.pyc differ
diff --git a/src/personal_notes/discord_extract/cleaning.py b/src/personal_notes/discord_extract/cleaning.py
new file mode 100755
index 0000000..093d4e9
--- /dev/null
+++ b/src/personal_notes/discord_extract/cleaning.py
@@ -0,0 +1,49 @@
+import json
+import pandas as pd
+
+"""
+# After posts are scraped, time to look for duplicates and remove them:
+def remove_duplicates(json_file):
+ # Read the contents of the JSON file into a list
+ with open(json_file, 'r') as file:
+ lines = file.readlines()
+
+ # Initialize lists to store message data
+ ids = []
+ channels = []
+ contents = []
+ timestamps = []
+
+ # Extract message data from each line in the JSON file
+ for line in lines:
+ message = json.loads(line)
+ message_id = next(iter(message)) # Get the message ID
+ ids.append(message_id)
+ channels.append(message[message_id]['channel'])
+ contents.append(message[message_id]['content'])
+ timestamps.append(message[message_id]['timestamp'])
+
+ # Create a DataFrame from the extracted data
+ df = pd.DataFrame({'id': ids, 'channel': channels, 'content': contents, 'timestamp': timestamps})
+
+ # Drop duplicates based on message ID
+ clean_df = df.drop_duplicates(subset=['id'])
+
+ # Open the new file to save cleaned messages
+ with open('clean_messages.json', 'w') as file:
+ # Serialize each message individually and write them to the file
+ for _, message_row in clean_df.iterrows():
+ message_data = {
+ 'id': message_row['id'],
+ 'channel': message_row['channel'],
+ 'content': message_row['content'],
+ 'timestamp': message_row['timestamp']
+ }
+ json.dump(message_data, file)
+ file.write('\n') # Add newline for readability
+
+# Example usage:
+remove_duplicates('messages.json')
+"""
+
+# working with final data
\ No newline at end of file
diff --git a/src/personal_notes/discord_extract/disc_del_channels.py b/src/personal_notes/discord_extract/disc_del_channels.py
new file mode 100755
index 0000000..a27ca6b
--- /dev/null
+++ b/src/personal_notes/discord_extract/disc_del_channels.py
@@ -0,0 +1,94 @@
+import os
+from dotenv import load_dotenv
+import discord
+from discord import Intents, Client
+from discord.errors import Forbidden
+import asyncio
+
+#instead of deleting posts, it's more efficient to delete channels and re-create them. !!! Manually insert category name in the code
+
+# Load bot token from .env file
+load_dotenv()
+TOKEN = os.getenv('DISCORD_TOKEN')
+
+# Set up intents
+intents = Intents.default()
+intents.messages = True
+
+# Define permission flags
+PERMISSION_READ_MESSAGES = 1 << 6 # 65536 uneccessary?
+PERMISSION_MANAGE_MESSAGES = 1 << 13 # 8192 uneccessary?
+PERMISSION_READ_MESSAGE_HISTORY = 1 << 11 # 73728 uneccessary?
+PERMISSION_MANAGE_CHANNELS = 1 << 4 # 16
+
+# Calculate permissions integer
+permissions = (
+ PERMISSION_READ_MESSAGES | PERMISSION_MANAGE_MESSAGES |
+ PERMISSION_READ_MESSAGE_HISTORY | PERMISSION_MANAGE_CHANNELS
+)
+
+# Initialize the Discord client with permissions
+client = Client(intents=intents, permissions=permissions)
+
+# Dictionary to store deleted channel names and their positions in the category
+deleted_channels = {}
+
+async def delete_and_recreate_channels(category):
+ global deleted_channels
+ try:
+ # Fetch the channels in the specified category
+ channels = category.channels
+ for channel in channels:
+ # Store the channel name and its position before deleting
+ deleted_channels[channel.name] = channel.position
+
+ # Delete the channel
+ await channel.delete()
+ except Forbidden as e:
+ print(f"Permission error: {e}")
+ except discord.HTTPException as e:
+ if e.status == 429:
+ retry_after = e.retry_after
+ print(f"We are being rate limited. Retrying in {retry_after} seconds.")
+ await asyncio.sleep(retry_after)
+ await delete_and_recreate_channels(category)
+ else:
+ print(f"HTTP error: {e}")
+ except Exception as e:
+ print(f"An error occurred: {e}")
+
+async def recreate_channels(category):
+ global deleted_channels
+ try:
+ # Iterate over the deleted channel names and positions
+ for channel_name, position in deleted_channels.items():
+ # Create the channel in the specified category
+ await category.create_text_channel(name=channel_name, position=position)
+ except Forbidden as e:
+ print(f"Permission error: {e}")
+ except discord.HTTPException as e:
+ if e.status == 429:
+ retry_after = e.retry_after
+ print(f"We are being rate limited. Retrying in {retry_after} seconds.")
+ await asyncio.sleep(retry_after)
+ await recreate_channels(category)
+ else:
+ print(f"HTTP error: {e}")
+ except Exception as e:
+ print(f"An error occurred: {e}")
+
+@client.event
+async def on_ready():
+ print(f'{client.user} has connected to Discord!')
+ print('Monitoring channels...')
+ for guild in client.guilds:
+ print(f'Guild: {guild.name}')
+ for category in guild.categories:
+ if category.name == 'Challenges for growth': # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Manually insert category name in the code
+ print(f'Category: {category.name}')
+ await delete_and_recreate_channels(category)
+ await recreate_channels(category)
+ await client.close()
+
+# Run the bot
+client.run(TOKEN)
diff --git a/src/personal_notes/discord_extract/disc_del_only.py b/src/personal_notes/discord_extract/disc_del_only.py
new file mode 100755
index 0000000..176c2bc
--- /dev/null
+++ b/src/personal_notes/discord_extract/disc_del_only.py
@@ -0,0 +1,64 @@
+import os
+from dotenv import load_dotenv
+import discord
+from discord import Intents, Client
+from discord.errors import Forbidden
+import asyncio
+
+# Load bot token from .env file
+load_dotenv()
+TOKEN = os.getenv('DISCORD_TOKEN')
+
+# Set up intents
+intents = Intents.default()
+intents.messages = True
+
+# Define permission flags
+PERMISSION_READ_MESSAGES = 1 << 6 # 65536 uneccessary?
+PERMISSION_MANAGE_MESSAGES = 1 << 13 # 8192 uneccessary?
+PERMISSION_READ_MESSAGE_HISTORY = 1 << 11 # 73728 uneccessary?
+PERMISSION_MANAGE_CHANNELS = 1 << 4 # 16
+
+# Calculate permissions integer
+permissions = (
+ PERMISSION_READ_MESSAGES | PERMISSION_MANAGE_MESSAGES |
+ PERMISSION_READ_MESSAGE_HISTORY | PERMISSION_MANAGE_CHANNELS
+)
+
+# Initialize the Discord client with permissions
+client = Client(intents=intents, permissions=permissions)
+
+async def delete_channels(category):
+ try:
+ # Fetch the channels in the specified category
+ channels = category.channels
+ for channel in channels:
+ # Delete the channel
+ await channel.delete()
+ except Forbidden as e:
+ print(f"Permission error: {e}")
+ except discord.HTTPException as e:
+ if e.status == 429:
+ retry_after = e.retry_after
+ print(f"We are being rate limited. Retrying in {retry_after} seconds.")
+ await asyncio.sleep(retry_after)
+ await delete_channels(category)
+ else:
+ print(f"HTTP error: {e}")
+ except Exception as e:
+ print(f"An error occurred: {e}")
+
+@client.event
+async def on_ready():
+ print(f'{client.user} has connected to Discord!')
+ print('Monitoring channels...')
+ for guild in client.guilds:
+ print(f'Guild: {guild.name}')
+ for category in guild.categories:
+ if category.name == 'Noble': # Manually insert category name in the code
+ print(f'Category: {category.name}')
+ await delete_channels(category)
+ await client.close()
+
+# Run the bot
+client.run(TOKEN)
diff --git a/src/personal_notes/discord_extract/disc_extract_c.py b/src/personal_notes/discord_extract/disc_extract_c.py
new file mode 100755
index 0000000..11051c9
--- /dev/null
+++ b/src/personal_notes/discord_extract/disc_extract_c.py
@@ -0,0 +1,181 @@
+import os
+import discord
+import json
+import asyncio
+from dotenv import load_dotenv
+from discord import Intents, Client
+from discord.errors import Forbidden, HTTPException
+import pandas as pd
+from collections import deque
+import time
+
+#this bot scrapes chosen categories. !!! manually choose categories
+
+# Load bot token from .env file
+load_dotenv()
+TOKEN = os.getenv('DISCORD_TOKEN')
+
+# Set up intents
+intents = Intents.default()
+intents.messages = True
+
+# Define permission flags
+PERMISSION_READ_MESSAGES = 1 << 6 # 65536
+PERMISSION_MANAGE_MESSAGES = 1 << 13 # 8192
+PERMISSION_READ_MESSAGE_HISTORY = 1 << 11 # 73728
+
+# Calculate permissions integer
+permissions = PERMISSION_READ_MESSAGES | PERMISSION_MANAGE_MESSAGES | PERMISSION_READ_MESSAGE_HISTORY
+
+# Initialize the Discord client with permissions
+client = Client(intents=intents, permissions=permissions)
+
+# Define rate limit settings
+RATE_LIMIT_THRESHOLD = 3 # Maximum number of requests per second
+RATE_LIMIT_BUCKET = RATE_LIMIT_THRESHOLD # Initial token bucket capacity
+categories_to_monitor = ['Aktualijos'] #!!! manually choose categories
+
+# Initialize request counter
+request_count = 0
+
+async def monitor_channel(channel):
+ global RATE_LIMIT_BUCKET
+ global request_count
+ try:
+ # Increment request count for each request
+ request_count += 1
+
+ # Check if there are enough tokens in the bucket
+ if RATE_LIMIT_BUCKET > 0:
+ # Make the request
+ async for msg in channel.history(limit=None):
+ # Create a dictionary to store message data
+ message_data = {
+ 'id': msg.id,
+ 'category': channel.category.name,
+ 'channel': channel.name,
+ 'content': msg.content,
+ 'timestamp': msg.created_at.strftime('%Y-%m-%d %H:%M')
+ }
+
+ # Serialize message data into a string
+ message_str = json.dumps({msg.id: message_data})
+
+ # Save message data to the file
+ with open('messages.json', 'a') as file:
+ file.write(message_str + '\n') # Write message and add newline for readability
+
+ # Decrement the token bucket
+ RATE_LIMIT_BUCKET -= 1
+ else:
+ # Wait until tokens refill in the bucket
+ await asyncio.sleep(1)
+ RATE_LIMIT_BUCKET = RATE_LIMIT_THRESHOLD # Refill the token bucket
+ await monitor_channel(channel)
+
+ except Forbidden as e:
+ print(f"Permission error: {e}")
+ except HTTPException as e:
+ # Check if the exception is due to rate limiting
+ if e.status == 429:
+ retry_after = e.retry_after #testing
+ print(f"We are being rate limited. We could retry in {retry_after} seconds.") #testing
+ print(f"We are being rate limited. Exiting for now.")
+ await client.close() # Exit the program
+ else:
+ print(f"HTTP error: {e}")
+ except Exception as e:
+ print(f"An error occurred: {e}")
+
+@client.event
+async def on_ready():
+ global request_count
+ print(f'{client.user} has connected to Discord!')
+ print('Monitoring channels...')
+
+ # Record the start time
+ start_time = time.time()
+
+ for guild in client.guilds:
+ print(f'Guild: {guild.name}')
+ for category in guild.categories:
+ if category.name in categories_to_monitor:
+ print(f'Category: {category.name}')
+ for channel in category.channels:
+ if isinstance(channel, discord.TextChannel):
+ await monitor_channel(channel)
+
+ # Calculate and display the elapsed time
+ elapsed_time = time.time() - start_time
+ print(f"Total requests made: {request_count}")
+ print(f"Total time elapsed: {elapsed_time} seconds")
+
+ await client.close()
+
+# Run the bot
+client.run(TOKEN)
+
+
+# After posts are scraped, time to look for duplicates and remove them:
+def remove_duplicates(json_file):
+ # Read the contents of the JSON file into a list
+ with open(json_file, 'r') as file:
+ lines = file.readlines()
+
+ # Initialize lists to store message data
+ ids = []
+ category = []
+ channels = []
+ contents = []
+ timestamps = []
+
+ # Extract message data from each line in the JSON file
+ for line in lines:
+ message = json.loads(line)
+ message_id = next(iter(message)) # Get the message ID
+ ids.append(message_id)
+ category.append(message[message_id]['category'])
+ channels.append(message[message_id]['channel'])
+ contents.append(message[message_id]['content'])
+ timestamps.append(message[message_id]['timestamp'])
+
+ # Create a DataFrame from the extracted data
+ df = pd.DataFrame({'id': ids, 'category': category, 'channel': channels, 'content': contents, 'timestamp': timestamps})
+
+ # Drop duplicates based on message ID
+ clean_df = df.drop_duplicates(subset=['id'])
+
+ # Open the file to append cleaned messages
+ # Set to store unique message IDs encountered so far
+ unique_message_ids = set()
+
+ # Open the file to append cleaned messages
+ with open('clean_messages.json', 'a') as file:
+ # Read existing messages to populate unique_message_ids set
+ with open('clean_messages.json', 'r') as existing_file:
+ for line in existing_file:
+ try:
+ existing_message = json.loads(line)
+ unique_message_ids.add(existing_message['id'])
+ except json.JSONDecodeError as e:
+ print(f"Error loading JSON data: {e}. Skipping this line and continuing.")
+
+ # Serialize each message individually and write them to the file if the ID is not already present
+ for _, message_row in clean_df.iterrows():
+ message_id = message_row['id']
+ if message_id not in unique_message_ids:
+ message_data = {
+ 'id': message_id,
+ 'category': message_row['category'],
+ 'channel': message_row['channel'],
+ 'content': message_row['content'],
+ 'timestamp': message_row['timestamp']
+ }
+ json.dump(message_data, file)
+ file.write('\n') # Add newline for readability
+ unique_message_ids.add(message_id) # Add the ID to the set of unique IDs
+
+
+
+# Example usage:
+remove_duplicates('messages.json')
diff --git a/src/personal_notes/discord_extract/disc_extract_s.py b/src/personal_notes/discord_extract/disc_extract_s.py
new file mode 100755
index 0000000..457106e
--- /dev/null
+++ b/src/personal_notes/discord_extract/disc_extract_s.py
@@ -0,0 +1,177 @@
+import os
+import discord
+import json
+import asyncio
+from dotenv import load_dotenv
+from discord import Intents, Client
+from discord.errors import Forbidden, HTTPException
+import pandas as pd
+from collections import deque
+import time
+
+#this bot scrapes whole server
+
+# Load bot token from .env file
+load_dotenv()
+TOKEN = os.getenv('DISCORD_TOKEN')
+
+# Set up intents
+intents = Intents.default()
+intents.messages = True
+
+# Define permission flags
+PERMISSION_READ_MESSAGES = 1 << 6 # 65536
+PERMISSION_MANAGE_MESSAGES = 1 << 13 # 8192
+PERMISSION_READ_MESSAGE_HISTORY = 1 << 11 # 73728
+
+# Calculate permissions integer
+permissions = PERMISSION_READ_MESSAGES | PERMISSION_MANAGE_MESSAGES | PERMISSION_READ_MESSAGE_HISTORY
+
+# Initialize the Discord client with permissions
+client = Client(intents=intents, permissions=permissions)
+
+# Define rate limit settings
+RATE_LIMIT_THRESHOLD = 3 # Maximum number of requests per second
+RATE_LIMIT_BUCKET = RATE_LIMIT_THRESHOLD # Initial token bucket capacity
+
+# Initialize request counter
+request_count = 0
+
+async def monitor_channel(channel):
+ global RATE_LIMIT_BUCKET
+ global request_count
+ try:
+ # Increment request count for each request
+ request_count += 1
+
+ # Check if there are enough tokens in the bucket
+ if RATE_LIMIT_BUCKET > 0:
+ # Make the request
+ async for msg in channel.history(limit=None):
+ # Create a dictionary to store message data
+ message_data = {
+ 'id': msg.id,
+ 'category': channel.category.name,
+ 'channel': channel.name,
+ 'content': msg.content,
+ 'timestamp': msg.created_at.strftime('%Y-%m-%d %H:%M')
+ }
+
+ # Serialize message data into a string
+ message_str = json.dumps({msg.id: message_data})
+
+ # Save message data to the file
+ with open('messages.json', 'a') as file:
+ file.write(message_str + '\n') # Write message and add newline for readability
+
+ # Decrement the token bucket
+ RATE_LIMIT_BUCKET -= 1
+ else:
+ # Wait until tokens refill in the bucket
+ await asyncio.sleep(1)
+ RATE_LIMIT_BUCKET = RATE_LIMIT_THRESHOLD # Refill the token bucket
+ await monitor_channel(channel)
+
+ except Forbidden as e:
+ print(f"Permission error: {e}")
+ except HTTPException as e:
+ # Check if the exception is due to rate limiting
+ if e.status == 429:
+ retry_after = e.retry_after #testing
+ print(f"We are being rate limited. We could retry in {retry_after} seconds.") #testing
+ print(f"We are being rate limited. Exiting for now.")
+ await client.close() # Exit the program
+ else:
+ print(f"HTTP error: {e}")
+ except Exception as e:
+ print(f"An error occurred: {e}")
+
+@client.event
+async def on_ready():
+ global request_count
+ print(f'{client.user} has connected to Discord!')
+ print('Monitoring channels...')
+
+ # Record the start time
+ start_time = time.time()
+
+ for guild in client.guilds:
+ print(f'Guild: {guild.name}')
+ for channel in guild.channels:
+ if isinstance(channel, discord.TextChannel):
+ await monitor_channel(channel)
+
+ # Calculate and display the elapsed time
+ elapsed_time = time.time() - start_time
+ print(f"Total requests made: {request_count}")
+ print(f"Total time elapsed: {elapsed_time} seconds")
+
+ await client.close()
+
+# Run the bot
+client.run(TOKEN)
+
+
+# After posts are scraped, time to look for duplicates and remove them:
+def remove_duplicates(json_file):
+ # Read the contents of the JSON file into a list
+ with open(json_file, 'r') as file:
+ lines = file.readlines()
+
+ # Initialize lists to store message data
+ ids = []
+ category = []
+ channels = []
+ contents = []
+ timestamps = []
+
+ # Extract message data from each line in the JSON file
+ for line in lines:
+ message = json.loads(line)
+ message_id = next(iter(message)) # Get the message ID
+ ids.append(message_id)
+ category.append(message[message_id]['category'])
+ channels.append(message[message_id]['channel'])
+ contents.append(message[message_id]['content'])
+ timestamps.append(message[message_id]['timestamp'])
+
+ # Create a DataFrame from the extracted data
+ df = pd.DataFrame({'id': ids, 'category': category, 'channel': channels, 'content': contents, 'timestamp': timestamps})
+
+ # Drop duplicates based on message ID
+ clean_df = df.drop_duplicates(subset=['id'])
+
+ # Open the file to append cleaned messages
+ # Set to store unique message IDs encountered so far
+ unique_message_ids = set()
+
+ # Open the file to append cleaned messages
+ with open('clean_messages.json', 'a') as file:
+ # Read existing messages to populate unique_message_ids set
+ with open('clean_messages.json', 'r') as existing_file:
+ for line in existing_file:
+ try:
+ existing_message = json.loads(line)
+ unique_message_ids.add(existing_message['id'])
+ except json.JSONDecodeError as e:
+ print(f"Error loading JSON data: {e}. Skipping this line and continuing.")
+
+ # Serialize each message individually and write them to the file if the ID is not already present
+ for _, message_row in clean_df.iterrows():
+ message_id = message_row['id']
+ if message_id not in unique_message_ids:
+ message_data = {
+ 'id': message_id,
+ 'category': message_row['category'],
+ 'channel': message_row['channel'],
+ 'content': message_row['content'],
+ 'timestamp': message_row['timestamp']
+ }
+ json.dump(message_data, file)
+ file.write('\n') # Add newline for readability
+ unique_message_ids.add(message_id) # Add the ID to the set of unique IDs
+
+
+
+# Example usage:
+remove_duplicates('messages.json')
\ No newline at end of file
diff --git a/src/personal_notes/discord_extract/json3.py b/src/personal_notes/discord_extract/json3.py
new file mode 100755
index 0000000..9c46927
--- /dev/null
+++ b/src/personal_notes/discord_extract/json3.py
@@ -0,0 +1,49 @@
+import os
+import json
+import pandas as pd
+
+# Function to export data into both txt and json files
+def export_data(channel, category, content_list):
+ # Create a folder for the category if it doesn't exist
+ category_folder = os.path.join('categories', category)
+ os.makedirs(category_folder, exist_ok=True)
+
+ # Create a folder for the channel
+ channel_folder = os.path.join(category_folder, channel)
+ os.makedirs(channel_folder, exist_ok=True)
+
+ # Export content to text file
+ text_file_path = os.path.join(channel_folder, 'content.txt')
+ with open(text_file_path, 'w', encoding='utf-8') as text_file:
+ for content in content_list:
+ if content and content != ".":
+ text_file.write(content + '\n')
+
+ # Export content to json file
+ json_file_path = os.path.join(channel_folder, 'content.json')
+ with open(json_file_path, 'w', encoding='utf-8') as json_file:
+ json.dump(content_list, json_file, indent=4)
+
+# Load the messages from the JSON file
+with open('clean_messages.json', 'r', encoding='utf-8') as file:
+ messages = [json.loads(line) for line in file]
+
+# Create a DataFrame from the messages
+df = pd.DataFrame(messages)
+
+# Sort the DataFrame by 'channel' and 'timestamp'
+df.sort_values(by=['channel', 'timestamp'], inplace=True)
+
+# Group the messages by the 'channel' column
+grouped = df.groupby('channel')
+
+# Iterate over each group (channel)
+for channel, group_df in grouped:
+ # Get the category of the channel
+ category = group_df['category'].iloc[0] # Assuming 'category' is a column in the DataFrame
+
+ # Extract content from the group dataframe
+ content_list = list(group_df['content'])
+
+ # Export data to text and json files
+ export_data(channel, category, content_list)
diff --git a/src/personal_notes/discord_extract/json_to_txt.py b/src/personal_notes/discord_extract/json_to_txt.py
new file mode 100755
index 0000000..9f6fcd7
--- /dev/null
+++ b/src/personal_notes/discord_extract/json_to_txt.py
@@ -0,0 +1,43 @@
+import os
+import json
+import pandas as pd
+
+
+
+# Load the messages from the JSON file
+with open('clean_messages.json', 'r', encoding='utf-8') as file:
+ messages = [json.loads(line) for line in file]
+
+# Create a DataFrame from the messages
+df = pd.DataFrame(messages)
+
+# Sort the DataFrame by 'channel' and 'timestamp'
+df.sort_values(by=['channel', 'timestamp'], inplace=True)
+
+# Group the messages by the 'channel' column
+grouped = df.groupby('channel')
+
+# Create a folder to store the text files for categories
+os.makedirs('categories', exist_ok=True)
+
+# Iterate over each group (channel)
+for channel, group_df in grouped:
+ # Get the category of the channel
+ category = group_df['category'].iloc[0] # Assuming 'category' is a column in the DataFrame
+
+ # Create a folder for the category if it doesn't exist
+ category_folder = os.path.join('categories', category)
+ os.makedirs(category_folder, exist_ok=True)
+
+ # Create a folder for the channel
+ channel_folder = os.path.join(category_folder, channel)
+ os.makedirs(channel_folder, exist_ok=True)
+
+ # Create a text file for the channel's content
+ text_file_path = os.path.join(channel_folder, 'content.txt')
+
+ # Write the non-empty content strings to the text file
+ with open(text_file_path, 'w', encoding='utf-8') as text_file:
+ for content in group_df['content']:
+ if content and content != ".":
+ text_file.write(content + '\n')
diff --git a/src/personal_notes/discord_extract/json_to_txt2.py b/src/personal_notes/discord_extract/json_to_txt2.py
new file mode 100755
index 0000000..bba8115
--- /dev/null
+++ b/src/personal_notes/discord_extract/json_to_txt2.py
@@ -0,0 +1,49 @@
+import os
+import json
+import pandas as pd
+import re
+
+#next: use regex for <#832925562611302400> format. either show these numbers as letters and tags or don't show at all
+
+# Load the messages from the JSON file
+with open('clean_messages.json', 'r', encoding='utf-8') as file:
+ messages = [json.loads(line) for line in file]
+
+# Create a DataFrame from the messages
+df = pd.DataFrame(messages)
+
+# Sort the DataFrame by 'channel' and 'timestamp'
+df.sort_values(by=['channel', 'timestamp'], inplace=True)
+
+# Replace the Discord ID format (<#832925562611302400>) with an empty string or a custom string
+def replace_discord_id(content, replacement=''):
+ return re.sub(r'<#\d+>', replacement, content)
+df['content'] = df['content'].apply(replace_discord_id)
+
+# Group the messages by the 'channel' column
+grouped = df.groupby('channel')
+
+# Create a folder to store the text files for categories
+os.makedirs('categories', exist_ok=True)
+
+# Iterate over each group (channel)
+for channel, group_df in grouped:
+ # Get the category of the channel
+ category = group_df['category'].iloc[0] # Assuming 'category' is a column in the DataFrame
+
+ # Create a folder for the category if it doesn't exist
+ category_folder = os.path.join('categories', category)
+ os.makedirs(category_folder, exist_ok=True)
+
+ # Create a folder for the channel
+ channel_folder = os.path.join(category_folder, channel)
+ os.makedirs(channel_folder, exist_ok=True)
+
+ # Create a text file for the channel's content
+ text_file_path = os.path.join(channel_folder, 'content.txt')
+
+ # Write the non-empty content strings to the text file
+ with open(text_file_path, 'w', encoding='utf-8') as text_file:
+ for content in group_df['content']:
+ if content and content != ".":
+ text_file.write(content + '\n')
diff --git a/src/personal_notes/discord_extract/responses.py b/src/personal_notes/discord_extract/responses.py
new file mode 100755
index 0000000..4c2a606
--- /dev/null
+++ b/src/personal_notes/discord_extract/responses.py
@@ -0,0 +1,2 @@
+def get_response(user_input: str) -> str:
+ raise NotImplementedError("Code is missing")
\ No newline at end of file
diff --git a/src/personal_notes/discord_extract/scheme.txt b/src/personal_notes/discord_extract/scheme.txt
new file mode 100755
index 0000000..7fc729d
--- /dev/null
+++ b/src/personal_notes/discord_extract/scheme.txt
@@ -0,0 +1,10 @@
+
+The extraction code scheme:
+1. setting up a bot in the server https://www.youtube.com/watch?v=UYJDKSah-Ww
+pip install discord
+pip install python-dotenv
+2. Read posts, copy and store each post content in json. (Dont forget to 1) right click on category and disable private category mode 2 server -> roles -> @everyone -> permissions -> view)
+3. Cleaning up: in chosen category delete channels and then create them again. Because it will take years to delete many posts, better to del channels
+4. run json_to_txt.py to save data in folders and txt as well
+5. pip uninstall discord
+