fix most of the listed bugs
This commit is contained in:
@@ -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 Gentry’s strategic skills."
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"risks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": [
|
||||
[
|
||||
"Without strict structure, Gentry’s 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 Gentry’s 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" });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user