import logging logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) import gradio as gr from gradio_calendar import Calendar from gematria import calculate_gematria, strip_diacritics from datetime import datetime, timedelta import json import inflect import os import tempfile # --- Helper Functions --- def calculate_gematria_sum(text): """Calculates the gematria sum of a given text.""" if text: text_gematria = calculate_gematria(strip_diacritics(text)) return text_gematria return None def number_to_ordinal_word(number): ordinal_dict = { 1: "first", 2: "second", 3: "third", 4: "fourth", 5: "fifth", 6: "sixth", 7: "seventh", 8: "eighth", 9: "ninth", 10: "tenth", 11: "eleventh", 12: "twelfth", 13: "thirteenth", 14: "fourteenth", 15: "fifteenth", 16: "sixteenth", 17: "seventeenth", 18: "eighteenth", 19: "nineteenth", 20: "twentieth", 21: "twentyfirst", 22: "twentysecond", 23: "twentythird", 24: "twentyfourth", 25: "twentyfifth", 26: "twentysixth", 27: "twentyseventh", 28: "twentyeighth", 29: "twentyninth", 30: "thirtieth", 31: "thirtyfirst" } return ordinal_dict.get(number, "") def date_to_words(date_string): """Converts a date in YYYY-MM-DD format to English words.""" inf_engine = inflect.engine() date_obj = datetime.strptime(date_string, "%Y-%m-%d") return format_date(date_obj, inf_engine) def month_year_to_words(date_string): """Converts a month and year in YYYY-MM format to English words.""" inf_engine = inflect.engine() date_obj = datetime.strptime(date_string, "%Y-%m") return format_date(date_obj, inf_engine) def format_date(date_obj, inf_engine): """Formats day-month-year into English words with ordinal day.""" year = date_obj.year if 1100 <= year <= 1999: year_words = f"{inf_engine.number_to_words(year // 100, andword='')} hundred" if year % 100 != 0: year_words += f" {inf_engine.number_to_words(year % 100, andword='')}" else: year_words = inf_engine.number_to_words(year, andword='') year_formatted = year_words.replace(',', '') month = date_obj.strftime("%B") # Tag auslesen und in Ordinalform umwandeln day = date_obj.day day_ordinal = number_to_ordinal_word(day) return f"{day_ordinal} {month} {year_formatted}" def format_year_to_words(year): """Formats a year as English words.""" inf_engine = inflect.engine() if 1100 <= year <= 1999: year_words = f"{inf_engine.number_to_words(year // 100, andword='')} hundred" if year % 100 != 0: year_words += f" {inf_engine.number_to_words(year % 100, andword='')}" else: year_words = inf_engine.number_to_words(year, andword='') return year_words.replace(',', '') def perform_gematria_calculation_for_date_range(start_date, end_date): """Performs gematria calculation for each day in a date range and groups by sum.""" logger.debug(f"Start Date: {start_date}, End Date: {end_date}") results = {} delta = timedelta(days=1) current_date = start_date processed_months = set() # To track processed month-year combinations processed_years = set() # To track processed years while current_date <= end_date: # 1) Full date calculation date_string = current_date.strftime("%Y-%m-%d") date_words = date_to_words(date_string) gematria_sum = calculate_gematria_sum(date_words) if gematria_sum not in results: results[gematria_sum] = [] results[gematria_sum].append({"date": date_string, "date_words": date_words}) # 2) Month+Year calculation (only once per unique month-year) month_year_key = current_date.strftime("%Y-%m") if month_year_key not in processed_months: processed_months.add(month_year_key) month_year_words = f"{current_date.strftime('%B')} {format_year_to_words(current_date.year)}" month_year_gematria_sum = calculate_gematria_sum(month_year_words) if month_year_gematria_sum not in results: results[month_year_gematria_sum] = [] results[month_year_gematria_sum].append({ "date": month_year_key, "date_words": month_year_words }) # 3) Year-only calculation (only once per unique year) year_key = str(current_date.year) if year_key not in processed_years: processed_years.add(year_key) year_words = format_year_to_words(current_date.year) year_gematria_sum = calculate_gematria_sum(year_words) if year_gematria_sum not in results: results[year_gematria_sum] = [] results[year_gematria_sum].append({ "date": year_key, "date_words": year_words }) current_date += delta return results # --- Event Handlers --- def generate_json_output(results, start_date, end_date, include_words, min_results): """ Erzeugt ein Dictionary (später für gr.JSON) mit Filterung nach Mindestanzahl, wenn min_results nicht None ist. """ output = { "DateRange": { "StartDate": start_date.strftime("%Y-%m-%d"), "EndDate": end_date.strftime("%Y-%m-%d") }, "Results": [] } for gematria_sum, entries in results.items(): # Falls eine Mindestanzahl gefordert ist, filtern if min_results is not None and len(entries) < min_results: continue group = { "GematriaSum": gematria_sum, "Entries": [] } for entry in entries: entry_data = {"date": entry["date"]} if include_words: entry_data["date_words"] = entry["date_words"] group["Entries"].append(entry_data) output["Results"].append(group) return output # <-- Wichtig: Als Dictionary zurückgeben def perform_calculation(start_date, end_date, include_words, min_results): """ Führt die Gematria-Berechnung durch und gibt ein Dictionary zurück, das direkt an gr.JSON übergeben werden kann. """ results = perform_gematria_calculation_for_date_range(start_date, end_date) return generate_json_output(results, start_date, end_date, include_words, min_results) def download_json(json_data, start_date, end_date): """ Nimmt das JSON-Dict (aus gr.JSON) und legt es als Datei ab, deren Pfad wir zurückgeben. Ältere Gradio-Versionen erwarten hier typischerweise einen String (also File-Pfad), keinen Tuple oder dict. """ # Einen Dateinamen konstruieren – der taucht nur intern auf: filename = f"gematria_{start_date.strftime('%Y%m%d')}_{end_date.strftime('%Y%m%d')}.json" # Dictionary -> JSON-String json_string = json.dumps(json_data, indent=4, ensure_ascii=False) # Temporäre Datei erstellen # delete=False => Datei bleibt bestehen, bis wir sie manuell entfernen wollen temp = tempfile.NamedTemporaryFile(suffix=".json", delete=False) temp_path = temp.name # Pfad merken try: # Schreiben als Bytes temp.write(json_string.encode("utf-8")) temp.flush() finally: # Datei-Handle schließen temp.close() # Jetzt returnen wir den Pfad # => Gradio wird damit umgehen können und bietet Download an return temp_path # --- Main Gradio App --- with gr.Blocks() as app: with gr.Row(): start_date = Calendar(type="datetime", label="Start Date") end_date = Calendar(type="datetime", label="End Date") with gr.Row(): include_date_words = gr.Checkbox(value=True, label="Include Date-Words in JSON") filter_results = gr.Checkbox(value=False, label="Filter to sums with at least") min_results_input = gr.Number(value=2, label="results", interactive=True, precision=0) calculate_btn = gr.Button("Calculate Gematria for Date Range") json_output = gr.JSON(label="JSON Output") download_btn = gr.Button("Download JSON") json_file = gr.File(label="Downloaded JSON") # Damit wir den Wert von filter_results berücksichtigen können: def handle_calculate(start_val, end_val, include_val, filter_val, min_val): if not filter_val: # Checkbox nicht gesetzt # => Kein Filtern, also min_results=None return perform_calculation(start_val, end_val, include_val, None) else: # => Filter aktiviert, also min_results = min_val # min_val kann float sein; auf int casten, falls gewünscht return perform_calculation(start_val, end_val, include_val, int(min_val)) # Button-Klick: wir rufen handle_calculate auf, # das ggf. min_results auf None setzt oder den Wert übernimmt. calculate_btn.click( handle_calculate, inputs=[start_date, end_date, include_date_words, filter_results, min_results_input], outputs=[json_output], api_name="calculate_gematria" ) # Hier der "fix" im Download-Button download_btn.click( download_json, # unsere neue Funktion inputs=[json_output, start_date, end_date], outputs=[json_file] # gr.File-Element ) filter_results.change( lambda checked: gr.update(interactive=checked), inputs=filter_results, outputs=min_results_input ) if __name__ == "__main__": app.launch(share=False)