3D Blueprints & Simulation Hub
ANSYS-inspired 3D blueprints, digital twin layouts, and simulation-ready visuals for mines, pipelines,
energy, water, and global infrastructure projects.
Building Beyond Borders © 2024 Global Infrastructure Advisory. All Rights Reserved.
blueprint-library.html
GIA-Website/pages/blueprint-library.html
Blueprint Library
Sector-specific blueprint modules inspired by 3D simulation and digital twin practices.
Building Beyond Borders © 2024 Global Infrastructure Advisory. All Rights Reserved.
blueprint-request.html
GIA-Website/pages/blueprint-request.html
Request a Custom 3D Blueprint
Tell us about your mine, pipeline, energy, water, or infrastructure project and we’ll scope a 3D, simulation-informed layout.
Building Beyond Borders © 2024 Global Infrastructure Advisory. All Rights Reserved.
blueprint-upload.html
GIA-Website/pages/blueprint-upload.html
Upload Drawings for 3D Enhancement
Upload PDFs, CAD exports, or sketches. We’ll align them with 3D-ready, simulation-informed layouts.
Building Beyond Borders © 2024 Global Infrastructure Advisory. All Rights Reserved.
blueprints-admin.html
GIA-Website/pages/blueprints-admin.html
No opportunities posted yet.
Global Engineering Marketplace
Connecting programs, opportunities, contractors, workforce, and 3D blueprints into a single, donor-ready
infrastructure delivery platform.
Building Beyond Borders © 2024 Global Infrastructure Advisory. All Rights Reserved.
opportunities: sector, region, type
workforce_applicants: sector, region, type (role)
contractors: sector, region, company_type
server/server.js
const sectorAdjacency = {
'Energy & Power': ['Digital Infrastructure', 'Climate & Resilience'],
'Pipelines': ['Energy & Power', 'Water & Sanitation'],
'Mines': ['Energy & Power', 'Transportation'],
// extend as needed
};
const regionAdjacency = {
'Sub-Saharan Africa': ['North Africa & Middle East'],
'Asia-Pacific': ['Europe & Central Asia'],
// extend as needed
};
function sectorScore(a, b) {
if (!a || !b) return 0;
if (a === b) return 3;
if (sectorAdjacency[a] && sectorAdjacency[a].includes(b)) return 1;
return 0;
}
function regionScore(a, b) {
if (!a || !b) return 0;
if (a === b) return 2;
if (regionAdjacency[a] && regionAdjacency[a].includes(b)) return 1;
return 0;
}
function typeScore(a, b) {
if (!a || !b) return 0;
return a === b ? 3 : 0;
}
function computeMatchScore(item, target) {
return (
sectorScore(item.sector, target.sector) +
regionScore(item.region, target.region) +
typeScore(item.type || item.company_type, target.type || target.company_type)
);
}
opportunity-detail.html?id=123
opportunities-admin.html
Building Beyond Borders © 2024 Global Infrastructure Advisory. All Rights Reserved.
GIA-Website/i18n/
en.
{
"nav.home": "Home",
"nav.marketplace": "Marketplace",
"nav.blueprints": "3D Blueprints",
"nav.programs": "Programs",
"nav.workforce": "Workforce",
"nav.contractors": "Contractors",
"marketplace.title": "Global Engineering Marketplace",
"marketplace.subtitle": "Connecting programs, opportunities, contractors, workforce, and 3D blueprints into a single delivery platform.",
"marketplace.how.title": "How the Marketplace Works",
"marketplace.how.programs": "Donors and governments define programs and post opportunities.",
"marketplace.how.contractors": "Contractors and workforce register their capabilities, sectors, and regions.",
"marketplace.how.blueprints": "3D blueprints and matching logic align the right teams to the right work.",
"marketplace.donors.title": "For Donors & Programs",
"marketplace.donors.point1": "Transparent contractor and workforce pools by sector and region.",
"marketplace.donors.point2": "Explainable matching logic for opportunity alignment.",
"marketplace.donors.point3": "3D blueprints to de-risk design and delivery.",
"marketplace.contractors.title": "For Contractors & Workforce",
"marketplace.contractors.point1": "Opportunities filtered by sector and geography.",
"marketplace.contractors.point2": "3D blueprint support for bids and execution.",
"marketplace.contractors.point3": "Visibility across multiple programs and donors.",
"marketplace.cta.workforce.title": "Join as Workforce",
"marketplace.cta.workforce.text": "Register your skills and be considered for global infrastructure work.",
"marketplace.cta.workforce.button": "Join Workforce",
"marketplace.cta.contractor.title": "Register as Contractor",
"marketplace.cta.contractor.text": "Position your company for donor-funded and private infrastructure projects.",
"marketplace.cta.contractor.button": "Register Contractor",
"marketplace.cta.blueprints.title": "Explore 3D Blueprints",
"marketplace.cta.blueprints.text": "See how 3D and simulation-informed layouts support better delivery.",
"marketplace.cta.blueprints.button": "Open 3D Hub"
}
es.json
{
"nav.home": "Inicio",
"nav.marketplace": "Mercado",
"nav.blueprints": "Planos 3D",
"nav.programs": "Programas",
"nav.workforce": "Fuerza Laboral",
"nav.contractors": "Contratistas",
"marketplace.title": "Mercado Global de Ingeniería",
"marketplace.subtitle": "Conectando programas, oportunidades, contratistas, fuerza laboral y planos 3D en una sola plataforma.",
"marketplace.how.title": "Cómo Funciona el Mercado",
"marketplace.how.programs": "Los donantes y gobiernos definen programas y publican oportunidades.",
"marketplace.how.contractors": "Los contratistas y trabajadores registran sus capacidades, sectores y regiones.",
"marketplace.how.blueprints": "Los planos 3D y la lógica de coincidencia alinean los equipos correctos con el trabajo adecuado.",
"marketplace.donors.title": "Para Donantes y Programas",
"marketplace.donors.point1": "Grupos transparentes de contratistas y trabajadores por sector y región.",
"marketplace.donors.point2": "Lógica de coincidencia explicable para alinear oportunidades.",
"marketplace.donors.point3": "Planos 3D para reducir riesgos en diseño y ejecución.",
"marketplace.contractors.title": "Para Contratistas y Fuerza Laboral",
"marketplace.contractors.point1": "Oportunidades filtradas por sector y geografía.",
"marketplace.contractors.point2": "Soporte de planos 3D para licitaciones y ejecución.",
"marketplace.contractors.point3": "Visibilidad en múltiples programas y donantes.",
"marketplace.cta.workforce.title": "Unirse como Trabajador",
"marketplace.cta.workforce.text": "Registra tus habilidades y participa en proyectos globales.",
"marketplace.cta.workforce.button": "Unirse",
"marketplace.cta.contractor.title": "Registrar Contratista",
"marketplace.cta.contractor.text": "Posiciona tu empresa para proyectos financiados por donantes.",
"marketplace.cta.contractor.button": "Registrar",
"marketplace.cta.blueprints.title": "Explorar Planos 3D",
"marketplace.cta.blueprints.text": "Descubre cómo los planos 3D mejoran la entrega de proyectos.",
"marketplace.cta.blueprints.button": "Abrir Centro 3D"
}
fr.json
{
"nav.home": "Accueil",
"nav.marketplace": "Marché",
"nav.blueprints": "Plans 3D",
"nav.programs": "Programmes",
"nav.workforce": "Main-d'œuvre",
"nav.contractors": "Entrepreneurs",
"marketplace.title": "Marché Mondial de l'Ingénierie",
"marketplace.subtitle": "Connecter programmes, opportunités, entrepreneurs, main-d'œuvre et plans 3D dans une seule plateforme.",
"marketplace.how.title": "Comment Fonctionne le Marché",
"marketplace.how.programs": "Les donateurs et gouvernements définissent des programmes et publient des opportunités.",
"marketplace.how.contractors": "Les entrepreneurs et travailleurs enregistrent leurs capacités, secteurs et régions.",
"marketplace.how.blueprints": "Les plans 3D et la logique d'appariement alignent les bonnes équipes sur les bons projets.",
"marketplace.donors.title": "Pour les Donateurs et Programmes",
"marketplace.donors.point1": "Groupes transparents d'entrepreneurs et de travailleurs par secteur et région.",
"marketplace.donors.point2": "Logique d'appariement explicable pour aligner les opportunités.",
"marketplace.donors.point3": "Plans 3D pour réduire les risques de conception et d'exécution.",
"marketplace.contractors.title": "Pour Entrepreneurs et Travailleurs",
"marketplace.contractors.point1": "Opportunités filtrées par secteur et géographie.",
"marketplace.contractors.point2": "Support de plans 3D pour les offres et l'exécution.",
"marketplace.contractors.point3": "Visibilité sur plusieurs programmes et donateurs.",
"marketplace.cta.workforce.title": "Rejoindre la Main-d'œuvre",
"marketplace.cta.workforce.text": "Enregistrez vos compétences et participez à des projets mondiaux.",
"marketplace.cta.workforce.button": "Rejoindre",
"marketplace.cta.contractor.title": "Enregistrer un Entrepreneur",
"marketplace.cta.contractor.text": "Positionnez votre entreprise pour des projets financés par des donateurs.",
"marketplace.cta.contractor.button": "Enregistrer",
"marketplace.cta.blueprints.title": "Explorer les Plans 3D",
"marketplace.cta.blueprints.text": "Découvrez comment les plans 3D améliorent la livraison des projets.",
"marketplace.cta.blueprints.button": "Ouvrir le Centre 3D"
}
pt.json
{
"nav.home": "Início",
"nav.marketplace": "Mercado",
"nav.blueprints": "Plantas 3D",
"nav.programs": "Programas",
"nav.workforce": "Mão de Obra",
"nav.contractors": "Empreiteiros",
"marketplace.title": "Mercado Global de Engenharia",
"marketplace.subtitle": "Conectando programas, oportunidades, empreiteiros, mão de obra e plantas 3D em uma única plataforma.",
"marketplace.how.title": "Como Funciona o Mercado",
"marketplace.how.programs": "Doadores e governos definem programas e publicam oportunidades.",
"marketplace.how.contractors": "Empreiteiros e trabalhadores registram suas capacidades, setores e regiões.",
"marketplace.how.blueprints": "Plantas 3D e lógica de correspondência alinham as equipes certas ao trabalho certo.",
"marketplace.donors.title": "Para Doadores e Programas",
"marketplace.donors.point1": "Grupos transparentes por setor e região.",
"marketplace.donors.point2": "Lógica de correspondência explicável.",
"marketplace.donors.point3": "Plantas 3D para reduzir riscos.",
"marketplace.contractors.title": "Para Empreiteiros e Trabalhadores",
"marketplace.contractors.point1": "Oportunidades filtradas por setor e geografia.",
"marketplace.contractors.point2": "Suporte de plantas 3D para propostas.",
"marketplace.contractors.point3": "Visibilidade em vários programas.",
"marketplace.cta.workforce.title": "Junte-se como Trabalhador",
"marketplace.cta.workforce.text": "Registre suas habilidades para projetos globais.",
"marketplace.cta.workforce.button": "Participar",
"marketplace.cta.contractor.title": "Registrar Empreiteiro",
"marketplace.cta.contractor.text": "Posicione sua empresa para projetos financiados.",
"marketplace.cta.contractor.button": "Registrar",
"marketplace.cta.blueprints.title": "Explorar Plantas 3D",
"marketplace.cta.blueprints.text": "Veja como plantas 3D melhoram a entrega.",
"marketplace.cta.blueprints.button": "Abrir Centro 3D"
}
ar.json
{
"nav.home": "الرئيسية",
"nav.marketplace": "السوق",
"nav.blueprints": "مخططات ثلاثية الأبعاد",
"nav.programs": "البرامج",
"nav.workforce": "العمالة",
"nav.contractors": "المقاولون",
"marketplace.title": "السوق العالمي للهندسة",
"marketplace.subtitle": "ربط البرامج والفرص والمقاولين والعمالة والمخططات ثلاثية الأبعاد في منصة واحدة.",
"marketplace.how.title": "كيف يعمل السوق",
"marketplace.how.programs": "يحدد المانحون والحكومات البرامج وينشرون الفرص.",
"marketplace.how.contractors": "يسجل المقاولون والعمال قدراتهم وقطاعاتهم ومناطقهم.",
"marketplace.how.blueprints": "تقوم المخططات ثلاثية الأبعاد والمنطق الذكي بمواءمة الفرق المناسبة مع العمل المناسب.",
"marketplace.donors.title": "للجهات المانحة والبرامج",
"marketplace.donors.point1": "مجموعات شفافة حسب القطاع والمنطقة.",
"marketplace.donors.point2": "منطق مطابق قابل للتفسير.",
"marketplace.donors.point3": "مخططات ثلاثية الأبعاد لتقليل المخاطر.",
"marketplace.contractors.title": "للمقاولين والعمال",
"marketplace.contractors.point1": "فرص مصنفة حسب القطاع والجغرافيا.",
"marketplace.contractors.point2": "دعم المخططات ثلاثية الأبعاد للعطاءات.",
"marketplace.contractors.point3": "رؤية عبر برامج متعددة.",
"marketplace.cta.workforce.title": "انضم كعامل",
"marketplace.cta.workforce.text": "سجل مهاراتك للمشاريع العالمية.",
"marketplace.cta.workforce.button": "انضمام",
"marketplace.cta.contractor.title": "تسجيل مقاول",
"marketplace.cta.contractor.text": "ضع شركتك في موقع مناسب للمشاريع الممولة.",
"marketplace.cta.contractor.button": "تسجيل",
"marketplace.cta.blueprints.title": "استكشاف المخططات ثلاثية الأبعاد",
"marketplace.cta.blueprints.text": "اكتشف كيف تحسن المخططات ثلاثية الأبعاد تنفيذ المشاريع.",
"marketplace.cta.blueprints.button": "فتح المركز ثلاثي الأبعاد"
}
sw.json
{
"nav.home": "Nyumbani",
"nav.marketplace": "Soko",
"nav.blueprints": "Michoro ya 3D",
"nav.programs": "Programu",
"nav.workforce": "Wafanyakazi",
"nav.contractors": "Wakandarasi",
"marketplace.title": "Soko la Uhandisi Duniani",
"marketplace.subtitle": "Kuunganisha programu, fursa, wakandarasi, wafanyakazi na michoro ya 3D katika jukwaa moja.",
"marketplace.how.title": "Jinsi Soko Linavyofanya Kazi",
"marketplace.how.programs": "Wafadhili na serikali hufafanua programu na kuchapisha fursa.",
"marketplace.how.contractors": "Wakandarasi na wafanyakazi husajili uwezo wao, sekta na maeneo.",
"marketplace.how.blueprints": "Michoro ya 3D na mantiki ya ulinganifu huunganisha timu sahihi na kazi sahihi.",
"marketplace.donors.title": "Kwa Wafadhili na Programu",
"marketplace.donors.point1": "Vikundi vya uwazi kwa sekta na eneo.",
"marketplace.donors.point2": "Mantiki ya ulinganifu inayoweza kuelezewa.",
"marketplace.donors.point3": "Michoro ya 3D kupunguza hatari.",
"marketplace.contractors.title": "Kwa Wakandarasi na Wafanyakazi",
"marketplace.contractors.point1": "Fursa zilizochujwa kwa sekta na jiografia.",
"marketplace.contractors.point2": "Msaada wa michoro ya 3D kwa zabuni.",
"marketplace.contractors.point3": "Mwonekano katika programu nyingi.",
"marketplace.cta.workforce.title": "Jiunge kama Mfanyakazi",
"marketplace.cta.workforce.text": "Sajili ujuzi wako kwa miradi ya kimataifa.",
"marketplace.cta.workforce.button": "Jiunge",
"marketplace.cta.contractor.title": "Sajili Mkandarasi",
"marketplace.cta.contractor.text": "Weka kampuni yako kwa miradi inayofadhiliwa.",
"marketplace.cta.contractor.button": "Sajili",
"marketplace.cta.blueprints.title": "Gundua Michoro ya 3D",
"marketplace.cta.blueprints.text": "Jifunze jinsi michoro ya 3D inavyoboreshwa utoaji wa miradi.",
"marketplace.cta.blueprints.button": "Fungua Kituo cha 3D"
}
hi.json
"nav.home": "होम",
"nav.marketplace": "मार्केटप्लेस",
"nav.blueprints": "3D ब्लूप्रिंट",
"nav.programs": "कार्यक्रम",
"nav.workforce": "कार्यबल",
"nav.contractors": "ठेकेदार",
"marketplace.title": "वैश्विक इंजीनियरिंग मार्केटप्लेस",
"marketplace.subtitle": "कार्यक्रमों, अवसरों, ठेकेदारों, कार्यबल और 3D ब्लूप्रिंट को एक प्लेटफ़ॉर्म में जोड़ना।",
"marketplace.how.title": "मार्केटप्लेस कैसे काम करता है",
"marketplace.how.programs": "दाता और सरकारें कार्यक्रम निर्धारित करती हैं और अवसर प्रकाशित करती हैं।",
"marketplace.how.contractors": "ठेकेदार और कार्यबल अपनी क्षमताएँ, क्षेत्र और क्षेत्र पंजीकृत करते हैं।",
"marketplace.how.blueprints": "3D ब्लूप्रिंट और मिलान तर्क सही टीमों को सही काम से जोड़ते हैं।",
"marketplace.donors.title": "दाताओं और कार्यक्रमों के लिए",
"marketplace.donors.point1": "क्षेत्र और क्षेत्र के अनुसार पारदर्शी समूह।",
"marketplace.donors.point2": "समझने योग्य मिलान तर्क।",
"marketplace.donors.point3": "जोखिम कम करने के लिए 3D ब्लूप्रिंट।",
"marketplace.contractors.title": "ठेकेदारों और कार्यबल के लिए",
"marketplace.contractors.point1": "क्षेत्र और भूगोल के अनुसार फ़िल्टर किए गए अवसर।",
"marketplace.contractors.point2": "बोली और निष्पादन के लिए 3D ब्लूप्रिंट समर्थन।",
"marketplace.contractors.point3": "कई कार्यक्रमों में दृश्यता।",
"marketplace.cta.workforce.title": "कार्यबल के रूप में शामिल हों",
"marketplace.cta.workforce.text": "अपनी कौशल पंजीकृत करें और वैश्विक परियोजनाओं में भाग लें।",
"
{
"policy.buyamerican.global.title": "Buy American Alignment (When Applicable)",
"policy.buyamerican.global.text": "GIA supports Buy American / Buy USA sourcing requirements for U.S.-funded programs. For international projects, where U.S.-origin materials are not available or not required, GIA follows the procurement rules of the donor, government, or project authority."
}
{
"policy.buyamerican.global.title": "Cumplimiento Buy American (Cuando Aplica)",
"policy.buyamerican.global.text": "GIA cumple con los requisitos Buy American / Buy USA para programas financiados por los Estados Unidos. En proyectos internacionales donde los materiales de origen estadounidense no están disponibles o no son obligatorios, GIA sigue las normas de adquisición del donante, gobierno o autoridad del proyecto."
}
{
"policy.buyamerican.global.title": "Conformité Buy American (Lorsque Applicable)",
"policy.buyamerican.global.text": "GIA respecte les exigences Buy American / Buy USA pour les programmes financés par les États-Unis. Pour les projets internationaux où les matériaux d'origine américaine ne sont pas disponibles ou ne sont pas requis, GIA suit les règles d'approvisionnement du bailleur de fonds, du gouvernement ou de l'autorité du projet."
}
{
"policy.buyamerican.global.title": "Conformidade Buy American (Quando Aplicável)",
"policy.buyamerican.global.text": "A GIA cumpre os requisitos Buy American / Buy USA para programas financiados pelos Estados Unidos. Em projetos internacionais onde materiais de origem americana não estão disponíveis ou não são exigidos, a GIA segue as regras de aquisição do doador, governo ou autoridade do projeto."
}
{
"policy.buyamerican.global.title": "الامتثال لسياسة Buy American (عند الاقتضاء)",
"policy.buyamerican.global.text": "تلتزم GIA بمتطلبات Buy American / Buy USA للبرامج الممولة من الولايات المتحدة. وفي المشاريع الدولية التي لا تتوفر فيها مواد أمريكية المنشأ أو لا تكون مطلوبة، تلتزم GIA بقواعد المشتريات الخاصة بالجهة المانحة أو الحكومة أو سلطة المشروع."
}
{
"policy.buyamerican.global.title": "Uzingatiaji wa Buy American (Inapotumika)",
"policy.buyamerican.global.text": "GIA inatii mahitaji ya Buy American / Buy USA kwa programu zinazofadhiliwa na Marekani. Kwa miradi ya kimataifa ambapo vifaa vya asili ya Marekani havipatikani au havihitajiki, GIA hufuata sheria za ununuzi za mfadhili, serikali au mamlaka ya mradi."
}
{
"policy.buyamerican.global.title": "Buy American अनुपालन (जब लागू हो)",
"policy.buyamerican.global.text": "GIA अमेरिकी वित्तपोषित कार्यक्रमों के लिए Buy American / Buy USA आवश्यकताओं का पालन करता है। अंतरराष्ट्रीय परियोजनाओं में, जहाँ अमेरिकी मूल की सामग्री उपलब्ध नहीं होती या आवश्यक नहीं होती, GIA दाता, सरकार या परियोजना प्राधिकरण के खरीद नियमों का पालन करता है।"
}
{
"policy.buyamerican.global.title": "Buy American 合规(在适用时)",
"policy.buyamerican.global.text": "GIA 遵守美国资助项目的 Buy American / Buy USA 要求。在国际项目中,如果美国原产材料不可获得或不被要求,GIA 遵循捐助方、政府或项目机构的采购规定。"
}
/pages/leadership.html
Leadership & Governance
A platform built for long-term continuity, institutional stability, and global infrastructure delivery.
Building Beyond Borders © 2024 Global Infrastructure Advisory. All Rights Reserved.
/assets/components/header.html
`
Format: JSON (except file downloads)
---
## 1. Endpoints Overview
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/` | Create document metadata or upload file |
| POST | `/:id/generate` | Generate PDF from template + data |
| GET | `/` | List documents (filterable) |
| GET | `/:id` | Get document metadata |
| POST | `/:id/tokens` | Create secure download token |
| GET | `/:id/tokens` | List active tokens |
| DELETE | `/tokens/:token` | Revoke token |
| GET | `/download/:token` | Secure download |
| GET | `/:id/logs` | Access logs |
---
## 2. Document Model
```json
{
"id": "string",
"title": "string",
"filename": "string",
"program_area": "string",
"tags": ["string"],
"version": "1.0",
"storage_provider": "local | s3 | r2",
"storage_path": "string",
"created_by": "email",
"created_at": "ISO8601",
"download_count": 0
}
```
---
## 3. Token Model (Secure Download Links)
```json
{
"token": "string",
"doc_id": "string",
"expires_at": "ISO8601",
"max_uses": 1,
"used_count": 0,
"created_for": "email",
"created_at": "ISO8601"
}
```
---
## 4. Endpoint Details
### POST /v1/docs
Create metadata + upload a file.
**Request**
```json
{
"title": "PMO Framework",
"program_area": "Global",
"tags": ["template", "pmo"],
"version": "1.0"
}
```
**Response**
```json
{
"status": "created",
"id": "doc_12345"
}
```
---
### POST /v1/docs/:id/generate
Generate a PDF from a template + data.
**Request**
```json
{
"template": "capability-statement",
"data": {
"company_name": "Global Infrastructure Advisory",
"program": "WV–Ireland Alliance"
}
}
```
**Response**
```json
{
"status": "generated",
"id": "doc_12345",
"version": "1.1"
}
```
---
### GET /v1/docs
List documents with filters.
**Query Params**
- `program_area`
- `tag`
- `version`
- `search`
**Response**
```json
[
{
"id": "doc_12345",
"title": "PMO Framework",
"program_area": "Global",
"tags": ["template"],
"version": "1.0"
}
]
```
---
### GET /v1/docs/:id
Get metadata for a document.
---
### POST /v1/docs/:id/tokens
Create a secure download token.
**Request**
```json
{
"expires_in": 86400,
"max_uses": 1,
"email": "client@example.com"
}
```
**Response**
```json
{
"token": "abc123",
"download_url": "https://api.globalinfrastructureadvisory.com/v1/docs/download/abc123"
}
```
---
### GET /v1/docs/:id/tokens
List active tokens for a document.
---
### DELETE /v1/docs/tokens/:token
Revoke a token.
---
### GET /v1/docs/download/:token
Secure download endpoint.
**Behavior**
- Validates token
- Checks expiration
- Checks max uses
- Logs download
- Streams file
---
### GET /v1/docs/:id/logs
Access logs for a document.
**Response**
```json
[
{
"timestamp": "2026-03-19T21:00:00Z",
"email": "client@example.com",
"ip": "123.45.67.89",
"user_agent": "Chrome",
"token": "abc123"
}
]
```
---
## 5. Event Emission (Next‑Gen)
Events emitted:
- `doc.created`
- `doc.generated`
- `doc.token.created`
- `doc.token.revoked`
- `doc.downloaded`
---
## 6. Storage Provider Abstraction
Supported providers:
- local filesystem
- S3
- Cloudflare R2
- Azure Blob
Switching providers requires config only.
---
## 7. Zero‑Trust Security Model
- Every request authenticated
- Every action logged
- Tokens scoped to a document
- Tokens tied to an email
- Optional IP binding
- Optional single‑use tokens
- Optional short‑lived signed URLs
/pages/document-manager.html
document-manager.html
Document Manager – GIA
Document Manager
Manage documents, generate PDFs, create secure links, and view logs.
Upload Document
Title
Program Area
Tags (comma separated)
Version
Upload File
Upload Document
Generate PDF
Document ID
Template Name
Data (JSON)
Generate PDF
Documents
ID
Title
Program
Version
Actions
backend/routes/docs.js
// backend/routes/docs.js
import express from "express";
import pool from "../config/db.js";
import fs from "fs";
import path from "path";
const router = express.Router();
// CONFIG: adjust this to your storage root
const LOCAL_DOCS_ROOT = path.join(process.cwd(), "docs");
// Helper: load document by id
async function getDocumentById(id) {
const result = await pool.query(
`SELECT id, title, filename, storage_provider, storage_path
FROM documents
WHERE id = $1`,
[id]
);
return result.rows[0] || null;
}
// Helper: load token
async function getToken(token) {
const result = await pool.query(
`SELECT token, doc_id, expires_at, max_uses, used_count, created_for
FROM doc_tokens
WHERE token = $1`,
[token]
);
return result.rows[0] || null;
}
// Helper: increment token usage
async function incrementTokenUse(token) {
await pool.query(
`UPDATE doc_tokens
SET used_count = used_count + 1
WHERE token = $1`,
[token]
);
}
// Helper: log download
async function logDownload({ doc_id, token, email, ip, user_agent }) {
await pool.query(
`INSERT INTO doc_download_logs (doc_id, token, email, ip, user_agent)
VALUES ($1, $2, $3, $4, $5)`,
[doc_id, token, email, ip, user_agent]
);
}
// GET /v1/docs/download/:token
router.get("/v1/docs/download/:token", async (req, res) => {
try {
const { token } = req.params;
const now = new Date();
const tokenRow = await getToken(token);
if (!tokenRow) {
return res.status(404).json({ error: "Invalid token" });
}
// Check expiration
if (tokenRow.expires_at && new Date(tokenRow.expires_at) < now) {
return res.status(410).json({ error: "Token expired" });
}
// Check max uses
if (
tokenRow.max_uses !== null &&
tokenRow.max_uses !== undefined &&
tokenRow.used_count >= tokenRow.max_uses
) {
return res.status(429).json({ error: "Token usage limit reached" });
}
const doc = await getDocumentById(tokenRow.doc_id);
if (!doc) {
return res.status(404).json({ error: "Document not found" });
}
// For now: local storage only (storage_provider = 'local')
if (doc.storage_provider !== "local") {
return res
.status(501)
.json({ error: "Storage provider not implemented in this route" });
}
const filePath = path.join(LOCAL_DOCS_ROOT, doc.storage_path || doc.filename);
if (!fs.existsSync(filePath)) {
return res.status(404).json({ error: "File not found on server" });
}
// Log download
await logDownload({
doc_id: doc.id,
token: tokenRow.token,
email: tokenRow.created_for || null,
ip: req.ip,
user_agent: req.headers["user-agent"] || ""
});
// Increment token usage
await incrementTokenUse(tokenRow.token);
// Stream file
res.download(filePath, doc.filename);
} catch (err) {
console.error("Download error:", err);
res.status(500).json({ error: "Error processing download" });
}
});
export default router;
backend/routes/docs.js
// backend/routes/docs.js
import express from "express";
import pool from "../config/db.js";
import multer from "multer";
import path from "path";
import fs from "fs";
const router = express.Router();
// Local storage root
const LOCAL_DOCS_ROOT = path.join(process.cwd(), "docs");
// Ensure docs folder exists
if (!fs.existsSync(LOCAL_DOCS_ROOT)) {
fs.mkdirSync(LOCAL_DOCS_ROOT, { recursive: true });
}
// Multer for file uploads
const upload = multer({ dest: LOCAL_DOCS_ROOT });
/* ---------------------------------------------------------
1. CREATE DOCUMENT METADATA + UPLOAD FILE
POST /v1/docs
--------------------------------------------------------- */
router.post("/v1/docs", upload.single("file"), async (req, res) => {
try {
const { title, program_area, tags, version } = req.body;
const file = req.file;
if (!file) {
return res.status(400).json({ error: "File is required" });
}
const filename = file.originalname;
const storage_path = file.filename; // stored name
const result = await pool.query(
`INSERT INTO documents
(title, filename, program_area, tags, version, storage_provider, storage_path)
VALUES ($1, $2, $3, $4, $5, 'local', $6)
RETURNING id`,
[title, filename, program_area, tags, version, storage_path]
);
res.json({ status: "created", id: result.rows[0].id });
} catch (err) {
console.error("Upload error:", err);
res.status(500).json({ error: "Error uploading document" });
}
});
/* ---------------------------------------------------------
2. GENERATE PDF FROM TEMPLATE
POST /v1/docs/:id/generate
--------------------------------------------------------- */
router.post("/v1/docs/:id/generate", async (req, res) => {
try {
const { id } = req.params;
const { template, data } = req.body;
// Placeholder: actual PDF generation logic goes here
// For now, we simulate a new version being created
const newVersion = Date.now().toString();
await pool.query(
`UPDATE documents
SET version = $1
WHERE id = $2`,
[newVersion, id]
);
res.json({ status: "generated", id, version: newVersion });
} catch (err) {
console.error("Generate error:", err);
res.status(500).json({ error: "Error generating PDF" });
}
});
/* ---------------------------------------------------------
3. LIST DOCUMENTS
GET /v1/docs
--------------------------------------------------------- */
router.get("/v1/docs", async (req, res) => {
try {
const result = await pool.query(
`SELECT id, title, program_area, tags, version
FROM documents
ORDER BY created_at DESC`
);
res.json(result.rows);
} catch (err) {
console.error("List error:", err);
res.status(500).json({ error: "Error loading documents" });
}
});
/* ---------------------------------------------------------
4. GET DOCUMENT METADATA
GET /v1/docs/:id
--------------------------------------------------------- */
router.get("/v1/docs/:id", async (req, res) => {
try {
const result = await pool.query(
`SELECT *
FROM documents
WHERE id = $1`,
[req.params.id]
);
if (!result.rows.length) {
return res.status(404).json({ error: "Document not found" });
}
res.json(result.rows[0]);
} catch (err) {
console.error("Metadata error:", err);
res.status(500).json({ error: "Error loading metadata" });
}
});
/* ---------------------------------------------------------
5. CREATE SECURE DOWNLOAD TOKEN
POST /v1/docs/:id/tokens
--------------------------------------------------------- */
router.post("/v1/docs/:id/tokens", async (req, res) => {
try {
const { id } = req.params;
const { expires_in, max_uses, email } = req.body;
const token = Math.random().toString(36).substring(2, 12);
const expires_at = new Date(Date.now() + expires_in * 1000);
await pool.query(
`INSERT INTO doc_tokens
(token, doc_id, expires_at, max_uses, used_count, created_for)
VALUES ($1, $2, $3, $4, 0, $5)`,
[token, id, expires_at, max_uses, email]
);
res.json({
token,
download_url: `https://api.globalinfrastructureadvisory.com/v1/docs/download/${token}`
});
} catch (err) {
console.error("Token error:", err);
res.status(500).json({ error: "Error creating token" });
}
});
/* ---------------------------------------------------------
6. LIST TOKENS FOR A DOCUMENT
GET /v1/docs/:id/tokens
--------------------------------------------------------- */
router.get("/v1/docs/:id/tokens", async (req, res) => {
try {
const result = await pool.query(
`SELECT token, expires_at, max_uses, used_count, created_for
FROM doc_tokens
WHERE doc_id = $1`,
[req.params.id]
);
res.json(result.rows);
} catch (err) {
console.error("Token list error:", err);
res.status(500).json({ error: "Error loading tokens" });
}
});
/* ---------------------------------------------------------
7. REVOKE TOKEN
DELETE /v1/docs/tokens/:token
--------------------------------------------------------- */
router.delete("/v1/docs/tokens/:token", async (req, res) => {
try {
await pool.query(
`DELETE FROM doc_tokens
WHERE token = $1`,
[req.params.token]
);
res.json({ status: "revoked" });
} catch (err) {
console.error("Revoke error:", err);
res.status(500).json({ error: "Error revoking token" });
}
});
/* ---------------------------------------------------------
8. DOWNLOAD GATEWAY (from previous file)
GET /v1/docs/download/:token
--------------------------------------------------------- */
// This route is already implemented in your previous file.
// Keep it in the same router or merge it here.
export default router;
// server.js
import express from "express";
import cors from "cors";
import docsRouter from "./routes/docs.js"; // <-- Document Manager Routes
import feedRouter from "./routes/feed.js"; // (already in your system)
import rssRouter from "./routes/rss.js"; // (already in your system)
import programsRouter from "./routes/programs.js";
import opportunitiesRouter from "./routes/opportunities.js";
import productsRouter from "./routes/products.js";
import usersRouter from "./routes/users.js";
import logsRouter from "./routes/logs.js";
const app = express();
// Middleware
app.use(cors());
app.use(express.json());
// ROUTES
app.use(docsRouter); // <-- Document Manager API (v1)
app.use(feedRouter);
app.use(rssRouter);
app.use(programsRouter);
app.use(opportunitiesRouter);
app.use(productsRouter);
app.use(usersRouter);
app.use(logsRouter);
// Health Check
app.get("/", (req, res) => {
res.json({ status: "GIA Backend Running" });
});
// Start Server
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`GIA backend running on port ${PORT}`);
});
/database/schema-document-manager.sql
schema-document-manager.sql
-- ============================================================
-- DOCUMENT MANAGER – DATABASE SCHEMA (v1)
-- Compatible with PostgreSQL
-- ============================================================
-- ------------------------------------------------------------
-- 1. DOCUMENTS TABLE
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS documents (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
filename TEXT NOT NULL,
program_area TEXT,
tags TEXT,
version TEXT DEFAULT '1.0',
storage_provider TEXT DEFAULT 'local',
storage_path TEXT,
created_by TEXT,
created_at TIMESTAMP DEFAULT NOW(),
download_count INTEGER DEFAULT 0
);
-- ------------------------------------------------------------
-- 2. SECURE TOKENS TABLE
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS doc_tokens (
token TEXT PRIMARY KEY,
doc_id INTEGER REFERENCES documents(id) ON DELETE CASCADE,
expires_at TIMESTAMP,
max_uses INTEGER DEFAULT 1,
used_count INTEGER DEFAULT 0,
created_for TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
-- Index for faster lookups
CREATE INDEX IF NOT EXISTS idx_doc_tokens_doc_id
ON doc_tokens (doc_id);
-- ------------------------------------------------------------
-- 3. DOWNLOAD LOGS TABLE
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS doc_download_logs (
id SERIAL PRIMARY KEY,
doc_id INTEGER REFERENCES documents(id) ON DELETE CASCADE,
token TEXT,
email TEXT,
ip TEXT,
user_agent TEXT,
downloaded_at TIMESTAMP DEFAULT NOW()
);
-- Index for analytics
CREATE INDEX IF NOT EXISTS idx_doc_logs_doc_id
ON doc_download_logs (doc_id);
-- ------------------------------------------------------------
-- 4. OPTIONAL: EVENT LOGS (NEXT-GEN)
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS doc_events (
id SERIAL PRIMARY KEY,
event_type TEXT NOT NULL,
doc_id INTEGER,
token TEXT,
metadata JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
-- Index for event queries
CREATE INDEX IF NOT EXISTS idx_doc_events_type
ON doc_events (event_type);
backend/services/pdfGenerator.js
// backend/services/pdfGenerator.js
import fs from "fs";
import path from "path";
import pdf from "html-pdf";
const TEMPLATE_ROOT = path.join(process.cwd(), "templates");
const OUTPUT_ROOT = path.join(process.cwd(), "docs/generated");
// Ensure output folder exists
if (!fs.existsSync(OUTPUT_ROOT)) {
fs.mkdirSync(OUTPUT_ROOT, { recursive: true });
}
/**
* Load an HTML template by name
*/
function loadTemplate(templateName) {
const filePath = path.join(TEMPLATE_ROOT, `${templateName}.html`);
if (!fs.existsSync(filePath)) {
throw new Error(`Template not found: ${templateName}`);
}
return fs.readFileSync(filePath, "utf8");
}
/**
* Replace {{placeholders}} in the template with data
*/
function applyDataToTemplate(html, data) {
let output = html;
for (const key in data) {
const placeholder = new RegExp(`{{${key}}}`, "g");
output = output.replace(placeholder, data[key]);
}
return output;
}
/**
* Generate a PDF from a template + data
*/
export async function generatePDF(templateName, data) {
return new Promise((resolve, reject) => {
try {
const html = loadTemplate(templateName);
const filled = applyDataToTemplate(html, data);
const filename = `${templateName}-${Date.now()}.pdf`;
const outputPath = path.join(OUTPUT_ROOT, filename);
pdf.create(filled).toFile(outputPath, (err, res) => {
if (err) return reject(err);
resolve({
filename,
path: outputPath
});
});
} catch (err) {
reject(err);
}
});
}
/templates/templateName.html
{{company_name}} → “Global Infrastructure Advisory
/docs/generated/
const pdf = await generatePDF("capability-statement", data);
templates/capability-statement.html
{{company_name}} – Capability Statement
{{company_name}}
Program: {{program}}
Website: {{website}}
Email: {{email}}
Phone: {{phone}}
Core Competencies
{{core_competencies}}
Differentiators
{{differentiators}}
Past Performance
{{past_performance}}
DUNS: {{duns}}
UEI: {{uei}}
CAGE: {{cage}}
NAICS: {{naics}}
PSC: {{psc}}
Socioeconomic: {{socioeconomic}}
Key Contacts
{{key_contacts}}
await generatePDF("capability-statement", {
company_name: "Global Infrastructure Advisory",
program: "WV–Ireland Alliance",
website: "https://globalinfrastructureadvisory.com",
email: "info@example.com",
phone: "+1 (555) 123-4567",
core_competencies: "...",
differentiators: "...",
past_performance: "...",
duns: "...",
uei: "...",
cage: "...",
naics: "...",
psc: "...",
socioeconomic: "...",
key_contacts: "..."
});
templates/program-sheet.html
program-sheet.html
{{program_name}} – Program Sheet
{{program_name}}
Region: {{region}}
Sponsor: {{sponsor}}
Program Lead: {{program_lead}}
Contact Email: {{contact_email}}
Program Overview
{{overview}}
Objectives
{{objectives}}
Target Beneficiaries
{{beneficiaries}}
Key Activities
{{activities}}
Start Date: {{start_date}}
End Date: {{end_date}}
Status: {{status}}
Funding Source: {{funding_source}}
Budget: {{budget}}
Currency: {{currency}}
Partners & Stakeholders
{{partners}}
Risks & Mitigation
{{risks}}
Monitoring & Evaluation
{{monitoring}}
templates/contractor-profile.html
contractor-profile.html
{{full_name}} – Contractor Profile
{{full_name}}
Location: {{location}}
Email: {{email}}
Phone: {{phone}}
Availability: {{availability}}
Professional Summary
{{summary}}
Certifications
{{certifications}}
Work Experience
{{experience}}
Years of Experience: {{years_experience}}
Primary Sector: {{primary_sector}}
Secondary Sector: {{secondary_sector}}
Hourly Rate: {{hourly_rate}}
Daily Rate: {{daily_rate}}
Contract Type: {{contract_type}}
Tools & Technologies
{{tools}}
References
{{references}}
templates/opportunity-summary.html
opportunity-summary.html
{{opportunity_title}} – Opportunity Summary
{{opportunity_title}}
Program: {{program}}
Region: {{region}}
Sponsor: {{sponsor}}
Contact Email: {{contact_email}}
Opportunity Overview
{{overview}}
Required Skills
{{required_skills}}
Deliverables
{{deliverables}}
Start Date: {{start_date}}
End Date: {{end_date}}
Status: {{status}}
Budget: {{budget}}
Funding Source: {{funding_source}}
Currency: {{currency}}
Eligibility Requirements
{{eligibility}}
Submission Instructions
{{submission_instructions}}
Evaluation Criteria
{{evaluation_criteria}}
templates/workforce-profile.html
workforce-profile.html
{{full_name}} – Workforce Profile
{{full_name}}
Location: {{location}}
Email: {{email}}
Phone: {{phone}}
Availability: {{availability}}
Preferred Work Type: {{work_type}}
Professional Summary
{{summary}}
Skills & Competencies
{{skills}}
Certifications & Training
{{certifications}}
Work History
{{work_history}}
Years of Experience: {{years_experience}}
Primary Sector: {{primary_sector}}
Secondary Sector: {{secondary_sector}}
Hourly Rate: {{hourly_rate}}
Daily Rate: {{daily_rate}}
Employment Type: {{employment_type}}
Tools & Technologies
{{tools}}
References
{{references}}
DOCUMENT-MANAGER-FOLDER-STRUCTURE.md
# Document Manager – Folder Structure (v1)
This structure organizes:
- Backend routes
- Services (PDF generator)
- Templates
- Generated PDFs
- Database schema
- API specs
- Admin UI
```
project-root/
│
├── backend/
│ ├── routes/
│ │ └── docs.js # Document Manager API routes
│ │
│ ├── services/
│ │ └── pdfGenerator.js # PDF generation engine
│ │
│ ├── config/
│ │ └── db.js # Database connection
│ │
│ └── server.js # Express server wiring
│
├── templates/
│ ├── capability-statement.html
│ ├── program-sheet.html
│ ├── contractor-profile.html
│ ├── opportunity-summary.html
│ └── workforce-profile.html
│
├── docs/
│ ├── generated/ # Auto-generated PDFs
│ └── uploads/ # (Optional) raw uploaded files
│
├── database/
│ └── schema-document-manager.sql # PostgreSQL schema
│
├── api-specs/
│ └── document-manager-v1.yaml # OpenAPI spec
│
├── pages/
│ └── document-manager.html # Admin UI
│
├── logs/
│ └── (optional) event logs, audit logs
│
├── .env # Environment variables
│
└── README.md # Developer documentation
```
backend/services/matchingEngine.js
matchingEngine.js
// backend/services/matchingEngine.js
/**
* Next‑Gen Matching Engine (v1)
* -----------------------------------------
* This engine scores matches between:
* - Workforce ↔ Opportunities
* - Contractors ↔ Opportunities
* - Workforce ↔ Programs
*
* Scoring is transparent, explainable, and donor‑aligned.
* Each scoring component returns:
* { score: Number, reason: String }
*/
export function matchCandidateToOpportunity(candidate, opportunity) {
const results = [];
// 1. Skills Match
results.push(scoreSkills(candidate.skills, opportunity.required_skills));
// 2. Sector Alignment
results.push(scoreSector(candidate.primary_sector, opportunity.sector));
// 3. Experience Level
results.push(scoreExperience(candidate.years_experience, opportunity.min_experience));
// 4. Location / Region
results.push(scoreLocation(candidate.location, opportunity.region));
// 5. Availability
results.push(scoreAvailability(candidate.availability, opportunity.start_date));
// Final score (0–100)
const finalScore = Math.round(
results.reduce((sum, r) => sum + r.score, 0) / results.length
);
return {
candidate_id: candidate.id,
opportunity_id: opportunity.id,
score: finalScore,
breakdown: results
};
}
/* ---------------------------------------------------------
SCORING COMPONENTS
--------------------------------------------------------- */
function scoreSkills(candidateSkills = [], requiredSkills = []) {
if (!requiredSkills.length) {
return { score: 100, reason: "No required skills listed" };
}
const matches = requiredSkills.filter(s =>
candidateSkills.map(x => x.toLowerCase()).includes(s.toLowerCase())
);
const score = Math.round((matches.length / requiredSkills.length) * 100);
return {
score,
reason: `Matched ${matches.length} of ${requiredSkills.length} required skills`
};
}
function scoreSector(candidateSector, opportunitySector) {
if (!opportunitySector) return { score: 100, reason: "No sector specified" };
const score = candidateSector?.toLowerCase() === opportunitySector?.toLowerCase()
? 100
: 40;
return {
score,
reason: score === 100
? "Primary sector aligns"
: "Sector mismatch"
};
}
function scoreExperience(candidateYears, requiredYears) {
if (!requiredYears) return { score: 100, reason: "No experience requirement" };
const score =
candidateYears >= requiredYears
? 100
: Math.max(20, Math.round((candidateYears / requiredYears) * 100));
return {
score,
reason: `Candidate has ${candidateYears} years; required ${requiredYears}`
};
}
function scoreLocation(candidateLocation, opportunityRegion) {
if (!opportunityRegion) return { score: 100, reason: "No region specified" };
const score =
candidateLocation?.toLowerCase() === opportunityRegion?.toLowerCase()
? 100
: 60;
return {
score,
reason: score === 100
? "Candidate is in the target region"
: "Candidate is outside the target region"
};
}
function scoreAvailability(candidateAvailability, startDate) {
if (!startDate) return { score: 100, reason: "No start date specified" };
// Simple version: if candidate is "Immediate", give full score
if (candidateAvailability?.toLowerCase() === "immediate") {
return { score: 100, reason: "Immediate availability" };
}
return {
score: 70,
reason: "Availability acceptable but not immediate"
};
}
.env.example
# ============================================================
# GLOBAL INFRASTRUCTURE ADVISORY – DOCUMENT MANAGER (v1)
# ============================================================
# ------------------------------------------------------------
# SERVER CONFIG
# ------------------------------------------------------------
PORT=5000
NODE_ENV=development
# ------------------------------------------------------------
# DATABASE (PostgreSQL)
# ------------------------------------------------------------
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=yourpassword
DB_NAME=gia
# ------------------------------------------------------------
# STORAGE PROVIDER
# Options: local | s3 | r2
# ------------------------------------------------------------
STORAGE_PROVIDER=local
# Local storage root (used when STORAGE_PROVIDER=local)
LOCAL_STORAGE_PATH=./docs
# ------------------------------------------------------------
# AWS S3 (optional)
# ------------------------------------------------------------
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_REGION=
AWS_S3_BUCKET=
# ------------------------------------------------------------
# CLOUDFLARE R2 (optional)
# ------------------------------------------------------------
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_BUCKET=
R2_ACCOUNT_ID=
R2_PUBLIC_URL=
# ------------------------------------------------------------
# SECURITY
# ------------------------------------------------------------
JWT_SECRET=changeme
TOKEN_EXPIRATION_HOURS=24
# ------------------------------------------------------------
# PDF GENERATION
# ------------------------------------------------------------
PDF_OUTPUT_PATH=./docs/generated
# ------------------------------------------------------------
# LOGGING
# ------------------------------------------------------------
LOG_LEVEL=info
backend/services/storageProvider.js
storageProvider.js
// backend/services/storageProvider.js
import fs from "fs";
import path from "path";
import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
const provider = process.env.STORAGE_PROVIDER || "local";
/* ---------------------------------------------------------
LOCAL STORAGE IMPLEMENTATION
--------------------------------------------------------- */
const LOCAL_ROOT = process.env.LOCAL_STORAGE_PATH || "./docs/uploads";
function ensureLocalDir() {
if (!fs.existsSync(LOCAL_ROOT)) {
fs.mkdirSync(LOCAL_ROOT, { recursive: true });
}
}
function saveLocal(file, filename) {
ensureLocalDir();
const dest = path.join(LOCAL_ROOT, filename);
fs.renameSync(file.path, dest);
return dest;
}
function getLocalPath(filename) {
return path.join(LOCAL_ROOT, filename);
}
/* ---------------------------------------------------------
AWS S3 / CLOUDFLARE R2 IMPLEMENTATION
--------------------------------------------------------- */
const s3 = new S3Client({
region: process.env.AWS_REGION || "auto",
endpoint: process.env.R2_PUBLIC_URL || undefined,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID || process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || process.env.R2_SECRET_ACCESS_KEY
}
});
async function saveS3(file, filename) {
const fileBuffer = fs.readFileSync(file.path);
await s3.send(
new PutObjectCommand({
Bucket: process.env.AWS_S3_BUCKET || process.env.R2_BUCKET,
Key: filename,
Body: fileBuffer,
ContentType: file.mimetype
})
);
fs.unlinkSync(file.path);
return filename;
}
function getS3Path(filename) {
const bucket = process.env.AWS_S3_BUCKET || process.env.R2_BUCKET;
const base = process.env.R2_PUBLIC_URL || `https://${bucket}.s3.amazonaws.com`;
return `${base}/${filename}`;
}
/* ---------------------------------------------------------
EXPORT UNIFIED INTERFACE
--------------------------------------------------------- */
export async function saveFile(file, filename) {
if (provider === "local") {
return saveLocal(file, filename);
}
return await saveS3(file, filename);
}
export function getFilePath(filename) {
if (provider === "local") {
return getLocalPath(filename);
}
return getS3Path(filename);
}
await saveFile(file, filename);
const path = getFilePath(filename);
.env
STORAGE_PROVIDER=s3
STORAGE_PROVIDER=r2
POST /v1/match
backend/routes/match.js
match.js
// backend/routes/match.js
import express from "express";
import { matchCandidateToOpportunity } from "../services/matchingEngine.js";
const router = express.Router();
/**
* POST /v1/match
* Body:
* {
* "candidate": { ... },
* "opportunity": { ... }
* }
*/
router.post("/v1/match", (req, res) => {
try {
const { candidate, opportunity } = req.body;
if (!candidate || !opportunity) {
return res.status(400).json({
error: "candidate and opportunity are required"
});
}
const result = matchCandidateToOpportunity(candidate, opportunity);
return res.json({
status: "success",
match: result
});
} catch (err) {
console.error("Matching error:", err);
return res.status(500).json({
error: "Internal server error"
});
}
});
export default router;
import matchRouter from "./routes/match.js";
app.use(matchRouter);
POST /v1/match
pages/matching-dashboard.html
matching-dashboard.html
Matching Dashboard – GIA
Matching Dashboard
Paste a candidate and opportunity JSON below to generate a match score.
Candidate JSON
Opportunity JSON
Run Match
backend/services/bulkMatcher.js
bulkMatcher.js
// backend/services/bulkMatcher.js
import { matchCandidateToOpportunity } from "./matchingEngine.js";
/**
* Bulk match MANY candidates to ONE opportunity
* ------------------------------------------------
* Input:
* candidates: [ { ... }, { ... }, ... ]
* opportunity: { ... }
*
* Output:
* [
* { candidate_id, score, breakdown },
* ...
* ] sorted by score DESC
*/
export function matchManyCandidatesToOpportunity(candidates, opportunity) {
const results = candidates.map(candidate => {
const match = matchCandidateToOpportunity(candidate, opportunity);
return {
candidate_id: candidate.id,
score: match.score,
breakdown: match.breakdown,
candidate
};
});
// Sort by score descending
return results.sort((a, b) => b.score - a.score);
}
/**
* Bulk match ONE candidate to MANY opportunities
* ------------------------------------------------
* Input:
* candidate: { ... }
* opportunities: [ { ... }, { ... }, ... ]
*
* Output:
* [
* { opportunity_id, score, breakdown },
* ...
* ] sorted by score DESC
*/
export function matchCandidateToManyOpportunities(candidate, opportunities) {
const results = opportunities.map(opportunity => {
const match = matchCandidateToOpportunity(candidate, opportunity);
return {
opportunity_id: opportunity.id,
score: match.score,
breakdown: match.breakdown,
opportunity
};
});
// Sort by score descending
return results.sort((a, b) => b.score - a.score);
}
/**
* Bulk match MANY candidates to MANY opportunities
* ------------------------------------------------
* This is the foundation for:
* - Marketplace auto‑matching
* - Workforce assignment engines
* - Donor reporting dashboards
*/
export function matchMatrix(candidates, opportunities) {
const matrix = [];
for (const candidate of candidates) {
for (const opportunity of opportunities) {
const match = matchCandidateToOpportunity(candidate, opportunity);
matrix.push({
candidate_id: candidate.id,
opportunity_id: opportunity.id,
score: match.score,
breakdown: match.breakdown
});
}
}
return matrix.sort((a, b) => b.score - a.score);
}
backend/routes/bulkMatch.js
bulkMatch.js
// backend/routes/bulkMatch.js
import express from "express";
import {
matchManyCandidatesToOpportunity,
matchCandidateToManyOpportunities,
matchMatrix
} from "../services/bulkMatcher.js";
const router = express.Router();
/**
* POST /v1/match/candidates-to-opportunity
* Body:
* {
* "candidates": [ ... ],
* "opportunity": { ... }
* }
*/
router.post("/v1/match/candidates-to-opportunity", (req, res) => {
try {
const { candidates, opportunity } = req.body;
if (!candidates || !opportunity) {
return res.status(400).json({
error: "candidates and opportunity are required"
});
}
const results = matchManyCandidatesToOpportunity(candidates, opportunity);
return res.json({
status: "success",
count: results.length,
matches: results
});
} catch (err) {
console.error("Bulk match error:", err);
return res.status(500).json({ error: "Internal server error" });
}
});
/**
* POST /v1/match/candidate-to-opportunities
* Body:
* {
* "candidate": { ... },
* "opportunities": [ ... ]
* }
*/
router.post("/v1/match/candidate-to-opportunities", (req, res) => {
try {
const { candidate, opportunities } = req.body;
if (!candidate || !opportunities) {
return res.status(400).json({
error: "candidate and opportunities are required"
});
}
const results = matchCandidateToManyOpportunities(candidate, opportunities);
return res.json({
status: "success",
count: results.length,
matches: results
});
} catch (err) {
console.error("Bulk match error:", err);
return res.status(500).json({ error: "Internal server error" });
}
});
/**
* POST /v1/match/matrix
* Body:
* {
* "candidates": [ ... ],
* "opportunities": [ ... ]
* }
*/
router.post("/v1/match/matrix", (req, res) => {
try {
const { candidates, opportunities } = req.body;
if (!candidates || !opportunities) {
return res.status(400).json({
error: "candidates and opportunities are required"
});
}
const results = matchMatrix(candidates, opportunities);
return res.json({
status: "success",
count: results.length,
matches: results
});
} catch (err) {
console.error("Matrix match error:", err);
return res.status(500).json({ error: "Internal server error" });
}
});
export default router;
server.js
import bulkMatchRouter from "./routes/bulkMatch.js";
app.use(bulkMatchRouter);
pages/bulk-matching-dashboard.html
bulk-matching-dashboard.html
Bulk Matching Dashboard – GIA
Bulk Matching Dashboard
Paste candidate and opportunity JSON below to generate ranked matches.
Candidates JSON (Array)
Opportunity JSON (Single Object)
Run Bulk Match
Bulk Match Results
Candidate ID
Score
Breakdown
backend/services/sectorAdjacency.js
sectorAdjacency.js
// backend/services/sectorAdjacency.js
/**
* Sector adjacency map
* -----------------------------------------
* Each sector lists other sectors that are
* considered "adjacent" or "transferable."
*
* This is donor‑aligned and workforce‑friendly.
*/
export const SECTOR_ADJACENCY = {
infrastructure: ["construction", "transportation", "energy", "water"],
construction: ["infrastructure", "energy"],
energy: ["infrastructure", "construction", "utilities"],
water: ["infrastructure", "utilities"],
broadband: ["telecom", "infrastructure"],
telecom: ["broadband", "infrastructure"],
utilities: ["energy", "water"],
agriculture: ["environment", "infrastructure"],
environment: ["agriculture", "water"],
transportation: ["infrastructure", "logistics"],
logistics: ["transportation"]
};
/**
* Score sector adjacency
* -----------------------------------------
* Returns:
* { score: Number, reason: String }
*
* Scoring:
* - Exact match: 100
* - Adjacent sector: 80
* - No relation: 40
*/
export function scoreSectorAdjacency(candidateSector, opportunitySector) {
if (!candidateSector || !opportunitySector) {
return { score: 100, reason: "No sector specified" };
}
const c = candidateSector.toLowerCase();
const o = opportunitySector.toLowerCase();
// Exact match
if (c === o) {
return { score: 100, reason: "Primary sector aligns" };
}
// Adjacent match
const adjacent = SECTOR_ADJACENCY[o] || [];
if (adjacent.includes(c)) {
return {
score: 80,
reason: `Sector is adjacent to ${opportunitySector}`
};
}
// No relation
return {
score: 40,
reason: `Sector not aligned or adjacent`
};
}
matchingEngine.js
import { scoreSectorAdjacency } from "./sectorAdjacency.js";
results.push(scoreSectorAdjacency(candidate.primary_sector, opportunity.sector));
backend/services/assignmentEngine.js
assignmentEngine.js
// backend/services/assignmentEngine.js
import { matchManyCandidatesToOpportunity } from "./bulkMatcher.js";
/**
* Workforce Assignment Engine (v1)
* ---------------------------------------------------------
* Assigns the top N candidates to an opportunity,
* based on a minimum score threshold.
*
* Input:
* candidates: [ ... ]
* opportunity: { ... }
* options:
* - limit: number of candidates to assign
* - minScore: minimum acceptable score
*
* Output:
* {
* assigned: [ ... ],
* rejected: [ ... ],
* summary: { assignedCount, rejectedCount }
* }
*/
export function assignCandidatesToOpportunity(
candidates,
opportunity,
{ limit = 5, minScore = 70 } = {}
) {
// Step 1: Score all candidates
const scored = matchManyCandidatesToOpportunity(candidates, opportunity);
// Step 2: Filter by threshold
const eligible = scored.filter(c => c.score >= minScore);
const rejected = scored.filter(c => c.score < minScore);
// Step 3: Take top N
const assigned = eligible.slice(0, limit);
return {
assigned,
rejected,
summary: {
assignedCount: assigned.length,
rejectedCount: rejected.length,
threshold: minScore,
limit
}
};
}
backend/routes/autoMatch.js
autoMatch.js
// backend/routes/autoMatch.js
import express from "express";
import { assignCandidatesToOpportunity } from "../services/assignmentEngine.js";
const router = express.Router();
/**
* POST /v1/auto-match
* Body:
* {
* "candidates": [ ... ],
* "opportunity": { ... },
* "limit": 5,
* "minScore": 70
* }
*/
router.post("/v1/auto-match", (req, res) => {
try {
const { candidates, opportunity, limit, minScore } = req.body;
if (!candidates || !opportunity) {
return res.status(400).json({
error: "candidates and opportunity are required"
});
}
const result = assignCandidatesToOpportunity(
candidates,
opportunity,
{ limit, minScore }
);
return res.json({
status: "success",
...result
});
} catch (err) {
console.error("Auto-match error:", err);
return res.status(500).json({
error: "Internal server error"
});
}
});
export default router;
server.js
import autoMatchRouter from "./routes/autoMatch.js";
app.use(autoMatchRouter);
POST /v1/auto-match
README.md
# Global Infrastructure Advisory
## Document Manager + Matching Engine (v1)
This system provides:
- Document generation (PDFs)
- Workforce & contractor matching
- Opportunity scoring
- Bulk matching
- Auto‑assignment
- Admin dashboards
- Modular storage (local, S3, R2)
Built for donor‑aligned, multilingual, global infrastructure programs.
---
# 📁 Folder Structure
```
project-root/
│
├── backend/
│ ├── routes/
│ │ ├── docs.js
│ │ ├── match.js
│ │ ├── bulkMatch.js
│ │ └── autoMatch.js
│ │
│ ├── services/
│ │ ├── pdfGenerator.js
│ │ ├── storageProvider.js
│ │ ├── matchingEngine.js
│ │ ├── bulkMatcher.js
│ │ ├── assignmentEngine.js
│ │ └── sectorAdjacency.js
│ │
│ ├── config/db.js
│ └── server.js
│
├── templates/
│ ├── capability-statement.html
│ ├── program-sheet.html
│ ├── contractor-profile.html
│ ├── opportunity-summary.html
│ └── workforce-profile.html
│
├── pages/
│ ├── document-manager.html
│ ├── matching-dashboard.html
│ └── bulk-matching-dashboard.html
│
├── docs/
│ ├── generated/
│ └── uploads/
│
├── database/schema-document-manager.sql
├── api-specs/document-manager-v1.yaml
├── .env.example
└── README.md
```
---
# ⚙️ Environment Variables
Copy `.env.example` → `.env` and fill in:
- Database credentials
- Storage provider (local, s3, r2)
- PDF output path
- Logging level
- Security keys
---
# 🧩 Templates
Located in `/templates`:
- Capability Statement
- Program Sheet
- Contractor Profile
- Opportunity Summary
- Workforce Profile
These are used by `pdfGenerator.js`.
---
# 🤖 Matching Engine
Core logic:
`backend/services/matchingEngine.js`
Features:
- Skills scoring
- Sector alignment
- Sector adjacency
- Experience scoring
- Location scoring
- Availability scoring
- Explainable breakdowns
---
# 📊 Bulk Matching
`bulkMatcher.js` provides:
- Many candidates → one opportunity
- One candidate → many opportunities
- Full matrix scoring
---
# 🧮 Assignment Engine
`assignmentEngine.js` provides:
- Top N assignment
- Threshold filtering
- Donor‑aligned scoring
- Explainable results
---
# 🌐 API Endpoints
### **Matching**
```
POST /v1/match
```
### **Bulk Matching**
```
POST /v1/match/candidates-to-opportunity
POST /v1/match/candidate-to-opportunities
POST /v1/match/matrix
```
### **Auto‑Assignment**
```
POST /v1/auto-match
```
### **Document Generation**
```
POST /v1/docs/generate
GET /v1/docs/:filename
```
---
# 🗄 Storage Providers
Configured via `.env`:
```
STORAGE_PROVIDER=local | s3 | r2
```
Adapters located in:
```
backend/services/storageProvider.js
```
---
# 🖥 Admin Dashboards
Located in `/pages`:
- Document Manager
- Matching Dashboard
- Bulk Matching Dashboard
These call your API routes directly.
---
# 🚀 Deployment Notes
Works on:
- Render
- Railway
- AWS
- Azure
- DigitalOcean
- Docker
Requires:
- Node.js 18+
- PostgreSQL 14+
---
# 🏁 Status
This is **v1** — fully functional, modular, and ready for expansion.
pages/program-matching-dashboard.html
program-matching-dashboard.html
Program‑Wide Matching Dashboard – GIA
Program‑Wide Matching Dashboard
Paste candidate and opportunity JSON arrays below to generate a full matrix match.
Candidates JSON (Array)
Opportunities JSON (Array)
Run Matrix Match
Matrix Match Results
Candidate ID
Opportunity ID
Score
Breakdown
pdfGenerator.js
templates/assignment-confirmation.html
assignment-confirmation.html
Assignment Confirmation – {{candidate_name}}
Assignment Confirmation
Date: {{date}}
Candidate Information
Name: {{candidate_name}}
Email: {{candidate_email}}
Phone: {{candidate_phone}}
Primary Sector: {{candidate_sector}}
Years of Experience: {{candidate_experience}}
Opportunity Details
Opportunity Title: {{opportunity_title}}
Organization: {{opportunity_org}}
Location / Region: {{opportunity_region}}
Start Date: {{opportunity_start}}
Sector: {{opportunity_sector}}
Match Summary
Match Score: {{match_score}} / 100
Reasoning:
{{match_reasoning}}
Assignment Status
{{assignment_status}}
backend/workflows/autoAssignmentWorkflow.js
// backend/workflows/autoAssignmentWorkflow.js
import { assignCandidatesToOpportunity } from "../services/assignmentEngine.js";
import { generatePDF } from "../services/pdfGenerator.js";
import fs from "fs";
/**
* Auto‑Assignment Workflow (v1)
* ---------------------------------------------------------
* This workflow:
* 1. Loads candidates + opportunities (placeholder)
* 2. Runs assignment engine
* 3. Generates assignment confirmation PDFs
* 4. Logs results
*
* In production, replace the placeholder loaders with:
* - Database queries
* - API calls
* - Event triggers
*/
export async function runAutoAssignmentWorkflow({
candidates,
opportunity,
limit = 5,
minScore = 70
}) {
console.log("Running auto‑assignment workflow...");
// Step 1: Run assignment engine
const result = assignCandidatesToOpportunity(
candidates,
opportunity,
{ limit, minScore }
);
// Step 2: Generate PDFs for assigned candidates
for (const assigned of result.assigned) {
const pdfData = {
candidate_name: assigned.candidate.name,
candidate_email: assigned.candidate.email,
candidate_phone: assigned.candidate.phone,
candidate_sector: assigned.candidate.primary_sector,
candidate_experience: assigned.candidate.years_experience,
opportunity_title: opportunity.title,
opportunity_org: opportunity.organization,
opportunity_region: opportunity.region,
opportunity_start: opportunity.start_date,
opportunity_sector: opportunity.sector,
match_score: assigned.score,
match_reasoning: assigned.breakdown.map(b => b.reason).join("; "),
assignment_status: "Assigned",
notes: "Automatically assigned via GIA Auto‑Assignment Engine",
date: new Date().toLocaleDateString()
};
await generatePDF("assignment-confirmation", pdfData);
}
// Step 3: Log results
const logEntry = {
timestamp: new Date().toISOString(),
opportunity_id: opportunity.id,
assigned: result.assigned.map(a => a.candidate_id),
rejected: result.rejected.map(r => r.candidate_id),
summary: result.summary
};
fs.appendFileSync("./logs/auto-assignment.log", JSON.stringify(logEntry) + "\n");
console.log("Auto‑assignment workflow complete.");
return result;
}
i18n/en.json
{
"app": {
"name": "Global Infrastructure Advisory",
"tagline": "Document Manager & Matching Engine"
},
"nav": {
"document_manager": "Document Manager",
"matching_dashboard": "Matching Dashboard",
"bulk_matching_dashboard": "Bulk Matching",
"program_matching_dashboard": "Program Matching",
"reports": "Reports",
"settings": "Settings"
},
"matching": {
"title_single": "Matching Dashboard",
"title_bulk": "Bulk Matching Dashboard",
"title_program": "Program-wide Matching Dashboard",
"candidate_json_label": "Candidate JSON",
"candidates_json_label": "Candidates JSON (Array)",
"opportunity_json_label": "Opportunity JSON",
"opportunities_json_label": "Opportunities JSON (Array)",
"run_match": "Run Match",
"run_bulk_match": "Run Bulk Match",
"run_matrix_match": "Run Matrix Match",
"results_title": "Match Results",
"matrix_results_title": "Matrix Match Results",
"candidate_id": "Candidate ID",
"opportunity_id": "Opportunity ID",
"score": "Score",
"breakdown": "Breakdown"
},
"assignment": {
"auto_match_title": "Auto-Assignment",
"limit_label": "Maximum Assignments",
"min_score_label": "Minimum Score",
"run_auto_match": "Run Auto-Assignment",
"assigned": "Assigned",
"rejected": "Rejected",
"summary": "Summary",
"assigned_count": "Assigned Count",
"rejected_count": "Rejected Count",
"threshold": "Threshold",
"notes_default": "Automatically assigned via GIA Auto-Assignment Engine"
},
"pdf": {
"assignment_confirmation_title": "Assignment Confirmation",
"candidate_information": "Candidate Information",
"opportunity_details": "Opportunity Details",
"match_summary": "Match Summary",
"assignment_status": "Assignment Status",
"notes": "Notes",
"date": "Date"
}
}
i18n/es.json
i18n/fr.json
i18n/ar.json
{
"app": {
"name": "Global Infrastructure Advisory",
"tagline": "Gestor de Documentos y Motor de Emparejamiento"
}
}
Generate es.json, fr.json, and ar.json with full translations
pages/donor-reporting-dashboard.html
Donor Reporting Dashboard – GIA
Donor Reporting Dashboard
Portfolio‑level view of assignments, scores, and program performance.
Filters
Assignment Summary
No data loaded yet.
Assignments
Opportunity ID
Candidate ID
Region
Sector
Score
Status
backend/services/analyticsEngine.js
// backend/services/analyticsEngine.js
/**
* Program‑Wide Analytics Engine (v1)
*
* This module takes:
* - Matrix match results (candidate ↔ opportunity scores)
* - Assignment records
*
* And returns:
* - Global stats
* - By‑sector stats
* - By‑region stats
* - Opportunity‑level stats
*/
/**
* Compute basic stats from an array of numeric scores.
*/
function computeScoreStats(scores) {
if (!scores.length) {
return { count: 0, avg: 0, min: 0, max: 0 };
}
const count = scores.length;
const sum = scores.reduce((s, v) => s + v, 0);
const avg = sum / count;
const min = Math.min(...scores);
const max = Math.max(...scores);
return { count, avg, min, max };
}
/**
* Aggregate by a key (e.g., sector, region, opportunity_id).
*/
function aggregateByKey(rows, key) {
const buckets = {};
for (const row of rows) {
const k = row[key] || "unknown";
if (!buckets[k]) buckets[k] = [];
buckets[k].push(row.score);
}
const result = {};
for (const [k, scores] of Object.entries(buckets)) {
result[k] = computeScoreStats(scores);
}
return result;
}
/**
* Build analytics from a matrix of matches.
*
* Expected shape:
* [
* {
* candidate_id,
* opportunity_id,
* score,
* breakdown,
* sector, // optional
* region // optional
* },
* ...
* ]
*/
export function buildMatrixAnalytics(matrixRows) {
const scores = matrixRows.map(r => r.score);
const global = computeScoreStats(scores);
const bySector = aggregateByKey(matrixRows, "sector");
const byRegion = aggregateByKey(matrixRows, "region");
const byOpportunity = aggregateByKey(matrixRows, "opportunity_id");
return {
global,
bySector,
byRegion,
byOpportunity
};
}
/**
* Build analytics from assignment records.
*
* Expected shape:
* [
* {
* opportunity_id,
* candidate_id,
* score,
* status, // "Assigned" | "Rejected" | ...
* sector, // optional
* region // optional
* },
* ...
* ]
*/
export function buildAssignmentAnalytics(assignments) {
const scores = assignments.map(a => a.score);
const global = computeScoreStats(scores);
const assigned = assignments.filter(a => a.status === "Assigned");
const rejected = assignments.filter(a => a.status !== "Assigned");
const assignedScores = assigned.map(a => a.score);
const rejectedScores = rejected.map(a => a.score);
const bySector = aggregateByKey(assignments, "sector");
const byRegion = aggregateByKey(assignments, "region");
const byOpportunity = aggregateByKey(assignments, "opportunity_id");
return {
global,
assigned: {
count: assigned.length,
stats: computeScoreStats(assignedScores)
},
rejected: {
count: rejected.length,
stats: computeScoreStats(rejectedScores)
},
bySector,
byRegion,
byOpportunity
};
}
/**
* High‑level program analytics wrapper.
*
* You can feed:
* - matrixRows from matchMatrix(...)
* - assignmentRows from your auto‑assignment workflow / DB
*/
export function buildProgramAnalytics({ matrixRows = [], assignmentRows = [] }) {
return {
matrixAnalytics: buildMatrixAnalytics(matrixRows),
assignmentAnalytics: buildAssignmentAnalytics(assignmentRows)
};
}
/pages
pages/onboarding.html
Workforce & Contractor Onboarding – GIA
Workforce & Contractor Onboarding
Join the GIA workforce and be matched to global infrastructure opportunities.
Your profile has been submitted successfully. Thank you for joining the GIA workforce.
POST /v1/onboarding
backend/routes/onboarding.js
onboarding.js
// backend/routes/onboarding.js
import express from "express";
import fs from "fs";
import path from "path";
const router = express.Router();
// Local storage placeholder (replace with DB later)
const DATA_FILE = path.join(process.cwd(), "database", "onboarding.json");
// Ensure file exists
if (!fs.existsSync(DATA_FILE)) {
fs.writeFileSync(DATA_FILE, JSON.stringify([]));
}
router.post("/v1/onboarding", (req, res) => {
try {
const {
name,
email,
phone,
profile_type,
primary_sector,
skills = [],
years_experience = 0,
region,
availability,
summary
} = req.body;
// Basic validation
if (!name || !email || !profile_type || !primary_sector) {
return res.status(400).json({
error: "name, email, profile_type, and primary_sector are required"
});
}
// Normalize skills
const normalizedSkills = Array.isArray(skills)
? skills.map(s => s.trim()).filter(Boolean)
: [];
// Build profile object
const profile = {
id: "prof-" + Date.now(),
name,
email,
phone,
profile_type,
primary_sector,
skills: normalizedSkills,
years_experience: Number(years_experience),
region,
availability,
summary,
created_at: new Date().toISOString()
};
// Load existing
const existing = JSON.parse(fs.readFileSync(DATA_FILE, "utf8"));
// Save
existing.push(profile);
fs.writeFileSync(DATA_FILE, JSON.stringify(existing, null, 2));
return res.json({
status: "success",
profile
});
} catch (err) {
console.error("Onboarding error:", err);
return res.status(500).json({ error: "Internal server error" });
}
});
export default router;
server.js
import onboardingRouter from "./routes/onboarding.js";
app.use(onboardingRouter);
database/schema-onboarding.sql
schema-onboarding.sql
-- ============================================================
-- WORKER / CONTRACTOR ONBOARDING SCHEMA (v1)
-- Global Infrastructure Advisory (GIA)
-- ============================================================
CREATE TABLE onboarding_profiles (
id VARCHAR(64) PRIMARY KEY,
profile_type VARCHAR(32) NOT NULL, -- worker | contractor
name TEXT NOT NULL,
email TEXT NOT NULL,
phone TEXT,
primary_sector TEXT NOT NULL,
skills TEXT[], -- array of skills
years_experience INTEGER DEFAULT 0,
region TEXT,
availability TEXT,
summary TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
-- Indexes for matching engine performance
CREATE INDEX idx_onboarding_sector ON onboarding_profiles (primary_sector);
CREATE INDEX idx_onboarding_region ON onboarding_profiles (region);
CREATE INDEX idx_onboarding_profile_type ON onboarding_profiles (profile_type);
-- ============================================================
-- OPTIONAL: Certifications / Licenses (future expansion)
-- ============================================================
CREATE TABLE onboarding_certifications (
id SERIAL PRIMARY KEY,
profile_id VARCHAR(64) REFERENCES onboarding_profiles(id) ON DELETE CASCADE,
cert_name TEXT NOT NULL,
cert_issuer TEXT,
issued_date DATE,
expiry_date DATE
);
CREATE INDEX idx_cert_profile ON onboarding_certifications (profile_id);
-- ============================================================
-- OPTIONAL: Uploaded Documents (resume, capability statement)
-- ============================================================
CREATE TABLE onboarding_documents (
id SERIAL PRIMARY KEY,
profile_id VARCHAR(64) REFERENCES onboarding_profiles(id) ON DELETE CASCADE,
doc_type TEXT NOT NULL, -- resume | capability_statement | license | etc.
file_path TEXT NOT NULL,
uploaded_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_docs_profile ON onboarding_documents (profile_id);
-- ============================================================
-- WORKER / CONTRACTOR ONBOARDING SCHEMA (v1)
-- Global Infrastructure Advisory (GIA)
-- ============================================================
CREATE TABLE onboarding_profiles (
id VARCHAR(64) PRIMARY KEY,
profile_type VARCHAR(32) NOT NULL, -- worker | contractor
name TEXT NOT NULL,
email TEXT NOT NULL,
phone TEXT,
primary_sector TEXT NOT NULL,
skills TEXT[], -- array of skills
years_experience INTEGER DEFAULT 0,
region TEXT,
availability TEXT,
summary TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
-- Indexes for matching engine performance
CREATE INDEX idx_onboarding_sector ON onboarding_profiles (primary_sector);
CREATE INDEX idx_onboarding_region ON onboarding_profiles (region);
CREATE INDEX idx_onboarding_profile_type ON onboarding_profiles (profile_type);
-- ============================================================
-- OPTIONAL: Certifications / Licenses (future expansion)
-- ============================================================
CREATE TABLE onboarding_certifications (
id SERIAL PRIMARY KEY,
profile_id VARCHAR(64) REFERENCES onboarding_profiles(id) ON DELETE CASCADE,
cert_name TEXT NOT NULL,
cert_issuer TEXT,
issued_date DATE,
expiry_date DATE
);
CREATE INDEX idx_cert_profile ON onboarding_certifications (profile_id);
-- ============================================================
-- OPTIONAL: Uploaded Documents (resume, capability statement)
-- ============================================================
CREATE TABLE onboarding_documents (
id SERIAL PRIMARY KEY,
profile_id VARCHAR(64) REFERENCES onboarding_profiles(id) ON DELETE CASCADE,
doc_type TEXT NOT NULL, -- resume | capability_statement | license | etc.
file_path TEXT NOT NULL,
uploaded_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_docs_profile ON onboarding_documents (profile_id);
pages/onboarding-review-dashboard.html
onboarding-review-dashboard.html
Onboarding Review Dashboard – GIA
Onboarding Review Dashboard
Review, approve, and activate workforce and contractor profiles.
Filters
Profiles
Name
Type
Sector
Region
Experience
Status
Actions
backend/services/onboardingToCandidate.js
// backend/services/onboardingToCandidate.js
import fs from "fs";
import path from "path";
/**
* Loads onboarding profiles from local JSON.
* Replace with DB queries in production.
*/
const DATA_FILE = path.join(process.cwd(), "database", "onboarding.json");
export function loadOnboardingProfiles() {
if (!fs.existsSync(DATA_FILE)) return [];
return JSON.parse(fs.readFileSync(DATA_FILE, "utf8"));
}
/**
* Normalize a single onboarding profile into a matching-ready candidate object.
*
* Matching Engine expects:
* {
* id,
* name,
* primary_sector,
* skills: [],
* years_experience,
* region,
* availability,
* metadata: { ... }
* }
*/
export function normalizeProfileToCandidate(profile) {
return {
id: profile.id,
name: profile.name,
primary_sector: profile.primary_sector,
skills: Array.isArray(profile.skills) ? profile.skills : [],
years_experience: Number(profile.years_experience || 0),
region: profile.region || null,
availability: profile.availability || null,
metadata: {
profile_type: profile.profile_type,
email: profile.email,
phone: profile.phone,
summary: profile.summary,
created_at: profile.created_at
}
};
}
/**
* Load all onboarding profiles and convert them to matching candidates.
*/
export function loadCandidatesForMatching() {
const profiles = loadOnboardingProfiles();
return profiles
.filter(p => p.status !== "rejected") // only approved/pending
.map(normalizeProfileToCandidate);
}
/**
* Load only approved candidates.
*/
export function loadApprovedCandidates() {
const profiles = loadOnboardingProfiles();
return profiles
.filter(p => p.status === "approved")
.map(normalizeProfileToCandidate);
}
pages/profile-detail.html
Profile Detail – GIA
Profile Detail
Loading profile…
Basic Information
Name:
Email:
Phone:
Type:
|
Status:
Sector & Experience
Primary Sector:
Region:
Years of Experience:
Availability:
Skills
Summary / Capabilities
Approve
Reject
backend/services/resumeGenerator.js
// backend/services/resumeGenerator.js
import { generatePDF } from "./pdfGenerator.js";
/**
* Resume / Capability Statement Auto‑Generator (v1)
* ---------------------------------------------------------
* Input: normalized candidate profile from onboardingToCandidate.js
* Output: PDF file generated using templates/resume.html or templates/capability-statement.html
*
* Supports:
* - Workers (resume)
* - Contractors (capability statement)
*/
export async function generateResumeOrCapability(profile) {
const isContractor = profile.metadata.profile_type === "contractor";
const templateName = isContractor
? "capability-statement"
: "resume";
const data = {
name: profile.name,
email: profile.metadata.email,
phone: profile.metadata.phone,
primary_sector: profile.primary_sector,
skills: profile.skills,
years_experience: profile.years_experience,
region: profile.region,
availability: profile.availability,
summary: profile.metadata.summary,
profile_type: profile.metadata.profile_type,
created_at: profile.metadata.created_at,
// Contractor‑specific fields (future expansion)
company_name: isContractor ? profile.name : null,
capabilities: isContractor ? profile.skills : null
};
const filename = `${profile.id}-${templateName}.pdf`;
await generatePDF(templateName, data, filename);
return {
status: "success",
file: filename,
type: templateName
};
}
templates/resume.html
{{name}} – Resume
{{name}}
{{email}} • {{phone}}
Professional Summary
{{summary}}
Experience
Years of Experience: {{years_experience}}
Primary Sector: {{primary_sector}}
Region: {{region}}
Availability: {{availability}}
templates/capability-statement.html
{{company_name}} – Capability Statement
{{company_name}}
{{email}} • {{phone}}
Capabilities
{{capabilities}}
Core Sector
{{primary_sector}}
Experience
Years of Experience: {{years_experience}}
Region: {{region}}
Company Summary
{{summary}}
backend/routes/resumeDownload.js
resumeDownload.js
// backend/routes/resumeDownload.js
import express from "express";
import fs from "fs";
import path from "path";
import { loadOnboardingProfiles } from "../services/onboardingToCandidate.js";
import { normalizeProfileToCandidate } from "../services/onboardingToCandidate.js";
import { generateResumeOrCapability } from "../services/resumeGenerator.js";
const router = express.Router();
router.get("/v1/profile/:id/resume", async (req, res) => {
try {
const { id } = req.params;
// Load onboarding profiles
const profiles = loadOnboardingProfiles();
const profile = profiles.find(p => p.id === id);
if (!profile) {
return res.status(404).json({ error: "Profile not found" });
}
// Normalize to matching-ready candidate
const candidate = normalizeProfileToCandidate(profile);
// Generate resume or capability statement
const result = await generateResumeOrCapability(candidate);
const filePath = path.join(process.cwd(), "docs", "generated", result.file);
if (!fs.existsSync(filePath)) {
return res.status(500).json({ error: "PDF generation failed" });
}
// Send file
res.download(filePath, result.file);
} catch (err) {
console.error("Resume download error:", err);
return res.status(500).json({ error: "Internal server error" });
}
});
export default router;
server.js
import resumeDownloadRouter from "./routes/resumeDownload.js";
app.use(resumeDownloadRouter);
backend/routes/profile.js
profile.js
// backend/routes/profile.js
import express from "express";
import { loadOnboardingProfiles } from "../services/onboardingToCandidate.js";
import { normalizeProfileToCandidate } from "../services/onboardingToCandidate.js";
const router = express.Router();
/**
* GET /v1/profile/:id
* Returns:
* - Raw onboarding profile
* - Normalized candidate object (matching-ready)
* - Metadata for admin dashboards
*/
router.get("/v1/profile/:id", (req, res) => {
try {
const { id } = req.params;
const profiles = loadOnboardingProfiles();
const profile = profiles.find(p => p.id === id);
if (!profile) {
return res.status(404).json({ error: "Profile not found" });
}
const normalized = normalizeProfileToCandidate(profile);
return res.json({
status: "success",
profile,
candidate: normalized
});
} catch (err) {
console.error("Profile API error:", err);
return res.status(500).json({ error: "Internal server error" });
}
});
export default router;
server.js
import profileRouter from "./routes/profile.js";
app.use(profileRouter);
backend/services/candidateCache.js
candidateCache.js
// backend/services/candidateCache.js
import { createClient } from "redis";
import {
loadOnboardingProfiles,
normalizeProfileToCandidate
} from "./onboardingToCandidate.js";
const redis = createClient();
redis.on("error", err => console.error("Redis error:", err));
await redis.connect();
/**
* Cache key helpers
*/
const KEY_ALL = "candidates:all";
const KEY_ONE = id => `candidate:${id}`;
/**
* Load all onboarding profiles, normalize them,
* and store them in Redis for fast matching.
*/
export async function warmCandidateCache() {
const profiles = loadOnboardingProfiles();
const normalized = profiles.map(normalizeProfileToCandidate);
// Store full list
await redis.set(KEY_ALL, JSON.stringify(normalized));
// Store each individually
for (const c of normalized) {
await redis.set(KEY_ONE(c.id), JSON.stringify(c));
}
return {
status: "success",
count: normalized.length
};
}
/**
* Get all candidates from cache.
* Falls back to onboarding loader if cache is empty.
*/
export async function getAllCandidates() {
const cached = await redis.get(KEY_ALL);
if (cached) {
return JSON.parse(cached);
}
// Fallback: warm cache
const profiles = loadOnboardingProfiles();
const normalized = profiles.map(normalizeProfileToCandidate);
await redis.set(KEY_ALL, JSON.stringify(normalized));
return normalized;
}
/**
* Get a single candidate from cache.
*/
export async function getCandidate(id) {
const cached = await redis.get(KEY_ONE(id));
if (cached) {
return JSON.parse(cached);
}
// Fallback: load from onboarding
const profiles = loadOnboardingProfiles();
const profile = profiles.find(p => p.id === id);
if (!profile) return null;
const normalized = normalizeProfileToCandidate(profile);
await redis.set(KEY_ONE(id), JSON.stringify(normalized));
return normalized;
}
/**
* Invalidate cache when onboarding changes.
*/
export async function invalidateCandidateCache(id = null) {
if (id) {
await redis.del(KEY_ONE(id));
} else {
await redis.del(KEY_ALL);
}
}
server.js
import { warmCandidateCache } from "./services/candidateCache.js";
await warmCandidateCache();
backend/routes/profileUpdate.js
profileUpdate.js
// backend/routes/profileUpdate.js
import express from "express";
import fs from "fs";
import path from "path";
import {
loadOnboardingProfiles,
normalizeProfileToCandidate
} from "../services/onboardingToCandidate.js";
import {
invalidateCandidateCache
} from "../services/candidateCache.js";
const router = express.Router();
const DATA_FILE = path.join(process.cwd(), "database", "onboarding.json");
/**
* PATCH /v1/profile/:id
* Allows updating:
* - name
* - email
* - phone
* - primary_sector
* - skills
* - years_experience
* - region
* - availability
* - summary
* - status (approved | rejected | pending)
*/
router.patch("/v1/profile/:id", (req, res) => {
try {
const { id } = req.params;
const updates = req.body;
const profiles = loadOnboardingProfiles();
const index = profiles.findIndex(p => p.id === id);
if (index === -1) {
return res.status(404).json({ error: "Profile not found" });
}
const profile = profiles[index];
// Apply updates
const allowedFields = [
"name",
"email",
"phone",
"primary_sector",
"skills",
"years_experience",
"region",
"availability",
"summary",
"status"
];
for (const key of Object.keys(updates)) {
if (allowedFields.includes(key)) {
profile[key] = updates[key];
}
}
// Save back to file
profiles[index] = profile;
fs.writeFileSync(DATA_FILE, JSON.stringify(profiles, null, 2));
// Invalidate Redis cache for this profile
invalidateCandidateCache(id);
return res.json({
status: "success",
profile,
candidate: normalizeProfileToCandidate(profile)
});
} catch (err) {
console.error("Profile update error:", err);
return res.status(500).json({ error: "Internal server error" });
}
});
export default router;
add to server.js
import profileUpdateRouter from "./routes/profileUpdate.js";
app.use(profileUpdateRouter);
backend/routes/assignmentHistory.js
// backend/routes/assignmentHistory.js
import express from "express";
import fs from "fs";
import path from "path";
const router = express.Router();
const DATA_FILE = path.join(process.cwd(), "database", "assignment-history.json");
// Ensure file exists
if (!fs.existsSync(DATA_FILE)) {
fs.writeFileSync(DATA_FILE, JSON.stringify([]));
}
/**
* Utility: load/save
*/
function loadHistory() {
return JSON.parse(fs.readFileSync(DATA_FILE, "utf8"));
}
function saveHistory(history) {
fs.writeFileSync(DATA_FILE, JSON.stringify(history, null, 2));
}
/**
* POST /v1/assignments
* Records an assignment event.
*
* Expected body:
* {
* candidate_id,
* opportunity_id,
* score,
* status, // assigned | rejected | waitlist | etc.
* reasoning,
* timestamp
* }
*/
router.post("/v1/assignments", (req, res) => {
try {
const event = {
...req.body,
id: "assign-" + Date.now(),
timestamp: req.body.timestamp || new Date().toISOString()
};
const history = loadHistory();
history.push(event);
saveHistory(history);
return res.json({ status: "success", event });
} catch (err) {
console.error("Assignment history error:", err);
return res.status(500).json({ error: "Internal server error" });
}
});
/**
* GET /v1/assignments/candidate/:id
* Returns all assignment events for a candidate.
*/
router.get("/v1/assignments/candidate/:id", (req, res) => {
try {
const { id } = req.params;
const history = loadHistory();
const events = history.filter(h => h.candidate_id === id);
return res.json({ status: "success", events });
} catch (err) {
console.error("Assignment history error:", err);
return res.status(500).json({ error: "Internal server error" });
}
});
/**
* GET /v1/assignments/opportunity/:id
* Returns all assignment events for an opportunity.
*/
router.get("/v1/assignments/opportunity/:id", (req, res) => {
try {
const { id } = req.params;
const history = loadHistory();
const events = history.filter(h => h.opportunity_id === id);
return res.json({ status: "success", events });
} catch (err) {
console.error("Assignment history error:", err);
return res.status(500).json({ error: "Internal server error" });
}
});
/**
* GET /v1/assignments
* Admin: returns all assignment events.
*/
router.get("/v1/assignments", (req, res) => {
try {
const history = loadHistory();
return res.json({ status: "success", events: history });
} catch (err) {
console.error("Assignment history error:", err);
return res.status(500).json({ error: "Internal server error" });
}
});
export default router;
add to server.js
import assignmentHistoryRouter from "./routes/assignmentHistory.js";
app.use(assignmentHistoryRouter);
integrate to auto-assignment workflow
await fetch("/v1/assignments", { method: "POST", body: JSON.stringify(event) });
pages/assignment-history-dashboard.html
Assignment History Dashboard – GIA
Assignment History Dashboard
Portfolio‑level view of assignment events across candidates and opportunities.
Filters
Summary
No data loaded yet.
Assignment Events
Timestamp
Candidate ID
Opportunity ID
Score
Status
Reasoning
pages/assignment-history-dashboard.html
Assignment History Dashboard – GIA
Assignment History Dashboard
Portfolio‑level view of assignment events across candidates and opportunities.
Filters
Summary
No data loaded yet.
Assignment Events
Timestamp
Candidate ID
Opportunity ID
Score
Status
Reasoning
/pages/assignment-history-dashboard.html
GET /v1/assignments
server.js
app.use(express.static("pages"));
Ctrl + S (Windows)
git add .
git commit -m "Add Assignment History Dashboard"
git push
pages/assignment-history-dashboard.html
Assignment History Dashboard – GIA
Assignment History Dashboard
Portfolio‑level view of assignment events across candidates and opportunities.
Filters
Summary
No data loaded yet.
Assignment Events
Timestamp
Candidate ID
Opportunity ID
Score
Status
Reasoning
/pages/assignment-history-dashboard.html
GET /v1/assignments
GlobalInfrastructureAdvisory/
server.js
app.use(express.static("pages"));
Ctrl + S (Windows)
git add .
git commit -m "Add Assignment History Dashboard"
git push