03 - Data Flow (jak wszystko się rusza)¶
Dwa główne flowy: CAPTURE PIPELINE (screenshot/text/audio → classify → route) i VOICE CONVERSATION (Konrad gada z Kony).
Flow 1: Capture Pipeline¶
KONRAD
│ (Flutter apka: tap orb=Conv, tap +=action_sheet=capture,
│ albo share intent z Chrome/Photos/WhatsApp)
▼
Flutter POST capture.chcedointernetu.pl/api/capture
│ FormData: {screenshot|audio|text, title, url, source_app, timestamp_iso}
│ Header: X-Capture-Secret
│
│ (CF edge sprawdza secret, jesli OK -> ctx.waitUntil(processCapture))
│ Response ~50ms: {status: "processing", capture_id}
│
▼
CF Worker: processCapture (background)
│
├─ 1. transcribeAudio (jesli audio) -> voice.chcedointernetu.pl/whisper
│ (WAŻNE: jesli KonyAI voice conversation, uzywamy /converse w brain,
│ ale tu w /api/capture uzywamy prostszego whisper transcribe)
│
├─ 2. enrichTextWithUrlPreview (jesli URL w text)
│ -> voice.chcedointernetu.pl/enrich/text (kony-brain /enrich/text)
│
├─ 3. uploadToDrive (screenshot lub audio) - opcjonalne
│ -> Google Drive API z OAuth refresh token
│ Wynik: drive_url
│
├─ 4. classifyIntent (Vision LLM)
│ Provider: env.VISION_PROVIDER
│ - "qwen" (default): POST qwen-vl.chcedointernetu.pl/v1/chat/completions
│ (Qwen2.5-VL-7B-Instruct-AWQ, multimodal)
│ - "claude" (fallback): POST api.anthropic.com/v1/messages
│ (Sonnet 4.6, multimodal, dla failoveru)
│ Prompt: "Jestes Kony, sklasyfikuj capture na intent + short_title + analysis
│ + wypelnij invoice_fields/task_fields/contact_fields per intent"
│ Wynik: {intent, confidence, short_title, analysis, suggestion, *_fields}
│
├─ 5. commitToGithub (markdown snapshot capture do konrad-brain repo)
│ Format: second-brain/konrad-brain/INBOX/z-komorki/YYYY-MM-DD-{title}.md
│ Zawiera: original text/image link, klasyfikacje, links do CRM
│ Wynik: github_md_raw_url (przydaje sie do Kony Hub referencji)
│
├─ 6. Router dispatch (per intent):
│ ├─ FAKTURA -> routeToKSeF (jesli PL) albo routeToFlexyCRM (jesli zagraniczna)
│ ├─ TASK -> routeToFlexyCRM (project match przez task_fields.client_hint + project_hint)
│ ├─ KONTAKT -> routeToFlexyCRM (create contact person)
│ ├─ IDEA/BUG/NOTATKA -> tylko Kony Hub (bez FlexyCRM)
│
├─ 7. pushToKonyHub (ZAWSZE - kazdy capture ma trafic do Hub)
│ POST crm.chcedointernetu.pl/api/kony/captures
│ Body: {capture_id, captured_at, source_app, intent, confidence,
│ short_title, original_text, agent_suggestion, llm_analysis,
│ drive_url, github_md_raw_url, ksef_id, flexy_task_id, ...}
│ Wynik: {id: N, created: true} -> Kony #N
│
└─ 8. ntfy push notification -> ntfy.chcedointernetu.pl/kony
Message: "Kony #N: {short_title} -> {intent}"
URL: link do Kony Hub UI
Latency total (typowe): 3-8s (Vision LLM to najwolniejsze - Qwen VL ~3-5s, Claude Sonnet ~2-4s).
Konrad widzi: instant "Wyslane!" screen w apce (bo Worker zwraca 200 od razu), ~30-60s później push notification na phone gdy Worker skonczy processCapture.
Flow 2: Voice Conversation¶
KONRAD (Flutter, Home → tap aureola → ConversationScreen)
│
│ Naciśnij orb (Tap mode) albo przytrzymaj (Hold mode)
│ → nagrywa audio (mic AAC 16kHz mono)
▼
Flutter POST voice.chcedointernetu.pl/converse
│ FormData: {audio: WAV/AAC, history: [prev turns]}
│
▼
kony-brain :8000 (przez tunel)
│
├─ 1. Whisper STT
│ POST localhost:8010/v1/audio/transcriptions
│ Wynik: user_text (transkrypcja PL)
│
├─ 2. RAG search (auto-inject)
│ POST localhost:8018/embed - embed user_text
│ Cosine similarity vs in-memory index (kony-brain trzyma embeddings
│ wszystkich captures z Kony Hub, backfill przy startup)
│ Wynik: top-3 relevant captures z similarity > 0.72
│
├─ 3. Qwen 32B orchestrator (z tools + RAG context)
│ POST localhost:8001/v1/chat/completions
│ messages = [
│ {role: 'system', content: QWEN_SYSTEM_PROMPT + RAG_CONTEXT},
│ ...history,
│ {role: 'user', content: user_text}
│ ]
│ tools = [27 tools registry z tools.py]
│
│ Qwen decyduje:
│ A) reply direct (text) - odpowiedz bez tool call
│ B) tool_call - wywoła jeden z 27 tools:
│ - search_captures_semantic (RAG dla dodatkowego kontekstu)
│ - google_calendar_today/this_week/next_event (przez n8n Kony Bridge)
│ - gmail_recent/search
│ - homeassistant_turn_on/off/speak
│ - flexycrm_create_task/find_project/etc.
│ - save_conversation_capture (zapisz turn do Kony Hub)
│
│ Jesli tool_call: kony-brain wykonuje przez bridge_tools.py (webhook do n8n)
│ albo bezposrednio (FlexyCRM REST, RAG search)
│ Wynik tool -> back to Qwen jako function_result, Qwen finalizuje reply
│
├─ 4. Bielik-guard safety check (opcjonalny)
│ POST localhost:8002/classify - reply_text
│ Jesli safety_score < 0.7 -> zablokuj, wyślij fallback message
│
├─ 5. Bielik-11B polish (dla naturalnego PL brzmienia)
│ POST localhost:8000/v1/chat/completions
│ Prompt: "Przeformułuj po polsku naturalnie, nieformalnie:"
│ Wynik: polish_reply_text
│
├─ 6. XTTS voice clone
│ POST localhost:8017/tts
│ Body: {text: polish_reply_text, voice_reference: konrad.wav}
│ Wynik: WAV bytes (glos Konrada)
│
└─ 7. Response
{
user_text: "...", # transcript
reply_text: "...", # tekst pokazany w apce
audio_bytes: base64 WAV, # audio do odtworzenia
tool_calls: [...], # co Qwen zrobił (dla debug)
rag_context: [...], # jakie captures Qwen widzial
}
Flutter otrzymuje response, gra audio (just_audio), wyswietla transcript w chat bubbles.
Latency total: 1-3s dla prostego reply (bez tool call), 3-8s dla tool_call flow (dodatkowy round-trip do n8n / FlexyCRM).
Flow 3: Widget "Rozmowa z Konym" (bypass Home)¶
Home screen Android → tap widget "Rozmowa" (kony_widget_conversation)
│
▼
Android WidgetProvider -> Intent z action='conversation' -> MainActivity
│
▼
Flutter main() sprawdza getInitialAction PRZED runApp
│ Jesli initialAction=='conversation':
│
▼
KonyApp build z routes: {'/': Home, '/conversation': Conv}, initialRoute='/conversation'
│ Flutter tworzy stack: [Home, Conversation]
│ User widzi natychmiast Conversation, back button -> Home
│
▼
ConversationScreen loaded, mic permission, gotowy do rozmowy
Dodane 2026-09-01: initial route bypass Home flash. Wcześniej Home się renderował 300ms przed push Conversation.
Flow 4: SALDEO Faktury (autonomiczne, cron)¶
n8n cron @HH:00:50 (co godzine) - workflow Aa71Vkjx49JPVX0e
│
├─ 1. Gmail search
│ Query: 'has:attachment (subject:invoice OR faktura OR receipt OR rechnung OR fattura)
│ newer_than:5d -label:SALDEO-* -from:allegro.pl -from:leaselink.pl
│ -from:morele.net -from:tygrys.net
│ -from:ratunku@... -to:viking_potatoes@... -in:sent'
│ (Ostatnie exclude broni przed feedback loop)
│
├─ 2. Loop po mailach - Split In Batches
│
├─ 3. Gmail get message + attachments (osobny node bo getAll nie downloaduje attachmentow)
│
├─ 4. Extract text (JS Code) - build qwen_input z subject + sender + body + attachments
│
├─ 5. Qwen 32B classify invoice
│ POST 10.0.0.114:8011/v1/chat/completions (uwaga: NIE :8001, dedykowany port n8n->Qwen)
│ System prompt: "Jestes ekstraktorem danych faktur zagranicznych..."
│ Wynik JSON: {is_invoice, confidence, invoice_date, amount, currency,
│ supplier_name, is_foreign, is_ksef_covered}
│
├─ 6. BDH shadow classify (dodane 2026-09-01, task #94)
│ POST bdh.chcedointernetu.pl/classify
│ Wynik: {predicted_intent: FAKTURA|OTHER, confidence, probs}
│ (Nie zmienia decyzji, tylko logujemy do Kony Hub llm_analysis)
│
├─ 7. Doklej response do meta - Set node
│ choices (Qwen), bdh_predicted, bdh_confidence, __gmail_msg_id, __subject, etc.
│
├─ 8. Rule engine (JS Code) - decyzja:
│ - qwen_parsed.is_invoice + is_foreign + !is_ksef_covered → SEND
│ - qwen_parsed.confidence < 0.7 → MANUAL (label review)
│ - reszta → SKIP (label skipped)
│
├─ 9. Switch po decyzji
│ ├─ SEND branch:
│ │ ├─ Gmail send do viking_potatoes@dok.saldeo.pl z załącznikami (attachmentsBinary)
│ │ ├─ Gmail label SALDEO-sent
│ │ └─ HTTP push Kony Hub (log capture z BDH info w llm_analysis)
│ ├─ MANUAL: Gmail label 'SALDEO-review'
│ └─ SKIP: Gmail label 'SALDEO-skipped'
│
└─ (loop next mail)
Obecny stan (2026-09-01): source_app=faktury-saldeo-dryrun (DRY-RUN mode, log do Hub, nie wysyla realnie do SALDEO - test przed prod push).
Flow 5: DMARC Watchdog (autonomiczne, cron)¶
n8n cron @HH:*:15 (co 15 min) - workflow rTasxDFwvfzN7p8h
│
├─ 1. Gmail search: nowe DMARC reports w spam-reports@ (alias ratunku)
├─ 2. Extract XML z gzip attachment
├─ 3. Parse XML → domena + auth results
├─ 4. Rule engine:
│ - Jesli wszystkie DKIM/SPF pass → archiwizuj + label DMARC-OK
│ - Jesli failure → alert email + label DMARC-ALERT
└─ (loop)
Pokrewne¶
- 00-overview.md - high level
- 01-modules.md - moduły
- 04-tools-registry.md - jakie tools ma Qwen w orchestracji
- 05-troubleshooting.md - fixed bugi