mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-17 23:09:35 +00:00
All 4 layers of Dealix are now fully built: Strategic Growth OS (2,715 lines): - acquisition_scouting.py (494): Target sourcing, scoring, Arabic briefs, watchlist - ecosystem_mapper.py (568): Partner landscape, gap detection, cluster analysis - strategic_simulator.py (596): 7 scenario types with financial modeling, sensitivity - roi_engine.py (484): NPV-based ROI, Saudi market benchmarks, annual projection - portfolio_intelligence.py (573): Vertical analysis, pattern detection, quarterly reports Updated __init__.py with 12 new exports. PROJECT STATUS: 100% COMPLETE - Layer 0: Core Platform ✅ - Layer 1: Sales OS ✅ - Layer 2: Deal Exchange OS ✅ - Layer 3: Strategic Growth OS ✅ - Frontend: 37 components ✅ - Governance: Full stack ✅ https://claude.ai/code/session_01LsnvBa7HwF5hs99VZbgLGj
569 lines
24 KiB
Python
569 lines
24 KiB
Python
"""
|
||
Ecosystem Mapper — Maps and analyzes B2B partner ecosystems in the Saudi market.
|
||
خريطة المنظومة: رسم وتحليل منظومة الشركاء في السوق السعودي
|
||
"""
|
||
|
||
import json
|
||
import logging
|
||
import uuid
|
||
from collections import defaultdict
|
||
from typing import Optional
|
||
|
||
from pydantic import BaseModel, Field
|
||
from sqlalchemy import select, func
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from app.models.strategic_deal import CompanyProfile
|
||
from app.services.llm.provider import get_llm
|
||
|
||
logger = logging.getLogger("dealix.strategic_deals.ecosystem_mapper")
|
||
|
||
# ── Entity type definitions ─────────────────────────────────────────────────
|
||
|
||
ENTITY_TYPES = {
|
||
"agency": "وكالة",
|
||
"integrator": "مُدمج أنظمة",
|
||
"reseller": "موزع معتمد",
|
||
"consultant": "مستشار",
|
||
"distributor": "موزع",
|
||
"supplier": "مورد",
|
||
"customer": "عميل",
|
||
"competitor": "منافس",
|
||
}
|
||
|
||
LINK_TYPES = ("partner", "competitor", "vendor", "client", "referral", "subsidiary")
|
||
|
||
# ── Capability clusters for gap analysis ────────────────────────────────────
|
||
|
||
CAPABILITY_CLUSTERS = {
|
||
"تقنية": ["تطوير برمجيات", "حوسبة سحابية", "أمن سيبراني", "ذكاء اصطناعي", "تحليل بيانات"],
|
||
"تسويق": ["تسويق رقمي", "إعلان", "علاقات عامة", "إدارة محتوى", "سوشل ميديا"],
|
||
"عمليات": ["لوجستيات", "سلسلة إمداد", "إدارة مخازن", "نقل", "توزيع"],
|
||
"مالية": ["محاسبة", "تدقيق", "استشارات مالية", "تمويل", "إدارة مخاطر"],
|
||
"موارد بشرية": ["توظيف", "تدريب", "تطوير مهني", "رواتب", "شؤون موظفين"],
|
||
"قانونية": ["استشارات قانونية", "عقود", "ملكية فكرية", "امتثال", "تراخيص"],
|
||
"مبيعات": ["مبيعات مباشرة", "مبيعات قنوات", "تطوير أعمال", "إدارة حسابات", "عروض أسعار"],
|
||
}
|
||
|
||
|
||
# ── Models ──────────────────────────────────────────────────────────────────
|
||
|
||
|
||
class EcosystemEntity(BaseModel):
|
||
"""A node in the ecosystem graph representing a company or organization."""
|
||
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
||
name: str
|
||
name_ar: str = ""
|
||
entity_type: str = "partner" # agency, integrator, reseller, consultant, distributor
|
||
industry: str = ""
|
||
city: str = ""
|
||
capabilities: list[str] = Field(default_factory=list)
|
||
relationship_strength: float = Field(0.0, ge=0.0, le=1.0)
|
||
partner_potential: float = Field(0.0, ge=0.0, le=1.0)
|
||
profile_id: Optional[str] = None
|
||
|
||
class Config:
|
||
json_schema_extra = {
|
||
"example": {
|
||
"name": "DataSphere Solutions",
|
||
"name_ar": "حلول داتا سفير",
|
||
"entity_type": "integrator",
|
||
"industry": "technology",
|
||
"city": "الرياض",
|
||
"capabilities": ["حوسبة سحابية", "أمن سيبراني"],
|
||
"relationship_strength": 0.8,
|
||
"partner_potential": 0.75,
|
||
}
|
||
}
|
||
|
||
|
||
class EcosystemLink(BaseModel):
|
||
"""An edge in the ecosystem graph representing a relationship."""
|
||
source_id: str
|
||
target_id: str
|
||
link_type: str = "partner" # partner, competitor, vendor, client
|
||
strength: float = Field(0.5, ge=0.0, le=1.0)
|
||
description_ar: str = ""
|
||
|
||
|
||
# ── Ecosystem Mapper Engine ─────────────────────────────────────────────────
|
||
|
||
|
||
class EcosystemMapper:
|
||
"""
|
||
Builds, analyzes, and visualizes B2B ecosystem maps.
|
||
Identifies gaps, suggests partners, and monitors ecosystem health.
|
||
بناء وتحليل وعرض خرائط منظومة الأعمال — تحديد الفجوات واقتراح الشركاء
|
||
"""
|
||
|
||
def __init__(self):
|
||
self.llm = get_llm()
|
||
|
||
# ── Build Map ───────────────────────────────────────────────────────────
|
||
|
||
async def build_map(
|
||
self,
|
||
tenant_id: str,
|
||
db: AsyncSession,
|
||
) -> dict:
|
||
"""
|
||
Build a complete ecosystem map from company profiles and deal history.
|
||
بناء خريطة منظومة كاملة من ملفات الشركات وتاريخ الصفقات
|
||
"""
|
||
result = await db.execute(
|
||
select(CompanyProfile).where(CompanyProfile.tenant_id == tenant_id)
|
||
)
|
||
profiles = result.scalars().all()
|
||
|
||
if not profiles:
|
||
logger.info("No profiles found for tenant %s", tenant_id)
|
||
return {"entities": [], "links": [], "stats": {}}
|
||
|
||
entities: list[EcosystemEntity] = []
|
||
links: list[EcosystemLink] = []
|
||
|
||
# Build entity nodes from profiles
|
||
entity_map: dict[str, EcosystemEntity] = {}
|
||
for profile in profiles:
|
||
entity_type = self._infer_entity_type(profile)
|
||
entity = EcosystemEntity(
|
||
name=profile.company_name or "",
|
||
name_ar=profile.company_name_ar if hasattr(profile, "company_name_ar") else "",
|
||
entity_type=entity_type,
|
||
industry=profile.industry or "",
|
||
city=profile.region or "",
|
||
capabilities=[c for c in (profile.capabilities or [])],
|
||
relationship_strength=float(profile.trust_score or 0.5),
|
||
partner_potential=0.0,
|
||
profile_id=str(profile.id),
|
||
)
|
||
entities.append(entity)
|
||
entity_map[str(profile.id)] = entity
|
||
|
||
# Infer links based on capability/need overlap and industry relationships
|
||
profile_list = list(profiles)
|
||
for i, prof_a in enumerate(profile_list):
|
||
for prof_b in profile_list[i + 1:]:
|
||
link_type, strength = self._infer_link(prof_a, prof_b)
|
||
if strength >= 0.2:
|
||
entity_a = entity_map.get(str(prof_a.id))
|
||
entity_b = entity_map.get(str(prof_b.id))
|
||
if entity_a and entity_b:
|
||
link = EcosystemLink(
|
||
source_id=entity_a.id,
|
||
target_id=entity_b.id,
|
||
link_type=link_type,
|
||
strength=round(strength, 4),
|
||
description_ar=self._link_description(
|
||
prof_a.company_name, prof_b.company_name, link_type
|
||
),
|
||
)
|
||
links.append(link)
|
||
|
||
# Compute partner potential for each entity
|
||
for entity in entities:
|
||
incoming = [lk for lk in links if lk.target_id == entity.id]
|
||
outgoing = [lk for lk in links if lk.source_id == entity.id]
|
||
partner_links = [
|
||
lk for lk in incoming + outgoing
|
||
if lk.link_type in ("partner", "referral")
|
||
]
|
||
if partner_links:
|
||
entity.partner_potential = round(
|
||
sum(lk.strength for lk in partner_links) / len(partner_links), 4
|
||
)
|
||
|
||
stats = {
|
||
"total_entities": len(entities),
|
||
"total_links": len(links),
|
||
"entity_types": defaultdict(int),
|
||
"link_types": defaultdict(int),
|
||
"avg_relationship_strength": 0.0,
|
||
}
|
||
for e in entities:
|
||
stats["entity_types"][e.entity_type] += 1
|
||
for lk in links:
|
||
stats["link_types"][lk.link_type] += 1
|
||
if entities:
|
||
stats["avg_relationship_strength"] = round(
|
||
sum(e.relationship_strength for e in entities) / len(entities), 4
|
||
)
|
||
stats["entity_types"] = dict(stats["entity_types"])
|
||
stats["link_types"] = dict(stats["link_types"])
|
||
|
||
logger.info(
|
||
"Built ecosystem map for tenant %s: %d entities, %d links",
|
||
tenant_id, len(entities), len(links),
|
||
)
|
||
|
||
return {
|
||
"entities": [e.model_dump() for e in entities],
|
||
"links": [lk.model_dump() for lk in links],
|
||
"stats": stats,
|
||
}
|
||
|
||
# ── Find Gaps ───────────────────────────────────────────────────────────
|
||
|
||
async def find_gaps(
|
||
self,
|
||
tenant_id: str,
|
||
db: AsyncSession,
|
||
) -> list[dict]:
|
||
"""
|
||
Identify underserved areas in the ecosystem where partners are missing.
|
||
تحديد المناطق غير المخدومة في المنظومة حيث ينقص الشركاء
|
||
"""
|
||
result = await db.execute(
|
||
select(CompanyProfile).where(CompanyProfile.tenant_id == tenant_id)
|
||
)
|
||
profiles = result.scalars().all()
|
||
|
||
if not profiles:
|
||
return []
|
||
|
||
# Collect all capabilities and all needs across the ecosystem
|
||
all_capabilities: set[str] = set()
|
||
all_needs: set[str] = set()
|
||
for profile in profiles:
|
||
for cap in (profile.capabilities or []):
|
||
all_capabilities.add(cap.lower().strip())
|
||
for need in (profile.needs or []):
|
||
all_needs.add(need.lower().strip())
|
||
|
||
# Gaps: needs that no one in the ecosystem can fulfill
|
||
unmet_needs = all_needs - all_capabilities
|
||
|
||
# Cluster-level gaps: entire capability clusters with low coverage
|
||
cluster_gaps: list[dict] = []
|
||
for cluster_name, cluster_caps in CAPABILITY_CLUSTERS.items():
|
||
cluster_lower = {c.lower() for c in cluster_caps}
|
||
covered = cluster_lower & all_capabilities
|
||
coverage = len(covered) / len(cluster_lower) if cluster_lower else 0
|
||
if coverage < 0.3:
|
||
cluster_gaps.append({
|
||
"gap_type": "cluster",
|
||
"cluster_name_ar": cluster_name,
|
||
"coverage": round(coverage, 4),
|
||
"missing_capabilities": list(cluster_lower - all_capabilities),
|
||
"recommendation_ar": f"المنظومة تفتقر لشركاء في مجال {cluster_name} — التغطية {coverage:.0%} فقط",
|
||
})
|
||
|
||
# Individual unmet needs
|
||
individual_gaps = [
|
||
{
|
||
"gap_type": "unmet_need",
|
||
"need": need,
|
||
"recommendation_ar": f"لا يوجد شريك يقدم: {need}",
|
||
}
|
||
for need in sorted(unmet_needs)[:20]
|
||
]
|
||
|
||
gaps = cluster_gaps + individual_gaps
|
||
|
||
logger.info(
|
||
"Found %d ecosystem gaps for tenant %s (%d cluster, %d individual)",
|
||
len(gaps), tenant_id, len(cluster_gaps), len(individual_gaps),
|
||
)
|
||
return gaps
|
||
|
||
# ── Suggest Partners ────────────────────────────────────────────────────
|
||
|
||
async def suggest_partners(
|
||
self,
|
||
gap_type: str,
|
||
tenant_id: str,
|
||
db: AsyncSession,
|
||
) -> list[EcosystemEntity]:
|
||
"""
|
||
Suggest potential partners to fill an ecosystem gap.
|
||
اقتراح شركاء محتملين لسد فجوة في المنظومة
|
||
"""
|
||
gaps = await self.find_gaps(tenant_id, db)
|
||
|
||
matching_gaps = [g for g in gaps if g.get("gap_type") == gap_type]
|
||
if not matching_gaps:
|
||
matching_gaps = gaps[:3]
|
||
|
||
gap_summary = json.dumps(matching_gaps[:5], ensure_ascii=False)
|
||
|
||
context = f"""فجوات المنظومة:
|
||
{gap_summary}
|
||
|
||
نوع الفجوة المطلوب: {gap_type}"""
|
||
|
||
system_prompt = """أنت مستشار تطوير أعمال سعودي. بناءً على فجوات المنظومة، اقترح شركاء محتملين.
|
||
|
||
Return JSON:
|
||
{
|
||
"suggestions": [
|
||
{
|
||
"name": "اسم النوع المقترح بالإنجليزي",
|
||
"name_ar": "اسم النوع المقترح بالعربي",
|
||
"entity_type": "agency/integrator/reseller/consultant/distributor",
|
||
"industry": "القطاع",
|
||
"capabilities": ["قدرة ١", "قدرة ٢"],
|
||
"rationale_ar": "سبب الاقتراح بالعربي",
|
||
"partner_potential": 0.0 to 1.0
|
||
}
|
||
]
|
||
}"""
|
||
|
||
try:
|
||
llm_response = await self.llm.complete(
|
||
system_prompt=system_prompt,
|
||
user_message=context,
|
||
json_mode=True,
|
||
temperature=0.4,
|
||
)
|
||
result = llm_response.parse_json() or {}
|
||
suggestions_data = result.get("suggestions", [])
|
||
except Exception as exc:
|
||
logger.warning("LLM partner suggestion failed: %s", exc)
|
||
suggestions_data = [
|
||
{
|
||
"name": f"Partner for {gap_type}",
|
||
"name_ar": f"شريك لسد فجوة {gap_type}",
|
||
"entity_type": "consultant",
|
||
"industry": "consulting",
|
||
"capabilities": [g.get("need", "") for g in matching_gaps if g.get("need")],
|
||
"rationale_ar": "اقتراح تلقائي بناءً على الفجوات المكتشفة",
|
||
"partner_potential": 0.5,
|
||
}
|
||
]
|
||
|
||
entities: list[EcosystemEntity] = []
|
||
for s in suggestions_data:
|
||
entity = EcosystemEntity(
|
||
name=s.get("name", ""),
|
||
name_ar=s.get("name_ar", ""),
|
||
entity_type=s.get("entity_type", "consultant"),
|
||
industry=s.get("industry", ""),
|
||
capabilities=s.get("capabilities", []),
|
||
relationship_strength=0.0,
|
||
partner_potential=min(1.0, max(0.0, float(s.get("partner_potential", 0.5)))),
|
||
)
|
||
entities.append(entity)
|
||
|
||
logger.info(
|
||
"Suggested %d partners for gap '%s' in tenant %s",
|
||
len(entities), gap_type, tenant_id,
|
||
)
|
||
return entities
|
||
|
||
# ── Get Clusters ────────────────────────────────────────────────────────
|
||
|
||
async def get_clusters(
|
||
self,
|
||
tenant_id: str,
|
||
db: AsyncSession,
|
||
) -> list[dict]:
|
||
"""
|
||
Identify clusters of related entities in the ecosystem.
|
||
تحديد تجمعات الكيانات المترابطة في المنظومة
|
||
"""
|
||
eco_map = await self.build_map(tenant_id, db)
|
||
entities = eco_map.get("entities", [])
|
||
links = eco_map.get("links", [])
|
||
|
||
if not entities:
|
||
return []
|
||
|
||
# Group entities by industry
|
||
industry_groups: dict[str, list[dict]] = defaultdict(list)
|
||
for entity in entities:
|
||
industry_groups[entity.get("industry", "other")].append(entity)
|
||
|
||
clusters: list[dict] = []
|
||
for industry, members in industry_groups.items():
|
||
if not members:
|
||
continue
|
||
|
||
member_ids = {m["id"] for m in members}
|
||
internal_links = [
|
||
lk for lk in links
|
||
if lk.get("source_id") in member_ids and lk.get("target_id") in member_ids
|
||
]
|
||
external_links = [
|
||
lk for lk in links
|
||
if (lk.get("source_id") in member_ids) != (lk.get("target_id") in member_ids)
|
||
]
|
||
|
||
avg_strength = 0.0
|
||
if internal_links:
|
||
avg_strength = sum(lk.get("strength", 0) for lk in internal_links) / len(internal_links)
|
||
|
||
all_caps: set[str] = set()
|
||
for m in members:
|
||
all_caps.update(m.get("capabilities", []))
|
||
|
||
clusters.append({
|
||
"cluster_name": industry,
|
||
"cluster_name_ar": ENTITY_TYPES.get(industry, industry),
|
||
"member_count": len(members),
|
||
"internal_links": len(internal_links),
|
||
"external_links": len(external_links),
|
||
"avg_internal_strength": round(avg_strength, 4),
|
||
"capabilities": sorted(all_caps),
|
||
"members": [{"id": m["id"], "name": m["name"]} for m in members],
|
||
})
|
||
|
||
clusters.sort(key=lambda c: c["member_count"], reverse=True)
|
||
|
||
logger.info("Identified %d clusters for tenant %s", len(clusters), tenant_id)
|
||
return clusters
|
||
|
||
# ── Ecosystem Health ────────────────────────────────────────────────────
|
||
|
||
async def get_ecosystem_health(
|
||
self,
|
||
tenant_id: str,
|
||
db: AsyncSession,
|
||
) -> dict:
|
||
"""
|
||
Calculate ecosystem health metrics: coverage, concentration, resilience.
|
||
حساب مؤشرات صحة المنظومة: التغطية والتركيز والمرونة
|
||
"""
|
||
eco_map = await self.build_map(tenant_id, db)
|
||
entities = eco_map.get("entities", [])
|
||
links = eco_map.get("links", [])
|
||
gaps = await self.find_gaps(tenant_id, db)
|
||
|
||
total_entities = len(entities)
|
||
total_links = len(links)
|
||
total_gaps = len(gaps)
|
||
|
||
if total_entities == 0:
|
||
return {
|
||
"overall_score": 0.0,
|
||
"coverage": 0.0,
|
||
"concentration_risk": 1.0,
|
||
"resilience": 0.0,
|
||
"diversity": 0.0,
|
||
"gap_count": 0,
|
||
"recommendations_ar": ["لا توجد بيانات كافية لتحليل صحة المنظومة"],
|
||
}
|
||
|
||
# Coverage: ratio of cluster gaps (lower = better coverage)
|
||
cluster_gaps = [g for g in gaps if g.get("gap_type") == "cluster"]
|
||
total_clusters = len(CAPABILITY_CLUSTERS)
|
||
coverage = 1.0 - (len(cluster_gaps) / total_clusters) if total_clusters > 0 else 0.0
|
||
|
||
# Concentration risk: how dependent the ecosystem is on few entities
|
||
type_counts = defaultdict(int)
|
||
for e in entities:
|
||
type_counts[e.get("entity_type", "unknown")] += 1
|
||
max_type_share = max(type_counts.values()) / total_entities if total_entities > 0 else 1.0
|
||
concentration_risk = max_type_share
|
||
|
||
# Diversity: number of distinct entity types / total possible
|
||
diversity = len(type_counts) / len(ENTITY_TYPES) if ENTITY_TYPES else 0.0
|
||
|
||
# Resilience: avg links per entity (more links = more resilient)
|
||
avg_links = total_links / total_entities if total_entities > 0 else 0.0
|
||
resilience = min(1.0, avg_links / 3.0) # 3+ links per entity = max resilience
|
||
|
||
# Overall health score
|
||
overall = round(
|
||
coverage * 0.35
|
||
+ (1.0 - concentration_risk) * 0.25
|
||
+ resilience * 0.25
|
||
+ diversity * 0.15,
|
||
4,
|
||
)
|
||
|
||
# Generate recommendations
|
||
recommendations_ar: list[str] = []
|
||
if coverage < 0.5:
|
||
recommendations_ar.append("تغطية المنظومة ضعيفة — يُنصح بإضافة شركاء في القطاعات الناقصة")
|
||
if concentration_risk > 0.6:
|
||
recommendations_ar.append("تركيز عالٍ على نوع واحد من الشركاء — يُنصح بالتنويع")
|
||
if resilience < 0.4:
|
||
recommendations_ar.append("مرونة المنظومة منخفضة — يُنصح بتعزيز الروابط بين الشركاء")
|
||
if diversity < 0.5:
|
||
recommendations_ar.append("تنوع أنواع الشركاء محدود — يُنصح بإضافة أنواع جديدة")
|
||
if total_gaps > 10:
|
||
recommendations_ar.append(f"يوجد {total_gaps} فجوة في المنظومة — يُنصح بمعالجة الفجوات الحرجة أولاً")
|
||
if not recommendations_ar:
|
||
recommendations_ar.append("المنظومة في حالة صحية جيدة — استمر في المراقبة الدورية")
|
||
|
||
health = {
|
||
"overall_score": overall,
|
||
"coverage": round(coverage, 4),
|
||
"concentration_risk": round(concentration_risk, 4),
|
||
"resilience": round(resilience, 4),
|
||
"diversity": round(diversity, 4),
|
||
"gap_count": total_gaps,
|
||
"total_entities": total_entities,
|
||
"total_links": total_links,
|
||
"entity_type_distribution": dict(type_counts),
|
||
"recommendations_ar": recommendations_ar,
|
||
}
|
||
|
||
logger.info(
|
||
"Ecosystem health for tenant %s: overall=%.2f coverage=%.2f risk=%.2f",
|
||
tenant_id, overall, coverage, concentration_risk,
|
||
)
|
||
return health
|
||
|
||
# ── Private Helpers ─────────────────────────────────────────────────────
|
||
|
||
def _infer_entity_type(self, profile: CompanyProfile) -> str:
|
||
"""Infer entity type from company profile characteristics."""
|
||
caps = {c.lower() for c in (profile.capabilities or [])}
|
||
industry = (profile.industry or "").lower()
|
||
|
||
if industry == "consulting" or "استشارات" in caps:
|
||
return "consultant"
|
||
if "توزيع" in caps or "distribution" in industry:
|
||
return "distributor"
|
||
if "تكامل" in caps or "integration" in industry or "تكامل أنظمة" in caps:
|
||
return "integrator"
|
||
if "إعادة بيع" in caps or "reselling" in industry:
|
||
return "reseller"
|
||
if industry in ("marketing", "media") or "تسويق" in caps:
|
||
return "agency"
|
||
return "partner"
|
||
|
||
def _infer_link(
|
||
self, prof_a: CompanyProfile, prof_b: CompanyProfile,
|
||
) -> tuple[str, float]:
|
||
"""Infer the link type and strength between two profiles."""
|
||
caps_a = {c.lower() for c in (prof_a.capabilities or [])}
|
||
caps_b = {c.lower() for c in (prof_b.capabilities or [])}
|
||
needs_a = {n.lower() for n in (prof_a.needs or [])}
|
||
needs_b = {n.lower() for n in (prof_b.needs or [])}
|
||
|
||
# Check if they are in the same industry (potential competitors)
|
||
same_industry = (prof_a.industry or "") == (prof_b.industry or "") and prof_a.industry
|
||
|
||
# Check vendor/client: A offers what B needs
|
||
a_serves_b = len(caps_a & needs_b)
|
||
b_serves_a = len(caps_b & needs_a)
|
||
|
||
if a_serves_b > 0 and b_serves_a > 0:
|
||
# Mutual exchange = partnership
|
||
strength = min(1.0, (a_serves_b + b_serves_a) / max(len(needs_a | needs_b), 1) * 2)
|
||
return "partner", round(strength, 4)
|
||
elif a_serves_b > 0:
|
||
strength = min(1.0, a_serves_b / max(len(needs_b), 1))
|
||
return "vendor", round(strength, 4)
|
||
elif b_serves_a > 0:
|
||
strength = min(1.0, b_serves_a / max(len(needs_a), 1))
|
||
return "client", round(strength, 4)
|
||
elif same_industry and caps_a & caps_b:
|
||
overlap = len(caps_a & caps_b) / max(len(caps_a | caps_b), 1)
|
||
return "competitor", round(overlap, 4)
|
||
else:
|
||
return "partner", 0.1
|
||
|
||
def _link_description(self, name_a: str, name_b: str, link_type: str) -> str:
|
||
"""Generate Arabic description for a link."""
|
||
descriptions = {
|
||
"partner": f"{name_a} و{name_b} شركاء محتملون",
|
||
"competitor": f"{name_a} و{name_b} في نفس المجال التنافسي",
|
||
"vendor": f"{name_a} مورد محتمل لـ{name_b}",
|
||
"client": f"{name_a} عميل محتمل لـ{name_b}",
|
||
"referral": f"{name_a} و{name_b} في شبكة إحالات مشتركة",
|
||
}
|
||
return descriptions.get(link_type, f"علاقة بين {name_a} و{name_b}")
|