├── src
├── __init__.py
├── build
│ └── _placeholder
├── analytics_league
│ ├── __init__.py
│ ├── utils.py
│ └── history.py
├── static
│ ├── js
│ │ ├── impact_summary.js
│ │ ├── test.js
│ │ ├── history.js
│ │ ├── top_squads.js
│ │ ├── load.js
│ │ ├── who_played.js
│ │ ├── weekly.js
│ │ ├── ownership_trend.js
│ │ ├── manager_form.js
│ │ ├── ownership_rates.js
│ │ └── sampling_utils.js
│ ├── asset
│ │ ├── logo.png
│ │ ├── logo-mini.png
│ │ ├── apple_logo.png
│ │ ├── apple_splash.png
│ │ ├── apple_logo_114.png
│ │ ├── apple_splash_320.png
│ │ └── manifest.json
│ ├── images
│ │ ├── blog1.jpg
│ │ ├── blog2.jpg
│ │ ├── blog3.jpg
│ │ ├── blog4.jpg
│ │ ├── blog5.jpg
│ │ ├── blog6.jpg
│ │ ├── ffhub.png
│ │ ├── repo.jpg
│ │ ├── review.png
│ │ ├── logo_128.png
│ │ ├── logo_400.png
│ │ ├── podcast.png
│ │ ├── clubs
│ │ │ ├── ARS.png
│ │ │ ├── AVL.png
│ │ │ ├── BHA.png
│ │ │ ├── BRE.png
│ │ │ ├── BUR.png
│ │ │ ├── CHE.png
│ │ │ ├── CRY.png
│ │ │ ├── EVE.png
│ │ │ ├── FUL.png
│ │ │ ├── LEE.png
│ │ │ ├── LEI.png
│ │ │ ├── LIV.png
│ │ │ ├── MCI.png
│ │ │ ├── MUN.png
│ │ │ ├── NEW.png
│ │ │ ├── NOR.png
│ │ │ ├── SHU.png
│ │ │ ├── SOU.png
│ │ │ ├── TOT.png
│ │ │ ├── TOT1.png
│ │ │ ├── WAT.png
│ │ │ ├── WBA.png
│ │ │ ├── WHU.png
│ │ │ └── WOL.png
│ │ ├── logo_400_tr.png
│ │ ├── youtube_1.jpg
│ │ ├── youtube_2.jpg
│ │ ├── youtube_3.jpg
│ │ ├── loading.svg
│ │ ├── field.svg
│ │ └── jersey.svg
│ ├── json
│ │ ├── top_managers.tsv
│ │ └── fpl_analytics.json
│ ├── extra
│ │ └── jersey_buttons.txt
│ ├── csv
│ │ └── team.csv
│ └── css
│ │ └── svg.css
├── static-values.json
├── freezer.py
├── sample.py
├── run.py
├── cache_api.py
├── templates
│ ├── test.html
│ ├── signup.html
│ ├── ownership_trend.html
│ ├── history.html
│ ├── footer.html
│ ├── spirit_team.html
│ ├── top_squads.html
│ ├── load.html
│ ├── manager_form.html
│ └── impact_summary.html
├── encrypt.py
└── prep.py
├── scripts
├── sample.sh
├── update_league.sh
└── requirements.txt
├── .gitignore
├── archive
├── Dockerfile-custom
├── Dockerfile-sampler
├── refresh_league.py
├── fpl_analytics_league.yml
├── highlights-fdr-parts.html
└── fpl_analytics_league.html
├── Dockerfile
├── Dockerfile-dev
├── docker-compose.yml
├── custom-request.yml
├── dev-compose.yml
└── .github
└── workflows
├── analytics_xp_league.yml
├── sample_fpl.yml
├── main.yml
└── regenerate_element_gameweek.yml
/src/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/build/_placeholder:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/analytics_league/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/scripts/sample.sh:
--------------------------------------------------------------------------------
1 | cd ../src
2 | python3 sample.py
3 |
--------------------------------------------------------------------------------
/src/static/js/impact_summary.js:
--------------------------------------------------------------------------------
1 | console.log("Yay")
--------------------------------------------------------------------------------
/scripts/update_league.sh:
--------------------------------------------------------------------------------
1 | cd ../src
2 | python3 xp_league.py
3 |
--------------------------------------------------------------------------------
/scripts/requirements.txt:
--------------------------------------------------------------------------------
1 | sasoptpy>=1.0.5a0
2 | pandas
3 | aiohttp
4 | asyncio
5 |
--------------------------------------------------------------------------------
/src/static/asset/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/asset/logo.png
--------------------------------------------------------------------------------
/src/static/images/blog1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/blog1.jpg
--------------------------------------------------------------------------------
/src/static/images/blog2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/blog2.jpg
--------------------------------------------------------------------------------
/src/static/images/blog3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/blog3.jpg
--------------------------------------------------------------------------------
/src/static/images/blog4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/blog4.jpg
--------------------------------------------------------------------------------
/src/static/images/blog5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/blog5.jpg
--------------------------------------------------------------------------------
/src/static/images/blog6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/blog6.jpg
--------------------------------------------------------------------------------
/src/static/images/ffhub.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/ffhub.png
--------------------------------------------------------------------------------
/src/static/images/repo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/repo.jpg
--------------------------------------------------------------------------------
/src/static/images/review.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/review.png
--------------------------------------------------------------------------------
/src/static/asset/logo-mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/asset/logo-mini.png
--------------------------------------------------------------------------------
/src/static/images/logo_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/logo_128.png
--------------------------------------------------------------------------------
/src/static/images/logo_400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/logo_400.png
--------------------------------------------------------------------------------
/src/static/images/podcast.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/podcast.png
--------------------------------------------------------------------------------
/src/static/asset/apple_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/asset/apple_logo.png
--------------------------------------------------------------------------------
/src/static/asset/apple_splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/asset/apple_splash.png
--------------------------------------------------------------------------------
/src/static/images/clubs/ARS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/ARS.png
--------------------------------------------------------------------------------
/src/static/images/clubs/AVL.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/AVL.png
--------------------------------------------------------------------------------
/src/static/images/clubs/BHA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/BHA.png
--------------------------------------------------------------------------------
/src/static/images/clubs/BRE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/BRE.png
--------------------------------------------------------------------------------
/src/static/images/clubs/BUR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/BUR.png
--------------------------------------------------------------------------------
/src/static/images/clubs/CHE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/CHE.png
--------------------------------------------------------------------------------
/src/static/images/clubs/CRY.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/CRY.png
--------------------------------------------------------------------------------
/src/static/images/clubs/EVE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/EVE.png
--------------------------------------------------------------------------------
/src/static/images/clubs/FUL.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/FUL.png
--------------------------------------------------------------------------------
/src/static/images/clubs/LEE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/LEE.png
--------------------------------------------------------------------------------
/src/static/images/clubs/LEI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/LEI.png
--------------------------------------------------------------------------------
/src/static/images/clubs/LIV.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/LIV.png
--------------------------------------------------------------------------------
/src/static/images/clubs/MCI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/MCI.png
--------------------------------------------------------------------------------
/src/static/images/clubs/MUN.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/MUN.png
--------------------------------------------------------------------------------
/src/static/images/clubs/NEW.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/NEW.png
--------------------------------------------------------------------------------
/src/static/images/clubs/NOR.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/NOR.png
--------------------------------------------------------------------------------
/src/static/images/clubs/SHU.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/SHU.png
--------------------------------------------------------------------------------
/src/static/images/clubs/SOU.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/SOU.png
--------------------------------------------------------------------------------
/src/static/images/clubs/TOT.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/TOT.png
--------------------------------------------------------------------------------
/src/static/images/clubs/TOT1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/TOT1.png
--------------------------------------------------------------------------------
/src/static/images/clubs/WAT.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/WAT.png
--------------------------------------------------------------------------------
/src/static/images/clubs/WBA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/WBA.png
--------------------------------------------------------------------------------
/src/static/images/clubs/WHU.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/WHU.png
--------------------------------------------------------------------------------
/src/static/images/clubs/WOL.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/clubs/WOL.png
--------------------------------------------------------------------------------
/src/static/images/logo_400_tr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/logo_400_tr.png
--------------------------------------------------------------------------------
/src/static/images/youtube_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/youtube_1.jpg
--------------------------------------------------------------------------------
/src/static/images/youtube_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/youtube_2.jpg
--------------------------------------------------------------------------------
/src/static/images/youtube_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/images/youtube_3.jpg
--------------------------------------------------------------------------------
/src/static/json/top_managers.tsv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/json/top_managers.tsv
--------------------------------------------------------------------------------
/src/static/asset/apple_logo_114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/asset/apple_logo_114.png
--------------------------------------------------------------------------------
/src/static/asset/apple_splash_320.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sertalpbilal/fpl_optimized/HEAD/src/static/asset/apple_splash_320.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | *.log
3 | *.pyc
4 | .vscode/*
5 | setup_gh.cmd
6 | src/build
7 | secret.key
8 | fplreview.py
9 | simulator.py
10 | *.tmp.*
11 |
12 |
--------------------------------------------------------------------------------
/archive/Dockerfile-custom:
--------------------------------------------------------------------------------
1 | FROM sertalpbilal/coin-or-optimization-with-batteries:latest
2 |
3 | COPY src /app
4 | WORKDIR /app
5 |
6 | CMD python3 -u custom.py
7 |
--------------------------------------------------------------------------------
/archive/Dockerfile-sampler:
--------------------------------------------------------------------------------
1 | FROM sertalpbilal/coin-or-optimization-with-batteries:latest
2 |
3 | COPY src /app
4 | WORKDIR /app
5 |
6 | CMD python3 -u sample.py
7 |
--------------------------------------------------------------------------------
/src/static-values.json:
--------------------------------------------------------------------------------
1 | {
2 | "season": "2025-26",
3 | "season_status": "live",
4 | "win_driver": "C:\\work\\chromedriver.exe",
5 | "unix_driver": "/usr/local/bin/chromedriver"
6 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM sertalpbilal/coin-or-optimization-with-batteries:latest
2 |
3 | RUN pip install cryptography pycryptodome aiohttp asyncio
4 | COPY src /app
5 | WORKDIR /app
6 |
7 | CMD python3 -u run.py
8 |
--------------------------------------------------------------------------------
/src/freezer.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from flask_frozen import Freezer
4 | from app import app, list_all_snapshots
5 |
6 | def freeze_all():
7 | freezer = Freezer(app)
8 | freezer.freeze()
9 |
--------------------------------------------------------------------------------
/Dockerfile-dev:
--------------------------------------------------------------------------------
1 | FROM sertalpbilal/coin-or-optimization-with-batteries:latest
2 |
3 | RUN pip install cryptography pycryptodome aiohttp asyncio
4 | COPY src /app
5 | WORKDIR /app
6 |
7 | CMD python3 -u app.py
8 |
--------------------------------------------------------------------------------
/src/sample.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from collect import sample_fpl_teams
4 | # from collect import create_folders
5 | import os
6 |
7 | if __name__ == "__main__":
8 | gw = os.environ.get('GW', None)
9 | sample_fpl_teams(gw)
10 |
--------------------------------------------------------------------------------
/archive/refresh_league.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from collect import create_folders, get_fpl_analytics_league
4 |
5 | if __name__ == "__main__":
6 | input_folder, output_folder, season_folder = create_folders()
7 | get_fpl_analytics_league(input_folder)
8 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | fpl_aa_generator:
4 | container_name: fpl_aa
5 | build: .
6 | environment:
7 | - PYTHONUNBUFFERED=1
8 | - LANG=C.UTF-8
9 | # env_file:
10 | # - ./user.env
11 | volumes:
12 | - ./src:/app
13 |
--------------------------------------------------------------------------------
/custom-request.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | fpl_aa_web_demo:
4 | container_name: fpl_aa_dev
5 | build:
6 | context: .
7 | dockerfile: Dockerfile-custom
8 | environment:
9 | - PYTHONUNBUFFERED=1
10 | - LANG=C.UTF-8
11 | volumes:
12 | - /fpl_aa:/app
13 |
--------------------------------------------------------------------------------
/dev-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | fpl_aa_web_demo:
4 | container_name: fpl_aa_dev
5 | build:
6 | context: .
7 | dockerfile: Dockerfile-dev
8 | environment:
9 | - PYTHONUNBUFFERED=1
10 | - LANG=C.UTF-8
11 | ports:
12 | - "8001:5000"
13 | volumes:
14 | - ./src:/app
15 |
--------------------------------------------------------------------------------
/src/static/extra/jersey_buttons.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 | ⥯
4 |
5 |
6 |
7 | 🗙
8 |
--------------------------------------------------------------------------------
/src/static/asset/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "dir": "ltr",
3 | "lang": "en",
4 | "name": "FPL Optimized",
5 | "display": "standalone",
6 | "start_url": "/",
7 | "scope": ".",
8 | "short_name": "FPL Optimized",
9 | "theme_color": "#494949",
10 | "description": "FPL Optimized - Tools, blogs, and tutorials about optimization in FPL",
11 | "orientation": "portrait",
12 | "background_color": "transparent",
13 | "icons": [{
14 | "src": "./static/asset/apple_logo_114.png"
15 | }]
16 | }
--------------------------------------------------------------------------------
/src/analytics_league/utils.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | import json
4 | import pathlib
5 | from collect import get_fpl_info
6 |
7 | def read_league_teams():
8 | base_folder = pathlib.Path(__file__).parent.resolve()
9 | with open(base_folder / '../static/json/fpl_analytics.json') as f:
10 | data = json.load(f)
11 | return data
12 |
13 | def get_current_gw():
14 | data = get_fpl_info('now')
15 | gws = [i for i in data['events'] if i['is_previous'] == True]
16 | return gws[0]['id']
17 |
18 | def save_to_json(name, data):
19 | with open(name, 'w') as f:
20 | json.dump(data, f)
21 |
22 |
--------------------------------------------------------------------------------
/src/run.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import sys
4 |
5 | from solve import solve_all
6 | from freezer import freeze_all
7 |
8 |
9 | if __name__ == "__main__":
10 |
11 | opts = ''
12 | if len(sys.argv) > 1:
13 | opts = sys.argv[1]
14 |
15 | if opts != "skip-opt":
16 | from collect import get_all_data, encrypt_files
17 | # from simulator import generate_simulations
18 | print("Step 1: Get All Data")
19 | input_folder, output_folder, next_gw = get_all_data()
20 | # if next_gw != 39:
21 | # solve_all(input_folder, output_folder)
22 | # generate_simulations(input_folder, output_folder, 100)
23 | # encrypt_files(input_folder, page='free-planner', remove=True)
24 | freeze_all()
25 |
--------------------------------------------------------------------------------
/src/cache_api.py:
--------------------------------------------------------------------------------
1 |
2 | import requests
3 | import json
4 | import os
5 |
6 | BASE = "https://fantasy.premierleague.com/api/"
7 | season = "2020-21"
8 | API = f"build/data/{season}/api/"
9 | TEAM = 2221044
10 |
11 | def cache_page_as(page, address):
12 | URL = BASE + page
13 | TARGET = API + address
14 |
15 | if os.path.exists(TARGET):
16 | print("Existing target... skipping")
17 | return
18 |
19 | print(f"Requesting {URL} -> {TARGET}")
20 | r = requests.get(URL)
21 | if r.status_code != 200:
22 | print(r.status_code)
23 | print(f"Error in request {page}")
24 | return
25 | with open(TARGET, 'w') as f:
26 | json.dump(r.json(), f)
27 |
28 | cache_page_as("bootstrap-static/", "main.json")
29 | cache_page_as("fixtures/", "fixtures/all.json")
30 | for gw in range(1,39):
31 | cache_page_as(f"fixtures/?event={gw}", f"fixtures/{gw}.json")
32 | for gw in range(1,39):
33 | cache_page_as(f"event/{gw}/live", f"live/{gw}.json")
34 | cache_page_as(f"entry/{TEAM}/", "team_main.json")
35 | cache_page_as(f"entry/{TEAM}/history/", "team_history.json")
36 | cache_page_as(f"entry/{TEAM}/transfers/", "team_transfers.json")
37 | for gw in range(1,39):
38 | cache_page_as(f"entry/{TEAM}/event/{gw}/picks/", f"picks/{gw}.json")
39 |
40 |
--------------------------------------------------------------------------------
/archive/fpl_analytics_league.yml:
--------------------------------------------------------------------------------
1 | name: Update FPL Analytics League
2 |
3 | on:
4 | workflow_dispatch:
5 | # schedule:
6 | # - cron: '0 7 * * *'
7 |
8 | jobs:
9 | build-and-run:
10 | name: Fetch FPLReview for Analytics League
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Check current directory
15 | run: pwd
16 | - name: Checkout repository
17 | uses: actions/checkout@v2
18 | - name: Checkout webpage branch
19 | uses: actions/checkout@v2
20 | with:
21 | ref: 'webpage'
22 | path: ./build
23 | - name: Make sure data folder exists
24 | run: mkdir -p ./build/data
25 | - name: Build docker image
26 | run: docker build -f Dockerfile-scrap -t scraper .
27 | - name: Run docker image
28 | run: |
29 | docker run -t --rm -e LANG=C.UTF-8 -v $(pwd)/build:/app/build scraper bash -c "python3 refresh_league.py"
30 | - name: Add changes to the branch
31 | run: |
32 | cd build
33 | git add -u
34 | git add .
35 | git config user.name "Github Action"
36 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
37 | git commit -m "Manual Update - Analytics League $GITHUB_RUN_ID"
38 | git push
39 |
--------------------------------------------------------------------------------
/src/templates/test.html:
--------------------------------------------------------------------------------
1 | "% include 'header.html' %"
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
Test Page!
11 |
{{ sample_data }}
12 |
13 |
{{ data_ac }}
14 |
15 |
{{ data_fo }}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
31 |
32 |
33 |
34 | "% with scripts=["main", "test"] %" "% include 'footer.html' %" "% endwith %"
--------------------------------------------------------------------------------
/.github/workflows/analytics_xp_league.yml:
--------------------------------------------------------------------------------
1 | name: Get data for Analytics xP League
2 |
3 | on:
4 | workflow_dispatch:
5 | # schedule:
6 | # - cron: '0 7 * * *'
7 |
8 | jobs:
9 | build-and-run:
10 | name: Fetch IDs and picks for Analytics xP league
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Check current directory
15 | run: pwd
16 | - name: Checkout repository
17 | uses: actions/checkout@v2
18 | - name: Checkout webpage branch
19 | uses: actions/checkout@v2
20 | with:
21 | ref: 'webpage'
22 | path: ./src/build
23 | - name: Make sure data folder exists
24 | run: mkdir -p ./src/build/data
25 | - name: Make sure statuc folder exists
26 | run: mkdir -p ./src/build/static
27 | - name: Fetch docker image
28 | run: docker pull sertalpbilal/selenium_docker:latest
29 | - name: Run docker image
30 | run: |
31 | docker run -t --rm -e LANG=C.UTF-8 -v $(pwd):/app sertalpbilal/selenium_docker bash -c "cd /app/scripts && chmod +x update_league.sh && ./update_league.sh"
32 | - name: Add changes to the branch
33 | run: |
34 | cd src/build
35 | git add -u
36 | git add .
37 | git config user.name "Github Action"
38 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
39 | git commit -m "Auto Update - Analytics xP League $GITHUB_RUN_ID"
40 | git push
41 |
--------------------------------------------------------------------------------
/src/static/js/test.js:
--------------------------------------------------------------------------------
1 |
2 | var app = new Vue({
3 | el: '#app',
4 | data: {
5 | sample_data: "test",
6 | data_ac: {source: 'ac', response_code: 0, data: ''},
7 | data_fo: {source: 'fo', response_code: 0, data: ''}
8 | },
9 | computed: {
10 |
11 | },
12 | methods: {
13 | test_ac() {
14 | let proxy = "https://cors.alpscode.com"
15 | $.ajax({
16 | type: "GET",
17 | url: `${proxy}/fantasy.premierleague.com/api/bootstrap-static/`,
18 | dataType: "json",
19 | async: true,
20 | success: data => {
21 | app.data_ac.data = data["events"][0]
22 | },
23 | error: (e) => {
24 | console.log("Cannot get FPL main data");
25 | console.error(e)
26 | }
27 | });
28 | },
29 | test_fo() {
30 | let proxy = "https://cors.fploptimized.com"
31 | $.ajax({
32 | type: "GET",
33 | url: `${proxy}/fantasy.premierleague.com/api/bootstrap-static/`,
34 | dataType: "json",
35 | async: true,
36 | success: data => {
37 | app.data_fo.data = data["events"][0]
38 | },
39 | error: (e) => {
40 | console.log("Cannot get FPL main data");
41 | console.error(e)
42 | }
43 | });
44 | }
45 | }
46 | })
47 |
48 |
49 | $(document).ready(() => {
50 |
51 | })
52 |
--------------------------------------------------------------------------------
/src/encrypt.py:
--------------------------------------------------------------------------------
1 | from cryptography.fernet import Fernet
2 | import os
3 | import json
4 |
5 | def write_key(key_name):
6 | key = Fernet.generate_key()
7 | with open("secret.key", "w") as key_file:
8 | json.dump({key_name: key.decode()}, key_file)
9 |
10 | def load_key(key_name):
11 | with open("secret.key", "r") as key_file:
12 | keys = json.load(key_file)
13 | return keys[key_name].encode()
14 |
15 |
16 | def encrypt(filename, key_name='FERNET_KEY'):
17 | try:
18 | key = load_key(key_name)
19 | except:
20 | key = os.environ.get(key_name)
21 | f = Fernet(key)
22 | with open(filename, "rb") as file:
23 | file_data = file.read()
24 | encrypted_data = f.encrypt(file_data)
25 | with open(filename + "-encrypted", "wb") as file:
26 | file.write(encrypted_data)
27 |
28 |
29 | def decrypt(filename, key_name='FERNET_KEY'):
30 | try:
31 | key = load_key(key_name)
32 | except:
33 | key = os.environ.get(key_name)
34 | f = Fernet(key)
35 | with open(filename + "-encrypted", "rb") as file:
36 | encrypted_data = file.read()
37 | decrypted_data = f.decrypt(encrypted_data)
38 | with open(filename, "wb") as file:
39 | file.write(decrypted_data)
40 |
41 |
42 | def read_encrypted(filename, key_name='FERNET_KEY'):
43 | try:
44 | key = load_key(key_name)
45 | except:
46 | key = os.environ.get(key_name)
47 | f = Fernet(key)
48 | with open(filename + "-encrypted", "rb") as file:
49 | encrypted_data = file.read()
50 | decrypted_data = f.decrypt(encrypted_data)
51 | return decrypted_data
52 |
53 |
--------------------------------------------------------------------------------
/src/static/images/loading.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/static/csv/team.csv:
--------------------------------------------------------------------------------
1 | ,code,draw,form,id,loss,name,played,points,position,short_name,strength,team_division,unavailable,win,strength_overall_home,strength_overall_away,strength_attack_home,strength_attack_away,strength_defence_home,strength_defence_away,pulse_id
2 | 0,3,0,,1,0,Arsenal,0,0,0,ARS,4,,False,0,1240,1250,1160,1210,1190,1230,1
3 | 1,7,0,,2,0,Aston Villa,0,0,0,AVL,3,,False,0,1130,1150,1140,1170,1120,1160,2
4 | 2,36,0,,3,0,Brighton,0,0,0,BHA,3,,False,0,1100,1120,1110,1130,1100,1120,131
5 | 3,90,0,,4,0,Burnley,0,0,0,BUR,3,,False,0,1060,1100,1130,1150,1010,1020,43
6 | 4,8,0,,5,0,Chelsea,0,0,0,CHE,4,,False,0,1250,1280,1180,1250,1230,1260,4
7 | 5,31,0,,6,0,Crystal Palace,0,0,0,CRY,3,,False,0,1080,1120,1100,1130,1020,1040,6
8 | 6,11,0,,7,0,Everton,0,0,0,EVE,4,,False,0,1200,1210,1160,1220,1210,1240,7
9 | 7,54,0,,8,0,Fulham,0,0,0,FUL,2,,False,0,1000,1020,1020,1020,1000,1010,34
10 | 8,13,0,,9,0,Leicester,0,0,0,LEI,4,,False,0,1200,1240,1190,1230,1200,1230,26
11 | 9,2,0,,10,0,Leeds,0,0,0,LEE,3,,False,0,1090,1130,1110,1140,1100,1120,9
12 | 10,14,0,,11,0,Liverpool,0,0,0,LIV,5,,False,0,1330,1350,1250,1310,1320,1340,10
13 | 11,43,0,,12,0,Man City,0,0,0,MCI,5,,False,0,1330,1350,1250,1310,1330,1340,11
14 | 12,1,0,,13,0,Man Utd,0,0,0,MUN,4,,False,0,1220,1250,1210,1230,1200,1290,12
15 | 13,4,0,,14,0,Newcastle,0,0,0,NEW,3,,False,0,1090,1130,1100,1130,1030,1070,23
16 | 14,49,0,,15,0,Sheffield Utd,0,0,0,SHU,3,,False,0,1140,1160,1130,1160,1020,1050,18
17 | 15,20,0,,16,0,Southampton,0,0,0,SOU,3,,False,0,1090,1120,1130,1130,1110,1120,20
18 | 16,6,0,,17,0,Spurs,0,0,0,TOT,4,,False,0,1270,1290,1160,1180,1260,1290,21
19 | 17,35,0,,18,0,West Brom,0,0,0,WBA,2,,False,0,1020,1040,1010,1010,990,1020,36
20 | 18,21,0,,19,0,West Ham,0,0,0,WHU,3,,False,0,1140,1170,1150,1170,1160,1200,25
21 | 19,39,0,,20,0,Wolves,0,0,0,WOL,4,,False,0,1190,1210,1190,1230,1140,1190,38
22 |
--------------------------------------------------------------------------------
/src/templates/signup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | The Super Secret FPL Tool Newsletter
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/.github/workflows/sample_fpl.yml:
--------------------------------------------------------------------------------
1 | name: Sample-FPL
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | GW:
7 | description: 'GW Number'
8 | required: true
9 |
10 | jobs:
11 | build-and-run:
12 | name: Sample values from FPL
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - name: Check current directory
17 | run: pwd
18 | - name: Checkout repository
19 | uses: actions/checkout@v2
20 | - name: Checkout webpage branch
21 | uses: actions/checkout@v2
22 | with:
23 | ref: 'webpage'
24 | path: ./src/build
25 | - name: Make sure data folder exists
26 | run: mkdir -p ./src/build/sample
27 | - name: Pull docker image
28 | run: docker pull sertalpbilal/selenium_docker:latest
29 | - name: Run docker image
30 | run: |
31 | if [ ${{ github.event.inputs.GW }} == 'season' ]; then
32 | docker run -t --rm -e LANG=C.UTF-8 -v $(pwd):/app sertalpbilal/selenium_docker bash -c "cd /app/scripts && python3 -m pip install -r requirements.txt && cd /app/src && python3 -c \"import collect; collect.sample_all_season()\" "
33 | else
34 | docker run -t --rm -e LANG=C.UTF-8 -e GW=${{ github.event.inputs.GW }} -v $(pwd):/app sertalpbilal/selenium_docker bash -c "cd /app/scripts && python3 -m pip install -r requirements.txt && chmod +x *.sh && ./sample.sh"
35 | fi
36 | shell: bash
37 | - name: Add changes to the branch
38 | run: |
39 | cd src/build
40 | git add -u
41 | git add .
42 | git config user.name "Github Action"
43 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
44 | git commit -m "Automated build - Data Sample GW${{ github.event.inputs.GW }} $GITHUB_RUN_ID"
45 | git push
46 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Populate-Website
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches: engine
7 | schedule:
8 | - cron: "0 4,10,16,22 * * *"
9 |
10 | jobs:
11 | build-and-run:
12 | name: Run the automated Docker build
13 | runs-on: ubuntu-latest
14 | if: "!contains(github.event.commits[0].message, '[skip ci]')"
15 |
16 | steps:
17 | - name: Check current directory
18 | run: pwd
19 | - name: Checkout repository
20 | uses: actions/checkout@v2
21 | - name: Checkout webpage branch
22 | uses: actions/checkout@v2
23 | with:
24 | ref: "webpage"
25 | path: ./build
26 | - name: Make sure data folder exists
27 | run: mkdir -p ./build/data
28 | - name: Build docker image
29 | run: docker build -t webapp .
30 | - name: Run docker image with optimization
31 | if: "!contains(github.event.commits[0].message, '[skip opt]')"
32 | run: |
33 | docker run -t --rm -e LANG=C.UTF-8 -e FERNET_KEY=$FERNET_KEY -v $(pwd)/build:/app/build webapp bash -c "python3 -c \"import encrypt; encrypt.decrypt('simulator.py')\" && python3 run.py"
34 | env:
35 | FERNET_KEY: ${{ secrets.FERNET_KEY }}
36 | REVIEW_KEY: ${{ secrets.REVIEW_KEY }}
37 | PATREON_COOKIE_REVIEW: ${{ secrets.PATREON_COOKIE_REVIEW }}
38 | - name: Only populate pages, no optimization
39 | if: "contains(github.event.commits[0].message, '[skip opt]')"
40 | run: |
41 | docker run -t --rm -e LANG=C.UTF-8 -e REVIEW_KEY=$REVIEW_KEY -v $(pwd)/build:/app/build webapp bash -c "python3 run.py skip-opt"
42 | env:
43 | REVIEW_KEY: ${{ secrets.REVIEW_KEY }}
44 | - name: Add changes to the branch
45 | run: |
46 | cd build
47 | git add -u
48 | git add .
49 | git config user.name "Github Action"
50 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
51 | git commit -m "Automated build $GITHUB_RUN_ID"
52 | git push
53 |
--------------------------------------------------------------------------------
/src/analytics_league/history.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import itertools
4 | import json
5 | import time
6 | import random
7 |
8 | from analytics_league.utils import read_league_teams, get_current_gw, save_to_json
9 | from collect import get_fpl_info, FPL_API
10 | from concurrent.futures import ProcessPoolExecutor
11 | from urllib.request import urlopen
12 |
13 |
14 | def get_team_picks(team, gw):
15 | return get_fpl_info('picks', PID=team, GW=gw)
16 |
17 |
18 | def get_team_history(team, start, end):
19 | if start is None:
20 | start = 1
21 | gw_range = range(start, end+1)
22 | with ProcessPoolExecutor(max_workers=8) as executor:
23 | picks = list(executor.map(get_team_picks, itertools.repeat(team), gw_range))
24 |
25 | transfers = get_fpl_info('transfers', PID=team)
26 | history = get_fpl_info('history', PID=team)
27 | return {'picks': picks, 'transfers': transfers, 'history': history}
28 |
29 |
30 | def get_analytics_league_history():
31 | '''
32 | Returns all squad picks, transfer, and chip history for Analytics League teams
33 | '''
34 | teams = read_league_teams()
35 | last_gw = get_current_gw()
36 |
37 | for team in teams:
38 | print(team)
39 | team.update(get_team_history(team=team['id'], start=None, end=last_gw))
40 |
41 | return teams
42 |
43 |
44 |
45 | def get_only_team_history(team):
46 | history = get_fpl_info('history', PID=team)
47 | return {'id': team, 'history': history}
48 |
49 |
50 | def get_rank_n_player(rank):
51 | page = ((rank-1)//50)+1
52 | order = (rank-1) % 50
53 | print(f"Fetching rank {rank}")
54 | try:
55 | with urlopen(FPL_API['overall'].format(LID=314, P=page)) as url:
56 | page_data = json.loads(url.read().decode())
57 | tid = page_data['standings']['results'][order]['entry']
58 | print(f"Done {rank}")
59 | return get_only_team_history(tid)
60 | except:
61 | print("Encountered page access error, waiting 5 seconds")
62 | time.sleep(5)
63 | print(f"Error {rank}")
64 | return None
65 |
66 |
67 | def sample_within_range():
68 |
69 | target = 1000000
70 | nsample = 5000
71 |
72 | player_targets = random.sample(range(1, target+1), nsample)
73 | with ProcessPoolExecutor(max_workers=16) as executor:
74 | results = list(executor.map(get_rank_n_player, player_targets))
75 | fetched_history = [i for i in results if i is not None]
76 | return fetched_history
77 |
78 |
79 | if __name__ == '__main__':
80 | # h = get_analytics_league_history()
81 | # save_to_json('history.json', h)
82 |
83 | h = sample_within_range()
84 | save_to_json('history.json', h)
85 |
--------------------------------------------------------------------------------
/src/prep.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | # Data prep steps
4 |
5 | import pandas as pd
6 | import glob
7 | import pathlib
8 |
9 | from app import folder_order
10 |
11 | def get_GW_folder(gw=None):
12 |
13 | if gw is None:
14 | all_dates = glob.glob(f'build/data/*/*/*/input/')
15 | else:
16 | all_dates = glob.glob(f'build/data/*/GW{gw}/*/input/')
17 | all_dates.sort(key=folder_order, reverse=True)
18 | return all_dates[0]
19 |
20 |
21 | def get_multistage_data(gw=None, n=3):
22 | """ Returns the multi-period data available right before given GW """
23 |
24 | input_folder = pathlib.Path(get_GW_folder(gw))
25 |
26 | df = pd.read_csv(input_folder / 'element_gameweek.csv')
27 | next_week = df['event'].min()
28 | element_gameweek_df = df[df['event'] < next_week+n].copy()
29 |
30 | sum_md_df = element_gameweek_df.groupby(['player_id', 'web_name'])['points_md'].sum()
31 | sum_md_df.sort_values(inplace=True, ascending=False)
32 | sum_md_df = sum_md_df.reset_index().set_index('player_id').copy()
33 |
34 | df = pd.read_csv(input_folder / 'element.csv')
35 | element_df = df.copy().set_index('id')
36 | element_df['ict_sum'] = element_df['influence'] + element_df['creativity'] + element_df['threat']
37 | element_gameweek_df = pd.merge(left=element_gameweek_df, right=df, how='inner', left_on=['player_id'], right_on=['id'], suffixes=('', '_extra'))
38 | element_gameweek_df['rawxp'] = element_gameweek_df.apply(lambda r: r['points_md'] * 90 / max(1, r['xmins_md']), axis=1)
39 | elements = element_gameweek_df['player_id'].unique().tolist()
40 | gameweeks = element_gameweek_df['event'].unique().tolist()
41 | total_weeks = len(gameweeks)
42 | element_gameweek_df.set_index(['player_id', 'event'], inplace=True, drop=True)
43 | popular_element_df = element_df.sort_values(by=['selected_by_percent'], ascending=False)[['web_name', 'selected_by_percent']]
44 |
45 | team_codes = element_gameweek_df['team_code'].unique().tolist()
46 |
47 | types_df = pd.read_csv(input_folder / 'element_type.csv')
48 | types = types_df['id'].to_list()
49 | types_df.set_index('id', inplace=True, drop=True)
50 |
51 | position = [1,2,3,4]
52 | element_gameweek = [(e, g) for e in elements for g in gameweeks]
53 |
54 | return {
55 | 'gw': gw,
56 | 'input_folder': input_folder,
57 | 'element_gameweek_df': element_gameweek_df,
58 | 'sum_md_df': sum_md_df,
59 | 'element_df': element_df,
60 | 'total_weeks': total_weeks,
61 | 'team_codes': team_codes,
62 | 'types': types,
63 | 'types_df': types_df,
64 | 'position': position,
65 | 'element_gameweek': element_gameweek,
66 | 'elements': elements,
67 | 'gameweeks': gameweeks
68 | }
69 |
--------------------------------------------------------------------------------
/.github/workflows/regenerate_element_gameweek.yml:
--------------------------------------------------------------------------------
1 | name: Regenerate Element Gameweek Data
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | gw:
7 | description: "Gameweek number to regenerate (leave empty to process all available projections)"
8 | required: false
9 | type: string
10 | force_new:
11 | description: "Force creation of new folder even if one exists"
12 | required: false
13 | type: boolean
14 | default: false
15 |
16 | jobs:
17 | regenerate:
18 | name: Regenerate element_gameweek.csv files
19 | runs-on: ubuntu-latest
20 |
21 | steps:
22 | - name: Checkout engine branch
23 | uses: actions/checkout@v2
24 | with:
25 | ref: engine
26 |
27 | - name: Checkout webpage branch
28 | uses: actions/checkout@v2
29 | with:
30 | ref: webpage
31 | path: ./build
32 |
33 | - name: Set up Python
34 | uses: actions/setup-python@v2
35 | with:
36 | python-version: "3.9"
37 |
38 | - name: Install dependencies
39 | run: |
40 | python -m pip install --upgrade pip
41 | pip install pandas numpy pytz
42 |
43 | - name: Run regeneration script (all GWs)
44 | if: ${{ github.event.inputs.gw == '' && !github.event.inputs.force_new }}
45 | run: |
46 | cd src
47 | python regenerate_element_gameweek.py
48 |
49 | - name: Run regeneration script (specific GW)
50 | if: ${{ github.event.inputs.gw != '' && !github.event.inputs.force_new }}
51 | run: |
52 | cd src
53 | python regenerate_element_gameweek.py --gw ${{ github.event.inputs.gw }}
54 |
55 | - name: Run regeneration script (all GWs, force new)
56 | if: ${{ github.event.inputs.gw == '' && github.event.inputs.force_new }}
57 | run: |
58 | cd src
59 | python regenerate_element_gameweek.py --force-new
60 |
61 | - name: Run regeneration script (specific GW, force new)
62 | if: ${{ github.event.inputs.gw != '' && github.event.inputs.force_new }}
63 | run: |
64 | cd src
65 | python regenerate_element_gameweek.py --gw ${{ github.event.inputs.gw }} --force-new
66 |
67 | - name: Commit and push changes
68 | run: |
69 | cd build
70 | git config user.name "Github Action"
71 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
72 | git add -A
73 | if git diff --staged --quiet; then
74 | echo "No changes to commit"
75 | else
76 | git commit -m "Regenerate element_gameweek data (GW: ${{ github.event.inputs.gw || 'all' }}) [skip ci]"
77 | git push
78 | echo "Changes pushed successfully"
79 | fi
80 |
--------------------------------------------------------------------------------
/src/templates/ownership_trend.html:
--------------------------------------------------------------------------------
1 | "% include 'header.html' %"
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | |
21 | FPL API |
22 | Sample - Top 1K |
23 | |
24 |
25 |
26 | | Name |
27 | Team |
28 | Now |
29 | Last GW |
30 | Trend |
31 | Last GW |
32 | Estimated |
33 | Pos |
34 | Price |
35 | ID |
36 |
37 |
38 |
39 |
40 | | {{ i.web_name }} |
41 | {{ team_codes[i.team_code].short }} |
42 | {{ i.selected_by_percent }} |
43 | {{ last_gw_data[i.id] }} |
44 | {{ getWithSign(parseFloat(i.selected_by_percent) - parseFloat(last_gw_data[i.id])) }} |
45 | {{ parseFloat(sample_last_gw[i.id]).toFixed(2) }} |
46 | {{ (sample_estimated[i.id]).toFixed(2) }} |
47 | {{ element_type[i.element_type].short }} |
48 | {{ parseInt(i.now_cost)/10 }} |
49 | {{ i.id }} |
50 |
51 |
52 |
53 |
54 | Loading data... Please wait...
55 |

56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | "% with scripts=["main", "ownership_trend"] %" "% include 'footer.html' %" "% endwith %"
--------------------------------------------------------------------------------
/src/templates/history.html:
--------------------------------------------------------------------------------
1 | "% include 'header.html' %"
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Choose a season
11 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | | ID |
26 | Pos |
27 | Name |
28 | CV |
29 | Total |
30 |
31 | {{ gw }}
32 | |
33 |
34 |
35 |
36 |
37 | | {{ player[0] }} |
38 | {{ pos_dict[player_dict[player[0]].element_type] }} |
39 | {{ player_dict[player[0]].web_name }} |
40 | {{ _.round(player_dict[player[0]].now_cost / 10,1) }} |
41 | {{ player[1] }} |
42 |
43 | {{ _.get(player_gw_dict, `${player[0]}.${gw}.total`, "") }}
44 | |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
63 |
64 |
65 |
66 |
69 |
70 | "% with scripts=["main", "history"] %" "% include 'footer.html' %" "% endwith %"
--------------------------------------------------------------------------------
/src/static/js/history.js:
--------------------------------------------------------------------------------
1 |
2 | var app = new Vue({
3 | el: '#app',
4 | data: {
5 | seasons: seasons,
6 | selected_season: '',
7 | season_data: undefined,
8 | player_gw_dict: undefined,
9 | player_sums: undefined,
10 | players_sorted: undefined,
11 | table_ready: false,
12 | pos_dict: {1: 'G', 2: 'D', 3: 'M', 4: 'F'}
13 | },
14 | computed: {
15 | player_dict() {
16 | if (!this.season_data) { return undefined }
17 | let elements = this.season_data.elements
18 | return _.fromPairs(elements.map(i => [i.id, i]))
19 | }
20 | },
21 | methods: {
22 | update_season() {
23 |
24 | let pts_table = $("#pts_table")
25 | if (pts_table) {
26 | pts_table.DataTable().destroy();
27 | }
28 | this.table_ready = false;
29 |
30 | let s = this.selected_season
31 | if (s == '') {
32 | this.season_data = undefined
33 | this.player_gw_dict = undefined
34 | this.player_sums = undefined
35 | this.players_sorted = undefined
36 | return
37 | }
38 |
39 | read_local_file(`data/${s}/points.json`).then((points_data) => {
40 | read_local_file(`data/${s}/static.json`).then((static_data) => {
41 | let final_dict = {}
42 | _.each(points_data, (entries, gw) => { // (val, key)
43 | _.forEach(entries, entry => {
44 | let player = {
45 | 'id': entry.id,
46 | 'total': _.sum(entry.e.map(i => i.stats.map(j => j.points)).flat())
47 | }
48 | _.set(final_dict, `${entry.id}.${gw}`, player)
49 | })
50 | })
51 |
52 | this.player_gw_dict = Object.freeze(final_dict)
53 | this.player_sums = Object.freeze(_(final_dict).mapValues((val,key) => _.sumBy(val, 'total')).value())
54 | this.players_sorted = Object.freeze(_(this.player_sums).toPairs().orderBy([1], ['desc']).value())
55 |
56 | this.season_data = Object.freeze(static_data)
57 |
58 | this.$nextTick(() => {
59 | let table = $("#pts_table").DataTable({
60 | "order": [4],
61 | // "lengthChange": true,
62 | // // "pageLength": 100,
63 | "searching": true,
64 | // "info": false,
65 | "paging": true,
66 | "columnDefs": [],
67 | scrollX: true,
68 | buttons: [
69 | 'copy', 'csv'
70 | ]
71 | });
72 | table.cells("td").invalidate().draw();
73 | table.buttons().container()
74 | .appendTo('#buttons');
75 |
76 | this.table_ready = true;
77 | })
78 | })
79 | })
80 | },
81 | pts_color(pid, gw) {
82 | let pts = _.get(this.player_gw_dict, `${pid}.${gw}.total`, undefined)
83 | if (pts == undefined) { return "none" }
84 | let colors = d3.scaleLinear().domain([-100, -1, 0, 3, 15]).range(["#e19797", "#e19797", "#ffffff", "#ffffff", "#7FD3D9"])
85 | return colors(pts)
86 | }
87 | }
88 | })
89 |
90 | $(document).ready(() => {
91 |
92 | })
93 |
--------------------------------------------------------------------------------
/src/static/json/fpl_analytics.json:
--------------------------------------------------------------------------------
1 | [
2 | { "id": 2221044, "twitter": "sertalpbilal" },
3 | { "id": 367, "twitter": "FplSimpson" },
4 | { "id": 17973, "twitter": "FplStatsdan" },
5 | { "id": 44142, "twitter": "FF_Trout" },
6 | { "id": 2227, "twitter": "dilksybab" },
7 | { "id": 36828, "twitter": "theFPLkiwi" },
8 | { "id": 4473, "twitter": "jameso_12" },
9 | { "id": 159049, "twitter": "api_kakayou" },
10 | { "id": 2134733, "twitter": "usafpl" },
11 | { "id": 11891, "twitter": "CazzaFPL" },
12 | { "id": 115, "twitter": "PlanetosFPL" },
13 | { "id": 59177, "twitter": "analytic_fpl" },
14 | { "id": 262, "twitter": "wee_rogue" },
15 | { "id": 15673, "twitter": "mtthwmn" },
16 | { "id": 1339675, "twitter": "cmusson32" },
17 | { "id": 106304, "twitter": "FPLstudent2" },
18 | { "id": 10350, "twitter": "peterhandleson" },
19 | { "id": 293113, "twitter": "chrishp" },
20 | { "id": 2243390, "twitter": "plfantasy" },
21 | { "id": 741, "twitter": "FPLmeta" },
22 | { "id": 23491, "twitter": "FplAuto" },
23 | { "id": 227516, "twitter": "NourEldin_z3r0" },
24 | { "id": 23337, "twitter": "UkendtPerson45" },
25 | { "id": 3561, "twitter": "MikkelTokvam" },
26 | { "id": 210810, "twitter": "Klaudioz" },
27 | { "id": 204827, "twitter": "FantasianPL" },
28 | { "id": 10187, "twitter": "rama96ab" },
29 | { "id": 634300, "twitter": "FPL_LFE" },
30 | { "id": 17157, "twitter": "_jtreble" },
31 | { "id": 1341730, "twitter": "DanielSaetre" },
32 | { "id": 274376, "twitter": "FBinary01" },
33 | { "id": 3861, "twitter": "FPL_Data" },
34 | { "id": 2978, "twitter": "FPL_ElStatto" },
35 | { "id": 795, "twitter": "FplSigurd" },
36 | { "id": 726810, "twitter": "JoeBopara" },
37 | { "id": 4033, "twitter": "dLincolnlawyer" },
38 | { "id": 1188, "twitter": "FPL_Statoholic" },
39 | { "id": 184956, "twitter": "FplAmateur" },
40 | { "id": 4084, "twitter": "wolacola" },
41 | { "id": 16804, "twitter": "Hashtag_MEGA" },
42 | { "id": 2409, "twitter": "dav_fifa" },
43 | { "id": 393944, "twitter": "MaulanaRiandi" },
44 | { "id": 392517, "twitter": "Rak464" },
45 | { "id": 1310, "twitter": "ArneBarsnes" },
46 | { "id": 416185, "twitter": "FPL_PapaSmurf" },
47 | { "id": 33480, "twitter": "gezza96" },
48 | { "id": 7193, "twitter": "Raghy78d" },
49 | { "id": 26787, "twitter": "pdhFPL" },
50 | { "id": 5390907, "twitter": "NeilRankinZA" },
51 | { "id": 50155, "twitter": "EuanThompson" },
52 | { "id": 501, "twitter": "FPLrookie1" },
53 | { "id": 1280279, "twitter": "FplBudget" },
54 | { "id": 150618, "twitter": "FF_Vader" },
55 | { "id": 2977, "twitter": "alasdairtweets" },
56 | { "id": 53926, "twitter": "fpl_analytic" },
57 | { "id": 2072032, "twitter": "stephendeyoung" },
58 | { "id": 496, "twitter": "James_Ingram15" },
59 | { "id": 896, "twitter": "FPL_Kid" },
60 | { "id": 3137, "twitter": "fpl_jan" },
61 | { "id": 1480, "twitter": "FPL_Mezzala" },
62 | { "id": 69496, "twitter": "morstats" },
63 | { "id": 374292, "twitter": "FplOpinion" },
64 | { "id": 472, "twitter": "_NeilFPL" },
65 | { "id": 121535, "twitter": "UnderstandingF8" },
66 | { "id": 92246, "twitter": "EejRoberts" },
67 | { "id": 1531, "twitter": "FPL_Gents" },
68 | { "id": 4417, "twitter": "FPLHaggis" },
69 | { "id": 12322, "twitter": "Jumpthewave" },
70 | { "id": 3683, "twitter": "DarraghNoonan" },
71 | { "id": 1697, "twitter": "FPL_Chase" },
72 | { "id": 300408, "twitter": "jonwalker88" },
73 | { "id": 241725, "twitter": "TomDavies_17" },
74 | { "id": 32982, "twitter": "FodWeTrust" },
75 | { "id": 123767, "twitter": "FplPass" },
76 | { "id": 455, "twitter": "prparsons" },
77 | { "id": 1215, "twitter": "FplJeb" },
78 | { "id": 267, "twitter": "JosesBusDrivers" },
79 | { "id": 5063, "twitter": "hidethecactus" },
80 | { "id": 884492, "twitter": "tweetsofagooner" },
81 | { "id": 5365714, "twitter": "FplRafiki" },
82 | { "id": 43745, "twitter": "FPL_Audit" },
83 | { "id": 48534, "twitter": "DaveCZfpl" },
84 | { "id": 10744, "twitter": "PaulGiurculet" },
85 | { "id": 127899, "twitter": "k_rybaczuk" },
86 | { "id": 421075, "twitter": "CoalportGlen" },
87 | { "id": 22274, "twitter": "FPLvariance" },
88 | { "id": 9754, "twitter": "SchadenfreudeFF" },
89 | { "id": 3468, "twitter": "desphorin" },
90 | { "id": 372678, "twitter": "arnarrafn1" },
91 | { "id": 3966, "twitter": "FPL_SOS" }
92 | ]
93 |
--------------------------------------------------------------------------------
/src/static/js/top_squads.js:
--------------------------------------------------------------------------------
1 | var app = new Vue({
2 | el: '#app',
3 | data: {
4 | season: season,
5 | gw: gw,
6 | date: date,
7 | listdates: listdates,
8 | solutions: []
9 | },
10 | methods: {
11 | refresh_results() {
12 | season = this.season;
13 | gw = this.gw;
14 | date = this.date;
15 | load_all();
16 | },
17 | close_date() {
18 | $("#dateModal").modal('hide');
19 | },
20 | setSolutions(values) {
21 | this.solutions = _.cloneDeep(values);
22 | }
23 | },
24 | computed: {
25 | seasongwdate: {
26 | get: function() {
27 | return this.season + " / " + this.gw + " / " + this.date;
28 | },
29 | set: function(value) {
30 | let v = value.split(' / ');
31 | this.season = v[0];
32 | this.gw = v[1];
33 | this.date = v[2];
34 | this.refresh_results();
35 | }
36 | },
37 | parsed_solutions: function() {
38 | let solutions = this.solutions;
39 | solutions.forEach(
40 | function(i) {
41 | i.AO = (i.ownership.squad.reduce((sume, el) => sume + el, 0) / 15).toFixed(2);
42 | i.SO = (i.ownership.sum).toFixed(2);
43 | let weeks = Object.keys(i.lineup);
44 | let week_returns = Object.fromEntries(weeks.map(w => [w, Object.fromEntries(i.squad.map((j, k) => [j, i.xP[w][k]]))]));
45 |
46 |
47 | let lineup_points = Object.fromEntries(weeks.map(w => [w, i.lineup[w].map(p => week_returns[w][p])]));
48 | let pts_lineup = Object.values(lineup_points).map(w => w.reduce((a, b) => a + b, 0)).reduce((a, b) => a + b, 0);
49 | let pts_captain = Object.entries(i.captain).map(v => week_returns[v[0]][v[1]]).reduce((a, b) => a + b, 0);
50 | i.LxP = (pts_lineup + pts_captain).toFixed(2);
51 |
52 | let bench_points = Object.fromEntries(weeks.map(w => [w, Object.entries(i.bench[w]).map(k => week_returns[w][k[1]])]))
53 | let weighted_bench_pts = Object.values(bench_points).map(v => v.map((p, x) => 10 ** Math.min(-x, -1) * p));
54 | let pts_bench = Object.values(weighted_bench_pts).map(v => v.reduce((a, b) => a + b, 0)).reduce((a, b) => a + b, 0);
55 | i.WxP = (pts_lineup + pts_captain + pts_bench).toFixed(2);
56 |
57 | let pts_bb = Object.values(bench_points).map(v => v.reduce((a, b) => a + b, 0)).reduce((a, b) => a + b, 0);
58 | i.BBxP = (pts_lineup + pts_captain + pts_bb).toFixed(2);
59 |
60 | let squad_with_type = _.zip(i.squad, i.element_type)
61 | i.sorted_squad = squad_with_type.sort(function(a, b) {
62 | if (a[1] == b[1]) { return a[0] - b[0]; }
63 | return a[1] - b[1];
64 | })
65 | i.player_names = _.zipObject(i.squad, i.players);
66 |
67 | })
68 | return solutions;
69 | }
70 | }
71 | })
72 |
73 | function load_all() {
74 | $.ajax({
75 | type: "GET",
76 | url: `data/${season}/${gw}/${date}/output/iterative_model.json`,
77 | dataType: "json",
78 | success: function(data) {
79 | app.setSolutions(data);
80 | $(document).ready(function() {
81 | $('#top_squads_table').DataTable({
82 | searchBuilder: {},
83 | dom: 'Qfrtip',
84 | responsive: {
85 | details: {
86 | display: $.fn.dataTable.Responsive.display.modal({
87 | header: function(row) {
88 | var data = row.data();
89 | return 'Details for ' + data[0] + ' ' + data[1];
90 | }
91 | }),
92 | renderer: $.fn.dataTable.Responsive.renderer.tableAll({
93 | tableClass: 'table'
94 | })
95 | }
96 | }
97 | });
98 | });
99 | },
100 | error: function(xhr, status, error) {
101 | // app.setSolution(name, []);
102 | console.log(error);
103 | console.error(xhr, status, error);
104 | }
105 | });
106 | }
107 |
108 | $(document).ready(function() {
109 | load_all();
110 | });
--------------------------------------------------------------------------------
/src/templates/footer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | "% if no_ev != True %"
4 |
5 |
6 |
7 |
9 |
10 |
11 |
13 |
14 |
15 |
16 | "% endif %"
17 |
18 |
19 |
22 |
25 |
27 |
28 |
33 |
36 |
39 |
40 |
41 |
43 |
45 |
46 |
47 |
50 |
51 |
52 |
53 |
54 |
55 |
57 |
58 |
59 |
60 |
63 |
64 |
65 |
66 |
81 |
82 | "% for item in scripts %"
83 |
84 | "% endfor %"
85 |
86 |
89 |
90 |