Enhance report generation by adding peak lightning activity analysis and key findings section. Introduce new functions for summarizing storm activity and update report strings for improved clarity. Refactor analysis radius logic to ensure accurate boundary calculations based on distance rings.
This commit is contained in:
parent
6d6e2e4d7c
commit
29622b48fb
@ -28,6 +28,29 @@ def filter_lightning_by_distance(lightning_df, centroid_lat, centroid_lng):
|
|||||||
mask = dists <= max_distance
|
mask = dists <= max_distance
|
||||||
return lightning_df.loc[mask].copy()
|
return lightning_df.loc[mask].copy()
|
||||||
|
|
||||||
|
def get_peak_activity(lightning_df, centroid_lat, centroid_lng):
|
||||||
|
"""
|
||||||
|
Return the single busiest minute within the analysis radius.
|
||||||
|
|
||||||
|
Returns a dict {'when': 'DD-MM-YYYY HH:MM', 'count': int} or None when there
|
||||||
|
is no activity to summarize.
|
||||||
|
"""
|
||||||
|
filtered_df = filter_lightning_by_distance(lightning_df, centroid_lat, centroid_lng)
|
||||||
|
if len(filtered_df) == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
f = filtered_df.copy()
|
||||||
|
if f['local_time'].dtype == 'object':
|
||||||
|
f['local_time'] = pd.to_datetime(f['local_time'])
|
||||||
|
|
||||||
|
f['minute_rounded'] = f['local_time'].dt.floor('min')
|
||||||
|
counts = f.groupby('minute_rounded').size()
|
||||||
|
if counts.empty:
|
||||||
|
return None
|
||||||
|
|
||||||
|
peak_minute = counts.idxmax()
|
||||||
|
return {'when': peak_minute.strftime('%d-%m-%Y %H:%M'), 'count': int(counts.max())}
|
||||||
|
|
||||||
def find_activity_periods(df, min_gap_minutes=30, min_events_per_period=10):
|
def find_activity_periods(df, min_gap_minutes=30, min_events_per_period=10):
|
||||||
"""
|
"""
|
||||||
Find concentrated activity periods based on time gaps.
|
Find concentrated activity periods based on time gaps.
|
||||||
@ -198,7 +221,7 @@ def _build_histogram_figure_for_periods(periods_chunk, max_distance_km, lang=Non
|
|||||||
period_str = f"{start.strftime('%d-%m-%Y %H:%M')}-{end.strftime('%d-%m-%Y %H:%M')}"
|
period_str = f"{start.strftime('%d-%m-%Y %H:%M')}-{end.strftime('%d-%m-%Y %H:%M')}"
|
||||||
|
|
||||||
# Use line breaks to fit within histogram width
|
# Use line breaks to fit within histogram width
|
||||||
return f"Date: {date_str}<br>Period: {period_str}<br>Total lightnings: {event_count}"
|
return s.hist_period_title.format(date=date_str, period=period_str, total=event_count)
|
||||||
|
|
||||||
fig = make_subplots(
|
fig = make_subplots(
|
||||||
rows=n_rows,
|
rows=n_rows,
|
||||||
@ -226,7 +249,7 @@ def _build_histogram_figure_for_periods(periods_chunk, max_distance_km, lang=Non
|
|||||||
if p_type in minute_counts.columns:
|
if p_type in minute_counts.columns:
|
||||||
counts = minute_counts[p_type].values
|
counts = minute_counts[p_type].values
|
||||||
|
|
||||||
p_type_name = "Cloud-to-Ground" if p_type == '0' else "Intercloud"
|
p_type_name = s.hist_cg_legend if p_type == '0' else s.hist_ic_legend
|
||||||
|
|
||||||
fig.add_trace(
|
fig.add_trace(
|
||||||
go.Bar(
|
go.Bar(
|
||||||
|
|||||||
@ -18,7 +18,7 @@ from docx.oxml import OxmlElement
|
|||||||
from docx.oxml.ns import qn
|
from docx.oxml.ns import qn
|
||||||
from docx.shared import Cm, Inches, Pt, RGBColor
|
from docx.shared import Cm, Inches, Pt, RGBColor
|
||||||
|
|
||||||
from src.analysis.histogram import create_lightning_histogram_pages
|
from src.analysis.histogram import create_lightning_histogram_pages, get_peak_activity
|
||||||
from src.analysis.risk import calculate_turbine_risks
|
from src.analysis.risk import calculate_turbine_risks
|
||||||
from src.analysis.statistics import calculate_lightning_statistics
|
from src.analysis.statistics import calculate_lightning_statistics
|
||||||
from src.config import config
|
from src.config import config
|
||||||
@ -36,7 +36,7 @@ from src.utils import (
|
|||||||
now_in_timezone,
|
now_in_timezone,
|
||||||
get_analysis_radius_m,
|
get_analysis_radius_m,
|
||||||
)
|
)
|
||||||
from src.analysis.geospatial import haversine_distance
|
from src.analysis.geospatial import haversine_distance, haversine_distance_vectorized
|
||||||
from src.visualization.maps import plot_cloud_to_ground_coordinate_plane, plot_intercloud_coordinate_plane
|
from src.visualization.maps import plot_cloud_to_ground_coordinate_plane, plot_intercloud_coordinate_plane
|
||||||
from src.visualization.storm_cells import (
|
from src.visualization.storm_cells import (
|
||||||
create_storm_cells_map,
|
create_storm_cells_map,
|
||||||
@ -169,6 +169,103 @@ def _add_bullets(doc: Document, items: Iterable[str], size_pt: int = 10) -> None
|
|||||||
r.font.size = Pt(size_pt)
|
r.font.size = Pt(size_pt)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_key_findings(
|
||||||
|
lightning_df: pd.DataFrame,
|
||||||
|
turbine_df: pd.DataFrame,
|
||||||
|
stats: dict[str, Any],
|
||||||
|
centroid_lat: float,
|
||||||
|
centroid_lng: float,
|
||||||
|
s,
|
||||||
|
storm_cell_count: int = 0,
|
||||||
|
storm_closest_to_centroid_km: float | None = None,
|
||||||
|
) -> tuple[list[str], str]:
|
||||||
|
radius_m = get_analysis_radius_m()
|
||||||
|
if radius_m <= 0:
|
||||||
|
radius_m = int(max(config.distance_rings)) if config.distance_rings else 0
|
||||||
|
innermost_km = (float(min(config.distance_rings)) / 1000.0) if config.distance_rings else (radius_m / 1000.0)
|
||||||
|
|
||||||
|
turbine_count = len(turbine_df)
|
||||||
|
total_events = int(stats.get("total_events", 0) or 0)
|
||||||
|
|
||||||
|
cg_total = 0
|
||||||
|
ic_total = 0
|
||||||
|
innermost_count = 0
|
||||||
|
within_df = lightning_df.iloc[0:0]
|
||||||
|
if len(lightning_df) > 0 and radius_m > 0:
|
||||||
|
dists_m = haversine_distance_vectorized(
|
||||||
|
centroid_lat,
|
||||||
|
centroid_lng,
|
||||||
|
lightning_df["lat"].values,
|
||||||
|
lightning_df["lng"].values,
|
||||||
|
)
|
||||||
|
within_mask = dists_m <= radius_m
|
||||||
|
within_df = lightning_df.loc[within_mask].copy()
|
||||||
|
within_dists = dists_m[within_mask]
|
||||||
|
ptype = within_df["p_type"].astype(str)
|
||||||
|
cg_total = int((ptype == "0").sum())
|
||||||
|
ic_total = int((ptype == "1").sum())
|
||||||
|
innermost_count = int((within_dists <= (innermost_km * 1000.0)).sum())
|
||||||
|
|
||||||
|
strongest_ka: float | None = None
|
||||||
|
nearest_km: float | None = None
|
||||||
|
nearest_turbine: str | None = None
|
||||||
|
if len(within_df) > 0 and "current" in within_df.columns:
|
||||||
|
cg_df = within_df[within_df["p_type"].astype(str) == "0"]
|
||||||
|
if len(cg_df) > 0:
|
||||||
|
currents = pd.to_numeric(cg_df["current"], errors="coerce").abs()
|
||||||
|
if bool(currents.notna().any()):
|
||||||
|
strongest_ka = float(currents.max()) / 1000.0
|
||||||
|
if turbine_count > 0 and {"lat", "lng"}.issubset(turbine_df.columns):
|
||||||
|
cg_lat = cg_df["lat"].values
|
||||||
|
cg_lng = cg_df["lng"].values
|
||||||
|
best = float("inf")
|
||||||
|
best_name: str | None = None
|
||||||
|
for _, t in turbine_df.iterrows():
|
||||||
|
d = haversine_distance_vectorized(float(t["lat"]), float(t["lng"]), cg_lat, cg_lng)
|
||||||
|
m = float(d.min())
|
||||||
|
if m < best:
|
||||||
|
best = m
|
||||||
|
best_name = str(t.get("name", "N/A"))
|
||||||
|
if best != float("inf"):
|
||||||
|
nearest_km = best / 1000.0
|
||||||
|
nearest_turbine = best_name
|
||||||
|
|
||||||
|
high_plus_count = 0
|
||||||
|
if "risk_log" in turbine_df.columns and turbine_count > 0:
|
||||||
|
high_plus_count = int((pd.to_numeric(turbine_df["risk_log"], errors="coerce") >= 0.8).sum())
|
||||||
|
|
||||||
|
active_days = len(stats.get("daily_lightning_by_rings") or {})
|
||||||
|
peak = get_peak_activity(lightning_df, centroid_lat, centroid_lng)
|
||||||
|
|
||||||
|
bullets: list[str] = [
|
||||||
|
s.kf_total_events.format(total=total_events, cg=cg_total, ic=ic_total),
|
||||||
|
s.kf_innermost.format(radius=innermost_km, count=innermost_count),
|
||||||
|
]
|
||||||
|
if peak:
|
||||||
|
bullets.append(s.kf_peak_activity.format(when=peak["when"], count=peak["count"]))
|
||||||
|
if strongest_ka is not None:
|
||||||
|
bullets.append(s.kf_strongest_strike.format(ka=strongest_ka))
|
||||||
|
if nearest_km is not None and nearest_turbine is not None:
|
||||||
|
bullets.append(s.kf_nearest_strike.format(distance=nearest_km, name=nearest_turbine))
|
||||||
|
if turbine_count > 0:
|
||||||
|
bullets.append(s.kf_high_risk_turbines.format(count=high_plus_count, total=turbine_count))
|
||||||
|
if active_days > 1:
|
||||||
|
bullets.append(s.kf_active_days.format(days=active_days))
|
||||||
|
if storm_cell_count > 0 and storm_closest_to_centroid_km is not None:
|
||||||
|
bullets.append(
|
||||||
|
s.kf_storm_distance.format(count=storm_cell_count, distance=storm_closest_to_centroid_km)
|
||||||
|
)
|
||||||
|
|
||||||
|
if total_events == 0:
|
||||||
|
recommendation = s.recommendation_no_activity
|
||||||
|
elif high_plus_count > 0:
|
||||||
|
recommendation = s.recommendation_high_plus.format(count=high_plus_count, total=turbine_count)
|
||||||
|
else:
|
||||||
|
recommendation = s.recommendation_routine
|
||||||
|
|
||||||
|
return bullets, recommendation
|
||||||
|
|
||||||
|
|
||||||
def _lighten_hex_color(hex_color: str, factor: float = 0.6) -> str:
|
def _lighten_hex_color(hex_color: str, factor: float = 0.6) -> str:
|
||||||
s = hex_color.strip()
|
s = hex_color.strip()
|
||||||
if s.startswith("#"):
|
if s.startswith("#"):
|
||||||
@ -602,6 +699,26 @@ def create_docx_report(
|
|||||||
storm_closest_distance_km: float | None = None
|
storm_closest_distance_km: float | None = None
|
||||||
storm_over_threshold_km = 1.0
|
storm_over_threshold_km = 1.0
|
||||||
|
|
||||||
|
# Closest storm-cell centroid to the farm centroid (independent of turbines): lets the
|
||||||
|
# report comment on detected storms even when no turbine was directly affected.
|
||||||
|
storm_cell_count = len(storm_data) if storm_data else 0
|
||||||
|
storm_closest_to_centroid_km: float | None = None
|
||||||
|
if storm_data:
|
||||||
|
min_centroid_dist = float("inf")
|
||||||
|
for storm in storm_data:
|
||||||
|
wkt_string = storm.get("cell_polygon_wkt", "") or ""
|
||||||
|
if not wkt_string:
|
||||||
|
continue
|
||||||
|
centroid = calculate_storm_cell_centroid(wkt_string)
|
||||||
|
if centroid is None:
|
||||||
|
continue
|
||||||
|
c_lat, c_lng = centroid
|
||||||
|
dist_km = haversine_distance(centroid_lat, centroid_lng, c_lat, c_lng) / 1000.0
|
||||||
|
if dist_km < min_centroid_dist:
|
||||||
|
min_centroid_dist = dist_km
|
||||||
|
if min_centroid_dist != float("inf"):
|
||||||
|
storm_closest_to_centroid_km = min_centroid_dist
|
||||||
|
|
||||||
if storm_data and top_turbine_name is not None and "risk_log" in turbine_df.columns and len(turbine_df) > 0:
|
if storm_data and top_turbine_name is not None and "risk_log" in turbine_df.columns and len(turbine_df) > 0:
|
||||||
# Use the coordinates of the highest-risk turbine for storm-distance calculations.
|
# Use the coordinates of the highest-risk turbine for storm-distance calculations.
|
||||||
if "lat" in turbine_df.columns and "lng" in turbine_df.columns and top_turbine_name is not None:
|
if "lat" in turbine_df.columns and "lng" in turbine_df.columns and top_turbine_name is not None:
|
||||||
@ -645,6 +762,9 @@ def create_docx_report(
|
|||||||
"storm_near_turbine_count": storm_near_turbine_count,
|
"storm_near_turbine_count": storm_near_turbine_count,
|
||||||
"storm_closest_distance_km": round(float(storm_closest_distance_km), 1) if storm_closest_distance_km is not None else None,
|
"storm_closest_distance_km": round(float(storm_closest_distance_km), 1) if storm_closest_distance_km is not None else None,
|
||||||
"storm_over_threshold_km": storm_over_threshold_km,
|
"storm_over_threshold_km": storm_over_threshold_km,
|
||||||
|
"storm_detected": storm_cell_count > 0,
|
||||||
|
"storm_cell_count": storm_cell_count,
|
||||||
|
"storm_closest_to_centroid_km": round(float(storm_closest_to_centroid_km), 1) if storm_closest_to_centroid_km is not None else None,
|
||||||
"turbine_risk_counts": turbine_risk_counts,
|
"turbine_risk_counts": turbine_risk_counts,
|
||||||
"storm_summary": storm_summary,
|
"storm_summary": storm_summary,
|
||||||
}
|
}
|
||||||
@ -656,16 +776,31 @@ def create_docx_report(
|
|||||||
if commentary_text.startswith(prefix):
|
if commentary_text.startswith(prefix):
|
||||||
commentary_text = commentary_text[len(prefix):].strip()
|
commentary_text = commentary_text[len(prefix):].strip()
|
||||||
break
|
break
|
||||||
|
key_findings, recommendation_text = _build_key_findings(
|
||||||
|
lightning_df,
|
||||||
|
turbine_df,
|
||||||
|
stats,
|
||||||
|
centroid_lat,
|
||||||
|
centroid_lng,
|
||||||
|
s,
|
||||||
|
storm_cell_count=storm_cell_count,
|
||||||
|
storm_closest_to_centroid_km=storm_closest_to_centroid_km,
|
||||||
|
)
|
||||||
|
|
||||||
# Keep the commentary close to the Turbine Information table (same page if possible).
|
# Keep the commentary close to the Turbine Information table (same page if possible).
|
||||||
doc.add_paragraph("")
|
doc.add_paragraph("")
|
||||||
doc.add_paragraph("")
|
doc.add_paragraph("")
|
||||||
_add_title(doc, s.report_summary, size_pt=14, bold=True, align=WD_ALIGN_PARAGRAPH.LEFT)
|
_add_title(doc, s.report_summary, size_pt=14, bold=True, align=WD_ALIGN_PARAGRAPH.LEFT)
|
||||||
|
_add_paragraph(doc, s.key_findings_heading, size_pt=11, bold=True)
|
||||||
|
_add_bullets(doc, key_findings, size_pt=10)
|
||||||
# DOCX line spacing for the Gemini commentary paragraph.
|
# DOCX line spacing for the Gemini commentary paragraph.
|
||||||
p = doc.add_paragraph()
|
p = doc.add_paragraph()
|
||||||
p.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
p.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
||||||
r = p.add_run(commentary_text)
|
r = p.add_run(commentary_text)
|
||||||
r.font.size = Pt(10)
|
r.font.size = Pt(10)
|
||||||
p.paragraph_format.line_spacing = 1.5
|
p.paragraph_format.line_spacing = 1.5
|
||||||
|
_add_paragraph(doc, s.recommendation_heading, size_pt=11, bold=True)
|
||||||
|
_add_paragraph(doc, recommendation_text, size_pt=10)
|
||||||
|
|
||||||
doc.add_page_break()
|
doc.add_page_break()
|
||||||
|
|
||||||
@ -692,6 +827,7 @@ def create_docx_report(
|
|||||||
_add_title(doc, s.cloud_to_ground_current_vs_distance, size_pt=14)
|
_add_title(doc, s.cloud_to_ground_current_vs_distance, size_pt=14)
|
||||||
if start_date and end_date:
|
if start_date and end_date:
|
||||||
_add_paragraph(doc, f"{start_date} - {end_date}", size_pt=10)
|
_add_paragraph(doc, f"{start_date} - {end_date}", size_pt=10)
|
||||||
|
_add_paragraph(doc, s.cg_current_vs_distance_desc, size_pt=10)
|
||||||
cg_chart = _build_current_vs_distance_chart(
|
cg_chart = _build_current_vs_distance_chart(
|
||||||
lightning_df,
|
lightning_df,
|
||||||
pre["dists_km"],
|
pre["dists_km"],
|
||||||
@ -717,6 +853,7 @@ def create_docx_report(
|
|||||||
_add_title(doc, s.intercloud_current_vs_distance, size_pt=14)
|
_add_title(doc, s.intercloud_current_vs_distance, size_pt=14)
|
||||||
if start_date and end_date:
|
if start_date and end_date:
|
||||||
_add_paragraph(doc, f"{start_date} - {end_date}", size_pt=10)
|
_add_paragraph(doc, f"{start_date} - {end_date}", size_pt=10)
|
||||||
|
_add_paragraph(doc, s.ic_current_vs_distance_desc, size_pt=10)
|
||||||
ic_chart = _build_current_vs_distance_chart(
|
ic_chart = _build_current_vs_distance_chart(
|
||||||
lightning_df,
|
lightning_df,
|
||||||
pre["dists_km"],
|
pre["dists_km"],
|
||||||
@ -734,6 +871,7 @@ def create_docx_report(
|
|||||||
risk_table_data, risk_row_colors = build_risk_table_data(turbine_df, lang)
|
risk_table_data, risk_row_colors = build_risk_table_data(turbine_df, lang)
|
||||||
if risk_table_data and len(risk_table_data) > 1:
|
if risk_table_data and len(risk_table_data) > 1:
|
||||||
_add_title(doc, s.turbine_risk_assessment, size_pt=14)
|
_add_title(doc, s.turbine_risk_assessment, size_pt=14)
|
||||||
|
_add_paragraph(doc, s.turbine_risk_assessment_desc, size_pt=10)
|
||||||
_add_table(doc, risk_table_data, row_colors=risk_row_colors)
|
_add_table(doc, risk_table_data, row_colors=risk_row_colors)
|
||||||
doc.add_page_break()
|
doc.add_page_break()
|
||||||
|
|
||||||
@ -797,8 +935,6 @@ def create_docx_report(
|
|||||||
size_pt=9,
|
size_pt=9,
|
||||||
)
|
)
|
||||||
|
|
||||||
_add_paragraph(doc, s.ring_section_intro, size_pt=10)
|
|
||||||
_add_paragraph(doc, s.ring_closer_risk.format(closest=closest_km), size_pt=10)
|
|
||||||
ring_items: list[str] = []
|
ring_items: list[str] = []
|
||||||
for ring_name, ring_data in sorted((stats.get("lightning_by_distance_rings") or {}).items()):
|
for ring_name, ring_data in sorted((stats.get("lightning_by_distance_rings") or {}).items()):
|
||||||
if ring_data.get("total", 0) > 0:
|
if ring_data.get("total", 0) > 0:
|
||||||
@ -854,9 +990,6 @@ def create_docx_report(
|
|||||||
size_pt=10,
|
size_pt=10,
|
||||||
)
|
)
|
||||||
|
|
||||||
_add_paragraph(doc, s.avg_direction.format(value=summary.get("avg_direction", 0)), size_pt=10)
|
|
||||||
_add_paragraph(doc, s.avg_speed.format(value=summary.get("avg_speed", 0)), size_pt=10)
|
|
||||||
|
|
||||||
daily_breakdown: dict[str, int] = summary.get("daily_breakdown", {}) or {}
|
daily_breakdown: dict[str, int] = summary.get("daily_breakdown", {}) or {}
|
||||||
_add_paragraph(doc, s.daily_storm_breakdown, size_pt=10, bold=True)
|
_add_paragraph(doc, s.daily_storm_breakdown, size_pt=10, bold=True)
|
||||||
sorted_days = sorted(daily_breakdown.keys())
|
sorted_days = sorted(daily_breakdown.keys())
|
||||||
|
|||||||
@ -104,6 +104,8 @@ def build_gemini_prompt(context: dict[str, Any], language: ReportLanguage | None
|
|||||||
storm_near_turbine_count = context.get("storm_near_turbine_count")
|
storm_near_turbine_count = context.get("storm_near_turbine_count")
|
||||||
storm_closest_distance_km = context.get("storm_closest_distance_km")
|
storm_closest_distance_km = context.get("storm_closest_distance_km")
|
||||||
storm_over_threshold_km = context.get("storm_over_threshold_km", 1.0)
|
storm_over_threshold_km = context.get("storm_over_threshold_km", 1.0)
|
||||||
|
storm_detected = context.get("storm_detected")
|
||||||
|
storm_closest_to_centroid_km = context.get("storm_closest_to_centroid_km")
|
||||||
|
|
||||||
ring_lines = _ring_lines(context, s)
|
ring_lines = _ring_lines(context, s)
|
||||||
storm_lines = _storm_lines(context)
|
storm_lines = _storm_lines(context)
|
||||||
@ -142,6 +144,8 @@ def build_gemini_prompt(context: dict[str, Any], language: ReportLanguage | None
|
|||||||
f"- storm_near_turbine_count: {storm_near_turbine_count}\n"
|
f"- storm_near_turbine_count: {storm_near_turbine_count}\n"
|
||||||
f"- storm_closest_distance_km: {storm_closest_distance_km}\n"
|
f"- storm_closest_distance_km: {storm_closest_distance_km}\n"
|
||||||
f"- storm_over_threshold_km: {storm_over_threshold_km}\n"
|
f"- storm_over_threshold_km: {storm_over_threshold_km}\n"
|
||||||
|
f"- storm_detected: {storm_detected}\n"
|
||||||
|
f"- storm_closest_to_centroid_km: {storm_closest_to_centroid_km}\n"
|
||||||
+ (f"\n- storm_summary:\n{chr(10).join(storm_lines)}" if storm_lines else "\n- storm_summary: not available")
|
+ (f"\n- storm_summary:\n{chr(10).join(storm_lines)}" if storm_lines else "\n- storm_summary: not available")
|
||||||
+ "\n\n"
|
+ "\n\n"
|
||||||
"Paragraf gereksinimleri:\n"
|
"Paragraf gereksinimleri:\n"
|
||||||
@ -150,7 +154,9 @@ def build_gemini_prompt(context: dict[str, Any], language: ReportLanguage | None
|
|||||||
"- is_single_turbine_report true ise: \"{top_turbine_name} türbini için log-risk skoru ... sınıfındadır\" gibi doğal bir ifade kullan.\n"
|
"- is_single_turbine_report true ise: \"{top_turbine_name} türbini için log-risk skoru ... sınıfındadır\" gibi doğal bir ifade kullan.\n"
|
||||||
"- is_single_turbine_report false ise: analiz alanında en yüksek riskli türbini ve risk sınıfını belirt.\n"
|
"- is_single_turbine_report false ise: analiz alanında en yüksek riskli türbini ve risk sınıfını belirt.\n"
|
||||||
"- top_turbine_name adını aynen kullan ve max_risk_definition ile ilişkilendir.\n"
|
"- top_turbine_name adını aynen kullan ve max_risk_definition ile ilişkilendir.\n"
|
||||||
"- Fırtına hücresi etkileşimini uygun olduğunda belirt; veri yoksa kısaca belirt.\n"
|
"- Fırtına hücresi etkileşimini yalnızca veri varken belirt.\n"
|
||||||
|
"- Fırtına tespit edildiyse (storm_detected true) ancak hiçbir türbinin üzerine gelmediyse, en yakın fırtına hücresinin merkeze uzaklığını (storm_closest_to_centroid_km) belirt.\n"
|
||||||
|
"- storm_detected false ise fırtınadan hiç bahsetme.\n"
|
||||||
"- Sayıları yuvarla: yoğunluk 3 ondalık, log risk 2 ondalık, mesafeler 1 ondalık km, sayılar tam sayı.\n"
|
"- Sayıları yuvarla: yoğunluk 3 ondalık, log risk 2 ondalık, mesafeler 1 ondalık km, sayılar tam sayı.\n"
|
||||||
"- Ton analitik, net ve alarmist olmayan olsun.\n"
|
"- Ton analitik, net ve alarmist olmayan olsun.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -189,6 +195,8 @@ def build_gemini_prompt(context: dict[str, Any], language: ReportLanguage | None
|
|||||||
f"- storm_near_turbine_count: {storm_near_turbine_count}\n"
|
f"- storm_near_turbine_count: {storm_near_turbine_count}\n"
|
||||||
f"- storm_closest_distance_km: {storm_closest_distance_km}\n"
|
f"- storm_closest_distance_km: {storm_closest_distance_km}\n"
|
||||||
f"- storm_over_threshold_km: {storm_over_threshold_km}\n"
|
f"- storm_over_threshold_km: {storm_over_threshold_km}\n"
|
||||||
|
f"- storm_detected: {storm_detected}\n"
|
||||||
|
f"- storm_closest_to_centroid_km: {storm_closest_to_centroid_km}\n"
|
||||||
+ (f"\n- storm_summary:\n{chr(10).join(storm_lines)}" if storm_lines else "\n- storm_summary: not available")
|
+ (f"\n- storm_summary:\n{chr(10).join(storm_lines)}" if storm_lines else "\n- storm_summary: not available")
|
||||||
+ "\n\n"
|
+ "\n\n"
|
||||||
"Requirements for the paragraph:\n"
|
"Requirements for the paragraph:\n"
|
||||||
@ -202,6 +210,8 @@ def build_gemini_prompt(context: dict[str, Any], language: ReportLanguage | None
|
|||||||
"- Mention storm-cell interaction with the turbine when storm information is available:\n"
|
"- Mention storm-cell interaction with the turbine when storm information is available:\n"
|
||||||
" - If storm_over_turbine is true: say that storm cells were very close to/over the turbine (based on centroid distance <= storm_over_threshold_km).\n"
|
" - If storm_over_turbine is true: say that storm cells were very close to/over the turbine (based on centroid distance <= storm_over_threshold_km).\n"
|
||||||
" - If storm_over_turbine is false and storm_closest_distance_km is provided: say the closest storm cell centroid came within storm_closest_distance_km km of the turbine.\n"
|
" - If storm_over_turbine is false and storm_closest_distance_km is provided: say the closest storm cell centroid came within storm_closest_distance_km km of the turbine.\n"
|
||||||
|
" - If storms were detected (storm_detected true) but did not pass over any turbine, mention the closest storm cell's distance to the centroid (storm_closest_to_centroid_km).\n"
|
||||||
|
" - If storm_detected is false, do not mention storms at all.\n"
|
||||||
"- If storm_summary is available, mention total storm cells and at least one severity count.\n"
|
"- If storm_summary is available, mention total storm cells and at least one severity count.\n"
|
||||||
"- Round numeric values as follows (use the rounded values you are given, avoid long decimals):\n"
|
"- Round numeric values as follows (use the rounded values you are given, avoid long decimals):\n"
|
||||||
" - lightning density to 3 decimals (events/km²)\n"
|
" - lightning density to 3 decimals (events/km²)\n"
|
||||||
@ -231,6 +241,7 @@ def fallback_commentary(context: dict[str, Any], language: ReportLanguage | None
|
|||||||
storm_closest_distance_km = context.get("storm_closest_distance_km")
|
storm_closest_distance_km = context.get("storm_closest_distance_km")
|
||||||
storm_over_threshold_km = context.get("storm_over_threshold_km", 1.0)
|
storm_over_threshold_km = context.get("storm_over_threshold_km", 1.0)
|
||||||
storm_near_turbine_count = context.get("storm_near_turbine_count")
|
storm_near_turbine_count = context.get("storm_near_turbine_count")
|
||||||
|
storm_closest_to_centroid_km = context.get("storm_closest_to_centroid_km")
|
||||||
|
|
||||||
outermost_ring = top_rings[-1] if top_rings else None
|
outermost_ring = top_rings[-1] if top_rings else None
|
||||||
best_ring = top_rings[0] if top_rings else None
|
best_ring = top_rings[0] if top_rings else None
|
||||||
@ -243,13 +254,14 @@ def fallback_commentary(context: dict[str, Any], language: ReportLanguage | None
|
|||||||
if severity_counts:
|
if severity_counts:
|
||||||
severity = max(severity_counts.items(), key=lambda kv: kv[1])[0]
|
severity = max(severity_counts.items(), key=lambda kv: kv[1])[0]
|
||||||
count = severity_counts.get(severity, 0)
|
count = severity_counts.get(severity, 0)
|
||||||
|
severity_label = s.storm_severity_names.get(str(severity).strip().lower(), str(severity).title())
|
||||||
if s.gemini_write_turkish:
|
if s.gemini_write_turkish:
|
||||||
storm_line = (
|
storm_line = (
|
||||||
f"Toplam {total_cells} fırtına hücresi kaydedilmiş; en fazla {count} hücre {severity} şiddetindedir."
|
f"Toplam {total_cells} fırtına hücresi kaydedilmiş; en fazla {count} hücre {severity_label} şiddetindedir."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
storm_line = (
|
storm_line = (
|
||||||
f"Storm data indicates {total_cells} storm cells, with the highest share in {severity} ({count} cells)."
|
f"Storm data indicates {total_cells} storm cells, with the highest share in {severity_label} ({count} cells)."
|
||||||
)
|
)
|
||||||
elif s.gemini_write_turkish:
|
elif s.gemini_write_turkish:
|
||||||
storm_line = f"Toplam {total_cells} fırtına hücresi kaydedilmiştir."
|
storm_line = f"Toplam {total_cells} fırtına hücresi kaydedilmiştir."
|
||||||
@ -289,13 +301,18 @@ def fallback_commentary(context: dict[str, Any], language: ReportLanguage | None
|
|||||||
f"Fırtına hücrelerinden {storm_near_turbine_count} tanesinin merkezi türbine "
|
f"Fırtına hücrelerinden {storm_near_turbine_count} tanesinin merkezi türbine "
|
||||||
f"{storm_over_threshold_km:.1f} km'den yakın konumlanmıştır. "
|
f"{storm_over_threshold_km:.1f} km'den yakın konumlanmıştır. "
|
||||||
)
|
)
|
||||||
|
elif isinstance(storm_closest_to_centroid_km, (int, float)):
|
||||||
|
storm_interaction_sentence = (
|
||||||
|
f"Tespit edilen fırtına hücreleri herhangi bir türbinin üzerine denk gelmemiş; "
|
||||||
|
f"en yakın hücre merkeze {storm_closest_to_centroid_km:.1f} km mesafeye kadar yaklaşmıştır. "
|
||||||
|
)
|
||||||
elif isinstance(storm_closest_distance_km, (int, float)):
|
elif isinstance(storm_closest_distance_km, (int, float)):
|
||||||
storm_interaction_sentence = (
|
storm_interaction_sentence = (
|
||||||
f"En yakın fırtına hücresi merkezi türbine {storm_closest_distance_km:.1f} km mesafededir. "
|
f"En yakın fırtına hücresi merkezi türbine {storm_closest_distance_km:.1f} km mesafededir. "
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
storm_interaction_sentence = ""
|
storm_interaction_sentence = ""
|
||||||
storm_severity_sentence = storm_line if storm_line else "Bu raporda fırtına hücresi verisi bulunmamaktadır."
|
storm_severity_sentence = storm_line
|
||||||
else:
|
else:
|
||||||
radius_txt = f"within {analysis_radius_km:.1f} km" if isinstance(analysis_radius_km, (int, float)) else ""
|
radius_txt = f"within {analysis_radius_km:.1f} km" if isinstance(analysis_radius_km, (int, float)) else ""
|
||||||
events_txt = f"{total_events} total lightning events" if total_events is not None else "N/A"
|
events_txt = f"{total_events} total lightning events" if total_events is not None else "N/A"
|
||||||
@ -317,13 +334,17 @@ def fallback_commentary(context: dict[str, Any], language: ReportLanguage | None
|
|||||||
storm_interaction_sentence = (
|
storm_interaction_sentence = (
|
||||||
f"Storm interaction: storm-cell centroids came within {storm_over_threshold_km:.1f} km of the turbine (count={storm_near_turbine_count}). "
|
f"Storm interaction: storm-cell centroids came within {storm_over_threshold_km:.1f} km of the turbine (count={storm_near_turbine_count}). "
|
||||||
)
|
)
|
||||||
|
elif isinstance(storm_closest_to_centroid_km, (int, float)):
|
||||||
|
storm_interaction_sentence = (
|
||||||
|
f"Detected storm cells did not pass directly over any turbine; the nearest cell came within {storm_closest_to_centroid_km:.1f} km of the centroid. "
|
||||||
|
)
|
||||||
elif isinstance(storm_closest_distance_km, (int, float)):
|
elif isinstance(storm_closest_distance_km, (int, float)):
|
||||||
storm_interaction_sentence = (
|
storm_interaction_sentence = (
|
||||||
f"Storm interaction: the closest storm-cell centroid came within {storm_closest_distance_km:.1f} km of the turbine. "
|
f"Storm interaction: the closest storm-cell centroid came within {storm_closest_distance_km:.1f} km of the turbine. "
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
storm_interaction_sentence = ""
|
storm_interaction_sentence = ""
|
||||||
storm_severity_sentence = storm_line if storm_line else "Storm severity distribution is not available for this report."
|
storm_severity_sentence = storm_line
|
||||||
|
|
||||||
return (paragraph_intro + turbine_sentence + method_sentence + storm_interaction_sentence + storm_severity_sentence).strip()
|
return (paragraph_intro + turbine_sentence + method_sentence + storm_interaction_sentence + storm_severity_sentence).strip()
|
||||||
|
|
||||||
|
|||||||
@ -37,13 +37,29 @@ class ReportStrings:
|
|||||||
turbine_information: str
|
turbine_information: str
|
||||||
turbine_information_desc: str
|
turbine_information_desc: str
|
||||||
report_summary: str
|
report_summary: str
|
||||||
|
key_findings_heading: str
|
||||||
|
kf_total_events: str
|
||||||
|
kf_innermost: str
|
||||||
|
kf_peak_activity: str
|
||||||
|
kf_strongest_strike: str
|
||||||
|
kf_nearest_strike: str
|
||||||
|
kf_high_risk_turbines: str
|
||||||
|
kf_active_days: str
|
||||||
|
kf_storm_distance: str
|
||||||
|
recommendation_heading: str
|
||||||
|
recommendation_high_plus: str
|
||||||
|
recommendation_routine: str
|
||||||
|
recommendation_no_activity: str
|
||||||
cloud_to_ground_lightnings: str
|
cloud_to_ground_lightnings: str
|
||||||
cloud_to_ground_current_vs_distance: str
|
cloud_to_ground_current_vs_distance: str
|
||||||
cg_chart_title: str
|
cg_chart_title: str
|
||||||
|
cg_current_vs_distance_desc: str
|
||||||
intercloud_lightnings: str
|
intercloud_lightnings: str
|
||||||
intercloud_current_vs_distance: str
|
intercloud_current_vs_distance: str
|
||||||
ic_chart_title: str
|
ic_chart_title: str
|
||||||
|
ic_current_vs_distance_desc: str
|
||||||
turbine_risk_assessment: str
|
turbine_risk_assessment: str
|
||||||
|
turbine_risk_assessment_desc: str
|
||||||
lightning_breakdown_rings: str
|
lightning_breakdown_rings: str
|
||||||
period_total_events: str
|
period_total_events: str
|
||||||
area_covered: str
|
area_covered: str
|
||||||
@ -192,8 +208,13 @@ class ReportStrings:
|
|||||||
map_cg_lightning: str
|
map_cg_lightning: str
|
||||||
map_ic_lightning: str
|
map_ic_lightning: str
|
||||||
map_distance_ring: str
|
map_distance_ring: str
|
||||||
|
map_severity_legend: str
|
||||||
|
storm_severity_names: dict[str, str]
|
||||||
hist_minutes_from_start: str
|
hist_minutes_from_start: str
|
||||||
hist_lightning_count: str
|
hist_lightning_count: str
|
||||||
|
hist_period_title: str
|
||||||
|
hist_cg_legend: str
|
||||||
|
hist_ic_legend: str
|
||||||
storm_cells_day_title: str
|
storm_cells_day_title: str
|
||||||
heatmap_title: str
|
heatmap_title: str
|
||||||
heatmap_subtitle: str
|
heatmap_subtitle: str
|
||||||
@ -223,13 +244,29 @@ _STRINGS_EN = ReportStrings(
|
|||||||
turbine_information="Turbine Information",
|
turbine_information="Turbine Information",
|
||||||
turbine_information_desc="This table contains detailed information about all turbines in the wind farm.",
|
turbine_information_desc="This table contains detailed information about all turbines in the wind farm.",
|
||||||
report_summary="Report Summary",
|
report_summary="Report Summary",
|
||||||
|
key_findings_heading="Key Findings",
|
||||||
|
kf_total_events="Total events: {total} ({cg} cloud-to-ground, {ic} intercloud)",
|
||||||
|
kf_innermost="Events within {radius:.1f} km of the centroid: {count}",
|
||||||
|
kf_peak_activity="Peak activity: {when} ({count} events in one minute)",
|
||||||
|
kf_strongest_strike="Strongest cloud-to-ground strike: {ka:.1f} kA",
|
||||||
|
kf_nearest_strike="Nearest cloud-to-ground strike to a turbine: {distance:.1f} km ({name})",
|
||||||
|
kf_high_risk_turbines="Turbines at High risk or above: {count} of {total}",
|
||||||
|
kf_active_days="Days with recorded activity: {days}",
|
||||||
|
kf_storm_distance="Storm cells detected: {count} (closest {distance:.1f} km from the centroid)",
|
||||||
|
recommendation_heading="Recommendation",
|
||||||
|
recommendation_high_plus="{count} of {total} turbines fall in the High risk category or above. Inspection of the lightning protection systems on these turbines is recommended.",
|
||||||
|
recommendation_routine="No turbines fall in the High risk category or above; routine monitoring is sufficient for this period.",
|
||||||
|
recommendation_no_activity="No lightning activity was recorded within the analysis area during this period.",
|
||||||
cloud_to_ground_lightnings="Cloud-to-Ground Lightnings",
|
cloud_to_ground_lightnings="Cloud-to-Ground Lightnings",
|
||||||
cloud_to_ground_current_vs_distance="Cloud-to-Ground — Current vs Distance",
|
cloud_to_ground_current_vs_distance="Cloud-to-Ground — Current vs Distance",
|
||||||
cg_chart_title="Cloud-to-Ground Lightning — Current vs Distance from Centroid",
|
cg_chart_title="Cloud-to-Ground Lightning — Current and Distance to Centroid",
|
||||||
|
cg_current_vs_distance_desc="The chart below shows the distance to the center point and the current magnitude of the cloud-to-ground events that occurred during the analyzed time period.",
|
||||||
intercloud_lightnings="Intercloud Lightnings",
|
intercloud_lightnings="Intercloud Lightnings",
|
||||||
intercloud_current_vs_distance="Intercloud — Current vs Distance",
|
intercloud_current_vs_distance="Intercloud — Current vs Distance",
|
||||||
ic_chart_title="Intercloud Lightning — Current vs Distance from Centroid",
|
ic_chart_title="Intercloud Lightning — Current and Distance to Centroid",
|
||||||
|
ic_current_vs_distance_desc="The chart below shows the distance to the center point and the current magnitude of the intercloud events that occurred during the analyzed time period.",
|
||||||
turbine_risk_assessment="Turbine Risk Assessment",
|
turbine_risk_assessment="Turbine Risk Assessment",
|
||||||
|
turbine_risk_assessment_desc="You can find the explanation of how the risk assessment is performed in Appendices 1, 2 and 3.",
|
||||||
lightning_breakdown_rings="Lightning Breakdown by Distance Rings",
|
lightning_breakdown_rings="Lightning Breakdown by Distance Rings",
|
||||||
period_total_events="Period: {start} - {end} (Total: {total} lightning events)",
|
period_total_events="Period: {start} - {end} (Total: {total} lightning events)",
|
||||||
area_covered="Area covered within {radius:.1f} km radius: {area:.1f} km²",
|
area_covered="Area covered within {radius:.1f} km radius: {area:.1f} km²",
|
||||||
@ -277,7 +314,7 @@ _STRINGS_EN = ReportStrings(
|
|||||||
alpha_desc="α (distance decay factor): controls how risk decays with distance (smaller = slower decay)",
|
alpha_desc="α (distance decay factor): controls how risk decays with distance (smaller = slower decay)",
|
||||||
included_events="Included events: only cloud-to-ground lightning (p_type = 0)",
|
included_events="Included events: only cloud-to-ground lightning (p_type = 0)",
|
||||||
per_strike_contribution="Per-strike contribution: increases with |I| and decreases exponentially with distance",
|
per_strike_contribution="Per-strike contribution: increases with |I| and decreases exponentially with distance",
|
||||||
turbine_risk_sum="Turbine's risk score: sum of per-strike contributions for all included strikes (typically within the outermost distance ring)",
|
turbine_risk_sum="Turbine's risk score: the sum of per-strike contributions from all cloud-to-ground strikes within the outermost distance ring",
|
||||||
log_transformed="For visualization and reporting, we use the log-transformed score",
|
log_transformed="For visualization and reporting, we use the log-transformed score",
|
||||||
risk_interpretation="2. Risk Score Interpretation",
|
risk_interpretation="2. Risk Score Interpretation",
|
||||||
understanding_risk="Understanding Risk Score Values:",
|
understanding_risk="Understanding Risk Score Values:",
|
||||||
@ -304,7 +341,7 @@ _STRINGS_EN = ReportStrings(
|
|||||||
centroid_bullet_1="Farm centroid is provided by the monitoring workflow and shared by every map and table in this report",
|
centroid_bullet_1="Farm centroid is provided by the monitoring workflow and shared by every map and table in this report",
|
||||||
centroid_bullet_2="Distance rings are drawn from the farm centroid; innermost rings represent the highest proximity risk",
|
centroid_bullet_2="Distance rings are drawn from the farm centroid; innermost rings represent the highest proximity risk",
|
||||||
centroid_bullet_3="Lightning proximity for each strike is measured from the farm centroid using the Haversine formula",
|
centroid_bullet_3="Lightning proximity for each strike is measured from the farm centroid using the Haversine formula",
|
||||||
centroid_bullet_4="The monitoring boundary defines the outer analysis radius — strikes beyond it are excluded",
|
centroid_bullet_4="The analysis is limited to the outermost distance ring; strikes beyond it are excluded from all statistics and charts",
|
||||||
freq_lightning_algo="5. Frequent Lightning Activity Period Detection Algorithm",
|
freq_lightning_algo="5. Frequent Lightning Activity Period Detection Algorithm",
|
||||||
how_period_timespans="How Period Timespans Are Determined:",
|
how_period_timespans="How Period Timespans Are Determined:",
|
||||||
gap_based_algo="The algorithm uses a gap-based approach to identify concentrated lightning activity periods.",
|
gap_based_algo="The algorithm uses a gap-based approach to identify concentrated lightning activity periods.",
|
||||||
@ -371,15 +408,20 @@ _STRINGS_EN = ReportStrings(
|
|||||||
map_longitude="Longitude",
|
map_longitude="Longitude",
|
||||||
map_latitude="Latitude",
|
map_latitude="Latitude",
|
||||||
map_legend="Legend",
|
map_legend="Legend",
|
||||||
map_cg_plane_title="Cloud-to-Ground Lightning - Coordinate Plane View - Central Turbine: {name}",
|
map_cg_plane_title="CG Event Distribution",
|
||||||
map_ic_plane_title="Intercloud Lightning - Coordinate Plane View - Central Turbine: {name}",
|
map_ic_plane_title="IC Event Distribution",
|
||||||
map_wind_turbines="Wind Turbines",
|
map_wind_turbines="Wind Turbines",
|
||||||
map_storm_cells="Storm Cells",
|
map_storm_cells="Storm Cells",
|
||||||
map_cg_lightning="Cloud-to-Ground Lightning",
|
map_cg_lightning="Cloud-to-Ground Lightning",
|
||||||
map_ic_lightning="Intercloud Lightning",
|
map_ic_lightning="Intercloud Lightning",
|
||||||
map_distance_ring="{radius:.1f}km Distance Ring",
|
map_distance_ring="{radius:.1f}km Distance Ring",
|
||||||
|
map_severity_legend="{severity} Severity",
|
||||||
|
storm_severity_names={"high": "High", "medium": "Medium", "low": "Low", "unknown": "Unknown"},
|
||||||
hist_minutes_from_start="Minutes from start",
|
hist_minutes_from_start="Minutes from start",
|
||||||
hist_lightning_count="Lightning Count",
|
hist_lightning_count="Lightning Count",
|
||||||
|
hist_period_title="Date: {date}<br>Period: {period}<br>Total lightnings: {total}",
|
||||||
|
hist_cg_legend="Cloud-to-Ground",
|
||||||
|
hist_ic_legend="Intercloud",
|
||||||
storm_cells_day_title="Storm Cells - {day}",
|
storm_cells_day_title="Storm Cells - {day}",
|
||||||
heatmap_title="Risk Score Heatmap",
|
heatmap_title="Risk Score Heatmap",
|
||||||
heatmap_subtitle="Current Magnitude vs Distance (0.1-{max_km:.1f}km) - Higher values (red) = Higher risk",
|
heatmap_subtitle="Current Magnitude vs Distance (0.1-{max_km:.1f}km) - Higher values (red) = Higher risk",
|
||||||
@ -418,21 +460,37 @@ _STRINGS_TR = ReportStrings(
|
|||||||
turbine_information="Türbin Bilgileri",
|
turbine_information="Türbin Bilgileri",
|
||||||
turbine_information_desc="Bu tablo, rüzgar çiftliğindeki tüm türbinlere ait ayrıntılı bilgileri içerir.",
|
turbine_information_desc="Bu tablo, rüzgar çiftliğindeki tüm türbinlere ait ayrıntılı bilgileri içerir.",
|
||||||
report_summary="Rapor Özeti",
|
report_summary="Rapor Özeti",
|
||||||
|
key_findings_heading="Öne Çıkan Bulgular",
|
||||||
|
kf_total_events="Toplam olay: {total} ({cg} yıldırım, {ic} şimşek)",
|
||||||
|
kf_innermost="Merkeze {radius:.1f} km mesafe içinde: {count} olay",
|
||||||
|
kf_peak_activity="En yoğun an: {when} (bir dakikada {count} olay)",
|
||||||
|
kf_strongest_strike="En güçlü yıldırım darbesi: {ka:.1f} kA",
|
||||||
|
kf_nearest_strike="Bir türbine en yakın yıldırım: {distance:.1f} km ({name})",
|
||||||
|
kf_high_risk_turbines="Yüksek ve üzeri riskli türbin: {total} türbinden {count} adet",
|
||||||
|
kf_active_days="Aktivite görülen gün sayısı: {days}",
|
||||||
|
kf_storm_distance="Tespit edilen fırtına hücresi: {count} (merkeze en yakın {distance:.1f} km)",
|
||||||
|
recommendation_heading="Öneri",
|
||||||
|
recommendation_high_plus="{total} türbinden {count} adedi Yüksek risk ve üzeri sınıfta yer almaktadır. Bu türbinlerin yıldırımdan korunma sistemlerinin incelenmesi önerilir.",
|
||||||
|
recommendation_routine="Yüksek risk ve üzeri sınıfta türbin bulunmamaktadır; bu dönem için rutin izleme yeterlidir.",
|
||||||
|
recommendation_no_activity="Bu dönemde analiz alanında yıldırım-şimşek aktivitesi kaydedilmemiştir.",
|
||||||
cloud_to_ground_lightnings="Yıldırım",
|
cloud_to_ground_lightnings="Yıldırım",
|
||||||
cloud_to_ground_current_vs_distance="Yıldırım — Akım ve Mesafe",
|
cloud_to_ground_current_vs_distance="Yıldırım — Akım ve Mesafe",
|
||||||
cg_chart_title="Şimşek — Merkezden Akım ve Mesafe",
|
cg_chart_title="Yıldırım — Akım ve Merkeze olan Mesafe",
|
||||||
|
cg_current_vs_distance_desc="Aşağıdaki grafik, incelenen zaman dilimi içinde gerçekleşen yıldırımların merkez noktaya mesafesini ve akım miktarını göstermektedir.",
|
||||||
intercloud_lightnings="Şimşek",
|
intercloud_lightnings="Şimşek",
|
||||||
intercloud_current_vs_distance="Şimşek — Akım ve Mesafe",
|
intercloud_current_vs_distance="Şimşek — Akım ve Mesafe",
|
||||||
ic_chart_title="Şimşek — Merkezden Akım ve Mesafe",
|
ic_chart_title="Şimşek — Akım ve Merkeze olan Mesafe",
|
||||||
|
ic_current_vs_distance_desc="Aşağıdaki grafik, incelenen zaman dilimi içinde gerçekleşen şimşeklerin merkez noktaya mesafesini ve akım miktarını göstermektedir.",
|
||||||
turbine_risk_assessment="Türbin Risk Değerlendirmesi",
|
turbine_risk_assessment="Türbin Risk Değerlendirmesi",
|
||||||
|
turbine_risk_assessment_desc="Risk değerlendirmesinin nasıl yapıldığına dair açıklamayı Ek. 1, 2 ve 3'te bulabilirsiniz.",
|
||||||
lightning_breakdown_rings="Mesafe Halkalarına Göre Yıldırım Dağılımı",
|
lightning_breakdown_rings="Mesafe Halkalarına Göre Yıldırım Dağılımı",
|
||||||
period_total_events="Dönem: {start} - {end} (Toplam: {total} yıldırım olayı)",
|
period_total_events="Dönem: {start} - {end} (Toplam: {total} yıldırım-şimşek olayı)",
|
||||||
area_covered="{radius:.1f} km yarıçap içinde kapsanan alan: {area:.1f} km²",
|
area_covered="{radius:.1f} km yarıçap içinde kapsanan alan: {area:.1f} km²",
|
||||||
total_lightnings_radius="{radius:.1f} km yarıçap içindeki toplam yıldırım: {total} olay",
|
total_lightnings_radius="{radius:.1f} km yarıçap içindeki toplam yıldırım-şimşek: {total} olay",
|
||||||
total_density="Toplam yıldırım yoğunluğu: {density:.3f} olay/km²",
|
total_density="Toplam yıldırım-şimşek yoğunluğu: {density:.3f} olay/km²",
|
||||||
density_calc="(Hesaplama: {total} toplam yıldırım / {area:.1f} km² alan)",
|
density_calc="(Hesaplama: {total} toplam yıldırım-şimşek olayı / {area:.1f} km² alan)",
|
||||||
daily_density="Günlük eşdeğer yoğunluk: {density:.3f} olay/km²/gün",
|
daily_density="Günlük eşdeğer yoğunluk: {density:.3f} olay/km²/gün",
|
||||||
daily_density_calc="(Hesaplama: {total} toplam yıldırım / {area:.1f} km² alan / {days_label})",
|
daily_density_calc="(Hesaplama: {total} toplam yıldırım-şimşek olayı / {area:.1f} km² alan / {days_label})",
|
||||||
day_in_period="dönemde 1 gün",
|
day_in_period="dönemde 1 gün",
|
||||||
days_in_period="dönemde {days} gün",
|
days_in_period="dönemde {days} gün",
|
||||||
ring_section_intro="Bu bölüm, analiz dönemindeki yıldırım olaylarını türbinlere olan mesafeye göre gösterir.",
|
ring_section_intro="Bu bölüm, analiz dönemindeki yıldırım olaylarını türbinlere olan mesafeye göre gösterir.",
|
||||||
@ -440,7 +498,7 @@ _STRINGS_TR = ReportStrings(
|
|||||||
ring_item="{ring}: {total} toplam ({cg} yıldırım, {ic} şimşek)",
|
ring_item="{ring}: {total} toplam ({cg} yıldırım, {ic} şimşek)",
|
||||||
frequent_lightning_report="Sık Yıldırım Aktivite Raporu",
|
frequent_lightning_report="Sık Yıldırım Aktivite Raporu",
|
||||||
lightning_activity_overview="Yıldırım Aktivite Genel Bakış:",
|
lightning_activity_overview="Yıldırım Aktivite Genel Bakış:",
|
||||||
histogram_overview="Aşağıdaki grafik, {radius:.1f} km yarıçap içindeki zaman içi yıldırım aktivite örüntülerini göstererek yüksek riskli dönemleri ve yoğun fırtına olaylarını belirlemeye yardımcı olur. Tepe noktaları dikkat gerektiren yoğun yıldırım aktivitesini gösterir.",
|
histogram_overview="Aşağıdaki grafik, {radius:.1f} km yarıçap içindeki yıldırım-şimşek aktivitesinin zaman içindeki dağılımını göstermekte ve yüksek riskli anların belirlenmesine yardımcı olmaktadır.",
|
||||||
histogram_appendix_ref='Ayrıntılı bilgi için ek bölümdeki "Sık Yıldırım Aktivite Dönemi Tespit Algoritması" bölümüne bakınız.',
|
histogram_appendix_ref='Ayrıntılı bilgi için ek bölümdeki "Sık Yıldırım Aktivite Dönemi Tespit Algoritması" bölümüne bakınız.',
|
||||||
frequent_lightning_periods="Sık Yıldırım Aktivite Dönemleri",
|
frequent_lightning_periods="Sık Yıldırım Aktivite Dönemleri",
|
||||||
storm_cells_analysis_summary="Fırtına Hücreleri Analiz Özeti",
|
storm_cells_analysis_summary="Fırtına Hücreleri Analiz Özeti",
|
||||||
@ -463,16 +521,16 @@ _STRINGS_TR = ReportStrings(
|
|||||||
appendix="Ek",
|
appendix="Ek",
|
||||||
risk_calc_method="1. Risk Hesaplama Yöntemi",
|
risk_calc_method="1. Risk Hesaplama Yöntemi",
|
||||||
how_risk_determined="Risk Skorları Nasıl Belirlenir:",
|
how_risk_determined="Risk Skorları Nasıl Belirlenir:",
|
||||||
risk_exposure_1="Bir türbinin risk skoru, analiz döneminde yıldırıma maruz kalmasını temsil eder.",
|
risk_exposure_1="Bir türbinin risk skoru, analiz dönemi boyunca buluttan yere düşen yıldırımlara ne ölçüde maruz kaldığını gösterir.",
|
||||||
risk_exposure_2="Her yıldırım darbesi, türbine olan mesafe ve akım büyüklüğüne göre bir risk katkısı yapar; bu katkılar toplanır.",
|
risk_exposure_2="Her yıldırım darbesi, türbine olan mesafesine ve akım büyüklüğüne bağlı olarak bir risk katkısı sağlar; bu katkıların tümü toplanarak skor elde edilir.",
|
||||||
p0_desc="P0 (temel faktör): her darbenin katkısına uygulanan temel ölçekleme",
|
p0_desc="P0 (temel faktör): her darbenin katkısına uygulanan temel ölçek katsayısı",
|
||||||
current_weight_desc="current_weight: akım büyüklüğünün riski ne kadar artırdığını kontrol eder (büyük = |I| üzerinde daha fazla ağırlık)",
|
current_weight_desc="current_weight: akım büyüklüğünün riski ne kadar artırdığını kontrol eder (büyük = |I| üzerinde daha fazla ağırlık)",
|
||||||
distance_desc="Mesafe (d): türbin-darbe mesafesi (kilometre)",
|
distance_desc="Mesafe (d): türbin-darbe mesafesi (kilometre)",
|
||||||
current_desc="Akım (|I|): mutlak akım büyüklüğü |I| (amper)",
|
current_desc="Akım (|I|): mutlak akım büyüklüğü |I| (amper)",
|
||||||
alpha_desc="α (mesafe azalma faktörü): riskin mesafeyle nasıl azaldığını kontrol eder (küçük = daha yavaş azalma)",
|
alpha_desc="α (mesafe azalma faktörü): riskin mesafeyle nasıl azaldığını kontrol eder (küçük = daha yavaş azalma)",
|
||||||
included_events="Dahil edilen olaylar: yalnızca yıldırım (p_type = 0)",
|
included_events="Dahil edilen olaylar: yalnızca buluttan yere düşen yıldırımlar (p_type = 0)",
|
||||||
per_strike_contribution="Darbe başına katkı: |I| ile artar, mesafe ile üstel olarak azalır",
|
per_strike_contribution="Darbe başına katkı: |I| ile artar, mesafe ile üstel olarak azalır",
|
||||||
turbine_risk_sum="Türbin risk skoru: dahil edilen tüm darbelerin katkılarının toplamı (genellikle en dış mesafe halkası içinde)",
|
turbine_risk_sum="Türbin risk skoru: en dış mesafe halkası içindeki tüm yıldırım (buluttan yere) darbelerinin katkılarının toplamıdır",
|
||||||
log_transformed="Görselleştirme ve raporlama için log dönüşümlü skor kullanılır",
|
log_transformed="Görselleştirme ve raporlama için log dönüşümlü skor kullanılır",
|
||||||
risk_interpretation="2. Risk Skoru Yorumu",
|
risk_interpretation="2. Risk Skoru Yorumu",
|
||||||
understanding_risk="Risk Skoru Değerlerini Anlama:",
|
understanding_risk="Risk Skoru Değerlerini Anlama:",
|
||||||
@ -499,17 +557,17 @@ _STRINGS_TR = ReportStrings(
|
|||||||
centroid_bullet_1="Çiftlik merkezi izleme iş akışı tarafından sağlanır ve bu rapordaki her harita ve tabloda kullanılır",
|
centroid_bullet_1="Çiftlik merkezi izleme iş akışı tarafından sağlanır ve bu rapordaki her harita ve tabloda kullanılır",
|
||||||
centroid_bullet_2="Mesafe halkaları çiftlik merkezinden çizilir; en iç halkalar en yüksek yakınlık riskini temsil eder",
|
centroid_bullet_2="Mesafe halkaları çiftlik merkezinden çizilir; en iç halkalar en yüksek yakınlık riskini temsil eder",
|
||||||
centroid_bullet_3="Her darbenin yakınlığı, çiftlik merkezinden Haversine formülü ile ölçülür",
|
centroid_bullet_3="Her darbenin yakınlığı, çiftlik merkezinden Haversine formülü ile ölçülür",
|
||||||
centroid_bullet_4="İzleme sınırı dış analiz yarıçapını tanımlar — dışındaki darbeler hariç tutulur",
|
centroid_bullet_4="Analiz, en dış mesafe halkası ile sınırlıdır; bu halkanın dışında kalan darbeler tüm istatistik ve grafiklerin dışında bırakılır",
|
||||||
freq_lightning_algo="5. Sık Yıldırım Aktivite Dönemi Tespit Algoritması",
|
freq_lightning_algo="5. Sık Yıldırım Aktivite Dönemi Tespit Algoritması",
|
||||||
how_period_timespans="Dönem Zaman Aralıkları Nasıl Belirlenir:",
|
how_period_timespans="Dönem Zaman Aralıkları Nasıl Belirlenir:",
|
||||||
gap_based_algo="Algoritma, yoğun yıldırım aktivite dönemlerini belirlemek için boşluk tabanlı bir yaklaşım kullanır.",
|
gap_based_algo="Algoritma, yoğun yıldırım aktivitesi dönemlerini belirlemek için olaylar arasındaki zaman boşluklarına dayalı bir yaklaşım kullanır.",
|
||||||
step_by_step="Adım Adım Süreç:",
|
step_by_step="Adım Adım Süreç:",
|
||||||
algo_chronological="Kronolojik Sıralama: Tüm yıldırım olayları zaman damgasına (local_time) göre sıralanır",
|
algo_chronological="Kronolojik Sıralama: Tüm yıldırım olayları zaman damgasına (local_time) göre sıralanır",
|
||||||
algo_gap="Boşluk Hesaplama: Ardışık yıldırım olayları arasındaki zaman farkları hesaplanır",
|
algo_gap="Boşluk Hesaplama: Ardışık yıldırım olayları arasındaki zaman farkları hesaplanır",
|
||||||
algo_period_boundary="Dönem Sınırı Tespiti: Ardışık iki olay arasındaki boşluk {gap} dakikayı aştığında bir dönem biter ve diğeri başlar",
|
algo_period_boundary="Dönem Sınırı Tespiti: Ardışık iki olay arasındaki boşluk {gap} dakikayı aştığında bir dönem biter ve diğeri başlar",
|
||||||
algo_period_validation="Dönem Doğrulama: Yalnızca ≥{min_events} yıldırım olayı içeren dönemler anlamlı kabul edilir",
|
algo_period_validation="Dönem Doğrulama: Yalnızca ≥{min_events} yıldırım olayı içeren dönemler anlamlı kabul edilir",
|
||||||
algo_timespan="Zaman Aralığı Tanımı: Başlangıç = ilk olay; Bitiş = son olay; Süre ilkten sona gerçek süredir",
|
algo_timespan="Zaman Aralığı Tanımı: Başlangıç, dönemdeki ilk olay; bitiş ise son olaydır. Süre, ilk olaydan son olaya kadar geçen gerçek zamandır",
|
||||||
algo_peak="Tepe Alt-Dönem Tespiti: 3 dakikalık hareketli ortalama; aktivite ortalama + 1 standart sapmayı aştığında tepe; histogramda sarı ile vurgulanır",
|
algo_peak="Tepe Alt-Dönem Tespiti: 3 dakikalık hareketli ortalama kullanılır; aktivite, ortalamanın 1 standart sapma üzerine çıktığında tepe dönem olarak kabul edilir ve histogramda sarı ile vurgulanır",
|
||||||
entln_title="6. EarthNetworks Toplam Yıldırım Ağı (ENTLN)",
|
entln_title="6. EarthNetworks Toplam Yıldırım Ağı (ENTLN)",
|
||||||
entln_block_1="Bu rapordaki yıldırım verileri doğrudan EarthNetworks Toplam Yıldırım Ağı'ndan (ENTLN) alınmıştır; hem bulut içi (şimşek) hem buluttan yere (yıldırım) darbeleri izler. ENTLN, küresel ölçekte konuşlandırılan ilk şimşek ve yıldırım tespit ağıdır. 1.500'den fazla sensör içerir.",
|
entln_block_1="Bu rapordaki yıldırım verileri doğrudan EarthNetworks Toplam Yıldırım Ağı'ndan (ENTLN) alınmıştır; hem bulut içi (şimşek) hem buluttan yere (yıldırım) darbeleri izler. ENTLN, küresel ölçekte konuşlandırılan ilk şimşek ve yıldırım tespit ağıdır. 1.500'den fazla sensör içerir.",
|
||||||
entln_block_2="Ulusal Hava Durumu Servisi, Hava Kuvvetleri Hava Durumu Ajansı ve kamu güvenliği, acil müdahale, havaalanları ve enerji kuruluşları gibi birçok kurum ENTLN bilgisine güvenir.",
|
entln_block_2="Ulusal Hava Durumu Servisi, Hava Kuvvetleri Hava Durumu Ajansı ve kamu güvenliği, acil müdahale, havaalanları ve enerji kuruluşları gibi birçok kurum ENTLN bilgisine güvenir.",
|
||||||
@ -566,15 +624,20 @@ _STRINGS_TR = ReportStrings(
|
|||||||
map_longitude="Boylam",
|
map_longitude="Boylam",
|
||||||
map_latitude="Enlem",
|
map_latitude="Enlem",
|
||||||
map_legend="Gösterge",
|
map_legend="Gösterge",
|
||||||
map_cg_plane_title="Yıldırım - Koordinat Düzlemi - Merkez Türbin: {name}",
|
map_cg_plane_title="Yıldırım Dağılımı",
|
||||||
map_ic_plane_title="Şimşek - Koordinat Düzlemi - Merkez Türbin: {name}",
|
map_ic_plane_title="Şimşek Dağılımı",
|
||||||
map_wind_turbines="Rüzgar Türbinleri",
|
map_wind_turbines="Rüzgar Türbinleri",
|
||||||
map_storm_cells="Fırtına Hücreleri",
|
map_storm_cells="Fırtına Hücreleri",
|
||||||
map_cg_lightning="Yıldırım",
|
map_cg_lightning="Yıldırım",
|
||||||
map_ic_lightning="Şimşek",
|
map_ic_lightning="Şimşek",
|
||||||
map_distance_ring="{radius:.1f} km Mesafe Halkası",
|
map_distance_ring="{radius:.1f} km Mesafe Halkası",
|
||||||
hist_minutes_from_start="Başlangıçtan dakika",
|
map_severity_legend="{severity} Şiddet",
|
||||||
|
storm_severity_names={"high": "Yüksek", "medium": "Orta", "low": "Düşük", "unknown": "Bilinmeyen"},
|
||||||
|
hist_minutes_from_start="Başlangıçtan itibaren geçen dakika",
|
||||||
hist_lightning_count="Yıldırım Sayısı",
|
hist_lightning_count="Yıldırım Sayısı",
|
||||||
|
hist_period_title="Tarih: {date}<br>Dönem: {period}<br>Toplam yıldırım-şimşek: {total}",
|
||||||
|
hist_cg_legend="Yıldırım",
|
||||||
|
hist_ic_legend="Şimşek",
|
||||||
storm_cells_day_title="Fırtına Hücreleri - {day}",
|
storm_cells_day_title="Fırtına Hücreleri - {day}",
|
||||||
heatmap_title="Risk Skoru Isı Haritası",
|
heatmap_title="Risk Skoru Isı Haritası",
|
||||||
heatmap_subtitle="Akım Büyüklüğü ve Mesafe (0.1-{max_km:.1f} km) - Yüksek değerler (kırmızı) = Yüksek risk",
|
heatmap_subtitle="Akım Büyüklüğü ve Mesafe (0.1-{max_km:.1f} km) - Yüksek değerler (kırmızı) = Yüksek risk",
|
||||||
|
|||||||
@ -130,11 +130,14 @@ def format_period_display_for_report(start_value: Optional[str], end_value: Opti
|
|||||||
|
|
||||||
def get_analysis_radius_m() -> int:
|
def get_analysis_radius_m() -> int:
|
||||||
from .config import config
|
from .config import config
|
||||||
|
rings = config.distance_rings or []
|
||||||
|
outermost_ring = int(max(rings)) if rings else 0
|
||||||
boundary = config.analysis_boundary_m
|
boundary = config.analysis_boundary_m
|
||||||
if isinstance(boundary, (int, float)) and boundary > 0:
|
if isinstance(boundary, (int, float)) and boundary > 0:
|
||||||
|
if outermost_ring > 0:
|
||||||
|
return min(int(boundary), outermost_ring)
|
||||||
return int(boundary)
|
return int(boundary)
|
||||||
rings = config.distance_rings or []
|
return outermost_ring
|
||||||
return int(max(rings)) if rings else 0
|
|
||||||
|
|
||||||
def get_turbine_color_by_fixed_intervals(risk_log_value: float) -> str:
|
def get_turbine_color_by_fixed_intervals(risk_log_value: float) -> str:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -170,7 +170,7 @@ def create_storm_cells_coordinate_plane(storm_data: List[Dict], turbine_df: pd.D
|
|||||||
mode='lines',
|
mode='lines',
|
||||||
line=dict(color=color, width=4),
|
line=dict(color=color, width=4),
|
||||||
opacity=0.6,
|
opacity=0.6,
|
||||||
name=f'{radius/1000:.0f}km Ring',
|
name=s.map_distance_ring.format(radius=radius / 1000),
|
||||||
showlegend=True
|
showlegend=True
|
||||||
))
|
))
|
||||||
|
|
||||||
@ -195,7 +195,7 @@ def create_storm_cells_coordinate_plane(storm_data: List[Dict], turbine_df: pd.D
|
|||||||
text=turbine_df['name'].tolist(),
|
text=turbine_df['name'].tolist(),
|
||||||
textfont=dict(size=12, color='black'),
|
textfont=dict(size=12, color='black'),
|
||||||
textposition='middle center',
|
textposition='middle center',
|
||||||
name='Wind Turbines',
|
name=s.map_wind_turbines,
|
||||||
showlegend=True,
|
showlegend=True,
|
||||||
hovertemplate=(
|
hovertemplate=(
|
||||||
"<b>Wind Turbine</b><br>"
|
"<b>Wind Turbine</b><br>"
|
||||||
@ -274,7 +274,9 @@ def create_storm_cells_coordinate_plane(storm_data: List[Dict], turbine_df: pd.D
|
|||||||
y=[None],
|
y=[None],
|
||||||
mode='lines',
|
mode='lines',
|
||||||
line=dict(color=color, width=3),
|
line=dict(color=color, width=3),
|
||||||
name=f"{severity.title()} Severity",
|
name=s.map_severity_legend.format(
|
||||||
|
severity=s.storm_severity_names.get(severity, severity.title())
|
||||||
|
),
|
||||||
showlegend=True,
|
showlegend=True,
|
||||||
hoverinfo='skip' # No hover for legend entries
|
hoverinfo='skip' # No hover for legend entries
|
||||||
))
|
))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user