AlexKurian commited on
Commit
9e7bb72
·
1 Parent(s): baac3f0

Backend (LFS enabled)

Browse files
.env.example ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # Google Gemini API Configuration
2
+ # Get your API key from https://ai.google.dev/
3
+ GROQ_API_KEY=your-groq-api-key-here
4
+
5
+ # Flask Configuration
6
+ FLASK_ENV=development
7
+ FLASK_DEBUG=True
8
+ FLASK_PORT=5000
9
+ AMBEE_DATA_KEY=your-ambee-data-api-key-here
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.faiss filter=lfs diff=lfs merge=lfs -text
37
+ *.xlsx filter=lfs diff=lfs merge=lfs -text
38
+ *.png filter=lfs diff=lfs merge=lfs -text
.gitignore CHANGED
@@ -1,3 +1,75 @@
1
- papers/*.pdf
2
  .env
 
 
 
 
 
 
3
  __pycache__/
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Environment variables and secrets
2
  .env
3
+ .env.local
4
+ .env.*.local
5
+ *.key
6
+ *.pem
7
+
8
+ # Python
9
  __pycache__/
10
+ *.py[cod]
11
+ *$py.class
12
+ *.so
13
+ .Python
14
+ build/
15
+ develop-eggs/
16
+ dist/
17
+ downloads/
18
+ eggs/
19
+ .eggs/
20
+ lib/
21
+ lib64/
22
+ parts/
23
+ sdist/
24
+ var/
25
+ wheels/
26
+ pip-wheel-metadata/
27
+ share/python-wheels/
28
+ *.egg-info/
29
+ .installed.cfg
30
+ *.egg
31
+ MANIFEST
32
+
33
+ # Virtual environments
34
+ venv/
35
+ ENV/
36
+ env/
37
+ .venv
38
+
39
+ # IDE
40
+ .vscode/
41
+ .idea/
42
+ *.swp
43
+ *.swo
44
+ *~
45
+ .DS_Store
46
+
47
+ # Logs
48
+ *.log
49
+ logs/
50
+
51
+ # Database and cache
52
+ *.db
53
+ *.sqlite
54
+ *.sqlite3
55
+
56
+ *.cache
57
+
58
+ # Node (if any Node modules in backend)
59
+ node_modules/
60
+ npm-debug.log
61
+
62
+ # OS
63
+ .DS_Store
64
+ Thumbs.db
65
+
66
+ # Research papers and large files
67
+ papers/
68
+ *.pdf
69
+ *.tar.gz
70
+ *.zip
71
+
72
+ # Temporary files
73
+ *.tmp
74
+ .temp/
75
+
AQI_Combined.csv ADDED
@@ -0,0 +1,2980 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Date,AQI
2
+ 01-01-2017,345
3
+ 02-01-2017,337
4
+ 03-01-2017,331
5
+ 04-01-2017,381
6
+ 05-01-2017,321
7
+ 06-01-2017,351
8
+ 07-01-2017,278
9
+ 08-01-2017,288
10
+ 09-01-2017,308
11
+ 10-01-2017,247
12
+ 11-01-2017,280
13
+ 12-01-2017,323
14
+ 13-01-2017,326
15
+ 14-01-2017,384
16
+ 15-01-2017,326
17
+ 16-01-2017,355
18
+ 17-01-2017,245
19
+ 18-01-2017,317
20
+ 19-01-2017,300
21
+ 20-01-2017,216
22
+ 21-01-2017,286
23
+ 22-01-2017,365
24
+ 23-01-2017,309
25
+ 24-01-2017,336
26
+ 25-01-2017,366
27
+ 26-01-2017,324
28
+ 27-01-2017,118
29
+ 28-01-2017,212
30
+ 29-01-2017,211
31
+ 31-01-2017,366
32
+ 01-02-2017,273
33
+ 02-02-2017,327
34
+ 03-02-2017,314
35
+ 04-02-2017,336
36
+ 05-02-2017,260
37
+ 06-02-2017,306
38
+ 07-02-2017,148
39
+ 08-02-2017,254
40
+ 09-02-2017,268
41
+ 10-02-2017,249
42
+ 11-02-2017,239
43
+ 12-02-2017,274
44
+ 13-02-2017,279
45
+ 14-02-2017,303
46
+ 15-02-2017,294
47
+ 16-02-2017,284
48
+ 17-02-2017,294
49
+ 18-02-2017,236
50
+ 19-02-2017,237
51
+ 20-02-2017,243
52
+ 21-02-2017,254
53
+ 22-02-2017,236
54
+ 23-02-2017,182
55
+ 24-02-2017,226
56
+ 25-02-2017,208
57
+ 26-02-2017,256
58
+ 27-02-2017,226
59
+ 28-02-2017,266
60
+ 01-03-2017,290
61
+ 02-03-2017,258
62
+ 03-03-2017,141
63
+ 04-03-2017,173
64
+ 05-03-2017,200
65
+ 06-03-2017,177
66
+ 07-03-2017,197
67
+ 08-03-2017,194
68
+ 10-03-2017,90
69
+ 11-03-2017,68
70
+ 12-03-2017,104
71
+ 13-03-2017,150
72
+ 14-03-2017,123
73
+ 15-03-2017,214
74
+ 16-03-2017,207
75
+ 17-03-2017,226
76
+ 18-03-2017,185
77
+ 19-03-2017,222
78
+ 20-03-2017,241
79
+ 21-03-2017,251
80
+ 22-03-2017,242
81
+ 23-03-2017,246
82
+ 24-03-2017,244
83
+ 25-03-2017,197
84
+ 26-03-2017,177
85
+ 27-03-2017,164
86
+ 28-03-2017,218
87
+ 30-03-2017,151
88
+ 31-03-2017,176
89
+ 01-04-2017,223
90
+ 02-04-2017,177
91
+ 03-04-2017,154
92
+ 04-04-2017,165
93
+ 05-04-2017,108
94
+ 06-04-2017,109
95
+ 07-04-2017,351
96
+ 08-04-2017,269
97
+ 09-04-2017,130
98
+ 10-04-2017,144
99
+ 11-04-2017,162
100
+ 12-04-2017,201
101
+ 13-04-2017,282
102
+ 14-04-2017,290
103
+ 15-04-2017,307
104
+ 16-04-2017,291
105
+ 17-04-2017,276
106
+ 18-04-2017,307
107
+ 19-04-2017,262
108
+ 20-04-2017,221
109
+ 21-04-2017,234
110
+ 22-04-2017,244
111
+ 23-04-2017,193
112
+ 24-04-2017,164
113
+ 25-04-2017,189
114
+ 26-04-2017,153
115
+ 28-04-2017,183
116
+ 29-04-2017,190
117
+ 30-04-2017,186
118
+ 01-05-2017,158
119
+ 02-05-2017,240
120
+ 03-05-2017,297
121
+ 04-05-2017,214
122
+ 05-05-2017,332
123
+ 06-05-2017,284
124
+ 07-05-2017,293
125
+ 08-05-2017,278
126
+ 09-05-2017,259
127
+ 10-05-2017,187
128
+ 11-05-2017,218
129
+ 12-05-2017,365
130
+ 13-05-2017,316
131
+ 14-05-2017,284
132
+ 15-05-2017,248
133
+ 16-05-2017,272
134
+ 17-05-2017,183
135
+ 18-05-2017,235
136
+ 19-05-2017,239
137
+ 20-05-2017,221
138
+ 21-05-2017,339
139
+ 22-05-2017,228
140
+ 23-05-2017,222
141
+ 24-05-2017,224
142
+ 25-05-2017,196
143
+ 26-05-2017,250
144
+ 27-05-2017,197
145
+ 28-05-2017,136
146
+ 29-05-2017,130
147
+ 30-05-2017,162
148
+ 31-05-2017,126
149
+ 01-06-2017,200
150
+ 02-06-2017,220
151
+ 03-06-2017,215
152
+ 04-06-2017,237
153
+ 05-06-2017,204
154
+ 06-06-2017,204
155
+ 07-06-2017,126
156
+ 08-06-2017,103
157
+ 09-06-2017,141
158
+ 10-06-2017,177
159
+ 11-06-2017,118
160
+ 12-06-2017,125
161
+ 13-06-2017,139
162
+ 14-06-2017,182
163
+ 15-06-2017,231
164
+ 16-06-2017,154
165
+ 17-06-2017,100
166
+ 18-06-2017,117
167
+ 19-06-2017,210
168
+ 20-06-2017,89
169
+ 21-06-2017,102
170
+ 22-06-2017,124
171
+ 23-06-2017,148
172
+ 24-06-2017,159
173
+ 25-06-2017,195
174
+ 26-06-2017,160
175
+ 27-06-2017,135
176
+ 28-06-2017,102
177
+ 29-06-2017,92
178
+ 30-06-2017,76
179
+ 01-07-2017,114
180
+ 02-07-2017,102
181
+ 03-07-2017,97
182
+ 07-07-2017,108
183
+ 08-07-2017,93
184
+ 09-07-2017,148
185
+ 10-07-2017,117
186
+ 11-07-2017,93
187
+ 12-07-2017,62
188
+ 13-07-2017,71
189
+ 14-07-2017,64
190
+ 15-07-2017,79
191
+ 16-07-2017,90
192
+ 17-07-2017,95
193
+ 18-07-2017,100
194
+ 19-07-2017,106
195
+ 20-07-2017,88
196
+ 21-07-2017,69
197
+ 22-07-2017,55
198
+ 23-07-2017,71
199
+ 24-07-2017,60
200
+ 25-07-2017,68
201
+ 26-07-2017,63
202
+ 27-07-2017,75
203
+ 28-07-2017,70
204
+ 29-07-2017,96
205
+ 30-07-2017,43
206
+ 31-07-2017,48
207
+ 24-08-2017,116
208
+ 25-08-2017,141
209
+ 26-08-2017,114
210
+ 27-08-2017,104
211
+ 28-08-2017,70
212
+ 29-08-2017,68
213
+ 30-08-2017,77
214
+ 31-08-2017,92
215
+ 01-09-2017,69
216
+ 02-09-2017,65
217
+ 03-09-2017,80
218
+ 04-09-2017,106
219
+ 05-09-2017,91
220
+ 06-09-2017,86
221
+ 07-09-2017,82
222
+ 08-09-2017,152
223
+ 09-09-2017,125
224
+ 10-09-2017,154
225
+ 11-09-2017,149
226
+ 12-09-2017,118
227
+ 13-09-2017,141
228
+ 14-09-2017,191
229
+ 15-09-2017,215
230
+ 16-09-2017,137
231
+ 17-09-2017,126
232
+ 18-09-2017,124
233
+ 19-09-2017,152
234
+ 20-09-2017,174
235
+ 21-09-2017,175
236
+ 22-09-2017,67
237
+ 23-09-2017,59
238
+ 24-09-2017,105
239
+ 25-09-2017,143
240
+ 26-09-2017,177
241
+ 27-09-2017,206
242
+ 28-09-2017,205
243
+ 29-09-2017,208
244
+ 30-09-2017,192
245
+ 01-10-2017,170
246
+ 02-10-2017,144
247
+ 03-10-2017,159
248
+ 04-10-2017,220
249
+ 05-10-2017,254
250
+ 06-10-2017,187
251
+ 08-10-2017,234
252
+ 09-10-2017,268
253
+ 10-10-2017,238
254
+ 11-10-2017,252
255
+ 12-10-2017,273
256
+ 13-10-2017,262
257
+ 15-10-2017,272
258
+ 16-10-2017,274
259
+ 17-10-2017,316
260
+ 18-10-2017,286
261
+ 19-10-2017,293
262
+ 20-10-2017,357
263
+ 21-10-2017,396
264
+ 22-10-2017,311
265
+ 23-10-2017,287
266
+ 24-10-2017,294
267
+ 25-10-2017,352
268
+ 26-10-2017,324
269
+ 27-10-2017,332
270
+ 28-10-2017,347
271
+ 29-10-2017,336
272
+ 30-10-2017,328
273
+ 31-10-2017,335
274
+ 01-11-2017,354
275
+ 02-11-2017,285
276
+ 03-11-2017,327
277
+ 04-11-2017,357
278
+ 05-11-2017,363
279
+ 06-11-2017,356
280
+ 07-11-2017,449
281
+ 08-11-2017,487
282
+ 09-11-2017,487
283
+ 10-11-2017,470
284
+ 11-11-2017,431
285
+ 12-11-2017,491
286
+ 13-11-2017,465
287
+ 14-11-2017,384
288
+ 15-11-2017,354
289
+ 16-11-2017,363
290
+ 17-11-2017,307
291
+ 18-11-2017,299
292
+ 19-11-2017,291
293
+ 20-11-2017,325
294
+ 21-11-2017,325
295
+ 22-11-2017,328
296
+ 23-11-2017,316
297
+ 24-11-2017,335
298
+ 25-11-2017,337
299
+ 26-11-2017,361
300
+ 27-11-2017,365
301
+ 28-11-2017,327
302
+ 29-11-2017,344
303
+ 30-11-2017,368
304
+ 01-12-2017,343
305
+ 02-12-2017,333
306
+ 03-12-2017,355
307
+ 04-12-2017,391
308
+ 05-12-2017,380
309
+ 06-12-2017,296
310
+ 07-12-2017,191
311
+ 08-12-2017,217
312
+ 09-12-2017,308
313
+ 10-12-2017,378
314
+ 11-12-2017,363
315
+ 12-12-2017,225
316
+ 13-12-2017,270
317
+ 14-12-2017,285
318
+ 15-12-2017,284
319
+ 16-12-2017,223
320
+ 17-12-2017,199
321
+ 18-12-2017,236
322
+ 19-12-2017,297
323
+ 20-12-2017,372
324
+ 21-12-2017,446
325
+ 22-12-2017,321
326
+ 23-12-2017,228
327
+ 24-12-2017,309
328
+ 25-12-2017,384
329
+ 26-12-2017,342
330
+ 27-12-2017,360
331
+ 28-12-2017,383
332
+ 29-12-2017,367
333
+ 30-12-2017,400
334
+ 31-12-2017,406
335
+ 01-01-2018,406
336
+ 02-01-2018,418
337
+ 03-01-2018,382
338
+ 04-01-2018,366
339
+ 05-01-2018,390
340
+ 06-01-2018,405
341
+ 07-01-2018,355
342
+ 08-01-2018,288
343
+ 09-01-2018,359
344
+ 10-01-2018,352
345
+ 11-01-2018,309
346
+ 12-01-2018,298
347
+ 13-01-2018,359
348
+ 14-01-2018,321
349
+ 15-01-2018,331
350
+ 16-01-2018,255
351
+ 17-01-2018,353
352
+ 18-01-2018,400
353
+ 19-01-2018,406
354
+ 20-01-2018,323
355
+ 21-01-2018,276
356
+ 22-01-2018,342
357
+ 23-01-2018,324
358
+ 24-01-2018,270
359
+ 25-01-2018,243
360
+ 26-01-2018,261
361
+ 27-01-2018,286
362
+ 28-01-2018,300
363
+ 29-01-2018,302
364
+ 30-01-2018,270
365
+ 31-01-2018,244
366
+ 01-02-2018,274
367
+ 02-02-2018,293
368
+ 03-02-2018,243
369
+ 04-02-2018,256
370
+ 05-02-2018,291
371
+ 06-02-2018,287
372
+ 07-02-2018,261
373
+ 08-02-2018,308
374
+ 09-02-2018,335
375
+ 10-02-2018,341
376
+ 11-02-2018,304
377
+ 12-02-2018,178
378
+ 13-02-2018,191
379
+ 14-02-2018,187
380
+ 15-02-2018,187
381
+ 16-02-2018,235
382
+ 17-02-2018,256
383
+ 18-02-2018,257
384
+ 19-02-2018,294
385
+ 20-02-2018,296
386
+ 21-02-2018,308
387
+ 22-02-2018,300
388
+ 23-02-2018,302
389
+ 24-02-2018,320
390
+ 25-02-2018,266
391
+ 26-02-2018,156
392
+ 27-02-2018,160
393
+ 28-02-2018,272
394
+ 01-03-2018,257
395
+ 02-03-2018,260
396
+ 03-03-2018,203
397
+ 04-03-2018,178
398
+ 05-03-2018,172
399
+ 06-03-2018,167
400
+ 07-03-2018,185
401
+ 08-03-2018,245
402
+ 09-03-2018,280
403
+ 10-03-2018,170
404
+ 11-03-2018,170
405
+ 12-03-2018,214
406
+ 13-03-2018,217
407
+ 14-03-2018,267
408
+ 15-03-2018,297
409
+ 16-03-2018,256
410
+ 17-03-2018,186
411
+ 18-03-2018,169
412
+ 19-03-2018,173
413
+ 20-03-2018,217
414
+ 21-03-2018,289
415
+ 22-03-2018,186
416
+ 23-03-2018,151
417
+ 24-03-2018,175
418
+ 25-03-2018,195
419
+ 26-03-2018,183
420
+ 27-03-2018,230
421
+ 28-03-2018,255
422
+ 29-03-2018,213
423
+ 30-03-2018,218
424
+ 31-03-2018,256
425
+ 01-04-2018,195
426
+ 02-04-2018,219
427
+ 03-04-2018,207
428
+ 04-04-2018,203
429
+ 05-04-2018,290
430
+ 06-04-2018,208
431
+ 07-04-2018,143
432
+ 08-04-2018,141
433
+ 09-04-2018,120
434
+ 10-04-2018,230
435
+ 11-04-2018,141
436
+ 12-04-2018,99
437
+ 13-04-2018,188
438
+ 14-04-2018,217
439
+ 15-04-2018,224
440
+ 16-04-2018,285
441
+ 17-04-2018,244
442
+ 18-04-2018,228
443
+ 19-04-2018,244
444
+ 20-04-2018,246
445
+ 21-04-2018,356
446
+ 22-04-2018,211
447
+ 23-04-2018,160
448
+ 24-04-2018,254
449
+ 25-04-2018,266
450
+ 26-04-2018,313
451
+ 27-04-2018,310
452
+ 28-04-2018,301
453
+ 29-04-2018,248
454
+ 30-04-2018,209
455
+ 01-05-2018,177
456
+ 02-05-2018,153
457
+ 03-05-2018,160
458
+ 04-05-2018,139
459
+ 05-05-2018,173
460
+ 06-05-2018,269
461
+ 07-05-2018,211
462
+ 08-05-2018,208
463
+ 09-05-2018,163
464
+ 10-05-2018,190
465
+ 11-05-2018,256
466
+ 12-05-2018,304
467
+ 13-05-2018,215
468
+ 14-05-2018,130
469
+ 15-05-2018,164
470
+ 16-05-2018,190
471
+ 17-05-2018,243
472
+ 18-05-2018,225
473
+ 19-05-2018,242
474
+ 20-05-2018,199
475
+ 21-05-2018,183
476
+ 22-05-2018,212
477
+ 23-05-2018,301
478
+ 24-05-2018,252
479
+ 25-05-2018,251
480
+ 26-05-2018,235
481
+ 27-05-2018,277
482
+ 28-05-2018,275
483
+ 29-05-2018,285
484
+ 30-05-2018,281
485
+ 31-05-2018,208
486
+ 01-06-2018,198
487
+ 02-06-2018,173
488
+ 03-06-2018,174
489
+ 04-06-2018,183
490
+ 05-06-2018,169
491
+ 06-06-2018,136
492
+ 07-06-2018,164
493
+ 08-06-2018,179
494
+ 09-06-2018,127
495
+ 10-06-2018,158
496
+ 11-06-2018,183
497
+ 12-06-2018,296
498
+ 13-06-2018,445
499
+ 14-06-2018,431
500
+ 15-06-2018,447
501
+ 16-06-2018,369
502
+ 17-06-2018,289
503
+ 18-06-2018,171
504
+ 19-06-2018,121
505
+ 20-06-2018,177
506
+ 21-06-2018,226
507
+ 22-06-2018,236
508
+ 23-06-2018,218
509
+ 24-06-2018,183
510
+ 25-06-2018,192
511
+ 26-06-2018,110
512
+ 27-06-2018,93
513
+ 28-06-2018,76
514
+ 29-06-2018,72
515
+ 30-06-2018,96
516
+ 01-07-2018,92
517
+ 02-07-2018,106
518
+ 03-07-2018,114
519
+ 04-07-2018,137
520
+ 05-07-2018,123
521
+ 06-07-2018,147
522
+ 07-07-2018,151
523
+ 08-07-2018,182
524
+ 09-07-2018,157
525
+ 10-07-2018,134
526
+ 11-07-2018,106
527
+ 12-07-2018,143
528
+ 13-07-2018,99
529
+ 14-07-2018,76
530
+ 15-07-2018,76
531
+ 16-07-2018,87
532
+ 17-07-2018,91
533
+ 18-07-2018,106
534
+ 19-07-2018,88
535
+ 20-07-2018,80
536
+ 21-07-2018,111
537
+ 22-07-2018,61
538
+ 23-07-2018,67
539
+ 24-07-2018,90
540
+ 25-07-2018,78
541
+ 26-07-2018,74
542
+ 27-07-2018,54
543
+ 28-07-2018,57
544
+ 29-07-2018,90
545
+ 30-07-2018,115
546
+ 31-07-2018,132
547
+ 01-08-2018,153
548
+ 02-08-2018,235
549
+ 03-08-2018,213
550
+ 04-08-2018,216
551
+ 05-08-2018,241
552
+ 06-08-2018,133
553
+ 07-08-2018,113
554
+ 08-08-2018,86
555
+ 09-08-2018,88
556
+ 10-08-2018,102
557
+ 11-08-2018,96
558
+ 12-08-2018,83
559
+ 13-08-2018,94
560
+ 14-08-2018,117
561
+ 15-08-2018,120
562
+ 16-08-2018,86
563
+ 17-08-2018,84
564
+ 18-08-2018,80
565
+ 19-08-2018,89
566
+ 20-08-2018,137
567
+ 21-08-2018,87
568
+ 22-08-2018,78
569
+ 23-08-2018,69
570
+ 24-08-2018,80
571
+ 25-08-2018,89
572
+ 26-08-2018,78
573
+ 27-08-2018,62
574
+ 28-08-2018,71
575
+ 29-08-2018,68
576
+ 30-08-2018,78
577
+ 31-08-2018,127
578
+ 01-09-2018,114
579
+ 02-09-2018,106
580
+ 03-09-2018,73
581
+ 04-09-2018,112
582
+ 05-09-2018,90
583
+ 06-09-2018,137
584
+ 07-09-2018,91
585
+ 08-09-2018,86
586
+ 09-09-2018,61
587
+ 10-09-2018,84
588
+ 11-09-2018,79
589
+ 12-09-2018,90
590
+ 13-09-2018,103
591
+ 14-09-2018,102
592
+ 15-09-2018,118
593
+ 16-09-2018,121
594
+ 17-09-2018,135
595
+ 18-09-2018,150
596
+ 19-09-2018,154
597
+ 20-09-2018,164
598
+ 21-09-2018,143
599
+ 22-09-2018,54
600
+ 23-09-2018,62
601
+ 24-09-2018,52
602
+ 25-09-2018,55
603
+ 26-09-2018,88
604
+ 27-09-2018,151
605
+ 28-09-2018,197
606
+ 29-09-2018,219
607
+ 30-09-2018,158
608
+ 01-10-2018,148
609
+ 02-10-2018,202
610
+ 03-10-2018,143
611
+ 04-10-2018,201
612
+ 05-10-2018,256
613
+ 06-10-2018,209
614
+ 07-10-2018,181
615
+ 08-10-2018,246
616
+ 09-10-2018,251
617
+ 10-10-2018,241
618
+ 11-10-2018,210
619
+ 12-10-2018,154
620
+ 13-10-2018,262
621
+ 14-10-2018,204
622
+ 15-10-2018,246
623
+ 16-10-2018,291
624
+ 17-10-2018,313
625
+ 18-10-2018,297
626
+ 19-10-2018,276
627
+ 20-10-2018,326
628
+ 21-10-2018,292
629
+ 22-10-2018,272
630
+ 23-10-2018,254
631
+ 24-10-2018,328
632
+ 25-10-2018,331
633
+ 26-10-2018,361
634
+ 27-10-2018,350
635
+ 28-10-2018,366
636
+ 29-10-2018,367
637
+ 30-10-2018,401
638
+ 31-10-2018,358
639
+ 01-11-2018,393
640
+ 02-11-2018,370
641
+ 03-11-2018,340
642
+ 04-11-2018,171
643
+ 05-11-2018,426
644
+ 06-11-2018,338
645
+ 07-11-2018,281
646
+ 08-11-2018,390
647
+ 09-11-2018,423
648
+ 10-11-2018,401
649
+ 11-11-2018,405
650
+ 12-11-2018,399
651
+ 13-11-2018,409
652
+ 14-11-2018,312
653
+ 15-11-2018,217
654
+ 16-11-2018,285
655
+ 17-11-2018,267
656
+ 18-11-2018,311
657
+ 19-11-2018,330
658
+ 20-11-2018,374
659
+ 21-11-2018,374
660
+ 22-11-2018,273
661
+ 23-11-2018,337
662
+ 24-11-2018,249
663
+ 25-11-2018,262
664
+ 26-11-2018,336
665
+ 27-11-2018,352
666
+ 28-11-2018,316
667
+ 29-11-2018,358
668
+ 30-11-2018,352
669
+ 01-12-2018,306
670
+ 02-12-2018,297
671
+ 03-12-2018,328
672
+ 04-12-2018,356
673
+ 05-12-2018,331
674
+ 06-12-2018,349
675
+ 07-12-2018,348
676
+ 08-12-2018,347
677
+ 09-12-2018,374
678
+ 10-12-2018,403
679
+ 11-12-2018,413
680
+ 12-12-2018,392
681
+ 13-12-2018,194
682
+ 14-12-2018,249
683
+ 15-12-2018,261
684
+ 16-12-2018,282
685
+ 17-12-2018,337
686
+ 18-12-2018,353
687
+ 19-12-2018,353
688
+ 20-12-2018,394
689
+ 21-12-2018,386
690
+ 22-12-2018,421
691
+ 23-12-2018,450
692
+ 24-12-2018,448
693
+ 25-12-2018,409
694
+ 26-12-2018,384
695
+ 27-12-2018,399
696
+ 28-12-2018,392
697
+ 29-12-2018,385
698
+ 30-12-2018,415
699
+ 31-12-2018,420
700
+ 01-01-2019,393
701
+ 02-01-2019,430
702
+ 03-01-2019,444
703
+ 04-01-2019,386
704
+ 05-01-2019,407
705
+ 06-01-2019,336
706
+ 07-01-2019,333
707
+ 08-01-2019,320
708
+ 09-01-2019,288
709
+ 10-01-2019,292
710
+ 11-01-2019,371
711
+ 12-01-2019,423
712
+ 13-01-2019,414
713
+ 14-01-2019,247
714
+ 15-01-2019,237
715
+ 16-01-2019,371
716
+ 17-01-2019,440
717
+ 18-01-2019,397
718
+ 19-01-2019,378
719
+ 20-01-2019,404
720
+ 21-01-2019,346
721
+ 22-01-2019,104
722
+ 23-01-2019,212
723
+ 24-01-2019,311
724
+ 25-01-2019,151
725
+ 26-01-2019,233
726
+ 27-01-2019,262
727
+ 28-01-2019,267
728
+ 29-01-2019,276
729
+ 30-01-2019,327
730
+ 31-01-2019,359
731
+ 01-02-2019,306
732
+ 02-02-2019,332
733
+ 03-02-2019,289
734
+ 04-02-2019,288
735
+ 05-02-2019,382
736
+ 06-02-2019,352
737
+ 07-02-2019,176
738
+ 08-02-2019,144
739
+ 09-02-2019,158
740
+ 10-02-2019,276
741
+ 11-02-2019,309
742
+ 12-02-2019,342
743
+ 13-02-2019,367
744
+ 14-02-2019,340
745
+ 15-02-2019,245
746
+ 16-02-2019,276
747
+ 17-02-2019,222
748
+ 18-02-2019,255
749
+ 19-02-2019,221
750
+ 20-02-2019,271
751
+ 21-02-2019,144
752
+ 22-02-2019,186
753
+ 23-02-2019,113
754
+ 24-02-2019,190
755
+ 25-02-2019,217
756
+ 26-02-2019,116
757
+ 27-02-2019,100
758
+ 28-02-2019,170
759
+ 01-03-2019,225
760
+ 02-03-2019,217
761
+ 03-03-2019,117
762
+ 04-03-2019,234
763
+ 05-03-2019,140
764
+ 06-03-2019,186
765
+ 07-03-2019,217
766
+ 08-03-2019,197
767
+ 09-03-2019,187
768
+ 10-03-2019,160
769
+ 11-03-2019,187
770
+ 12-03-2019,134
771
+ 13-03-2019,178
772
+ 14-03-2019,266
773
+ 15-03-2019,154
774
+ 16-03-2019,139
775
+ 17-03-2019,192
776
+ 18-03-2019,196
777
+ 19-03-2019,216
778
+ 20-03-2019,208
779
+ 21-03-2019,242
780
+ 22-03-2019,114
781
+ 23-03-2019,132
782
+ 24-03-2019,121
783
+ 25-03-2019,139
784
+ 26-03-2019,159
785
+ 27-03-2019,171
786
+ 28-03-2019,234
787
+ 29-03-2019,232
788
+ 30-03-2019,233
789
+ 31-03-2019,182
790
+ 01-04-2019,151
791
+ 02-04-2019,198
792
+ 03-04-2019,187
793
+ 04-04-2019,254
794
+ 05-04-2019,264
795
+ 06-04-2019,266
796
+ 07-04-2019,302
797
+ 08-04-2019,264
798
+ 09-04-2019,204
799
+ 10-04-2019,238
800
+ 11-04-2019,183
801
+ 12-04-2019,212
802
+ 13-04-2019,170
803
+ 14-04-2019,137
804
+ 15-04-2019,168
805
+ 16-04-2019,180
806
+ 17-04-2019,84
807
+ 18-04-2019,114
808
+ 19-04-2019,157
809
+ 20-04-2019,198
810
+ 21-04-2019,203
811
+ 22-04-2019,236
812
+ 23-04-2019,247
813
+ 24-04-2019,221
814
+ 25-04-2019,251
815
+ 26-04-2019,215
816
+ 27-04-2019,256
817
+ 28-04-2019,264
818
+ 29-04-2019,248
819
+ 30-04-2019,250
820
+ 01-05-2019,295
821
+ 02-05-2019,257
822
+ 03-05-2019,183
823
+ 04-05-2019,182
824
+ 05-05-2019,227
825
+ 06-05-2019,249
826
+ 07-05-2019,294
827
+ 08-05-2019,357
828
+ 09-05-2019,347
829
+ 10-05-2019,277
830
+ 11-05-2019,251
831
+ 12-05-2019,334
832
+ 13-05-2019,322
833
+ 14-05-2019,153
834
+ 15-05-2019,186
835
+ 16-05-2019,187
836
+ 17-05-2019,265
837
+ 18-05-2019,105
838
+ 19-05-2019,185
839
+ 20-05-2019,206
840
+ 21-05-2019,190
841
+ 22-05-2019,158
842
+ 23-05-2019,223
843
+ 24-05-2019,123
844
+ 25-05-2019,121
845
+ 26-05-2019,142
846
+ 27-05-2019,160
847
+ 28-05-2019,177
848
+ 29-05-2019,185
849
+ 30-05-2019,218
850
+ 31-05-2019,280
851
+ 01-06-2019,264
852
+ 02-06-2019,201
853
+ 03-06-2019,261
854
+ 04-06-2019,169
855
+ 05-06-2019,192
856
+ 06-06-2019,206
857
+ 07-06-2019,173
858
+ 08-06-2019,181
859
+ 09-06-2019,239
860
+ 10-06-2019,207
861
+ 11-06-2019,307
862
+ 12-06-2019,233
863
+ 13-06-2019,175
864
+ 14-06-2019,171
865
+ 15-06-2019,224
866
+ 16-06-2019,178
867
+ 17-06-2019,123
868
+ 18-06-2019,97
869
+ 19-06-2019,199
870
+ 20-06-2019,141
871
+ 21-06-2019,177
872
+ 22-06-2019,189
873
+ 23-06-2019,148
874
+ 24-06-2019,109
875
+ 25-06-2019,133
876
+ 26-06-2019,162
877
+ 27-06-2019,184
878
+ 28-06-2019,191
879
+ 29-06-2019,231
880
+ 30-06-2019,237
881
+ 01-07-2019,183
882
+ 02-07-2019,218
883
+ 03-07-2019,136
884
+ 04-07-2019,103
885
+ 05-07-2019,106
886
+ 06-07-2019,88
887
+ 07-07-2019,91
888
+ 08-07-2019,127
889
+ 09-07-2019,126
890
+ 10-07-2019,148
891
+ 11-07-2019,317
892
+ 12-07-2019,272
893
+ 13-07-2019,275
894
+ 14-07-2019,235
895
+ 15-07-2019,168
896
+ 16-07-2019,93
897
+ 17-07-2019,73
898
+ 18-07-2019,85
899
+ 19-07-2019,145
900
+ 20-07-2019,124
901
+ 21-07-2019,108
902
+ 22-07-2019,112
903
+ 23-07-2019,148
904
+ 24-07-2019,164
905
+ 25-07-2019,78
906
+ 26-07-2019,81
907
+ 27-07-2019,61
908
+ 28-07-2019,64
909
+ 29-07-2019,65
910
+ 30-07-2019,80
911
+ 31-07-2019,80
912
+ 01-08-2019,89
913
+ 02-08-2019,84
914
+ 03-08-2019,89
915
+ 04-08-2019,87
916
+ 05-08-2019,105
917
+ 06-08-2019,76
918
+ 07-08-2019,75
919
+ 08-08-2019,79
920
+ 09-08-2019,75
921
+ 10-08-2019,59
922
+ 11-08-2019,68
923
+ 12-08-2019,57
924
+ 13-08-2019,69
925
+ 14-08-2019,63
926
+ 15-08-2019,75
927
+ 16-08-2019,53
928
+ 17-08-2019,49
929
+ 18-08-2019,49
930
+ 19-08-2019,81
931
+ 20-08-2019,97
932
+ 21-08-2019,109
933
+ 22-08-2019,123
934
+ 23-08-2019,129
935
+ 24-08-2019,147
936
+ 25-08-2019,91
937
+ 26-08-2019,66
938
+ 27-08-2019,117
939
+ 28-08-2019,105
940
+ 29-08-2019,91
941
+ 30-08-2019,101
942
+ 31-08-2019,114
943
+ 01-09-2019,108
944
+ 02-09-2019,96
945
+ 03-09-2019,141
946
+ 04-09-2019,173
947
+ 05-09-2019,95
948
+ 06-09-2019,90
949
+ 07-09-2019,82
950
+ 08-09-2019,77
951
+ 09-09-2019,108
952
+ 10-09-2019,137
953
+ 11-09-2019,168
954
+ 12-09-2019,151
955
+ 13-09-2019,86
956
+ 14-09-2019,72
957
+ 15-09-2019,72
958
+ 16-09-2019,105
959
+ 17-09-2019,135
960
+ 18-09-2019,110
961
+ 19-09-2019,66
962
+ 20-09-2019,90
963
+ 21-09-2019,114
964
+ 22-09-2019,67
965
+ 23-09-2019,83
966
+ 24-09-2019,94
967
+ 25-09-2019,90
968
+ 26-09-2019,74
969
+ 27-09-2019,63
970
+ 28-09-2019,67
971
+ 29-09-2019,60
972
+ 30-09-2019,68
973
+ 01-10-2019,93
974
+ 02-10-2019,90
975
+ 03-10-2019,136
976
+ 04-10-2019,100
977
+ 05-10-2019,98
978
+ 06-10-2019,127
979
+ 07-10-2019,130
980
+ 08-10-2019,112
981
+ 09-10-2019,173
982
+ 10-10-2019,211
983
+ 11-10-2019,216
984
+ 12-10-2019,222
985
+ 13-10-2019,270
986
+ 14-10-2019,252
987
+ 15-10-2019,270
988
+ 16-10-2019,304
989
+ 17-10-2019,284
990
+ 18-10-2019,248
991
+ 19-10-2019,161
992
+ 20-10-2019,238
993
+ 21-10-2019,249
994
+ 22-10-2019,207
995
+ 23-10-2019,242
996
+ 24-10-2019,311
997
+ 25-10-2019,284
998
+ 26-10-2019,287
999
+ 27-10-2019,337
1000
+ 28-10-2019,368
1001
+ 29-10-2019,400
1002
+ 30-10-2019,419
1003
+ 31-10-2019,410
1004
+ 01-11-2019,484
1005
+ 02-11-2019,399
1006
+ 03-11-2019,494
1007
+ 04-11-2019,407
1008
+ 05-11-2019,324
1009
+ 06-11-2019,214
1010
+ 07-11-2019,309
1011
+ 08-11-2019,330
1012
+ 09-11-2019,283
1013
+ 10-11-2019,321
1014
+ 11-11-2019,360
1015
+ 12-11-2019,425
1016
+ 13-11-2019,456
1017
+ 14-11-2019,463
1018
+ 15-11-2019,458
1019
+ 16-11-2019,357
1020
+ 17-11-2019,215
1021
+ 18-11-2019,214
1022
+ 19-11-2019,242
1023
+ 20-11-2019,301
1024
+ 21-11-2019,366
1025
+ 22-11-2019,360
1026
+ 23-11-2019,312
1027
+ 24-11-2019,234
1028
+ 25-11-2019,252
1029
+ 26-11-2019,270
1030
+ 27-11-2019,134
1031
+ 28-11-2019,106
1032
+ 29-11-2019,84
1033
+ 30-11-2019,193
1034
+ 01-12-2019,250
1035
+ 02-12-2019,279
1036
+ 03-12-2019,282
1037
+ 04-12-2019,296
1038
+ 05-12-2019,382
1039
+ 06-12-2019,404
1040
+ 07-12-2019,371
1041
+ 08-12-2019,391
1042
+ 09-12-2019,343
1043
+ 10-12-2019,369
1044
+ 11-12-2019,408
1045
+ 12-12-2019,430
1046
+ 13-12-2019,240
1047
+ 14-12-2019,163
1048
+ 15-12-2019,213
1049
+ 16-12-2019,186
1050
+ 17-12-2019,168
1051
+ 18-12-2019,292
1052
+ 19-12-2019,362
1053
+ 20-12-2019,432
1054
+ 21-12-2019,418
1055
+ 22-12-2019,322
1056
+ 23-12-2019,327
1057
+ 24-12-2019,383
1058
+ 25-12-2019,350
1059
+ 26-12-2019,349
1060
+ 27-12-2019,373
1061
+ 28-12-2019,409
1062
+ 29-12-2019,431
1063
+ 30-12-2019,446
1064
+ 31-12-2019,387
1065
+ 01-01-2020,437
1066
+ 02-01-2020,417
1067
+ 03-01-2020,352
1068
+ 04-01-2020,334
1069
+ 05-01-2020,330
1070
+ 06-01-2020,325
1071
+ 07-01-2020,254
1072
+ 08-01-2020,266
1073
+ 09-01-2020,203
1074
+ 10-01-2020,255
1075
+ 11-01-2020,288
1076
+ 12-01-2020,348
1077
+ 13-01-2020,366
1078
+ 14-01-2020,283
1079
+ 15-01-2020,218
1080
+ 16-01-2020,281
1081
+ 17-01-2020,265
1082
+ 18-01-2020,243
1083
+ 19-01-2020,277
1084
+ 20-01-2020,269
1085
+ 21-01-2020,364
1086
+ 22-01-2020,370
1087
+ 23-01-2020,211
1088
+ 24-01-2020,151
1089
+ 25-01-2020,250
1090
+ 26-01-2020,325
1091
+ 27-01-2020,345
1092
+ 28-01-2020,243
1093
+ 29-01-2020,194
1094
+ 30-01-2020,202
1095
+ 31-01-2020,190
1096
+ 01-02-2020,232
1097
+ 02-02-2020,244
1098
+ 03-02-2020,270
1099
+ 04-02-2020,310
1100
+ 05-02-2020,312
1101
+ 06-02-2020,231
1102
+ 07-02-2020,295
1103
+ 08-02-2020,289
1104
+ 09-02-2020,285
1105
+ 10-02-2020,309
1106
+ 11-02-2020,323
1107
+ 12-02-2020,320
1108
+ 13-02-2020,241
1109
+ 14-02-2020,173
1110
+ 15-02-2020,167
1111
+ 16-02-2020,247
1112
+ 17-02-2020,293
1113
+ 18-02-2020,252
1114
+ 19-02-2020,272
1115
+ 20-02-2020,230
1116
+ 21-02-2020,144
1117
+ 22-02-2020,100
1118
+ 23-02-2020,225
1119
+ 24-02-2020,142
1120
+ 25-02-2020,249
1121
+ 26-02-2020,274
1122
+ 27-02-2020,233
1123
+ 28-02-2020,198
1124
+ 01-03-2020,90
1125
+ 02-03-2020,205
1126
+ 03-03-2020,205
1127
+ 04-03-2020,195
1128
+ 05-03-2020,79
1129
+ 06-03-2020,64
1130
+ 07-03-2020,67
1131
+ 08-03-2020,164
1132
+ 09-03-2020,123
1133
+ 10-03-2020,188
1134
+ 11-03-2020,116
1135
+ 12-03-2020,127
1136
+ 13-03-2020,173
1137
+ 14-03-2020,108
1138
+ 15-03-2020,131
1139
+ 16-03-2020,139
1140
+ 17-03-2020,157
1141
+ 18-03-2020,151
1142
+ 19-03-2020,186
1143
+ 20-03-2020,192
1144
+ 21-03-2020,186
1145
+ 22-03-2020,191
1146
+ 23-03-2020,124
1147
+ 24-03-2020,122
1148
+ 25-03-2020,77
1149
+ 26-03-2020,92
1150
+ 27-03-2020,69
1151
+ 28-03-2020,45
1152
+ 29-03-2020,62
1153
+ 30-03-2020,71
1154
+ 31-03-2020,76
1155
+ 01-04-2020,73
1156
+ 02-04-2020,69
1157
+ 03-04-2020,79
1158
+ 04-04-2020,87
1159
+ 05-04-2020,102
1160
+ 06-04-2020,142
1161
+ 07-04-2020,90
1162
+ 08-04-2020,83
1163
+ 09-04-2020,86
1164
+ 10-04-2020,118
1165
+ 11-04-2020,124
1166
+ 12-04-2020,94
1167
+ 13-04-2020,126
1168
+ 14-04-2020,130
1169
+ 15-04-2020,155
1170
+ 16-04-2020,180
1171
+ 17-04-2020,105
1172
+ 18-04-2020,98
1173
+ 19-04-2020,85
1174
+ 20-04-2020,119
1175
+ 21-04-2020,87
1176
+ 22-04-2020,131
1177
+ 23-04-2020,122
1178
+ 24-04-2020,139
1179
+ 25-04-2020,98
1180
+ 26-04-2020,117
1181
+ 27-04-2020,89
1182
+ 28-04-2020,100
1183
+ 29-04-2020,125
1184
+ 30-04-2020,138
1185
+ 01-05-2020,146
1186
+ 02-05-2020,112
1187
+ 03-05-2020,93
1188
+ 04-05-2020,94
1189
+ 05-05-2020,119
1190
+ 06-05-2020,139
1191
+ 07-05-2020,127
1192
+ 08-05-2020,134
1193
+ 09-05-2020,144
1194
+ 10-05-2020,121
1195
+ 11-05-2020,116
1196
+ 12-05-2020,125
1197
+ 13-05-2020,150
1198
+ 14-05-2020,149
1199
+ 15-05-2020,123
1200
+ 16-05-2020,179
1201
+ 17-05-2020,200
1202
+ 18-05-2020,206
1203
+ 19-05-2020,212
1204
+ 20-05-2020,166
1205
+ 21-05-2020,147
1206
+ 22-05-2020,199
1207
+ 23-05-2020,181
1208
+ 24-05-2020,172
1209
+ 25-05-2020,153
1210
+ 26-05-2020,154
1211
+ 27-05-2020,156
1212
+ 28-05-2020,188
1213
+ 29-05-2020,101
1214
+ 30-05-2020,78
1215
+ 31-05-2020,72
1216
+ 01-06-2020,104
1217
+ 02-06-2020,124
1218
+ 03-06-2020,122
1219
+ 04-06-2020,153
1220
+ 05-06-2020,111
1221
+ 06-06-2020,102
1222
+ 07-06-2020,107
1223
+ 08-06-2020,108
1224
+ 09-06-2020,139
1225
+ 10-06-2020,152
1226
+ 11-06-2020,141
1227
+ 12-06-2020,153
1228
+ 13-06-2020,123
1229
+ 14-06-2020,154
1230
+ 15-06-2020,115
1231
+ 16-06-2020,118
1232
+ 17-06-2020,109
1233
+ 18-06-2020,95
1234
+ 19-06-2020,148
1235
+ 20-06-2020,149
1236
+ 21-06-2020,98
1237
+ 22-06-2020,92
1238
+ 23-06-2020,76
1239
+ 24-06-2020,66
1240
+ 25-06-2020,85
1241
+ 26-06-2020,105
1242
+ 27-06-2020,108
1243
+ 28-06-2020,209
1244
+ 29-06-2020,230
1245
+ 30-06-2020,102
1246
+ 01-07-2020,117
1247
+ 02-07-2020,127
1248
+ 03-07-2020,156
1249
+ 04-07-2020,105
1250
+ 05-07-2020,90
1251
+ 06-07-2020,90
1252
+ 07-07-2020,65
1253
+ 08-07-2020,51
1254
+ 09-07-2020,78
1255
+ 10-07-2020,67
1256
+ 11-07-2020,68
1257
+ 12-07-2020,71
1258
+ 13-07-2020,72
1259
+ 14-07-2020,127
1260
+ 15-07-2020,124
1261
+ 16-07-2020,72
1262
+ 17-07-2020,79
1263
+ 18-07-2020,92
1264
+ 19-07-2020,65
1265
+ 20-07-2020,60
1266
+ 21-07-2020,61
1267
+ 22-07-2020,59
1268
+ 23-07-2020,65
1269
+ 24-07-2020,100
1270
+ 25-07-2020,67
1271
+ 26-07-2020,78
1272
+ 27-07-2020,100
1273
+ 28-07-2020,72
1274
+ 29-07-2020,90
1275
+ 30-07-2020,55
1276
+ 31-07-2020,75
1277
+ 01-08-2020,81
1278
+ 02-08-2020,61
1279
+ 03-08-2020,67
1280
+ 04-08-2020,62
1281
+ 05-08-2020,86
1282
+ 06-08-2020,69
1283
+ 07-08-2020,54
1284
+ 08-08-2020,87
1285
+ 09-08-2020,74
1286
+ 10-08-2020,72
1287
+ 11-08-2020,75
1288
+ 12-08-2020,63
1289
+ 13-08-2020,50
1290
+ 14-08-2020,58
1291
+ 15-08-2020,67
1292
+ 16-08-2020,52
1293
+ 17-08-2020,66
1294
+ 18-08-2020,72
1295
+ 19-08-2020,57
1296
+ 20-08-2020,50
1297
+ 21-08-2020,57
1298
+ 22-08-2020,53
1299
+ 23-08-2020,53
1300
+ 24-08-2020,45
1301
+ 25-08-2020,61
1302
+ 26-08-2020,57
1303
+ 27-08-2020,64
1304
+ 28-08-2020,79
1305
+ 29-08-2020,77
1306
+ 30-08-2020,70
1307
+ 31-08-2020,41
1308
+ 01-09-2020,53
1309
+ 02-09-2020,69
1310
+ 03-09-2020,84
1311
+ 04-09-2020,82
1312
+ 05-09-2020,101
1313
+ 06-09-2020,70
1314
+ 07-09-2020,93
1315
+ 08-09-2020,79
1316
+ 09-09-2020,105
1317
+ 10-09-2020,108
1318
+ 11-09-2020,136
1319
+ 12-09-2020,144
1320
+ 13-09-2020,142
1321
+ 14-09-2020,152
1322
+ 15-09-2020,144
1323
+ 16-09-2020,151
1324
+ 17-09-2020,120
1325
+ 18-09-2020,108
1326
+ 19-09-2020,118
1327
+ 20-09-2020,148
1328
+ 21-09-2020,142
1329
+ 22-09-2020,115
1330
+ 23-09-2020,76
1331
+ 24-09-2020,104
1332
+ 25-09-2020,143
1333
+ 26-09-2020,165
1334
+ 27-09-2020,117
1335
+ 28-09-2020,159
1336
+ 29-09-2020,177
1337
+ 30-09-2020,156
1338
+ 01-10-2020,152
1339
+ 02-10-2020,180
1340
+ 03-10-2020,189
1341
+ 04-10-2020,184
1342
+ 05-10-2020,179
1343
+ 06-10-2020,178
1344
+ 07-10-2020,215
1345
+ 08-10-2020,208
1346
+ 09-10-2020,202
1347
+ 10-10-2020,221
1348
+ 11-10-2020,216
1349
+ 12-10-2020,261
1350
+ 13-10-2020,300
1351
+ 14-10-2020,276
1352
+ 15-10-2020,312
1353
+ 16-10-2020,237
1354
+ 17-10-2020,287
1355
+ 18-10-2020,254
1356
+ 19-10-2020,244
1357
+ 20-10-2020,223
1358
+ 21-10-2020,256
1359
+ 22-10-2020,296
1360
+ 23-10-2020,366
1361
+ 24-10-2020,345
1362
+ 25-10-2020,349
1363
+ 26-10-2020,353
1364
+ 27-10-2020,312
1365
+ 28-10-2020,297
1366
+ 29-10-2020,395
1367
+ 30-10-2020,374
1368
+ 31-10-2020,367
1369
+ 01-11-2020,364
1370
+ 02-11-2020,293
1371
+ 03-11-2020,302
1372
+ 04-11-2020,343
1373
+ 05-11-2020,450
1374
+ 06-11-2020,406
1375
+ 07-11-2020,427
1376
+ 08-11-2020,416
1377
+ 09-11-2020,477
1378
+ 10-11-2020,476
1379
+ 11-11-2020,344
1380
+ 12-11-2020,314
1381
+ 13-11-2020,339
1382
+ 14-11-2020,414
1383
+ 15-11-2020,435
1384
+ 16-11-2020,221
1385
+ 17-11-2020,171
1386
+ 18-11-2020,211
1387
+ 19-11-2020,283
1388
+ 20-11-2020,296
1389
+ 21-11-2020,251
1390
+ 22-11-2020,274
1391
+ 23-11-2020,295
1392
+ 24-11-2020,379
1393
+ 25-11-2020,413
1394
+ 26-11-2020,302
1395
+ 27-11-2020,137
1396
+ 28-11-2020,231
1397
+ 29-11-2020,256
1398
+ 30-11-2020,318
1399
+ 01-12-2020,367
1400
+ 02-12-2020,373
1401
+ 03-12-2020,341
1402
+ 04-12-2020,382
1403
+ 05-12-2020,404
1404
+ 06-12-2020,389
1405
+ 07-12-2020,400
1406
+ 08-12-2020,383
1407
+ 09-12-2020,358
1408
+ 10-12-2020,284
1409
+ 11-12-2020,295
1410
+ 12-12-2020,356
1411
+ 13-12-2020,305
1412
+ 14-12-2020,160
1413
+ 15-12-2020,230
1414
+ 16-12-2020,262
1415
+ 17-12-2020,256
1416
+ 18-12-2020,281
1417
+ 19-12-2020,290
1418
+ 20-12-2020,321
1419
+ 21-12-2020,332
1420
+ 22-12-2020,418
1421
+ 23-12-2020,433
1422
+ 24-12-2020,423
1423
+ 25-12-2020,357
1424
+ 26-12-2020,337
1425
+ 27-12-2020,396
1426
+ 28-12-2020,253
1427
+ 29-12-2020,265
1428
+ 30-12-2020,290
1429
+ 31-12-2020,347
1430
+ 01-01-2021,441
1431
+ 02-01-2021,443
1432
+ 03-01-2021,354
1433
+ 04-01-2021,151
1434
+ 05-01-2021,140
1435
+ 06-01-2021,226
1436
+ 07-01-2021,255
1437
+ 08-01-2021,234
1438
+ 09-01-2021,301
1439
+ 10-01-2021,245
1440
+ 11-01-2021,243
1441
+ 12-01-2021,293
1442
+ 13-01-2021,354
1443
+ 14-01-2021,429
1444
+ 15-01-2021,460
1445
+ 16-01-2021,407
1446
+ 17-01-2021,347
1447
+ 18-01-2021,372
1448
+ 19-01-2021,404
1449
+ 20-01-2021,283
1450
+ 21-01-2021,296
1451
+ 22-01-2021,364
1452
+ 23-01-2021,326
1453
+ 24-01-2021,364
1454
+ 25-01-2021,323
1455
+ 26-01-2021,330
1456
+ 27-01-2021,318
1457
+ 28-01-2021,357
1458
+ 29-01-2021,387
1459
+ 30-01-2021,309
1460
+ 31-01-2021,289
1461
+ 01-02-2021,352
1462
+ 02-02-2021,364
1463
+ 03-02-2021,330
1464
+ 04-02-2021,316
1465
+ 05-02-2021,133
1466
+ 06-02-2021,199
1467
+ 07-02-2021,232
1468
+ 08-02-2021,303
1469
+ 09-02-2021,305
1470
+ 10-02-2021,291
1471
+ 11-02-2021,330
1472
+ 12-02-2021,341
1473
+ 13-02-2021,334
1474
+ 14-02-2021,351
1475
+ 15-02-2021,313
1476
+ 16-02-2021,327
1477
+ 17-02-2021,324
1478
+ 18-02-2021,302
1479
+ 19-02-2021,311
1480
+ 20-02-2021,250
1481
+ 21-02-2021,296
1482
+ 22-02-2021,288
1483
+ 23-02-2021,250
1484
+ 24-02-2021,278
1485
+ 25-02-2021,298
1486
+ 26-02-2021,229
1487
+ 27-02-2021,203
1488
+ 28-02-2021,208
1489
+ 01-03-2021,177
1490
+ 02-03-2021,175
1491
+ 03-03-2021,188
1492
+ 04-03-2021,278
1493
+ 05-03-2021,255
1494
+ 06-03-2021,204
1495
+ 07-03-2021,256
1496
+ 08-03-2021,207
1497
+ 09-03-2021,283
1498
+ 10-03-2021,175
1499
+ 11-03-2021,242
1500
+ 12-03-2021,218
1501
+ 13-03-2021,209
1502
+ 14-03-2021,209
1503
+ 15-03-2021,206
1504
+ 16-03-2021,258
1505
+ 17-03-2021,311
1506
+ 18-03-2021,315
1507
+ 19-03-2021,279
1508
+ 20-03-2021,234
1509
+ 21-03-2021,244
1510
+ 22-03-2021,196
1511
+ 23-03-2021,244
1512
+ 24-03-2021,175
1513
+ 25-03-2021,148
1514
+ 26-03-2021,150
1515
+ 27-03-2021,199
1516
+ 28-03-2021,233
1517
+ 29-03-2021,225
1518
+ 30-03-2021,232
1519
+ 31-03-2021,203
1520
+ 01-04-2021,182
1521
+ 02-04-2021,176
1522
+ 03-04-2021,159
1523
+ 04-04-2021,171
1524
+ 05-04-2021,205
1525
+ 06-04-2021,279
1526
+ 07-04-2021,250
1527
+ 08-04-2021,153
1528
+ 09-04-2021,170
1529
+ 10-04-2021,181
1530
+ 11-04-2021,186
1531
+ 12-04-2021,241
1532
+ 13-04-2021,231
1533
+ 14-04-2021,199
1534
+ 15-04-2021,220
1535
+ 16-04-2021,238
1536
+ 17-04-2021,128
1537
+ 18-04-2021,154
1538
+ 19-04-2021,187
1539
+ 20-04-2021,191
1540
+ 21-04-2021,134
1541
+ 22-04-2021,140
1542
+ 23-04-2021,126
1543
+ 24-04-2021,125
1544
+ 25-04-2021,192
1545
+ 26-04-2021,256
1546
+ 27-04-2021,284
1547
+ 28-04-2021,312
1548
+ 29-04-2021,296
1549
+ 30-04-2021,287
1550
+ 01-05-2021,238
1551
+ 02-05-2021,174
1552
+ 03-05-2021,245
1553
+ 04-05-2021,173
1554
+ 05-05-2021,175
1555
+ 06-05-2021,156
1556
+ 07-05-2021,142
1557
+ 08-05-2021,173
1558
+ 09-05-2021,166
1559
+ 10-05-2021,136
1560
+ 11-05-2021,151
1561
+ 12-05-2021,115
1562
+ 13-05-2021,121
1563
+ 14-05-2021,132
1564
+ 15-05-2021,140
1565
+ 16-05-2021,166
1566
+ 17-05-2021,191
1567
+ 18-05-2021,93
1568
+ 19-05-2021,78
1569
+ 20-05-2021,58
1570
+ 21-05-2021,85
1571
+ 22-05-2021,94
1572
+ 23-05-2021,237
1573
+ 24-05-2021,169
1574
+ 25-05-2021,140
1575
+ 26-05-2021,149
1576
+ 27-05-2021,150
1577
+ 28-05-2021,106
1578
+ 29-05-2021,87
1579
+ 30-05-2021,101
1580
+ 31-05-2021,135
1581
+ 01-06-2021,115
1582
+ 02-06-2021,143
1583
+ 03-06-2021,159
1584
+ 04-06-2021,196
1585
+ 05-06-2021,124
1586
+ 06-06-2021,134
1587
+ 07-06-2021,180
1588
+ 08-06-2021,205
1589
+ 09-06-2021,305
1590
+ 10-06-2021,221
1591
+ 11-06-2021,145
1592
+ 12-06-2021,133
1593
+ 13-06-2021,83
1594
+ 14-06-2021,124
1595
+ 15-06-2021,113
1596
+ 16-06-2021,115
1597
+ 17-06-2021,108
1598
+ 18-06-2021,81
1599
+ 19-06-2021,70
1600
+ 20-06-2021,62
1601
+ 21-06-2021,84
1602
+ 22-06-2021,127
1603
+ 23-06-2021,218
1604
+ 24-06-2021,146
1605
+ 25-06-2021,163
1606
+ 26-06-2021,135
1607
+ 27-06-2021,168
1608
+ 28-06-2021,161
1609
+ 29-06-2021,195
1610
+ 30-06-2021,206
1611
+ 01-07-2021,266
1612
+ 02-07-2021,245
1613
+ 03-07-2021,139
1614
+ 04-07-2021,140
1615
+ 05-07-2021,149
1616
+ 06-07-2021,144
1617
+ 07-07-2021,178
1618
+ 08-07-2021,164
1619
+ 09-07-2021,144
1620
+ 10-07-2021,97
1621
+ 11-07-2021,94
1622
+ 12-07-2021,90
1623
+ 13-07-2021,80
1624
+ 14-07-2021,83
1625
+ 15-07-2021,83
1626
+ 16-07-2021,90
1627
+ 17-07-2021,117
1628
+ 18-07-2021,94
1629
+ 19-07-2021,62
1630
+ 20-07-2021,81
1631
+ 21-07-2021,74
1632
+ 22-07-2021,83
1633
+ 23-07-2021,68
1634
+ 24-07-2021,99
1635
+ 25-07-2021,107
1636
+ 26-07-2021,86
1637
+ 27-07-2021,98
1638
+ 28-07-2021,61
1639
+ 29-07-2021,63
1640
+ 30-07-2021,76
1641
+ 31-07-2021,57
1642
+ 01-08-2021,81
1643
+ 02-08-2021,95
1644
+ 03-08-2021,71
1645
+ 04-08-2021,69
1646
+ 05-08-2021,99
1647
+ 06-08-2021,104
1648
+ 07-08-2021,101
1649
+ 08-08-2021,109
1650
+ 09-08-2021,101
1651
+ 10-08-2021,101
1652
+ 11-08-2021,122
1653
+ 12-08-2021,114
1654
+ 13-08-2021,118
1655
+ 14-08-2021,116
1656
+ 15-08-2021,114
1657
+ 16-08-2021,121
1658
+ 17-08-2021,150
1659
+ 18-08-2021,152
1660
+ 19-08-2021,167
1661
+ 20-08-2021,111
1662
+ 21-08-2021,69
1663
+ 22-08-2021,59
1664
+ 23-08-2021,82
1665
+ 24-08-2021,100
1666
+ 25-08-2021,115
1667
+ 26-08-2021,114
1668
+ 27-08-2021,134
1669
+ 28-08-2021,147
1670
+ 29-08-2021,119
1671
+ 30-08-2021,81
1672
+ 31-08-2021,73
1673
+ 01-09-2021,64
1674
+ 02-09-2021,64
1675
+ 03-09-2021,70
1676
+ 04-09-2021,68
1677
+ 05-09-2021,88
1678
+ 06-09-2021,122
1679
+ 07-09-2021,98
1680
+ 08-09-2021,70
1681
+ 09-09-2021,75
1682
+ 10-09-2021,79
1683
+ 11-09-2021,61
1684
+ 12-09-2021,60
1685
+ 13-09-2021,89
1686
+ 14-09-2021,69
1687
+ 15-09-2021,70
1688
+ 16-09-2021,79
1689
+ 17-09-2021,62
1690
+ 18-09-2021,69
1691
+ 19-09-2021,82
1692
+ 20-09-2021,82
1693
+ 21-09-2021,83
1694
+ 22-09-2021,69
1695
+ 23-09-2021,68
1696
+ 24-09-2021,64
1697
+ 25-09-2021,80
1698
+ 26-09-2021,75
1699
+ 27-09-2021,108
1700
+ 28-09-2021,120
1701
+ 29-09-2021,81
1702
+ 30-09-2021,84
1703
+ 01-10-2021,108
1704
+ 02-10-2021,125
1705
+ 03-10-2021,105
1706
+ 04-10-2021,91
1707
+ 05-10-2021,115
1708
+ 06-10-2021,114
1709
+ 07-10-2021,127
1710
+ 08-10-2021,167
1711
+ 09-10-2021,171
1712
+ 10-10-2021,168
1713
+ 11-10-2021,166
1714
+ 12-10-2021,179
1715
+ 13-10-2021,171
1716
+ 14-10-2021,182
1717
+ 15-10-2021,198
1718
+ 16-10-2021,284
1719
+ 17-10-2021,298
1720
+ 18-10-2021,46
1721
+ 19-10-2021,69
1722
+ 20-10-2021,221
1723
+ 21-10-2021,199
1724
+ 22-10-2021,170
1725
+ 23-10-2021,173
1726
+ 24-10-2021,160
1727
+ 25-10-2021,82
1728
+ 26-10-2021,139
1729
+ 27-10-2021,232
1730
+ 28-10-2021,268
1731
+ 29-10-2021,283
1732
+ 30-10-2021,268
1733
+ 31-10-2021,289
1734
+ 01-11-2021,281
1735
+ 02-11-2021,303
1736
+ 03-11-2021,314
1737
+ 04-11-2021,382
1738
+ 05-11-2021,462
1739
+ 06-11-2021,437
1740
+ 07-11-2021,428
1741
+ 08-11-2021,390
1742
+ 09-11-2021,404
1743
+ 10-11-2021,372
1744
+ 11-11-2021,411
1745
+ 12-11-2021,471
1746
+ 13-11-2021,437
1747
+ 14-11-2021,330
1748
+ 15-11-2021,353
1749
+ 16-11-2021,403
1750
+ 17-11-2021,375
1751
+ 18-11-2021,347
1752
+ 19-11-2021,380
1753
+ 20-11-2021,374
1754
+ 21-11-2021,349
1755
+ 22-11-2021,311
1756
+ 23-11-2021,290
1757
+ 24-11-2021,361
1758
+ 25-11-2021,400
1759
+ 26-11-2021,406
1760
+ 27-11-2021,402
1761
+ 28-11-2021,405
1762
+ 29-11-2021,389
1763
+ 30-11-2021,328
1764
+ 01-12-2021,370
1765
+ 02-12-2021,429
1766
+ 03-12-2021,346
1767
+ 04-12-2021,362
1768
+ 05-12-2021,305
1769
+ 06-12-2021,322
1770
+ 07-12-2021,255
1771
+ 08-12-2021,237
1772
+ 09-12-2021,289
1773
+ 10-12-2021,314
1774
+ 11-12-2021,281
1775
+ 12-12-2021,254
1776
+ 13-12-2021,331
1777
+ 14-12-2021,367
1778
+ 15-12-2021,363
1779
+ 16-12-2021,368
1780
+ 17-12-2021,329
1781
+ 18-12-2021,291
1782
+ 19-12-2021,271
1783
+ 20-12-2021,332
1784
+ 21-12-2021,402
1785
+ 22-12-2021,407
1786
+ 23-12-2021,423
1787
+ 24-12-2021,415
1788
+ 25-12-2021,431
1789
+ 26-12-2021,459
1790
+ 27-12-2021,283
1791
+ 28-12-2021,305
1792
+ 29-12-2021,267
1793
+ 30-12-2021,286
1794
+ 31-12-2021,321
1795
+ 01-01-2022,362
1796
+ 02-01-2022,404
1797
+ 03-01-2022,387
1798
+ 04-01-2022,378
1799
+ 05-01-2022,397
1800
+ 06-01-2022,258
1801
+ 07-01-2022,182
1802
+ 08-01-2022,91
1803
+ 09-01-2022,69
1804
+ 10-01-2022,151
1805
+ 11-01-2022,224
1806
+ 12-01-2022,191
1807
+ 13-01-2022,321
1808
+ 14-01-2022,348
1809
+ 15-01-2022,258
1810
+ 16-01-2022,264
1811
+ 17-01-2022,327
1812
+ 18-01-2022,352
1813
+ 19-01-2022,322
1814
+ 20-01-2022,387
1815
+ 21-01-2022,365
1816
+ 22-01-2022,316
1817
+ 23-01-2022,202
1818
+ 24-01-2022,241
1819
+ 25-01-2022,234
1820
+ 26-01-2022,260
1821
+ 27-01-2022,262
1822
+ 28-01-2022,215
1823
+ 29-01-2022,251
1824
+ 30-01-2022,278
1825
+ 31-01-2022,338
1826
+ 01-02-2022,347
1827
+ 02-02-2022,319
1828
+ 03-02-2022,321
1829
+ 04-02-2022,152
1830
+ 05-02-2022,224
1831
+ 06-02-2022,285
1832
+ 07-02-2022,250
1833
+ 08-02-2022,270
1834
+ 09-02-2022,227
1835
+ 10-02-2022,172
1836
+ 11-02-2022,184
1837
+ 12-02-2022,191
1838
+ 13-02-2022,253
1839
+ 14-02-2022,239
1840
+ 15-02-2022,221
1841
+ 16-02-2022,272
1842
+ 17-02-2022,241
1843
+ 18-02-2022,252
1844
+ 19-02-2022,180
1845
+ 20-02-2022,173
1846
+ 21-02-2022,165
1847
+ 22-02-2022,252
1848
+ 23-02-2022,211
1849
+ 24-02-2022,307
1850
+ 25-02-2022,286
1851
+ 26-02-2022,102
1852
+ 27-02-2022,92
1853
+ 28-02-2022,107
1854
+ 01-03-2022,170
1855
+ 02-03-2022,225
1856
+ 03-03-2022,178
1857
+ 04-03-2022,199
1858
+ 05-03-2022,115
1859
+ 06-03-2022,162
1860
+ 07-03-2022,237
1861
+ 08-03-2022,283
1862
+ 09-03-2022,162
1863
+ 10-03-2022,150
1864
+ 11-03-2022,164
1865
+ 12-03-2022,160
1866
+ 13-03-2022,193
1867
+ 14-03-2022,231
1868
+ 15-03-2022,253
1869
+ 16-03-2022,218
1870
+ 17-03-2022,218
1871
+ 18-03-2022,293
1872
+ 19-03-2022,231
1873
+ 20-03-2022,242
1874
+ 21-03-2022,271
1875
+ 22-03-2022,180
1876
+ 23-03-2022,206
1877
+ 24-03-2022,280
1878
+ 25-03-2022,211
1879
+ 26-03-2022,207
1880
+ 27-03-2022,195
1881
+ 28-03-2022,251
1882
+ 29-03-2022,274
1883
+ 30-03-2022,276
1884
+ 31-03-2022,298
1885
+ 01-04-2022,218
1886
+ 02-04-2022,226
1887
+ 03-04-2022,245
1888
+ 04-04-2022,262
1889
+ 05-04-2022,233
1890
+ 06-04-2022,248
1891
+ 07-04-2022,264
1892
+ 08-04-2022,242
1893
+ 09-04-2022,261
1894
+ 10-04-2022,244
1895
+ 11-04-2022,258
1896
+ 12-04-2022,229
1897
+ 13-04-2022,286
1898
+ 14-04-2022,280
1899
+ 15-04-2022,201
1900
+ 16-04-2022,253
1901
+ 17-04-2022,251
1902
+ 18-04-2022,257
1903
+ 19-04-2022,317
1904
+ 20-04-2022,277
1905
+ 21-04-2022,296
1906
+ 22-04-2022,204
1907
+ 23-04-2022,245
1908
+ 24-04-2022,261
1909
+ 25-04-2022,244
1910
+ 26-04-2022,209
1911
+ 27-04-2022,287
1912
+ 28-04-2022,295
1913
+ 29-04-2022,299
1914
+ 30-04-2022,270
1915
+ 01-05-2022,256
1916
+ 02-05-2022,218
1917
+ 03-05-2022,227
1918
+ 04-05-2022,269
1919
+ 05-05-2022,146
1920
+ 06-05-2022,273
1921
+ 07-05-2022,206
1922
+ 08-05-2022,205
1923
+ 09-05-2022,171
1924
+ 10-05-2022,160
1925
+ 11-05-2022,156
1926
+ 12-05-2022,164
1927
+ 13-05-2022,176
1928
+ 14-05-2022,270
1929
+ 15-05-2022,242
1930
+ 16-05-2022,297
1931
+ 17-05-2022,239
1932
+ 18-05-2022,251
1933
+ 19-05-2022,202
1934
+ 20-05-2022,266
1935
+ 21-05-2022,285
1936
+ 22-05-2022,203
1937
+ 23-05-2022,136
1938
+ 24-05-2022,89
1939
+ 25-05-2022,166
1940
+ 26-05-2022,199
1941
+ 27-05-2022,206
1942
+ 28-05-2022,210
1943
+ 29-05-2022,273
1944
+ 30-05-2022,201
1945
+ 31-05-2022,215
1946
+ 01-06-2022,322
1947
+ 02-06-2022,202
1948
+ 03-06-2022,240
1949
+ 04-06-2022,227
1950
+ 05-06-2022,215
1951
+ 06-06-2022,195
1952
+ 07-06-2022,280
1953
+ 08-06-2022,346
1954
+ 09-06-2022,223
1955
+ 10-06-2022,303
1956
+ 11-06-2022,256
1957
+ 12-06-2022,249
1958
+ 13-06-2022,200
1959
+ 14-06-2022,222
1960
+ 15-06-2022,153
1961
+ 16-06-2022,132
1962
+ 17-06-2022,88
1963
+ 18-06-2022,79
1964
+ 19-06-2022,99
1965
+ 20-06-2022,124
1966
+ 21-06-2022,128
1967
+ 22-06-2022,139
1968
+ 23-06-2022,140
1969
+ 24-06-2022,197
1970
+ 25-06-2022,230
1971
+ 26-06-2022,169
1972
+ 27-06-2022,127
1973
+ 28-06-2022,137
1974
+ 29-06-2022,163
1975
+ 30-06-2022,116
1976
+ 01-07-2022,76
1977
+ 02-07-2022,95
1978
+ 03-07-2022,76
1979
+ 04-07-2022,113
1980
+ 05-07-2022,161
1981
+ 06-07-2022,104
1982
+ 07-07-2022,100
1983
+ 08-07-2022,94
1984
+ 09-07-2022,110
1985
+ 10-07-2022,71
1986
+ 11-07-2022,95
1987
+ 12-07-2022,100
1988
+ 13-07-2022,79
1989
+ 14-07-2022,87
1990
+ 15-07-2022,79
1991
+ 16-07-2022,81
1992
+ 17-07-2022,61
1993
+ 18-07-2022,92
1994
+ 19-07-2022,105
1995
+ 20-07-2022,98
1996
+ 21-07-2022,70
1997
+ 22-07-2022,140
1998
+ 23-07-2022,72
1999
+ 24-07-2022,72
2000
+ 25-07-2022,67
2001
+ 26-07-2022,69
2002
+ 27-07-2022,74
2003
+ 28-07-2022,68
2004
+ 29-07-2022,71
2005
+ 30-07-2022,58
2006
+ 31-07-2022,71
2007
+ 01-08-2022,71
2008
+ 02-08-2022,84
2009
+ 03-08-2022,122
2010
+ 04-08-2022,112
2011
+ 05-08-2022,72
2012
+ 06-08-2022,98
2013
+ 07-08-2022,118
2014
+ 08-08-2022,84
2015
+ 09-08-2022,112
2016
+ 10-08-2022,102
2017
+ 11-08-2022,75
2018
+ 12-08-2022,88
2019
+ 13-08-2022,80
2020
+ 14-08-2022,84
2021
+ 15-08-2022,62
2022
+ 16-08-2022,63
2023
+ 17-08-2022,60
2024
+ 18-08-2022,72
2025
+ 19-08-2022,71
2026
+ 20-08-2022,80
2027
+ 21-08-2022,117
2028
+ 22-08-2022,104
2029
+ 23-08-2022,83
2030
+ 24-08-2022,63
2031
+ 25-08-2022,96
2032
+ 26-08-2022,113
2033
+ 27-08-2022,105
2034
+ 28-08-2022,119
2035
+ 29-08-2022,144
2036
+ 30-08-2022,116
2037
+ 31-08-2022,124
2038
+ 01-09-2022,121
2039
+ 02-09-2022,110
2040
+ 03-09-2022,119
2041
+ 04-09-2022,115
2042
+ 05-09-2022,102
2043
+ 06-09-2022,119
2044
+ 07-09-2022,133
2045
+ 08-09-2022,136
2046
+ 09-09-2022,141
2047
+ 10-09-2022,110
2048
+ 11-09-2022,85
2049
+ 12-09-2022,84
2050
+ 13-09-2022,72
2051
+ 14-09-2022,71
2052
+ 15-09-2022,57
2053
+ 16-09-2022,47
2054
+ 17-09-2022,70
2055
+ 18-09-2022,119
2056
+ 19-09-2022,182
2057
+ 20-09-2022,130
2058
+ 21-09-2022,109
2059
+ 22-09-2022,66
2060
+ 23-09-2022,57
2061
+ 24-09-2022,54
2062
+ 25-09-2022,52
2063
+ 26-09-2022,100
2064
+ 27-09-2022,108
2065
+ 28-09-2022,140
2066
+ 29-09-2022,151
2067
+ 30-09-2022,173
2068
+ 01-10-2022,186
2069
+ 02-10-2022,181
2070
+ 03-10-2022,128
2071
+ 04-10-2022,150
2072
+ 05-10-2022,211
2073
+ 06-10-2022,79
2074
+ 07-10-2022,55
2075
+ 08-10-2022,56
2076
+ 09-10-2022,48
2077
+ 10-10-2022,44
2078
+ 11-10-2022,66
2079
+ 12-10-2022,143
2080
+ 13-10-2022,130
2081
+ 14-10-2022,154
2082
+ 15-10-2022,186
2083
+ 16-10-2022,232
2084
+ 17-10-2022,237
2085
+ 18-10-2022,241
2086
+ 19-10-2022,234
2087
+ 20-10-2022,232
2088
+ 21-10-2022,262
2089
+ 22-10-2022,265
2090
+ 23-10-2022,259
2091
+ 24-10-2022,312
2092
+ 25-10-2022,302
2093
+ 26-10-2022,271
2094
+ 27-10-2022,354
2095
+ 28-10-2022,357
2096
+ 29-10-2022,397
2097
+ 30-10-2022,352
2098
+ 31-10-2022,392
2099
+ 01-11-2022,424
2100
+ 02-11-2022,376
2101
+ 03-11-2022,450
2102
+ 04-11-2022,447
2103
+ 05-11-2022,381
2104
+ 06-11-2022,339
2105
+ 07-11-2022,354
2106
+ 08-11-2022,372
2107
+ 09-11-2022,260
2108
+ 10-11-2022,295
2109
+ 11-11-2022,346
2110
+ 12-11-2022,303
2111
+ 13-11-2022,303
2112
+ 14-11-2022,294
2113
+ 15-11-2022,227
2114
+ 16-11-2022,264
2115
+ 17-11-2022,260
2116
+ 18-11-2022,289
2117
+ 19-11-2022,280
2118
+ 20-11-2022,314
2119
+ 21-11-2022,310
2120
+ 22-11-2022,255
2121
+ 23-11-2022,237
2122
+ 24-11-2022,213
2123
+ 25-11-2022,294
2124
+ 26-11-2022,336
2125
+ 27-11-2022,328
2126
+ 28-11-2022,333
2127
+ 29-11-2022,369
2128
+ 30-11-2022,365
2129
+ 01-12-2022,368
2130
+ 02-12-2022,352
2131
+ 03-12-2022,370
2132
+ 04-12-2022,407
2133
+ 05-12-2022,347
2134
+ 06-12-2022,353
2135
+ 07-12-2022,304
2136
+ 08-12-2022,281
2137
+ 09-12-2022,314
2138
+ 10-12-2022,360
2139
+ 11-12-2022,306
2140
+ 12-12-2022,218
2141
+ 13-12-2022,177
2142
+ 14-12-2022,163
2143
+ 15-12-2022,189
2144
+ 16-12-2022,223
2145
+ 17-12-2022,304
2146
+ 18-12-2022,353
2147
+ 19-12-2022,410
2148
+ 20-12-2022,366
2149
+ 21-12-2022,328
2150
+ 22-12-2022,342
2151
+ 23-12-2022,359
2152
+ 24-12-2022,349
2153
+ 25-12-2022,308
2154
+ 26-12-2022,331
2155
+ 27-12-2022,339
2156
+ 28-12-2022,321
2157
+ 29-12-2022,306
2158
+ 30-12-2022,399
2159
+ 31-12-2022,349
2160
+ 01-01-2023,259
2161
+ 02-01-2023,357
2162
+ 03-01-2023,385
2163
+ 04-01-2023,343
2164
+ 05-01-2023,340
2165
+ 06-01-2023,400
2166
+ 07-01-2023,377
2167
+ 08-01-2023,375
2168
+ 09-01-2023,434
2169
+ 10-01-2023,407
2170
+ 11-01-2023,308
2171
+ 12-01-2023,371
2172
+ 13-01-2023,378
2173
+ 14-01-2023,353
2174
+ 15-01-2023,213
2175
+ 16-01-2023,270
2176
+ 17-01-2023,288
2177
+ 18-01-2023,306
2178
+ 19-01-2023,338
2179
+ 20-01-2023,226
2180
+ 21-01-2023,294
2181
+ 22-01-2023,407
2182
+ 23-01-2023,335
2183
+ 24-01-2023,237
2184
+ 25-01-2023,160
2185
+ 26-01-2023,298
2186
+ 27-01-2023,229
2187
+ 28-01-2023,236
2188
+ 29-01-2023,331
2189
+ 30-01-2023,207
2190
+ 31-01-2023,192
2191
+ 01-02-2023,164
2192
+ 02-02-2023,194
2193
+ 03-02-2023,199
2194
+ 04-02-2023,242
2195
+ 05-02-2023,244
2196
+ 06-02-2023,265
2197
+ 07-02-2023,285
2198
+ 08-02-2023,144
2199
+ 09-02-2023,212
2200
+ 10-02-2023,187
2201
+ 11-02-2023,275
2202
+ 12-02-2023,175
2203
+ 13-02-2023,135
2204
+ 14-02-2023,132
2205
+ 15-02-2023,190
2206
+ 16-02-2023,270
2207
+ 17-02-2023,367
2208
+ 18-02-2023,371
2209
+ 19-02-2023,331
2210
+ 20-02-2023,324
2211
+ 21-02-2023,245
2212
+ 22-02-2023,302
2213
+ 23-02-2023,227
2214
+ 24-02-2023,171
2215
+ 25-02-2023,225
2216
+ 26-02-2023,291
2217
+ 27-02-2023,260
2218
+ 28-02-2023,219
2219
+ 01-03-2023,181
2220
+ 02-03-2023,226
2221
+ 03-03-2023,152
2222
+ 04-03-2023,129
2223
+ 05-03-2023,142
2224
+ 06-03-2023,142
2225
+ 07-03-2023,173
2226
+ 08-03-2023,215
2227
+ 09-03-2023,119
2228
+ 10-03-2023,187
2229
+ 11-03-2023,200
2230
+ 12-03-2023,216
2231
+ 13-03-2023,231
2232
+ 14-03-2023,219
2233
+ 15-03-2023,213
2234
+ 16-03-2023,259
2235
+ 17-03-2023,188
2236
+ 18-03-2023,184
2237
+ 19-03-2023,162
2238
+ 20-03-2023,154
2239
+ 21-03-2023,75
2240
+ 22-03-2023,164
2241
+ 23-03-2023,151
2242
+ 24-03-2023,205
2243
+ 25-03-2023,78
2244
+ 26-03-2023,164
2245
+ 27-03-2023,146
2246
+ 28-03-2023,147
2247
+ 29-03-2023,198
2248
+ 30-03-2023,170
2249
+ 31-03-2023,73
2250
+ 01-04-2023,106
2251
+ 02-04-2023,128
2252
+ 03-04-2023,179
2253
+ 04-04-2023,109
2254
+ 05-04-2023,135
2255
+ 06-04-2023,142
2256
+ 07-04-2023,142
2257
+ 08-04-2023,172
2258
+ 09-04-2023,217
2259
+ 10-04-2023,195
2260
+ 11-04-2023,260
2261
+ 12-04-2023,213
2262
+ 13-04-2023,211
2263
+ 14-04-2023,249
2264
+ 15-04-2023,235
2265
+ 16-04-2023,229
2266
+ 17-04-2023,206
2267
+ 18-04-2023,235
2268
+ 19-04-2023,236
2269
+ 20-04-2023,159
2270
+ 21-04-2023,129
2271
+ 22-04-2023,141
2272
+ 23-04-2023,118
2273
+ 24-04-2023,160
2274
+ 25-04-2023,156
2275
+ 26-04-2023,210
2276
+ 27-04-2023,219
2277
+ 28-04-2023,150
2278
+ 29-04-2023,216
2279
+ 30-04-2023,132
2280
+ 01-05-2023,91
2281
+ 02-05-2023,76
2282
+ 03-05-2023,107
2283
+ 04-05-2023,113
2284
+ 05-05-2023,183
2285
+ 06-05-2023,234
2286
+ 07-05-2023,173
2287
+ 08-05-2023,131
2288
+ 09-05-2023,197
2289
+ 10-05-2023,203
2290
+ 11-05-2023,274
2291
+ 12-05-2023,227
2292
+ 13-05-2023,235
2293
+ 14-05-2023,259
2294
+ 15-05-2023,162
2295
+ 16-05-2023,259
2296
+ 17-05-2023,336
2297
+ 18-05-2023,149
2298
+ 19-05-2023,152
2299
+ 20-05-2023,186
2300
+ 21-05-2023,215
2301
+ 22-05-2023,199
2302
+ 23-05-2023,198
2303
+ 24-05-2023,158
2304
+ 25-05-2023,106
2305
+ 26-05-2023,98
2306
+ 27-05-2023,110
2307
+ 28-05-2023,134
2308
+ 29-05-2023,136
2309
+ 30-05-2023,115
2310
+ 31-05-2023,85
2311
+ 01-06-2023,96
2312
+ 02-06-2023,120
2313
+ 03-06-2023,121
2314
+ 04-06-2023,171
2315
+ 05-06-2023,166
2316
+ 06-06-2023,139
2317
+ 07-06-2023,239
2318
+ 08-06-2023,161
2319
+ 09-06-2023,152
2320
+ 10-06-2023,134
2321
+ 11-06-2023,130
2322
+ 12-06-2023,169
2323
+ 13-06-2023,205
2324
+ 14-06-2023,213
2325
+ 15-06-2023,154
2326
+ 16-06-2023,119
2327
+ 17-06-2023,119
2328
+ 18-06-2023,90
2329
+ 19-06-2023,75
2330
+ 20-06-2023,104
2331
+ 21-06-2023,91
2332
+ 22-06-2023,110
2333
+ 23-06-2023,132
2334
+ 24-06-2023,169
2335
+ 25-06-2023,71
2336
+ 26-06-2023,93
2337
+ 27-06-2023,86
2338
+ 28-06-2023,93
2339
+ 29-06-2023,101
2340
+ 30-06-2023,70
2341
+ 01-07-2023,69
2342
+ 02-07-2023,71
2343
+ 03-07-2023,118
2344
+ 04-07-2023,144
2345
+ 05-07-2023,96
2346
+ 06-07-2023,72
2347
+ 07-07-2023,77
2348
+ 08-07-2023,71
2349
+ 09-07-2023,64
2350
+ 10-07-2023,65
2351
+ 11-07-2023,69
2352
+ 12-07-2023,77
2353
+ 13-07-2023,77
2354
+ 14-07-2023,73
2355
+ 15-07-2023,100
2356
+ 16-07-2023,69
2357
+ 17-07-2023,85
2358
+ 18-07-2023,113
2359
+ 19-07-2023,77
2360
+ 20-07-2023,93
2361
+ 21-07-2023,109
2362
+ 22-07-2023,96
2363
+ 23-07-2023,73
2364
+ 24-07-2023,77
2365
+ 25-07-2023,102
2366
+ 26-07-2023,73
2367
+ 27-07-2023,92
2368
+ 28-07-2023,81
2369
+ 29-07-2023,59
2370
+ 30-07-2023,65
2371
+ 31-07-2023,89
2372
+ 01-08-2023,92
2373
+ 02-08-2023,91
2374
+ 03-08-2023,75
2375
+ 04-08-2023,105
2376
+ 05-08-2023,85
2377
+ 06-08-2023,101
2378
+ 07-08-2023,102
2379
+ 08-08-2023,110
2380
+ 09-08-2023,118
2381
+ 10-08-2023,133
2382
+ 11-08-2023,123
2383
+ 12-08-2023,126
2384
+ 13-08-2023,105
2385
+ 14-08-2023,114
2386
+ 15-08-2023,108
2387
+ 16-08-2023,109
2388
+ 17-08-2023,131
2389
+ 18-08-2023,153
2390
+ 19-08-2023,125
2391
+ 20-08-2023,91
2392
+ 21-08-2023,91
2393
+ 22-08-2023,106
2394
+ 23-08-2023,71
2395
+ 24-08-2023,85
2396
+ 25-08-2023,117
2397
+ 26-08-2023,152
2398
+ 27-08-2023,186
2399
+ 28-08-2023,144
2400
+ 29-08-2023,162
2401
+ 30-08-2023,154
2402
+ 31-08-2023,146
2403
+ 01-09-2023,140
2404
+ 02-09-2023,136
2405
+ 03-09-2023,136
2406
+ 04-09-2023,136
2407
+ 05-09-2023,121
2408
+ 06-09-2023,106
2409
+ 07-09-2023,105
2410
+ 08-09-2023,84
2411
+ 09-09-2023,55
2412
+ 10-09-2023,46
2413
+ 11-09-2023,54
2414
+ 12-09-2023,90
2415
+ 13-09-2023,106
2416
+ 14-09-2023,121
2417
+ 15-09-2023,100
2418
+ 16-09-2023,85
2419
+ 17-09-2023,63
2420
+ 18-09-2023,63
2421
+ 19-09-2023,73
2422
+ 20-09-2023,97
2423
+ 21-09-2023,113
2424
+ 22-09-2023,102
2425
+ 23-09-2023,108
2426
+ 24-09-2023,131
2427
+ 25-09-2023,118
2428
+ 26-09-2023,152
2429
+ 27-09-2023,129
2430
+ 28-09-2023,139
2431
+ 29-09-2023,157
2432
+ 30-09-2023,166
2433
+ 01-10-2023,146
2434
+ 02-10-2023,144
2435
+ 03-10-2023,154
2436
+ 04-10-2023,176
2437
+ 05-10-2023,177
2438
+ 06-10-2023,211
2439
+ 07-10-2023,215
2440
+ 08-10-2023,164
2441
+ 09-10-2023,174
2442
+ 10-10-2023,179
2443
+ 11-10-2023,191
2444
+ 12-10-2023,218
2445
+ 13-10-2023,255
2446
+ 14-10-2023,256
2447
+ 15-10-2023,232
2448
+ 16-10-2023,206
2449
+ 17-10-2023,87
2450
+ 18-10-2023,125
2451
+ 19-10-2023,118
2452
+ 20-10-2023,195
2453
+ 21-10-2023,246
2454
+ 22-10-2023,311
2455
+ 23-10-2023,263
2456
+ 24-10-2023,220
2457
+ 25-10-2023,243
2458
+ 26-10-2023,256
2459
+ 27-10-2023,259
2460
+ 28-10-2023,303
2461
+ 29-10-2023,324
2462
+ 30-10-2023,346
2463
+ 31-10-2023,357
2464
+ 01-11-2023,364
2465
+ 02-11-2023,392
2466
+ 03-11-2023,468
2467
+ 04-11-2023,415
2468
+ 05-11-2023,453
2469
+ 06-11-2023,421
2470
+ 07-11-2023,395
2471
+ 08-11-2023,426
2472
+ 09-11-2023,437
2473
+ 10-11-2023,278
2474
+ 11-11-2023,220
2475
+ 12-11-2023,218
2476
+ 13-11-2023,358
2477
+ 14-11-2023,396
2478
+ 15-11-2023,398
2479
+ 16-11-2023,418
2480
+ 17-11-2023,404
2481
+ 18-11-2023,318
2482
+ 19-11-2023,301
2483
+ 20-11-2023,348
2484
+ 21-11-2023,372
2485
+ 22-11-2023,395
2486
+ 23-11-2023,390
2487
+ 24-11-2023,415
2488
+ 25-11-2023,388
2489
+ 26-11-2023,397
2490
+ 27-11-2023,395
2491
+ 28-11-2023,311
2492
+ 29-11-2023,289
2493
+ 30-11-2023,398
2494
+ 01-12-2023,372
2495
+ 02-12-2023,352
2496
+ 03-12-2023,313
2497
+ 04-12-2023,310
2498
+ 05-12-2023,297
2499
+ 06-12-2023,285
2500
+ 07-12-2023,320
2501
+ 08-12-2023,325
2502
+ 09-12-2023,322
2503
+ 10-12-2023,314
2504
+ 11-12-2023,317
2505
+ 12-12-2023,355
2506
+ 13-12-2023,378
2507
+ 14-12-2023,326
2508
+ 15-12-2023,323
2509
+ 16-12-2023,355
2510
+ 17-12-2023,331
2511
+ 18-12-2023,330
2512
+ 19-12-2023,286
2513
+ 20-12-2023,285
2514
+ 21-12-2023,361
2515
+ 22-12-2023,409
2516
+ 23-12-2023,449
2517
+ 24-12-2023,411
2518
+ 25-12-2023,383
2519
+ 26-12-2023,377
2520
+ 27-12-2023,380
2521
+ 28-12-2023,358
2522
+ 29-12-2023,381
2523
+ 30-12-2023,400
2524
+ 31-12-2023,382
2525
+ 01-01-2024,346
2526
+ 02-01-2024,340
2527
+ 03-01-2024,341
2528
+ 04-01-2024,377
2529
+ 05-01-2024,333
2530
+ 06-01-2024,319
2531
+ 07-01-2024,333
2532
+ 08-01-2024,345
2533
+ 09-01-2024,343
2534
+ 10-01-2024,273
2535
+ 11-01-2024,348
2536
+ 12-01-2024,340
2537
+ 13-01-2024,398
2538
+ 14-01-2024,447
2539
+ 15-01-2024,359
2540
+ 16-01-2024,371
2541
+ 17-01-2024,368
2542
+ 18-01-2024,318
2543
+ 19-01-2024,348
2544
+ 20-01-2024,329
2545
+ 21-01-2024,349
2546
+ 22-01-2024,333
2547
+ 23-01-2024,368
2548
+ 24-01-2024,409
2549
+ 25-01-2024,332
2550
+ 26-01-2024,408
2551
+ 27-01-2024,356
2552
+ 28-01-2024,365
2553
+ 29-01-2024,356
2554
+ 30-01-2024,357
2555
+ 31-01-2024,391
2556
+ 01-02-2024,176
2557
+ 02-02-2024,215
2558
+ 03-02-2024,199
2559
+ 04-02-2024,274
2560
+ 05-02-2024,177
2561
+ 06-02-2024,140
2562
+ 07-02-2024,174
2563
+ 08-02-2024,161
2564
+ 09-02-2024,146
2565
+ 10-02-2024,295
2566
+ 11-02-2024,313
2567
+ 12-02-2024,300
2568
+ 13-02-2024,341
2569
+ 14-02-2024,340
2570
+ 15-02-2024,278
2571
+ 16-02-2024,268
2572
+ 17-02-2024,245
2573
+ 18-02-2024,270
2574
+ 19-02-2024,231
2575
+ 20-02-2024,226
2576
+ 21-02-2024,243
2577
+ 22-02-2024,150
2578
+ 23-02-2024,143
2579
+ 24-02-2024,193
2580
+ 25-02-2024,193
2581
+ 26-02-2024,172
2582
+ 27-02-2024,159
2583
+ 28-02-2024,141
2584
+ 29-02-2024,147
2585
+ 01-03-2024,208
2586
+ 02-03-2024,117
2587
+ 03-03-2024,126
2588
+ 04-03-2024,141
2589
+ 05-03-2024,125
2590
+ 06-03-2024,138
2591
+ 07-03-2024,181
2592
+ 08-03-2024,157
2593
+ 09-03-2024,145
2594
+ 10-03-2024,182
2595
+ 11-03-2024,201
2596
+ 12-03-2024,190
2597
+ 13-03-2024,165
2598
+ 14-03-2024,195
2599
+ 15-03-2024,128
2600
+ 16-03-2024,168
2601
+ 17-03-2024,193
2602
+ 18-03-2024,199
2603
+ 19-03-2024,222
2604
+ 20-03-2024,230
2605
+ 21-03-2024,171
2606
+ 22-03-2024,166
2607
+ 23-03-2024,232
2608
+ 24-03-2024,195
2609
+ 25-03-2024,197
2610
+ 26-03-2024,134
2611
+ 27-03-2024,178
2612
+ 28-03-2024,160
2613
+ 29-03-2024,176
2614
+ 30-03-2024,190
2615
+ 31-03-2024,243
2616
+ 01-04-2024,133
2617
+ 02-04-2024,144
2618
+ 03-04-2024,167
2619
+ 04-04-2024,173
2620
+ 05-04-2024,174
2621
+ 06-04-2024,168
2622
+ 07-04-2024,179
2623
+ 08-04-2024,191
2624
+ 09-04-2024,203
2625
+ 10-04-2024,222
2626
+ 11-04-2024,240
2627
+ 12-04-2024,232
2628
+ 13-04-2024,198
2629
+ 14-04-2024,141
2630
+ 15-04-2024,205
2631
+ 16-04-2024,228
2632
+ 17-04-2024,147
2633
+ 18-04-2024,164
2634
+ 19-04-2024,186
2635
+ 20-04-2024,219
2636
+ 21-04-2024,139
2637
+ 22-04-2024,129
2638
+ 23-04-2024,190
2639
+ 24-04-2024,178
2640
+ 25-04-2024,200
2641
+ 26-04-2024,197
2642
+ 27-04-2024,165
2643
+ 28-04-2024,176
2644
+ 29-04-2024,225
2645
+ 30-04-2024,189
2646
+ 01-05-2024,200
2647
+ 02-05-2024,197
2648
+ 03-05-2024,264
2649
+ 04-05-2024,282
2650
+ 05-05-2024,292
2651
+ 06-05-2024,248
2652
+ 07-05-2024,303
2653
+ 08-05-2024,225
2654
+ 09-05-2024,179
2655
+ 10-05-2024,180
2656
+ 11-05-2024,242
2657
+ 12-05-2024,184
2658
+ 13-05-2024,228
2659
+ 14-05-2024,235
2660
+ 15-05-2024,244
2661
+ 16-05-2024,236
2662
+ 17-05-2024,233
2663
+ 18-05-2024,262
2664
+ 19-05-2024,245
2665
+ 20-05-2024,228
2666
+ 21-05-2024,240
2667
+ 22-05-2024,193
2668
+ 23-05-2024,156
2669
+ 24-05-2024,157
2670
+ 25-05-2024,168
2671
+ 26-05-2024,191
2672
+ 27-05-2024,225
2673
+ 28-05-2024,246
2674
+ 29-05-2024,240
2675
+ 30-05-2024,230
2676
+ 31-05-2024,192
2677
+ 01-06-2024,245
2678
+ 02-06-2024,173
2679
+ 03-06-2024,155
2680
+ 04-06-2024,211
2681
+ 05-06-2024,251
2682
+ 06-06-2024,183
2683
+ 07-06-2024,231
2684
+ 08-06-2024,237
2685
+ 09-06-2024,182
2686
+ 10-06-2024,172
2687
+ 11-06-2024,223
2688
+ 12-06-2024,213
2689
+ 13-06-2024,195
2690
+ 14-06-2024,189
2691
+ 15-06-2024,186
2692
+ 16-06-2024,197
2693
+ 17-06-2024,169
2694
+ 18-06-2024,196
2695
+ 19-06-2024,306
2696
+ 20-06-2024,179
2697
+ 21-06-2024,175
2698
+ 22-06-2024,186
2699
+ 23-06-2024,145
2700
+ 24-06-2024,138
2701
+ 25-06-2024,134
2702
+ 26-06-2024,132
2703
+ 27-06-2024,74
2704
+ 28-06-2024,64
2705
+ 29-06-2024,118
2706
+ 30-06-2024,115
2707
+ 01-07-2024,105
2708
+ 02-07-2024,118
2709
+ 03-07-2024,108
2710
+ 04-07-2024,61
2711
+ 05-07-2024,77
2712
+ 06-07-2024,65
2713
+ 07-07-2024,56
2714
+ 08-07-2024,56
2715
+ 09-07-2024,84
2716
+ 10-07-2024,138
2717
+ 11-07-2024,113
2718
+ 12-07-2024,107
2719
+ 13-07-2024,102
2720
+ 14-07-2024,107
2721
+ 15-07-2024,109
2722
+ 16-07-2024,100
2723
+ 17-07-2024,90
2724
+ 18-07-2024,95
2725
+ 19-07-2024,124
2726
+ 20-07-2024,109
2727
+ 21-07-2024,103
2728
+ 22-07-2024,85
2729
+ 23-07-2024,93
2730
+ 24-07-2024,97
2731
+ 25-07-2024,109
2732
+ 26-07-2024,84
2733
+ 27-07-2024,131
2734
+ 28-07-2024,83
2735
+ 29-07-2024,76
2736
+ 30-07-2024,97
2737
+ 31-07-2024,96
2738
+ 01-08-2024,64
2739
+ 02-08-2024,76
2740
+ 03-08-2024,68
2741
+ 04-08-2024,64
2742
+ 05-08-2024,59
2743
+ 06-08-2024,61
2744
+ 07-08-2024,61
2745
+ 08-08-2024,54
2746
+ 09-08-2024,61
2747
+ 10-08-2024,71
2748
+ 11-08-2024,69
2749
+ 12-08-2024,56
2750
+ 13-08-2024,63
2751
+ 14-08-2024,79
2752
+ 15-08-2024,72
2753
+ 16-08-2024,63
2754
+ 17-08-2024,74
2755
+ 18-08-2024,83
2756
+ 19-08-2024,84
2757
+ 20-08-2024,66
2758
+ 21-08-2024,87
2759
+ 22-08-2024,85
2760
+ 23-08-2024,77
2761
+ 24-08-2024,105
2762
+ 25-08-2024,77
2763
+ 26-08-2024,72
2764
+ 27-08-2024,64
2765
+ 28-08-2024,70
2766
+ 29-08-2024,58
2767
+ 30-08-2024,102
2768
+ 31-08-2024,92
2769
+ 01-09-2024,101
2770
+ 02-09-2024,87
2771
+ 03-09-2024,89
2772
+ 04-09-2024,69
2773
+ 05-09-2024,83
2774
+ 06-09-2024,95
2775
+ 07-09-2024,71
2776
+ 08-09-2024,91
2777
+ 09-09-2024,114
2778
+ 10-09-2024,106
2779
+ 11-09-2024,71
2780
+ 12-09-2024,64
2781
+ 13-09-2024,52
2782
+ 14-09-2024,62
2783
+ 15-09-2024,107
2784
+ 16-09-2024,139
2785
+ 17-09-2024,172
2786
+ 18-09-2024,115
2787
+ 19-09-2024,56
2788
+ 20-09-2024,98
2789
+ 21-09-2024,116
2790
+ 22-09-2024,162
2791
+ 23-09-2024,165
2792
+ 24-09-2024,195
2793
+ 25-09-2024,233
2794
+ 26-09-2024,94
2795
+ 27-09-2024,80
2796
+ 28-09-2024,66
2797
+ 29-09-2024,74
2798
+ 30-09-2024,126
2799
+ 01-10-2024,149
2800
+ 02-10-2024,173
2801
+ 03-10-2024,162
2802
+ 04-10-2024,184
2803
+ 05-10-2024,145
2804
+ 06-10-2024,143
2805
+ 07-10-2024,126
2806
+ 08-10-2024,167
2807
+ 09-10-2024,164
2808
+ 10-10-2024,132
2809
+ 11-10-2024,141
2810
+ 12-10-2024,155
2811
+ 13-10-2024,224
2812
+ 14-10-2024,234
2813
+ 15-10-2024,198
2814
+ 16-10-2024,230
2815
+ 17-10-2024,285
2816
+ 18-10-2024,292
2817
+ 19-10-2024,278
2818
+ 20-10-2024,277
2819
+ 21-10-2024,310
2820
+ 22-10-2024,327
2821
+ 23-10-2024,364
2822
+ 24-10-2024,306
2823
+ 25-10-2024,270
2824
+ 26-10-2024,255
2825
+ 27-10-2024,356
2826
+ 28-10-2024,304
2827
+ 29-10-2024,268
2828
+ 30-10-2024,307
2829
+ 31-10-2024,328
2830
+ 01-11-2024,339
2831
+ 02-11-2024,318
2832
+ 03-11-2024,381
2833
+ 04-11-2024,380
2834
+ 05-11-2024,372
2835
+ 06-11-2024,351
2836
+ 07-11-2024,377
2837
+ 08-11-2024,379
2838
+ 09-11-2024,350
2839
+ 10-11-2024,334
2840
+ 11-11-2024,352
2841
+ 12-11-2024,334
2842
+ 13-11-2024,418
2843
+ 14-11-2024,424
2844
+ 15-11-2024,396
2845
+ 16-11-2024,417
2846
+ 17-11-2024,441
2847
+ 18-11-2024,494
2848
+ 19-11-2024,460
2849
+ 20-11-2024,419
2850
+ 21-11-2024,371
2851
+ 22-11-2024,393
2852
+ 23-11-2024,412
2853
+ 24-11-2024,318
2854
+ 25-11-2024,349
2855
+ 26-11-2024,343
2856
+ 27-11-2024,303
2857
+ 28-11-2024,325
2858
+ 29-11-2024,331
2859
+ 30-11-2024,346
2860
+ 01-12-2024,285
2861
+ 02-12-2024,280
2862
+ 03-12-2024,268
2863
+ 04-12-2024,178
2864
+ 05-12-2024,165
2865
+ 06-12-2024,197
2866
+ 07-12-2024,233
2867
+ 08-12-2024,302
2868
+ 09-12-2024,186
2869
+ 10-12-2024,234
2870
+ 11-12-2024,199
2871
+ 12-12-2024,288
2872
+ 13-12-2024,262
2873
+ 14-12-2024,193
2874
+ 15-12-2024,294
2875
+ 16-12-2024,379
2876
+ 17-12-2024,433
2877
+ 18-12-2024,445
2878
+ 19-12-2024,451
2879
+ 20-12-2024,429
2880
+ 21-12-2024,370
2881
+ 22-12-2024,409
2882
+ 23-12-2024,406
2883
+ 24-12-2024,369
2884
+ 25-12-2024,336
2885
+ 26-12-2024,345
2886
+ 27-12-2024,353
2887
+ 28-12-2024,139
2888
+ 29-12-2024,225
2889
+ 30-12-2024,173
2890
+ 31-12-2024,283
2891
+ 01-01-2025,328
2892
+ 02-01-2025,318
2893
+ 03-01-2025,371
2894
+ 04-01-2025,378
2895
+ 05-01-2025,339
2896
+ 06-01-2025,335
2897
+ 07-01-2025,296
2898
+ 08-01-2025,297
2899
+ 09-01-2025,357
2900
+ 10-01-2025,397
2901
+ 11-01-2025,327
2902
+ 12-01-2025,278
2903
+ 13-01-2025,248
2904
+ 14-01-2025,275
2905
+ 15-01-2025,386
2906
+ 16-01-2025,302
2907
+ 17-01-2025,289
2908
+ 18-01-2025,255
2909
+ 19-01-2025,368
2910
+ 20-01-2025,314
2911
+ 21-01-2025,289
2912
+ 22-01-2025,260
2913
+ 23-01-2025,257
2914
+ 24-01-2025,199
2915
+ 25-01-2025,174
2916
+ 26-01-2025,216
2917
+ 27-01-2025,268
2918
+ 28-01-2025,276
2919
+ 29-01-2025,365
2920
+ 30-01-2025,363
2921
+ 31-01-2025,351
2922
+ 01-02-2025,355
2923
+ 02-02-2025,326
2924
+ 03-02-2025,286
2925
+ 04-02-2025,264
2926
+ 05-02-2025,284
2927
+ 06-02-2025,210
2928
+ 07-02-2025,156
2929
+ 08-02-2025,152
2930
+ 09-02-2025,227
2931
+ 10-02-2025,271
2932
+ 11-02-2025,293
2933
+ 12-02-2025,212
2934
+ 13-02-2025,134
2935
+ 14-02-2025,131
2936
+ 15-02-2025,191
2937
+ 16-02-2025,294
2938
+ 17-02-2025,231
2939
+ 18-02-2025,209
2940
+ 19-02-2025,180
2941
+ 20-02-2025,160
2942
+ 21-02-2025,158
2943
+ 22-02-2025,155
2944
+ 23-02-2025,144
2945
+ 24-02-2025,186
2946
+ 25-02-2025,208
2947
+ 26-02-2025,247
2948
+ 27-02-2025,215
2949
+ 28-02-2025,121
2950
+ 01-03-2025,126
2951
+ 02-03-2025,125
2952
+ 03-03-2025,156
2953
+ 04-03-2025,148
2954
+ 05-03-2025,119
2955
+ 06-03-2025,124
2956
+ 07-03-2025,202
2957
+ 08-03-2025,158
2958
+ 09-03-2025,209
2959
+ 10-03-2025,197
2960
+ 11-03-2025,262
2961
+ 12-03-2025,228
2962
+ 13-03-2025,179
2963
+ 14-03-2025,198
2964
+ 15-03-2025,85
2965
+ 16-03-2025,99
2966
+ 17-03-2025,108
2967
+ 18-03-2025,145
2968
+ 19-03-2025,160
2969
+ 20-03-2025,156
2970
+ 21-03-2025,144
2971
+ 22-03-2025,161
2972
+ 23-03-2025,194
2973
+ 24-03-2025,206
2974
+ 25-03-2025,234
2975
+ 26-03-2025,231
2976
+ 27-03-2025,259
2977
+ 28-03-2025,205
2978
+ 29-03-2025,153
2979
+ 30-03-2025,138
2980
+ 31-03-2025,160
app.py ADDED
@@ -0,0 +1,560 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Flask API server for the policy engine.
3
+ Endpoints for generating policies, applying them, and analyzing impacts.
4
+ """
5
+ from flask import Flask, request, jsonify
6
+ from flask_cors import CORS
7
+ from datetime import datetime
8
+ import json
9
+ import traceback
10
+
11
+ from policy_engine import PolicyEngine, get_graph_context_from_file
12
+ from graph_engine import GraphState, ImpactAnalyzer
13
+ from health_analyzer import HealthImpactAnalyzer
14
+ from explainability import generate_policy_explanation
15
+ from explainability import generate_policy_explanation
16
+ from aqi import register_aqi_routes
17
+ from emission_forecast import register_emission_routes
18
+ import config
19
+ import os
20
+
21
+
22
+ # ============================================================================
23
+ # SETUP
24
+ # ============================================================================
25
+
26
+ app = Flask(__name__)
27
+ CORS(app) # Enable CORS for frontend requests
28
+
29
+ # Initialize engines
30
+ policy_engine = PolicyEngine()
31
+ health_analyzer = HealthImpactAnalyzer()
32
+
33
+ # Register AQI routes
34
+ register_aqi_routes(app)
35
+ register_emission_routes(app)
36
+ from aqi_history import register_aqi_history_routes
37
+ register_aqi_history_routes(app)
38
+
39
+ # Pre-initialize AQI Data & Model to prevent timeout on first request
40
+ from aqi_history import get_aqi_history
41
+ print("Initializing AQI History & Model...")
42
+ get_aqi_history()
43
+ from aqi_map import generate_aqi_map_html, render_map_to_png
44
+ import threading
45
+
46
+ # Ensure map directory exists
47
+ STATIC_DIR = os.path.join(os.getcwd(), 'static')
48
+ os.makedirs(STATIC_DIR, exist_ok=True)
49
+ MAP_IMAGE_PATH = os.path.join(STATIC_DIR, 'aqi_map.png')
50
+
51
+ @app.route('/api/aqi-map', methods=['GET'])
52
+ def get_aqi_map_html():
53
+ """Returns the Interactive Folium Map HTML"""
54
+ return generate_aqi_map_html()
55
+
56
+ @app.route('/api/aqi-map.png', methods=['GET'])
57
+ def get_aqi_map_png():
58
+ """Returns the static PNG image of the map, regenerating if needed"""
59
+ from flask import send_file
60
+
61
+ # Check if we need to regenerate (e.g. if file is old or missing)
62
+ # For now, we'll generate if missing, or maybe background refresh
63
+ if not os.path.exists(MAP_IMAGE_PATH):
64
+ html = generate_aqi_map_html()
65
+ render_map_to_png(html, MAP_IMAGE_PATH)
66
+
67
+ return send_file(MAP_IMAGE_PATH, mimetype='image/png')
68
+
69
+ # Background task to refresh map periodically (optional)
70
+ def refresh_map_periodically():
71
+ while True:
72
+ try:
73
+ print("Refreshing AQI Map image...")
74
+ html = generate_aqi_map_html()
75
+ render_map_to_png(html, MAP_IMAGE_PATH)
76
+ except Exception as e:
77
+ print(f"Error refreshing map: {e}")
78
+ time.sleep(3600) # Every hour
79
+
80
+ # Start background thread for map refresh
81
+ # threading.Thread(target=refresh_map_periodically, daemon=True).start()
82
+
83
+
84
+ # ============================================================================
85
+ # HELPER FUNCTIONS
86
+ # ============================================================================
87
+
88
+ def get_graph_state():
89
+ """Load current graph state."""
90
+ try:
91
+ return GraphState.from_file(str(config.GRAPH_STATE_PATH))
92
+ except Exception as e:
93
+ print(f"Error loading graph: {e}")
94
+ return None
95
+
96
+
97
+ # ============================================================================
98
+ # API ENDPOINTS
99
+ # ============================================================================
100
+
101
+ @app.route('/', methods=['GET'])
102
+ def index():
103
+ """Root endpoint to show server status."""
104
+ return jsonify({
105
+ 'message': 'Digital Twin Policy Engine API is running',
106
+ 'endpoints': {
107
+ 'health': '/api/health',
108
+ 'aqi': '/api/aqi?lat=28.7041&lon=77.1025'
109
+ },
110
+ 'status': 'active'
111
+ })
112
+
113
+ @app.route('/api/health', methods=['GET'])
114
+ def health_check():
115
+ """Health check endpoint."""
116
+ return jsonify({
117
+ 'status': 'ok',
118
+ 'timestamp': datetime.now().isoformat(),
119
+ 'service': 'policy-engine'
120
+ })
121
+
122
+
123
+ @app.route('/api/graph-state', methods=['GET'])
124
+ def get_current_graph_state():
125
+ """
126
+ Get current graph state (nodes, edges, values).
127
+ Used by frontend for validation and context.
128
+ """
129
+ try:
130
+ graph = get_graph_state()
131
+ if not graph:
132
+ return jsonify({'error': 'Could not load graph'}), 500
133
+
134
+ return jsonify({
135
+ 'status': 'success',
136
+ 'graph': graph.to_dict(),
137
+ 'timestamp': datetime.now().isoformat()
138
+ })
139
+ except Exception as e:
140
+ return jsonify({'error': str(e)}), 500
141
+
142
+
143
+ @app.route('/api/generate-policy', methods=['POST'])
144
+ def generate_policy():
145
+ """
146
+ Generate policy from research query.
147
+
148
+ Request body:
149
+ {
150
+ "research_query": "How to reduce transport emissions?"
151
+ }
152
+
153
+ Returns:
154
+ {
155
+ "policy": { Policy JSON },
156
+ "research_evidence": ["chunk1", "chunk2", ...],
157
+ "status": "success"
158
+ }
159
+ """
160
+ try:
161
+ data = request.json
162
+ query = data.get('research_query')
163
+
164
+ if not query:
165
+ return jsonify({'error': 'Missing research_query'}), 400
166
+
167
+ # Retrieve research
168
+ research_chunks = policy_engine.query_research(query, k=3)
169
+
170
+ # Get graph context for validation
171
+ graph_context = data.get('graph_context')
172
+ if not graph_context:
173
+ print("Using static graph context from file")
174
+ graph_context = get_graph_context_from_file(str(config.GRAPH_STATE_PATH))
175
+ else:
176
+ print("Using dynamic graph context from frontend")
177
+
178
+ # Extract policy via LLM
179
+ policy = policy_engine.extract_policy(research_chunks, graph_context, user_query=query)
180
+
181
+ return jsonify({
182
+ 'status': 'success',
183
+ 'policy': policy.dict(),
184
+ 'research_evidence': research_chunks,
185
+ 'timestamp': datetime.now().isoformat()
186
+ })
187
+
188
+ except Exception as e:
189
+ print(f"Error in generate_policy: {e}")
190
+ traceback.print_exc()
191
+ return jsonify({'error': str(e)}), 500
192
+
193
+
194
+ @app.route('/api/apply-policy', methods=['POST'])
195
+ def apply_policy():
196
+ """
197
+ Apply policy to graph and calculate impact.
198
+
199
+ Request body:
200
+ {
201
+ "policy": { Policy JSON from generate-policy }
202
+ }
203
+
204
+ Returns:
205
+ {
206
+ "snapshot": {
207
+ "scenario_id": "id",
208
+ "policy_id": "id",
209
+ "impact": { CO2 and AQI changes },
210
+ "cascade_analysis": { affected nodes },
211
+ ...
212
+ },
213
+ "status": "success"
214
+ }
215
+ """
216
+ try:
217
+ data = request.json
218
+ policy_dict = data.get('policy')
219
+
220
+ if not policy_dict:
221
+ return jsonify({'error': 'Missing policy'}), 400
222
+
223
+ # Load baseline
224
+ graph_context = data.get('graph_context')
225
+ if graph_context:
226
+ print("Using dynamic graph context for baseline")
227
+ # Reconstruct GraphState from context
228
+ # Context has { node_ids: [], edges: [{source, target, weight}] }
229
+ # We need to map this back to the GraphState structure
230
+ try:
231
+ # Load full default graph to get default node data (values, labels) which might be missing in context list
232
+ file_graph = get_graph_state()
233
+
234
+ # Reconstruct nodes with enabled status
235
+ context_nodes = graph_context.get('nodes', [])
236
+ if context_nodes and isinstance(context_nodes, list):
237
+ # New format: list of dicts with enabled status
238
+ reconstructed_nodes = []
239
+ for n_ctx in context_nodes:
240
+ # Find original node data to preserve other fields (x, y, label, etc)
241
+ original = next((on for on in file_graph.nodes if on['id'] == n_ctx['id']), None)
242
+ if original:
243
+ new_node = original.copy()
244
+ if 'data' not in new_node:
245
+ new_node['data'] = {}
246
+ new_node['data']['enabled'] = n_ctx.get('enabled', True)
247
+ reconstructed_nodes.append(new_node)
248
+ baseline_nodes = reconstructed_nodes
249
+ else:
250
+ # Old format: list of IDs
251
+ valid_node_ids = set(graph_context.get('node_ids', context_nodes)) # fallback if context_nodes is list of strings
252
+ baseline_nodes = [n for n in file_graph.nodes if n['id'] in valid_node_ids]
253
+
254
+ # Reconstruct edges
255
+ context_edges = graph_context.get('edges', [])
256
+ reconstructed_edges = []
257
+ for e in context_edges:
258
+ reconstructed_edges.append({
259
+ 'source': e['source'],
260
+ 'target': e['target'],
261
+ 'data': {'weight': e['weight']}
262
+ })
263
+
264
+ baseline = GraphState(baseline_nodes, reconstructed_edges)
265
+ except Exception as e:
266
+ print(f"Error reconstructing dynamic baseline: {e}")
267
+ baseline = get_graph_state()
268
+ else:
269
+ baseline = get_graph_state()
270
+
271
+ if not baseline:
272
+ return jsonify({'error': 'Could not load baseline graph'}), 500
273
+
274
+ # Create post-policy state (deep copy baseline)
275
+ import copy
276
+ post_policy = GraphState(
277
+ copy.deepcopy(baseline.nodes),
278
+ copy.deepcopy(baseline.edges)
279
+ )
280
+
281
+ # Apply mutations
282
+ mutation_results = post_policy.apply_policy(policy_dict)
283
+
284
+ # Log mutations with before/after values
285
+ print(f"\n[Policy Applied]")
286
+ # Print transport value if it exists, safely
287
+ trans_node = baseline.get_node('transport')
288
+ if trans_node:
289
+ print(f"Baseline Transport value: {trans_node['data'].get('value', 'N/A')}")
290
+
291
+ print(f"Baseline CO2 value: {baseline.get_node('co2')['data']['value']}")
292
+ print(f"Baseline AQI value: {baseline.get_node('aqi')['data']['value']}")
293
+
294
+ for i, mut in enumerate(mutation_results.get('mutations_applied', [])):
295
+ print(f"Mutation {i+1}: {mut['type']}")
296
+ if 'before' in mut and 'after' in mut:
297
+ print(f" Before: {mut['before']}")
298
+ print(f" After: {mut['after']}")
299
+
300
+ # Calculate impact
301
+ analyzer = ImpactAnalyzer(baseline, post_policy)
302
+ impact = analyzer.calculate_impact()
303
+
304
+ if trans_node:
305
+ post_trans = post_policy.get_node('transport')
306
+ print(f"Post-policy Transport value: {post_trans['data'].get('value', 'N/A')}")
307
+
308
+ print(f"Post-policy CO2 value: {post_policy.get_node('co2')['data']['value']}")
309
+ print(f"Post-policy AQI value: {post_policy.get_node('aqi')['data']['value']}")
310
+ print(f"Impact: CO₂ change {impact['co2']['change_pct']:.1f}%, AQI change {impact['aqi']['change_pct']:.1f}%\n")
311
+
312
+ # Create snapshot
313
+ snapshot = {
314
+ 'scenario_id': f"snap-{datetime.now().strftime('%Y%m%d-%H%M%S')}",
315
+ 'policy_id': policy_dict.get('policy_id'),
316
+ 'policy_name': policy_dict.get('name'),
317
+ 'baseline_graph': baseline.to_dict(),
318
+ 'post_policy_graph': post_policy.to_dict(),
319
+ 'mutations_applied': mutation_results['mutations_applied'],
320
+ 'impact': impact,
321
+ 'timestamp': datetime.now().isoformat()
322
+ }
323
+
324
+ return jsonify({
325
+ 'snapshot': snapshot,
326
+ 'status': 'success'
327
+ })
328
+
329
+ except Exception as e:
330
+ print(f"Error in apply_policy: {e}")
331
+ traceback.print_exc()
332
+ return jsonify({'error': str(e)}), 500
333
+
334
+
335
+ @app.route('/api/compare-scenarios', methods=['POST'])
336
+ def compare_scenarios():
337
+ """
338
+ Compare multiple scenarios side-by-side.
339
+
340
+ Request body:
341
+ {
342
+ "scenarios": [
343
+ { "name": "Scenario 1", "policy": { Policy JSON } },
344
+ { "name": "Scenario 2", "policy": { Policy JSON } },
345
+ ...
346
+ ]
347
+ }
348
+
349
+ Returns:
350
+ {
351
+ "comparison": [ Snapshots for each scenario ],
352
+ "ranking": {
353
+ "best_co2_reduction": "Scenario name",
354
+ "best_aqi_improvement": "Scenario name"
355
+ }
356
+ }
357
+ """
358
+ try:
359
+ data = request.json
360
+ scenarios = data.get('scenarios', [])
361
+
362
+ if not scenarios:
363
+ return jsonify({'error': 'Missing scenarios'}), 400
364
+
365
+ results = []
366
+
367
+ for scenario in scenarios:
368
+ # Apply each policy
369
+ baseline = get_graph_state()
370
+ if not baseline:
371
+ continue
372
+
373
+ import copy
374
+ post_policy = GraphState(
375
+ copy.deepcopy(baseline.nodes),
376
+ copy.deepcopy(baseline.edges)
377
+ )
378
+
379
+ post_policy.apply_policy(scenario.get('policy', {}))
380
+
381
+ analyzer = ImpactAnalyzer(baseline, post_policy)
382
+ impact = analyzer.calculate_impact()
383
+
384
+ results.append({
385
+ 'name': scenario.get('name'),
386
+ 'impact': impact
387
+ })
388
+
389
+ # Rank by impact
390
+ best_co2 = max(results, key=lambda r: abs(r['impact']['co2']['change_pct']), default={})
391
+ best_aqi = max(results, key=lambda r: abs(r['impact']['aqi']['change_pct']), default={})
392
+
393
+ return jsonify({
394
+ 'status': 'success',
395
+ 'comparison': results,
396
+ 'ranking': {
397
+ 'best_co2_reduction': best_co2.get('name'),
398
+ 'best_aqi_improvement': best_aqi.get('name')
399
+ },
400
+ 'timestamp': datetime.now().isoformat()
401
+ })
402
+
403
+ except Exception as e:
404
+ print(f"Error in compare_scenarios: {e}")
405
+ traceback.print_exc()
406
+ return jsonify({'error': str(e)}), 500
407
+
408
+
409
+ @app.route('/api/explain-policy', methods=['POST'])
410
+ def explain_policy():
411
+ """
412
+ Generate explanation for a policy.
413
+
414
+ Request body:
415
+ {
416
+ "policy": { Policy JSON },
417
+ "research_evidence": ["chunk1", "chunk2", ...]
418
+ }
419
+
420
+ Returns:
421
+ {
422
+ "explanation": {
423
+ "policy_id": "...",
424
+ "narrative_intro": "...",
425
+ "mutations": [ { narrative, supporting_research, stakeholders } ],
426
+ "overall_narrative": "..."
427
+ }
428
+ }
429
+ """
430
+ try:
431
+ data = request.json
432
+ policy = data.get('policy')
433
+ research_evidence = data.get('research_evidence', [])
434
+
435
+ if not policy:
436
+ return jsonify({'error': 'Missing policy'}), 400
437
+
438
+ # Generate explanation
439
+ explanation = generate_policy_explanation(policy, research_evidence)
440
+
441
+ return jsonify({
442
+ 'status': 'success',
443
+ 'explanation': explanation,
444
+ 'timestamp': datetime.now().isoformat()
445
+ })
446
+
447
+ except Exception as e:
448
+ print(f"Error in explain_policy: {e}")
449
+ traceback.print_exc()
450
+ return jsonify({'error': str(e)}), 500
451
+
452
+
453
+ @app.route('/api/analyze-aqi-health', methods=['POST'])
454
+ def analyze_aqi_health():
455
+ """
456
+ Analyze health impacts based on AQI data.
457
+
458
+ Request body:
459
+ {
460
+ "aqi_data": {
461
+ "aqi": 410,
462
+ "city": "Delhi",
463
+ "pm2_5": 250,
464
+ "pm10": 400,
465
+ "no2": 85,
466
+ "o3": 45,
467
+ "so2": 15,
468
+ "co": 1.5
469
+ }
470
+ }
471
+
472
+ Response:
473
+ {
474
+ "health_impact": { Health analysis from Gemini },
475
+ "status": "success",
476
+ "timestamp": "..."
477
+ }
478
+ """
479
+ try:
480
+ data = request.json
481
+ aqi_data = data.get('aqi_data')
482
+
483
+ if not aqi_data:
484
+ return jsonify({'error': 'Missing aqi_data'}), 400
485
+
486
+ # Analyze health impact
487
+ health_impact = health_analyzer.analyze_aqi_health(aqi_data)
488
+
489
+ return jsonify({
490
+ 'health_impact': health_impact,
491
+ 'status': 'success',
492
+ 'timestamp': datetime.now().isoformat()
493
+ })
494
+
495
+ except Exception as e:
496
+ print(f"Error analyzing AQI health: {e}")
497
+ print(traceback.format_exc())
498
+ return jsonify({'error': str(e)}), 500
499
+
500
+
501
+ @app.route('/api/chat-health', methods=['POST'])
502
+ def chat_health():
503
+ """
504
+ Chat with health expert.
505
+
506
+ Request:
507
+ {
508
+ "message": "Should I wear a mask?",
509
+ "context": { "aqi": 350, "city": "Delhi", ... }
510
+ }
511
+ """
512
+ try:
513
+ data = request.json
514
+ message = data.get('message')
515
+ context = data.get('context', {})
516
+
517
+ if not message:
518
+ return jsonify({'error': 'Missing message'}), 400
519
+
520
+ response = health_analyzer.chat_with_health_expert(message, context)
521
+
522
+ return jsonify({
523
+ 'response': response,
524
+ 'status': 'success'
525
+ })
526
+ except Exception as e:
527
+ print(f"Error in chat_health: {e}")
528
+ return jsonify({'error': str(e)}), 500
529
+
530
+
531
+ # ============================================================================
532
+ # ERROR HANDLERS
533
+ # ============================================================================
534
+
535
+ @app.errorhandler(404)
536
+ def not_found(error):
537
+ return jsonify({'error': 'Not found'}), 404
538
+
539
+
540
+ @app.errorhandler(500)
541
+ def internal_error(error):
542
+ return jsonify({'error': 'Internal server error'}), 500
543
+
544
+
545
+ # ============================================================================
546
+ # MAIN
547
+ # ============================================================================
548
+
549
+ if __name__ == "__main__":
550
+ is_hf = bool(os.getenv("HF_SPACE_ID"))
551
+
552
+ port = 7860 if is_hf else config.FLASK_PORT
553
+ debug = False if is_hf else config.FLASK_DEBUG
554
+
555
+ app.run(
556
+ host="0.0.0.0",
557
+ port=port,
558
+ debug=debug
559
+ )
560
+
aqi.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import os
3
+ from config import AMBEE_DATA_KEY
4
+
5
+
6
+ def get_live_aqi(lat: float = 28.7041, lon: float = 77.1025) -> dict:
7
+ """Fetch live AQI data from Ambee Data API for Delhi"""
8
+
9
+ # Try Ambee API first
10
+ if AMBEE_DATA_KEY:
11
+ try:
12
+ url = "https://api.ambeedata.com/latest/by-lat-lng"
13
+ headers = {
14
+ "x-api-key": AMBEE_DATA_KEY,
15
+ "Content-Type": "application/json"
16
+ }
17
+ params = {
18
+ "lat": lat,
19
+ "lng": lon
20
+ }
21
+
22
+ print(f"Fetching AQI from Ambee for lat={lat}, lng={lon}")
23
+ response = requests.get(url, headers=headers, params=params, timeout=10)
24
+ print(f"Response status: {response.status_code}")
25
+
26
+
27
+ response.raise_for_status()
28
+ data = response.json()
29
+ print(f"Ambee response: {data}")
30
+
31
+ # Parse Ambee API response
32
+ if data.get("stations") and len(data["stations"]) > 0:
33
+ station = data["stations"][0] # Get first station
34
+ print(f"AQI data extracted from station: {station}")
35
+
36
+ # Map Ambee pollution to our format
37
+ return {
38
+ "aqi": station.get("AQI", 50), # Actual AQI value (0-500)
39
+ "aqi_category": station.get("aqiInfo", {}).get("category", "Unknown"),
40
+ "pm2_5": station.get("PM25", 0),
41
+ "pm10": station.get("PM10", 0),
42
+ "o3": station.get("OZONE", 0),
43
+ "no2": station.get("NO2", 0),
44
+ "so2": station.get("SO2", 0),
45
+ "co": station.get("CO", 0),
46
+ "city": station.get("city", "Unknown"),
47
+ "source": "Ambee Data API"
48
+ }
49
+ else:
50
+ print(f"No 'stations' array in response: {data}")
51
+ except Exception as e:
52
+ print(f"Error fetching from Ambee API: {type(e).__name__}: {e}")
53
+ import traceback
54
+ traceback.print_exc()
55
+ else:
56
+ print("AMBEE_API_KEY not set in environment")
57
+
58
+ # Return mock data as fallback
59
+ print("Using mock AQI data (Ambee API failed or not configured)")
60
+ return {
61
+ "aqi": 50,
62
+ "aqi_category": "Good",
63
+ "pm2_5": 12.5,
64
+ "pm10": 25.0,
65
+ "o3": 45.2,
66
+ "no2": 28.5,
67
+ "so2": 8.1,
68
+ "co": 0.5,
69
+ "source": "Mock Data"
70
+ }
71
+
72
+ # Function to register Flask routes (call this from main app file)
73
+ def register_aqi_routes(app):
74
+ """Register AQI routes with Flask app"""
75
+ from flask import request, jsonify
76
+
77
+ @app.route('/api/aqi', methods=['GET'])
78
+ def fetch_aqi():
79
+ try:
80
+ lat = request.args.get('lat', 40.7128, type=float)
81
+ lon = request.args.get('lon', -74.0060, type=float)
82
+ aqi_data = get_live_aqi(lat, lon)
83
+ return jsonify(aqi_data), 200
84
+ except Exception as e:
85
+ print(f"Error in fetch_aqi: {e}")
86
+ return jsonify({"error": str(e), "aqi_index": 2, "pm2_5": 12.5}), 200
aqi_history.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import numpy as np
3
+ import os
4
+ from flask import jsonify, request
5
+ from datetime import datetime, timedelta
6
+
7
+ # Try importing Random Forest (Standard sklearn)
8
+ try:
9
+ from sklearn.ensemble import RandomForestRegressor
10
+ from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
11
+ HAS_MODEL = True
12
+ except ImportError:
13
+ HAS_MODEL = False
14
+
15
+ # Global instance
16
+ _aqi_history = None
17
+
18
+ class AQIHistory:
19
+ def __init__(self, data_path='AQI_Combined.csv'):
20
+ self.data_path = data_path
21
+ self.df = None
22
+ self.model = None
23
+ self.metrics = {}
24
+ self._load_data()
25
+
26
+ if HAS_MODEL and self.df is not None and not self.df.empty:
27
+ self._train_model()
28
+
29
+ def _load_data(self):
30
+ if os.path.exists(self.data_path):
31
+ try:
32
+ self.df = pd.read_csv(self.data_path)
33
+ # Parse date - format is DD-MM-YYYY based on user input
34
+ self.df['Date'] = pd.to_datetime(self.df['Date'], format='%d-%m-%Y')
35
+ self.df = self.df.sort_values('Date').dropna()
36
+ except Exception as e:
37
+ print(f"Error loading AQI history: {e}")
38
+ self.df = pd.DataFrame()
39
+ else:
40
+ print(f"AQI history file not found: {self.data_path}")
41
+ self.df = pd.DataFrame()
42
+
43
+ def get_history(self, start_year=None, end_year=None):
44
+ if self.df is None or self.df.empty:
45
+ return []
46
+
47
+ data = self.df.copy()
48
+
49
+ if start_year:
50
+ data = data[data['Date'].dt.year >= start_year]
51
+ if end_year:
52
+ data = data[data['Date'].dt.year <= end_year]
53
+
54
+ # Return raw daily data instead of monthly averages
55
+ # This ensures the values match the CSV exactly as requested by the user
56
+
57
+ return [
58
+ {
59
+ 'date': row['Date'].strftime('%Y-%m-%d'),
60
+ 'aqi': int(row['AQI'])
61
+ }
62
+ for _, row in data.iterrows()
63
+ ]
64
+
65
+ def _create_features(self, data):
66
+ df_feat = data.copy()
67
+ target_col = 'AQI'
68
+
69
+ # Time features
70
+ df_feat['day_of_week'] = df_feat['Date'].dt.dayofweek
71
+ df_feat['month'] = df_feat['Date'].dt.month
72
+ df_feat['day_of_year'] = df_feat['Date'].dt.dayofyear
73
+
74
+ # Seasonality (Cyclical)
75
+ df_feat['day_year_sin'] = np.sin(2 * np.pi * df_feat['day_of_year'] / 365.25)
76
+ df_feat['day_year_cos'] = np.cos(2 * np.pi * df_feat['day_of_year'] / 365.25)
77
+ df_feat['month_sin'] = np.sin(2 * np.pi * df_feat['month'] / 12)
78
+ df_feat['month_cos'] = np.cos(2 * np.pi * df_feat['month'] / 12)
79
+
80
+ # Lags (Extended for long-term dependency)
81
+ # 365 lag is crucial for year-over-year patterns
82
+ lags = [1, 2, 3, 7, 14, 30, 60, 90, 365]
83
+ for lag in lags:
84
+ df_feat[f'lag_{lag}'] = df_feat[target_col].shift(lag)
85
+
86
+ # Rolling stats
87
+ for window in [7, 30, 90]:
88
+ df_feat[f'rolling_mean_{window}'] = df_feat[target_col].shift(1).rolling(window=window).mean()
89
+ df_feat[f'rolling_std_{window}'] = df_feat[target_col].shift(1).rolling(window=window).std()
90
+
91
+ return df_feat.dropna()
92
+
93
+ def _train_model(self):
94
+ print("Training AQI Forecast Model (Random Forest)...")
95
+ try:
96
+ df_feat = self._create_features(self.df)
97
+
98
+ features = [c for c in df_feat.columns if c not in ['Date', 'AQI']]
99
+ X = df_feat[features].values
100
+ y = df_feat['AQI'].values
101
+
102
+ # Split
103
+ # Use last 365 days as test set
104
+ split_idx = len(X) - 365
105
+ X_train, X_test = X[:split_idx], X[split_idx:]
106
+ y_train, y_test = y[:split_idx], y[split_idx:]
107
+
108
+ self.model = RandomForestRegressor(
109
+ n_estimators=1000,
110
+ max_depth=20,
111
+ random_state=42,
112
+ n_jobs=-1
113
+ )
114
+
115
+ self.model.fit(X_train, y_train)
116
+
117
+ # Metrics
118
+ preds = self.model.predict(X_test)
119
+ mae = mean_absolute_error(y_test, preds)
120
+ mse = mean_squared_error(y_test, preds)
121
+ rmse = np.sqrt(mse)
122
+ r2 = r2_score(y_test, preds)
123
+
124
+ self.metrics = {
125
+ 'mae': float(mae),
126
+ 'mse': float(mse),
127
+ 'rmse': float(rmse),
128
+ 'r2': float(r2)
129
+ }
130
+ print(f"AQI RF Model trained. MAE: {mae:.2f}, R2: {r2:.2f}")
131
+
132
+ except Exception as e:
133
+ print(f"Error training AQI model: {e}")
134
+
135
+ def forecast_days(self, n_days=180):
136
+ if not HAS_MODEL or self.df is None or self.df.empty:
137
+ return {'error': 'Model not trained'}
138
+
139
+ future_dates = []
140
+ last_date = self.df['Date'].iloc[-1]
141
+
142
+ # Calculate gap to today
143
+ today = pd.Timestamp.now().normalize()
144
+ gap_days = (today - last_date).days
145
+
146
+ # We want to fill the gap AND predict n_days into the future
147
+ total_days = max(0, gap_days) + n_days
148
+
149
+ print(f"Bridging gap of {gap_days} days + Forecasting {n_days} days. Total: {total_days}")
150
+
151
+ # We need to iteratively predict because of lags
152
+ # Start with the last known data window
153
+ current_data = self.df.copy()
154
+
155
+ predictions = []
156
+
157
+ for i in range(total_days):
158
+ next_date = last_date + timedelta(days=i+1)
159
+
160
+ # Create a temporary row for feature engineering
161
+ temp_df = pd.concat([
162
+ current_data,
163
+ pd.DataFrame({'Date': [next_date], 'AQI': [0]}) # Dummy AQI
164
+ ], ignore_index=True)
165
+
166
+ feats_df = self._create_features(temp_df)
167
+ if feats_df.empty:
168
+ break
169
+
170
+ # Get the features for the last row (the one we want to predict)
171
+ last_row_feats = feats_df.iloc[-1]
172
+ features_cols = [c for c in feats_df.columns if c not in ['Date', 'AQI']]
173
+
174
+ X_pred = last_row_feats[features_cols].values.reshape(1, -1)
175
+ pred_aqi = float(self.model.predict(X_pred)[0])
176
+ pred_aqi = max(0, pred_aqi) # AQI can't be negative
177
+
178
+ predictions.append({
179
+ 'date': next_date.strftime('%Y-%m-%d'),
180
+ 'aqi': int(round(pred_aqi))
181
+ })
182
+
183
+ # Append prediction to current_data for next iteration's lags
184
+ current_data = pd.concat([
185
+ current_data,
186
+ pd.DataFrame({'Date': [next_date], 'AQI': [pred_aqi]})
187
+ ], ignore_index=True)
188
+
189
+ return {
190
+ 'forecast': predictions,
191
+ 'metrics': self.metrics
192
+ }
193
+
194
+ def get_aqi_history():
195
+ global _aqi_history
196
+ if _aqi_history is None:
197
+ _aqi_history = AQIHistory()
198
+ return _aqi_history
199
+
200
+ def register_aqi_history_routes(app):
201
+ @app.route('/api/aqi/history', methods=['GET'])
202
+ def api_aqi_history():
203
+ try:
204
+ handler = get_aqi_history()
205
+ trends = handler.get_history()
206
+ return jsonify({
207
+ 'status': 'success',
208
+ 'data': trends
209
+ })
210
+ except Exception as e:
211
+ return jsonify({'error': str(e)}), 500
212
+
213
+ @app.route('/api/aqi/forecast', methods=['POST'])
214
+ def api_aqi_forecast():
215
+ try:
216
+ data = request.json or {}
217
+ days = data.get('days', 30)
218
+
219
+ handler = get_aqi_history()
220
+ result = handler.forecast_days(days)
221
+
222
+ return jsonify({
223
+ 'status': 'success',
224
+ 'data': result
225
+ })
226
+ except Exception as e:
227
+ return jsonify({'error': str(e)}), 500
228
+
229
+ if __name__ == "__main__":
230
+ print("Initialize AQI History and Training Model...")
231
+ try:
232
+ handler = AQIHistory()
233
+ if handler.model:
234
+ print("\nModel Trained Successfully!")
235
+ print("-" * 30)
236
+ print("Performance Metrics (Test Set):")
237
+ for metric, value in handler.metrics.items():
238
+ print(f" {metric.upper()}: {value:.4f}")
239
+ print("-" * 30)
240
+
241
+ print("\nSample 5-Day Forecast:")
242
+ forecast_result = handler.forecast_days(5)
243
+ if 'forecast' in forecast_result:
244
+ for day in forecast_result['forecast']:
245
+ print(f" {day['date']}: {day['aqi']} (AQI)")
246
+ else:
247
+ print("Model failed to train or Scikit-learn not installed.")
248
+
249
+ except Exception as e:
250
+ print(f"\nError: {e}")
aqi_map.py ADDED
@@ -0,0 +1,344 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import folium
3
+ import os
4
+ import time
5
+ import random
6
+ from selenium import webdriver
7
+ from selenium.webdriver.chrome.options import Options
8
+ from selenium.webdriver.chrome.service import Service
9
+ from webdriver_manager.chrome import ChromeDriverManager
10
+
11
+ # Delhi center coordinates
12
+ DELHI_LAT = 28.7041
13
+ DELHI_LON = 77.1025
14
+
15
+ def generate_aqi_map_html():
16
+ """Generates a Folium map with AQI hotspots and returns the HTML string."""
17
+
18
+ # Create base map
19
+ m = folium.Map(location=[DELHI_LAT, DELHI_LON], zoom_start=11)
20
+
21
+ # Add Delhi Boundary
22
+ try:
23
+ # Using DataMeet's Delhi Boundary GeoJSON
24
+ geojson_url = "https://raw.githubusercontent.com/datameet/Municipal_Spatial_Data/master/Delhi/Delhi_Boundary.geojson"
25
+ folium.GeoJson(
26
+ geojson_url,
27
+ name="Delhi Boundary",
28
+ style_function=lambda x: {
29
+ 'fillColor': 'none',
30
+ 'color': 'black',
31
+ 'weight': 2,
32
+ 'dashArray': '5, 5'
33
+ }
34
+ ).add_to(m)
35
+ except Exception as e:
36
+ print(f"Error loading Delhi GeoJSON: {e}")
37
+
38
+ # Randomly generate some "hotspot" locations around Delhi for demo
39
+ # In a real app, this would come from the AQI data source
40
+ hotspots = [
41
+ {
42
+ "name": "Chandni Chowk",
43
+ "lat": 28.656,
44
+ "lon": 77.231,
45
+ "aqi": 384
46
+ },
47
+ {
48
+ "name": "Nehru Nagar",
49
+ "lat": 28.5697,
50
+ "lon": 77.253,
51
+ "aqi": 384
52
+ },
53
+ {
54
+ "name": "New Moti Bagh",
55
+ "lat": 28.582,
56
+ "lon": 77.1717,
57
+ "aqi": 377
58
+ },
59
+ {
60
+ "name": "Shadipur",
61
+ "lat": 28.6517,
62
+ "lon": 77.1582,
63
+ "aqi": 375
64
+ },
65
+ {
66
+ "name": "Wazirpur",
67
+ "lat": 28.6967,
68
+ "lon": 77.1658,
69
+ "aqi": 354
70
+ },
71
+ {
72
+ "name": "Ashok Vihar",
73
+ "lat": 28.6856,
74
+ "lon": 77.178,
75
+ "aqi": 231
76
+ },
77
+ {
78
+ "name": "Mundka",
79
+ "lat": 28.6794,
80
+ "lon": 77.0284,
81
+ "aqi": 353
82
+ },
83
+ {
84
+ "name": "Punjabi Bagh",
85
+ "lat": 28.673,
86
+ "lon": 77.1374,
87
+ "aqi": 349
88
+ },
89
+ {
90
+ "name": "RK Puram",
91
+ "lat": 28.5505,
92
+ "lon": 77.1849,
93
+ "aqi": 342
94
+ },
95
+ {
96
+ "name": "Jahangirpuri",
97
+ "lat": 28.716,
98
+ "lon": 77.1829,
99
+ "aqi": 338
100
+ },
101
+ {
102
+ "name": "Okhla Phase-2",
103
+ "lat": 28.5365,
104
+ "lon": 77.2803,
105
+ "aqi": 337
106
+ },
107
+ {
108
+ "name": "Dwarka Sector-8",
109
+ "lat": 28.5656,
110
+ "lon": 77.067,
111
+ "aqi": 336
112
+ },
113
+ {
114
+ "name": "Patparganj",
115
+ "lat": 28.612,
116
+ "lon": 77.292,
117
+ "aqi": 334
118
+ },
119
+ {
120
+ "name": "Bawana",
121
+ "lat": 28.8,
122
+ "lon": 77.03,
123
+ "aqi": 328
124
+ },
125
+ {
126
+ "name": "Sonia Vihar",
127
+ "lat": 28.7074,
128
+ "lon": 77.2599,
129
+ "aqi": 317
130
+ },
131
+ {
132
+ "name": "Rohini",
133
+ "lat": 28.7019,
134
+ "lon": 77.0984,
135
+ "aqi": 312
136
+ },
137
+ {
138
+ "name": "Narela",
139
+ "lat": 28.85,
140
+ "lon": 77.1,
141
+ "aqi": 311
142
+ },
143
+ {
144
+ "name": "Mandir Marg",
145
+ "lat": 28.6325,
146
+ "lon": 77.1994,
147
+ "aqi": 311
148
+ },
149
+ {
150
+ "name": "Vivek Vihar",
151
+ "lat": 28.6635,
152
+ "lon": 77.3152,
153
+ "aqi": 303
154
+ },
155
+ {
156
+ "name": "Anand Vihar",
157
+ "lat": 28.6508,
158
+ "lon": 77.3152,
159
+ "aqi": 335
160
+ },
161
+ {
162
+ "name": "Dhyanchand Stadium",
163
+ "lat": 28.6125,
164
+ "lon": 77.2372,
165
+ "aqi": 335
166
+ },
167
+ {
168
+ "name": "Sirifort",
169
+ "lat": 28.5521,
170
+ "lon": 77.2193,
171
+ "aqi": 326
172
+ },
173
+ {
174
+ "name": "Karni Singh Shooting Range",
175
+ "lat": 28.4998,
176
+ "lon": 77.2668,
177
+ "aqi": 323
178
+ },
179
+ {
180
+ "name": "ITO",
181
+ "lat": 28.6294,
182
+ "lon": 77.241,
183
+ "aqi": 324
184
+ },
185
+ {
186
+ "name": "JLN Stadium",
187
+ "lat": 28.5828,
188
+ "lon": 77.2344,
189
+ "aqi": 314
190
+ },
191
+ {
192
+ "name": "IGI Airport T3",
193
+ "lat": 28.5562,
194
+ "lon": 77.1,
195
+ "aqi": 305
196
+ },
197
+ {
198
+ "name": "Pusa IMD",
199
+ "lat": 28.6335,
200
+ "lon": 77.1651,
201
+ "aqi": 298
202
+ },
203
+ {
204
+ "name": "Burari Crossing",
205
+ "lat": 28.7592,
206
+ "lon": 77.1938,
207
+ "aqi": 294
208
+ },
209
+ {
210
+ "name": "Aurobindo Marg",
211
+ "lat": 28.545,
212
+ "lon": 77.205,
213
+ "aqi": 287
214
+ },
215
+ {
216
+ "name": "Dilshad Garden (IHBAS)",
217
+ "lat": 28.681,
218
+ "lon": 77.305,
219
+ "aqi": 283
220
+ },
221
+ {
222
+ "name": "Lodhi Road",
223
+ "lat": 28.5921,
224
+ "lon": 77.2284,
225
+ "aqi": 282
226
+ },
227
+ {
228
+ "name": "NSIT-Dwarka",
229
+ "lat": 28.6081,
230
+ "lon": 77.0193,
231
+ "aqi": 275
232
+ },
233
+ {
234
+ "name": "Alipur",
235
+ "lat": 28.8,
236
+ "lon": 77.15,
237
+ "aqi": 273
238
+ },
239
+ {
240
+ "name": "Najafgarh",
241
+ "lat": 28.6125,
242
+ "lon": 76.983,
243
+ "aqi": 262
244
+ },
245
+ {
246
+ "name": "CRRI-Mathura Road",
247
+ "lat": 28.5518,
248
+ "lon": 77.2752,
249
+ "aqi": 252
250
+ },
251
+ {
252
+ "name": "DTU",
253
+ "lat": 28.7501,
254
+ "lon": 77.1177,
255
+ "aqi": 219
256
+ },
257
+ {
258
+ "name": "DU North Campus",
259
+ "lat": 28.69,
260
+ "lon": 77.21,
261
+ "aqi": 298
262
+ },
263
+ {
264
+ "name": "Ayanagar",
265
+ "lat": 28.4809,
266
+ "lon": 77.1255,
267
+ "aqi": 342
268
+ }
269
+ ]
270
+
271
+ for spot in hotspots:
272
+ color = "gray" # Default for NA or invalid
273
+ aqi = spot['aqi']
274
+
275
+ if isinstance(aqi, (int, float)):
276
+ color = "green"
277
+ if aqi > 100: color = "yellow"
278
+ if aqi > 200: color = "orange"
279
+ if aqi > 300: color = "red"
280
+ if aqi > 400: color = "purple"
281
+
282
+ # Add marker
283
+ folium.CircleMarker(
284
+ location=[spot['lat'], spot['lon']],
285
+ radius=20,
286
+ popup=f"{spot['name']}: AQI {spot['aqi']}",
287
+ color=color,
288
+ fill=True,
289
+ fill_color=color,
290
+ fill_opacity=0.7
291
+ ).add_to(m)
292
+
293
+ folium.Marker(
294
+ location=[spot['lat'], spot['lon']],
295
+ icon=folium.DivIcon(html=f'<div style="font-weight: bold; color: black;">{spot["aqi"]}</div>')
296
+ ).add_to(m)
297
+
298
+ return m.get_root().render()
299
+
300
+ def render_map_to_png(html_content, output_path):
301
+ """
302
+ Renders the HTML content to a PNG image using Selenium headless Chrome.
303
+ """
304
+ tmp_html_path = os.path.abspath("temp_map.html")
305
+
306
+ # Write HTML to temp file
307
+ with open(tmp_html_path, "w", encoding="utf-8") as f:
308
+ f.write(html_content)
309
+
310
+ chrome_options = Options()
311
+ chrome_options.add_argument("--headless")
312
+ chrome_options.add_argument("--window-size=800,600")
313
+ chrome_options.add_argument("--disable-gpu")
314
+ chrome_options.add_argument("--no-sandbox")
315
+
316
+ driver = None
317
+ try:
318
+ # Auto-install chromedriver
319
+ service = Service(ChromeDriverManager().install())
320
+ driver = webdriver.Chrome(service=service, options=chrome_options)
321
+
322
+ # Load local HTML file
323
+ driver.get(f"file:///{tmp_html_path}")
324
+ time.sleep(2) # Wait for map to render tiles
325
+
326
+ # Save screenshot
327
+ driver.save_screenshot(output_path)
328
+ print(f"Map image saved to {output_path}")
329
+
330
+ except Exception as e:
331
+ print(f"Error rendering map to PNG: {e}")
332
+ import traceback
333
+ traceback.print_exc()
334
+ finally:
335
+ if driver:
336
+ driver.quit()
337
+ # Cleanup temp file
338
+ if os.path.exists(tmp_html_path):
339
+ os.remove(tmp_html_path)
340
+
341
+ if __name__ == "__main__":
342
+ # Test run
343
+ html = generate_aqi_map_html()
344
+ render_map_to_png(html, "aqi_map_test.png")
config.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configuration for the policy engine.
3
+ """
4
+ import os
5
+ from pathlib import Path
6
+ from dotenv import load_dotenv
7
+
8
+ load_dotenv()
9
+
10
+ # Paths
11
+ BASE_DIR = Path(__file__).parent
12
+ FAISS_INDEX_PATH = BASE_DIR / "faiss_index"
13
+ GRAPH_STATE_PATH = BASE_DIR / "graph_state.json"
14
+ SCENARIOS_PATH = BASE_DIR / "scenarios.json"
15
+
16
+ AMBEE_DATA_KEY = os.getenv("AMBEE_DATA_KEY", "")
17
+ # LLM Configuration
18
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY", "")
19
+ LLM_MODEL = "llama-3.3-70b-versatile"
20
+ LLM_TEMPERATURE = 0.2
21
+
22
+ # FAISS Configuration
23
+ EMBEDDINGS_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
24
+ FAISS_K_SEARCH = 3
25
+
26
+ # Graph Simulation
27
+ SIMULATION_PASSES = 6
28
+
29
+ # API Configuration
30
+ FLASK_PORT = 5000
31
+ FLASK_DEBUG = True
emission_forecast.py ADDED
@@ -0,0 +1,323 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Emission Forecasting API Routes
3
+ Random Forest model for CO2 emission forecasting
4
+ """
5
+ from flask import jsonify, request
6
+ import pandas as pd
7
+ import numpy as np
8
+ # from sklearn.ensemble import RandomForestRegressor (Removed)
9
+ from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error
10
+ from datetime import datetime
11
+ import os
12
+
13
+ # Global model instance
14
+ _forecaster = None
15
+
16
+ class EmissionForecaster:
17
+ def __init__(self, data_path='new_daily_emissions_column_format.xlsx'):
18
+ self.data_path = data_path
19
+ self.model = None
20
+ self.df_features = None
21
+ self.feature_cols = None
22
+ self.avg_sectors = None
23
+ self.metrics = {}
24
+ self.n_lags = 14
25
+ self.target_col = 'Total Emissions'
26
+ self.sector_cols = ['Aviation (%)', 'Ground Transport (%)', 'Industry (%)', 'Power (%)', 'Residential (%)']
27
+
28
+ if os.path.exists(data_path):
29
+ self._train_model()
30
+
31
+ def _create_features(self, data):
32
+ df_feat = data.copy()
33
+ df_feat = df_feat.dropna(subset=['Date', self.target_col]).reset_index(drop=True)
34
+
35
+ df_feat['day_of_week'] = df_feat['Date'].dt.dayofweek
36
+ df_feat['day_of_month'] = df_feat['Date'].dt.day
37
+ df_feat['month'] = df_feat['Date'].dt.month
38
+ df_feat['week_of_year'] = df_feat['Date'].dt.isocalendar().week.fillna(1).astype(int)
39
+ df_feat['is_weekend'] = (df_feat['day_of_week'] >= 5).astype(int)
40
+ df_feat['quarter'] = df_feat['Date'].dt.quarter
41
+ df_feat['day_sin'] = np.sin(2 * np.pi * df_feat['day_of_week'] / 7)
42
+ df_feat['day_cos'] = np.cos(2 * np.pi * df_feat['day_of_week'] / 7)
43
+ df_feat['month_sin'] = np.sin(2 * np.pi * df_feat['month'] / 12)
44
+ df_feat['month_cos'] = np.cos(2 * np.pi * df_feat['month'] / 12)
45
+
46
+ for lag in range(1, self.n_lags + 1):
47
+ df_feat[f'lag_{lag}'] = df_feat[self.target_col].shift(lag)
48
+ df_feat['lag_7_same_day'] = df_feat[self.target_col].shift(7)
49
+ df_feat['lag_14_same_day'] = df_feat[self.target_col].shift(14)
50
+
51
+ for window in [3, 7, 14]:
52
+ df_feat[f'rolling_mean_{window}'] = df_feat[self.target_col].shift(1).rolling(window=window).mean()
53
+ df_feat[f'rolling_std_{window}'] = df_feat[self.target_col].shift(1).rolling(window=window).std()
54
+
55
+ df_feat['ema_7'] = df_feat[self.target_col].shift(1).ewm(span=7).mean()
56
+ df_feat['diff_1'] = df_feat[self.target_col].diff(1)
57
+ df_feat = df_feat.dropna().reset_index(drop=True)
58
+ return df_feat
59
+
60
+ def _train_model(self):
61
+ df = pd.read_excel(self.data_path)
62
+ df['Date'] = pd.to_datetime(df['Date'], dayfirst=True)
63
+ df = df.sort_values('Date').reset_index(drop=True)
64
+ df['Year'] = df['Date'].dt.year
65
+
66
+ self.avg_sectors = df[self.sector_cols].mean().to_dict()
67
+ self.df = df
68
+ self.df_features = self._create_features(df)
69
+
70
+ exclude_cols = ['Date', self.target_col, 'Year'] + self.sector_cols
71
+ self.feature_cols = [col for col in self.df_features.columns if col not in exclude_cols]
72
+
73
+ X = self.df_features[self.feature_cols].values
74
+ y = self.df_features[self.target_col].values
75
+
76
+ test_size = int(len(X) * 0.15)
77
+ X_train, X_test = X[:-test_size], X[-test_size:]
78
+ y_train, y_test = y[:-test_size], y[-test_size:]
79
+
80
+ # Using XGBoost for better performance
81
+ from xgboost import XGBRegressor
82
+
83
+ self.model = XGBRegressor(
84
+ n_estimators=1000,
85
+ learning_rate=0.05,
86
+ max_depth=6,
87
+ subsample=0.8,
88
+ colsample_bytree=0.8,
89
+ random_state=42,
90
+ n_jobs=-1,
91
+ early_stopping_rounds=50
92
+ )
93
+
94
+ # Train with early stopping
95
+ self.model.fit(
96
+ X_train, y_train,
97
+ eval_set=[(X_test, y_test)],
98
+ verbose=False
99
+ )
100
+
101
+ y_pred = self.model.predict(X_test)
102
+ y_pred = self.model.predict(X_test)
103
+ self.metrics = {
104
+ 'r2': round(float(r2_score(y_test, y_pred)), 4),
105
+ 'mae': round(float(mean_absolute_error(y_test, y_pred)), 4),
106
+ 'rmse': round(float(np.sqrt(mean_squared_error(y_test, y_pred))), 4)
107
+ }
108
+
109
+ def _forecast_daily(self, n_days):
110
+ last_date = self.df_features['Date'].iloc[-1]
111
+ recent = self.df_features[self.target_col].values[-max(self.n_lags, 14):].tolist()
112
+ forecasts = []
113
+
114
+ for step in range(1, n_days + 1):
115
+ fd = pd.Timestamp(last_date) + pd.Timedelta(days=step)
116
+ f = {
117
+ 'day_of_week': fd.dayofweek, 'day_of_month': fd.day, 'month': fd.month,
118
+ 'week_of_year': fd.isocalendar()[1], 'is_weekend': 1 if fd.dayofweek >= 5 else 0,
119
+ 'quarter': fd.quarter,
120
+ 'day_sin': np.sin(2 * np.pi * fd.dayofweek / 7),
121
+ 'day_cos': np.cos(2 * np.pi * fd.dayofweek / 7),
122
+ 'month_sin': np.sin(2 * np.pi * fd.month / 12),
123
+ 'month_cos': np.cos(2 * np.pi * fd.month / 12),
124
+ }
125
+ for lag in range(1, self.n_lags + 1):
126
+ f[f'lag_{lag}'] = recent[-lag] if lag <= len(recent) else np.mean(recent)
127
+ f['lag_7_same_day'] = recent[-7] if len(recent) >= 7 else np.mean(recent)
128
+ f['lag_14_same_day'] = recent[-14] if len(recent) >= 14 else np.mean(recent)
129
+ for w in [3, 7, 14]:
130
+ wd = recent[-w:] if len(recent) >= w else recent
131
+ f[f'rolling_mean_{w}'] = np.mean(wd)
132
+ f[f'rolling_std_{w}'] = np.std(wd) if len(wd) > 1 else 0
133
+ f['ema_7'] = pd.Series(recent).ewm(span=7).mean().iloc[-1]
134
+ f['diff_1'] = recent[-1] - recent[-2] if len(recent) >= 2 else 0
135
+
136
+ X_pred = np.array([f[col] for col in self.feature_cols]).reshape(1, -1)
137
+ pred = float(self.model.predict(X_pred)[0])
138
+
139
+ sector_breakdown = {
140
+ k.replace(' (%)', '').replace(' ', '_'): round(pred * v / 100, 4)
141
+ for k, v in self.avg_sectors.items()
142
+ }
143
+
144
+ forecasts.append({
145
+ 'date': fd.strftime('%Y-%m-%d'),
146
+ 'emission': round(pred, 4),
147
+ 'sectors': sector_breakdown
148
+ })
149
+ recent.append(pred)
150
+
151
+ return forecasts
152
+
153
+ def forecast_days(self, n_days):
154
+ n_days = max(1, min(365, n_days))
155
+
156
+ # Calculate gap from last data point to today
157
+ last_date = self.df_features['Date'].iloc[-1]
158
+ today = pd.Timestamp(datetime.now().date())
159
+ days_gap = (today - last_date).days
160
+
161
+ # If there is a gap, extend forecast to cover it + requested days
162
+ total_days = n_days
163
+ if days_gap > 0:
164
+ total_days += days_gap
165
+
166
+ forecasts = self._forecast_daily(total_days)
167
+
168
+ # Determine history length: match n_days (requested forecast length)
169
+ # Note: 'n_days' here is the requested length, 'total_days' includes gap fill.
170
+ # User wants "previous 30 days if we choose 30", so we use original n_days.
171
+ history_days = n_days
172
+
173
+ # Get historical data
174
+ history_data = []
175
+ if self.df is not None and not self.df.empty:
176
+ hist_df = self.df.tail(history_days)
177
+ for _, row in hist_df.iterrows():
178
+ # Calculate sector values from percentages
179
+ sector_breakdown = {}
180
+ for col in self.sector_cols:
181
+ sector_name = col.replace(' (%)', '').replace(' ', '_')
182
+ # If column exists in row, use it, otherwise use average
183
+ pct = row[col] if col in row else self.avg_sectors.get(col, 0)
184
+ sector_breakdown[sector_name] = round(row[self.target_col] * pct / 100, 4)
185
+
186
+ history_data.append({
187
+ 'date': row['Date'].strftime('%Y-%m-%d'),
188
+ 'emission': round(row[self.target_col], 4),
189
+ 'sectors': sector_breakdown,
190
+ 'is_historical': True
191
+ })
192
+
193
+ return {
194
+ 'type': 'daily',
195
+ 'days': n_days,
196
+ 'forecasts': forecasts,
197
+ 'history': history_data,
198
+ 'metrics': self.metrics,
199
+ 'sector_percentages': {k.replace(' (%)', ''): round(v, 2) for k, v in self.avg_sectors.items()}
200
+ }
201
+
202
+ def forecast_years(self, n_years):
203
+ n_years = max(1, min(3, n_years))
204
+ n_days = 365 * (n_years + 1)
205
+ daily = self._forecast_daily(n_days)
206
+
207
+ # Historical yearly averages
208
+ historical = self.df.groupby('Year')[self.target_col].mean().reset_index()
209
+
210
+ # Forecasted yearly averages
211
+ df_forecast = pd.DataFrame(daily)
212
+ df_forecast['year'] = pd.to_datetime(df_forecast['date']).dt.year
213
+ forecast_yearly = df_forecast[df_forecast['year'] >= 2026].groupby('year')['emission'].mean()
214
+ forecast_yearly = forecast_yearly.head(n_years)
215
+
216
+ years_data = []
217
+ for _, row in historical.iterrows():
218
+ years_data.append({
219
+ 'year': int(row['Year']),
220
+ 'avg_emission': round(row[self.target_col], 4),
221
+ 'type': 'historical'
222
+ })
223
+
224
+ for year, emission in forecast_yearly.items():
225
+ sector_breakdown = {
226
+ k.replace(' (%)', '').replace(' ', '_'): round(emission * v / 100, 4)
227
+ for k, v in self.avg_sectors.items()
228
+ }
229
+ years_data.append({
230
+ 'year': int(year),
231
+ 'avg_emission': round(emission, 4),
232
+ 'type': 'forecast',
233
+ 'sectors': sector_breakdown
234
+ })
235
+
236
+ return {
237
+ 'type': 'yearly',
238
+ 'years': n_years,
239
+ 'data': years_data,
240
+ 'metrics': self.metrics,
241
+ 'sector_percentages': {k.replace(' (%)', ''): round(v, 2) for k, v in self.avg_sectors.items()}
242
+ }
243
+
244
+
245
+ def get_forecaster():
246
+ global _forecaster
247
+ if _forecaster is None:
248
+ _forecaster = EmissionForecaster()
249
+ return _forecaster
250
+
251
+
252
+ def register_emission_routes(app):
253
+ """Register emission forecast routes with Flask app."""
254
+
255
+ @app.route('/api/emission/metrics', methods=['GET'])
256
+ def emission_metrics():
257
+ """Get model performance metrics."""
258
+ try:
259
+ forecaster = get_forecaster()
260
+ return jsonify({
261
+ 'status': 'success',
262
+ 'metrics': forecaster.metrics,
263
+ 'sector_percentages': {k.replace(' (%)', ''): round(v, 2) for k, v in forecaster.avg_sectors.items()},
264
+ 'timestamp': datetime.now().isoformat()
265
+ })
266
+ except Exception as e:
267
+ return jsonify({'error': str(e)}), 500
268
+
269
+ @app.route('/api/emission/forecast/days', methods=['POST'])
270
+ def emission_forecast_days():
271
+ """Forecast emissions for 1-365 days."""
272
+ try:
273
+ data = request.json or {}
274
+ n_days = data.get('days', 30)
275
+
276
+ forecaster = get_forecaster()
277
+ result = forecaster.forecast_days(n_days)
278
+
279
+ return jsonify({
280
+ 'status': 'success',
281
+ **result,
282
+ 'timestamp': datetime.now().isoformat()
283
+ })
284
+ except Exception as e:
285
+ return jsonify({'error': str(e)}), 500
286
+
287
+ @app.route('/api/emission/forecast/years', methods=['POST'])
288
+ def emission_forecast_years():
289
+ """Forecast yearly averages for 1-3 years."""
290
+ try:
291
+ data = request.json or {}
292
+ n_years = data.get('years', 1)
293
+
294
+ forecaster = get_forecaster()
295
+ result = forecaster.forecast_years(n_years)
296
+
297
+ return jsonify({
298
+ 'status': 'success',
299
+ **result,
300
+ 'timestamp': datetime.now().isoformat()
301
+ })
302
+ except Exception as e:
303
+ return jsonify({'error': str(e)}), 500
304
+
305
+ if __name__ == "__main__":
306
+ print("Training XGBoost Emission Model...")
307
+ try:
308
+ forecaster = EmissionForecaster()
309
+ print("\nModel Trained Successfully!")
310
+ print("-" * 30)
311
+ print("Performance Metrics (Test Set):")
312
+ for metric, value in forecaster.metrics.items():
313
+ print(f" {metric.upper()}: {value}")
314
+ print("-" * 30)
315
+
316
+ # Optional: Print a sample forecast
317
+ print("\nSample 5-Day Forecast:")
318
+ forecast = forecaster.forecast_days(5)
319
+ for day in forecast['forecasts']:
320
+ print(f" {day['date']}: {day['emission']} tonnes CO2")
321
+
322
+ except Exception as e:
323
+ print(f"\nError: {e}")
explainability.py ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Explainability layer: generates human-readable explanations for policy actions.
3
+ Links mutations to research evidence and identifies trade-offs.
4
+ """
5
+ import json
6
+ from typing import List, Dict, Any, Optional
7
+ from langchain_groq import ChatGroq
8
+ import config
9
+
10
+
11
+ class ExplanationGenerator:
12
+ """Generates narrative explanations for policy mutations."""
13
+
14
+ def __init__(self, policy: Dict[str, Any], research_chunks: List[str]):
15
+ """
16
+ Initialize generator.
17
+
18
+ Args:
19
+ policy: Policy dict with mutations
20
+ research_chunks: List of research excerpts supporting the policy
21
+ """
22
+ self.policy = policy
23
+ self.research = research_chunks
24
+ self.llm = ChatGroq(
25
+ model=config.LLM_MODEL,
26
+ temperature=0.5,
27
+ api_key=config.GROQ_API_KEY
28
+ )
29
+
30
+
31
+ def generate_full_explanation(self) -> Dict[str, Any]:
32
+ """
33
+ Generate complete explainability output for a policy.
34
+
35
+ Returns:
36
+ Dict with intro narrative, per-mutation explanations, trade-offs
37
+ """
38
+ explanations = {
39
+ 'policy_id': self.policy.get('policy_id'),
40
+ 'policy_name': self.policy.get('name'),
41
+ 'narrative_intro': self._generate_intro(),
42
+ 'mutations': [],
43
+ 'overall_narrative': self._generate_overall_narrative()
44
+ }
45
+
46
+ # Generate explanation for each mutation
47
+ for mutation in self.policy.get('mutations', []):
48
+ exp = self._explain_mutation(mutation)
49
+ explanations['mutations'].append(exp)
50
+
51
+ return explanations
52
+
53
+ def _generate_intro(self) -> str:
54
+ """Generate opening narrative for the policy."""
55
+ prompt = f"""
56
+ Write a compelling 2-3 sentence introduction to this climate policy intervention.
57
+ Focus on why it matters for public health and urban sustainability.
58
+
59
+ Policy Name: {self.policy.get('name')}
60
+ Number of Actions: {len(self.policy.get('mutations', []))}
61
+ Expected CO₂ Reduction: {self.policy.get('estimated_impacts', {}).get('co2_reduction_pct', 0)}%
62
+ Expected AQI Improvement: {self.policy.get('estimated_impacts', {}).get('aqi_improvement_pct', 0)}%
63
+
64
+ Keep it concise, impactful, and accessible to policymakers.
65
+ """
66
+
67
+ try:
68
+ response = self.llm.invoke(prompt)
69
+ return response.content
70
+ except Exception as e:
71
+ print(f"Error generating intro: {e}")
72
+ return f"Policy: {self.policy.get('name')} aims to reduce emissions and improve air quality."
73
+
74
+ def _generate_overall_narrative(self) -> str:
75
+ """Generate overall policy narrative."""
76
+ prompt = f"""
77
+ Summarize this climate policy in 3-4 sentences suitable for a policy brief.
78
+ Explain WHAT the policy does, WHY it's effective, and WHO benefits.
79
+
80
+ Policy: {self.policy.get('name')}
81
+ Description: {self.policy.get('description', '')}
82
+ Trade-offs: {json.dumps(self.policy.get('trade_offs', []))}
83
+
84
+ Be concise and professional. Avoid jargon.
85
+ """
86
+
87
+ try:
88
+ response = self.llm.invoke(prompt)
89
+ return response.content
90
+ except Exception as e:
91
+ print(f"Error generating overall narrative: {e}")
92
+ return "Policy designed to reduce urban emissions through targeted sector interventions."
93
+
94
+ def _explain_mutation(self, mutation: Dict[str, Any]) -> Dict[str, Any]:
95
+ """
96
+ Generate explanation for a single mutation.
97
+
98
+ Args:
99
+ mutation: Single mutation dict
100
+
101
+ Returns:
102
+ Dict with narrative, research backing, affected stakeholders
103
+ """
104
+ # Generate narrative explaining the mutation
105
+ narrative = self._generate_mutation_narrative(mutation)
106
+
107
+ # Extract relevant research quotes
108
+ relevant_quotes = self._extract_relevant_quotes(mutation)
109
+
110
+ # Identify stakeholders affected
111
+ stakeholders = self._identify_stakeholders(mutation)
112
+
113
+ return {
114
+ 'mutation': {
115
+ 'type': mutation.get('type'),
116
+ 'target': mutation.get('node_id') or f"{mutation.get('source')} → {mutation.get('target')}",
117
+ 'reason': mutation.get('reason', '')
118
+ },
119
+ 'narrative': narrative,
120
+ 'supporting_research': relevant_quotes,
121
+ 'affected_stakeholders': stakeholders
122
+ }
123
+
124
+ def _generate_mutation_narrative(self, mutation: Dict[str, Any]) -> str:
125
+ """Generate 2-3 sentence explanation of why this mutation is applied."""
126
+ mutation_type = mutation.get('type')
127
+
128
+ if mutation_type == 'disable_node':
129
+ target = mutation.get('node_id', 'sector')
130
+ prompt = f"""
131
+ Explain why disabling the {target} sector is effective for reducing emissions.
132
+ Write 2-3 sentences suitable for a policy document.
133
+ Focus on: causal mechanism, expected benefits, and evidence base.
134
+
135
+ Research evidence available:
136
+ {chr(10).join(self.research[:2])}
137
+ """
138
+ elif mutation_type == 'reduce_edge_weight':
139
+ source = mutation.get('source', 'source')
140
+ target = mutation.get('target', 'target')
141
+ new_weight = mutation.get('new_weight', 0.5)
142
+ reduction = ((1 - new_weight) * 100) if new_weight else 0
143
+
144
+ prompt = f"""
145
+ Explain why reducing the {source} → {target} relationship (by ~{reduction:.0f}%) helps reduce emissions.
146
+ This represents implementing technology or policy to reduce the causal influence.
147
+
148
+ Write 2-3 sentences. Include: HOW (technology/policy), WHY (mechanism), IMPACT (benefits).
149
+
150
+ Research evidence:
151
+ {chr(10).join(self.research[:2])}
152
+ """
153
+ elif mutation_type == 'increase_edge_weight':
154
+ source = mutation.get('source', 'source')
155
+ target = mutation.get('target', 'target')
156
+ new_weight = mutation.get('new_weight', 0.7)
157
+ increase = ((new_weight - 1) * 100) if new_weight > 1 else 0
158
+
159
+ prompt = f"""
160
+ Explain why strengthening the {source} → {target} relationship helps achieve climate goals.
161
+ This might represent policy incentives or investment in beneficial activities.
162
+
163
+ Write 2-3 sentences explaining the mechanism and benefits.
164
+
165
+ Research evidence:
166
+ {chr(10).join(self.research[:2])}
167
+ """
168
+ else:
169
+ return "This policy action adjusts system dynamics to improve environmental outcomes."
170
+
171
+ try:
172
+ response = self.llm.invoke(prompt)
173
+ return response.content
174
+ except Exception as e:
175
+ print(f"Error generating mutation narrative: {e}")
176
+ return mutation.get('reason', 'Policy action targeting emissions reduction.')
177
+
178
+ def _extract_relevant_quotes(self, mutation: Dict[str, Any]) -> List[str]:
179
+ """
180
+ Find research quotes most relevant to this mutation.
181
+ Simple keyword matching; can be enhanced with semantic search.
182
+ """
183
+ quotes = []
184
+ keywords = []
185
+
186
+ # Determine relevant keywords based on mutation
187
+ if mutation.get('type') == 'disable_node':
188
+ keywords = [mutation.get('node_id', '')]
189
+ else:
190
+ keywords = [
191
+ mutation.get('source', ''),
192
+ mutation.get('target', '')
193
+ ]
194
+
195
+ # Search for quotes containing keywords
196
+ for chunk in self.research:
197
+ for keyword in keywords:
198
+ if keyword.lower() in chunk.lower():
199
+ if chunk not in quotes:
200
+ quotes.append(chunk[:150] + "..." if len(chunk) > 150 else chunk)
201
+ break
202
+
203
+ # Return first 2 relevant quotes
204
+ return quotes[:2] if quotes else [
205
+ "Research demonstrates effectiveness of targeted emission control policies.",
206
+ "Evidence supports multi-sector approach to urban air quality improvement."
207
+ ]
208
+
209
+ def _identify_stakeholders(self, mutation: Dict[str, Any]) -> List[Dict[str, str]]:
210
+ """
211
+ Identify sectors/groups affected by this mutation.
212
+ """
213
+ stakeholders = []
214
+ mutation_type = mutation.get('type')
215
+
216
+ # Map nodes/sectors to stakeholders
217
+ sector_to_stakeholders = {
218
+ 'transport': [
219
+ {'group': 'Transport operators', 'impact': 'operational changes'},
220
+ {'group': 'Auto manufacturers', 'impact': 'R&D investment'},
221
+ {'group': 'Urban commuters', 'impact': 'improved air quality'}
222
+ ],
223
+ 'energy': [
224
+ {'group': 'Power utilities', 'impact': 'generation mix shift'},
225
+ {'group': 'Coal industry', 'impact': 'transition costs'},
226
+ {'group': 'Solar/wind companies', 'impact': 'growth opportunities'},
227
+ {'group': 'Citizens', 'impact': 'cleaner air'}
228
+ ],
229
+ 'industries': [
230
+ {'group': 'Manufacturers', 'impact': 'efficiency improvements'},
231
+ {'group': 'Workers', 'impact': 'job transitions'},
232
+ {'group': 'Consumers', 'impact': 'potential cost changes'}
233
+ ],
234
+ 'infrastructure': [
235
+ {'group': 'Construction sector', 'impact': 'green building standards'},
236
+ {'group': 'Urban planners', 'impact': 'planning considerations'},
237
+ {'group': 'Real estate', 'impact': 'sustainable development'}
238
+ ]
239
+ }
240
+
241
+ # Extract stakeholders based on affected node/sector
242
+ if mutation_type == 'disable_node':
243
+ target = mutation.get('node_id', '')
244
+ else:
245
+ target = mutation.get('source', '') or mutation.get('target', '')
246
+
247
+ for key, groups in sector_to_stakeholders.items():
248
+ if key in target.lower():
249
+ stakeholders = groups
250
+ break
251
+
252
+ return stakeholders if stakeholders else [
253
+ {'group': 'Urban residents', 'impact': 'health and quality of life'},
254
+ {'group': 'Industry stakeholders', 'impact': 'economic adaptation'}
255
+ ]
256
+
257
+
258
+ # ============================================================================
259
+ # STANDALONE HELPER FUNCTIONS
260
+ # ============================================================================
261
+
262
+ def generate_policy_explanation(
263
+ policy: Dict[str, Any],
264
+ research_chunks: List[str]
265
+ ) -> Dict[str, Any]:
266
+ """
267
+ Convenience function to generate explanation for a policy.
268
+
269
+ Args:
270
+ policy: Policy dict
271
+ research_chunks: List of research excerpts
272
+
273
+ Returns:
274
+ Full explanation dict
275
+ """
276
+ generator = ExplanationGenerator(policy, research_chunks)
277
+ return generator.generate_full_explanation()
faiss_index/index.faiss ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:58f3a028bd783459688d8d8a61b53a6d6c595580136134e3a9783f094c1de21d
3
+ size 205869
faiss_index/index.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8ca9101043d20234372987de2f68ff0522cc674bdd0e6c339a0b7f42e0044bc6
3
+ size 123525
graph_engine.py ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Graph mutation and simulation engine.
3
+ Applies policies to the causal graph and runs multi-pass propagation.
4
+ """
5
+ import json
6
+ import copy
7
+ from typing import Dict, List, Any, Tuple
8
+ from datetime import datetime
9
+
10
+
11
+ class GraphState:
12
+ """Represents the state of the causal graph."""
13
+
14
+ def __init__(self, nodes: List[Dict], edges: List[Dict]):
15
+ """
16
+ Initialize graph state.
17
+
18
+ Args:
19
+ nodes: List of node dicts with {id, label, type, enabled, value, ...}
20
+ edges: List of edge dicts with {id, source, target, weight, ...}
21
+ """
22
+ self.nodes = copy.deepcopy(nodes)
23
+ self.edges = copy.deepcopy(edges)
24
+ self.history = [] # For undo/redo
25
+ self.baseline_snapshot = None
26
+
27
+ @staticmethod
28
+ def from_file(filepath: str) -> 'GraphState':
29
+ """Load graph state from JSON file."""
30
+ with open(filepath, 'r') as f:
31
+ data = json.load(f)
32
+
33
+ instance = GraphState(data.get('nodes', []), data.get('edges', []))
34
+ instance.baseline_snapshot = copy.deepcopy(data)
35
+ return instance
36
+
37
+ def to_dict(self) -> Dict[str, Any]:
38
+ """Export graph state to dict."""
39
+ return {
40
+ 'nodes': self.nodes,
41
+ 'edges': self.edges,
42
+ 'timestamp': datetime.now().isoformat()
43
+ }
44
+
45
+ def get_node(self, node_id: str) -> Dict:
46
+ """Get a node by ID."""
47
+ for node in self.nodes:
48
+ if node['id'] == node_id:
49
+ return node
50
+ return None
51
+
52
+ def get_edge(self, source: str, target: str) -> Dict:
53
+ """Get an edge by source and target."""
54
+ for edge in self.edges:
55
+ if edge['source'] == source and edge['target'] == target:
56
+ return edge
57
+ return None
58
+
59
+ def apply_mutation(self, mutation: Dict) -> Dict:
60
+ """
61
+ Apply a single mutation, return change record.
62
+
63
+ Args:
64
+ mutation: {type, node_id, source, target, new_weight, reason, ...}
65
+
66
+ Returns:
67
+ Change record for audit trail
68
+ """
69
+ change = {
70
+ 'type': mutation['type'],
71
+ 'before': None,
72
+ 'after': None,
73
+ 'timestamp': datetime.now().isoformat()
74
+ }
75
+
76
+ if mutation['type'] == 'disable_node':
77
+ node_id = mutation['node_id']
78
+ node = next((n for n in self.nodes if n['id'] == node_id), None)
79
+
80
+ if not node:
81
+ raise ValueError(f"Node not found: {node_id}")
82
+
83
+ change['before'] = {'id': node_id, 'enabled': node.get('enabled', True)}
84
+ node['enabled'] = False
85
+ change['after'] = {'id': node_id, 'enabled': False}
86
+
87
+ elif mutation['type'] == 'reduce_edge_weight' or mutation['type'] == 'increase_edge_weight':
88
+ source = mutation['source']
89
+ target = mutation['target']
90
+ edge = next((e for e in self.edges if e['source'] == source and e['target'] == target), None)
91
+
92
+ if not edge:
93
+ raise ValueError(f"Edge not found: {source} -> {target}")
94
+
95
+ old_weight = edge.get('data', {}).get('weight', 0.5) if isinstance(edge.get('data'), dict) else 0.5
96
+ change['before'] = {'source': source, 'target': target, 'weight': old_weight}
97
+
98
+ # Update edge weight
99
+ if 'data' not in edge:
100
+ edge['data'] = {}
101
+ edge['data']['weight'] = mutation['new_weight']
102
+
103
+ change['after'] = {'source': source, 'target': target, 'weight': mutation['new_weight']}
104
+
105
+ self.history.append(change)
106
+ return change
107
+
108
+ def apply_policy(self, policy: Dict) -> Dict:
109
+ """
110
+ Apply all mutations in a policy.
111
+
112
+ Args:
113
+ policy: Policy dict with 'mutations' list
114
+
115
+ Returns:
116
+ Result dict with applied mutations and any errors
117
+ """
118
+ results = {
119
+ 'policy_id': policy.get('policy_id'),
120
+ 'mutations_applied': [],
121
+ 'timestamp': datetime.now().isoformat(),
122
+ 'errors': []
123
+ }
124
+
125
+ for mutation in policy.get('mutations', []):
126
+ try:
127
+ result = self.apply_mutation(mutation)
128
+ results['mutations_applied'].append(result)
129
+ except Exception as e:
130
+ results['errors'].append({
131
+ 'mutation': mutation.get('type'),
132
+ 'error': str(e)
133
+ })
134
+
135
+ return results
136
+
137
+ def run_simulation(self) -> Dict[str, Any]:
138
+ """
139
+ Multi-pass value propagation through causal graph.
140
+ Simulates how changes cascade through the system.
141
+
142
+ Returns:
143
+ Dict with node_values and output metrics (co2, aqi)
144
+ """
145
+ # Initialize node values
146
+ node_values = {}
147
+ for node in self.nodes:
148
+ node_id = node['id']
149
+ node_type = node.get('data', {}).get('type') if isinstance(node.get('data'), dict) else node.get('type', 'intermediate')
150
+
151
+ # Sector nodes start with baseline values
152
+ if node_type == 'sector':
153
+ node_values[node_id] = node.get('data', {}).get('value', 100) if isinstance(node.get('data'), dict) else 100
154
+ else:
155
+ node_values[node_id] = 0
156
+
157
+ # Multi-pass propagation (captures cascading effects)
158
+ for iteration in range(6): # config.SIMULATION_PASSES
159
+ for edge in self.edges:
160
+ source_id = edge['source']
161
+ target_id = edge['target']
162
+
163
+ # Skip if nodes disabled or not found
164
+ source_node = next((n for n in self.nodes if n['id'] == source_id), None)
165
+ target_node = next((n for n in self.nodes if n['id'] == target_id), None)
166
+
167
+ if not source_node or not target_node:
168
+ continue
169
+
170
+ source_enabled = source_node.get('data', {}).get('enabled', True) if isinstance(source_node.get('data'), dict) else True
171
+ target_enabled = target_node.get('data', {}).get('enabled', True) if isinstance(target_node.get('data'), dict) else True
172
+
173
+ if not (source_enabled and target_enabled):
174
+ continue
175
+
176
+ # Propagate value
177
+ source_val = node_values.get(source_id, 0)
178
+ weight = edge.get('data', {}).get('weight', 0.5) if isinstance(edge.get('data'), dict) else 0.5
179
+
180
+ if source_val > 0:
181
+ node_values[target_id] = node_values.get(target_id, 0) + source_val * weight
182
+
183
+ # Extract outputs
184
+ return {
185
+ 'node_values': node_values,
186
+ 'outputs': {
187
+ 'co2': node_values.get('co2', 0),
188
+ 'aqi': node_values.get('aqi', 0)
189
+ }
190
+ }
191
+
192
+ def reset(self):
193
+ """Reset graph to baseline state."""
194
+ if self.baseline_snapshot:
195
+ self.nodes = copy.deepcopy(self.baseline_snapshot.get('nodes', []))
196
+ self.edges = copy.deepcopy(self.baseline_snapshot.get('edges', []))
197
+ self.history = []
198
+
199
+ def undo(self, steps: int = 1) -> bool:
200
+ """Revert last N mutations."""
201
+ for _ in range(steps):
202
+ if not self.history:
203
+ return False
204
+
205
+ change = self.history.pop()
206
+
207
+ # Reverse the change
208
+ if change['type'] == 'disable_node':
209
+ node_id = change['before']['id']
210
+ node = next((n for n in self.nodes if n['id'] == node_id), None)
211
+ if node:
212
+ node['enabled'] = change['before']['enabled']
213
+
214
+ elif 'weight' in str(change['type']):
215
+ source = change['before']['source']
216
+ target = change['before']['target']
217
+ edge = next((e for e in self.edges if e['source'] == source and e['target'] == target), None)
218
+ if edge:
219
+ if 'data' not in edge:
220
+ edge['data'] = {}
221
+ edge['data']['weight'] = change['before']['weight']
222
+
223
+ return True
224
+
225
+
226
+ class ImpactAnalyzer:
227
+ """Analyzes impact of policies by comparing baseline vs post-policy states."""
228
+
229
+ def __init__(self, baseline_state: GraphState, post_policy_state: GraphState):
230
+ """
231
+ Initialize analyzer.
232
+
233
+ Args:
234
+ baseline_state: GraphState before policy
235
+ post_policy_state: GraphState after policy
236
+ """
237
+ self.baseline = baseline_state
238
+ self.post_policy = post_policy_state
239
+
240
+ def calculate_impact(self) -> Dict[str, Any]:
241
+ """
242
+ Calculate impact metrics.
243
+
244
+ Returns:
245
+ Dict with CO₂ and AQI changes, cascade analysis, etc.
246
+ """
247
+ baseline_sim = self.baseline.run_simulation()
248
+ post_sim = self.post_policy.run_simulation()
249
+
250
+ baseline_co2 = baseline_sim['outputs']['co2']
251
+ post_co2 = post_sim['outputs']['co2']
252
+ baseline_aqi = baseline_sim['outputs']['aqi']
253
+ post_aqi = post_sim['outputs']['aqi']
254
+
255
+ # Calculate impacts
256
+ impact = {
257
+ 'co2': {
258
+ 'baseline': baseline_co2,
259
+ 'post_policy': post_co2,
260
+ 'change_absolute': post_co2 - baseline_co2,
261
+ 'change_pct': (
262
+ ((post_co2 - baseline_co2) / baseline_co2 * 100)
263
+ if baseline_co2 > 0 else 0
264
+ )
265
+ },
266
+ 'aqi': {
267
+ 'baseline': baseline_aqi,
268
+ 'post_policy': post_aqi,
269
+ 'change_absolute': post_aqi - baseline_aqi,
270
+ 'change_pct': (
271
+ ((post_aqi - baseline_aqi) / baseline_aqi * 100)
272
+ if baseline_aqi > 0 else 0
273
+ )
274
+ }
275
+ }
276
+
277
+ # Cascade analysis
278
+ cascade = self.analyze_cascade(baseline_sim, post_sim)
279
+ impact['cascade_analysis'] = cascade
280
+
281
+ return impact
282
+
283
+ def analyze_cascade(self, baseline_sim: Dict, post_sim: Dict) -> Dict[str, Any]:
284
+ """
285
+ Identify which nodes changed most (1st, 2nd, 3rd order effects).
286
+ """
287
+ baseline_vals = baseline_sim['node_values']
288
+ post_vals = post_sim['node_values']
289
+
290
+ node_changes = {}
291
+ for node_id in baseline_vals:
292
+ baseline_val = baseline_vals[node_id]
293
+ post_val = post_vals.get(node_id, 0)
294
+
295
+ if baseline_val > 0.001: # Avoid division by very small numbers
296
+ pct_change = ((post_val - baseline_val) / baseline_val) * 100
297
+ node_changes[node_id] = {
298
+ 'baseline': baseline_val,
299
+ 'post_policy': post_val,
300
+ 'change_pct': pct_change
301
+ }
302
+
303
+ # Sort by magnitude of change
304
+ sorted_changes = sorted(
305
+ node_changes.items(),
306
+ key=lambda x: abs(x[1]['change_pct']),
307
+ reverse=True
308
+ )
309
+
310
+ return {
311
+ 'most_affected_nodes': [(node_id, data['change_pct']) for node_id, data in sorted_changes[:10]],
312
+ 'all_node_changes': node_changes,
313
+ 'summary': {
314
+ 'nodes_with_reduction': len([d for d in node_changes.values() if d['change_pct'] < 0]),
315
+ 'nodes_with_increase': len([d for d in node_changes.values() if d['change_pct'] > 0]),
316
+ 'avg_change_pct': sum(d['change_pct'] for d in node_changes.values()) / len(node_changes) if node_changes else 0
317
+ }
318
+ }
health_analyzer.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Air Quality Health Impact Analyzer
3
+ Uses Gemini to provide health risks based on AQI levels
4
+ """
5
+ from langchain_groq import ChatGroq
6
+ import config
7
+ from typing import Dict, Any
8
+
9
+ HEALTH_IMPACT_KNOWLEDGE_BASE = """
10
+ # SYSTEM ROLE
11
+ You are the Air Quality Health Impact Expert. Your purpose is to provide highly specific, data-driven health advice based on Air Quality Index (AQI) levels. You must use the provided tables to determine physiological impacts, mortality risks, and required safeguards.
12
+
13
+ # KNOWLEDGE BASE
14
+
15
+ ## 1. CORE AQI STAGES & PHYSIOLOGICAL IMPACT
16
+ - 0–50 (Good): PM2.5 0–30. Normal pulmonary clearance.
17
+ - 51–100 (Satisfactory): PM2.5 31–60. Mild airway irritation, asthma discomfort.
18
+ - 101–200 (Moderate): PM2.5 61–90. Reduced lung efficiency, cardiac strain.
19
+ - 201–300 (Poor): PM2.5 91–120. Chronic inflammation, COPD exacerbation.
20
+ - 301–400 (Very Poor): PM2.5 121–250. Systemic oxidative stress, heart attack risk.
21
+ - 401–500+ (Severe): PM2.5 >250. Multi-organ stress, respiratory failure, stroke.
22
+
23
+ ## 2. INFANT, CHILD & PREGNANCY RISKS
24
+ - Newborns (<28 days): Wheezing risk at 101-200; Chronic inflammation at 201-300; Hypoxia/Failure at 401+.
25
+ - Children (1-5 yrs): Lung growth delay at 201-300; Arrested alveolar development at 301-400.
26
+ - Pregnancy: Preterm birth risk increases at 101-200. Preeclampsia/Gestational Diabetes at 301-400. Stillbirth/Placental failure risk at 401+.
27
+
28
+ ## 3. AGE-SPECIFIC THREATS (VITAL THRESHOLDS)
29
+ - 16–25 yrs: Reduced VO₂ max and early lung aging starts at 201-300.
30
+ - 26–35 yrs: Fertility markers and sperm quality decrease at 101-200+.
31
+ - 36–50 yrs: Hypertension onset at 101-200; Heart disease/MI risk at 301+.
32
+ - 51–65 yrs: Stroke risk increases at 101-200; Neurological deficits at 301+.
33
+ - 66–75+ yrs: Cognitive decline/Dementia onset at 201-300; Mortality spikes at 401+.
34
+
35
+ ## 4. PRE-EXISTING CONDITIONS
36
+ - Asthma: Inhaler use ↑ (101-200), ER visits (301-400), Life-threatening (401+).
37
+ - Diabetes: Insulin resistance ↑ (201-300), Vascular damage (301-400).
38
+ - Elderly: Infection susceptibility (101-200), Mobility loss (301-400).
39
+ - Cardiovascular Disease: Increased risk of MI/stroke at 201-300; Exacerbation of heart failure at 301-400.
40
+
41
+ ## 5. LIFE EXPECTANCY LOSS (LONG-TERM)
42
+ - Delhi Residents: 3–5 yrs lost at Poor (201-300); 10–12 yrs lost at Severe (401+).
43
+ - Elderly (65+): Up to 15 years lost in Severe conditions.
44
+ - Children (NCR): Permanent lifelong health deficits and lifespan reduction.
45
+
46
+ ## 6. EXPOSURE DIMENSIONS
47
+ - Acute (1-7 days): Hospitalization risk at 301+.
48
+ - Sub-chronic (Weeks): Structural lung damage begins at 301+.
49
+ - Chronic (Years): Dementia and premature death at 401+.
50
+
51
+ ## 7. SAFEGUARD PROTOCOLS
52
+ - 101–200: Use exhaust fans/filtration; N95 for long exposure.
53
+ - 201–300: HEPA purifiers mandatory; Wet mopping; N95 mandatory outdoors.
54
+ - 301–400: Seal rooms; Stay indoors; N95/N99 compulsory.
55
+ - 401–500+: Full isolation; No dust sources; Emergency meds/nebulizers ready.
56
+
57
+ # RESPONSE GUIDELINES
58
+ 1. If a user provides an AQI, cross-reference all tables (Age, Condition, and Safeguard).
59
+ 2. If the user is in Delhi/NCR, emphasize the Life Expectancy Loss data.
60
+ 3. Be clinical but empathetic. Use terms like "Systemic oxidative stress" or "Placental perfusion" to explain risks.
61
+ 4. Respond in numbered sections: Risk Summary, Age-Specific Impact, Immediate Actions, Long-Term Risk.
62
+ """
63
+
64
+
65
+ class HealthImpactAnalyzer:
66
+ """Analyzes health impacts of AQI levels using Gemini"""
67
+
68
+ def __init__(self):
69
+
70
+ self.llm = ChatGroq(
71
+ model=config.LLM_MODEL,
72
+ temperature=0.5,
73
+ api_key=config.GROQ_API_KEY
74
+ )
75
+
76
+
77
+ def analyze_aqi_health(self, aqi_data: Dict[str, Any]) -> Dict[str, Any]:
78
+ """
79
+ Analyze health impacts based on AQI data
80
+
81
+ Args:
82
+ aqi_data: {
83
+ 'aqi': int (0-500),
84
+ 'city': str,
85
+ 'pm2_5': float,
86
+ 'pm10': float,
87
+ 'no2': float,
88
+ 'o3': float,
89
+ 'so2': float,
90
+ 'co': float
91
+ }
92
+
93
+ Returns:
94
+ Health impact analysis with risk assessment
95
+ """
96
+ aqi = aqi_data.get('aqi', 0)
97
+ city = aqi_data.get('city', 'Unknown')
98
+ pm2_5 = aqi_data.get('pm2_5', 0)
99
+
100
+ prompt = f"""{HEALTH_IMPACT_KNOWLEDGE_BASE}
101
+
102
+ # USER DATA
103
+ AQI Level: {aqi}
104
+ City: {city}
105
+ PM2.5: {pm2_5} µg/m³
106
+ PM10: {aqi_data.get('pm10', 0)} µg/m³
107
+ NO₂: {aqi_data.get('no2', 0)} ppb
108
+ O₃: {aqi_data.get('o3', 0)} ppb
109
+ SO₂: {aqi_data.get('so2', 0)} ppb
110
+ CO: {aqi_data.get('co', 0)} ppm
111
+
112
+ Based on the AQI level ({aqi}) and the knowledge base above, provide a strict JSON response.
113
+
114
+ Return ONLY valid JSON (no markdown):
115
+ {{
116
+ "aqi_level": {aqi},
117
+ "category": "Good|Satisfactory|Moderate|Poor|Very Poor|Severe",
118
+ "risk_summary": "Overall assessment. Be clinical but empathetic.",
119
+ "age_specific_impacts": {{
120
+ "newborns": "Risk assessment for <28 days",
121
+ "children": "Risk assessment for 1-5 yrs",
122
+ "teenagers_young_adults": "Risk assessment for 16-35 yrs",
123
+ "adults_36_65": "Risk assessment for 36-65 yrs",
124
+ "elderly": "Risk assessment for >65 yrs"
125
+ }},
126
+ "pregnancy_risks": "Specific risks for pregnancy",
127
+ "pre_existing_conditions": {{
128
+ "asthma": "Specific advice",
129
+ "diabetes": "Specific advice",
130
+ "cardiovascular": "Specific advice"
131
+ }},
132
+ "immediate_actions": [
133
+ "Action 1",
134
+ "Action 2",
135
+ "Action 3"
136
+ ],
137
+ "long_term_risk": {{
138
+ "life_expectancy_loss": "e.g., 3-5 years",
139
+ "chronic_conditions": "List potential chronic issues"
140
+ }},
141
+ "safeguard_protocols": [
142
+ "Protocol 1",
143
+ "Protocol 2",
144
+ "Protocol 3"
145
+ ],
146
+ "urgency_level": "Low|Medium|High|Critical"
147
+ }}"""
148
+
149
+ response = self.llm.invoke(prompt)
150
+ response_text = response.content
151
+
152
+ # Extract JSON
153
+ import json
154
+ import re
155
+ json_match = re.search(r'\{[\s\S]*\}', response_text)
156
+ if not json_match:
157
+ raise ValueError(f"No JSON in response: {response_text}")
158
+
159
+ return json.loads(json_match.group())
160
+
161
+ def chat_with_health_expert(self, message: str, context: Dict[str, Any] = None) -> str:
162
+ """
163
+ Chat with the expert about specific health concerns.
164
+ """
165
+ aqi = context.get('aqi', 'Unknown') if context else 'Unknown'
166
+ risk = context.get('risk_summary', 'Unknown') if context else 'Unknown'
167
+
168
+ prompt = f"""You are a helpful, empathetic medical assistant specializing in Air Quality health effects.
169
+
170
+ Current Context:
171
+ - Live AQI: {aqi}
172
+ - Risk Level: {risk}
173
+ - User Location: {context.get('city', 'Delhi') if context else 'Delhi'}
174
+
175
+ User Question: "{message}"
176
+
177
+ Provide a short, practical, and medically sound answer (2-3 sentences max unless asked for detail).
178
+ Focus on immediate actions and reassurance or warnings as appropriate. Do not verify the AQI yourself, assume the context is true.
179
+ """
180
+ response = self.llm.invoke(prompt)
181
+ return response.content
ingest.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ from langchain_community.document_loaders import PyPDFLoader
4
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
5
+ from langchain_huggingface import HuggingFaceEmbeddings
6
+ from langchain_community.vectorstores import FAISS
7
+
8
+ PDF_DIR = "papers"
9
+
10
+ docs = []
11
+
12
+ for file in os.listdir(PDF_DIR):
13
+ if file.endswith(".pdf"):
14
+ loader = PyPDFLoader(os.path.join(PDF_DIR, file))
15
+ docs.extend(loader.load())
16
+
17
+ # Split into chunks
18
+ splitter = RecursiveCharacterTextSplitter(
19
+ chunk_size=800,
20
+ chunk_overlap=150
21
+ )
22
+
23
+ chunks = splitter.split_documents(docs)
24
+
25
+ # Embeddings
26
+ embeddings = HuggingFaceEmbeddings(
27
+ model_name="sentence-transformers/all-MiniLM-L6-v2"
28
+ )
29
+
30
+ # FAISS index
31
+ db = FAISS.from_documents(chunks, embeddings)
32
+
33
+ db.save_local("faiss_index")
34
+
35
+ print("✅ FAISS index created successfully.")
new_daily_emissions_column_format.xlsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9c577399c88c05f4cedaf0f4926fd0cdf81a17f19936798a24ced3ebdbc86ccd
3
+ size 119421
policy_engine.py ADDED
@@ -0,0 +1,393 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Core policy engine: transforms research into structured policies.
3
+ Uses LLM with structured prompting and Pydantic validation.
4
+ """
5
+ import json
6
+ import re
7
+ from typing import List, Optional, Dict, Any
8
+ from pydantic import BaseModel, ValidationError, Field
9
+ from langchain_community.vectorstores import FAISS
10
+ from langchain_huggingface import HuggingFaceEmbeddings
11
+ from langchain_groq import ChatGroq
12
+ from langchain_core.prompts import PromptTemplate
13
+
14
+ import config
15
+
16
+
17
+ # ============================================================================
18
+ # PYDANTIC MODELS
19
+ # ============================================================================
20
+
21
+ class PolicyMutation(BaseModel):
22
+ """Represents a single mutation to the causal graph."""
23
+ type: str = Field(..., description="disable_node | reduce_edge_weight | increase_edge_weight")
24
+ node_id: Optional[str] = Field(None, description="For disable_node mutations")
25
+ source: Optional[str] = Field(None, description="For edge mutations")
26
+ target: Optional[str] = Field(None, description="For edge mutations")
27
+ new_weight: Optional[float] = Field(None, description="New edge weight [0.0, 1.0]")
28
+ original_weight: Optional[float] = Field(None, description="Original weight (optional)")
29
+ reason: str = Field(..., description="Why this mutation is applied")
30
+ reversible: bool = Field(True, description="Can this be undone?")
31
+
32
+
33
+ class SourceResearch(BaseModel):
34
+ """Research evidence backing the policy."""
35
+ paper_ids: List[str] = Field(default_factory=list)
36
+ key_quotes: List[str] = Field(default_factory=list)
37
+ confidence: float = Field(0.8, description="Confidence in policy [0.0, 1.0]")
38
+
39
+
40
+ class TradeOff(BaseModel):
41
+ """Trade-off from policy implementation."""
42
+ sector: str
43
+ impact: str = Field(..., description="positive | negative | neutral")
44
+ magnitude: str = Field(..., description="mild | moderate | strong")
45
+ description: str
46
+
47
+
48
+ class EstimatedImpact(BaseModel):
49
+ """Estimated system-wide impacts."""
50
+ co2_reduction_pct: float = Field(0.0, description="% reduction in CO₂")
51
+ aqi_improvement_pct: float = Field(0.0, description="% reduction in AQI")
52
+ confidence: float = Field(0.7)
53
+
54
+
55
+ class Policy(BaseModel):
56
+ """Complete structured policy JSON."""
57
+ policy_id: str
58
+ name: str
59
+ description: Optional[str] = None
60
+ mutations: List[PolicyMutation]
61
+ estimated_impacts: EstimatedImpact
62
+ trade_offs: List[TradeOff] = Field(default_factory=list)
63
+ source_research: SourceResearch
64
+ timestamp: Optional[str] = None
65
+
66
+
67
+ # ============================================================================
68
+ # POLICY ENGINE
69
+ # ============================================================================
70
+
71
+ class PolicyEngine:
72
+ """Converts research insights into structured policies via LLM."""
73
+
74
+ def __init__(self):
75
+ """Initialize FAISS index and LLM."""
76
+ self.embeddings = HuggingFaceEmbeddings(
77
+ model_name=config.EMBEDDINGS_MODEL
78
+ )
79
+
80
+ try:
81
+ self.db = FAISS.load_local(
82
+ str(config.FAISS_INDEX_PATH),
83
+ self.embeddings,
84
+ allow_dangerous_deserialization=True
85
+ )
86
+ except Exception as e:
87
+ print(f"Warning: FAISS index not found at {config.FAISS_INDEX_PATH}")
88
+ print(f"Error: {e}")
89
+ self.db = None
90
+
91
+ self.llm = ChatGroq(
92
+ model=config.LLM_MODEL,
93
+ temperature=0.5,
94
+ api_key=config.GROQ_API_KEY
95
+ )
96
+
97
+
98
+ def query_research(self, question: str, k: int = None) -> tuple[List[str], bool]:
99
+ """
100
+ Retrieve research chunks from FAISS.
101
+
102
+ Args:
103
+ question: Search query
104
+ k: Number of results (default from config)
105
+
106
+ Returns:
107
+ Tuple of (research chunks or [question], is_direct_query)
108
+ """
109
+ if not self.db:
110
+ print(f"FAISS DB not initialized, using direct query: {question}")
111
+ # Return the user's query directly when FAISS is not available
112
+ return [question], True
113
+
114
+ k = k or config.FAISS_K_SEARCH
115
+ results = self.db.similarity_search(question, k=k)
116
+ return [r.page_content for r in results], False
117
+
118
+ def extract_policy(
119
+ self,
120
+ research_chunks: List[str],
121
+ graph_context: Dict[str, Any],
122
+ is_direct_query: bool = False,
123
+ user_query: str = ""
124
+ ) -> Policy:
125
+ """
126
+ Use LLM to extract structured policy from research.
127
+
128
+ Args:
129
+ research_chunks: List of research excerpts or [user_query] if direct
130
+ graph_context: Dict with node/edge structure for validation
131
+ is_direct_query: True if research_chunks contains user query (no FAISS)
132
+ user_query: The original user query string (optional but recommended)
133
+
134
+ Returns:
135
+ Validated Policy object
136
+ """
137
+ # Format content for prompt
138
+ # Ensure all chunks are strings (handle potential nested lists)
139
+ flat_chunks = []
140
+ for chunk in research_chunks:
141
+ if isinstance(chunk, list):
142
+ flat_chunks.extend([str(c) for c in chunk])
143
+ else:
144
+ flat_chunks.append(str(chunk))
145
+
146
+ # Determine intent from user_query if available, otherwise check chunks
147
+ query_text = user_query if user_query else (flat_chunks[0] if flat_chunks else "")
148
+ query_lower = query_text.lower()
149
+
150
+ increase_emissions = any(word in query_lower for word in ["increase", "raise", "boost", "expand", "grow", "worsen", "high"])
151
+
152
+ if is_direct_query:
153
+ research_section = f"USER QUERY: {query_text}\n\nUse your knowledge to create a policy addressing this query."
154
+ else:
155
+ research_section = f"USER QUERY: {query_text}\n\nRESEARCH FINDINGS:\n" + "\n---\n".join(flat_chunks)
156
+
157
+ formatted_nodes = ""
158
+ disabled_nodes = []
159
+
160
+ # Handle new graph_context structure (list of dicts) vs old (list of ids)
161
+ if 'nodes' in graph_context and isinstance(graph_context['nodes'], list):
162
+ formatted_nodes = ", ".join([n['id'] for n in graph_context['nodes']])
163
+ disabled_nodes = [n['id'] for n in graph_context['nodes'] if not n.get('enabled', True)]
164
+ else:
165
+ formatted_nodes = ", ".join(graph_context.get("node_ids", []))
166
+
167
+ formatted_edges = "\n".join([
168
+ f" {e['source']}->{e['target']} (current weight: {e.get('weight', 0.5)})"
169
+ for e in graph_context.get("edges", [])
170
+ ])
171
+
172
+ disabled_section = ""
173
+ if disabled_nodes:
174
+ disabled_section = f"""
175
+ DISABLED SECTORS (User has manually disconnected these):
176
+ {', '.join(disabled_nodes)}
177
+
178
+ IMPORTANT: The above sectors are DISCONNECTED.
179
+ - Do NOT try to modify edges coming FROM these sectors, as they have no effect.
180
+ - You should acknowledge that they are disabled in your reasoning.
181
+ - Focus policies on the remaining ACTIVE sectors to achieve the goal."""
182
+
183
+ # Determine policy direction
184
+ if increase_emissions:
185
+ task_description = "INCREASE emissions"
186
+ mutation_type = "increase_edge_weight"
187
+ mechanics_section = """TO INCREASE EMISSIONS:
188
+ - MUST increase the edge weight to a LARGER number
189
+ - Example: 0.7 → 0.9 (increases flow by 28%)
190
+ - Example: 0.5 → 0.75 (increases flow by 50%)
191
+ - Example: 0.4 → 0.7 (increases flow by 75%)
192
+
193
+ CRITICAL: new_weight MUST BE GREATER THAN current weight. Do not decrease!"""
194
+ correct_examples = """ ✓ Change transport→co2 from 0.7 to 0.9 (increases flow)
195
+ ✓ Change energy→co2 from 0.8 to 0.95 (increases propagation)"""
196
+ estimated_field = "co2_increase_pct"
197
+ else:
198
+ task_description = "REDUCE emissions"
199
+ mutation_type = "reduce_edge_weight"
200
+ mechanics_section = """TO REDUCE EMISSIONS:
201
+ - MUST decrease the edge weight to a SMALLER number
202
+ - Example: 0.7 → 0.35 (50% reduction in flow)
203
+ - Example: 0.5 → 0.25 (50% reduction in flow)
204
+ - Example: 0.8 → 0.4 (50% reduction in flow)
205
+
206
+ CRITICAL: new_weight MUST BE LESS THAN current weight. Do not increase!"""
207
+ correct_examples = """ ✓ Change transport→co2 from 0.7 to 0.35 (cuts in half)
208
+ ✓ Change transport→co2 from 0.7 to 0.49 (30% reduction)
209
+ ✓ Change energy→co2 from 0.8 to 0.48 (40% reduction)"""
210
+ estimated_field = "co2_reduction_pct"
211
+
212
+ prompt = f"""You are a climate policy expert. Your task is to design policies that {task_description}.
213
+
214
+ {research_section}
215
+ {disabled_section}
216
+
217
+ CURRENT SYSTEM EDGES (with current weights):
218
+ {formatted_edges}
219
+
220
+ Available nodes: {formatted_nodes}
221
+
222
+ EMISSION MECHANICS - READ CAREFULLY:
223
+ The system propagates emissions through edges. Each edge has a weight (0.0 to 1.0):
224
+ - target_value = source_value × weight
225
+ - A weight of 0.7 means 70% of the source value flows to the target
226
+ - A weight of 0.3 means 30% of the source value flows to the target
227
+
228
+ {mechanics_section}
229
+
230
+ CRITICAL INSTRUCTION:
231
+ 1. You MUST follow the TASK direction ({task_description}).
232
+ 2. USE ONLY THE EDGES LISTED ABOVE. Do not hallucinate connections.
233
+ 3. If a node (e.g., 'transport') is NOT in the "Available nodes" list, YOU CANNOT CREATE A POLICY FOR IT.
234
+ 4. If a node is listed in DISABLED SECTORS, do not attempt to change its edges (it is already off).
235
+ 5. If the user asks to INCREASE emissions, you MUST generate a policy that INCREASES them.
236
+ 6. Ignore research advice if it contradicts the goal to {task_description}.
237
+ 7. Use the research only for context on *what* to modify, but reverse the action if needed to match the goal.
238
+
239
+ WRONG EXAMPLES (DO NOT DO THIS):
240
+ ✗ Change 0.7 to 0.75 (wrong direction)
241
+ ✗ Change 0.5 to 0.8 (wrong direction)
242
+ ✗ Change 0.6 to 0.7 (wrong direction)
243
+
244
+ CORRECT EXAMPLES:
245
+ {correct_examples}
246
+
247
+ TASK: Create ONE policy to {task_description} by addressing: "{flat_chunks[0][:100] if flat_chunks else 'emissions policy'}..."
248
+ If the research suggests "Promoting EV", and your task is to INCREASE emissions, your policy should be "Tax EVs / Promote Gas Cars".
249
+
250
+ Return ONLY valid JSON:
251
+ {{
252
+ "policy_id": "policy-slug",
253
+ "name": "Policy Name",
254
+ "description": "Description",
255
+ "mutations": [
256
+ {{
257
+ "type": "{mutation_type}",
258
+ "source": "transport",
259
+ "target": "vehicle-emissions",
260
+ "new_weight": {"0.35" if not increase_emissions else "0.9"},
261
+ "original_weight": 0.7,
262
+ "reason": "Research shows..."
263
+ }}
264
+ ],
265
+ "estimated_impacts": {{
266
+ "{estimated_field}": {"15.0" if not increase_emissions else "12.0"},
267
+ "aqi_improvement_pct": {"18.0" if not increase_emissions else "-15.0"},
268
+ "confidence": 0.8
269
+ }},
270
+ "trade_offs": [],
271
+ "source_research": {{
272
+ "paper_ids": [],
273
+ "key_quotes": [],
274
+ "confidence": 0.85
275
+ }}
276
+ }}"""
277
+
278
+ # Call LLM
279
+ response = self.llm.invoke(prompt)
280
+ response_text = response.content
281
+
282
+ # Extract JSON (handle markdown code blocks)
283
+ json_match = re.search(r'\{[\s\S]*\}', response_text)
284
+ if not json_match:
285
+ raise ValueError(f"No JSON found in LLM response: {response_text}")
286
+
287
+ json_str = json_match.group()
288
+ policy_dict = json.loads(json_str)
289
+
290
+ # Clean up trade_offs - LLM sometimes returns strings instead of objects
291
+ if 'trade_offs' in policy_dict:
292
+ cleaned_trade_offs = []
293
+ for item in policy_dict['trade_offs']:
294
+ if isinstance(item, str):
295
+ # Convert string to proper TradeOff object
296
+ cleaned_trade_offs.append({
297
+ 'sector': 'general',
298
+ 'impact': 'neutral',
299
+ 'magnitude': 'mild',
300
+ 'description': item
301
+ })
302
+ elif isinstance(item, dict):
303
+ cleaned_trade_offs.append(item)
304
+ policy_dict['trade_offs'] = cleaned_trade_offs
305
+
306
+ # Validate against schema
307
+ policy = Policy(**policy_dict)
308
+
309
+ # Log the mutations for debugging
310
+ print(f"\n[Policy Generated]")
311
+ print(f"Policy: {policy.name}")
312
+ print(f"Description: {policy.description}")
313
+ print(f"Mutations: {len(policy.mutations)}")
314
+ for i, mut in enumerate(policy.mutations):
315
+ if mut.type in ["reduce_edge_weight", "increase_edge_weight"]:
316
+ print(f" {i+1}. {mut.type}: {mut.source} -> {mut.target}")
317
+ print(f" New weight: {mut.new_weight} (reason: {mut.reason})")
318
+ print(f"Estimated CO₂ reduction: {policy.estimated_impacts.co2_reduction_pct}%")
319
+ print(f"Estimated AQI improvement: {policy.estimated_impacts.aqi_improvement_pct}%\n")
320
+
321
+ # Validate mutations reference real nodes/edges
322
+ self._validate_mutations(policy, graph_context)
323
+
324
+ return policy
325
+
326
+ def _validate_mutations(self, policy: Policy, graph_context: Dict) -> None:
327
+ """Ensure mutations reference real nodes/edges."""
328
+ # Handle both old (node_ids list) and new (nodes dict list) formats
329
+ if 'nodes' in graph_context and isinstance(graph_context['nodes'], list):
330
+ node_ids = set(n['id'] for n in graph_context['nodes'])
331
+ else:
332
+ node_ids = set(graph_context.get("node_ids", []))
333
+
334
+ edge_pairs = set((e['source'], e['target']) for e in graph_context.get("edges", []))
335
+
336
+ for mutation in policy.mutations:
337
+ if mutation.type == "disable_node":
338
+ if mutation.node_id not in node_ids:
339
+ raise ValueError(f"Unknown node: {mutation.node_id}")
340
+
341
+ elif mutation.type in ["reduce_edge_weight", "increase_edge_weight"]:
342
+ if (mutation.source, mutation.target) not in edge_pairs:
343
+ raise ValueError(f"Unknown edge: {mutation.source} -> {mutation.target}")
344
+
345
+ if mutation.new_weight is None or not (0.0 <= mutation.new_weight <= 1.0):
346
+ raise ValueError(f"Invalid weight: {mutation.new_weight}")
347
+
348
+
349
+ # ============================================================================
350
+ # HELPER FUNCTIONS
351
+ # ============================================================================
352
+
353
+ def get_graph_context_from_file(filepath: str) -> Dict[str, Any]:
354
+ """
355
+ Load graph context (nodes, edges) from snapshot file.
356
+ Used for validation during policy extraction.
357
+ """
358
+ try:
359
+ with open(filepath, 'r') as f:
360
+ data = json.load(f)
361
+
362
+ # Extract node IDs
363
+ node_ids = [n['id'] for n in data.get('nodes', [])]
364
+
365
+ # Extract edges with weights
366
+ edges = [
367
+ {
368
+ 'source': e['source'],
369
+ 'target': e['target'],
370
+ 'weight': e.get('data', {}).get('weight', 0.5)
371
+ }
372
+ for e in data.get('edges', [])
373
+ ]
374
+
375
+ return {
376
+ 'node_ids': node_ids,
377
+ 'edges': edges,
378
+ 'full_graph': data
379
+ }
380
+ except Exception as e:
381
+ print(f"Could not load graph context: {e}")
382
+ return {
383
+ 'node_ids': [
384
+ 'industries', 'transport', 'energy', 'infrastructure',
385
+ 'moves-goods', 'uses-power', 'fuels-transport', 'co2', 'aqi'
386
+ ],
387
+ 'edges': [
388
+ {'source': 'industries', 'target': 'transport', 'weight': 0.6},
389
+ {'source': 'transport', 'target': 'vehicle-emissions', 'weight': 0.7},
390
+ {'source': 'energy', 'target': 'co2', 'weight': 0.8},
391
+ {'source': 'co2', 'target': 'aqi', 'weight': 0.9}
392
+ ]
393
+ }
requirements.txt ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core dependencies
2
+ langchain
3
+ langchain-community
4
+ langchain-huggingface
5
+ langchain-openai
6
+ langchain-groq
7
+ pydantic
8
+
9
+ # LLM & Embeddings
10
+ openai
11
+ sentence-transformers
12
+ huggingface-hub
13
+
14
+ # Vector DB
15
+ faiss-cpu
16
+
17
+ # Web framework
18
+ flask==3.0.0
19
+ flask-cors==4.0.0
20
+
21
+ # PDF processing
22
+ pypdf==4.1.0
23
+
24
+ # Utilities
25
+ python-dotenv==1.0.0
26
+ openpyxl==3.1.5
27
+ pandas
28
+ scikit-learn
29
+
30
+ gunicorn
31
+ xgboost
32
+ folium
33
+ selenium
34
+ webdriver-manager
static/aqi_map.png ADDED

Git LFS Details

  • SHA256: 687c30365da87046c8524385e8098a2db608a45d42a368e9f4788ba97d4b601e
  • Pointer size: 131 Bytes
  • Size of remote file: 434 kB