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)
|
[](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 pathlib import Path
|
||||||
|
|
||||||
from fastapi import FastAPI, HTTPException
|
from fastapi import FastAPI, HTTPException
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse, Response
|
||||||
|
|
||||||
from .fehler import AbbruchFehler
|
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")
|
_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 --
|
# In-memory Job-Store für Stufe 2 (LLM, läuft im Thread). Schlicht gehalten --
|
||||||
# ein lokales Single-User-Werkzeug.
|
# ein lokales Single-User-Werkzeug.
|
||||||
|
|
@ -46,6 +47,12 @@ def index() -> str:
|
||||||
return _HTML
|
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")
|
@app.get("/api/modelle")
|
||||||
def api_modelle(q: str = "") -> list[dict]:
|
def api_modelle(q: str = "") -> list[dict]:
|
||||||
"""Modell-Liste fürs Dropdown. Ohne Suche: das EMPFOHLENE Default-Modell
|
"""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="color-scheme" content="light dark" />
|
||||||
<meta name="theme-color" content="#0e5d42" media="(prefers-color-scheme: light)" />
|
<meta name="theme-color" content="#0e5d42" media="(prefers-color-scheme: light)" />
|
||||||
<meta name="theme-color" content="#13120d" media="(prefers-color-scheme: dark)" />
|
<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>
|
<style>
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
Farb-Tokens — AAA-verifiziert (jedes Text-Paar >=7:1,
|
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);
|
.appbar-inner { max-width:1100px; margin:0 auto; height:100%; padding:0 var(--s6);
|
||||||
display:flex; align-items:center; gap:var(--s3); }
|
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; }
|
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 {
|
.brand .logo {
|
||||||
width:34px; height:34px; border-radius:10px; flex:none; display:grid; place-items:center;
|
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%));
|
background:linear-gradient(145deg,var(--brand),color-mix(in srgb,var(--brand) 70%, #000 12%));
|
||||||
|
|
@ -339,9 +345,17 @@
|
||||||
|
|
||||||
<header class="appbar">
|
<header class="appbar">
|
||||||
<div class="appbar-inner">
|
<div class="appbar-inner">
|
||||||
<a class="brand" href="#" aria-label="Angebots-Übersicht — Start">
|
<a class="brand" href="#" aria-label="MABOTO — Marktbeobachtungstool, zum Anfang">
|
||||||
<span class="logo" aria-hidden="true">🛒</span>
|
<span class="logo" aria-hidden="true">
|
||||||
<span class="word">Angebote</span>
|
<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>
|
</a>
|
||||||
<span class="place leer" id="appbarPlz" aria-live="polite">PLZ …</span>
|
<span class="place leer" id="appbarPlz" aria-live="polite">PLZ …</span>
|
||||||
<span class="spacer"></span>
|
<span class="spacer"></span>
|
||||||
|
|
@ -350,11 +364,12 @@
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="intro">
|
<div class="intro">
|
||||||
<p class="eyebrow">Ortskonkret · händlerübergreifend · belegt</p>
|
<p class="eyebrow">MABOTO · ortskonkret · händlerübergreifend · belegt</p>
|
||||||
<h1>Angebots-Übersicht</h1>
|
<h1>MABOTO</h1>
|
||||||
<p>Zwei strikt getrennte Stufen: zuerst die Rohdaten <b>deterministisch</b> holen
|
<p><b>Marktbeobachtungstool.</b> Zwei strikt getrennte Stufen: zuerst die Rohdaten
|
||||||
und speichern (kein LLM, kein Key), danach erst per LLM in Produktgruppen
|
<b>deterministisch</b> holen und speichern (kein LLM, kein Key), danach erst per
|
||||||
einordnen. Jedes Angebot ist belegt — kein Auffüllen, Unsicheres ist markiert.</p>
|
LLM in Produktgruppen einordnen. Jedes Angebot ist belegt — kein Auffüllen,
|
||||||
|
Unsicheres ist markiert.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,15 @@ def test_index_liefert_html():
|
||||||
client = TestClient(web.app)
|
client = TestClient(web.app)
|
||||||
r = client.get("/")
|
r = client.get("/")
|
||||||
assert r.status_code == 200
|
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):
|
def test_api_modelle_gibt_top_free(monkeypatch):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue