fix most of the listed bugs

This commit is contained in:
Ra
2025-08-26 11:23:27 -07:00
parent 772d1d4c10
commit ad15aaa35e
22 changed files with 3493 additions and 2375 deletions

View File

@@ -80,99 +80,394 @@ const stripe = process.env.STRIPE_SECRET_KEY ? new Stripe(process.env.STRIPE_SEC
apiVersion: '2024-11-20.acacia',
}) : null;
const RESPONSE_FORMAT = {
type: "json_schema",
json_schema: {
name: "company_artifacts",
strict: true,
schema: {
type: "object",
additionalProperties: false,
properties: {
companyPerformance: {
type: "object",
additionalProperties: false,
properties: {
summary: { type: "string" },
metrics: {
type: "array",
items: {
type: "object",
additionalProperties: false,
properties: {
name: { type: "string" },
value: { anyOf: [{ type: "string" }, { type: "number" }] },
trend: { enum: ["up", "down", "flat"] }
},
required: ["name", "value", "trend"]
}
}
const RESPONSE_FORMAT_EMPLOYEE = {
"type": "object",
"properties": {
"employeeId": {
"type": "string"
},
"department": {
"type": "string"
},
"role": {
"type": "string"
},
"roleAndOutput": {
"type": "object",
"properties": {
"responsibilities": {
"type": "string",
"examples": [
"Recruiting influencers, onboarding, campaign support, business development."
]
},
"clarityOnRole": {
"type": "string",
"examples": [
"10/10 Feels very clear on responsibilities."
]
},
"selfRatedOutput": {
"type": "string",
"examples": [
"7/10 Indicates decent performance but room to grow."
]
},
"recurringTasks": {
"type": "string",
"examples": [
"Influencer outreach, onboarding, communications."
]
}
}
},
"insights": {
"type": "object",
"properties": {
"personalityInsights": {
"type": "string",
"examples": [
"Loyal, well-liked by influencers, eager to grow, client-facing interest."
]
},
"psychologicalIndicators": {
"type": "array",
"items": {
"type": "string"
},
required: ["summary", "metrics"]
"examples": [
[
"Scores high on optimism and external motivation.",
"Shows ambition but lacks self-discipline in execution.",
"Displays a desire for recognition and community; seeks more appreciation."
]
]
},
immediateHiringNeeds: {
type: "array",
items: {
type: "object",
additionalProperties: false,
properties: {
role: { type: "string" },
urgency: { enum: ["low", "medium", "high"] },
reason: { type: "string" }
},
required: ["role", "urgency", "reason"]
}
"selfAwareness": {
"type": "string",
"examples": [
"High acknowledges weaknesses like lateness and disorganization."
]
},
forwardOperatingPlan: {
type: "object",
additionalProperties: false,
properties: {
nextQuarterObjectives: { type: "array", items: { type: "string" } },
initiatives: {
type: "array",
items: {
type: "object",
additionalProperties: false,
properties: {
name: { type: "string" },
owner: { type: "string" },
kpis: { type: "array", items: { type: "string" } }
},
required: ["name", "owner", "kpis"]
}
},
risks: {
type: "array",
items: {
type: "object",
additionalProperties: false,
properties: {
risk: { type: "string" },
mitigation: { type: "string" }
},
required: ["risk", "mitigation"]
}
}
},
required: ["nextQuarterObjectives", "initiatives", "risks"]
"emotionalResponses": {
"type": "string",
"examples": [
"Frustrated by campaign disorganization; would prefer closer collaboration."
]
},
organizationalInsights: {
type: "object",
additionalProperties: false,
properties: {
culture: { type: "string" },
teamDynamics: { type: "string" },
blockers: { type: "array", items: { type: "string" } }
},
required: ["culture", "teamDynamics", "blockers"]
},
strengths: { type: "array", items: { type: "string" } },
"growthDesire": {
"type": "string",
"examples": [
"Interested in becoming more client-facing and shifting toward biz dev."
]
}
}
},
"strengths": {
"type": "array",
"items": {
"type": "string"
},
required: ["companyPerformance", "immediateHiringNeeds", "forwardOperatingPlan", "organizationalInsights", "strengths"]
"examples": [
[
"Builds strong relationships with influencers.",
"Has sales and outreach potential.",
"Loyal, driven, and values-aligned with the company mission.",
"Open to feedback and self-improvement."
]
]
},
"weaknessess": {
"type": "array",
"items": {
"type": "string"
},
"examples": [
"Critical Issue: Disorganized and late with deliverables — confirmed by previous internal notes.",
"Poor implementation and recruiting output — does not effectively close the loop on influencer onboarding.",
"May unintentionally cause friction with campaigns team by stepping outside process boundaries."
]
},
"opportunities": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"description": {
"type": "string"
}
}
},
"examples": [
[
{
"title": "Role Adjustment",
"description": "Shift fully to Influencer Manager & Biz Dev Outreach as planned. Remove all execution and recruitment responsibilities."
},
{
"title": "Accountability Support",
"description": "Pair with a high-output implementer (new hire) to balance Gentrys strategic skills."
}
]
]
},
"risks": {
"type": "array",
"items": {
"type": "string"
},
"examples": [
[
"Without strict structure, Gentrys performance will stay flat or become a bottleneck.",
"If kept in a dual-role (recruiting + outreach), productivity will suffer.",
"He needs system constraints and direct oversight to stay focused."
]
]
},
"recommendations": {
"type": "array",
"items": {
"type": "string"
},
"examples": [
[
"Keep. But immediately restructure his role: Remove recruiting and logistical tasks. Focus only on influencer relationship-building, pitching, and business development.",
"Pair him with a new hire who is ultra-organized and can execute on Gentrys deals."
]
]
},
"gradingOverview": {
"grade": { "type": "string" },
"reliability": { "type": "number" },
"roleFit": { "type": "number" },
"scalability": { "type": "number" },
"output": { "type": "number" },
"initiative": { "type": "number" }
}
}
};
}
RESPONSE_FORMAT_COMPANY = {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "CompanyReport",
"type": "object",
"properties": {
"id": { "type": "string" },
"createdAt": { "type": "number" },
"overview": {
"type": "object",
"properties": {
"totalEmployees": { "type": "number" },
"departmentBreakdown": {
"type": "array",
"items": {
"type": "object",
"properties": {
"department": { "type": "string" },
"count": { "type": "number" }
},
"required": ["department", "count"]
}
},
"submissionRate": { "type": "number" },
"lastUpdated": { "type": "number" },
"averagePerformanceScore": { "type": "number" },
"riskLevel": {
"type": "string",
"enum": ["Low", "Medium", "High"]
}
},
"required": ["totalEmployees", "departmentBreakdown", "submissionRate", "lastUpdated"]
},
"weaknesses": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": { "type": "string" },
"description": { "type": "string" }
},
"required": ["title", "description"]
}
},
"personnelChanges": {
"type": "object",
"properties": {
"newHires": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"department": { "type": "string" },
"role": { "type": "string" },
"impact": { "type": "string" }
},
"required": ["name", "department", "role"]
}
},
"promotions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"fromRole": { "type": "string" },
"toRole": { "type": "string" },
"impact": { "type": "string" }
},
"required": ["name", "fromRole", "toRole"]
}
},
"departures": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"department": { "type": "string" },
"reason": { "type": "string" },
"impact": { "type": "string" }
},
"required": ["name", "department", "reason"]
}
}
},
"required": ["newHires", "promotions", "departures"]
},
"immediateHiringNeeds": {
"type": "array",
"items": {
"type": "object",
"properties": {
"department": { "type": "string" },
"role": { "type": "string" },
"priority": {
"type": "string",
"enum": ["High", "Medium", "Low"]
},
"reasoning": { "type": "string" },
"urgency": {
"type": "string",
"enum": ["high", "medium", "low"]
}
},
"required": ["department", "role", "priority", "reasoning"]
}
},
"forwardOperatingPlan": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": { "type": "string" },
"details": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["title", "details"]
}
},
"strengths": {
"type": "array",
"items": { "type": "string" }
},
"organizationalImpactSummary": {
"type": "array",
"items": {
"type": "object",
"properties": {
"category": {
"type": "string",
"enum": [
"Mission Critical",
"Highly Valuable",
"Core Support",
"Low Criticality"
]
},
"employees": {
"type": "array",
"items": {
"type": "object",
"properties": {
"employeeName": { "type": "string" },
"impact": { "type": "string" },
"description": { "type": "string" },
"suggestedPay": { "type": "string", "description": "Suggested yearly wage for the employee", "example": "$70,000" }
},
"required": ["employeeName", "impact", "description", "suggestedPay"]
}
}
},
"required": ["category", "employees"]
}
},
"gradingBreakdown": {
"type": "array",
"items": {
"type": "object",
"properties": {
"departmentNameShort": { "type": "string" },
"departmentName": { "type": "string" },
"lead": { "type": "string" },
"support": { "type": "string" },
"departmentGrade": { "type": "string" },
"executiveSummary": { "type": "string" },
"teamScores": {
"type": "array",
"items": {
"type": "object",
"properties": {
"employeeName": { "type": "string" },
"grade": { "type": "string" },
"reliability": { "type": "number" },
"roleFit": { "type": "number" },
"scalability": { "type": "number" },
"output": { "type": "number" },
"initiative": { "type": "number" }
},
"required": [
"employeeName",
"grade",
"reliability",
"roleFit",
"scalability",
"output",
"initiative"
]
}
}
},
"required": [
"departmentNameShort",
"departmentName",
"lead",
"support",
"departmentGrade",
"executiveSummary",
"teamScores"
]
}
},
"executiveSummary": { "type": "string" }
},
"required": [
"id",
"createdAt",
"overview",
"weaknesses",
"personnelChanges",
"immediateHiringNeeds",
"strengths",
"gradingBreakdown",
"executiveSummary"
]
}
// Helper function to generate OTP
@@ -714,22 +1009,22 @@ exports.submitEmployeeAnswers = onRequest({ cors: true }, async (req, res) => {
const orgData = orgDoc.exists ? orgDoc.data() : {};
// Prepare company context (onboarding data)
const companyContext = {
let companyContext = {
name: orgData.name,
industry: orgData.industry,
mission: orgData.mission,
values: orgData.values,
culture: orgData.cultureDescription,
size: orgData.size,
onboardingData: orgData // Include all org data for comprehensive context
};
if (orgData.onboardingData) {
companyContext = {
...companyContext,
...orgData.onboardingData
};
}
// Prepare submission data
const submissionData = {
employeeId: finalEmployeeId,
answers,
submittedAt: Date.now(),
status: "completed"
status: "completed",
companyContext,
};
// Generate the report using the existing function logic
@@ -737,7 +1032,7 @@ exports.submitEmployeeAnswers = onRequest({ cors: true }, async (req, res) => {
if (openai) {
// Use OpenAI to generate the report with company context
const prompt = `
You are an expert HR analyst. Generate a comprehensive employee performance report based on the following data:
You are a cut-and-dry expert business analyst. Return ONLY JSON that conforms to the provided schema:
Employee Information:
- Name: ${employeeData?.name || employeeData?.email || 'Unknown'}
@@ -762,17 +1057,7 @@ Generate a detailed report that:
8. Provides numerical grading across key performance areas
Return ONLY valid JSON that matches this structure:
{
"roleAndOutput": { "currentRole": string, "keyResponsibilities": string[], "performanceRating": number },
"behavioralInsights": { "workStyle": string, "communicationSkills": string, "teamDynamics": string },
"strengths": string[],
"weaknesses": string[],
"opportunities": string[],
"risks": string[],
"recommendations": string[],
"companyAlignment": { "valuesAlignment": number, "cultureAlignment": number, "missionAlignment": number },
"grading": { "overall": number, "technical": number, "communication": number, "teamwork": number, "leadership": number }
}
${JSON.stringify(RESPONSE_FORMAT_EMPLOYEE, null, 2)}
Be thorough, professional, and focus on actionable insights.
`.trim();
@@ -796,8 +1081,13 @@ Be thorough, professional, and focus on actionable insights.
const aiResponse = completion.choices[0].message.content;
const parsedReport = JSON.parse(aiResponse);
console.log(parsedReport);
report = {
employeeId: finalEmployeeId,
employeeName: employeeData?.name || employeeData?.email || 'Employee',
role: employeeData?.role || "Team Member",
email: employeeData?.email || 'Unknown',
generatedAt: Date.now(),
summary: `AI-generated performance analysis for ${employeeData?.name || employeeData?.email || 'Employee'}`,
submissionId: finalEmployeeId,
@@ -910,12 +1200,13 @@ exports.generateEmployeeReport = onRequest({ cors: true }, async (req, res) => {
if (openai) {
// Use OpenAI to generate the report
const prompt = `
You are an expert HR analyst. Generate a comprehensive employee performance report based on the following data:
You are a cut-and-dry expert business analyst. Return ONLY JSON that conforms to the provided schema:
Employee Information:
- Name: ${employee.name || employee.email}
- Role: ${employee.role || "Team Member"}
- Department: ${employee.department || "General"}
- Name: ${employee?.name || employee?.email || 'Unknown'}
- Role: ${employee?.role || "Team Member"}
- Department: ${employee?.department || "General"}
- Email: ${employee?.email || 'Unknown'}
Employee Submission Data:
${JSON.stringify(submission, null, 2)}
@@ -923,18 +1214,21 @@ ${JSON.stringify(submission, null, 2)}
Company Context:
${companyWiki ? JSON.stringify(companyWiki, null, 2) : "No company context provided"}
Generate a detailed report with the following structure:
- roleAndOutput: Current role assessment and performance rating
- behavioralInsights: Work style, communication, and team dynamics
- strengths: List of employee strengths
- weaknesses: Areas for improvement (mark critical issues)
- opportunities: Growth and development opportunities
- risks: Potential risks or concerns
- recommendations: Specific action items
- grading: Numerical scores for different performance areas
Generate a detailed report that:
1. Evaluates how well the employee aligns with company values and culture
2. Assesses their role performance and output
3. Identifies behavioral insights and work patterns
4. Highlights strengths and areas for improvement
5. Provides specific recommendations for growth
6. Suggests opportunities that align with company goals
7. Identifies any risks or concerns
8. Provides numerical grading across key performance areas
Return ONLY valid JSON that matches this structure. Be thorough but professional.
`.trim();
Return ONLY valid JSON that matches this structure:
${JSON.stringify(RESPONSE_FORMAT_EMPLOYEE, null, 2)}
Be thorough, professional, and focus on actionable insights.
`.trim();
const completion = await openai.chat.completions.create({
model: "gpt-4o",
@@ -1031,63 +1325,82 @@ exports.generateCompanyWiki = onRequest({ cors: true }, async (req, res) => {
return res.status(405).json({ error: "Method not allowed" });
}
const authContext = await validateAuthAndGetContext(req);
const orgId = authContext.orgId;
if (!orgId) {
return res.status(400).json({ error: "User has no associated organizations" });
}
const { org, submissions = [] } = req.body;
if (!org) {
return res.status(400).json({ error: "Organization data is required" });
}
const orgData = {
id: org.id,
name: org.name,
contextualData: org.onboardingData,
metrics: org.metrics
}
try {
let report, wiki;
if (openai) {
// Use OpenAI to generate the company report and wiki
db.collection("orgs").doc(orgId)
const system = "You are a cut-and-dry expert business analyst. Return ONLY JSON that conforms to the provided schema.";
const user = [
"Generate a COMPANY REPORT and COMPANY WIKI that fully leverage the input data.",
"Be thorough and professional.",
"",
"Organization Information:",
JSON.stringify(org, null, 2),
"",
"Employee Submissions:",
JSON.stringify(submissions, null, 2)
].join("\n");
// Use OpenAI to generate the company report
const user = `You are a cut-and-dry expert business analyst who shys to no truths and with get a business in tip-top shape within swiftness. Return ONLY JSON that conforms to the provided schema:
Employee Submissions:
${JSON.stringify(submissions, null, 2)}
Company Context:
${JSON.stringify(orgData, null, 2)}
Generate a detailed report that:
1. Evaluates the company based on all the key sections in the JSON schema, being thorough to touch on all categories and employees
2. Attempts to at your best effort further the companies success and growth potential
3. Provides clear, concise, and actionable recommendations for improvement
4. Doesn't cater to sugarcoating or vague generalities
5. Will beat the nail into the coffin of inefficiency with precise solutions, getting rid of all weak points.
Return ONLY valid JSON that matches this JSON SCHEMA:
${JSON.stringify(RESPONSE_FORMAT_COMPANY, null, 0)}
Be thorough, professional, and focus on actionable insights.
`;
const completion = await openai.chat.completions.create({
model: "gpt-4o",
temperature: 0, // consistency
response_format: RESPONSE_FORMAT,
response_format: { type: "json_object" },
messages: [
{ role: "system", content: system },
{ role: "user", content: user }
]
});
// content is guaranteed to be schema-conformant JSON
console.log(completion.choices[0].message);
console.log(completion.choices[0].message.content);
const parsed = JSON.parse(completion.choices[0].message.content);
const report = {
report = {
generatedAt: Date.now(),
...parsed
};
const wiki = {
companyName: org?.name ?? parsed.wiki.companyName,
generatedAt: Date.now(),
const reportRef = db
.collection("orgs")
.doc(orgId)
.collection("companyReport")
.doc("main");
};
const companyReport = db.collection("orgs").doc(orgId).collection("companyReport");
await companyReport.set(report);
const companyWiki = db.collection("orgs").doc(orgId).collection("companyWiki");
await companyWiki.set(wiki);
await reportRef.set(report);
console.log(report);
console.log(wiki);
return res.status(200).json({
success: true,
report
});
} else {
// Fallback to mock data when OpenAI is not available
@@ -1136,13 +1449,11 @@ exports.generateCompanyWiki = onRequest({ cors: true }, async (req, res) => {
culture: "Collaborative and growth-oriented",
generatedAt: Date.now(),
};
return res.status(200).json({
success: true,
...report
});
}
res.json({
success: true,
...report,
...wiki,
});
} catch (error) {
console.error("Generate company wiki error:", error);
res.status(500).json({ error: "Failed to generate company wiki" });
@@ -1173,7 +1484,7 @@ exports.chat = onRequest({ cors: true }, async (req, res) => {
if (openai) {
// Use OpenAI for chat responses
const systemPrompt = `
You are an expert HR consultant and business analyst with access to employee performance data and company analytics.
You are a cut-and-dry expert business analyst.
You provide thoughtful, professional advice based on the employee context and company data provided.
${context ? `
@@ -1186,7 +1497,14 @@ Mentioned Employees:
${mentions.map(emp => `- ${emp.name} (${emp.role || 'Employee'})`).join('\n')}
` : ''}
Provide helpful, actionable insights while maintaining professional confidentiality and focusing on constructive feedback.
You will discuss employees with the employer to help:
1. Evaluate the company based on all provided data, being thorough to touch on all information gathered from said employee doubled with information known about the company
2. Attempt to at your best effort further the companies success and growth potential
3. Provide clear, concise, and actionable recommendations for improvement
4. Don't cater to sugarcoating or vague generalities
5. Beat the nail into the coffin of inefficiency with precise solutions, getting rid of all weak points.
Provide helpful, actionable insights while maintaining professional tone and focusing on critical must-know knowledge and actionable recommendations.
`.trim();
// Build the user message content
@@ -1332,17 +1650,18 @@ exports.createOrganization = onRequest({ cors: true }, async (req, res) => {
const userData = userDoc.data();
// Add user as owner to organization's employees collection
const employeeRef = orgRef.collection("employees").doc(authContext.userId);
await employeeRef.set({
// Add owner info to organization document (owners are NOT employees)
const ownerInfo = {
id: authContext.userId,
role: "owner",
isOwner: true,
joinedAt: Date.now(),
status: "active",
name: userData.displayName || userData.email.split("@")[0],
email: userData.email,
department: "Management",
joinedAt: Date.now()
};
// Update org document with owner info
await orgRef.update({
ownerInfo: ownerInfo,
updatedAt: Date.now()
});
// Add organization to user's organizations (for multi-org support)
@@ -2077,12 +2396,16 @@ exports.getEmployees = onRequest({ cors: true }, async (req, res) => {
return res.status(400).json({ error: "User has no associated organizations" });
}
// Get all employees
// Get all employees (excluding owners - they should not be in employees collection)
const employeesSnapshot = await db.collection("orgs").doc(orgId).collection("employees").get();
const employees = [];
employeesSnapshot.forEach(doc => {
employees.push({ id: doc.id, ...doc.data() });
const employeeData = doc.data();
// Skip any owner records that might still exist (defensive programming)
if (employeeData.role !== "owner" && !employeeData.isOwner) {
employees.push({ id: doc.id, ...employeeData });
}
});
res.json({
@@ -2305,13 +2628,15 @@ exports.getCompanyReports = onRequest({ cors: true }, async (req, res) => {
}
// Get all company reports
const reportsSnapshot = await db.collection("orgs").doc(orgId).collection("fullCompanyReports").get();
const reports = [];
const reportsSnapshot = await db.collection("orgs").doc(orgId).collection("companyReport").doc("main").get();
reportsSnapshot.forEach(doc => {
reports.push({ id: doc.id, ...doc.data() });
});
const reportsData = reportsSnapshot.data();
const reports = reportsData ? [reportsData] : [];
// Convert the reports object to an array
// for (const [id, report] of Object.entries(reportsData || {})) {
// reports.push({ id, ...report });
// }
// Sort by creation date (newest first)
reports.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
@@ -2487,4 +2812,82 @@ exports.deleteImage = onRequest({ cors: true }, async (req, res) => {
console.error("Delete image error:", error);
res.status(500).json({ error: "Failed to delete image" });
}
});
// Migration Function - Remove Owners from Employees Collection
exports.migrateOwnersFromEmployees = onRequest({ cors: true }, async (req, res) => {
if (req.method === 'OPTIONS') {
res.status(204).send('');
return;
}
if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" });
}
try {
// Validate auth token and get user context
const authContext = await validateAuthAndGetContext(req);
const orgId = authContext.orgId;
if (!orgId) {
return res.status(400).json({ error: "User has no associated organizations" });
}
// Get organization document
const orgRef = db.collection("orgs").doc(orgId);
const orgDoc = await orgRef.get();
if (!orgDoc.exists) {
return res.status(404).json({ error: "Organization not found" });
}
const orgData = orgDoc.data();
let migratedCount = 0;
let ownerInfo = null;
// Find and remove any owners from employees collection
const employeesSnapshot = await orgRef.collection("employees").where("role", "==", "owner").get();
if (!employeesSnapshot.empty) {
// Get owner info before deletion
const ownerEmployee = employeesSnapshot.docs[0].data();
ownerInfo = {
id: ownerEmployee.id,
name: ownerEmployee.name,
email: ownerEmployee.email,
joinedAt: ownerEmployee.joinedAt || Date.now()
};
// Delete all owner records from employees collection
const batch = db.batch();
employeesSnapshot.docs.forEach(doc => {
batch.delete(doc.ref);
migratedCount++;
});
await batch.commit();
}
// Update org document with owner info if we found it
if (ownerInfo) {
await orgRef.update({
ownerInfo: ownerInfo,
updatedAt: Date.now()
});
}
res.json({
success: true,
message: `Migration completed. Removed ${migratedCount} owner record(s) from employees collection.`,
migratedCount,
ownerInfo
});
} catch (error) {
console.error("Migration error:", error);
if (error.message.includes('Missing or invalid authorization') ||
error.message.includes('Token')) {
return res.status(401).json({ error: error.message });
}
res.status(500).json({ error: "Failed to complete migration" });
}
});