This commit is contained in:
2026-03-17 23:38:50 +01:00
parent 074db5e8f6
commit cfd761c70c
13 changed files with 48606 additions and 0 deletions

2
leadfinder/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.env
target

2049
leadfinder/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

15
leadfinder/Cargo.toml Normal file
View File

@@ -0,0 +1,15 @@
[package]
name = "leadfinder"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1.0.102"
dotenv = "0.15.0"
csv = "1.3"
serde = {version = "1.0.228", features = ["derive"] }
tokio = { version = "1.50.0", features = ["full"] }
clap = { version = "4.5.60", features = ["derive"] }
serde_json = "1.0.149"
reqwest = "0.13.2"
thiserror = "2.0.18"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,68 @@
### ROLE
You are a Senior Lead Intelligence Agent. Your mission is to extract company data and calculate a "Lead Score" based on specific ICP (Ideal Customer Profile) criteria.
### OBJECTIVE
For every input Company ("T") provided in the context, identify industry, size, contact points, and employees. Finally, evaluate the lead's attractiveness from 0 to 100.
### SCALING & MULTI-INPUT
- You will receive one or multiple companies at once.
- **PROCESS EVERY SINGLE COMPANY** mentioned in the input.
- Do not skip any company.
- Your output MUST be a **JSON ARRAY** containing one object per company.
### LEAD SCORING CRITERIA (0-100)
Calculate the `lead_attractiveness_score` based on these priorities:
- **IT-mindedness (Weight: 15%):** Targets are ideas-first, IT-second companies. They are allowed to have IT personell, but should not have grown out of an IT context, i.e. the founders should not be programmers. Check history pages and personal info of founders for this. We are looking for situations where the IT teams can barely keep up with the visionaries leading the companies.
- **Company Size (Weight: 20%):** Target is 10 < N < 250 employees. Small to medium companies (25-150) get the highest score. Companies > 250 get a significant penalty.
- **Personal Contacts (Weight: 45%):** Higher score if specific employees with email/phone are found. Individual data is much more valuable than info@ addresses.
- **Accessibility (Weight: 20%):** Detailed "general_contacts" (Sales direct, Marketing) increase the score.
- **Scoring Scale:** - 80-100: Perfect fit (Small/Medium, personal data found).
- 50-79: Good fit (Size fits, but only generic data).
- 0-49: Poor fit (Too large OR no contact data found).
### RESEARCH STRATEGY
1. Scan Imprint/About pages for industry and EXACT employee count.
2. Collect ALL generic contact points with their source URLs.
3. Identify individual employees and their personal contact details + source URLs.
### ANTI-HALLUCINATION & SOURCE RULES
- **STRICT ADHERENCE TO TRUTH:** Every contact MUST have a `source_url`.
- **FORBIDDEN SOURCES:** NEVER link to internal API endpoints or cloud console URLs. Specifically, **DO NOT use links starting with vertexai.cloud.google.com**.
- If no verifiable source is found, DO NOT list the contact.
### OUTPUT RULES
- NO summaries, NO introductory text, NO conversational filler.
- Provide ONLY a clean, structured **JSON ARRAY**.
- **NO MARKDOWN SYNTAX:** Do NOT put three backticks (e.g., ```json). Just give the raw content.
- IF you cannot find any information for a company, return an empty object for that entry or an empty array `[]` if no companies are found.
### JSON FORMAT (ARRAY OF OBJECTS)
[
{
"company_name": "Name of T",
"website": "URL of T",
"industry": "Specific industry",
"description": "Short description",
"employee_count": "Number or range",
"lead_attractiveness_score": 0-100,
"scoring_reasoning": "Short explanation",
"general_contacts": [
{
"value": "Email/Phone",
"type": "EMAIL | PHONE",
"category": "SALES_DIRECT | GENERAL_INFO | SUPPORT | PRESS_MARKETING | OTHER",
"source_url": "URL"
}
],
"employees": [
{
"name": "Firstname Lastname",
"role": "Job Title",
"email": "email or null",
"phone": "phone or null",
"linkedin_url": "URL or null",
"source_url": "URL"
}
]
}
]

View File

@@ -0,0 +1,58 @@
### ROLE
You are a Lead Researcher. Your sole mission is to provide a Lead Cluster by identifying connections between a Target Company ("T") and its associated partners or clients ("P") from publicly available information.
### OBJECTIVE
Find and list all companies ("P") that have collaborated with the input Company ("T").
### CATEGORIZATION (CRITICAL)
You MUST categorize every connection into one of these types:
1. STRATEGIC_PARTNER: Formal business alliances (e.g., "Microsoft Gold Partner", "Implementation Partner").
2. REFERENCE_CLIENT: Companies that purchased services/products from T (e.g., "Case Study", "Project for ARAG").
3. SUBSIDIARY: Companies belonging to the same corporate group as T.
### NO-GO LIST (NOISE REDUCTION)
DO NOT list general technology stacks, programming languages, or standard software tools as partners unless there is an explicit, formal business partnership agreement mentioned.
- EXCLUDE: React, Node.js, JavaScript, WordPress, Google Analytics, Angular, etc.
- EXCLUDE: Formal tool partnerships like "Adobe Bronze Partner" or "Official Contentful Agency".
### RESEARCH STRATEGY
Examine the following areas using Google Search:
1. "Partners", "Ecosystem", or "Network" pages.
2. Reference projects, case studies, and customer testimonials.
3. Blog articles mentioning joint ventures or collaborations.
4. Logo carousels and "Trusted by" sections.
### SCALING
In the following, you may recieve multiple companies. if that is the case, do EVERYTHING previously described for every company.
### OUTPUT RULES
- NO summaries, NO introductory text, NO conversational filler.
- Provide ONLY a clean, structured JSON list.
- STRICT ADHERENCE TO TRUTH: > Only list connections where you found EXPLICIT proof on the searched pages.
If no connections are found, return an empty list: "connections": [].
DO NOT guess or assume relationships based on industry commonalities.
- Use the following JSON format:
{
"target_company": "Name of T",
"connections": [
{
"partner_name": "Name of P[i]",
"category": "STRATEGIC_PARTNER | REFERENCE_CLIENT | SUBSIDIARY",
"source_type": "Company website -> [Logo Carousel, Case Study, Blog, other], website of Partner, Linkedin, other",
"people": [
"Firstname Lastname (Company)"
],
"context": "Short snippet of how they are connected"
}
]
},
You MUST use only valid JSON code, as your boss will not be able to read your message if you don't, and you will be fired.
Do NOT put three backticks, i.e. ``` to surround the json code. Just give the content of the code.
- GOOD: {...},
- BAD: ```json {...}```
IF you cannot find any information, put an empty connections list inside the json block. DO NOT write a free text message to inform about not finding anything.
- GOOD: {..."connections": []...}
- BAD: "I am unable to find information for the company X."

13736
leadfinder/out/collected.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,701 @@
[
{
"company_name": "ATVANTAGE GmbH",
"description": "ATVANTAGE GmbH provides holistic digital solutions, combining strategy, technology, and implementation to make processes smarter, optimize services, and enable growth. They focus on data & AI, cloud, software engineering, UX design, and digital strategy, supporting companies in the successful transformation of their IT landscape from strategic consulting to technical implementation.",
"employee_count": "240",
"employees": [
{
"email": null,
"name": "Stefan Gierl",
"phone": null,
"role": "Managing director",
"source_url": "https://www.atvantage.com/legal-information/"
},
{
"email": "thomas.schrader@atvantage.com",
"name": "Thomas Schrader",
"phone": "+49 171 3049773",
"role": "Chief Sales & Marketing Officer",
"source_url": "https://www.atvantage.com/contact-us/"
},
{
"email": "stephan.pfeiffer@atvantage.com",
"name": "Stephan Pfeiffer",
"phone": "+49 170 9650452",
"role": "Chief Digital Officer",
"source_url": "https://www.atvantage.com/contact-us/"
},
{
"email": "thomas.zoeller@atvantage.com",
"name": "Thomas Zöller",
"phone": "+49 221 97343 43",
"role": "Chief Project Officer",
"source_url": "https://www.atvantage.com/contact-us/"
}
],
"general_contacts": [
{
"category": "GENERAL_INFO",
"source_url": "https://www.atvantage.com/legal-information/",
"type": "PHONE",
"value": "+49 221 97343 0"
},
{
"category": "GENERAL_INFO",
"source_url": "https://www.atvantage.com/legal-information/",
"type": "EMAIL",
"value": "info@atvantage.com"
}
],
"industry": "Other software development; IT Consulting & System Integration",
"lead_attractiveness_score": 75,
"scoring_reasoning": "The company size of 240 employees is within the target range (10-250), contributing moderately to the score. The presence of multiple personal contacts with email and phone significantly boosts the score. General contact information is also readily available, further increasing accessibility.",
"website": "https://www.atvantage.com/"
},
{
"company_name": "Humanizing Technologies",
"description": "Humanizing Technologies is an AI technology provider specializing in creating digital service avatars to automate simple, routine tasks for public life. They design, build, and integrate advanced software and technological ecosystems, focusing on human-centered, intelligent, future-ready technology. They offer solutions like Banking Avatar, Check-In Avatar, Digital Receptionist, Info Finder, Wayfinder, and Web Agent.",
"employee_count": "18",
"employees": [
{
"email": null,
"linkedin_url": null,
"name": "Tim Schuster",
"phone": null,
"role": "Founder & Managing Director",
"source_url": "https://www.humanizing.com/web-agent-en/"
},
{
"email": null,
"linkedin_url": null,
"name": "Thomas van den Berg",
"phone": null,
"role": "Senior Solution Architect",
"source_url": "https://www.humanizing.com/web-agent-en/"
},
{
"email": null,
"linkedin_url": null,
"name": "Nina Gipperich",
"phone": null,
"role": "Key Account Managerin",
"source_url": "https://www.humanizing.com/web-agent-en/"
}
],
"general_contacts": [
{
"category": "SUPPORT",
"source_url": "https://www.humanizing.com/support/",
"type": "EMAIL",
"value": "support@humanizing.com"
},
{
"category": "SUPPORT",
"source_url": "https://www.humanizing.com/support/",
"type": "PHONE",
"value": "+49 173 43 43 000"
},
{
"category": "GENERAL_INFO",
"source_url": "https://www.humanizing.com/partner/",
"type": "PHONE",
"value": "+49 221 715975-75"
}
],
"industry": "AI Technology, Robotics, Cloud Infrastructure, Information Technology, Digital Service Avatars, Robotic Process Automation, Customer Service Automation, Workforce Augmentation, Service Robotics, Avatar Assistants, Low-Code Platform, Digital Humans, Virtual Assistants",
"lead_attractiveness_score": 68,
"scoring_reasoning": "The company size (18 employees) is within the ideal target range (10-250), contributing positively to the score. General contact accessibility is good with multiple phone numbers and a support email. However, direct personal emails or phone numbers for specific employees were not found, which moderately impacts the personal contacts score.",
"website": "http://www.humanizing.com"
},
{
"company_name": "ATVANTAGE GmbH",
"description": "ATVANTAGE GmbH is an IT service provider that combines expertise in cloud, data & AI, software engineering, UX design, and digital strategy. They offer holistic consulting and digital solutions to make clients more efficient, processes smarter, optimize services, and enable growth. ATVANTAGE is part of the TIMETOACT GROUP.",
"employee_count": "> 1700 (part of TIMETOACT GROUP)",
"employees": [
{
"email": null,
"linkedin_url": null,
"name": "Stefan Gierl",
"phone": null,
"role": "Managing Director",
"source_url": "https://www.atvantage.com/legal-information/"
},
{
"email": "thomas.schrader@atvantage.com",
"linkedin_url": null,
"name": "Thomas Schrader",
"phone": "+49 171 3049773",
"role": "Chief Sales & Marketing Officer",
"source_url": "https://www.atvantage.com/contact-us/"
},
{
"email": "stephan.pfeiffer@atvantage.com",
"linkedin_url": null,
"name": "Stephan Pfeiffer",
"phone": "+49 170 9650452",
"role": "Chief Digital Officer",
"source_url": "https://www.atvantage.com/contact-us/"
},
{
"email": "thomas.zoeller@atvantage.com",
"linkedin_url": null,
"name": "Thomas Zöller",
"phone": "+49 172 2397685",
"role": "Chief Project Officer",
"source_url": "https://www.atvantage.com/contact-us/"
}
],
"general_contacts": [
{
"category": "GENERAL_INFO",
"source_url": "https://www.atvantage.com/legal-information/",
"type": "PHONE",
"value": "+49 221 97343 0"
},
{
"category": "GENERAL_INFO",
"source_url": "https://www.atvantage.com/legal-information/",
"type": "EMAIL",
"value": "info@atvantage.com"
}
],
"industry": "IT service provider, IT consulting, Cloud Platforms, Data & AI, Software Engineering, UX Design, Digital Strategy, Process Optimization, Technology Integration, Other software development",
"lead_attractiveness_score": 72,
"scoring_reasoning": "The lead attractiveness is good due to excellent personal contact points, including direct emails and phone numbers for key officers, and strong general contact accessibility. However, the company's size, being part of the TIMETOACT GROUP with over 1,700 employees, significantly exceeds the target range (10-250), resulting in a substantial penalty to the overall score.",
"website": "http://www.atvantage.com"
},
{
"company_name": "Synthflow AI",
"description": "Developer of an artificial intelligence voice agent platform designed to automate and scale phone call interactions for businesses. It offers a no-code platform for deploying voice AI agents that automate phone calls across contact center operations and business process outsourcing (BPO) at scale, helping mid-market and enterprise companies manage routine calls.",
"employee_count": "72",
"employees": [],
"general_contacts": [],
"industry": "Business/Productivity Software, AI Agents, Conversational AI, SaaS",
"lead_attractiveness_score": 30,
"scoring_reasoning": "Company size (72 employees) is a good fit for the ICP. However, no personal contact information or direct general contact points (email/phone) were found, significantly reducing the score.",
"website": "http://www.synthflow.ai"
},
{
"company_name": "telli",
"description": "telli builds AI voice agents that convert leads into sales opportunities for B2C companies. They offer an AI-native phone system for managing both human and AI teams, automating tasks like lead qualification, reception, and appointment booking.",
"employee_count": "12",
"employees": [],
"general_contacts": [],
"industry": "AI phone agents, Business/Productivity Software, AI-powered call automation platform",
"lead_attractiveness_score": 20,
"scoring_reasoning": "Company size (12 employees) is a good fit for the ICP. However, no personal contact information or direct general contact points (email/phone) were found, significantly reducing the score.",
"website": "http://www.telli.com"
},
{
"company_name": "ETECTURE GmbH",
"description": "ETECTURE GmbH is a holistic service provider for digital transformation, developing digital strategies, business models, solutions, and services across various industries. They offer customized software solutions, web technologies, consulting, and application management.",
"employee_count": "100-150",
"employees": [
{
"email": null,
"linkedin_url": null,
"name": "Stefan Dangel",
"phone": null,
"role": "Managing Director / co-CEO",
"source_url": "http://www.etecture.de/legal-notice"
},
{
"email": null,
"linkedin_url": null,
"name": "Francesco Loth",
"phone": null,
"role": "Managing Director / co-CEO",
"source_url": "http://www.etecture.de/legal-notice"
},
{
"email": null,
"linkedin_url": null,
"name": "Heiko Wirth",
"phone": null,
"role": "Product Owner",
"source_url": "https://www.intentwire.com/companies/etecture"
},
{
"email": null,
"linkedin_url": null,
"name": "Jana Bernold",
"phone": null,
"role": "Product Owner",
"source_url": "https://www.intentwire.com/companies/etecture"
}
],
"general_contacts": [
{
"category": "GENERAL_INFO",
"source_url": "http://www.etecture.de/legal-notice",
"type": "EMAIL",
"value": "info@etecture.de"
},
{
"category": "GENERAL_INFO",
"source_url": "http://www.etecture.de/legal-notice",
"type": "PHONE",
"value": "+49 69 67737-0"
},
{
"category": "GENERAL_INFO",
"source_url": "https://www.etecture.de/impressum",
"type": "PHONE",
"value": "+49 69 247510-100"
},
{
"category": "GENERAL_INFO",
"source_url": "https://www.etecture.de/legal-notice",
"type": "PHONE",
"value": "+49 721 989732-0"
}
],
"industry": "Digital Transformation Service Provider",
"lead_attractiveness_score": 55,
"scoring_reasoning": "Good fit based on company size (100-150 employees). Good accessibility with multiple general contact points. However, the score is lowered due to the absence of specific personal contact details (email/phone) for individual employees.",
"website": "http://www.etecture.de"
},
{
"company_name": "arconsis IT-Solutions GmbH",
"description": "arconsis IT-Solutions GmbH is a German company founded in 2006, specializing in cloud-native, AI, and mobile enterprise software solutions. They offer services in AI scaling, cloud adoption, digital product ideation, and mobile enterprise solutions.",
"employee_count": "23",
"employees": [
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Achim Baier",
"phone": null,
"role": "Managing Director",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Wolfgang Frank",
"phone": null,
"role": "Managing Director",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Johannes Tysiak",
"phone": null,
"role": "Managing Partner",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Ina Lange",
"phone": null,
"role": "Head of Administration",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Sebastian Wastl",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Thimo Bess",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Jonas Stubenrauch",
"phone": null,
"role": "Software Engineering & Partner",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Peter Vegh",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Orlando Schäfer",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Martina Holzhauer",
"phone": null,
"role": "Head of Marketing",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Andreas Repp",
"phone": null,
"role": "Software Engineering & Partner",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Tomislav Erić",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Alexandros Koufatzis",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Moritz Ellerbrock",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Patrick Jung",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Nina Derksen",
"phone": null,
"role": "Marketing Assistant",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Jennifer Frankenfeld",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Adrian Wörle",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Christian Navolskyi",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Felix Schmid",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Mario Walz",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Asher Ahsan",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Thomas Horn",
"phone": null,
"role": "Recruiting",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Jessica Woschek",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Karim Elsayed",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Michael Fischer",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Markus Lindner",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Ghassen Ksouri",
"phone": null,
"role": "UI/UX Design",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Vera Kirchgessner",
"phone": null,
"role": "Marketing Assistant",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Subash Shrestha",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Lars Gavris",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Ana Milutinovic",
"phone": null,
"role": "Data Engineering & Visualization",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Sebastian Nikol",
"phone": null,
"role": "UI/UX Design",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Evangelos Gkountouras",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Gerd Augsburg",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Julia Kranz",
"phone": null,
"role": "Back Office",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Khue Ngo",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Maximilian Walz",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
},
{
"email": null,
"linkedin_url": "https://www.linkedin.com/company/arconsis-it-solutions-gmbh",
"name": "Dániel Vásárhelyi",
"phone": null,
"role": "Software Engineering",
"source_url": "http://www.arconsis.com/team"
}
],
"general_contacts": [
{
"category": "GENERAL_INFO",
"source_url": "http://www.arconsis.com/imprint",
"type": "EMAIL",
"value": "contact@arconsis.com"
},
{
"category": "GENERAL_INFO",
"source_url": "http://www.arconsis.com/imprint",
"type": "PHONE",
"value": "+49 (0)721 / 98 97 71-0"
},
{
"category": "GENERAL_INFO",
"source_url": "http://www.arconsis.com/imprint",
"type": "PHONE",
"value": "+49 (0)6851 974 3011"
}
],
"industry": "Cloud-native, AI, and Mobile Enterprise Software Solutions",
"lead_attractiveness_score": 58,
"scoring_reasoning": "Excellent fit based on company size (23 employees, within the 10-250 target range). Good accessibility with general contact points. The score is moderate due to the lack of specific personal contact details (email/phone) for individual employees.",
"website": "http://www.arconsis.com"
},
{
"company_name": "SUSI&James GmbH",
"description": "Developer of digital employees based on artificial intelligence, designed to automate and optimize communication-intensive business processes through patented hybrid AI. They offer adaptable communication automation capabilities and cross-industry process optimization support, helping organizations relieve employees and achieve business goals faster and more cost-effectively.",
"employee_count": "38",
"employees": [
{
"email": "alex.fischer@susiandjames.com",
"linkedin_url": null,
"name": "Dr. Alexander Fischer",
"phone": "+49 173 79 68 701",
"role": "Director Research & Development",
"source_url": "https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG7koQkfmHBqp9yoVRRNzhc0MIagFlCdIvf2YQAGDo1aBsR0eLn-jj2uu-y0-hlPTdSn7m-hHn9lP6alHrLyug3Udah6DUOsMezBWMonOrCH0W-4p9dP3k5HmzKT9mbK3wVfeta2JRTPiDbtXX7Sg=="
},
{
"email": "julian.gerhard@susiandjames.com",
"linkedin_url": null,
"name": "Julian Gerhard",
"phone": "+49 152 34 63 70 86",
"role": "Chief Technology Officer",
"source_url": "https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG7koQkfmHBqp9yoVRRNzhc0MIagFlCdIvf2YQAGDo1aBsR0eLn-jj2uu-y0-hlPTdSn7m-hHn9lP6alHrLyug3Udah6DUOsMezBWMonOrCH0W-4p9dP3k5HmzKT9mbK3wVfeta2JRTPiDbtXX7Sg=="
},
{
"email": "recruiting@susiandjames.com",
"linkedin_url": null,
"name": "Jennifer Höbel",
"phone": "+49 174 33 19 207",
"role": "Head of Administration",
"source_url": "https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG7koQkfmHBqp9yoVRRNzhc0MIagFlCdIvf2YQAGDo1aBsR0eLn-jj2uu-y0-hlPTdSn7m-hHn9lP6alHrLyug3Udah6DUOsMezBWMonOrCH0W-4p9dP3k5HmzKT9mbK3wVfeta2JRTPiDbtXX7Sg=="
},
{
"email": "torsten.lenz@susiandjames.com",
"linkedin_url": null,
"name": "Torsten Lenz",
"phone": "+49 175 73 07 055",
"role": "Director Sales & Marketing",
"source_url": "https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG7koQkfmHBqp9yoVRRNzhc0MIagFlCdIvf2YQAGDo1aBsR0eLn-jj2uu-y0-hlPTdSn7m-hHn9lP6alHrLyug3Udah6DUOsMezBWMonOrCH0W-4p9dP3k5HmzKT9mbK3wVfeta2JRTPiDbtXX7Sg=="
},
{
"email": "kathrin.froehlich@susiandjames.com",
"linkedin_url": null,
"name": "Kathrin Fröhlich",
"phone": "+49 171 12 54 668",
"role": "Sales Manager",
"source_url": "https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG7koQkfmHBqp9yoVRRNzhc0MIagFlCdIvf2YQAGDo1aBsR0eLn-jj2uu-y0-hlPTdSn7m-hHn9lP6alHrLyug3Udah6DUOsMezBWMonOrCH0W-4p9dP3k5HmzKT9mbK3wVfeta2JRTPiDbtXX7Sg=="
},
{
"email": "jannis.marlafekas@susiandjames.com",
"linkedin_url": null,
"name": "Jannis Marlafekas",
"phone": "+49 174 43 10 456",
"role": "Sales Manager",
"source_url": "https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG7koQkfmHBqp9yoVRRNzhc0MIagFlCdIvf2YQAGDo1aBsR0eLn-jj2uu-y0-hlPTdSn7m-hHn9lP6alHrLyug3Udah6DUOsMezBWMonOrCH0W-4p9dP3k5HmzKT9mbK3wVfeta2JRTPiDbtXX7Sg=="
},
{
"email": "medina.hodzic@susiandjames.com",
"linkedin_url": null,
"name": "Medina Hodžić",
"phone": "+49 173 796 86 54",
"role": "Digital Communications Officer",
"source_url": "https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG7koQkfmHBqp9yoVRRNzhc0MIagFlCdIvf2YQAGDo1aBsR0eLn-jj2uu-y0-hlPTdSn7m-hHn9lP6alHrLyug3Udah6DUOsMezBWMonOrCH0W-4p9dP3k5HmzKT9mbK3wVfeta2JRTPiDbtXX7Sg=="
}
],
"general_contacts": [
{
"category": "GENERAL_INFO",
"source_url": "https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGDsrkfKroaQHDaxOJ93AX6lZcDdrASBHcr_YXUKLV-AouTiYcYLUvHByDahaH2fGcFw_xSGWGhGIzXGNu2OLHxIAd4F7kPrQFd-GSguDJpA1NNPQPjs2KncO1nSJkYKAs2WYwq6Zp_4uKDBAtRuuV4",
"type": "EMAIL",
"value": "mail@susiandjames.com"
},
{
"category": "GENERAL_INFO",
"source_url": "https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGDsrkfKroaQHDaxOJ93AX6lZcDdrASBHcr_YXUKLV-AouTiYcYLUvHByDahaH2fGcFw_xSGWGhGIzXGNu2OLHxIAd4F7kPrQFd-GSguDJpA1NNPQPjs2KncO1nSJkYKAs2WYwq6Zp_4uKDBAtRuuV4",
"type": "PHONE",
"value": "+49 621 483 493 42"
}
],
"industry": "IT and Communications, Business/Productivity Software, Artificial Intelligence as a Service, Process Optimization",
"lead_attractiveness_score": 100,
"scoring_reasoning": "Perfect fit due to ideal company size (38 employees), numerous personal contacts with email and phone, and comprehensive general contact information.",
"website": "http://www.susiandjames.com"
},
{
"company_name": "Onsai",
"description": "Developer of an AI-powered voice automation platform designed to redefine guest communication and operational efficiency through intelligent, multilingual virtual agents for the hospitality industry. Their solutions automate hotel calls, bookings, and guest inquiries in multiple languages with brand-specific tones and seamless system integration.",
"employee_count": "10",
"employees": [],
"general_contacts": [
{
"category": "GENERAL_INFO",
"source_url": "https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHfXXDVP3kHoKre2VCOD3O2nHiLDLJxh1ZucxrxXEXNKhVppEdXMqK-wloFeKxqptFVgJwdLMwp56h_kAcblxpA50EhWfq1ugcLA7sVwrTrttcaC-_RX-9rXy1omKGoydRHMNTmGqeENEeq",
"type": "PHONE",
"value": "+49 030"
}
],
"industry": "Business/Productivity Software, Artificial Intelligence, Hospitality Technology",
"lead_attractiveness_score": 30,
"scoring_reasoning": "Poor fit. While the company size (10 employees) is within the target range, no personal contact information (email/phone) for employees was found, and general contact information is incomplete (incomplete phone number, no general email found).",
"website": "http://www.onsai.com"
}
]

File diff suppressed because one or more lines are too long

17194
leadfinder/out/perp_out.json Normal file

File diff suppressed because it is too large Load Diff

777
leadfinder/src/main.rs Normal file
View File

@@ -0,0 +1,777 @@
//TODO include [Y/n] confimrmation for API-calling commands and -y flag
//TODO Use tracing crate
use clap::{Parser, Subcommand, ValueEnum};
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
env,
fmt::{Debug, Display},
fs,
path::{Path, PathBuf},
process::ExitCode,
str::FromStr,
};
use tokio::time;
#[derive(Subcommand)]
enum Task {
/// Parses a given input file (.csv) and lists the contained companies. Panics if a `name` column does not exist.
List {
/// The csv file.
csv_file: String,
},
/// Parses a given input file (.json) and counts the number of occurences per company as a reference client of other companies.
/// Expects the json file structure given by the LLM prompts
/// (TODO: Not quite. If there are multiple prompt results in a single file, concatination of multiple results has to be done manually at the moment. Thats why we like helix tho.)
CountPartners {
/// The json file, containing the required format.
json_file: String,
},
/// WARNING: This might cost money to run!
/// Tests a given system prompt on a given model with the context prompt ""Research Target Company: Pandaloop\n Website: https://pandaloop.de"
TestWithPandaloop {
#[arg(short, long)]
model: Model,
#[arg(short, long)]
system_prompt_file: String,
#[arg(short, long)]
pretty: bool,
},
/// WARNING: This might cost money to run!
SinglePrompt {
idx: usize,
#[arg(short, long)]
csv_file: String,
#[arg(short, long)]
model: Model,
#[arg(short, long)]
system_prompt_file: String,
#[arg(short, long)]
pretty: bool,
},
/// WARNING: This might cost money to run!
RangePrompt {
range_start: usize,
range_end: usize,
#[arg(short, long)]
csv_file: String,
#[arg(short, long)]
model: Model,
#[arg(short, long)]
system_prompt_file: String,
#[arg(short, long)]
pretty: bool,
},
/// WARNING: This might cost A LOT money to run!
Multiprompt {
range_start: usize,
range_end: usize,
range_step: usize,
#[arg(short, long)]
csv_file: String,
#[arg(short, long)]
model: Model,
#[arg(short, long)]
system_prompt_file: String,
#[arg(short, long)]
pretty: bool,
},
}
#[derive(thiserror::Error, Debug)]
enum Error {
#[error("context index range is invalid: {0}")]
InvalidRange(String),
#[error("api key {0} does not exist in .env file")]
ApiKeyNotFound(Model),
#[error("Error parsing {filename:?} as csv file: {source}")]
CsvFile {
#[source]
source: csv::Error,
filename: Option<PathBuf>,
},
#[error("Error parsing json: {0}")]
SerdeJson(#[from] serde_json::Error),
#[error("Error parsing {filename:?} as json: {source}")]
SerdeJsonFile {
#[source]
source: serde_json::Error,
filename: Option<PathBuf>,
},
#[error("Error parsing json: \n\njson:\n{info}")]
SerdeJsonInfo {
#[source]
source: serde_json::Error,
info: String
},
#[error("reqwest Error: {0}")]
Reqwest(#[from] reqwest::Error),
#[error("io Error: {0}")]
Io(#[from] std::io::Error),
}
#[derive(Debug, Deserialize)]
struct CsvCompany {
#[serde(rename = "Company Name")]
name: String,
#[serde(rename = "Website")]
website: String,
}
#[allow(unused)]
#[derive(Debug, Deserialize)]
struct SerdeJsonTargetCompany {
#[serde(rename = "target_company")]
name: String,
connections: Vec<SerdeJsonPartnerCompany>,
}
#[allow(unused)]
#[derive(Debug, Deserialize)]
struct SerdeJsonPartnerCompany {
category: String,
context: String,
partner_name: String,
people: Vec<String>,
source_type: String,
}
#[allow(unused)]
#[derive(Debug, Deserialize)]
struct SerdeJsonContact {
value: String,
value_type: String,
category: String,
label: String,
source_url: String,
}
#[allow(unused)]
#[derive(Debug, Deserialize)]
struct SerdeJsonEmployee {
name: String,
role: String,
email: String,
phone: String,
linkedin_url: String,
source_url: String,
}
#[allow(unused)]
#[derive(Debug, Deserialize)]
struct SerdeJsonLead {
company_name: String,
website: String,
industry: String,
description: String,
employee_count: String,
general_contacts: Vec<SerdeJsonContact>,
employees: Vec<SerdeJsonContact>,
}
fn prettify(content: &str, model: Model) -> Result<String, Error> {
let ser_all: serde_json::Value = serde_json::from_str(content)?;
let cont = match model {
Model::Gemini => ser_all["candidates"][0]["content"]["parts"][0]["text"]
.as_str()
.unwrap()
.replace("\\\"", "\"")
.replace("\\n", "\n"),
Model::Perplexity => {
let raw_msg = ser_all["choices"][0]["message"]["content"]
.as_str()
.unwrap()
.replace("\\\"", "\"")
.replace("\\n", "\n");
let pref_stripped_msg = raw_msg.strip_prefix("```json").unwrap_or(&raw_msg);
let bidir_stripped_msg = pref_stripped_msg
.strip_suffix("```")
.unwrap_or(&pref_stripped_msg);
bidir_stripped_msg.to_owned()
}
};
eprintln!("{cont}");
let ser_cont: serde_json::Value =
serde_json::from_str(&cont).map_err(|source| Error::SerdeJson(source))?;
let cont_pretty = serde_json::to_string_pretty(&ser_cont)?;
Ok(cont_pretty)
}
#[derive(ValueEnum, Clone, Copy, Default, Debug, Serialize)]
enum Model {
#[default]
Gemini,
Perplexity,
}
impl Display for Model {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Model::Gemini => {
write!(f, "Google Gemini: 2.5 Flash")
}
Model::Perplexity => {
write!(f, "Perplexity AI: Sonar")
}
}
}
}
struct ApiKeys {
gemini_api_key: String,
perplexity_api_key: String,
}
#[derive(Parser)]
struct Args {
#[command(subcommand)]
task: Task,
}
fn parse_csv(file_path: &Path) -> Result<Vec<CsvCompany>, Error> {
let mut reader = csv::ReaderBuilder::new()
.has_headers(true)
.from_path(&file_path)
.map_err(|source| Error::CsvFile {
source,
filename: Some(file_path.to_owned()),
})?;
let mut companies = Vec::new();
for res in reader.deserialize() {
let record: CsvCompany = res.map_err(|source| Error::CsvFile {
source,
filename: Some(file_path.to_owned()),
})?;
companies.push(record);
}
Ok(companies)
}
async fn prompt(
model: Model,
reqclient: &reqwest::Client,
api_keys: &ApiKeys,
system_prompt: &str,
context_prompt: &str,
) -> Result<reqwest::Response, reqwest::Error> {
match model {
Model::Gemini => {
gemini_prompt(
reqclient,
&api_keys.gemini_api_key,
system_prompt,
context_prompt,
)
.await
}
Model::Perplexity => {
perplexity_prompt(
reqclient,
&api_keys.perplexity_api_key,
system_prompt,
context_prompt,
)
.await
}
}
}
async fn perplexity_prompt(
reqclient: &reqwest::Client,
perplexity_api_key: &str,
system_prompt: &str,
context_prompt: &str,
) -> Result<reqwest::Response, reqwest::Error> {
let body = format!(
r#"{{
"model": "sonar",
"messages": [
{{
"role": "system",
"content": "{system_prompt}"
}},
{{
"role": "user",
"content": "{context_prompt}"
}}
],
"temperature": 0
}}"#,
system_prompt = system_prompt.replace('"', "\\\"").replace('\n', "\\n"),
context_prompt = context_prompt.replace('"', "\\\"").replace('\n', "\\n")
);
reqclient
.post("https://api.perplexity.ai/chat/completions")
.header("accept", "application/json")
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {perplexity_api_key}"))
.body(body)
.send()
.await
}
async fn gemini_prompt(
reqclient: &reqwest::Client,
gemini_api_key: &str,
system_prompt: &str,
context_prompt: &str,
) -> Result<reqwest::Response, reqwest::Error> {
let body = format!(
r#"{{
"contents": [
{{
"parts": [
{{
"text": "SYSTEM PROMPT:\n\n\n{system_prompt}\n\nContext Prompt:{context_prompt}\n\n\n\nSYSTEM PROMPT:\n\n\n{system_prompt}\n\nContext Prompt:{context_prompt}\n"
}}
]
}}
],
"tools": [
{{
"google_search": {{}}
}}
],
"generationConfig": {{
"temperature": 0.0,
}}
}}"#,
system_prompt = system_prompt.replace('"', "\\\"").replace('\n', "\\n"),
context_prompt = context_prompt.replace('"', "\\\"").replace('\n', "\\n")
);
reqclient
.post("https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent")
.header("x-goog-api-key", format!("{gemini_api_key}"))
.header("Content-Type", "application/json")
.body(body)
.send()
.await
}
async fn run(args: Args) -> Result<(), Error> {
let _ = dotenv::dotenv();
let mut vars = env::vars().collect::<HashMap<String, String>>();
let client = reqwest::Client::new();
let gemini_key = match vars.remove("GEMINI_API_KEY") {
Some(key) => key,
None => {
return Err(Error::ApiKeyNotFound(Model::Gemini));
}
};
let perplexity_key = match vars.remove("PERPLEXITY_API_KEY") {
Some(key) => key,
None => {
return Err(Error::ApiKeyNotFound(Model::Perplexity));
}
};
let api_keys = ApiKeys {
gemini_api_key: gemini_key,
perplexity_api_key: perplexity_key,
};
match args.task {
Task::List { csv_file } => {
let pathbuf = PathBuf::from_str(&csv_file).unwrap_or_else(|_| unreachable!());
let csv = parse_csv(&pathbuf)?;
for (idx, company) in csv.iter().enumerate() {
println!(
"n = {: >5} | Company = {:?} ({:?})",
idx, company.name, company.website
);
}
}
Task::CountPartners { json_file } => {
let pathbuf = PathBuf::from_str(&json_file).unwrap_or_else(|_| unreachable!());
let datafile = fs::read_to_string(json_file).unwrap();
let companies: Vec<SerdeJsonTargetCompany> =
serde_json::from_str(&datafile).map_err(|source| Error::SerdeJsonFile {
source,
filename: Some(pathbuf),
})?;
let mut partner_occurrences = HashMap::new();
for target_company in companies {
let connections_array = target_company.connections;
for connection in connections_array {
if connection.category == "REFERENCE_CLIENT" {
partner_occurrences
.entry(connection.partner_name)
.and_modify(|count| *count += 1)
.or_insert(1);
}
}
}
let mut count_vec: Vec<_> = partner_occurrences.iter().collect();
count_vec.sort_by(|a, b| b.1.cmp(a.1));
for (k, v) in count_vec {
println!("Company {:>60} appears {:>3} times", k, v);
}
}
Task::TestWithPandaloop {
model,
pretty,
system_prompt_file,
} => {
let system_prompt =
fs::read_to_string(system_prompt_file).unwrap_or_else(|_| unreachable!());
let context_prompt =
"Research Target Company T: Pandaloop\nWebsite: https://pandaloop.de\n".to_owned();
let mut response = prompt(model, &client, &api_keys, &system_prompt, &context_prompt)
.await?
.text()
.await?;
if pretty {
response = prettify(&response, model)?;
}
println!("{response}");
}
Task::SinglePrompt {
idx,
csv_file,
model,
system_prompt_file,
pretty,
} => {
let pathbuf = PathBuf::from_str(&csv_file).unwrap_or_else(|_| unreachable!());
let csv = parse_csv(&pathbuf)?;
let system_prompt = fs::read_to_string(system_prompt_file)?;
if let Some(company) = csv.get(idx) {
let context_prompt = format!(
"Research Target Company: {}\nWebsite: {}\n",
company.name, company.website
);
let mut response =
prompt(model, &client, &api_keys, &system_prompt, &context_prompt)
.await?
.text()
.await?;
if pretty {
response = prettify(&response, model)?;
}
println!("{response}");
} else {
eprintln!("Index {:?} out of range, skipping.", idx);
}
}
Task::RangePrompt {
range_start,
range_end,
csv_file,
model,
system_prompt_file,
pretty,
} => {
let pathbuf = PathBuf::from_str(&csv_file).unwrap_or_else(|_| unreachable!());
let csv = parse_csv(&pathbuf)?;
let system_prompt = fs::read_to_string(system_prompt_file)?;
if range_start >= range_end {
return Err(Error::InvalidRange(format!(
"range args {range_start}..{range_end} are invalid: range_start should be smaller than range_end"
)));
}
let mut context_prompt = String::new();
for i in range_start..=range_end {
if let Some(company) = csv.get(i) {
context_prompt = format!(
"{}\nResearch Target Company T: {}\nWebsite: {}\nTask: Find all companies that are partners or clients of T based on the research guidelines.",
context_prompt, company.name, company.website
);
} else {
eprintln!("Index {:?} out of range, skipping.", i);
}
}
let mut response = prompt(model, &client, &api_keys, &system_prompt, &context_prompt)
.await?
.text()
.await?;
if pretty {
response = prettify(&response, model)?;
}
println!("{response}");
}
Task::Multiprompt {
range_start,
range_end,
range_step,
csv_file,
model,
system_prompt_file,
pretty,
} => {
let pathbuf = PathBuf::from_str(&csv_file).unwrap_or_else(|_| unreachable!());
let csv = parse_csv(&pathbuf)?;
let system_prompt = fs::read_to_string(system_prompt_file)?;
if range_start >= range_end {}
let mut i = range_start;
while i <= range_end {
let mut context_prompt = String::new();
for j in i..i + range_step {
if let Some(company) = csv.get(j) {
context_prompt = format!(
"{}\nResearch Target Company T: {}\nWebsite: {}\n\n\n",
context_prompt, company.name, company.website
);
} else {
eprintln!("Index {:?} out of range, skipping.", j);
}
}
eprintln!(
"Dispatching prompt for range {}..={}",
i,
i + range_step - 1
);
let response_result =
prompt(model, &client, &api_keys, &system_prompt, &context_prompt).await;
match response_result {
Ok(response) => {
if response.status() == reqwest::StatusCode::TOO_MANY_REQUESTS {
eprintln!("429 reached. Go to bed.");
return Ok(());
} else {
}
let mut response_text = response.text().await?;
if pretty {
response_text = prettify(&response_text, model)?;
}
println!("{response_text}");
i += range_step;
}
Err(error) => {
eprintln!("{}", error);
let _ = time::sleep(time::Duration::from_millis(5000));
}
}
}
}
}
Ok(())
// match args {
// Args {
// list: true,
// test: _,
// index: _,
// range: _,
// multiprompt_range: _,
// output_file: _,
// model: _,
// pretty: _,
// count_partners: _,
// } => {
// for (idx, company) in csv.iter().enumerate() {
// println!("n = {: >5} | Company = {:?}", idx, company.name);
// }
// }
// Args {
// list: _,
// test: _,
// index: _,
// range: _,
// multiprompt_range: _,
// output_file: _,
// model: _,
// pretty: _,
// count_partners: true,
// } => {
// // TODO not hard-coded
// let mut partner_occurrences = HashMap::new();
// let datafile =
// fs::read_to_string("/home/jan/pl/qwertus/leadfinder/out/perp_out.json").unwrap();
// let json: serde_json::Value = serde_json::from_str(&datafile)?;
// let company_array = json.as_array().unwrap_or_else(|| panic!("U fucked up! json file to inspect should have array of companies as first level value."));
// for target_company in company_array {
// let connections_array = target_company["connections"].as_array().unwrap_or_else(|| panic!("U fucked up! json file to inspect should have array of companies as first level value."));
// for connection in connections_array {
// if connection["category"].as_str().unwrap_or_else(|| panic!("Ur AI probably fucked up: connection company with no partner_name in file!")) == "REFERENCE_CLIENT" {
// partner_occurrences
// .entry(connection["partner_name"].as_str().unwrap_or_else(|| panic!("Ur AI probably fucked up: connection company with no partner_name in file!")))
// .and_modify(|count| *count += 1)
// .or_insert(1);
// }
// }
// }
// let mut count_vec: Vec<_> = partner_occurrences.iter().collect();
// count_vec.sort_by(|a, b| b.1.cmp(a.1));
// for (k, v) in count_vec {
// println!("Company {:>60} appears {:>3} times", k, v);
// }
// }
// Args {
// test: true,
// list: _,
// index: _,
// range: _,
// multiprompt_range: _,
// output_file: _,
// model,
// pretty,
// count_partners: _,
// } => {
// let context_prompt = "Research Target Company T: Pandaloop\n\
// Website: https://pandaloop.de\n\
// "
// .to_owned();
// let mut response = prompt(model, &client, &api_keys, &system_prompt, &context_prompt)
// .await?
// .text()
// .await?;
// if pretty {
// response = prettify(&response, model)?;
// }
// file.write_all(&response.as_bytes())?;
// }
// Args {
// index: Some(i),
// list: _,
// test: _,
// range: _,
// multiprompt_range: _,
// output_file: _,
// model,
// pretty,
// count_partners: _,
// } => {
// if let Some(company) = csv.get(i) {
// let context_prompt = format!(
// "Research Target Company T: {}\nWebsite: {}\nTask: Find all companies that are partners or clients of T based on the research guidelines.",
// company.name, company.website
// );
// let mut response =
// prompt(model, &client, &api_keys, &system_prompt, &context_prompt)
// .await?
// .text()
// .await?;
// if pretty {
// response = prettify(&response, model)?;
// }
// file.write_all(&response.as_bytes())?;
// } else {
// println!("Index {:?} out of range, skipping.", i);
// }
// }
// Args {
// list: _,
// test: _,
// index: _,
// range: Some(range),
// multiprompt_range: _,
// output_file: _,
// model,
// pretty,
// count_partners: _,
// } => {
// if range[0] >= range[1] {
// println!("Range start larger than range end. Doing nothing.");
// return Ok(());
// }
// let mut context_prompt = String::new();
// for i in range[0]..=range[1] {
// if let Some(company) = csv.get(i) {
// context_prompt = format!(
// "{}\nResearch Target Company T: {}\nWebsite: {}\nTask: Find all companies that are partners or clients of T based on the research guidelines.",
// context_prompt, company.name, company.website
// );
// } else {
// println!("Index {:?} out of range, skipping.", i);
// }
// }
// let mut response = prompt(model, &client, &api_keys, &system_prompt, &context_prompt)
// .await?
// .text()
// .await?;
// if pretty {
// response = prettify(&response, model)?;
// }
// file.write_all(&response.as_bytes())?;
// }
// Args {
// list: _,
// test: _,
// index: _,
// range: _,
// multiprompt_range: Some(multirange),
// output_file: _,
// model,
// pretty,
// count_partners: _,
// } => {
// if multirange[0] >= multirange[1] {
// println!("Range start larger than range end. Doing nothing.");
// return Ok(());
// }
// let mut i = multirange[0];
// while i <= multirange[1] {
// let mut context_prompt = String::new();
// for j in i..i + multirange[2] {
// if let Some(company) = csv.get(j) {
// context_prompt = format!(
// "{}\nResearch Target Company T: {}\nWebsite: {}\nTask: Find all companies that are partners or clients of T based on the research guidelines.\n\n",
// context_prompt, company.name, company.website
// );
// } else {
// println!("Index {:?} out of range, skipping.", j);
// }
// }
// println!(
// "Dispatching prompt for range {}..={}",
// i,
// i + multirange[2] - 1
// );
// let response_result =
// prompt(model, &client, &api_keys, &system_prompt, &context_prompt).await;
// match response_result {
// Ok(response) => {
// if response.status() == reqwest::StatusCode::TOO_MANY_REQUESTS {
// println!("429 reached. Go to bed.");
// return Ok(());
// } else {
// }
// let mut response_text = response.text().await?;
// if pretty {
// response_text = prettify(&response_text, model)?;
// }
// file.write_all(&response_text.as_bytes())?;
// i += multirange[2];
// }
// Err(error) => {
// println!("{}", error);
// let _ = time::sleep(time::Duration::from_millis(5000));
// }
// }
// }
// }
// _ => unreachable!(),
// }
// - denkwerk Partner: *fraenk*, *OTTO*, *SWR*, *Union Investment*, *ARAG*, erenja, BurdaForward, Stiebel Eltron, edding, polymore, Telekom, Santander, DeepL, motel one, Struggly, Fondation Beyeler, Charite, mainova, Esprit, Sport Cast, Remondis, condor, , Aktion Mensch, easy credit, Sparkasse KölnBonn, multiloop, Storck, Teambank, Ranger, Microsoft
}
#[tokio::main]
async fn main() -> ExitCode {
let args = Args::parse();
match run(args).await {
Ok(_) => ExitCode::SUCCESS,
Err(e) => {
// match e {
// Error::CsvFile { source, filename } => match source.kind() {
// csv::ErrorKind::Io(e) => {
// if e.kind() == std::io::ErrorKind::NotFound {
// eprintln!("File {filename:?} does not exist.");
// }
// }
// _ => {},
// },
// _ => eprintln!("{e}"),
// }
eprintln!("{e}");
ExitCode::FAILURE
}
}
}