JangoEye - מבנה בסיס הנתונים

סכמת PostgreSQL מלאה של מערכת ניטור המכירות בטלגרם עבור SmartAds
פרויקט Supabase: ohmkpunoxloxwvtkdkee

13
טבלאות
378,908
שורות
16
קשרי FK
48
אינדקסים
22
מדיניות RLS
שכבת ליבה — אנשים, ארגונים, מבנה
שכבת נתונים — קליטת טלגרם בזמן אמת
שכבת אנליטיקס — ניקוד ודוחות
שכבת CRM — מתוכנן (עדיין לא פעיל)

דיאגרמת קשרים בין טבלאות

erDiagram
    networks {
        bigint id PK
        text name UK
        text deals_sheet_id
        timestamptz created_at
    }
    agents {
        bigint id PK
        text name
        timestamptz created_at
    }
    agent_accounts {
        bigint id PK
        bigint agent_id FK
        bigint network_id FK
        bigint telegram_user_id UK
        text phone_number
        text display_name
        text session_status
        timestamptz last_heartbeat
        timestamptz created_at
    }
    groups {
        bigint id PK
        bigint telegram_chat_id UK
        text name
        bigint network_id FK
        bigint company_id FK
        text category
        text folder_name
        int total_messages
        int outgoing_messages
        timestamptz last_message_at
    }
    messages {
        bigint id PK
        bigint telegram_message_id
        bigint chat_id
        bigint sender_id
        text direction
        text chat_type
        text text
        timestamptz timestamp
        bigint agent_account_id FK
        bigint attributed_group_id FK
        text attribution_method
    }
    group_members {
        bigint id PK
        bigint group_id FK
        bigint telegram_user_id
        boolean is_active
    }
    ingestion_health {
        bigint id PK
        bigint agent_account_id FK
        text event
        timestamptz timestamp
        jsonb details
    }
    daily_scores {
        bigint id PK
        bigint group_id FK
        date date
        int score
        text summary
    }
    weekly_reports {
        bigint id PK
        bigint agent_id FK
        date week_start
        date week_end
        text report_text
        jsonb highlights
        jsonb follow_ups
        jsonb concerns
    }
    companies {
        bigint id PK
        text name
        text_arr aliases
        text source
    }
    contacts {
        bigint id PK
        bigint company_id FK
        bigint telegram_user_id UK
        text display_name
    }
    deals {
        bigint id PK
        bigint network_id FK
        bigint agent_id FK
        bigint company_id FK
        text brand_name_raw
        numeric buy_price
        numeric sell_price
        numeric profit
    }
    group_assignments {
        bigint id PK
        bigint group_id FK
        bigint agent_id FK
    }

    agents ||--o{ agent_accounts : "has accounts"
    networks ||--o{ agent_accounts : "belongs to"
    agent_accounts ||--o{ messages : "captures"
    agent_accounts ||--o{ ingestion_health : "monitors"
    groups ||--o{ messages : "attributed to"
    groups ||--o{ group_members : "has members"
    groups ||--o{ daily_scores : "scored daily"
    networks ||--o{ groups : "organizes"
    companies ||--o{ groups : "owns"
    agents ||--o{ weekly_reports : "receives"
    agents ||--o{ deals : "closes"
    networks ||--o{ deals : "tracks"
    companies ||--o{ deals : "for"
    companies ||--o{ contacts : "employs"
    agents ||--o{ group_assignments : "assigned"
    groups ||--o{ group_assignments : "assigned to"
      

שכבות הארכיטקטורה

4 שכבות
CORE / IDENTITY

רשתות, סוכנים, חשבונות 7 rows

מי זה מי. מגדיר את רשתות הפרסום שרועי עובד איתן, את סוכני המכירות (מתחיל עם דייב), ואת חשבונות הטלגרם שלהם.

ערך עסקי: בלי השכבה הזו, הודעות הן סתם רעש. זה מה שמאפשר לנו לומר "דייב שלח 190 הודעות היום דרך רשת AllStar".

DATA / INGESTION

הודעות, קבוצות, חברים, בריאות 378,316 rows

הפיד החי. כל הודעת טלגרם שנקלטת בזמן אמת, כל קבוצה/ערוץ שמתגלה, וטלמטריית בריאות מהליסנר. טבלת messages עם 317K+ שורות היא הלב של המערכת. קבוצות מסווגות לפי תיקיות טלגרם (broker/network/affiliate/finance/new).

ערך עסקי: זה ה"עין" ב-JangoEye — נראות מלאה של שיחות מכירות שמתרחשות ברחבי ערוצי הטלגרם, עם מעקב כיוון (נכנסת/יוצאת), זיהוי עריכה/מחיקה, ומודעות למדיה.

ANALYTICS / INTELLIGENCE

ניקוד יומי, דוחות שבועיים 585 rows

להבין את הנתונים. daily_scores מדרג כל קבוצה 0-10 יומית לפי איכות השיחה. weekly_reports הם סיכומי LLM לכל סוכן עם הדגשות, המלצות, מעקב עסקאות וחששות.

ערך עסקי: זה מה שרועי באמת קורא. במקום לגלול 10,000 הודעות בשבוע, הוא מקבל דשבורד עם ניקוד ותקציר שבועי. הניקוד מזהה אילו קבוצות חמות (קרובות לעסקה) לעומת רדומות.

CRM / PLANNED

חברות, אנשי קשר, עסקאות 0 rows

עדיין לא פעיל. שמור לשלב 2 — שכבת CRM. יעקוב אחרי חברות (מותגי פרסום), אנשי קשר (אנשים באותן חברות לפי Telegram user ID), ועסקאות (מחירי קנייה/מכירה מגיליונות הרשתות). טבלת deals כוללת עמודות רווח/הפסד מלאות.

ערך עסקי (כשיופעל): סוגר את המעגל — מחבר שיחות להכנסות. "הקבוצה הזו ייצרה 3 עסקאות בשווי $X רווח החודש."

סכמה מלאה של כל טבלה

13 טבלאות
networks LIVE רשתות הפרסום שהסוכנים עובדים בהן 5 rows
מטרה: רשתות הפרסום ש-SmartAds פועלת בהן (למשל AllStar, ClickDealer). לכל רשת יכול להיות גיליון עסקאות משלה. זו הישות הארגונית העליונה — סוכנים עובדים עבור רשתות, וקבוצות שייכות לרשתות.

למה זה חשוב: מאפשר חיתוך נתונים לפי רשת. "כמה הודעות יצאו ב-AllStar השבוע?" "איזו רשת הכי פעילה?"
ColumnTypeAttributesNotes
idbigintPKIDENTITYAuto-generated
nametextUNIQUEשם הרשת (למשל "AllStar")
deals_sheet_idtextNULLABLEGoogle Sheets ID לייבוא עסקאות (שלב 2)
created_attimestamptznow()

אינדקסים

networks_pkey — btree(id)
networks_name_key — btree(name) UNIQUE
agents LIVE סוכני המכירות (כרגע רק דייב) 1 row
מטרה: סוכני המכירות — האנשים שעושים את העבודה. כרגע רק Dave All Star. מתוכנן לגדול ל-8-15 סוכנים ככל שהאונבורדינג ממשיך. לכל סוכן יכולים להיות מספר agent_accounts (אחד לכל רשת).

למה זה חשוב: מאפשר דוחות ברמת סוכן. "מה הביצועים של דייב השבוע?" ובעתיד — השוואה בין סוכנים.
ColumnTypeAttributesNotes
idbigintPKIDENTITY
nametextשם תצוגה של הסוכן
created_attimestamptznow()

אינדקסים

agents_pkey — btree(id)
agent_accounts LIVE חשבונות טלגרם של הסוכנים + סטטוס הסשן 1 row
מטרה: הגשר בין סוכן לסשן הטלגרם שלו. עוקב אחרי איזה Telegram user ID שייך לאיזה סוכן באיזו רשת, בריאות הסשן (active/revoked/pending), ותזמון heartbeat. קריטי לסקיילינג מרובה סוכנים — כשירוצו 15 מכונות, ככה נדע מי מחובר.

למה זה חשוב: בלי הטבלה הזו אין קשר בין "הודעה שנקלטה" לבין "סוכן שעובד". גם מאפשר מעקב על מכונות שנפלו.
ColumnTypeAttributesNotes
idbigintPKIDENTITY
agent_idbigintFK → agentsאיזה סוכן אנושי
network_idbigintFK → networksבאיזו רשת
telegram_user_idbigintUNIQUEמזהה המשתמש בטלגרם
phone_numbertextNULLABLEלזיהוי
display_nametextNULLABLE
session_statustextCHECK: active|revoked|pendingברירת מחדל: 'pending'
last_heartbeattimestamptzNULLABLEפינג אחרון מהליסנר
created_attimestamptznow()

אינדקסים

agent_accounts_pkey — btree(id)
agent_accounts_telegram_user_id_key — btree(telegram_user_id) UNIQUE
idx_agent_accounts_agent — btree(agent_id)
idx_agent_accounts_network — btree(network_id)
groups LIVE קבוצות וערוצים בטלגרם + קטגוריה אוטומטית 837 rows
מטרה: כל קבוצה/ערוץ טלגרם שהסוכנים משתתפים בהם. מסווגת אוטומטית באמצעות תיקיות טלגרם (folder_sync.py - קרון יומי). קטגוריות: broker, network, affiliate, finance, new, industry, internal. רק broker+network+new נמדדות ומדוּוחות.

למה זה חשוב: הקבוצות הן "מגרש המשחקים" של הסוכנים. הסיווג לפי תיקיות מאפשר לדעת בדיוק איפה הפעילות קורית — האם זה ברוקר חם, רשת חדשה, או ערוץ פיננסי. הקאונטרים (total_messages, outgoing_messages) מאפשרים בדיקת פעילות מהירה בלי לשאול את טבלת ההודעות הענקית.
ColumnTypeAttributesNotes
idbigintPKIDENTITY
telegram_chat_idbigintUNIQUEמזהה הצ'אט בטלגרם
nametextNULLABLEכותרת הצ'אט מטלגרם
network_idbigintFK → networksNULLABLEלאיזו רשת שייכת
company_idbigintFK → companiesNULLABLEשלב 2: קישור לחברה
categorytextCHECK: broker|network|new|affiliate|finance|industry|internalנגזר מתיקיות טלגרם
folder_nametextNULLABLEשם התיקייה (למשל "Broker 1", "Net 2")
total_messagesinteger0קאונטר מד-נורמליזציה
outgoing_messagesinteger0הודעות יוצאות בלבד
last_message_attimestamptzNULLABLEבדיקת "רדום" מהירה
created_attimestamptznow()
updated_attimestamptznow()

אינדקסים

groups_pkey — btree(id)
groups_telegram_chat_id_key — btree(telegram_chat_id) UNIQUE
idx_groups_network — btree(network_id)
idx_groups_company — btree(company_id)
idx_groups_folder_name — btree(folder_name) WHERE NOT NULL
messages LIVE כל הודעת טלגרם שנקלטה — הלב של המערכת 317,409 rows
מטרה: טבלת הנתונים המרכזית. כל הודעת טלגרם בודדת שנקלטת ע"י הליסנר בזמן אמת. עוקבת אחרי כיוון (נכנסת/יוצאת), סוג צ'אט, שרשור תשובות, נוכחות מדיה, עריכות ומחיקות. הודעות משויכות לקבוצות דרך attributed_group_id באמצעות התאמה ישירה או היוריסטיקת התאמת חברים.

למה זה חשוב: זה מקור האמת היחיד למדידת כל פעילות המכירות. כל מטריקה, כל דוח, כל ניקוד — הכל מתחיל מכאן. העמודה direction היא המפתח: הודעות outgoing = פעילות הסוכן, הודעות incoming = תגובות מהשוק.
ColumnTypeAttributesNotes
idbigintPKIDENTITY
telegram_message_idbigintמזהה ההודעה המקורי בטלגרם
chat_idbigintמזהה הצ'אט בטלגרם
sender_idbigintNULLABLEמי שלח (null לפוסטים בערוצים)
directiontextCHECK: incoming|outgoingמטריקה מפתח: outgoing = פעילות סוכן
chat_typetextCHECK: group|private|channel|unknown
texttextNULLABLEתוכן ההודעה (null להודעות מדיה בלבד)
timestamptimestamptzמתי נשלחה בטלגרם
agent_account_idbigintFK → agent_accountsאיזה ליסנר קלט את זה
attributed_group_idbigintFK → groupsNULLABLEשיוך לקבוצה (גם ל-DM דרך member match)
attribution_methodtextCHECK: direct_group|member_match|manual|unattributedאיך בוצע השיוך
reply_to_msg_idbigintNULLABLEשרשור: לאיזו הודעה זו תשובה
media_typetextNULLABLEphoto, video, document וכו'
is_editedbooleanfalse
is_deletedbooleanfalse
created_attimestamptznow()מתי נכנס ל-DB

אינדקסים (8 אינדקסים — אופטימיזציה כבדה)

messages_pkey — btree(id)
messages_..._key — UNIQUE(agent_account_id, telegram_message_id, chat_id) — מניעת כפילויות
idx_messages_timestamp — btree(timestamp) — שאילתות טווח זמן
idx_messages_direction_ts — btree(direction, timestamp) — פעילות יוצאת לאורך זמן
idx_messages_chat — btree(chat_id) — חיפוש לפי צ'אט
idx_messages_sender — btree(sender_id) — פעילות לפי משתמש
idx_messages_agent_account — btree(agent_account_id) — שאילתות לפי סוכן
idx_messages_attributed_group — btree(attributed_group_id) WHERE NOT NULL — joins לניקוד
group_members LIVE חברי קבוצות — משמש לשיוך הודעות פרטיות 35 rows
מטרה: עוקב אחרי אילו משתמשי טלגרם חברים באילו קבוצות. משמש לשיוך הודעות פרטיות — כשמגיעה הודעה פרטית, בודקים אם השולח חבר בקבוצה מוכרת, ומשייכים את ההודעה לקבוצה הזו. גם שימושי למעקב נטישה (first_seen לעומת last_seen).

למה זה חשוב: בלי זה, הודעות פרטיות (DMs) "אבודות" — לא יודעים מאיזו קבוצה הגיע איש הקשר. זה מה שמאפשר לשייך הודעה פרטית מ"יוסי" לקבוצת "Broker X".
ColumnTypeAttributesNotes
idbigintPKIDENTITY
group_idbigintFK → groups
telegram_user_idbigintUNIQUE(group_id, telegram_user_id)
first_seentimestamptznow()מתי נראה לראשונה
last_seentimestamptznow()פעילות אחרונה
is_activebooleantrue

אינדקסים

group_members_pkey — btree(id)
group_members_group_id_telegram_user_id_key — UNIQUE(group_id, telegram_user_id)
idx_group_members_group — btree(group_id)
idx_group_members_telegram — btree(telegram_user_id)
idx_group_members_active — btree(group_id) WHERE is_active = true
ingestion_health LIVE Heartbeat ומוניטורינג של הליסנר 60,035 rows
מטרה: ניטור בריאות כל אינסטנס של הליסנר. אירועים: connected (התחבר), disconnected (התנתק), error (שגיאה), heartbeat (פעימה). דף הסטטוס בדשבורד שואל את הטבלה הזו כדי להראות אם הליסנר חי. 60K+ אירועים = בערך heartbeat כל כמה דקות מאז ההשקה.

למה זה חשוב: כשליסנר נופל ולא שולח הודעות, בלי הטבלה הזו לא נדע. זה מה שמפעיל את דף status.html ומאפשר לזהות בעיות לפני שהלקוח מתלונן.
ColumnTypeAttributesNotes
idbigintPKIDENTITY
agent_account_idbigintFK → agent_accounts
eventtextCHECK: connected|disconnected|error|heartbeat
timestamptimestamptznow()
detailsjsonbNULLABLEהודעות שגיאה, מטאדאטה

אינדקסים

ingestion_health_pkey — btree(id)
idx_ingestion_health_account — btree(agent_account_id)
idx_ingestion_health_ts — btree(timestamp)
daily_scores LIVE ניקוד איכות שיחה יומי לכל קבוצה (0-10) 580 rows
מטרה: ניקוד איכות שיחה יומי לכל קבוצה בסקאלה 0-10. מיוצר ע"י score_conversations.py שמנתח דפוסי הודעות. רק קבוצות עם קטגוריה broker/network/new מנוקדות. שדה summary מכיל הסבר טקסטואלי לניקוד.

למה זה חשוב: זה מה שמפעיל את דשבורד הניקוד שרועי בודק יומית. במקום לנחש "איזו קבוצה שווה תשומת לב", הניקוד מגלה את זה: 8-10 = חם, קרוב לעסקה. 0-2 = רדום, צריך לטפל.
ColumnTypeAttributesNotes
idbigintPKIDENTITY
group_idbigintFK → groups
datedateUNIQUE(group_id, date)
scoreintegerCHECK: 0-100=רדום, 10=מוכן לעסקה
summarytextNULLABLEהסבר טקסטואלי
created_attimestamptznow()

אינדקסים

daily_scores_pkey — btree(id)
daily_scores_group_id_date_key — UNIQUE(group_id, date)
idx_daily_scores_group — btree(group_id)
idx_daily_scores_date — btree(date)
idx_daily_scores_score — btree(score DESC) — שאילתת קבוצות מובילות
weekly_reports LIVE דוחות מודיעין שבועיים מבוססי LLM לכל סוכן 5 rows
מטרה: דוחות מודיעין שבועיים שמיוצרים ע"י LLM לכל סוכן. מכילים הדגשות, המלצות לפעולה, מעקב עסקאות (סגורות + בתהליך), חששות, וסטטיסטיקות מצטברות (ממוצע ניקוד, קבוצות פעילות/רדומות). מיוצר ע"י generate_weekly_report.py.

למה זה חשוב: זהו התקציר המנהלי — ה-TL;DR של שבוע שלם של סוכן. רועי מקבל במקום אלפי הודעות סיכום ברור: מה עבד, מה בסיכון, ומה צריך תשומת לב. השדות highlights, follow_ups, concerns הם JSON arrays שמאפשרים הצגה מובנית בדשבורד.
ColumnTypeAttributesNotes
idbigintPKIDENTITY
agent_idbigintFK → agents
week_startdateUNIQUE(agent_id, week_start)
week_enddate
report_texttextNULLABLEדוח LLM מלא
highlightsjsonb[]מערך הדגשות
follow_upsjsonb[]פעולות מומלצות
concernsjsonb[]סיגנלי סיכון
avg_scorenumericNULLABLEממוצע daily_scores לשבוע
active_groupsinteger0כמה קבוצות פעילות
dormant_groupsinteger0כמה קבוצות רדומות
near_deal_countinteger0קבוצות קרובות לסגירה
deals_closedinteger0עסקאות שנסגרו
deals_closed_listjsonb[]פירוט עסקאות סגורות
deals_in_progressinteger0עסקאות בתהליך
deals_in_progress_listjsonb[]פירוט עסקאות בתהליך
created_attimestamptznow()

אינדקסים

weekly_reports_pkey — btree(id)
weekly_reports_agent_id_week_start_key — UNIQUE(agent_id, week_start)
companies מתוכנן מותגי פרסום וחברות — שלב 2 0 rows
מטרה (שלב 2): מותגי פרסום וחברות ש-SmartAds עובדת איתן. מערך aliases תומך בהתאמת מותגים מטושטשת (חברה יכולה להופיע כ-"ClickMedia", "Click Media Ltd", "CM" בהקשרים שונים). שדה source מבדיל בין לקוחות פעילים לרשימת הפורטפוליו הלא-פעיל שרועי דיבר עליה.

ערך עתידי: יאפשר לשאול "באילו קבוצות נמצאת חברה X?" ו-"כמה עסקאות סגרנו מול חברה Y?".
ColumnTypeAttributesNotes
idbigintPKIDENTITY
nametextשם קנוני של החברה
aliasestext[]'{}'שמות חלופיים להתאמה
sourcetextCHECK: active_client|inactive_portfolioברירת מחדל: 'active_client'
created_attimestamptznow()
contacts מתוכנן אנשי קשר בחברות — מזוהים לפי Telegram ID 0 rows
מטרה (שלב 2): אנשים בודדים בחברות, מזוהים לפי Telegram user ID שלהם. מקשר sender_id מהודעות לאיש קשר מוכר בחברה. מאפשר ניתוח "מי מחברה X מדבר באילו קבוצות".

ערך עתידי: יהפוך כל sender_id מספר חסר משמעות לשם ופנים. "ההודעה הזו נשלחה ע"י יוסי מחברת ClickMedia".
ColumnTypeAttributesNotes
idbigintPKIDENTITY
company_idbigintFK → companiesNULLABLE
telegram_user_idbigintUNIQUE
display_nametextNULLABLE
created_attimestamptznow()
deals מתוכנן עסקאות פיננסיות — שכבת ההכנסות (שלב 2) 0 rows
מטרה (שלב 2): מעקב עסקאות פיננסיות מיובאות מגיליונות הרשתות. כל עסקה מקושרת לרשת, סוכן, ו(אופציונלית) חברה. כוללת רווח/הפסד מלא: מחיר קנייה, מחיר מכירה, רווח. match_status עוקב אם העסקה הותאמה אוטומטית לחברה או דורשת סקירה ידנית.

ערך עתידי: זו שכבת ההכנסות — מחברת שיחות לכסף. "הקבוצה הזו ייצרה 3 עסקאות בשווי $X רווח החודש." סוגרת את המעגל מ-"כמה הודעות" ל-"כמה כסף".
ColumnTypeAttributesNotes
idbigintPKIDENTITY
network_idbigintFK → networks
agent_idbigintFK → agents
company_idbigintFK → companiesNULLABLE
brand_name_rawtextNULLABLEשם המותג הגולמי מהגיליון
datedateתאריך העסקה
buy_pricenumericNULLABLEמחיר קנייה
sell_pricenumericNULLABLEמחיר מכירה
profitnumericNULLABLEsell_price - buy_price
match_statustextCHECK: auto_matched|manual_override|unmatchedברירת מחדל: 'unmatched'
created_attimestamptznow()

אינדקסים

deals_pkey — btree(id)
idx_deals_agent — btree(agent_id)
idx_deals_company — btree(company_id)
idx_deals_date — btree(date)
idx_deals_network — btree(network_id)
group_assignments הוצא משימוש שיוך ידני של סוכנים לקבוצות — הוחלף ע"י תיקיות 0 rows
סטטוס: הוחלף בפועל ע"י מערכת הקטגוריות מבוססת התיקיות. תוכנן במקור לשיוך ידני של סוכנים לקבוצות. עם folder_sync.py, הסיווג קורה אוטומטית ממבנה התיקיות של טלגרם עצמו, מה שהופך שיוך ידני למיותר בפיילוט. ייתכן שינוצל מחדש לתרחישים מרובי-סוכנים בשלב 2.
ColumnTypeAttributesNotes
idbigintPKIDENTITY
group_idbigintFK → groups
agent_idbigintFK → agents
assigned_attimestamptznow()
unassigned_attimestamptzNULLABLEהסרת שיוך רכה

אבטחה ברמת שורה (RLS)

22 מדיניות

לכל הטבלאות מופעל RLS. מדיניות נוכחית: גישת קריאה בלבד עבור anon ו-authenticated. כתיבות עוברות דרך הליסנר ב-Python באמצעות מפתח service_role (עוקף RLS). מדיניות הכתיבה היחידה היא groups.UPDATE ל-anon (משמשת את ממשק הסיווג הישן).

networks
SELECT anon + authenticated
agents
SELECT anon + authenticated
agent_accounts
SELECT anon + authenticated
groups
SELECT anon + authenticated
UPDATE anon (ממשק סיווג ישן)
messages
SELECT public (כל הרולים)
group_members
SELECT authenticated בלבד
ingestion_health
SELECT public (כל הרולים)
daily_scores
SELECT anon + authenticated
weekly_reports
SELECT anon + authenticated
companies
SELECT authenticated בלבד
contacts
SELECT authenticated בלבד
deals
SELECT authenticated בלבד
group_assignments
SELECT authenticated בלבד

זרימת הנתונים

flowchart LR
    subgraph Telegram["Telegram"]
        TG["קבוצות והודעות פרטיות"]
    end

    subgraph Listener["listener.py\n(VPS ווינדוס)"]
        L1["קליטה בזמן אמת"]
        L2["מעקב עריכה/מחיקה"]
        L3["Heartbeat"]
    end

    subgraph Supabase["Supabase PostgreSQL"]
        M["messages\n317K"]
        G["groups\n837"]
        GM["group_members\n35"]
        IH["ingestion_health\n60K"]
    end

    subgraph DailyCron["קרונים יומיים"]
        FS["folder_sync.py\nסיווג תיקיות"]
        SC["score_conversations.py\nניקוד שיחות"]
    end

    subgraph WeeklyCron["קרון שבועי"]
        WR["generate_weekly_report.py\nדוח LLM"]
    end

    subgraph Analytics["טבלאות אנליטיקס"]
        DS["daily_scores\n580"]
        WRT["weekly_reports\n5"]
    end

    subgraph Dashboard["דשבורד אדמין\n(Vercel)"]
        D1["scores.html"]
        D2["status.html"]
        D3["progress.html"]
    end

    TG --> L1
    L1 --> M
    L1 --> G
    L1 --> GM
    L3 --> IH
    FS --> G
    SC --> DS
    M --> SC
    DS --> WR
    WR --> WRT
    M --> Dashboard
    DS --> D1
    IH --> D2
    WRT --> Dashboard