Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt

Use this file to discover all available pages before exploring further.

Version: 2.0.0
Last Updated: 2025-01-12
Constitution Reference: Section 1.3 (Integration Patterns)
This document provides concrete examples of each integration pattern used in the Encore Health OS Platform, demonstrating correct usage and common anti-patterns to avoid.

Pattern 1: Platform Integration Layer

When to Use: Cross-cutting capabilities needed by multiple cores (forms, notifications, file uploads) Structure: Shared utilities in /src/platform/<capability>/

Example: PF-08 Forms Integration Layer

Problem: Multiple cores need forms functionality, but cores cannot depend on each other. Solution: Platform Foundation provides integration layer that wraps FW core functionality. Implementation:
// ✅ CORRECT: Platform Integration Layer
// Location: /src/platform/forms/FormEmbed.tsx

import { useFormDefinition } from './useFormDefinition';
import { useFormSubmission } from './useFormSubmission';
import { FormRenderer } from './FormRenderer';

export function FormEmbed({ formId, onSuccess, onError }) {
  const { data: form, isLoading } = useFormDefinition(formId);
  const { submit, isSubmitting } = useFormSubmission({ formId });

  if (isLoading) return <LoadingSpinner />;
  if (!form) return <ErrorMessage>Form not found</ErrorMessage>;

  return (
    <FormRenderer
      form={form}
      onSubmit={async (data) => {
        const submission = await submit(data);
        onSuccess?.(submission);
      }}
      isSubmitting={isSubmitting}
    />
  );
}
Usage in RH Core:
// ✅ CORRECT: Import from platform
// Location: /src/cores/rh/pages/RHFormsPage.tsx

import { ModuleFormsPage } from '@/platform/forms/ModuleFormsPage';

export default function RHFormsPage() {
  return (
    <ModuleFormsPage
      owningCore="rh"
      moduleDisplayName="Recovery Housing"
      createFormPath="/fw/forms/new?core=rh"
    />
  );
}
Usage in Other Cores:
// ✅ CORRECT: HR Core using the same platform integration layer
// Location: /src/cores/hr/pages/HRFormsPage.tsx

import { ModuleFormsPage } from '@/platform/forms/ModuleFormsPage';

export default function HRFormsPage() {
  return (
    <ModuleFormsPage
      owningCore="hr"
      moduleDisplayName="Human Resources"
      createFormPath="/fw/forms/new?core=hr"
    />
  );
}
Anti-Pattern (What NOT to Do):
// ❌ WRONG: Direct import from FW core
import { FormEmbed } from '@/cores/fw/components/FormEmbed';

// ❌ WRONG: Shared table between cores
CREATE TABLE shared_form_submissions (...); // Violates core boundaries

// ❌ WRONG: Core-to-core dependency
// In RH core
import { FormService } from '@/cores/fw/services/FormService';
Key Benefits:
  • ✅ Cores remain isolated (no direct FW imports)
  • ✅ Stable API contract (platform layer doesn’t change often)
  • ✅ Single source of truth (all cores use same integration)
  • ✅ Easy to test (mock platform layer, not FW core)

Example: PF-15 Data Lookup Integration Layer

Problem: Form fields need dynamic dropdowns populated from database tables, but cores cannot directly query each other’s tables. Solution: Platform Foundation provides useTableLookup hook that safely queries whitelisted tables with automatic organization scoping. Implementation:
// ✅ CORRECT: Platform Integration Layer
// Location: /src/platform/data-lookup/useTableLookup.ts

import { useQuery } from '@tanstack/react-query';
import { supabase } from '@/integrations/supabase/client';
import { getLookupTableConfig } from './lookupTables';

export function useTableLookup(options: UseTableLookupOptions): UseTableLookupReturn {
  const { table, organizationId, filters = {}, searchQuery = '' } = options;
  
  // Get table configuration from whitelist
  const tableConfig = getLookupTableConfig(table);
  
  const { data, isLoading, error } = useQuery({
    queryKey: ['platform-data-lookup', table, filters, searchQuery, organizationId],
    queryFn: async () => {
      if (!tableConfig) {
        throw new Error(`Table "${table}" is not in the lookup whitelist`);
      }

      const labelColumn = tableConfig.labelColumn;
      const valueColumn = tableConfig.valueColumn;

      // Build query with automatic organization scoping
      let query = supabase
        .from(table as any)
        .select(`${valueColumn}, ${labelColumn}`) as any;

      // Apply organization scope if required
      if (tableConfig.hasOrganizationScope && organizationId) {
        query = query.eq('organization_id', organizationId);
      }

      // Apply additional filters
      Object.entries(filters).forEach(([key, value]) => {
        if (value !== undefined && value !== null) {
          query = query.eq(key, value);
        }
      });

      // Apply search if provided
      if (searchQuery) {
        query = query.ilike(labelColumn, `%${searchQuery}%`);
      }

      const { data, error } = await query;
      if (error) throw error;

      // Map to LookupOption format
      return (data || []).map((row: any) => ({
        value: String(row[valueColumn]),
        label: String(row[labelColumn]),
      }));
    },
    enabled: !!tableConfig,
    staleTime: 5 * 60 * 1000, // 5 minutes
  });

  return { options: data || [], isLoading, error, search, refetch };
}
Usage in Form Fields:
// ✅ CORRECT: Use in form field configuration
// Location: Form field with lookup type

{
  field_type: 'lookup',
  field_key: 'assigned_department',
  label: 'Department',
  settings: {
    lookupTable: 'hr_departments',
    valueColumn: 'id',
    labelColumn: 'name',
    lookupFilters: { is_active: true },
    searchable: true,
  },
}
Anti-Pattern (What NOT to Do):
// ❌ WRONG: Direct query from FW core to HR table
const departments = await supabase
  .from('hr_departments')  // Direct access to HR core table!
  .select('id, name')
  .eq('organization_id', orgId);

// ❌ WRONG: Hardcoded options that need to be dynamic
const departments = [
  { value: '1', label: 'Operations' },
  { value: '2', label: 'Clinical' },
]; // Becomes stale, requires code changes
Key Benefits:
  • ✅ Table whitelist enforcement (security)
  • ✅ Automatic organization scoping (multi-tenant safety)
  • ✅ RLS policy compliance
  • ✅ Reusable across all cores

Example: PF-14 Platform Workforce Integration Layer

Problem: Multiple cores need to select employees (RH for staff assignments, FW for form assignees, etc.), but cores cannot depend on HR core. Solution: Platform Foundation provides EmployeeSelector component and useEmployeeLookup hook. Implementation:
// ✅ CORRECT: Platform Integration Layer
// Location: /src/platform/workforce/EmployeeSelector.tsx

import { useEmployeeLookup } from './useEmployeeLookup';

export function EmployeeSelector({
  onSelect,
  filterBySite,
  filterByDepartment,
  excludeEmployeeIds,
  includeInactive,
  placeholder = 'Select employee...',
}: EmployeeSelectorProps) {
  const [open, setOpen] = useState(false);
  const [selectedId, setSelectedId] = useState<string | undefined>();

  const { employees, isLoading, searchEmployees } = useEmployeeLookup({
    siteId: filterBySite,
    departmentId: filterByDepartment,
    employmentStatus: includeInactive ? undefined : 'active',
  });

  const filteredEmployees = employees.filter(
    (emp) => !excludeEmployeeIds?.includes(emp.id)
  );

  const selectedEmployee = filteredEmployees.find((emp) => emp.id === selectedId);

  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger asChild>
        <Button variant="outline" role="combobox" aria-expanded={open}>
          {selectedEmployee ? (
            <div className="flex items-center gap-2">
              <Avatar className="h-6 w-6">
                <AvatarImage src={selectedEmployee.avatar_url} />
                <AvatarFallback>
                  {selectedEmployee.full_name.split(' ').map((n) => n[0]).join('')}
                </AvatarFallback>
              </Avatar>
              <span>{selectedEmployee.full_name}</span>
            </div>
          ) : (
            <>
              <User className="mr-2 h-4 w-4" />
              {placeholder}
            </>
          )}
        </Button>
      </PopoverTrigger>
      <PopoverContent className="w-[400px] p-0">
        <Command>
          <CommandInput
            placeholder="Search employees..."
            onValueChange={searchEmployees}
          />
          <CommandEmpty>
            {isLoading ? 'Loading employees...' : 'No employees found.'}
          </CommandEmpty>
          <CommandGroup className="max-h-[300px] overflow-auto">
            {filteredEmployees.map((employee) => (
              <CommandItem
                key={employee.id}
                value={employee.id}
                onSelect={() => {
                  setSelectedId(employee.id);
                  onSelect(employee);
                  setOpen(false);
                }}
              >
                <div className="flex flex-col">
                  <span className="font-medium">{employee.full_name}</span>
                  <span className="text-muted-foreground text-sm">
                    {employee.employee_number} • {employee.job_title}
                  </span>
                </div>
              </CommandItem>
            ))}
          </CommandGroup>
        </Command>
      </PopoverContent>
    </Popover>
  );
}
Usage in RH Core (Staff Assignment):
// ✅ CORRECT: Use EmployeeSelector in RH forms
// Location: /src/cores/rh/components/forms/InvestigationActionForm.tsx

import { EmployeeSelector } from '@/platform/workforce/EmployeeSelector';

export function InvestigationActionForm({ action, investigationId, onSuccess }: Props) {
  const form = useForm<FormValues>({
    resolver: zodResolver(schema),
    defaultValues: {
      assigned_to: action?.assigned_to || '',
      // ... other fields
    },
  });

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)}>
        <FormField
          control={form.control}
          name="assigned_to"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Assigned To</FormLabel>
              <FormControl>
                <EmployeeSelector
                  onSelect={(employee) => field.onChange(employee.id)}
                  filterBySite={currentSiteId}
                  placeholder="Select staff member..."
                />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        {/* ... other fields */}
      </form>
    </Form>
  );
}
Anti-Pattern (What NOT to Do):
// ❌ WRONG: Direct import from HR core
import { EmployeeSelector } from '@/cores/hr/components/EmployeeSelector';

// ❌ WRONG: Direct query to HR tables
const employees = await supabase
  .from('hr_employees')  // Direct access to HR core table!
  .select('*')
  .eq('organization_id', orgId);
Key Benefits:
  • ✅ Consistent employee selection UI across all cores
  • ✅ Automatic filtering by site, department, position
  • ✅ Search functionality built-in
  • ✅ No direct HR core dependency

Pattern 2: Event-Based Integration

When to Use: Asynchronous workflows, loose coupling between cores Structure: Domain events published via pg_notify, consumed via triggers or edge functions

Example: FW-03 Automation Engine

Problem: Automation engine needs to react to form submissions without tight coupling. Solution: Form submissions publish events, automation engine subscribes. Implementation:
-- ✅ CORRECT: Event published via database trigger
-- Location: supabase/migrations/20251125032809_7fc8dff8-de99-4948-aaa3-52eb163fd4eb.sql

CREATE OR REPLACE FUNCTION trigger_automation_on_submission()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
  _form_record RECORD;
BEGIN
  -- Get form details for context
  SELECT id, name, organization_id, site_id INTO _form_record
  FROM fw_forms WHERE id = NEW.form_id;

  -- Notify automation executor via pg_notify
  PERFORM pg_notify(
    'automation_trigger',
    json_build_object(
      'trigger_type', CASE 
        WHEN TG_OP = 'INSERT' THEN 'form_submitted'
        WHEN TG_OP = 'UPDATE' THEN 'form_updated'
      END,
      'submission_id', NEW.id,
      'form_id', NEW.form_id,
      'organization_id', _form_record.organization_id,
      'site_id', _form_record.site_id,
      'submitted_by', NEW.submitted_by,
      'submission_data', NEW.submission_data,
      'old_status', CASE WHEN TG_OP = 'UPDATE' THEN OLD.status ELSE NULL END,
      'new_status', NEW.status
    )::text
  );

  RETURN NEW;
END;
$$;

-- Create triggers for form submissions
CREATE TRIGGER after_form_submission_insert
AFTER INSERT ON fw_form_submissions
FOR EACH ROW EXECUTE FUNCTION trigger_automation_on_submission();

CREATE TRIGGER after_form_submission_update
AFTER UPDATE ON fw_form_submissions
FOR EACH ROW EXECUTE FUNCTION trigger_automation_on_submission();
Consumer (Automation Engine Edge Function):
// ✅ CORRECT: Subscribe to events via edge function
// Location: /supabase/functions/automation-executor/index.ts

import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';

serve(async (req) => {
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
  );

  // Subscribe to form submission events
  const channel = supabase
    .channel('form-submissions')
    .on(
      'postgres_changes',
      {
        event: 'INSERT',
        schema: 'public',
        table: 'fw_form_submissions',
      },
      async (payload) => {
        // Process automation rules for this submission
        await processAutomationRules(payload.new);
      }
    )
    .subscribe();

  return new Response(JSON.stringify({ status: 'listening' }), {
    headers: { 'Content-Type': 'application/json' },
  });
});

Example: RH-01 → FA-01 Integration (Planned)

Problem: When resident is admitted, billing account must be created automatically. Solution: RH publishes resident_admitted event, FA subscribes and creates account. Publisher (RH Core):
-- ✅ CORRECT: Event published on resident admission
-- Location: Migration for RH-01

CREATE OR REPLACE FUNCTION rh_publish_admission_event()
RETURNS TRIGGER AS $$
BEGIN
  PERFORM pg_notify(
    'resident_admitted',
    json_build_object(
      'resident_id', NEW.id,
      'organization_id', NEW.organization_id,
      'site_id', NEW.site_id,
      'bed_id', NEW.bed_id,
      'admission_date', NEW.admission_date
    )::text
  );
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_resident_admission_event
AFTER INSERT ON rh_residents
FOR EACH ROW
WHEN (NEW.status = 'admitted')
EXECUTE FUNCTION rh_publish_admission_event();
Subscriber (FA Core Edge Function):
// ✅ CORRECT: Subscribe and create billing account
// Location: /supabase/functions/create-billing-account/index.ts

serve(async (req) => {
  const supabase = createClient(...);

  const channel = supabase
    .channel('resident-admissions')
    .on(
      'postgres_changes',
      {
        event: 'INSERT',
        schema: 'public',
        table: 'rh_residents',
        filter: 'status=eq.admitted',
      },
      async (payload) => {
        const resident = payload.new;
        
        // Create billing account for resident
        const { data: account, error } = await supabase
          .from('fa_resident_accounts')
          .insert({
            resident_id: resident.id,
            organization_id: resident.organization_id,
            site_id: resident.site_id,
            account_status: 'active',
            current_balance: 0,
          })
          .select()
          .single();

        if (error) {
          console.error('Failed to create billing account:', error);
          // Log error, but don't fail resident admission
        }
      }
    )
    .subscribe();

  return new Response(JSON.stringify({ status: 'listening' }), {
    headers: { 'Content-Type': 'application/json' },
  });
});
Anti-Pattern (What NOT to Do):
// ❌ WRONG: Direct function call from RH to FA
// In RH core
import { createBillingAccount } from '@/cores/fa/services/BillingService';

async function admitResident(data) {
  const resident = await createResident(data);
  await createBillingAccount(resident.id); // Direct dependency!
  return resident;
}

// ❌ WRONG: Shared database table
CREATE TABLE shared_resident_billing (
  resident_id uuid REFERENCES rh_residents(id),
  balance decimal,
  -- Violates core boundaries
);

// ❌ WRONG: Synchronous API call (tight coupling)
const account = await fetch('/api/fa/create-account', {
  method: 'POST',
  body: JSON.stringify({ resident_id: resident.id })
}); // Blocks resident admission if FA is down
Key Benefits:
  • ✅ Loose coupling (cores don’t know about each other)
  • ✅ Resilient (if FA is down, resident admission still succeeds)
  • ✅ Scalable (events can be processed asynchronously)
  • ✅ Testable (mock events, not direct calls)

Pattern 3: API Contracts

When to Use: Synchronous request-response interactions Structure: Versioned API endpoints (edge functions) with clear request/response schemas

Example: FA-01 Billing Balance Query (Planned)

Problem: RH needs to query resident billing balance synchronously. Solution: FA provides versioned API endpoint, RH calls it. Provider (FA Core Edge Function):
// ✅ CORRECT: Versioned API endpoint
// Location: /supabase/functions/get-resident-balance/index.ts

interface BalanceRequest {
  resident_id: string;
  organization_id: string;
  as_of_date?: string;
}

interface BalanceResponse {
  resident_id: string;
  current_balance: number;
  past_due_balance: number;
  last_payment_date?: string;
  account_status: 'current' | 'past_due' | 'delinquent';
}

serve(async (req) => {
  const { resident_id, organization_id, as_of_date } = await req.json() as BalanceRequest;

  // Validate organization access (RLS enforced in query)
  const supabase = createClient(...);
  
  const { data: account, error } = await supabase
    .from('fa_resident_accounts')
    .select('*, transactions(*)')
    .eq('resident_id', resident_id)
    .eq('organization_id', organization_id)
    .single();

  if (error || !account) {
    return new Response(
      JSON.stringify({ error: 'Account not found' }),
      { status: 404, headers: { 'Content-Type': 'application/json' } }
    );
  }

  // Calculate balance
  const balance = calculateBalance(account.transactions, as_of_date);

  const response: BalanceResponse = {
    resident_id: account.resident_id,
    current_balance: balance.current,
    past_due_balance: balance.past_due,
    last_payment_date: balance.last_payment?.date,
    account_status: balance.status,
  };

  return new Response(JSON.stringify(response), {
    headers: { 'Content-Type': 'application/json' },
  });
});
Consumer (RH Core):
// ✅ CORRECT: Call API endpoint
// Location: /src/cores/rh/hooks/useResidentBalance.ts

import { useQuery } from '@tanstack/react-query';

export function useResidentBalance(residentId: string) {
  return useQuery({
    queryKey: ['resident-balance', residentId],
    queryFn: async () => {
      const response = await fetch('/api/v1/fa/resident-balance', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          resident_id: residentId,
          organization_id: currentOrg.id,
        }),
      });

      if (!response.ok) {
        throw new Error('Failed to fetch balance');
      }

      return response.json() as BalanceResponse;
    },
  });
}
API Contract Documentation:
// ✅ CORRECT: Contract defined in spec
// Location: /docs/architecture/INTEGRATION_CONTRACTS.md

/**
 * GET /api/v1/fa/resident-balance
 * 
 * Returns current billing balance for a resident.
 * 
 * Request:
 * {
 *   resident_id: uuid;
 *   organization_id: uuid;
 *   as_of_date?: timestamp; // Optional, defaults to now()
 * }
 * 
 * Response:
 * {
 *   resident_id: uuid;
 *   current_balance: number;
 *   past_due_balance: number;
 *   last_payment_date?: timestamp;
 *   account_status: 'current' | 'past_due' | 'delinquent';
 * }
 * 
 * Errors:
 * - 404: Account not found
 * - 403: Access denied (RLS policy)
 * - 500: Server error
 */
Anti-Pattern (What NOT to Do):
// ❌ WRONG: Direct database query from RH to FA tables
// In RH core
const balance = await supabase
  .from('fa_resident_accounts') // Direct access to FA table!
  .select('current_balance')
  .eq('resident_id', residentId)
  .single();

// ❌ WRONG: Unversioned API (breaking changes break consumers)
// /api/fa/balance (no version, can't evolve)

// ❌ WRONG: Tight coupling (RH depends on FA implementation)
import { BillingService } from '@/cores/fa/services/BillingService';
const balance = await BillingService.getBalance(residentId);
Key Benefits:
  • ✅ Versioned APIs (can evolve without breaking consumers)
  • ✅ Clear contracts (request/response schemas documented)
  • ✅ RLS enforced (organization isolation at API level)
  • ✅ Testable (mock API endpoints)

Pattern Selection Guide

When to Use Pattern 1 (Platform Integration Layer)

  • ✅ Multiple cores need the same capability
  • ✅ Capability is cross-cutting (forms, notifications, documents)
  • ✅ Stable API surface (doesn’t change often)
  • ✅ Examples: Forms, Notifications, Document Management

When to Use Pattern 2 (Event-Based)

  • ✅ Asynchronous workflows
  • ✅ Loose coupling desired
  • ✅ Resilience important (consumer can be down)
  • ✅ Examples: Resident admission → Billing, Payment → Status update

When to Use Pattern 3 (API Contracts)

  • ✅ Synchronous request-response needed
  • ✅ Real-time data required
  • ✅ Versioning important for evolution
  • ✅ Examples: Balance queries, Census lookups

Common Anti-Patterns Summary

❌ Direct Core Imports

// ❌ WRONG
import { BillingService } from '@/cores/fa/services/BillingService';
Why Wrong: Violates Constitution Section 1.2 (cores cannot depend on each other) Correct Approach: Use Platform Integration Layer, Events, or API Contracts

❌ Shared Database Tables

-- ❌ WRONG
CREATE TABLE shared_resident_billing (
  resident_id uuid REFERENCES rh_residents(id),
  balance decimal
);
Why Wrong: Table owned by multiple cores violates boundaries Correct Approach: Each core owns its tables, integrate via events/APIs

❌ Hidden Dependencies

// ❌ WRONG: Implicit dependency on FA
function getResidentBalance(id: uuid) {
  return supabase.from('fa_resident_accounts').select(...);
}
Why Wrong: Hidden dependency makes testing and evolution difficult Correct Approach: Explicit integration contract (API, event, or Platform Layer)

Testing Integration Patterns

Pattern 1 Testing

// Mock platform layer, not FW core
jest.mock('@/platform/forms', () => ({
  FormEmbed: jest.fn(() => <div>Mock Form</div>),
}));

Pattern 2 Testing

// Test event publishing and consumption
test('resident admission publishes event', async () => {
  const resident = await admitResident({ ... });
  await waitForEvent('resident_admitted', 5000);
  // Verify event payload
});

Pattern 3 Testing

// Mock API endpoint
nock('https://api.example.com')
  .post('/api/v1/fa/resident-balance')
  .reply(200, { current_balance: 1000 });

Last Updated: 2025-01-12
Next Review: Quarterly (Q2 2025)