PRC142004 commited on
Commit
b70d70b
·
verified ·
1 Parent(s): 39a4b2f

Update templates/results.html

Browse files
Files changed (1) hide show
  1. templates/results.html +677 -742
templates/results.html CHANGED
@@ -1,743 +1,678 @@
1
- <!DOCTYPE html>
2
- <html lang="{{ language_code }}">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Pest Prediction Intelligence</title>
8
- <!-- Fonts -->
9
- <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&display=swap"
10
- rel="stylesheet">
11
- <!-- Bootstrap 5 -->
12
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
13
- <!-- Icons -->
14
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
15
-
16
- <style>
17
- /* --- PROFESSIONAL UI SYSTEM --- */
18
- :root {
19
- --primary: #166534;
20
- --surface: #ffffff;
21
- --surface-subtle: #f8fafc;
22
- --border-light: #e2e8f0;
23
- --text-main: #1e293b;
24
- --text-muted: #64748b;
25
- --radius-lg: 24px;
26
- --radius-md: 16px;
27
- --shadow-soft: 0 20px 40px -10px rgba(0, 0, 0, 0.1);
28
- --shadow-crisp: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
29
- }
30
-
31
- body {
32
- font-family: 'Outfit', sans-serif;
33
- background-color:#d5fae5;
34
- color: var(--text-main);
35
- padding-bottom: 5rem;
36
- overflow-x: hidden;
37
- }
38
-
39
- /* Hero */
40
- .hero-banner {
41
- background: linear-gradient(135deg, #166534 0%, #14532d 100%);
42
- color: white;
43
- padding: 2rem 1rem 2rem;
44
- position: relative;
45
- margin-bottom: 2rem;
46
- }
47
-
48
- .report-meta-badge {
49
- background: rgba(255, 255, 255, 0.15);
50
- backdrop-filter: blur(5px);
51
- padding: 0.5rem 1rem;
52
- border-radius: 50px;
53
- font-size: 0.9rem;
54
- display: inline-flex;
55
- align-items: center;
56
- gap: 0.5rem;
57
- border: 1px solid rgba(255, 255, 255, 0.1);
58
- }
59
-
60
- /* Container */
61
- .content-wrapper {
62
- max-width: 1100px;
63
- margin: 0 auto;
64
- position: relative;
65
- z-index: 20;
66
- padding: 0 1.5rem;
67
- }
68
-
69
- /* 1. AUDIO STRIP */
70
- .audio-sketch-box {
71
- background: var(--surface);
72
- border-radius: var(--radius-md);
73
- padding: 1rem 1.5rem;
74
- margin-bottom: 2rem;
75
- box-shadow: var(--shadow-crisp);
76
- border: 1px solid var(--border-light);
77
- display: flex;
78
- align-items: center;
79
- justify-content: space-between;
80
- border: 3px dashed #035225;
81
- }
82
-
83
- .audio-icon-circle {
84
- width: 48px;
85
- height: 48px;
86
- background: #f0fdf4;
87
- color: var(--primary);
88
- border-radius: 50%;
89
- display: grid;
90
- place-items: center;
91
- font-size: 1.25rem;
92
- border: 3px solid #035225;
93
- }
94
-
95
- /* 2. CALENDAR FLIPBOOK */
96
- .flipbook-wrapper {
97
- position: relative;
98
- max-width: 1000px;
99
- margin: 0 auto;
100
- perspective: 2000px;
101
- display: flex;
102
- align-items: center;
103
- gap: 1.5rem;
104
- }
105
-
106
- .flipbook-stage {
107
- width: 100%;
108
- height: 640px;
109
- /* Fixed Optimal Height */
110
- position: relative;
111
- border: 3px dashed #035225;
112
- border-radius: 16px;
113
- }
114
-
115
- .flip-card {
116
- position: absolute;
117
- top: 0;
118
- left: 0;
119
- width: 100%;
120
- height: 100%;
121
- background: var(--surface);
122
- border-radius: var(--radius-lg);
123
- border: 1px solid var(--border-light);
124
- box-shadow: var(--shadow-soft);
125
- display: flex;
126
- flex-direction: column;
127
- overflow: hidden;
128
-
129
- /* Transitions */
130
- opacity: 0;
131
- transform: translateX(50px) scale(0.95);
132
- pointer-events: none;
133
- transition: all 0.5s cubic-bezier(0.2, 0.8, 0.2, 1);
134
- }
135
-
136
- .flip-card.active {
137
- opacity: 1;
138
- transform: translateX(0) scale(1);
139
- pointer-events: all;
140
- z-index: 10;
141
- }
142
-
143
- /* HEADER: Stats Enhanced */
144
- .cal-header {
145
- background: white;
146
- border-bottom: 1px solid var(--border-light);
147
- padding: 1.2rem 2rem;
148
- height: 100px;
149
- display: flex;
150
- align-items: center;
151
- justify-content: space-between;
152
- }
153
-
154
- .month-title {
155
- font-size: 2.2rem;
156
- font-weight: 800;
157
- color: #0f172a;
158
- margin: 0;
159
- letter-spacing: -1px;
160
- }
161
-
162
- .stats-row {
163
- display: flex;
164
- gap: 0.75rem;
165
- }
166
-
167
- .stat-badge {
168
- display: flex;
169
- flex-direction: column;
170
- align-items: flex-start;
171
- background: #f8fafc;
172
- border: 1px solid #e2e8f0;
173
- padding: 0.4rem 0.8rem;
174
- border-radius: 8px;
175
- min-width: 80px;
176
- border: 1px dashed #035225;
177
- }
178
-
179
- .stat-label {
180
- font-size: 0.65rem;
181
- text-transform: uppercase;
182
- color: #64748b;
183
- font-weight: 700;
184
- }
185
-
186
- .stat-val {
187
- font-size: 0.95rem;
188
- font-weight: 700;
189
- color: #334155;
190
- }
191
-
192
- .stat-val i {
193
- margin-right: 4px;
194
- }
195
-
196
- /* BODY SPLIT */
197
- .cal-body {
198
- flex-grow: 1;
199
- display: flex;
200
- overflow: hidden;
201
- }
202
-
203
- /* LEFT: RISKS */
204
- .cal-left {
205
- width: 35%;
206
- background: #fdfdfd;
207
- border-right: 1px solid var(--border-light);
208
- padding: 1.5rem;
209
- display: flex;
210
- flex-direction: column;
211
-
212
- }
213
-
214
- .section-head {
215
- font-size: 0.7rem;
216
- font-weight: 800;
217
- letter-spacing: 1px;
218
- color: #94a3b8;
219
- text-transform: uppercase;
220
- margin-bottom: 1rem;
221
- display: flex;
222
- align-items: center;
223
- gap: 0.5rem;
224
-
225
- }
226
-
227
- .threat-list {
228
- overflow-y: auto;
229
- flex-grow: 1;
230
- padding-right: 0.5rem;
231
- }
232
-
233
- .threat-item {
234
- background: white;
235
- border: 1px solid var(--border-light);
236
- padding: 0.9rem;
237
- border-radius: 12px;
238
- margin-bottom: 0.75rem;
239
- display: flex;
240
- align-items: center;
241
- justify-content: space-between;
242
- transition: 0.2s;
243
- border: 1px dashed #035225;
244
- }
245
-
246
- .threat-item:hover {
247
- border-color: #94a3b8;
248
- transform: translateX(2px);
249
- }
250
-
251
- .risk-dot {
252
- width: 8px;
253
- height: 8px;
254
- border-radius: 50%;
255
- display: block;
256
- }
257
-
258
- .risk-dot.high {
259
- background: #ef4444;
260
- box-shadow: 0 0 0 3px #fef2f2;
261
- }
262
-
263
- .risk-dot.medium {
264
- background: #f97316;
265
- }
266
-
267
- /* RIGHT: STRATEGY */
268
- .cal-right {
269
- width: 65%;
270
- background: white;
271
- padding: 2rem;
272
- display: flex;
273
- flex-direction: column;
274
- }
275
-
276
- /* 1. Overview Grid */
277
- .overview-panel {
278
- display: grid;
279
- grid-template-columns: 1fr 1fr;
280
- gap: 1rem;
281
- margin-bottom: 2rem;
282
- }
283
-
284
- .insight-card {
285
- background: #f8fafc;
286
- border: 1px solid #e2e8f0;
287
- padding: 1rem;
288
- border-radius: 12px;
289
- display: flex;
290
- align-items: center;
291
- gap: 1rem;
292
- border: 1px dashed #035225;
293
- }
294
-
295
- .ic-icon {
296
- font-size: 1.5rem;
297
- }
298
-
299
- .ic-content {
300
- display: flex;
301
- flex-direction: column;
302
- }
303
-
304
- .ic-label {
305
- font-size: 0.7rem;
306
- font-weight: 700;
307
- color: #64748b;
308
- text-transform: uppercase;
309
- }
310
-
311
- .ic-value {
312
- font-size: 0.95rem;
313
- font-weight: 800;
314
- color: #0f172a;
315
- }
316
-
317
- /* Critical Logic */
318
- .insight-card.critical {
319
- background: #fff1f2;
320
- border-color: #ffe4e6;
321
- border: 1px dashed #035225;
322
- }
323
-
324
- .insight-card.critical .ic-icon {
325
- color: #e11d48;
326
- }
327
-
328
- .insight-card.critical .ic-value {
329
- color: #881337;
330
- }
331
-
332
- .insight-card.advisory {
333
- background: #eff6ff;
334
- border-color: #dbeafe;
335
- border: 1px dashed #035225;
336
- }
337
-
338
- .insight-card.advisory .ic-icon {
339
- color: #2563eb;
340
- }
341
-
342
- /* 2. Tasks */
343
- .task-scroll {
344
- flex-grow: 1;
345
- overflow-y: auto;
346
- padding-right: 0.5rem;
347
- border-top: 1px dashed var(--border-light);
348
- padding-top: 1.5rem;
349
- border: 1px solid #c0c4f9;
350
- }
351
-
352
- .task-row {
353
- display: flex;
354
- gap: 1rem;
355
- margin-bottom: 1.25rem;
356
- }
357
-
358
- .t-check {
359
- width: 20px;
360
- height: 20px;
361
- border: 2px solid #cbd5e1;
362
- border-radius: 6px;
363
- flex-shrink: 0;
364
- margin-top: 2px;
365
- }
366
-
367
- .task-row.urgent .t-check {
368
- border-color: #ef4444;
369
- background: #fee2e2;
370
- }
371
-
372
- .t-text {
373
- font-size: 0.95rem;
374
- color: #334155;
375
- line-height: 1.5;
376
- margin-bottom: 0.25rem;
377
- }
378
-
379
- .t-tag {
380
- background: #f1f5f9;
381
- color: #64748b;
382
- padding: 2px 8px;
383
- border-radius: 4px;
384
- font-size: 0.7rem;
385
- font-weight: 700;
386
- }
387
-
388
- /* Footer */
389
- .stage-footer {
390
- margin-top: 1rem;
391
- padding-top: 1rem;
392
- border-top: 1px solid var(--border-light);
393
- display: flex;
394
- gap: 0.5rem;
395
- flex-wrap: wrap;
396
- align-items: center;
397
- }
398
-
399
- .stage-pill {
400
- font-size: 0.75rem;
401
- font-weight: 700;
402
- color: #b45309;
403
- background: #fffbeb;
404
- border: 1px solid #fcd34d;
405
- padding: 0.25rem 0.75rem;
406
- border-radius: 20px;
407
- }
408
-
409
- /* Nav Buttons */
410
- .nav-btn {
411
- width: 50px;
412
- height: 50px;
413
- border-radius: 50%;
414
- background: #1e293b;
415
- color: white;
416
- border: none;
417
- display: grid;
418
- place-items: center;
419
- font-size: 1.25rem;
420
- cursor: pointer;
421
- transition: 0.2s;
422
- box-shadow: 0 8px 20px -5px rgba(0, 0, 0, 0.3);
423
- }
424
-
425
- .nav-btn:hover {
426
- transform: scale(1.1);
427
- background: #0f172a;
428
- }
429
-
430
- @media (max-width: 900px) {
431
- .flipbook-stage {
432
- height: auto;
433
- }
434
-
435
- .flip-card {
436
- position: relative;
437
- opacity: 1;
438
- transform: none;
439
- display: block;
440
- margin-bottom: 2rem;
441
- height: auto;
442
- }
443
-
444
- .cal-body {
445
- flex-direction: column;
446
- }
447
-
448
- .cal-left {
449
- width: 100%;
450
- height: 300px;
451
- border-right: none;
452
- border-bottom: 1px solid var(--border-light);
453
- }
454
-
455
- .cal-right {
456
- width: 100%;
457
- border: none;
458
- }
459
-
460
- .overview-panel {
461
- grid-template-columns: 1fr;
462
- }
463
- }
464
- </style>
465
- </head>
466
-
467
- <body>
468
-
469
- <div class="hero-banner">
470
- <div class="container text-center">
471
- <h1 class="display-4 fw-bold mb-3">{{ report_data.report_title }}</h1>
472
- <div class="d-flex justify-content-center gap-3">
473
- <span class="report-meta-badge"><i class="bi bi-geo-alt-fill"></i> {{ location.derived_location
474
- }}</span>
475
- <span class="report-meta-badge"><i class="bi bi-calendar-check"></i> {{ current_date }}</span>
476
- </div>
477
- </div>
478
- </div>
479
-
480
- <div class="content-wrapper">
481
-
482
- <!-- AUDIO BOX (Asynchronous Loading) -->
483
- <div class="audio-sketch-box" id="audioContainer">
484
- <div class="d-flex align-items-center gap-3">
485
- <div class="audio-icon-circle" id="audioIcon">
486
- <i class="bi bi-mic-fill"></i>
487
- </div>
488
- <div>
489
- <h5 class="fw-bold mb-0 text-dark">Field Briefing</h5>
490
- <small class="text-muted" id="audioStatus">Preparing summary...</small>
491
- </div>
492
- </div>
493
- <div id="audioPlayerWrapper" style="display: none;">
494
- <audio id="audioPlayer" controls style="height: 36px; width: 250px; border-radius: 20px;"></audio>
495
- </div>
496
- <div id="audioLoader" class="spinner-border spinner-border-sm text-success" role="status">
497
- <span class="visually-hidden">Loading...</span>
498
- </div>
499
- </div>
500
-
501
- <!-- CALENDAR -->
502
- <div class="flipbook-wrapper">
503
- <button class="nav-btn" onclick="changeMonth(-1)"><i class="bi bi-chevron-left"></i></button>
504
-
505
- <div id="flipbookStage" class="flipbook-stage">
506
- {% if weather_profile %}
507
- {% for w in weather_profile %}
508
- <div class="flip-card {{ 'active' if loop.index0 == 0 else '' }}">
509
-
510
- <!-- Header with ENHANCED STATS -->
511
- <div class="cal-header">
512
- <h2 class="month-title">{{ w.month }}</h2>
513
- <div class="stats-row">
514
- <div class="stat-badge">
515
- <span class="stat-label">Avg Temp</span>
516
- <span class="stat-val"><i class="bi bi-thermometer-half text-danger"></i> {{ w.avg_temp
517
- }}°</span>
518
- </div>
519
- <div class="stat-badge">
520
- <span class="stat-label">Rainfall</span>
521
- <span class="stat-val"><i class="bi bi-droplet-fill text-primary"></i> {{ w.rainfall
522
- }}mm</span>
523
- </div>
524
-
525
- <!-- NEW: Humidity -->
526
- <div class="stat-badge">
527
- <span class="stat-label">Humidity</span>
528
- <span class="stat-val"><i class="bi bi-moisture text-info"></i> {{ w.humidity }}%</span>
529
- </div>
530
-
531
- <!-- NEW: Rainy Days Calculation -->
532
- {% set rainy_days = w.days | selectattr('rain', '>', 0) | list | length %}
533
- <div class="stat-badge">
534
- <span class="stat-label">Rainy Days</span>
535
- <span class="stat-val"><i class="bi bi-cloud-drizzle-fill text-dark"></i> {{ rainy_days
536
- }}</span>
537
- </div>
538
- </div>
539
- </div>
540
-
541
- <div class="cal-body">
542
- <!-- Left: Threats -->
543
- <div class="cal-left">
544
- <div class="section-head"><i class="bi bi-bug"></i> Identified Risks</div>
545
- <div class="threat-list">
546
- {% set ns = namespace(found=false) %}
547
- {% for pest in report_data.pest_prediction_table %}
548
- {% set current_month_name = w.month.split(' ')[0] %}
549
- {% if current_month_name in pest.outbreak_months or "All" in pest.outbreak_months %}
550
- {% set ns.found = true %}
551
- <div class="threat-item">
552
- <span class="fw-bold text-dark">{{ pest.pest_name }}</span>
553
- <span class="risk-dot {{ pest.severity|lower }}"></span>
554
- </div>
555
- {% endif %}
556
- {% endfor %}
557
- {% if not ns.found %}
558
- <div class="text-center py-5 text-muted opacity-50">
559
- <i class="bi bi-shield-check fs-1"></i>
560
- <p class="small mt-2">Zero Threats</p>
561
- </div>
562
- {% endif %}
563
- </div>
564
- </div>
565
-
566
- <!-- Right: Strategy -->
567
- <div class="cal-right">
568
- <!-- Overview -->
569
- <div class="overview-panel">
570
- {% set ns_meta = namespace(has_high=false, stages=[]) %}
571
- {% for pest in report_data.pest_prediction_table %}
572
- {% set current_month_name = w.month.split(' ')[0] %}
573
- {% if current_month_name in pest.outbreak_months or "All" in pest.outbreak_months %}
574
- {% if 'High' in pest.severity %} {% set ns_meta.has_high = true %} {% endif %}
575
- {% if pest.impacting_stage and pest.impacting_stage not in ns_meta.stages %}
576
- {% set _ = ns_meta.stages.append(pest.impacting_stage) %}
577
- {% endif %}
578
- {% endif %}
579
- {% endfor %}
580
-
581
- <!-- Status Card -->
582
- <div class="insight-card {{ 'critical' if ns_meta.has_high else 'stable' }}">
583
- <div class="ic-icon"><i
584
- class="bi {{ 'bi-exclamation-octagon-fill' if ns_meta.has_high else 'bi-check-circle-fill text-success' }}"></i>
585
- </div>
586
- <div class="ic-content">
587
- <span class="ic-label">Threat Level</span>
588
- <span class="ic-value">{{ 'CRITICAL' if ns_meta.has_high else 'MONITORING'
589
- }}</span>
590
- </div>
591
- </div>
592
-
593
- <!-- Weather Tip Card -->
594
- <div class="insight-card advisory">
595
- <div class="ic-icon"><i class="bi bi-lightbulb-fill"></i></div>
596
- <div class="ic-content">
597
- <span class="ic-label">Advisory</span>
598
- <span class="ic-value" style="font-size: 0.85rem; font-weight: 600;">
599
- {% if w.rainfall > 150 %} Ensure Drainage
600
- {% elif w.rainfall < 10 %} Increase Irrigation {% elif w.avg_temp> 35 %}
601
- Heat Protection
602
- {% else %} Standard Monitoring {% endif %}
603
- </span>
604
- </div>
605
- </div>
606
- </div>
607
-
608
- <!-- Tasks -->
609
- <div class="section-head text-dark"><i class="bi bi-list-check"></i> Action Checklist</div>
610
- <div class="task-scroll">
611
- {% set ns_act = namespace(found=false) %}
612
- {% for pest in report_data.pest_prediction_table %}
613
- {% set current_month_name = w.month.split(' ')[0] %}
614
- {% if current_month_name in pest.outbreak_months or "All" in pest.outbreak_months %}
615
- {% set ns_act.found = true %}
616
- <div class="task-row {{ 'urgent' if 'High' in pest.severity else '' }}">
617
- <div class="t-check"></div>
618
- <div>
619
- <div class="t-text">{{ pest.precautionary_measures | striptags }}</div>
620
- <span class="t-tag">Preventing: {{ pest.pest_name }}</span>
621
- </div>
622
- </div>
623
- {% endif %}
624
- {% endfor %}
625
-
626
- {% if not ns_act.found %}
627
- <div class="task-row">
628
- <div class="t-check bg-success border-success"></div>
629
- <div class="t-text text-success">All clear. Proceed with standard crop husbandry.
630
- </div>
631
- </div>
632
- {% endif %}
633
- </div>
634
-
635
- <!-- Footer -->
636
- {% if ns_meta.stages %}
637
- <div class="stage-footer">
638
- <span class="small fw-bold text-muted">VULNERABLE STAGES:</span>
639
- {% for stage in ns_meta.stages %}
640
- <span class="stage-pill">{{ stage }}</span>
641
- {% endfor %}
642
- </div>
643
- {% endif %}
644
- </div>
645
- </div>
646
- </div>
647
- {% endfor %}
648
- {% else %}
649
- <div class="alert alert-danger">Loading Data...</div>
650
- {% endif %}
651
- </div>
652
-
653
- <button class="nav-btn" onclick="changeMonth(1)"><i class="bi bi-chevron-right"></i></button>
654
- </div>
655
-
656
- <div class="text-center mt-4 mb-5">
657
- <span id="pIndicator" class="badge rounded-pill bg-dark py-2 px-4 shadow">Month 1</span>
658
- </div>
659
-
660
- <div class="text-center pb-5">
661
- <a href="/" class="btn btn-outline-dark rounded-pill px-4"><i class="bi bi-arrow-repeat me-2"></i>Analyze
662
- Another Field</a>
663
- </div>
664
-
665
- </div>
666
-
667
- <!-- JS -->
668
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
669
- <script>
670
- let curr = 0;
671
- const cards = document.querySelectorAll('.flip-card');
672
- const ind = document.getElementById('pIndicator');
673
- const len = cards.length;
674
-
675
- function render() {
676
- cards.forEach((c, i) => {
677
- c.classList.remove('active');
678
- if (i === curr) c.classList.add('active');
679
- });
680
- if (len > 0) ind.innerText = `Month ${curr + 1} of ${len}`;
681
- }
682
- function changeMonth(d) {
683
- let n = curr + d;
684
- if (n >= 0 && n < len) { curr = n; render(); }
685
- }
686
- render();
687
-
688
- // --- ASYNC AUDIO GENERATION ---
689
- async function loadAudio() {
690
- const reportData = {{ report_data | tojson }};
691
- const location = {{ location | tojson }};
692
- const langCode = "{{ language_code }}";
693
-
694
- // Construct summary
695
- let summary = `Pest Outbreak Report for ${location.derived_location || 'your location'}. `;
696
- summary += (reportData.agricultural_inputs_analysis || '').substring(0, 200).replace(/[#*`-]/g, '') + "... ";
697
-
698
- if (reportData.pest_prediction_table && reportData.pest_prediction_table.length > 0) {
699
- const pests = reportData.pest_prediction_table.map(p => p.pest_name).join(', ');
700
- summary += `Predicted pests include: ${pests}. `;
701
- }
702
- summary += (reportData.predicted_pest_damage_info || '').substring(0, 200).replace(/[#*`-]/g, '');
703
-
704
- try {
705
- const response = await fetch('/generate_audio', {
706
- method: 'POST',
707
- headers: { 'Content-Type': 'application/json' },
708
- body: JSON.stringify({
709
- summary: summary,
710
- language_code: langCode,
711
- lat: location.latitude,
712
- lon: location.longitude
713
- })
714
- });
715
-
716
- const data = await response.json();
717
- if (data.audio_url) {
718
- const player = document.getElementById('audioPlayer');
719
- player.src = data.audio_url;
720
- document.getElementById('audioPlayerWrapper').style.display = 'block';
721
- document.getElementById('audioLoader').style.display = 'none';
722
- document.getElementById('audioStatus').innerText = `Executive Summary • ${langCode.toUpperCase()}`;
723
- document.getElementById('audioIcon').classList.add('bi-play-fill');
724
- document.getElementById('audioIcon').classList.remove('bi-mic-fill');
725
- } else {
726
- document.getElementById('audioStatus').innerText = "Audio unavailable";
727
- document.getElementById('audioLoader').style.display = 'none';
728
- }
729
- } catch (err) {
730
- console.error("Audio Load Error:", err);
731
- document.getElementById('audioStatus').innerText = "Audio failed to load";
732
- document.getElementById('audioLoader').style.display = 'none';
733
- }
734
- }
735
-
736
- // Delay audio load slightly to prioritize page render
737
- window.addEventListener('load', () => {
738
- setTimeout(loadAudio, 500);
739
- });
740
- </script>
741
- </body>
742
-
743
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="{{ language_code }}">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Pest Prediction Intelligence</title>
8
+ <!-- Fonts -->
9
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&display=swap"
10
+ rel="stylesheet">
11
+ <!-- Bootstrap 5 -->
12
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
13
+ <!-- Icons -->
14
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
15
+
16
+ <style>
17
+ /* --- PROFESSIONAL UI SYSTEM --- */
18
+ :root {
19
+ --primary: #166534;
20
+ --surface: #ffffff;
21
+ --surface-subtle: #f8fafc;
22
+ --border-light: #e2e8f0;
23
+ --text-main: #1e293b;
24
+ --text-muted: #64748b;
25
+ --radius-lg: 24px;
26
+ --radius-md: 16px;
27
+ --shadow-soft: 0 20px 40px -10px rgba(0, 0, 0, 0.1);
28
+ --shadow-crisp: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
29
+ }
30
+
31
+ body {
32
+ font-family: 'Outfit', sans-serif;
33
+ background-color: var(--surface-subtle);
34
+ color: var(--text-main);
35
+ padding-bottom: 5rem;
36
+ overflow-x: hidden;
37
+ }
38
+
39
+ /* Hero */
40
+ .hero-banner {
41
+ background: linear-gradient(135deg, #166534 0%, #14532d 100%);
42
+ color: white;
43
+ padding: 3rem 1rem 6rem;
44
+ position: relative;
45
+ margin-bottom: -4rem;
46
+ }
47
+
48
+ .report-meta-badge {
49
+ background: rgba(255, 255, 255, 0.15);
50
+ backdrop-filter: blur(5px);
51
+ padding: 0.5rem 1rem;
52
+ border-radius: 50px;
53
+ font-size: 0.9rem;
54
+ display: inline-flex;
55
+ align-items: center;
56
+ gap: 0.5rem;
57
+ border: 1px solid rgba(255, 255, 255, 0.1);
58
+ }
59
+
60
+ /* Container */
61
+ .content-wrapper {
62
+ max-width: 1100px;
63
+ margin: 0 auto;
64
+ position: relative;
65
+ z-index: 20;
66
+ padding: 0 1.5rem;
67
+ }
68
+
69
+ /* 1. AUDIO STRIP */
70
+ .audio-sketch-box {
71
+ background: var(--surface);
72
+ border-radius: var(--radius-md);
73
+ padding: 1rem 1.5rem;
74
+ margin-bottom: 2rem;
75
+ box-shadow: var(--shadow-crisp);
76
+ border: 1px solid var(--border-light);
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: space-between;
80
+ }
81
+
82
+ .audio-icon-circle {
83
+ width: 48px;
84
+ height: 48px;
85
+ background: #f0fdf4;
86
+ color: var(--primary);
87
+ border-radius: 50%;
88
+ display: grid;
89
+ place-items: center;
90
+ font-size: 1.25rem;
91
+ }
92
+
93
+ /* 2. CALENDAR FLIPBOOK */
94
+ .flipbook-wrapper {
95
+ position: relative;
96
+ max-width: 1000px;
97
+ margin: 0 auto;
98
+ perspective: 2000px;
99
+ display: flex;
100
+ align-items: center;
101
+ gap: 1.5rem;
102
+ }
103
+
104
+ .flipbook-stage {
105
+ width: 100%;
106
+ height: 640px;
107
+ /* Fixed Optimal Height */
108
+ position: relative;
109
+ }
110
+
111
+ .flip-card {
112
+ position: absolute;
113
+ top: 0;
114
+ left: 0;
115
+ width: 100%;
116
+ height: 100%;
117
+ background: var(--surface);
118
+ border-radius: var(--radius-lg);
119
+ border: 1px solid var(--border-light);
120
+ box-shadow: var(--shadow-soft);
121
+ display: flex;
122
+ flex-direction: column;
123
+ overflow: hidden;
124
+
125
+ /* Transitions */
126
+ opacity: 0;
127
+ transform: translateX(50px) scale(0.95);
128
+ pointer-events: none;
129
+ transition: all 0.5s cubic-bezier(0.2, 0.8, 0.2, 1);
130
+ }
131
+
132
+ .flip-card.active {
133
+ opacity: 1;
134
+ transform: translateX(0) scale(1);
135
+ pointer-events: all;
136
+ z-index: 10;
137
+ }
138
+
139
+ /* HEADER: Stats Enhanced */
140
+ .cal-header {
141
+ background: white;
142
+ border-bottom: 1px solid var(--border-light);
143
+ padding: 1.2rem 2rem;
144
+ height: 100px;
145
+ display: flex;
146
+ align-items: center;
147
+ justify-content: space-between;
148
+ }
149
+
150
+ .month-title {
151
+ font-size: 2.2rem;
152
+ font-weight: 800;
153
+ color: #0f172a;
154
+ margin: 0;
155
+ letter-spacing: -1px;
156
+ }
157
+
158
+ .stats-row {
159
+ display: flex;
160
+ gap: 0.75rem;
161
+ }
162
+
163
+ .stat-badge {
164
+ display: flex;
165
+ flex-direction: column;
166
+ align-items: flex-start;
167
+ background: #f8fafc;
168
+ border: 1px solid #e2e8f0;
169
+ padding: 0.4rem 0.8rem;
170
+ border-radius: 8px;
171
+ min-width: 80px;
172
+ }
173
+
174
+ .stat-label {
175
+ font-size: 0.65rem;
176
+ text-transform: uppercase;
177
+ color: #64748b;
178
+ font-weight: 700;
179
+ }
180
+
181
+ .stat-val {
182
+ font-size: 0.95rem;
183
+ font-weight: 700;
184
+ color: #334155;
185
+ }
186
+
187
+ .stat-val i {
188
+ margin-right: 4px;
189
+ }
190
+
191
+ /* BODY SPLIT */
192
+ .cal-body {
193
+ flex-grow: 1;
194
+ display: flex;
195
+ overflow: hidden;
196
+ }
197
+
198
+ /* LEFT: RISKS */
199
+ .cal-left {
200
+ width: 35%;
201
+ background: #fdfdfd;
202
+ border-right: 1px solid var(--border-light);
203
+ padding: 1.5rem;
204
+ display: flex;
205
+ flex-direction: column;
206
+ }
207
+
208
+ .section-head {
209
+ font-size: 0.7rem;
210
+ font-weight: 800;
211
+ letter-spacing: 1px;
212
+ color: #94a3b8;
213
+ text-transform: uppercase;
214
+ margin-bottom: 1rem;
215
+ display: flex;
216
+ align-items: center;
217
+ gap: 0.5rem;
218
+ }
219
+
220
+ .threat-list {
221
+ overflow-y: auto;
222
+ flex-grow: 1;
223
+ padding-right: 0.5rem;
224
+ }
225
+
226
+ .threat-item {
227
+ background: white;
228
+ border: 1px solid var(--border-light);
229
+ padding: 0.9rem;
230
+ border-radius: 12px;
231
+ margin-bottom: 0.75rem;
232
+ display: flex;
233
+ align-items: center;
234
+ justify-content: space-between;
235
+ transition: 0.2s;
236
+ }
237
+
238
+ .threat-item:hover {
239
+ border-color: #94a3b8;
240
+ transform: translateX(2px);
241
+ }
242
+
243
+ .risk-dot {
244
+ width: 8px;
245
+ height: 8px;
246
+ border-radius: 50%;
247
+ display: block;
248
+ }
249
+
250
+ .risk-dot.high {
251
+ background: #ef4444;
252
+ box-shadow: 0 0 0 3px #fef2f2;
253
+ }
254
+
255
+ .risk-dot.medium {
256
+ background: #f97316;
257
+ }
258
+
259
+ /* RIGHT: STRATEGY */
260
+ .cal-right {
261
+ width: 65%;
262
+ background: white;
263
+ padding: 2rem;
264
+ display: flex;
265
+ flex-direction: column;
266
+ }
267
+
268
+ /* 1. Overview Grid */
269
+ .overview-panel {
270
+ display: grid;
271
+ grid-template-columns: 1fr 1fr;
272
+ gap: 1rem;
273
+ margin-bottom: 2rem;
274
+ }
275
+
276
+ .insight-card {
277
+ background: #f8fafc;
278
+ border: 1px solid #e2e8f0;
279
+ padding: 1rem;
280
+ border-radius: 12px;
281
+ display: flex;
282
+ align-items: center;
283
+ gap: 1rem;
284
+ }
285
+
286
+ .ic-icon {
287
+ font-size: 1.5rem;
288
+ }
289
+
290
+ .ic-content {
291
+ display: flex;
292
+ flex-direction: column;
293
+ }
294
+
295
+ .ic-label {
296
+ font-size: 0.7rem;
297
+ font-weight: 700;
298
+ color: #64748b;
299
+ text-transform: uppercase;
300
+ }
301
+
302
+ .ic-value {
303
+ font-size: 0.95rem;
304
+ font-weight: 800;
305
+ color: #0f172a;
306
+ }
307
+
308
+ /* Critical Logic */
309
+ .insight-card.critical {
310
+ background: #fff1f2;
311
+ border-color: #ffe4e6;
312
+ }
313
+
314
+ .insight-card.critical .ic-icon {
315
+ color: #e11d48;
316
+ }
317
+
318
+ .insight-card.critical .ic-value {
319
+ color: #881337;
320
+ }
321
+
322
+ .insight-card.advisory {
323
+ background: #eff6ff;
324
+ border-color: #dbeafe;
325
+ }
326
+
327
+ .insight-card.advisory .ic-icon {
328
+ color: #2563eb;
329
+ }
330
+
331
+ /* 2. Tasks */
332
+ .task-scroll {
333
+ flex-grow: 1;
334
+ overflow-y: auto;
335
+ padding-right: 0.5rem;
336
+ border-top: 1px dashed var(--border-light);
337
+ padding-top: 1.5rem;
338
+ }
339
+
340
+ .task-row {
341
+ display: flex;
342
+ gap: 1rem;
343
+ margin-bottom: 1.25rem;
344
+ }
345
+
346
+ .t-check {
347
+ width: 20px;
348
+ height: 20px;
349
+ border: 2px solid #cbd5e1;
350
+ border-radius: 6px;
351
+ flex-shrink: 0;
352
+ margin-top: 2px;
353
+ }
354
+
355
+ .task-row.urgent .t-check {
356
+ border-color: #ef4444;
357
+ background: #fee2e2;
358
+ }
359
+
360
+ .t-text {
361
+ font-size: 0.95rem;
362
+ color: #334155;
363
+ line-height: 1.5;
364
+ margin-bottom: 0.25rem;
365
+ }
366
+
367
+ .t-tag {
368
+ background: #f1f5f9;
369
+ color: #64748b;
370
+ padding: 2px 8px;
371
+ border-radius: 4px;
372
+ font-size: 0.7rem;
373
+ font-weight: 700;
374
+ }
375
+
376
+ /* Footer */
377
+ .stage-footer {
378
+ margin-top: 1rem;
379
+ padding-top: 1rem;
380
+ border-top: 1px solid var(--border-light);
381
+ display: flex;
382
+ gap: 0.5rem;
383
+ flex-wrap: wrap;
384
+ align-items: center;
385
+ }
386
+
387
+ .stage-pill {
388
+ font-size: 0.75rem;
389
+ font-weight: 700;
390
+ color: #b45309;
391
+ background: #fffbeb;
392
+ border: 1px solid #fcd34d;
393
+ padding: 0.25rem 0.75rem;
394
+ border-radius: 20px;
395
+ }
396
+
397
+ /* Nav Buttons */
398
+ .nav-btn {
399
+ width: 50px;
400
+ height: 50px;
401
+ border-radius: 50%;
402
+ background: #1e293b;
403
+ color: white;
404
+ border: none;
405
+ display: grid;
406
+ place-items: center;
407
+ font-size: 1.25rem;
408
+ cursor: pointer;
409
+ transition: 0.2s;
410
+ box-shadow: 0 8px 20px -5px rgba(0, 0, 0, 0.3);
411
+ }
412
+
413
+ .nav-btn:hover {
414
+ transform: scale(1.1);
415
+ background: #0f172a;
416
+ }
417
+
418
+ @media (max-width: 900px) {
419
+ .flipbook-stage {
420
+ height: auto;
421
+ }
422
+
423
+ .flip-card {
424
+ position: relative;
425
+ opacity: 1;
426
+ transform: none;
427
+ display: block;
428
+ margin-bottom: 2rem;
429
+ height: auto;
430
+ }
431
+
432
+ .cal-body {
433
+ flex-direction: column;
434
+ }
435
+
436
+ .cal-left {
437
+ width: 100%;
438
+ height: 300px;
439
+ border-right: none;
440
+ border-bottom: 1px solid var(--border-light);
441
+ }
442
+
443
+ .cal-right {
444
+ width: 100%;
445
+ border: none;
446
+ }
447
+
448
+ .overview-panel {
449
+ grid-template-columns: 1fr;
450
+ }
451
+ }
452
+ </style>
453
+ </head>
454
+
455
+ <body>
456
+
457
+ <div class="hero-banner">
458
+ <div class="container text-center">
459
+ <h1 class="display-4 fw-bold mb-3">{{ report_data.report_title }}</h1>
460
+ <div class="d-flex justify-content-center gap-3">
461
+ <span class="report-meta-badge"><i class="bi bi-geo-alt-fill"></i> {{ location.derived_location
462
+ }}</span>
463
+ <span class="report-meta-badge"><i class="bi bi-calendar-check"></i> {{ current_date }}</span>
464
+ </div>
465
+ </div>
466
+ </div>
467
+
468
+ <div class="content-wrapper">
469
+
470
+ <!-- AUDIO BOX -->
471
+ {% if audio_url %}
472
+ <div class="audio-sketch-box">
473
+ <div class="d-flex align-items-center gap-3">
474
+ <div class="audio-icon-circle"><i class="bi bi-mic-fill"></i></div>
475
+ <div>
476
+ <h5 class="fw-bold mb-0 text-dark">Field Briefing</h5>
477
+ <small class="text-muted">Executive Summary • {{ language_code|upper }}</small>
478
+ </div>
479
+ </div>
480
+ <audio controls style="height: 36px; width: 250px; border-radius: 20px;">
481
+ <source src="{{ audio_url }}" type="audio/mpeg">
482
+ </audio>
483
+ </div>
484
+ {% endif %}
485
+
486
+ <!-- CALENDAR -->
487
+ <div class="flipbook-wrapper">
488
+ <button class="nav-btn" onclick="changeMonth(-1)"><i class="bi bi-chevron-left"></i></button>
489
+
490
+ <div id="flipbookStage" class="flipbook-stage">
491
+ {% if weather_profile %}
492
+ {% for w in weather_profile %}
493
+ <div class="flip-card {{ 'active' if loop.index0 == 0 else '' }}">
494
+
495
+ <!-- Header with ENHANCED STATS -->
496
+ <div class="cal-header">
497
+ <h2 class="month-title">{{ w.month }}</h2>
498
+ <div class="stats-row">
499
+ <div class="stat-badge">
500
+ <span class="stat-label">Avg Temp</span>
501
+ <span class="stat-val"><i class="bi bi-thermometer-half text-danger"></i> {{ w.avg_temp
502
+ }}°</span>
503
+ </div>
504
+ <div class="stat-badge">
505
+ <span class="stat-label">Rainfall</span>
506
+ <span class="stat-val"><i class="bi bi-droplet-fill text-primary"></i> {{ w.rainfall
507
+ }}mm</span>
508
+ </div>
509
+
510
+ <!-- NEW: Humidity -->
511
+ <div class="stat-badge">
512
+ <span class="stat-label">Humidity</span>
513
+ <span class="stat-val"><i class="bi bi-moisture text-info"></i> {{ w.humidity }}%</span>
514
+ </div>
515
+
516
+ <!-- NEW: Rainy Days Calculation -->
517
+ {% set rainy_days = w.days | selectattr('rain', '>', 0) | list | length %}
518
+ <div class="stat-badge">
519
+ <span class="stat-label">Rainy Days</span>
520
+ <span class="stat-val"><i class="bi bi-cloud-drizzle-fill text-dark"></i> {{ rainy_days
521
+ }}</span>
522
+ </div>
523
+ </div>
524
+ </div>
525
+
526
+ <div class="cal-body">
527
+ <!-- Left: Threats -->
528
+ <div class="cal-left">
529
+ <div class="section-head"><i class="bi bi-bug"></i> Identified Risks</div>
530
+ <div class="threat-list">
531
+ {% set ns = namespace(found=false) %}
532
+ {% for pest in report_data.pest_prediction_table %}
533
+ {% set current_month_name = w.month.split(' ')[0] %}
534
+ {% if current_month_name in pest.outbreak_months or "All" in pest.outbreak_months %}
535
+ {% set ns.found = true %}
536
+ <div class="threat-item">
537
+ <span class="fw-bold text-dark">{{ pest.pest_name }}</span>
538
+ <span class="risk-dot {{ pest.severity|lower }}"></span>
539
+ </div>
540
+ {% endif %}
541
+ {% endfor %}
542
+ {% if not ns.found %}
543
+ <div class="text-center py-5 text-muted opacity-50">
544
+ <i class="bi bi-shield-check fs-1"></i>
545
+ <p class="small mt-2">Zero Threats</p>
546
+ </div>
547
+ {% endif %}
548
+ </div>
549
+ </div>
550
+
551
+ <!-- Right: Strategy -->
552
+ <div class="cal-right">
553
+ <!-- Overview -->
554
+ <div class="overview-panel">
555
+ {% set ns_meta = namespace(has_high=false, stages=[]) %}
556
+ {% for pest in report_data.pest_prediction_table %}
557
+ {% set current_month_name = w.month.split(' ')[0] %}
558
+ {% if current_month_name in pest.outbreak_months or "All" in pest.outbreak_months %}
559
+ {% if 'High' in pest.severity %} {% set ns_meta.has_high = true %} {% endif %}
560
+ {% if pest.impacting_stage and pest.impacting_stage not in ns_meta.stages %}
561
+ {% set _ = ns_meta.stages.append(pest.impacting_stage) %}
562
+ {% endif %}
563
+ {% endif %}
564
+ {% endfor %}
565
+
566
+ <!-- Status Card -->
567
+ <div class="insight-card {{ 'critical' if ns_meta.has_high else 'stable' }}">
568
+ <div class="ic-icon"><i
569
+ class="bi {{ 'bi-exclamation-octagon-fill' if ns_meta.has_high else 'bi-check-circle-fill text-success' }}"></i>
570
+ </div>
571
+ <div class="ic-content">
572
+ <span class="ic-label">Threat Level</span>
573
+ <span class="ic-value">{{ 'CRITICAL' if ns_meta.has_high else 'MONITORING'
574
+ }}</span>
575
+ </div>
576
+ </div>
577
+
578
+ <!-- Weather Tip Card -->
579
+ <div class="insight-card advisory">
580
+ <div class="ic-icon"><i class="bi bi-lightbulb-fill"></i></div>
581
+ <div class="ic-content">
582
+ <span class="ic-label">Advisory</span>
583
+ <span class="ic-value" style="font-size: 0.85rem; font-weight: 600;">
584
+ {% if w.rainfall > 150 %} Ensure Drainage
585
+ {% elif w.rainfall < 10 %} Increase Irrigation {% elif w.avg_temp> 35 %}
586
+ Heat Protection
587
+ {% else %} Standard Monitoring {% endif %}
588
+ </span>
589
+ </div>
590
+ </div>
591
+ </div>
592
+
593
+ <!-- Tasks -->
594
+ <div class="section-head text-dark"><i class="bi bi-list-check"></i> Action Checklist</div>
595
+ <div class="task-scroll">
596
+ {% set ns_act = namespace(found=false) %}
597
+ {% for pest in report_data.pest_prediction_table %}
598
+ {% set current_month_name = w.month.split(' ')[0] %}
599
+ {% if current_month_name in pest.outbreak_months or "All" in pest.outbreak_months %}
600
+ {% set ns_act.found = true %}
601
+ <div class="task-row {{ 'urgent' if 'High' in pest.severity else '' }}">
602
+ <div class="t-check"></div>
603
+ <div>
604
+ <div class="t-text">{{ pest.precautionary_measures | striptags }}</div>
605
+ <span class="t-tag">Preventing: {{ pest.pest_name }}</span>
606
+ </div>
607
+ </div>
608
+ {% endif %}
609
+ {% endfor %}
610
+
611
+ {% if not ns_act.found %}
612
+ <div class="task-row">
613
+ <div class="t-check bg-success border-success"></div>
614
+ <div class="t-text text-success">All clear. Proceed with standard crop husbandry.
615
+ </div>
616
+ </div>
617
+ {% endif %}
618
+ </div>
619
+
620
+ <!-- Footer -->
621
+ {% if ns_meta.stages %}
622
+ <div class="stage-footer">
623
+ <span class="small fw-bold text-muted">VULNERABLE STAGES:</span>
624
+ {% for stage in ns_meta.stages %}
625
+ <span class="stage-pill">{{ stage }}</span>
626
+ {% endfor %}
627
+ </div>
628
+ {% endif %}
629
+ </div>
630
+ </div>
631
+ </div>
632
+ {% endfor %}
633
+ {% else %}
634
+ <div class="alert alert-danger">Loading Data...</div>
635
+ {% endif %}
636
+ </div>
637
+
638
+ <button class="nav-btn" onclick="changeMonth(1)"><i class="bi bi-chevron-right"></i></button>
639
+ </div>
640
+
641
+ <div class="text-center mt-4 mb-5">
642
+ <span id="pIndicator" class="badge rounded-pill bg-dark py-2 px-4 shadow">Month 1</span>
643
+ </div>
644
+
645
+ <div class="text-center pb-5">
646
+ <a href="/" class="btn btn-outline-dark rounded-pill px-4"><i class="bi bi-arrow-repeat me-2"></i>Analyze
647
+ Another Field</a>
648
+ <div class="mt-4 text-muted small opacity-75">
649
+ Powered by Gemini & NVIDIA AI Intelligence
650
+ </div>
651
+ </div>
652
+
653
+ </div>
654
+
655
+ <!-- JS -->
656
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
657
+ <script>
658
+ let curr = 0;
659
+ const cards = document.querySelectorAll('.flip-card');
660
+ const ind = document.getElementById('pIndicator');
661
+ const len = cards.length;
662
+
663
+ function render() {
664
+ cards.forEach((c, i) => {
665
+ c.classList.remove('active');
666
+ if (i === curr) c.classList.add('active');
667
+ });
668
+ if (len > 0) ind.innerText = `Month ${curr + 1} of ${len}`;
669
+ }
670
+ function changeMonth(d) {
671
+ let n = curr + d;
672
+ if (n >= 0 && n < len) { curr = n; render(); }
673
+ }
674
+ render();
675
+ </script>
676
+ </body>
677
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
678
  </html>