tanyar09 8de65bc04c Add initial project structure for Job Market Intelligence platform
- Created core modules: `ai-analyzer`, `core-parser`, and `job-search-parser`.
- Implemented LinkedIn and job search parsers with integrated AI analysis.
- Added CLI tools for AI analysis and job parsing.
- Included comprehensive README files for each module detailing usage and features.
- Established a `.gitignore` file to exclude unnecessary files.
- Introduced sample data for testing and demonstration purposes.
- Set up package.json files for dependency management across modules.
- Implemented logging and error handling utilities for better debugging and user feedback.
2025-12-12 14:23:01 -05:00

413 lines
13 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* LinkedIn Parser Demo
*
* Demonstrates the LinkedIn Parser's capabilities for scraping LinkedIn content
* with keyword-based searching, location filtering, and AI analysis.
*
* This demo uses simulated data for safety and demonstration purposes.
*/
const { logger } = require("../ai-analyzer");
const fs = require("fs");
const path = require("path");
// Terminal colors for demo output
const colors = {
reset: "\x1b[0m",
bright: "\x1b[1m",
cyan: "\x1b[36m",
green: "\x1b[32m",
yellow: "\x1b[33m",
blue: "\x1b[34m",
magenta: "\x1b[35m",
red: "\x1b[31m",
};
const demo = {
title: (text) =>
console.log(`\n${colors.bright}${colors.cyan}${text}${colors.reset}`),
section: (text) =>
console.log(`\n${colors.bright}${colors.magenta}${text}${colors.reset}`),
success: (text) => console.log(`${colors.green}${text}${colors.reset}`),
info: (text) => console.log(`${colors.blue} ${text}${colors.reset}`),
warning: (text) => console.log(`${colors.yellow}⚠️ ${text}${colors.reset}`),
error: (text) => console.log(`${colors.red}${text}${colors.reset}`),
code: (text) => console.log(`${colors.cyan}${text}${colors.reset}`),
};
// Mock data for demonstration
const mockPosts = [
{
id: "post_1",
content:
"Just got laid off from my software engineering role at TechCorp. Looking for new opportunities in Toronto. This is really tough but I'm staying positive!",
original_content:
"Just got #laidoff from my software engineering role at TechCorp! Looking for new opportunities in #Toronto. This is really tough but I'm staying positive! 🚀",
author: {
name: "John Doe",
title: "Software Engineer",
company: "TechCorp",
location: "Toronto, Ontario, Canada",
profile_url: "https://linkedin.com/in/johndoe",
},
engagement: { likes: 45, comments: 12, shares: 3 },
metadata: {
post_date: "2024-01-10T14:30:00Z",
scraped_at: "2024-01-15T10:30:00Z",
search_keyword: "layoff",
location_validated: true,
},
},
{
id: "post_2",
content:
"Our company is downsizing and I'm affected. This is really tough news but I'm grateful for the time I had here.",
original_content:
"Our company is #downsizing and I'm affected. This is really tough news but I'm grateful for the time I had here. #RIF #layoff",
author: {
name: "Jane Smith",
title: "Product Manager",
company: "StartupXYZ",
location: "Vancouver, British Columbia, Canada",
profile_url: "https://linkedin.com/in/janesmith",
},
engagement: { likes: 23, comments: 8, shares: 1 },
metadata: {
post_date: "2024-01-09T16:45:00Z",
scraped_at: "2024-01-15T10:30:00Z",
search_keyword: "downsizing",
location_validated: true,
},
},
{
id: "post_3",
content:
"Open to work! Looking for new opportunities in software development. I have 5 years of experience in React, Node.js, and cloud technologies.",
original_content:
"Open to work! Looking for new opportunities in software development. I have 5 years of experience in #React, #NodeJS, and #cloud technologies. #opentowork #jobsearch",
author: {
name: "Bob Wilson",
title: "Full Stack Developer",
company: "Freelance",
location: "Calgary, Alberta, Canada",
profile_url: "https://linkedin.com/in/bobwilson",
},
engagement: { likes: 67, comments: 15, shares: 8 },
metadata: {
post_date: "2024-01-08T11:20:00Z",
scraped_at: "2024-01-15T10:30:00Z",
search_keyword: "open to work",
location_validated: true,
},
},
];
async function runDemo() {
demo.title("=== LinkedIn Parser Demo ===");
demo.info(
"This demo showcases the LinkedIn Parser's capabilities for scraping LinkedIn content."
);
demo.info("All data shown is simulated for demonstration purposes.");
demo.info("Press Enter to continue through each section...\n");
await waitForEnter();
// 1. Configuration Demo
await demonstrateConfiguration();
// 2. Keyword Loading Demo
await demonstrateKeywordLoading();
// 3. Search Process Demo
await demonstrateSearchProcess();
// 4. Location Filtering Demo
await demonstrateLocationFiltering();
// 5. AI Analysis Demo
await demonstrateAIAnalysis();
// 6. Output Generation Demo
await demonstrateOutputGeneration();
demo.title("=== Demo Complete ===");
demo.success("LinkedIn Parser demo completed successfully!");
demo.info("Check the README.md for detailed usage instructions.");
}
async function demonstrateConfiguration() {
demo.section("1. Configuration Setup");
demo.info(
"The LinkedIn Parser uses environment variables and command-line options for configuration."
);
demo.code("// Environment Variables (.env file)");
demo.info("LINKEDIN_USERNAME=your_email@example.com");
demo.info("LINKEDIN_PASSWORD=your_password");
demo.info("CITY=Toronto");
demo.info("DATE_POSTED=past-week");
demo.info("SORT_BY=date_posted");
demo.info("WHEELS=5");
demo.info("LOCATION_FILTER=Ontario,Manitoba");
demo.info("ENABLE_LOCATION_CHECK=true");
demo.info("ENABLE_LOCAL_AI=true");
demo.info('AI_CONTEXT="job layoffs and workforce reduction"');
demo.info("OLLAMA_MODEL=mistral");
demo.code("// Command Line Options");
demo.info('node index.js --keyword="layoff,downsizing" --city="Vancouver"');
demo.info("node index.js --no-location --no-ai");
demo.info("node index.js --output=results/my-results.json");
demo.info("node index.js --ai-after");
await waitForEnter();
}
async function demonstrateKeywordLoading() {
demo.section("2. Keyword Loading");
demo.info(
"Keywords can be loaded from CSV files or specified via command line."
);
// Simulate loading keywords from CSV
demo.code("// Loading keywords from CSV file");
logger.step("Loading keywords from keywords/linkedin-keywords.csv");
const keywords = [
"layoff",
"downsizing",
"reduction in force",
"RIF",
"termination",
"job loss",
"workforce reduction",
"open to work",
"actively seeking",
"job search",
];
demo.success(`Loaded ${keywords.length} keywords from CSV file`);
demo.info("Keywords: " + keywords.slice(0, 5).join(", ") + "...");
demo.code("// Command line keyword override");
demo.info('node index.js --keyword="layoff,downsizing"');
demo.info('node index.js --add-keyword="hiring freeze"');
await waitForEnter();
}
async function demonstrateSearchProcess() {
demo.section("3. Search Process Simulation");
demo.info(
"The parser performs automated LinkedIn searches for each keyword."
);
const keywords = ["layoff", "downsizing", "open to work"];
for (const keyword of keywords) {
demo.code(`// Searching for keyword: "${keyword}"`);
logger.search(`Searching for "${keyword}" in Toronto`);
// Simulate search process
await simulateSearch();
const foundCount = Math.floor(Math.random() * 50) + 10;
const acceptedCount = Math.floor(foundCount * 0.3);
logger.info(`Found ${foundCount} posts, checking profiles for location...`);
logger.success(`Accepted ${acceptedCount} posts after location validation`);
console.log();
}
await waitForEnter();
}
async function demonstrateLocationFiltering() {
demo.section("4. Location Filtering");
demo.info(
"Posts are filtered based on author location using geographic validation."
);
demo.code("// Location filter configuration");
demo.info("LOCATION_FILTER=Ontario,Manitoba");
demo.info("ENABLE_LOCATION_CHECK=true");
demo.code("// Location validation examples");
const testLocations = [
{ location: "Toronto, Ontario, Canada", valid: true },
{ location: "Vancouver, British Columbia, Canada", valid: false },
{ location: "Calgary, Alberta, Canada", valid: false },
{ location: "Winnipeg, Manitoba, Canada", valid: true },
{ location: "New York, NY, USA", valid: false },
];
testLocations.forEach(({ location, valid }) => {
logger.location(`Checking location: ${location}`);
if (valid) {
logger.success(`✅ Location valid - post accepted`);
} else {
logger.warning(`❌ Location invalid - post rejected`);
}
});
await waitForEnter();
}
async function demonstrateAIAnalysis() {
demo.section("5. AI Analysis");
demo.info(
"Posts can be analyzed using local Ollama or OpenAI for relevance scoring."
);
demo.code("// AI analysis configuration");
demo.info("ENABLE_LOCAL_AI=true");
demo.info('AI_CONTEXT="job layoffs and workforce reduction"');
demo.info("OLLAMA_MODEL=mistral");
demo.code("// Analyzing posts with AI");
logger.ai("Starting AI analysis of accepted posts...");
for (let i = 0; i < mockPosts.length; i++) {
const post = mockPosts[i];
logger.info(`Analyzing post ${i + 1}: ${post.content.substring(0, 50)}...`);
// Simulate AI analysis
await simulateProcessing();
const relevanceScore = 0.7 + Math.random() * 0.3;
const confidence = 0.8 + Math.random() * 0.2;
logger.success(
`Relevance: ${relevanceScore.toFixed(
2
)}, Confidence: ${confidence.toFixed(2)}`
);
// Add AI analysis to post
post.ai_analysis = {
relevance_score: relevanceScore,
confidence: confidence,
context_match: relevanceScore > 0.7,
analysis_text: `This post discusses ${post.metadata.search_keyword} and is relevant to the search context.`,
};
}
await waitForEnter();
}
async function demonstrateOutputGeneration() {
demo.section("6. Output Generation");
demo.info("Results are saved to JSON files with comprehensive metadata.");
demo.code("// Generating output file");
logger.file("Saving results to JSON file...");
const outputData = {
metadata: {
timestamp: new Date().toISOString(),
keywords: ["layoff", "downsizing", "open to work"],
city: "Toronto",
date_posted: "past-week",
sort_by: "date_posted",
total_posts_found: 150,
accepted_posts: mockPosts.length,
rejected_posts: 147,
processing_time_seconds: 180,
},
posts: mockPosts,
};
// Save to demo file
const outputPath = path.join(__dirname, "demo-results.json");
fs.writeFileSync(outputPath, JSON.stringify(outputData, null, 2));
demo.success(`Results saved to: ${outputPath}`);
demo.info(`Total posts processed: ${outputData.metadata.total_posts_found}`);
demo.info(`Posts accepted: ${outputData.metadata.accepted_posts}`);
demo.info(`Posts rejected: ${outputData.metadata.rejected_posts}`);
demo.code("// Output file structure");
demo.info("📁 demo-results.json");
demo.info(" ├── metadata");
demo.info(" │ ├── timestamp");
demo.info(" │ ├── keywords");
demo.info(" │ ├── city");
demo.info(" │ ├── total_posts_found");
demo.info(" │ ├── accepted_posts");
demo.info(" │ └── processing_time_seconds");
demo.info(" └── posts[]");
demo.info(" ├── id");
demo.info(" ├── content");
demo.info(" ├── author");
demo.info(" ├── engagement");
demo.info(" ├── ai_analysis");
demo.info(" └── metadata");
await waitForEnter();
}
// Helper functions
function waitForEnter() {
return new Promise((resolve) => {
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question("\nPress Enter to continue...", () => {
rl.close();
resolve();
});
});
}
async function simulateSearch() {
return new Promise((resolve) => {
const steps = [
"Launching browser",
"Logging in",
"Navigating to search",
"Loading results",
];
let i = 0;
const interval = setInterval(() => {
if (i < steps.length) {
logger.info(steps[i]);
i++;
} else {
clearInterval(interval);
resolve();
}
}, 800);
});
}
async function simulateProcessing() {
return new Promise((resolve) => {
const dots = [".", "..", "..."];
let i = 0;
const interval = setInterval(() => {
process.stdout.write(`\rProcessing${dots[i]}`);
i = (i + 1) % dots.length;
}, 500);
setTimeout(() => {
clearInterval(interval);
process.stdout.write("\r");
resolve();
}, 1500);
});
}
// Run the demo if this file is executed directly
if (require.main === module) {
runDemo().catch((error) => {
demo.error(`Demo failed: ${error.message}`);
process.exit(1);
});
}
module.exports = { runDemo };