k96beni's picture
Rename app (3).py to app.py
b234c30 verified
import os
import json
import time
import requests
import gspread
from google.oauth2.service_account import Credentials
import pandas as pd
from datetime import datetime, timedelta
import pytz
import schedule
import threading
import gradio as gr
# ============================================================================
# KONFIGURATION
# ============================================================================
SWEDISH_TZ = pytz.timezone('Europe/Stockholm')
# Google Sheets setup
SCOPES = [
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive'
]
# ============================================================================
# GOOGLE SHEETS ANSLUTNING
# ============================================================================
def get_google_sheets_client():
"""Anslut till Google Sheets med service account credentials."""
try:
# Hämta credentials från Hugging Face Secrets
google_credentials_json = os.environ.get('GOOGLE_CREDENTIALS')
if not google_credentials_json:
raise ValueError("GOOGLE_CREDENTIALS saknas i Hugging Face Secrets")
# Parse JSON credentials
creds_dict = json.loads(google_credentials_json)
# Skapa credentials objekt
creds = Credentials.from_service_account_info(creds_dict, scopes=SCOPES)
# Skapa gspread client
gc = gspread.authorize(creds)
print("✅ Google Sheets-anslutning etablerad")
return gc
except Exception as e:
print(f"❌ Fel vid anslutning till Google Sheets: {e}")
return None
def get_sheet_data(gc):
"""Hämta data från Google Sheet."""
try:
# Öppna spreadsheet
spreadsheet = gc.open("Omrade_updater")
# Hämta data från huvudflik
main_sheet = spreadsheet.worksheet("Sheet1")
main_data = pd.DataFrame(main_sheet.get_all_records())
# Hämta loggdata
try:
logs_sheet = spreadsheet.worksheet("Omrade_updater_LOGS")
logs_data = pd.DataFrame(logs_sheet.get_all_records())
except gspread.exceptions.WorksheetNotFound:
print("⚠️ LOGS sheet hittades inte, skapar tom DataFrame")
logs_data = pd.DataFrame()
print(f"✅ Hämtade {len(main_data)} rader från huvudsheet")
print(f"✅ Hämtade {len(logs_data)} loggrader")
return main_data, logs_data, spreadsheet
except Exception as e:
print(f"❌ Fel vid hämtning av sheet-data: {e}")
return None, None, None
# ============================================================================
# METRICS-BERÄKNINGAR
# ============================================================================
def calculate_login_metrics(logs_df):
"""Beräkna inloggningsmetrics för olika tidsperioder."""
if logs_df.empty:
return {
'last_24h': 0,
'last_3_days': 0,
'last_7_days': 0,
'unique_companies_24h': [],
'unique_companies_3d': [],
'unique_companies_7d': []
}
try:
# Filtrera bara LOGIN events
login_logs = logs_df[logs_df['Event Type'] == 'LOGIN'].copy()
if login_logs.empty:
return {
'last_24h': 0,
'last_3_days': 0,
'last_7_days': 0,
'unique_companies_24h': [],
'unique_companies_3d': [],
'unique_companies_7d': []
}
# Parse timestamp kolumn
login_logs['timestamp_parsed'] = pd.to_datetime(
login_logs['Timestamp'],
format='%Y-%m-%d %H:%M:%S',
errors='coerce'
)
# Beräkna cutoff-tider
now = datetime.now(SWEDISH_TZ)
cutoff_24h = now - timedelta(hours=24)
cutoff_3d = now - timedelta(days=3)
cutoff_7d = now - timedelta(days=7)
# Gör timestamps timezone-aware
login_logs['timestamp_parsed'] = login_logs['timestamp_parsed'].dt.tz_localize(SWEDISH_TZ, ambiguous='infer')
# Filtrera per tidsperiod
logins_24h = login_logs[login_logs['timestamp_parsed'] >= cutoff_24h]
logins_3d = login_logs[login_logs['timestamp_parsed'] >= cutoff_3d]
logins_7d = login_logs[login_logs['timestamp_parsed'] >= cutoff_7d]
# Räkna unika användare (User ID = Account ID)
unique_24h = logins_24h['User ID'].nunique()
unique_3d = logins_3d['User ID'].nunique()
unique_7d = logins_7d['User ID'].nunique()
# Hämta företagsnamn för de som loggat in
companies_24h = logins_24h['Company Name'].dropna().unique().tolist()
companies_3d = logins_3d['Company Name'].dropna().unique().tolist()
companies_7d = logins_7d['Company Name'].dropna().unique().tolist()
return {
'last_24h': unique_24h,
'last_3_days': unique_3d,
'last_7_days': unique_7d,
'unique_companies_24h': companies_24h,
'unique_companies_3d': companies_3d,
'unique_companies_7d': companies_7d
}
except Exception as e:
print(f"❌ Fel vid beräkning av login metrics: {e}")
return {
'last_24h': 0,
'last_3_days': 0,
'last_7_days': 0,
'unique_companies_24h': [],
'unique_companies_3d': [],
'unique_companies_7d': []
}
def calculate_edit_metrics(logs_df):
"""Beräkna redigeringsmetrics - Excel upload vs manuella ändringar."""
if logs_df.empty:
return {
'excel_uploads_24h': 0,
'excel_uploads_7d': 0,
'manual_edits_24h': 0,
'manual_edits_7d': 0,
'excel_companies': [],
'manual_companies': []
}
try:
# Filtrera UPLOAD och EDIT events
upload_logs = logs_df[logs_df['Event Type'] == 'UPLOAD'].copy()
edit_logs = logs_df[logs_df['Event Type'] == 'EDIT'].copy()
# Parse timestamps
if not upload_logs.empty:
upload_logs['timestamp_parsed'] = pd.to_datetime(
upload_logs['Timestamp'],
format='%Y-%m-%d %H:%M:%S',
errors='coerce'
)
upload_logs['timestamp_parsed'] = upload_logs['timestamp_parsed'].dt.tz_localize(SWEDISH_TZ, ambiguous='infer')
if not edit_logs.empty:
edit_logs['timestamp_parsed'] = pd.to_datetime(
edit_logs['Timestamp'],
format='%Y-%m-%d %H:%M:%S',
errors='coerce'
)
edit_logs['timestamp_parsed'] = edit_logs['timestamp_parsed'].dt.tz_localize(SWEDISH_TZ, ambiguous='infer')
# Beräkna cutoff-tider
now = datetime.now(SWEDISH_TZ)
cutoff_24h = now - timedelta(hours=24)
cutoff_7d = now - timedelta(days=7)
# Räkna uploads
if not upload_logs.empty:
uploads_24h = len(upload_logs[upload_logs['timestamp_parsed'] >= cutoff_24h])
uploads_7d = len(upload_logs[upload_logs['timestamp_parsed'] >= cutoff_7d])
excel_companies = upload_logs[upload_logs['timestamp_parsed'] >= cutoff_7d]['Company Name'].dropna().unique().tolist()
else:
uploads_24h = 0
uploads_7d = 0
excel_companies = []
# Räkna manuella editeringar
if not edit_logs.empty:
edits_24h = len(edit_logs[edit_logs['timestamp_parsed'] >= cutoff_24h])
edits_7d = len(edit_logs[edit_logs['timestamp_parsed'] >= cutoff_7d])
manual_companies = edit_logs[edit_logs['timestamp_parsed'] >= cutoff_7d]['Company Name'].dropna().unique().tolist()
else:
edits_24h = 0
edits_7d = 0
manual_companies = []
return {
'excel_uploads_24h': uploads_24h,
'excel_uploads_7d': uploads_7d,
'manual_edits_24h': edits_24h,
'manual_edits_7d': edits_7d,
'excel_companies': excel_companies,
'manual_companies': manual_companies
}
except Exception as e:
print(f"❌ Fel vid beräkning av edit metrics: {e}")
return {
'excel_uploads_24h': 0,
'excel_uploads_7d': 0,
'manual_edits_24h': 0,
'manual_edits_7d': 0,
'excel_companies': [],
'manual_companies': []
}
def calculate_completion_status(main_df):
"""Beräkna hur många företag som fyllt i all data."""
if main_df.empty:
return {
'total_companies': 0,
'completed_companies': 0,
'completion_rate': 0,
'missing_fields': {}
}
try:
# Definiera obligatoriska fält
required_fields = ['Namn', 'Email adress', 'Telefon', 'Tillgänglighet']
# Räkna unika företag
total_companies = main_df['Account ID'].nunique()
# Räkna företag som fyllt i alla fält
completed = 0
missing_fields = {}
for account_id in main_df['Account ID'].unique():
company_data = main_df[main_df['Account ID'] == account_id]
# Kolla om alla required fields är ifyllda för alla områden
all_complete = True
for field in required_fields:
if field in company_data.columns:
empty_count = company_data[field].isna().sum() + (company_data[field] == '').sum()
if empty_count > 0:
all_complete = False
if field not in missing_fields:
missing_fields[field] = 0
missing_fields[field] += 1
if all_complete:
completed += 1
completion_rate = (completed / total_companies * 100) if total_companies > 0 else 0
return {
'total_companies': total_companies,
'completed_companies': completed,
'completion_rate': round(completion_rate, 1),
'missing_fields': missing_fields
}
except Exception as e:
print(f"❌ Fel vid beräkning av completion status: {e}")
return {
'total_companies': 0,
'completed_companies': 0,
'completion_rate': 0,
'missing_fields': {}
}
# ============================================================================
# SLACK INTEGRATION
# ============================================================================
def send_to_slack(subject, content, color="#2a9d8f"):
"""Skicka meddelande till Slack via webhook."""
webhook_url = os.environ.get("SLACK_WEBHOOK_URL")
if not webhook_url:
print("❌ SLACK_WEBHOOK_URL saknas i Hugging Face Secrets")
return False
try:
payload = {
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": subject
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": content
}
}
]
}
response = requests.post(
webhook_url,
json=payload,
headers={"Content-Type": "application/json"}
)
if response.status_code == 200:
print(f"✅ Slack-meddelande skickat: {subject}")
return True
else:
print(f"❌ Slack-anrop misslyckades: {response.status_code}, {response.text}")
return False
except Exception as e:
print(f"❌ Fel vid sändning till Slack: {e}")
return False
# ============================================================================
# HUVUDFUNKTION - GENERERA DAGLIG RAPPORT
# ============================================================================
def generate_daily_report():
"""Generera och skicka daglig rapport till Slack."""
try:
print(f"\n{'='*60}")
print(f"📊 Startar daglig rapport - {datetime.now(SWEDISH_TZ).strftime('%Y-%m-%d %H:%M:%S')}")
print(f"{'='*60}\n")
# Anslut till Google Sheets
gc = get_google_sheets_client()
if not gc:
send_to_slack(
"⚠️ ChargeNode Migration - Fel",
"Kunde inte ansluta till Google Sheets. Kontrollera credentials.",
"#ff0000"
)
return
# Hämta data
main_df, logs_df, spreadsheet = get_sheet_data(gc)
if main_df is None or logs_df is None:
send_to_slack(
"⚠️ ChargeNode Migration - Fel",
"Kunde inte hämta data från Google Sheets.",
"#ff0000"
)
return
# Beräkna metrics
login_metrics = calculate_login_metrics(logs_df)
edit_metrics = calculate_edit_metrics(logs_df)
completion_metrics = calculate_completion_status(main_df)
# Bygg Slack-meddelande
now = datetime.now(SWEDISH_TZ)
subject = f"📊 ChargeNode Migration - Daglig Rapport {now.strftime('%Y-%m-%d')}"
content = f"""
*God morgon! Här är dagens migrationsstatistik* ☕
━━━━━━━━━━━━━━━━━━━━━━━━━━━
*👥 INLOGGNINGAR*
━━━━━━━━━━━━━━━━━━━━━━━━━━━
• *Senaste 24h:* {login_metrics['last_24h']} unika företag
• *Senaste 3 dagar:* {login_metrics['last_3_days']} unika företag
• *Senaste 7 dagar:* {login_metrics['last_7_days']} unika företag
━━━━━━━━━━━━━━━━━━━━━━━━━━━
*✏️ UPPDATERINGAR*
━━━━━━━━━━━━━━━━━━━━━━━━━━━
*Excel-uppladdningar:*
• Senaste 24h: {edit_metrics['excel_uploads_24h']} st
• Senaste 7 dagar: {edit_metrics['excel_uploads_7d']} st
*Manuella ändringar i matris:*
• Senaste 24h: {edit_metrics['manual_edits_24h']} editeringar
• Senaste 7 dagar: {edit_metrics['manual_edits_7d']} editeringar
━━━━━━━━━━━━━━━━━━━━━━━━━━━
*📋 KOMPLETTERINGSSTATUS*
━━━━━━━━━━━━━━━━━━━━━━━━━━━
• *Totalt företag:* {completion_metrics['total_companies']} st
• *Komplett ifyllda:* {completion_metrics['completed_companies']} st
• *Kompletteringsgrad:* {completion_metrics['completion_rate']}%
"""
# Lägg till företag som loggat in senaste 24h
if login_metrics['unique_companies_24h']:
content += f"\n*Företag som loggat in senaste 24h:*\n"
for company in login_metrics['unique_companies_24h'][:10]: # Max 10 företag
content += f"• {company}\n"
if len(login_metrics['unique_companies_24h']) > 10:
content += f"_...och {len(login_metrics['unique_companies_24h']) - 10} till_\n"
# Lägg till företag som laddat upp Excel
if edit_metrics['excel_companies']:
content += f"\n*Företag som använt Excel-upload (senaste 7d):*\n"
for company in edit_metrics['excel_companies'][:5]:
content += f"• {company}\n"
if len(edit_metrics['excel_companies']) > 5:
content += f"_...och {len(edit_metrics['excel_companies']) - 5} till_\n"
content += f"\n━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
content += f"_Rapport genererad: {now.strftime('%Y-%m-%d %H:%M:%S')}_"
# Skicka till Slack
success = send_to_slack(subject, content, "#2a9d8f")
if success:
print("\n✅ Daglig rapport skickad till Slack framgångsrikt!\n")
else:
print("\n❌ Kunde inte skicka rapport till Slack\n")
return success
except Exception as e:
print(f"\n❌ FEL vid generering av daglig rapport: {e}\n")
send_to_slack(
"⚠️ ChargeNode Migration - Kritiskt Fel",
f"Ett fel uppstod vid generering av daglig rapport:\n```{str(e)}```",
"#ff0000"
)
return False
# ============================================================================
# SCHEMALÄGGNING
# ============================================================================
def run_scheduler():
"""Kör schemaläggaren i en separat tråd."""
# Schemalägg daglig rapport kl 09:00 svensk tid
schedule.every().day.at("09:00").do(generate_daily_report)
print(f"\n⏰ Scheduler startad - Daglig rapport körs kl 09:00 svensk tid")
print(f" Nästa körning: {schedule.next_run()}\n")
while True:
schedule.run_pending()
time.sleep(60) # Kolla varje minut
# Starta scheduler i bakgrundstråd
scheduler_thread = threading.Thread(target=run_scheduler, daemon=True)
scheduler_thread.start()
# ============================================================================
# GRADIO UI (minimal för att hålla Space:n aktiv)
# ============================================================================
def manual_trigger():
"""Manuell trigger för att testa rapporten."""
success = generate_daily_report()
if success:
return "✅ Rapport skickad till Slack!"
else:
return "❌ Något gick fel. Kolla loggarna."
with gr.Blocks(title="ChargeNode Migration Reporter") as app:
gr.Markdown("""
# 📊 ChargeNode Migration - Daglig Rapport
Denna applikation kör automatiskt varje dag kl **09:00 svensk tid** och skickar en rapport till Slack-kanalen **#ai-chat**.
Rapporten innehåller:
- 👥 Inloggningsstatistik (24h, 3 dagar, 7 dagar)
- ✏️ Uppdateringar via Excel vs manuellt
- 📋 Kompletteringsstatus för företagen
""")
with gr.Row():
trigger_btn = gr.Button("🔄 Kör rapport manuellt (test)", size="lg")
output = gr.Textbox(label="Status", lines=3)
trigger_btn.click(manual_trigger, outputs=output)
gr.Markdown(f"""
---
**Status:** 🟢 Aktiv
**Nästa schemalagd körning:** {schedule.next_run().strftime('%Y-%m-%d %H:%M:%S') if schedule.next_run() else 'Väntar på initiering...'}
**Tidszon:** Europe/Stockholm (CET/CEST)
""")
# Skicka en initial rapport när appen startar (för test)
print("\n🚀 Skickar initial testrapport vid start...\n")
time.sleep(5) # Vänta lite för att systemet ska bli klart
generate_daily_report()
if __name__ == "__main__":
app.launch(server_name="0.0.0.0", server_port=7860)