#!/usr/bin/env python3
"""
סקריפט ליצירת תיקיית forecast עם קובץ index.html המכיל נתוני תחזית אמיתיים
"""
import pandas as pd
import numpy as np
import json
import pathlib
from datetime import datetime
import shutil
def load_real_data(config):
"""טוען נתונים אמיתיים מהקבצים של המערכת"""
try:
# טעינת נתונים מצטברים
agg_path = pathlib.Path(config.AGG_CSV)
forecast_path = pathlib.Path(config.FORECAST_CSV)
if not agg_path.exists() or not forecast_path.exists():
print("⚠️ לא נמצאו קבצי נתונים. משתמש בנתוני דמו.")
return None, None
# קריאת הנתונים
all_data = pd.read_csv(agg_path, index_col=0, parse_dates=True)
forecast = pd.read_csv(forecast_path, index_col=0, parse_dates=True)
# 48 שעות אחרונות
observed = all_data[-192:] # 48 hours * 4 quarters
return observed['count'], forecast['count']
except Exception as e:
print(f"⚠️ שגיאה בטעינת נתונים: {e}")
return None, None
def generate_js_data(observed, forecast):
"""ממיר את הנתונים לפורמט JavaScript"""
# נתוני עבר
observed_js = []
for idx, value in observed.items():
observed_js.append({
'time': idx.isoformat(),
'value': float(value)
})
# נתוני תחזית
forecast_js = []
for idx, value in forecast.items():
forecast_js.append({
'time': idx.isoformat(),
'value': float(value)
})
return {
'observed': observed_js,
'forecast': forecast_js,
'generatedAt': datetime.now().isoformat(),
'modelName': 'SARIMAX' # או לקחת מהמערכת
}
def create_basic_html(output_path):
"""יוצר את קובץ ה-HTML הבסיסי המלא"""
html_content = """<!DOCTYPE html>
<html dir="rtl" lang="he">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>תחזית התרעות - כפר סבא</title>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 40px;
text-align: center;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
}
.header .subtitle {
font-size: 1.1em;
opacity: 0.9;
}
.content {
padding: 40px;
}
.alert-box {
background: #fff3cd;
border-right: 5px solid #ffc107;
border-radius: 10px;
padding: 20px;
margin-bottom: 30px;
display: flex;
align-items: center;
gap: 15px;
animation: slideIn 0.5s ease-out;
}
.alert-box.high-risk {
background: #f8d7da;
border-color: #dc3545;
}
.alert-box .icon {
font-size: 2em;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin: 30px 0;
}
.stat-card {
background: #f8f9fa;
padding: 25px;
border-radius: 15px;
text-align: center;
transition: all 0.3s ease;
border: 2px solid transparent;
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
border-color: #667eea;
}
.stat-value {
font-size: 2.5em;
font-weight: bold;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.stat-label {
color: #666;
margin-top: 10px;
font-size: 0.95em;
}
.chart-container {
margin: 40px 0;
padding: 20px;
background: #f8f9fa;
border-radius: 15px;
}
.section-title {
font-size: 1.8em;
color: #333;
margin: 40px 0 20px;
display: flex;
align-items: center;
gap: 10px;
}
.table-container {
overflow-x: auto;
margin: 20px 0;
border-radius: 10px;
box-shadow: 0 5px 20px rgba(0,0,0,0.05);
}
table {
width: 100%;
border-collapse: collapse;
background: white;
}
th {
background: #667eea;
color: white;
padding: 15px;
text-align: right;
font-weight: 600;
}
td {
padding: 12px 15px;
border-bottom: 1px solid #eee;
}
tr:hover {
background: #f8f9fa;
}
.risk-low { background: #d4edda; }
.risk-medium { background: #fff3cd; }
.risk-high { background: #f8d7da; }
.risk-critical { background: #f5c6cb; }
.footer {
background: #f8f9fa;
padding: 30px;
text-align: center;
color: #666;
}
.loading {
text-align: center;
padding: 50px;
font-size: 1.2em;
color: #666;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.pulse {
animation: pulse 2s infinite;
}
@media (max-width: 768px) {
.header h1 {
font-size: 1.8em;
}
.stats-grid {
grid-template-columns: 1fr 1fr;
}
.content {
padding: 20px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚨 מערכת תחזית התרעות - כפר סבא</h1>
<div class="subtitle">עדכון אחרון: <span id="updateTime"></span></div>
</div>
<div class="content">
<div id="alertSection"></div>
<div class="stats-grid" id="statsGrid">
<div class="stat-card">
<div class="stat-value" id="avgForecast">-</div>
<div class="stat-label">ממוצע התרעות צפוי (24 שעות)</div>
</div>
<div class="stat-card">
<div class="stat-value" id="maxProb">-</div>
<div class="stat-label">הסתברות מקסימלית</div>
</div>
<div class="stat-card">
<div class="stat-value" id="totalExpected">-</div>
<div class="stat-label">סה"כ התרעות צפויות</div>
</div>
<div class="stat-card">
<div class="stat-value" id="riskHours">-</div>
<div class="stat-label">שעות בסיכון גבוה</div>
</div>
</div>
<div class="chart-container">
<div id="mainChart" class="loading">טוען נתונים...</div>
</div>
<h2 class="section-title">📊 פירוט תחזית לפי שעות</h2>
<div class="table-container">
<table id="forecastTable">
<thead>
<tr>
<th>שעה</th>
<th>תחזית התרעות</th>
<th>הסתברות</th>
<th>רמת סיכון</th>
</tr>
</thead>
<tbody id="tableBody">
<!-- יתמלא דינמית -->
</tbody>
</table>
</div>
</div>
<div class="footer">
<p>התחזית מבוססת על נתונים היסטוריים ומודל SARIMAX/LSTM</p>
<p>הנתונים מתעדכנים אוטומטית ועשויים להשתנות</p>
</div>
</div>
<script>
// הגדרות ונתונים
const CONFIG = {
cityName: 'כפר סבא',
modelName: 'SARIMAX',
riskLevels: {
low: 2,
medium: 5,
high: 10,
critical: 15
}
};
// פונקציה ליצירת נתוני דמו (תחליף בנתונים אמיתיים)
function generateMockData() {
const now = new Date();
const observedData = [];
const forecastData = [];
// 48 שעות אחרונות
for (let i = 192; i > 0; i--) {
const time = new Date(now.getTime() - i * 15 * 60000);
observedData.push({
time: time,
value: Math.max(0, Math.random() * 8 + Math.sin(time.getHours() / 24 * Math.PI * 2) * 3)
});
}
// 24 שעות תחזית
for (let i = 0; i < 96; i++) {
const time = new Date(now.getTime() + i * 15 * 60000);
forecastData.push({
time: time,
value: Math.max(0, Math.random() * 6 + Math.sin(time.getHours() / 24 * Math.PI * 2) * 2)
});
}
return { observed: observedData, forecast: forecastData };
}
// חישוב הסתברות Poisson
function calculateProbability(lambda) {
return 1 - Math.exp(-lambda);
}
// קביעת רמת סיכון
function getRiskLevel(value) {
if (value < CONFIG.riskLevels.low) return { level: 'נמוך', class: 'risk-low' };
if (value < CONFIG.riskLevels.medium) return { level: 'בינוני', class: 'risk-medium' };
if (value < CONFIG.riskLevels.high) return { level: 'גבוה', class: 'risk-high' };
return { level: 'קריטי', class: 'risk-critical' };
}
// עדכון סטטיסטיקות
function updateStats(forecastData) {
const values = forecastData.map(d => d.value);
const probabilities = values.map(v => calculateProbability(v));
const avgForecast = values.reduce((a, b) => a + b, 0) / values.length;
const maxProb = Math.max(...probabilities);
const totalExpected = values.reduce((a, b) => a + b, 0);
const riskHours = values.filter(v => v > CONFIG.riskLevels.medium).length / 4;
document.getElementById('avgForecast').textContent = avgForecast.toFixed(1);
document.getElementById('maxProb').textContent = (maxProb * 100).toFixed(0) + '%';
document.getElementById('totalExpected').textContent = Math.round(totalExpected);
document.getElementById('riskHours').textContent = riskHours.toFixed(0);
// עדכון התראות
const highRiskTimes = forecastData.filter(d => d.value > CONFIG.riskLevels.high);
const alertSection = document.getElementById('alertSection');
if (highRiskTimes.length > 0) {
const times = highRiskTimes.slice(0, 3).map(d =>
d.time.toLocaleTimeString('he-IL', { hour: '2-digit', minute: '2-digit' })
).join(', ');
alertSection.innerHTML = `
<div class="alert-box high-risk">
<div class="icon pulse">⚠️</div>
<div>
<strong>אזהרה:</strong> צפוי סיכון גבוה להתרעות בשעות: ${times}
</div>
</div>
`;
} else {
alertSection.innerHTML = `
<div class="alert-box">
<div class="icon">ℹ️</div>
<div>
<strong>מידע:</strong> לא צפוי סיכון גבוה במיוחד ב-24 השעות הקרובות
</div>
</div>
`;
}
}
// יצירת גרפים
function createCharts(data) {
const observedTrace = {
x: data.observed.map(d => d.time),
y: data.observed.map(d => d.value),
mode: 'lines+markers',
name: 'נתוני עבר',
line: { color: '#1f77b4', width: 2 },
marker: { size: 4 }
};
const forecastTrace = {
x: data.forecast.map(d => d.time),
y: data.forecast.map(d => d.value),
mode: 'lines+markers',
name: 'תחזית',
line: { color: '#ff7f0e', width: 2, dash: 'dash' },
marker: { size: 4 }
};
// גרף ראשי
const layout1 = {
title: 'התרעות - 48 שעות אחרונות + 24 שעות תחזית',
xaxis: { title: 'זמן', gridcolor: '#eee' },
yaxis: { title: 'מספר התרעות', gridcolor: '#eee' },
height: 400,
margin: { t: 50, b: 50, l: 60, r: 40 },
shapes: [
{
type: 'rect',
xref: 'paper',
yref: 'y',
x0: 0, x1: 1,
y0: 0, y1: CONFIG.riskLevels.low,
fillcolor: 'green',
opacity: 0.1,
line: { width: 0 }
},
{
type: 'rect',
xref: 'paper',
yref: 'y',
x0: 0, x1: 1,
y0: CONFIG.riskLevels.low, y1: CONFIG.riskLevels.medium,
fillcolor: 'yellow',
opacity: 0.1,
line: { width: 0 }
},
{
type: 'rect',
xref: 'paper',
yref: 'y',
x0: 0, x1: 1,
y0: CONFIG.riskLevels.medium, y1: CONFIG.riskLevels.high,
fillcolor: 'orange',
opacity: 0.1,
line: { width: 0 }
}
]
};
// גרף הסתברויות
const probabilities = data.forecast.map(d => calculateProbability(d.value) * 100);
const probTrace = {
x: data.forecast.map(d => d.time),
y: probabilities,
type: 'bar',
marker: {
color: probabilities.map(p =>
p < 20 ? 'green' : p < 50 ? 'yellow' : p < 70 ? 'orange' : 'red'
)
},
text: probabilities.map(p => p.toFixed(0) + '%'),
textposition: 'outside'
};
const layout2 = {
title: 'הסתברות להתרעה (%) - 24 שעות קדימה',
xaxis: { title: 'זמן' },
yaxis: { title: 'הסתברות (%)', range: [0, 100] },
height: 300,
margin: { t: 50, b: 50, l: 60, r: 40 }
};
// הצגת הגרפים
const mainChart = document.getElementById('mainChart');
mainChart.innerHTML = '';
const div1 = document.createElement('div');
const div2 = document.createElement('div');
mainChart.appendChild(div1);
mainChart.appendChild(div2);
Plotly.newPlot(div1, [observedTrace, forecastTrace], layout1, {responsive: true});
Plotly.newPlot(div2, [probTrace], layout2, {responsive: true});
}
// יצירת טבלה
function createTable(forecastData) {
const tableBody = document.getElementById('tableBody');
tableBody.innerHTML = '';
// רק 12 השעות הקרובות
forecastData.slice(0, 48).forEach(item => {
const risk = getRiskLevel(item.value);
const prob = calculateProbability(item.value);
const row = document.createElement('tr');
row.className = risk.class;
row.innerHTML = `
<td>${item.time.toLocaleTimeString('he-IL', { hour: '2-digit', minute: '2-digit' })}</td>
<td>${item.value.toFixed(2)}</td>
<td>${(prob * 100).toFixed(0)}%</td>
<td>${risk.level}</td>
`;
tableBody.appendChild(row);
});
}
// עדכון זמן
function updateTime() {
const now = new Date();
document.getElementById('updateTime').textContent =
now.toLocaleString('he-IL', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}
// אתחול
function init() {
updateTime();
const data = generateMockData();
updateStats(data.forecast);
createCharts(data);
createTable(data.forecast);
// רענון אוטומטי כל 5 דקות
setInterval(() => {
updateTime();
const newData = generateMockData();
updateStats(newData.forecast);
createCharts(newData);
createTable(newData.forecast);
}, 5 * 60 * 1000);
}
// הפעלה בטעינת הדף
document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>"""
with open(output_path, 'w', encoding='utf-8') as f:
f.write(html_content)
def update_html_with_data(template_path, output_path, data, city_name):
"""מעדכן את קובץ ה-HTML עם הנתונים האמיתיים"""
with open(template_path, 'r', encoding='utf-8') as f:
html_content = f.read()
# הכנת סקריפט עם הנתונים האמיתיים
data_script = f"""
<script>
// נתונים אמיתיים מהמערכת
const REAL_DATA = {json.dumps(data, ensure_ascii=False, indent=2)};
const CITY_NAME = "{city_name}";
// החלפת פונקציית generateMockData
function generateMockData() {{
return {{
observed: REAL_DATA.observed.map(d => ({{
time: new Date(d.time),
value: d.value
}})),
forecast: REAL_DATA.forecast.map(d => ({{
time: new Date(d.time),
value: d.value
}}))
}};
}}
// עדכון שם העיר
document.addEventListener('DOMContentLoaded', function() {{
const title = document.querySelector('.header h1');
if (title) {{
title.textContent = '🚨 מערכת תחזית התרעות - ' + CITY_NAME;
}}
CONFIG.cityName = CITY_NAME;
}});
</script>
"""
# הוספת הנתונים לפני סגירת ה-body
html_content = html_content.replace('</body>', data_script + '\n</body>')
# שמירה
with open(output_path, 'w', encoding='utf-8') as f:
f.write(html_content)
def create_forecast_folder(city_name="כפר סבא", use_real_data=True):
"""יוצר תיקיית forecast עם כל הקבצים הדרושים"""
# יצירת תיקייה
output_dir = pathlib.Path("forecast")
output_dir.mkdir(exist_ok=True)
if use_real_data:
try:
import config
observed, forecast = load_real_data(config)
if observed is not None and forecast is not None:
# יצירת נתוני JS
js_data = generate_js_data(observed, forecast)
# כתיבת HTML בסיסי
output_html = output_dir / "index.html"
create_basic_html(output_html)
# עדכון עם נתונים אמיתיים
update_html_with_data(output_html, output_html, js_data, config.CITY_NAME)
print(f"✅ נוצרה תיקיית forecast עם נתונים אמיתיים עבור {config.CITY_NAME}")
else:
create_basic_html(output_dir / "index.html")
print("✅ נוצרה תיקיית forecast עם נתוני דמו")
except ImportError:
create_basic_html(output_dir / "index.html")
print("✅ נוצרה תיקיית forecast עם נתוני דמו (config.py לא נמצא)")
else:
create_basic_html(output_dir / "index.html")
print("✅ נוצרה תיקיית forecast עם נתוני דמו")
# יצירת README
create_readme(output_dir)
return output_dir
def create_readme(output_dir):
"""יוצר קובץ README עם הוראות"""
readme_content = """# תחזית התרעות - הוראות שימוש
## 🚀 הפעלה מהירה
1. פתח את `index.html` בדפדפן
2. זהו! התחזית מוצגת
## 📤 אפשרויות שיתוף
### שיתוף מקומי
- שלח את הקובץ ב-WhatsApp/Email
- העלה ל-Google Drive ושתף קישור
### אירוח באינטרנט
#### Netlify Drop (מהיר וחינם)
1. גש ל: https://app.netlify.com/drop
2. גרור את תיקיית forecast
3. קבל קישור מיידי
#### GitHub Pages (חינם)
```bash
# צור repository חדש ב-GitHub
# העלה את התיקייה
# Settings > Pages > Deploy from branch
# הקישור: https://[username].github.io/[repo-name]/
```
#### Vercel
```bash
npm i -g vercel
vercel --prod
```
## 🔄 עדכון אוטומטי
להרצה אוטומטית כל שעה:
```bash
# Linux/Mac
0 * * * * cd /path/to/project && python generate_forecast_html.py
# Windows Task Scheduler
python C:\\path\\to\\generate_forecast_html.py
```
## 📱 תמיכה
- ✅ מותאם למובייל
- ✅ עובד בכל הדפדפנים
- ✅ ללא תלות בשרת
## 🛠️ התאמה אישית
לשינוי עיר או הגדרות, ערוך את `config.py`
"""
with open(output_dir / "README.md", 'w', encoding='utf-8') as f:
f.write(readme_content)
if __name__ == "__main__":
# יצירת התיקייה
folder = create_forecast_folder(use_real_data=True)
print(f"\n📁 התיקייה נוצרה ב: {folder.absolute()}")
print(f"🌐 לפתיחה: file://{folder.absolute()}/index.html")
print(f"\n💡 טיפ: קרא את {folder}/README.md להוראות שיתוף")
# אופציונלי: פתיחה אוטומטית בדפדפן
try:
import webbrowser
webbrowser.open(f"file://{folder.absolute()}/index.html")
except:
pass# alarmks