Branding: App heißt MABOTO (Marktbeobachtungstool) + Icon/Favicon
- Eigenes Icon: Lupe über aufsteigenden Preis-Balken (Beobachtung + Markt) in Markengrün, als skalierbares SVG. Favicon via /favicon.svg-Route (image/svg+xml, gecached) + Inline-Motiv im App-Bar-Logo. - App-Bar-Wortmarke „MABOTO / Marktbeobachtung", Seitentitel, Intro-Kopf und FastAPI-App-Titel umbenannt; README-H1 + Doku-Screenshots neu. - Tests: Index prüft jetzt auf MABOTO, neuer favicon.svg-Test (77 grün). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
aed664dc16
commit
4b833f3785
7 changed files with 62 additions and 14 deletions
|
|
@ -1,4 +1,4 @@
|
|||
# Angebots-Übersicht
|
||||
# MABOTO — Marktbeobachtungstool
|
||||
|
||||
[](https://github.com/Jeuners/timopro/actions/workflows/tests.yml)
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 403 KiB After Width: | Height: | Size: 408 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 317 KiB After Width: | Height: | Size: 321 KiB |
|
|
@ -23,13 +23,14 @@ import uuid
|
|||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.responses import HTMLResponse, Response
|
||||
|
||||
from .fehler import AbbruchFehler
|
||||
|
||||
app = FastAPI(title="Angebots-Übersicht")
|
||||
app = FastAPI(title="MABOTO — Marktbeobachtungstool")
|
||||
|
||||
_HTML = (Path(__file__).parent / "web_static" / "index.html").read_text("utf-8")
|
||||
_FAVICON = (Path(__file__).parent / "web_static" / "favicon.svg").read_text("utf-8")
|
||||
|
||||
# In-memory Job-Store für Stufe 2 (LLM, läuft im Thread). Schlicht gehalten --
|
||||
# ein lokales Single-User-Werkzeug.
|
||||
|
|
@ -46,6 +47,12 @@ def index() -> str:
|
|||
return _HTML
|
||||
|
||||
|
||||
@app.get("/favicon.svg")
|
||||
def favicon() -> Response:
|
||||
return Response(content=_FAVICON, media_type="image/svg+xml",
|
||||
headers={"Cache-Control": "public, max-age=86400"})
|
||||
|
||||
|
||||
@app.get("/api/modelle")
|
||||
def api_modelle(q: str = "") -> list[dict]:
|
||||
"""Modell-Liste fürs Dropdown. Ohne Suche: das EMPFOHLENE Default-Modell
|
||||
|
|
|
|||
18
src/angebote/web_static/favicon.svg
Normal file
18
src/angebote/web_static/favicon.svg
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="MABOTO">
|
||||
<defs>
|
||||
<linearGradient id="mb" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#22936b"/>
|
||||
<stop offset="1" stop-color="#0c5238"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="32" height="32" rx="8" fill="url(#mb)"/>
|
||||
<g transform="translate(3.4,3.4)">
|
||||
<!-- Preis-Balken (Marktdaten) im Sichtfeld -->
|
||||
<rect x="7.0" y="9.6" width="1.8" height="3.8" rx="0.6" fill="#fff"/>
|
||||
<rect x="9.1" y="7.7" width="1.8" height="5.7" rx="0.6" fill="#fff"/>
|
||||
<rect x="11.2" y="5.8" width="1.8" height="7.6" rx="0.6" fill="#fff"/>
|
||||
<!-- Lupe (Beobachtung) -->
|
||||
<circle cx="10" cy="10" r="6.4" fill="none" stroke="#fff" stroke-width="2.0"/>
|
||||
<path d="M14.8 14.8 L20.4 20.4" stroke="#fff" stroke-width="2.7" stroke-linecap="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 875 B |
|
|
@ -6,7 +6,10 @@
|
|||
<meta name="color-scheme" content="light dark" />
|
||||
<meta name="theme-color" content="#0e5d42" media="(prefers-color-scheme: light)" />
|
||||
<meta name="theme-color" content="#13120d" media="(prefers-color-scheme: dark)" />
|
||||
<title>Angebots-Übersicht</title>
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
|
||||
<link rel="apple-touch-icon" href="favicon.svg" />
|
||||
<title>MABOTO — Marktbeobachtungstool</title>
|
||||
<meta name="description" content="MABOTO — Marktbeobachtungstool: ortskonkrete, händlerübergreifende Übersicht der wöchentlichen Angebote, nach Produktgruppen." />
|
||||
<style>
|
||||
/* ============================================================
|
||||
Farb-Tokens — AAA-verifiziert (jedes Text-Paar >=7:1,
|
||||
|
|
@ -72,8 +75,11 @@
|
|||
}
|
||||
.appbar-inner { max-width:1100px; margin:0 auto; height:100%; padding:0 var(--s6);
|
||||
display:flex; align-items:center; gap:var(--s3); }
|
||||
.brand { display:flex; align-items:center; gap:10px; font-weight:800; font-size:17px;
|
||||
.brand { display:flex; align-items:center; gap:11px; font-weight:800; font-size:17px;
|
||||
letter-spacing:-.02em; color:var(--ink); text-decoration:none; }
|
||||
.brand .word { display:flex; flex-direction:column; line-height:1.04; }
|
||||
.brand .word small { font-size:10px; font-weight:700; letter-spacing:.1em;
|
||||
text-transform:uppercase; color:var(--muted); margin-top:1px; }
|
||||
.brand .logo {
|
||||
width:34px; height:34px; border-radius:10px; flex:none; display:grid; place-items:center;
|
||||
background:linear-gradient(145deg,var(--brand),color-mix(in srgb,var(--brand) 70%, #000 12%));
|
||||
|
|
@ -339,9 +345,17 @@
|
|||
|
||||
<header class="appbar">
|
||||
<div class="appbar-inner">
|
||||
<a class="brand" href="#" aria-label="Angebots-Übersicht — Start">
|
||||
<span class="logo" aria-hidden="true">🛒</span>
|
||||
<span class="word">Angebote</span>
|
||||
<a class="brand" href="#" aria-label="MABOTO — Marktbeobachtungstool, zum Anfang">
|
||||
<span class="logo" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" width="21" height="21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="6.2" y="9.0" width="1.7" height="3.6" rx="0.5" fill="#fff"/>
|
||||
<rect x="8.1" y="7.2" width="1.7" height="5.4" rx="0.5" fill="#fff"/>
|
||||
<rect x="10.0" y="5.4" width="1.7" height="7.2" rx="0.5" fill="#fff"/>
|
||||
<circle cx="9.2" cy="9.2" r="6.1" fill="none" stroke="#fff" stroke-width="1.9"/>
|
||||
<path d="M13.7 13.7 L18.6 18.6" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="word">MABOTO<small>Marktbeobachtung</small></span>
|
||||
</a>
|
||||
<span class="place leer" id="appbarPlz" aria-live="polite">PLZ …</span>
|
||||
<span class="spacer"></span>
|
||||
|
|
@ -350,11 +364,12 @@
|
|||
</header>
|
||||
|
||||
<div class="intro">
|
||||
<p class="eyebrow">Ortskonkret · händlerübergreifend · belegt</p>
|
||||
<h1>Angebots-Übersicht</h1>
|
||||
<p>Zwei strikt getrennte Stufen: zuerst die Rohdaten <b>deterministisch</b> holen
|
||||
und speichern (kein LLM, kein Key), danach erst per LLM in Produktgruppen
|
||||
einordnen. Jedes Angebot ist belegt — kein Auffüllen, Unsicheres ist markiert.</p>
|
||||
<p class="eyebrow">MABOTO · ortskonkret · händlerübergreifend · belegt</p>
|
||||
<h1>MABOTO</h1>
|
||||
<p><b>Marktbeobachtungstool.</b> Zwei strikt getrennte Stufen: zuerst die Rohdaten
|
||||
<b>deterministisch</b> holen und speichern (kein LLM, kein Key), danach erst per
|
||||
LLM in Produktgruppen einordnen. Jedes Angebot ist belegt — kein Auffüllen,
|
||||
Unsicheres ist markiert.</p>
|
||||
</div>
|
||||
|
||||
<div class="wrap">
|
||||
|
|
|
|||
|
|
@ -51,7 +51,15 @@ def test_index_liefert_html():
|
|||
client = TestClient(web.app)
|
||||
r = client.get("/")
|
||||
assert r.status_code == 200
|
||||
assert "Angebots-Übersicht" in r.text
|
||||
assert "MABOTO" in r.text
|
||||
|
||||
|
||||
def test_favicon_liefert_svg():
|
||||
client = TestClient(web.app)
|
||||
r = client.get("/favicon.svg")
|
||||
assert r.status_code == 200
|
||||
assert r.headers["content-type"].startswith("image/svg+xml")
|
||||
assert "<svg" in r.text
|
||||
|
||||
|
||||
def test_api_modelle_gibt_top_free(monkeypatch):
|
||||
|
|
|
|||
Loading…
Reference in a new issue