F10: Company Detail Page Flow - Feature Analysis

Overview

The Company Detail Page serves as the public profile for each registered AI company within the Futr Connect platform. It provides users with comprehensive information about an AI company, including its metadata, insights, products, and services. The page allows users to follow companies, book meetings, and explore detailed company insights across multiple categories. This feature bridges the gap between users and companies by presenting organized, accessible company information while providing interactive elements that facilitate engagement. The responsive design ensures a consistent user experience across devices, with a focus on clear information architecture and intuitive navigation through company insights.

API Endpoints

1. Get Company Profile

GET /api/company/{company_id}/

Response:

{
  "id": "uuid-string",
  "name": "AI Solutions Ltd",
  "logo": "https://storage.futrconnect.com/company-logos/ai-solutions.png",
  "overview": "AI Solutions Ltd is a leading provider of artificial intelligence solutions for enterprise clients, specializing in natural language processing and machine learning applications.",
  "social_link": {
    "website": "https://aisolutions.example.com",
    "linkedin": "https://linkedin.com/company/ai-solutions",
    "twitter": "https://twitter.com/aisolutions",
    "facebook": "https://facebook.com/aisolutions"
  },
  "company_founded": "2018-01-01",
  "is_claimed": true,
  "is_featured_by_admin": true,
  "total_followers_count": 12850,
  "is_followed": false,
  "meeting_url": "https://calendly.com/ai-solutions/meeting"
}

2. Follow Company

POST /api/company-follow/

Request Body:

{
  "company": "company-uuid"
}

Response (201 Created):

{
  "id": "uuid-string",
  "company": "company-uuid"
}

3. Get Company People

GET /api/people/?company={company_id}

4. Get Company Finance Insights

GET /api/finance/?company={company_id}

5. Get Company Metrics

GET /api/metric/?company={company_id}

6. Get Company Content

GET /api/content/?company={company_id}

7. Get Company News

GET /api/news/?company={company_id}

8. Get Company Privacy

GET /api/company-privacy-policy/?company={company_id}

9. Submit Company Feedback

POST /api/admin-feedback/

Request Body:

{
  "rating": 4,
  "message": "Great product, but could use more documentation. The customer support team was very helpful."
}

State Management

React-query

interface CompanyDetails {
id: string;
category: Category[];
user: User;
claimed_by: null;
company_addresses: any[];
company_social_links: CompanySocialLinks | null;
is_followed: boolean;
following_users: any[];
created_on: string;
edited_on: string;
name: string;
email: string;
company_founded: string;
phone_number: string;
logo: string;
meeting_url: string;
short_video: string;
overview: string;
about: string;
best_suited_for: string[];
where_to_use: string;
standout_function: string;
status: string;
publish_date: string;
is_claimed: boolean;
is_featured_by_admin: boolean;
total_followers_count: number;
demo_video?: null | string;
demo_video_url?: null | string;
first_call?: null | string;
first_call_url?: null | string;
}

interface User {
id: string;
email: string;
full_name: string;
avatar: string;
title: null;
location: null;
organization_name: null;
}

interface CompanySocialLinks {
id: string;
created_on: string;
edited_on: string;
career: null | string;
event_page: null | string;
customer_portal: null | string;
facebook: null | string;
instagram: null | string;
youtube: null | string;
twitter: null | string;
linkedin: null | string;
website: string | null;
company: string | null;
}
interface Category {
id: string;
title: string;
description: string;
}

type PeopleTypes = {
id: string;
created_on: string;
edited_on: string;
avatar?: string | null;
name?: string | null;
title?: string | null;
url?: string | null;
company?: string | null;
designation_type?: string | null;
};

type CompanyFinanceType = {
id: string;
created_on: string;
edited_on: string;
ipo_status: string;
latest_funding_round: string;
yahoo_finance_url: string | null;
latest_valuation: string;
annual_revenue: string;
total_funding: string;
company: string;
};

Database Models

PostgreSQL Models

Company

  • id (UUID, primary key)
  • name (varchar, required)
  • logo (ImageField, nullable)
  • overview (text, nullable)
  • short_video (URLField, nullable)
  • category (ManyToManyField to CompanyCategory)
  • is_featured_by_admin (boolean, default: false)
  • is_claimed (boolean, default: false)
  • claimed_by (ForeignKey to User, nullable)
  • meeting_url (URLField, nullable)
  • social_link (OneToOneField to CompanySocialLink)

People

  • id (UUID, primary key)
  • company (ForeignKey to Company)
  • designation_type (ForeignKey to DesignationType)
  • avatar (ImageField, nullable)
  • name (varchar, required)
  • title (varchar, nullable)
  • url (URLField, nullable)

Finance

  • id (UUID, primary key)
  • company (ForeignKey to Company)
  • ipo_status (varchar, required)
  • latest_funding_round (varchar, required)
  • yahoo_finance_url (URLField, nullable)
  • latest_valuation (varchar, nullable)
  • annual_revenue (varchar, required)
  • total_funding (varchar, nullable)

Metric

  • id (UUID, primary key)
  • company (ForeignKey to Company)
  • total_employees (varchar, required)
  • total_customers (varchar, required)
  • client_retention (IntegerField, nullable)

Content

  • id (UUID, primary key)
  • company (ForeignKey to Company)
  • link (URLField, required)
  • title (varchar, required)
  • video_type (varchar, required)
  • marked_video_as (varchar, choices: demo_video, featured_video, video_call, nullable)
  • total_count (PositiveIntegerField, default: 0)

News

  • id (UUID, primary key)
  • company (ForeignKey to Company)
  • source_url (URLField, nullable)
  • total_count (PositiveIntegerField, default: 0)

CompanyPrivacyPolicy

  • id (UUID, primary key)
  • company (OneToOneField to Company)
  • file (FileField, nullable)
  • privacy_url (URLField, nullable)

Adminfeedback

  • id (UUID, primary key)
  • user (ForeignKey to User, nullable)
  • rating (DecimalField, required)
  • message (text, required)
  • is_active (boolean, default: false)

Developer Note on Company Insight Models

Multiple Models Approach

We have chosen to implement separate database models for each type of company insight. This decision was made to optimize for type safety, schema clarity, and long-term maintainability. Each insight type has its own dedicated model with type-specific fields.

Naming Convention

All specialized company insight models follow a consistent naming convention:

  • CompanyInsightProduct - For product information
  • CompanyInsightPeople - For team members and leadership
  • CompanyInsightFinance - For financial information
  • CompanyInsightMetrics - For company metrics
  • CompanyInsightContent - For content and media
  • CompanyInsightNews - For news articles
  • CompanyInsightAwards - For awards and recognitions
  • CompanyInsightReferences - For testimonials and references
  • CompanyInsightFeedback - For user feedback
  • CompanyInsightPrivacy - For privacy policies and data handling

This naming convention ensures consistency across the codebase and makes it clear that these models are specialized versions of company insights.

Key Implementation Details

  1. Model Structure:
  • Individual models for each insight type: CompanyInsightProduct, CompanyInsightPeople, CompanyInsightFinance, CompanyInsightMetrics, CompanyInsightContent, CompanyInsightNews, CompanyInsightAwards, CompanyInsightReferences, CompanyInsightFeedback, and CompanyInsightPrivacy
  • Each model has common fields (title, description, media) and type-specific fields
  • Clear schema definition with explicit fields for each insight type
  1. API Layer:
  • Type-specific endpoints: /api/companies/{company_id}/products/, /api/companies/{company_id}/people/, etc.
  • Each endpoint works with its own dedicated model
  • Type-specific filtering and sorting capabilities for each endpoint
  1. Validation:
    - Built-in database-level validation for each model
    - Type safety through defined schema for each insight type
    - Clear constraints on required and optional fields

Benefits of Specialized Models

  1. Type Safety: Each model has its own specific fields with appropriate types, reducing runtime errors and providing better IDE autocomplete support.

  2. Schema Clarity: Developers can easily understand what fields are available for each insight type without wading through conditional logic or documentation.

  3. Enhanced Validation: Each model can have its own validation rules specific to that type of insight, making it easier to ensure data integrity.

  4. Optimized Queries: Queries are more efficient as they target only the relevant tables for each insight type rather than filtering a larger generic table.

  5. Independent Evolution: Each insight type can evolve independently without affecting other types, making it easier to add or modify fields for a specific insight type.

  6. Simplified UI Components: UI components can be built specifically for each insight type, with props that exactly match the model structure.

  7. Better Documentation: API documentation is clearer because each endpoint has a well-defined request and response structure specific to an insight type.

  8. Reduced Conditional Logic: Less need for conditional rendering or processing based on insight type, as each type already has its own dedicated processing flow.

API Usage Guidelines

Developers should use these endpoints to interact with company insights:

  1. Get Company Profile - To see what insight types are available:
GET /api/companies/{company_id}/

This returns the company profile including the insight_types array showing available insight types.

  1. Type-Specific Endpoints - Use dedicated endpoints for each type:
GET /api/companies/{company_id}/people/
GET /api/companies/{company_id}/finance/
GET /api/companies/{company_id}/metrics/

Benefits of This Approach

  1. Type Safety: Strong typing at the database level
  2. Schema Clarity: Explicit fields for each type
  3. Maintainability: Easier to evolve each insight type independently
  4. Performance: Optimal query performance with specific indexes
  5. Validation: Straightforward validation rules for each model

UI Component Strategy

Create type-specific UI components that correspond to each model:

'use client';

import { forwardRef, useState } from 'react';

import FianceAndMetrics from '../FianceAndMetrics/FianceAndMetrics';
import NewsAndContent from '../NewsAndContent/NewAndContent';
import PeopleSection from '../PeopleSection/PeopleSection';
import { motion } from 'framer-motion';

const navItems = [
    'People',
    'Finance'
];

const companyComponents = {
    People: <PeopleSection />,
    Finance: <FianceAndMetrics />
};

const CompanyInsightsSection = forwardRef<HTMLDivElement>((_, ref) => {
    const [activeItem, setActiveItem] = useState<keyof typeof companyComponents>('People');

    return (
        <div className=' max-w-[1530px]  w-full '>
            <div className='flex flex-col md:flex-row mt-9 gap-8 md:gap-11'>
                {/** Sidebar with animation **/}
                <motion.div
                    initial={{ x: -50, opacity: 0 }}
                    animate={{ x: 0, opacity: 1 }}
                    transition={{ duration: 0.4 }}
                    className='w-full self-start md:w-[300px] flex-shrink-1 bg-white rounded-xl shadow space-y-2 overflow-hidden'>
                    <div className='block sm:flex md:block sm:p-2 md:p-0'>
                        {navItems.map((item, index) => {
                            return (
                                <motion.div
                                    key={index}
                                    initial={{ opacity: 0, y: 10 }}
                                    animate={{ opacity: 1, y: 0 }}
                                    transition={{ delay: index * 0.1 }}
                                    onClick={() => setActiveItem(item as keyof typeof companyComponents)}
                                    className={`px-4 flex-1 cursor-pointer py-5 block border-b border-gray-100 transition-colors
                      ${
                          item === activeItem
                              ? 'bg-primaryColor text-white sm:rounded-lg md:rounded-none'
                              : 'text-black font-normal hover:bg-orientSecondary'
                      }`}>
                                    {item}
                                </motion.div>
                            );
                        })}
                    </div>
                </motion.div>

                <motion.div
                    initial={{ opacity: 0, x: 10 }}
                    animate={{ opacity: 1, x: 0 }}
                    transition={{ duration: 1 }}
                    ref={ref}
                    className='flex-1 overflow-x-hidden bg-white p-8 rounded-lg '>
                    {companyComponents[activeItem]}
                </motion.div>
            </div>
        </div>
    );
});

export default CompanyInsightsSection;

'use client';

import React, { Suspense } from 'react';

import { getDesignationQuery } from '@/services/modules/company-insights/queries';
import { designationType } from '@/services/modules/company-insights/types';
import { ApiResponseBaseType } from '@/types/api';
import { PEOPLE_TYPES_COMPANY_INSIGHTS } from '@/utils';
import { useQuery } from '@tanstack/react-query';

import PeopleLists from '../CompanyInsights/PeopleLists';
import CompanyDetailsInsightsPeopleLoading from './CompanyDetailsInsightsPeopleLoading';
import InvestorList from './InvestorList';

export default function PeopleSection() {
    const { data, isLoading } = useQuery<ApiResponseBaseType<designationType[]>>({
        queryKey: ['designation_key'],
        queryFn: getDesignationQuery
    });

    if (isLoading || !data) {
        return <CompanyDetailsInsightsPeopleLoading />;
    }

    return (
        <Suspense fallback={<CompanyDetailsInsightsPeopleLoading />}>
            <h1 className='text-3xl'>People</h1>
            <PeopleLists
                isEditAccess={false}
                peopleQueryKey={
                    data.results.filter((value) => value.title == PEOPLE_TYPES_COMPANY_INSIGHTS.Executive)?.[0]?.id ??
                    ''
                }
                title='Executive leadership'
            />
            <PeopleLists
                isEditAccess={false}
                peopleQueryKey={
                    data.results.filter((value) => value.title == PEOPLE_TYPES_COMPANY_INSIGHTS.advisor)?.[0]?.id ?? ''
                }
                title='Advisors'
            />
            <InvestorList
                peopleQuery={
                    data.results.find((item) => item.title == PEOPLE_TYPES_COMPANY_INSIGHTS.Investor)?.id ?? ''
                }
            />
            <PeopleLists
                isEditAccess={false}
                peopleQueryKey={
                    data.results.filter((value) => value.title == PEOPLE_TYPES_COMPANY_INSIGHTS.board)?.[0]?.id ?? ''
                }
                title='Board Members'
            />
        </Suspense>
    );
}


'use client';

import React, { Fragment, useEffect, useState } from 'react';

import { useParams } from 'next/navigation';

import { Skeleton } from '@/components/ui/skeleton';
import useErrorHandelForGet from '@/hooks/useErrorHandelForGet';
import { getFinanceListQuery, getMetricListQuery } from '@/services/modules/company-insights/queries';
import { companyInsightsKeys } from '@/services/modules/company-insights/query-keys';
import { CompanyFinanceType, CompanyMetrics } from '@/services/modules/company-insights/types';
import { ApiResponseBaseType, CustomErrorType } from '@/types/api';
import { formatNumber } from '@/utils/formatNumber';
import { useQuery } from '@tanstack/react-query';

import { FinanceCard } from '../FinanceCard/Finance';
import MetricCard from '../MetricCard/MetricCard';

const pageType = {
    Finance: 'Finance',
    Metric: 'Metrics'
};
export default function FianceAndMetrics() {
    const { slug } = useParams();
    const { errorHandelForGet } = useErrorHandelForGet();
    const [selectContentType, setSelectContentType] = useState<'Finance' | 'Metrics'>('Finance');

    const {
        data: financeData,
        isLoading: loadingData,
        isError,
        error
    } = useQuery<ApiResponseBaseType<CompanyFinanceType[]>, CustomErrorType>({
        queryKey: [...companyInsightsKeys.companyType('finance'), slug],
        queryFn: () =>
            getFinanceListQuery({
                company: slug as string
            }),
        enabled: selectContentType == 'Finance'
    });

    const { data: metricData, isLoading: loadingMetricData } = useQuery<
        ApiResponseBaseType<CompanyMetrics[]>,
        CustomErrorType
    >({
        queryKey: [...companyInsightsKeys.companyType('metrics'), slug],
        queryFn: () =>
            getMetricListQuery({
                company: slug as string
            }),
        enabled: selectContentType == 'Metrics'
    });

    useEffect(() => {
        if (isError) {
            errorHandelForGet(error.code);
        }
    }, [error, isError]);

    return (
        <div>
            <div className='flex gap-5 '>
                {['Finance', 'Metrics'].map((item) => (
                    <button
                        key={item}
                        className={`px-4 min-w-[120px]  py-3 text-mirageLight font-normal bg-orientSecondary text-sm rounded-md whitespace-nowrap ${
                            selectContentType == item ? 'border-primaryColor  border-2 font-semibold' : ''
                        }`}
                        onClick={() => setSelectContentType(item as 'Finance' | 'Metrics')}>
                        {item}
                    </button>
                ))}
            </div>
            <p className='text-3xl my-4 md:my-9'> {pageType[selectContentType]}</p>

            <div className='flex flex-wrap gap-6 max-h-[500px] overflow-auto py-4 px-2'>
                {pageType[selectContentType] == 'Finance'
                    ? loadingData
                        ? Array.from({ length: 4 }).map((_, index) => (
                              <div
                                  key={index}
                                  className={`shadow-[0_4px_10px_0_rgba(0,0,0,0.15)]
                px-4 py-5 max-w-[500px] w-full gap-3 flex rounded-lg transition-colors `}>
                                  <div>
                                      <Skeleton className='w-[50px] h-[50px] rounded-full' />
                                  </div>
                                  <div className='flex flex-col justify-center'>
                                      <Skeleton className='h-4 w-[100px] mb-2' />
                                      <Skeleton className='h-5 w-[150px]' />
                                  </div>
                              </div>
                          ))
                        : financeData?.results.map((item, index) => (
                              <Fragment key={item.id}>
                                  <FinanceCard title={item.ipo_status ?? 'N/A'} label='IPO Status' />
                                  <FinanceCard
                                      title={item.latest_funding_round ?? 'N/A'}
                                      label='Latest Funding Round'
                                  />{' '}
                                  <FinanceCard
                                      title={item.total_funding ? formatNumber(item.total_funding) : 'N/A'}
                                      label='Total Funding'
                                  />{' '}
                                  <FinanceCard
                                      title={item.latest_valuation ? formatNumber(item.latest_valuation) : 'N/A'}
                                      label='Latest Valuation'
                                  />{' '}
                                  <FinanceCard title={item.annual_revenue} label='Annual Revenue' />
                              </Fragment>
                          ))
                    : loadingMetricData
                      ? Array.from({ length: 4 }).map((_, index) => (
                            <div
                                key={index}
                                className={`bg-backgroundLight h-[180px] max-w-[300px] w-full flex flex-col items-center justify-center shadow-sm rounded-lg`}>
                                <Skeleton className='h-4 w-[200px] mb-3' />
                                <Skeleton className='h-6 w-[200px]' />
                            </div>
                        ))
                      : metricData?.results.map((item, index) => (
                            <Fragment key={index}>
                                <MetricCard title='Employees' value={item.total_employees ?? 'N/A'} />
                                <MetricCard title='Customers' value={item.total_customers ?? 'N/A'} />
                                {/* <MetricCard
                  title="Client retention rate"
                  value={item.client_retention ? `${item.client_retention+"%"}`: "N/A"}
                /> */}
                            </Fragment>
                        ))}
            </div>
        </div>
    );
}

Algorithms

1. Company Similarity Algorithm

This algorithm calculates similarity between companies to recommend related companies based on industry tags, content, and user interactions.

Inputs:

  • Primary company’s industry tags
  • Primary company’s content and insights
  • User interaction data (which users follow which companies)

Process:

  1. Calculate tag-based similarity using Jaccard similarity coefficient
  2. Analyze insight content using TF-IDF for content similarity
  3. Apply collaborative filtering based on users who follow similar companies
  4. Combine scores using a weighted approach:
similarity_score = (
  (0.4 * tag_similarity) +
  (0.3 * content_similarity) +
  (0.3 * collaborative_similarity)
)
  1. Sort by similarity score and filter out the primary company

Implementation:

def get_related_companies(company_id, limit=5):
    # Get the primary company
    primary_company = Company.objects.get(id=company_id)
    primary_tags = set(primary_company.industry_tags)

    # Get all other companies
    other_companies = Company.objects.exclude(id=company_id)

    related_companies = []
    for company in other_companies:
        # Tag similarity (Jaccard)
        company_tags = set(company.industry_tags)
        tag_intersection = len(primary_tags.intersection(company_tags))
        tag_union = len(primary_tags.union(company_tags))
        tag_similarity = tag_intersection / tag_union if tag_union > 0 else 0

        # Content similarity
        content_similarity = calculate_content_similarity(primary_company, company)

        # Collaborative similarity
        collaborative_similarity = calculate_collaborative_similarity(primary_company, company)

        # Combined score
        similarity_score = (
            (0.4 * tag_similarity) +
            (0.3 * content_similarity) +
            (0.3 * collaborative_similarity)
        )

        related_companies.append({
            'company': company,
            'similarity_score': similarity_score
        })

    # Sort by similarity score and return top matches
    related_companies.sort(key=lambda x: x['similarity_score'], reverse=True)
    return related_companies[:limit]

def calculate_content_similarity(company1, company2):
    # Get insights for both companies (across all insight types)
    company1_products = CompanyInsightProduct.objects.filter(company_id=company1.id, status='published')
    company1_people = CompanyInsightPeople.objects.filter(company_id=company1.id, status='published')
    company1_finance = CompanyInsightFinance.objects.filter(company_id=company1.id, status='published')
    company1_metrics = CompanyInsightMetrics.objects.filter(company_id=company1.id, status='published')
    company1_content = CompanyInsightContent.objects.filter(company_id=company1.id, status='published')
    company1_news = CompanyInsightNews.objects.filter(company_id=company1.id, status='published')
    company1_awards = CompanyInsightAwards.objects.filter(company_id=company1.id, status='published')
    company1_references = CompanyInsightReferences.objects.filter(company_id=company1.id, status='published')
    company1_feedback = CompanyInsightFeedback.objects.filter(company_id=company1.id, status='published')
    company1_privacy = CompanyInsightPrivacy.objects.filter(company_id=company1.id, status='published')

    company2_products = CompanyInsightProduct.objects.filter(company_id=company2.id, status='published')
    company2_people = CompanyInsightPeople.objects.filter(company_id=company2.id, status='published')
    company2_finance = CompanyInsightFinance.objects.filter(company_id=company2.id, status='published')
    company2_metrics = CompanyInsightMetrics.objects.filter(company_id=company2.id, status='published')
    company2_content = CompanyInsightContent.objects.filter(company_id=company2.id, status='published')
    company2_news = CompanyInsightNews.objects.filter(company_id=company2.id, status='published')
    company2_awards = CompanyInsightAwards.objects.filter(company_id=company2.id, status='published')
    company2_references = CompanyInsightReferences.objects.filter(company_id=company2.id, status='published')
    company2_feedback = CompanyInsightFeedback.objects.filter(company_id=company2.id, status='published')
    company2_privacy = CompanyInsightPrivacy.objects.filter(company_id=company2.id, status='published')

    # Combine text from all insight types
    company1_texts = []
    for product in company1_products:
        company1_texts.append(product.title + ' ' + product.description)
    for person in company1_people:
        company1_texts.append(person.title + ' ' + person.description)
    for finance in company1_finance:
        company1_texts.append(finance.title + ' ' + finance.description)
    for metric in company1_metrics:
        company1_texts.append(metric.title + ' ' + metric.description)
    for content in company1_content:
        company1_texts.append(content.title + ' ' + content.description)
    for news in company1_news:
        company1_texts.append(news.title + ' ' + news.description)
    for award in company1_awards:
        company1_texts.append(award.title + ' ' + award.description)
    for reference in company1_references:
        company1_texts.append(reference.title + ' ' + reference.description)
    for feedback in company1_feedback:
        company1_texts.append(feedback.title + ' ' + feedback.description)
    for privacy in company1_privacy:
        company1_texts.append(privacy.title + ' ' + privacy.description)

    company2_texts = []
    for product in company2_products:
        company2_texts.append(product.title + ' ' + product.description)
    for person in company2_people:
        company2_texts.append(person.title + ' ' + person.description)
    for finance in company2_finance:
        company2_texts.append(finance.title + ' ' + finance.description)
    for metric in company2_metrics:
        company2_texts.append(metric.title + ' ' + metric.description)
    for content in company2_content:
        company2_texts.append(content.title + ' ' + content.description)
    for news in company2_news:
        company2_texts.append(news.title + ' ' + news.description)
    for award in company2_awards:
        company2_texts.append(award.title + ' ' + award.description)
    for reference in company2_references:
        company2_texts.append(reference.title + ' ' + reference.description)
    for feedback in company2_feedback:
        company2_texts.append(feedback.title + ' ' + feedback.description)
    for privacy in company2_privacy:
        company2_texts.append(privacy.title + ' ' + privacy.description)

    company1_text = ' '.join(company1_texts)
    company2_text = ' '.join(company2_texts)

    # Use TF-IDF to calculate similarity
    vectorizer = TfidfVectorizer()
    tfidf_matrix = vectorizer.fit_transform([company1_text, company2_text])

    # Calculate cosine similarity
    return cosine_similarity(tfidf_matrix[0:1], tfidf_matrix[1:2])[0][0]

def calculate_collaborative_similarity(company1, company2):
    # Get followers for both companies
    company1_followers = CompanyFollower.objects.filter(company_id=company1.id).values_list('user_id', flat=True)
    company2_followers = CompanyFollower.objects.filter(company_id=company2.id).values_list('user_id', flat=True)

    # Find common followers
    common_followers = set(company1_followers).intersection(set(company2_followers))

    # Calculate Jaccard similarity
    union_followers = set(company1_followers).union(set(company2_followers))
    return len(common_followers) / len(union_followers) if len(union_followers) > 0 else 0

2. Follower Count Tracking Algorithm

This algorithm efficiently tracks and updates follower counts for companies while maintaining consistency.

Inputs:

  • Company ID
  • User action (follow or unfollow)
  • User ID

Process:

  1. When a user follows a company:
    - Create a CompanyFollower record
    - Atomically increment the follower_count in the denormalized counter
  2. When a user unfollows a company:
    - Delete the CompanyFollower record
    - Atomically decrement the follower_count
  3. Schedule periodic reconciliation to ensure count accuracy

Implementation:

def follow_company(company_id, user_id):
    # Get company
    company = Company.objects.get(id=company_id)

    # Create follower record if it doesn't exist
    _, created = CompanyFollower.objects.get_or_create(
        company_id=company_id,
        user_id=user_id,
        defaults={'followed_at': timezone.now()}
    )

    if created:
        # Atomically increment follower count
        Company.objects.filter(id=company_id).update(
            follower_count=F('follower_count') + 1
        )

        # Refresh company object to get updated count
        company.refresh_from_db()

        # Log the follow event for analytics
        log_follow_event(company_id, user_id)

    return {
        'company_id': company_id,
        'is_followed': True,
        'followed_at': timezone.now(),
        'follower_count': company.follower_count
    }

def unfollow_company(company_id, user_id):
    # Try to find and delete the follower record
    try:
        follower = CompanyFollower.objects.get(company_id=company_id, user_id=user_id)
        follower.delete()

        # Atomically decrement follower count
        Company.objects.filter(id=company_id).update(
            follower_count=F('follower_count') - 1
        )

        # Log the unfollow event for analytics
        log_unfollow_event(company_id, user_id)

        # Refresh company object to get updated count
        company = Company.objects.get(id=company_id)

        return {
            'company_id': company_id,
            'is_followed': False,
            'follower_count': company.follower_count
        }
    except CompanyFollower.DoesNotExist:
        # User wasn't following this company
        company = Company.objects.get(id=company_id)
        return {
            'company_id': company_id,
            'is_followed': False,
            'follower_count': company.follower_count
        }

def reconcile_follower_counts():
    """
    Periodic task to ensure follower counts are accurate.
    Runs daily or weekly to fix any inconsistencies.
    """
    companies = Company.objects.all()

    for company in companies:
        actual_count = CompanyFollower.objects.filter(company_id=company.id).count()

        if company.follower_count != actual_count:
            # Log the discrepancy
            log_follower_count_discrepancy(company.id, company.follower_count, actual_count)

            # Update to the correct count
            company.follower_count = actual_count
            company.save(update_fields=['follower_count'])

Implementation Guide for Multiple Models Approach

Working with Type-Specific Models

The multiple models approach for company insights provides strong type safety and schema clarity. Here’s how to effectively implement and work with this approach:

1. Data Structure Overview

We use separate models for each insight type:

  • PeopleSection - For peoples information
  • FianceAndMetrics - For team finance and metrics

2. Backend Implementation

Working with Products

# Creating a product insight
def create_company_product(company_id, data):
    product = CompanyInsightProduct.objects.create(
        company_id=company_id,
        title=data.get('title'),
        description=data.get('description', ''),
        media=data.get('media', []),
        highlights=data.get('highlights', []),
        cta=data.get('cta'),
        pricing_model=data.get('pricing_model'),
        features=data.get('features', []),
        technical_specs=data.get('technical_specs', {}),
        published_date=data.get('published_date'),
        created_by=data.get('created_by')
    )
    return product

# Retrieving products with filtering
def get_company_products(company_id, filters=None):
    query = CompanyInsightProduct.objects.filter(company_id=company_id)

    if filters:
        if 'featured' in filters:
            query = query.filter(status='published')
        if 'pricing_model' in filters:
            query = query.filter(pricing_model=filters['pricing_model'])

    return query.order_by('-published_date')

Working with People

# Creating a people insight
def create_company_person(company_id, data):
    person = CompanyInsightPeople.objects.create(
        company_id=company_id,
        title=data.get('title'),
        description=data.get('description', ''),
        media=data.get('media', []),
        highlights=data.get('highlights', []),
        cta=data.get('cta'),
        person_name=data.get('person_name'),
        role=data.get('role'),
        bio=data.get('bio', ''),
        linkedin_url=data.get('linkedin_url'),
        is_executive=data.get('is_executive', False),
        published_date=data.get('published_date'),
        created_by=data.get('created_by')
    )
    return person

# Retrieving executives
def get_company_executives(company_id):
    return CompanyInsightPeople.objects.filter(
        company_id=company_id,
        is_executive=True,
        status='published'
    ).order_by('role')

3. Adding New Insight Types

When adding a new insight type, follow these steps:

  1. Create a new database model:
class CompanyInsightAwards(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4)
    company = models.ForeignKey(Company, on_delete=models.CASCADE)
    title = models.CharField(max_length=255)
    description = models.TextField(null=True, blank=True)
    # Common fields...

    # Type-specific fields
    award_name = models.CharField(max_length=255)
    award_date = models.DateField()
    issuing_organization = models.CharField(max_length=255)
    award_category = models.CharField(max_length=255, null=True, blank=True)
    # ...
  1. Create API endpoints:
# urls.py
urlpatterns = [
    # ...
    path('companies/<uuid:company_id>/awards/', views.CompanyAwardsListView.as_view()),
    path('companies/<uuid:company_id>/awards/<uuid:award_id>/', views.CompanyAwardDetailView.as_view()),
]

# views.py
class CompanyAwardsListView(APIView):
    def get(self, request, company_id):
        awards = CompanyInsightAwards.objects.filter(company_id=company_id)
        serializer = CompanyAwardSerializer(awards, many=True)
        return Response(serializer.data)

    def post(self, request, company_id):
        serializer = CompanyAwardSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save(company_id=company_id)
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Backend implementation of the Privacy API endpoint

@api_view([‘GET’])
@permission_classes([IsAuthenticated])
def get_company_privacy(request, company_id):
“”“
Retrieve privacy policy information for a company
“”“
try: # Check if company exists
company = Company.objects.get(id=company_id)

    # Get privacy insights
    privacy_insights = CompanyInsightPrivacy.objects.filter(
        company_id=company_id,
        status='published'
    ).order_by('-published_date')

    # Paginate results
    paginator = PageNumberPagination()
    paginator.page_size = 10
    result_page = paginator.paginate_queryset(privacy_insights, request)

    # Serialize
    serializer = CompanyInsightPrivacySerializer(result_page, many=True)

    # Track view
    track_company_view(request, company)

    return paginator.get_paginated_response(serializer.data)

except Company.DoesNotExist:
    return Response(
        {"detail": "Company not found"},
        status=status.HTTP_404_NOT_FOUND
    )
except Exception as e:
    return Response(
        {"detail": str(e)},
        status=status.HTTP_500_INTERNAL_SERVER_ERROR
    )

Privacy serializer

class CompanyInsightPrivacySerializer(serializers.ModelSerializer):
class Meta:
model = CompanyInsightPrivacy
fields = ‘all