Files
bitcoin/bitcoin_programm_handoff.py
T

386 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import json
import os
import subprocess
import sys
from pathlib import Path
from urllib.request import Request, urlopen
from urllib.error import HTTPError, URLError
REPO_PATH = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else Path.cwd()
GITEA_BASE = os.environ.get("GITEA_BASE", "https://gitea.schaefinik.xyz")
GITEA_TOKEN = os.environ.get("GITEA_TOKEN", "").strip()
FILES = {
"README.md": """# bitcoin-programm
Lokales Projekt zur strukturierten Erfassung und Verwaltung von:
- To-dos
- Kalendereinträgen
- Dokumentation
- später: Bitcoin-Transaktionen und Auswertungen
## Ziel
Das Projekt dient als zentrales Arbeitsverzeichnis für persönliche Verwaltung und spätere Automatisierung.
Geplant ist:
- eine einfache Eingabemaske für To-dos und Kalendertermine
- CSV als einfache Zwischen- und Exportform
- später ein SQLite-Backend für Auswertung und Dashboard
- optional Sync mit Google Calendar oder iCloud Kalender
## Aktueller Fokus
Der aktuelle Fokus liegt auf:
1. Dokumentation
2. To-do-Erfassung
3. Kalender-Erfassung
4. saubere Datenstruktur für späteren Import nach SQLite
Bitcoin-Tracking kann später ergänzt werden.
""",
"docs/00_overview.md": """# Überblick
Dieses Projekt ist ein lokales Arbeits- und Dokumentationsprojekt für persönliche Organisation.
## Zweck
Das Projekt soll folgende Bereiche abdecken:
- To-dos
- Kalendertermine
- Projektdokumentation
- später Bitcoin-Tracking
- später SQLite-basierte Auswertung
## Leitprinzipien
1. Daten sauber strukturieren
2. Eingaben einfach halten
3. CSV als Zwischenformat nutzbar machen
4. später SQL-Backend ergänzen
5. lokale Kontrolle behalten
## Arbeitsweise
### 1. Erfassung
Neue Einträge werden über eine Eingabemaske erfasst.
### 2. Speicherung
Die Daten werden in CSV abgelegt und später in SQLite überführt.
## Zielbild
Am Ende soll das Projekt enthalten:
- eine einfache Eingabemaske
- eine saubere Dokumentation
- exportierbare Daten
- ein lokales Dashboard
- Kalenderanbindung
- To-do-Verwaltung
- später Bitcoin-Verwaltung
## Status
Der Fokus liegt aktuell auf Dokumentation, Kalender und To-do. Bitcoin-Tracking wird später ergänzt.
""",
"docs/01_requirements.md": """# Anforderungen
## Hauptziel
Das Projekt soll eine einfache und verlässliche Verwaltung von To-dos und Kalenderterminen ermöglichen.
## Funktionale Anforderungen
### To-dos
- To-dos sollen über eine Eingabemaske erfasst werden
- Pflichtfelder müssen vorhanden sein
- feste Werte sollen per Dropdown gewählt werden
- die Daten sollen als CSV exportierbar sein
### Kalender
- Kalendertermine sollen über eine Eingabemaske erfasst werden
- Datum und Uhrzeit müssen wählbar sein
- Terminarten sollen per Dropdown auswählbar sein
- Wiederholungen sollen unterstützt werden
- Erinnerungen sollen vorbereitet sein
### Datenhaltung
- CSV als Zwischenspeicher
- später SQLite als Datenbasis
- klare Trennung von Rohdaten und verarbeiteten Daten
## Nicht-funktionale Anforderungen
- lokale Nutzung ohne Cloud-Zwang
- später synchronisierbar mit Google Calendar oder iCloud
- leicht verständlich und wartbar
- importfreundlich für SQLite
- dokumentierbar und versionierbar
## UI-Anforderungen
Die Eingabemaske soll enthalten:
- Pflichtfelder
- Dropdowns
- Datumsauswahl
- Uhrzeitauswahl
- Textfelder für Notizen
- klare Trennung zwischen To-do und Kalender
## Datenqualität
- einheitliche Schreibweisen
- feste Statuswerte
- saubere Datumsformate
- keine freien Sonderformate in Pflichtfeldern
- keine Mischfelder für unterschiedliche Datentypen
""",
"docs/02_todo_calendar.md": """# To-do- und Kalenderkonzept
## Ziel
Dieses Projekt soll eine einfache Möglichkeit bieten, Aufgaben und Termine zentral zu erfassen.
## To-do-Felder
- id
- titel
- beschreibung
- kategorie
- prioritaet
- status
- faellig_am
- wiederholen
- notiz
## Kalender-Felder
- id
- titel
- datum
- uhrzeit
- dauer_min
- kalender
- kategorie
- erinnerung_min
- wiederholen
- notiz
## Dropdown-Werte
### Priorität
- Niedrig
- Mittel
- Hoch
### Status
- Offen
- In Arbeit
- Wartet
- Erledigt
### Wiederholen
- Nein
- Täglich
- Wöchentlich
- Monatlich
### Kalender
- Arbeit
- Privat
- Bitcoin
- Todo
### Kategorie
- Allgemein
- Dokumentation
- Erinnerung
- Termin
- Aufgabe
- Sonstiges
## Eingaberegeln
- Pflichtfelder müssen ausgefüllt sein
- freie Texte nur dort, wo sie sinnvoll sind
- Datum und Uhrzeit strikt getrennt
- Wiederholungen nur aus erlaubten Werten
- Kalenderziel muss auswählbar sein
## Workflow
1. Eintrag über Maske anlegen
2. Daten als CSV speichern
3. später nach SQLite importieren
4. optional an Google Calendar oder iCloud übergeben
""",
"sqlite/schema.sql": """PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS todo (
id INTEGER PRIMARY KEY AUTOINCREMENT,
titel TEXT NOT NULL,
beschreibung TEXT,
kategorie TEXT NOT NULL DEFAULT 'Allgemein',
prioritaet TEXT NOT NULL DEFAULT 'Mittel',
status TEXT NOT NULL DEFAULT 'Offen',
faellig_am TEXT,
wiederholen TEXT NOT NULL DEFAULT 'Nein',
notiz TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS calendar_event (
id INTEGER PRIMARY KEY AUTOINCREMENT,
titel TEXT NOT NULL,
datum TEXT NOT NULL,
uhrzeit TEXT NOT NULL,
dauer_min INTEGER NOT NULL DEFAULT 60,
kalender TEXT NOT NULL DEFAULT 'Privat',
kategorie TEXT NOT NULL DEFAULT 'Sonstiges',
erinnerung_min INTEGER NOT NULL DEFAULT 30,
wiederholen TEXT NOT NULL DEFAULT 'Nein',
notiz TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_todo_faellig_am ON todo(faellig_am);
CREATE INDEX IF NOT EXISTS idx_todo_status ON todo(status);
CREATE INDEX IF NOT EXISTS idx_calendar_event_datum ON calendar_event(datum);
CREATE INDEX IF NOT EXISTS idx_calendar_event_kalender ON calendar_event(kalender);
""",
"requirements.txt": """streamlit
pandas
"""
}
APP_PY = """import streamlit as st
import pandas as pd
from pathlib import Path
from datetime import date, time
DATA_DIR = Path("data")
TODO_FILE = DATA_DIR / "todo.csv"
CALENDAR_FILE = DATA_DIR / "calendar.csv"
DATA_DIR.mkdir(parents=True, exist_ok=True)
TODO_COLUMNS = [
"id","titel","beschreibung","kategorie","prioritaet","status",
"faellig_am","wiederholen","notiz"
]
CALENDAR_COLUMNS = [
"id","titel","datum","uhrzeit","dauer_min","kalender","kategorie",
"erinnerung_min","wiederholen","notiz"
]
def load_csv(path: Path, columns: list[str]) -> pd.DataFrame:
if path.exists():
df = pd.read_csv(path)
for col in columns:
if col not in df.columns:
df[col] = ""
return df[columns]
return pd.DataFrame(columns=columns)
def save_csv(df: pd.DataFrame, path: Path) -> None:
df.to_csv(path, index=False)
def next_id(df: pd.DataFrame) -> int:
if df.empty or "id" not in df.columns:
return 1
try:
return int(df["id"].max()) + 1
except Exception:
return 1
st.set_page_config(page_title="Bitcoin-Programm", layout="wide")
st.title("Bitcoin-Programm – To-do & Kalender")
tab1, tab2, tab3 = st.tabs(["To-do anlegen", "Kalendertermin anlegen", "Daten ansehen"])
with tab1:
st.subheader("Neues To-do")
todo_df = load_csv(TODO_FILE, TODO_COLUMNS)
new_todo_id = next_id(todo_df)
with st.form("todo_form", clear_on_submit=True):
titel = st.text_input("Titel")
beschreibung = st.text_area("Beschreibung")
kategorie = st.selectbox("Kategorie", ["Allgemein", "Bitcoin", "Dokumentation", "Kalender", "Admin", "Sonstiges"])
prioritaet = st.selectbox("Priorität", ["Niedrig", "Mittel", "Hoch"])
status = st.selectbox("Status", ["Offen", "In Arbeit", "Wartet", "Erledigt"])
faellig_am = st.date_input("Fällig am", value=date.today())
wiederholen = st.selectbox("Wiederholen", ["Nein", "Täglich", "Wöchentlich", "Monatlich"])
notiz = st.text_area("Notiz")
submitted = st.form_submit_button("To-do speichern")
if submitted:
if not titel.strip():
st.error("Bitte mindestens einen Titel eingeben.")
else:
new_row = pd.DataFrame([{
"id": new_todo_id,
"titel": titel,
"beschreibung": beschreibung,
"kategorie": kategorie,
"prioritaet": prioritaet,
"status": status,
"faellig_am": faellig_am.isoformat(),
"wiederholen": wiederholen,
"notiz": notiz,
}])
todo_df = pd.concat([todo_df, new_row], ignore_index=True)
save_csv(todo_df, TODO_FILE)
st.success("To-do gespeichert.")
with tab2:
st.subheader("Neuen Kalendereintrag")
cal_df = load_csv(CALENDAR_FILE, CALENDAR_COLUMNS)
new_cal_id = next_id(cal_df)
with st.form("calendar_form", clear_on_submit=True):
titel = st.text_input("Titel")
datum = st.date_input("Datum", value=date.today())
uhrzeit = st.time_input("Uhrzeit", value=time(9, 0))
dauer_min = st.number_input("Dauer in Minuten", min_value=5, max_value=1440, value=60, step=5)
kalender = st.selectbox("Kalender", ["Arbeit", "Privat", "Bitcoin", "Todo"])
kategorie = st.selectbox("Kategorie", ["Meeting", "Aufgabe", "Erinnerung", "Termin", "Sonstiges"])
erinnerung_min = st.number_input("Erinnerung vorab in Minuten", min_value=0, max_value=10080, value=30, step=5)
wiederholen = st.selectbox("Wiederholen", ["Nein", "Täglich", "Wöchentlich", "Monatlich"])
notiz = st.text_area("Notiz")
submitted = st.form_submit_button("Kalendertermin speichern")
if submitted:
if not titel.strip():
st.error("Bitte mindestens einen Titel eingeben.")
else:
new_row = pd.DataFrame([{
"id": new_cal_id,
"titel": titel,
"datum": datum.isoformat(),
"uhrzeit": uhrzeit.strftime("%H:%M"),
"dauer_min": int(dauer_min),
"kalender": kalender,
"kategorie": kategorie,
"erinnerung_min": int(erinnerung_min),
"wiederholen": wiederholen,
"notiz": notiz,
}])
cal_df = pd.concat([cal_df, new_row], ignore_index=True)
save_csv(cal_df, CALENDAR_FILE)
st.success("Kalendertermin gespeichert.")
with tab3:
st.subheader("Gespeicherte Daten")
col1, col2 = st.columns(2)
with col1:
st.markdown("### To-dos")
todo_df = load_csv(TODO_FILE, TODO_COLUMNS)
st.dataframe(todo_df, use_container_width=True)
st.download_button("To-do CSV herunterladen", data=todo_df.to_csv(index=False).encode("utf-8"), file_name="todo.csv", mime="text/csv")
with col2:
st.markdown("### Kalender")
cal_df = load_csv(CALENDAR_FILE, CALENDAR_COLUMNS)
st.dataframe(cal_df, use_container_width=True)
st.download_button("Kalender CSV herunterladen", data=cal_df.to_csv(index=False).encode("utf-8"), file_name="calendar.csv", mime="text/csv")
"""
def run(cmd, check=True, capture=True):
return subprocess.run(cmd, cwd=REPO_PATH, check=check, text=True, capture_output=capture)
def git(*args, check=True):
return run(["git", *args], check=check)
def write_file(path, content):
full = REPO_PATH / path
full.parent.mkdir(parents=True, exist_ok=True)
full.write_text(content, encoding="utf-8")
print(f"OK wrote {path}")
def get_fetch_remote():
remotes = git("remote", "-v").stdout.strip().splitlines()
if not remotes:
raise SystemExit("No git remotes configured")
for line in remotes:
parts = line.split()
if len(parts) >= 3 and parts[2] == "(fetch)":
return parts[0], parts[1]
parts = remotes[0].split()
return parts[0], parts[1]
def get_repo_owner_name():
_, remote_url = get_fetch_remote()
if remote_url.endswith(".git"):
remote_url = remote_url[:-4]
if "://" in remote_url:
tail = remote_url.split("://", 1)[1]
tail = tail.split("/", 1)[1]
else:
tail = remote_url.split(":", 1)[1]
owner, repo = tail.split("/", 1)
return owner, repo
def main():
if not REPO_PATH.exists():
raise SystemExit(f"Repo path not found: {REPO_PATH}")
if not (REPO_PATH / ".git").exists():
raise SystemExit(f"No .git directory in {REPO_PATH}")
print("Repo path:", REPO_PATH)
print("Branch:", git("branch", "--show-current").stdout.strip())
print("Status:\n" + (git("status", "--short").stdout.strip() or "(clean)"))
print("Remotes:\n" + git("remote", "-v").stdout.strip())
remote_name, remote_url = get_fetch_remote()
print(f"Using remote: {remote_name} -> {remote_url}")
owner, repo = get_repo_owner_name()
print(f"Detected repo: {owner}/{repo}")
for path, content in FILES.items():
write_file(path, content)
write_file("app.py", APP_PY)
py = subprocess.run([sys.executable, "-m", "py_compile", "app.py"], cwd=REPO_PATH, text=True, capture_output=True)
if py.returncode != 0:
print(py.stdout)
print(py.stderr)
raise SystemExit("app.py syntax check failed")
git("add", "README.md", "docs", "sqlite", "requirements.txt", "app.py")
if git("diff", "--cached", "--quiet", check=False).returncode == 0:
print("No changes to commit.")
else:
git("commit", "-m", "docs: add todo-calendar workflow and streamlit form")
print("Committed.")
branch = git("branch", "--show-current").stdout.strip()
git("push", remote_name, branch)
print("Pushed to origin remote:", remote_name)
if __name__ == "__main__":
main()