← back to drew1618t__StockDashboard

Function bodies 126 total

All specs Real LLM only Function bodies
handleAdd function · javascript · L66-L97 (32 LOC)
public/js/components/companySelector.js
    function handleAdd() {
      const ticker = input.value.trim().toUpperCase();
      if (!ticker) return;

      errorMsg.textContent = '';

      // Already in portfolio
      if (opts.portfolioTickers.includes(ticker)) {
        errorMsg.textContent = 'Already in portfolio';
        return;
      }

      // Already added as comparison
      if (opts.comparisonCompanies.some(c => c.ticker === ticker)) {
        errorMsg.textContent = 'Already added';
        return;
      }

      // Check availability — if missing, log a request
      if (!opts.availableTickers.includes(ticker)) {
        errorMsg.textContent = `No data for ${ticker}`;
        API.requestComparison(ticker).then(result => {
          errorMsg.textContent = result.alreadyRequested
            ? `No data for ${ticker} \u2014 already requested`
            : `No data for ${ticker} \u2014 requested`;
        }).catch(() => {});
        return;
      }

      input.value = '';
      opts.onAddComparison(ticker);
    }
heatmapQuarterLabel function · javascript · L76-L79 (4 LOC)
public/js/components/heatmap.js
function heatmapQuarterLabel(q, useCalendar) {
  if (useCalendar) return (q.calendarQuarter || q.quarter).replace(' FY', ' ');
  return q.quarter.replace(' FY', ' ');
}
quarterSort function · javascript · L81-L86 (6 LOC)
public/js/components/heatmap.js
function quarterSort(a, b) {
  const pa = parseQuarterLabel(a);
  const pb = parseQuarterLabel(b);
  if (pa.year !== pb.year) return pa.year - pb.year;
  return pa.q - pb.q;
}
parseQuarterLabel function · javascript · L88-L92 (5 LOC)
public/js/components/heatmap.js
function parseQuarterLabel(label) {
  const m = label.match(/Q(\d)\s+(\d{4})/);
  if (m) return { q: parseInt(m[1]), year: parseInt(m[2]) };
  return { q: 0, year: 0 };
}
doSort function · javascript · L23-L39 (17 LOC)
public/js/components/sortableTable.js
    function doSort() {
      sortedData = [...data].sort((a, b) => {
        let av = a[sortKey], bv = b[sortKey];
        // Handle nulls
        if (av === null || av === undefined || av === 'N/A') av = sortDir === 'asc' ? Infinity : -Infinity;
        if (bv === null || bv === undefined || bv === 'N/A') bv = sortDir === 'asc' ? Infinity : -Infinity;
        // Numeric comparison
        if (typeof av === 'number' && typeof bv === 'number') {
          return sortDir === 'asc' ? av - bv : bv - av;
        }
        // String comparison
        return sortDir === 'asc'
          ? String(av).localeCompare(String(bv))
          : String(bv).localeCompare(String(av));
      });
      renderTable();
    }
renderTable function · javascript · L41-L98 (58 LOC)
public/js/components/sortableTable.js
    function renderTable() {
      const headerHtml = columns.map(col => {
        const sortable = col.sortable !== false;
        const arrow = sortKey === col.key ? (sortDir === 'asc' ? ' \u25B2' : ' \u25BC') : '';
        const cls = [
          'th',
          col.align === 'right' ? 'text-right' : '',
          sortable ? 'sortable' : '',
        ].filter(Boolean).join(' ');
        return `<div class="${cls}" data-key="${col.key}">${col.label}${arrow}</div>`;
      }).join('');

      const rowsHtml = sortedData.map((row, i) => {
        const isComp = row._isComparison;
        const cells = columns.map(col => {
          let val = row[col.key];
          if (col.format) val = col.format(val, row);
          else if (val === null || val === undefined) val = 'N/A';
          const clsList = [col.align === 'right' ? 'text-right' : ''];
          if (isComp && col.key === 'ticker') clsList.push('comparison-ticker');
          const cls = clsList.filter(Boolean).join(' ');
        
applyFilters function · javascript · L40-L52 (13 LOC)
public/js/dashboards/growth.js
    function applyFilters() {
      const accelOnly = document.getElementById('growth-filter-accel')?.checked || false;
      const profitOnly = document.getElementById('growth-filter-profit')?.checked || false;
      const useCalendarQuarters = document.getElementById('growth-filter-calendar')?.checked || false;

      let filtered = companies.filter(c => {
        if (accelOnly && c.calculated?.momentum?.trend !== 'accelerating') return false;
        if (profitOnly && c.currentlyProfitable !== true) return false;
        return true;
      });

      self._renderData(dataContainer, filtered, companies, useCalendarQuarters);
    }
Want this analysis on your repo? https://repobility.com/scan/
quarterLabel function · javascript · L183-L186 (4 LOC)
public/js/dashboards/growth.js
function quarterLabel(q, useCalendar) {
  if (useCalendar) return (q.calendarQuarter || q.quarter).replace(' FY', ' ');
  return q.quarter.replace(' FY', ' ');
}
quarterSort function · javascript · L188-L193 (6 LOC)
public/js/dashboards/growth.js
function quarterSort(a, b) {
  const pa = parseQuarterLabel(a);
  const pb = parseQuarterLabel(b);
  if (pa.year !== pb.year) return pa.year - pb.year;
  return pa.q - pb.q;
}
parseQuarterLabel function · javascript · L195-L199 (5 LOC)
public/js/dashboards/growth.js
function parseQuarterLabel(label) {
  const m = label.match(/Q(\d)\s+(\d{4})/);
  if (m) return { q: parseInt(m[1]), year: parseInt(m[2]) };
  return { q: 0, year: 0 };
}
decodeBase64Url function · javascript · L9-L14 (6 LOC)
server/auth/accessAuth.js
function decodeBase64Url(input) {
  const normalized = input.replace(/-/g, '+').replace(/_/g, '/');
  const padding = normalized.length % 4;
  const padded = padding ? normalized + '='.repeat(4 - padding) : normalized;
  return Buffer.from(padded, 'base64');
}
parseJwt function · javascript · L16-L37 (22 LOC)
server/auth/accessAuth.js
function parseJwt(token) {
  const parts = String(token || '').split('.');
  if (parts.length !== 3) {
    throw new Error('Malformed JWT');
  }

  let header;
  let payload;
  try {
    header = JSON.parse(decodeBase64Url(parts[0]).toString('utf8'));
    payload = JSON.parse(decodeBase64Url(parts[1]).toString('utf8'));
  } catch (err) {
    throw new Error('Invalid JWT encoding');
  }

  return {
    header,
    payload,
    signature: decodeBase64Url(parts[2]),
    signingInput: `${parts[0]}.${parts[1]}`,
  };
}
JwksCache class · javascript · L39-L94 (56 LOC)
server/auth/accessAuth.js
class JwksCache {
  constructor(teamDomain) {
    this.teamDomain = teamDomain;
    this.keys = new Map();
    this.expiresAt = 0;
    this.pendingFetch = null;
  }

  async getKey(kid) {
    if (!kid) {
      throw new Error('JWT missing key id');
    }

    const now = Date.now();
    if (this.keys.has(kid) && now < this.expiresAt) {
      return this.keys.get(kid);
    }

    if (!this.pendingFetch) {
      this.pendingFetch = this.refresh().finally(() => {
        this.pendingFetch = null;
      });
    }

    await this.pendingFetch;
    const key = this.keys.get(kid);
    if (!key) {
      throw new Error(`Unknown JWT key id: ${kid}`);
    }
    return key;
  }

  async refresh() {
    const url = `https://${this.teamDomain}/cdn-cgi/access/certs`;
    const body = await fetchJson(url);
    const keys = Array.isArray(body.keys) ? body.keys : [];
    if (keys.length === 0) {
      throw new Error('Cloudflare cert endpoint returned no keys');
    }

    const nextKeys = new Map();
 
constructor method · javascript · L40-L45 (6 LOC)
server/auth/accessAuth.js
  constructor(teamDomain) {
    this.teamDomain = teamDomain;
    this.keys = new Map();
    this.expiresAt = 0;
    this.pendingFetch = null;
  }
getKey method · javascript · L47-L69 (23 LOC)
server/auth/accessAuth.js
  async getKey(kid) {
    if (!kid) {
      throw new Error('JWT missing key id');
    }

    const now = Date.now();
    if (this.keys.has(kid) && now < this.expiresAt) {
      return this.keys.get(kid);
    }

    if (!this.pendingFetch) {
      this.pendingFetch = this.refresh().finally(() => {
        this.pendingFetch = null;
      });
    }

    await this.pendingFetch;
    const key = this.keys.get(kid);
    if (!key) {
      throw new Error(`Unknown JWT key id: ${kid}`);
    }
    return key;
  }
Repobility — same analyzer, your code, free for public repos · /scan/
refresh method · javascript · L71-L93 (23 LOC)
server/auth/accessAuth.js
  async refresh() {
    const url = `https://${this.teamDomain}/cdn-cgi/access/certs`;
    const body = await fetchJson(url);
    const keys = Array.isArray(body.keys) ? body.keys : [];
    if (keys.length === 0) {
      throw new Error('Cloudflare cert endpoint returned no keys');
    }

    const nextKeys = new Map();
    keys.forEach(jwk => {
      if (jwk && jwk.kid && jwk.kty === 'RSA') {
        const publicKey = crypto.createPublicKey({ key: jwk, format: 'jwk' });
        nextKeys.set(jwk.kid, publicKey);
      }
    });

    if (nextKeys.size === 0) {
      throw new Error('Cloudflare cert endpoint returned no RSA keys');
    }

    this.keys = nextKeys;
    this.expiresAt = Date.now() + JWKS_CACHE_TTL_MS;
  }
fetchJson function · javascript · L96-L128 (33 LOC)
server/auth/accessAuth.js
function fetchJson(url) {
  return new Promise((resolve, reject) => {
    const req = https.get(
      url,
      {
        headers: {
          Accept: 'application/json',
        },
      },
      res => {
        let data = '';
        res.setEncoding('utf8');
        res.on('data', chunk => {
          data += chunk;
        });
        res.on('end', () => {
          if (res.statusCode < 200 || res.statusCode >= 300) {
            reject(new Error(`Failed to fetch Cloudflare certs: ${res.statusCode}`));
            return;
          }

          try {
            resolve(JSON.parse(data));
          } catch (err) {
            reject(new Error('Cloudflare cert endpoint returned invalid JSON'));
          }
        });
      }
    );

    req.on('error', reject);
  });
}
verifyAudience function · javascript · L130-L135 (6 LOC)
server/auth/accessAuth.js
function verifyAudience(payloadAud, expectedAudience) {
  if (Array.isArray(payloadAud)) {
    return payloadAud.includes(expectedAudience);
  }
  return payloadAud === expectedAudience;
}
assertClaimChecks function · javascript · L137-L153 (17 LOC)
server/auth/accessAuth.js
function assertClaimChecks(payload, config) {
  const now = Math.floor(Date.now() / 1000);
  if (payload.exp && now >= payload.exp) {
    throw new Error('JWT expired');
  }
  if (payload.nbf && now < payload.nbf) {
    throw new Error('JWT not active yet');
  }
  if (!verifyAudience(payload.aud, config.audience)) {
    throw new Error('JWT audience mismatch');
  }

  const expectedIssuer = `https://${config.teamDomain}`;
  if (payload.iss !== expectedIssuer) {
    throw new Error('JWT issuer mismatch');
  }
}
extractEmail function · javascript · L155-L162 (8 LOC)
server/auth/accessAuth.js
function extractEmail(payload) {
  const email = payload.email || payload.sub || '';
  const normalized = String(email).trim().toLowerCase();
  if (!normalized.includes('@')) {
    throw new Error('JWT missing email claim');
  }
  return normalized;
}
createAccessAuth function · javascript · L164-L239 (76 LOC)
server/auth/accessAuth.js
function createAccessAuth(options = {}) {
  const config = {
    ...getAccessConfig(options.env),
    ...(options.config || {}),
  };

  const problems = validateConfig(config);
  if (problems.length > 0) {
    throw new Error(`Access auth misconfigured: ${problems.join('; ')}`);
  }

  const jwks = options.jwksCache || new JwksCache(config.teamDomain);

  async function authenticateRequest(req) {
    const canUseDevOverride = config.nodeEnv !== 'production' && config.devAccessEmail;
    if (canUseDevOverride) {
      const role = getRoleForEmail(config.devAccessEmail, config);
      if (!role) {
        throw new Error('DEV_ACCESS_EMAIL is not on an approved access list');
      }

      return {
        email: config.devAccessEmail,
        role,
        subject: config.devAccessEmail,
        issuer: 'dev-override',
      };
    }

    const token = req.headers[ACCESS_JWT_HEADER];
    if (!token) {
      throw new Error('Missing Cloudflare Access JWT');
    }

    const parsed = par
authenticateRequest function · javascript · L177-L225 (49 LOC)
server/auth/accessAuth.js
  async function authenticateRequest(req) {
    const canUseDevOverride = config.nodeEnv !== 'production' && config.devAccessEmail;
    if (canUseDevOverride) {
      const role = getRoleForEmail(config.devAccessEmail, config);
      if (!role) {
        throw new Error('DEV_ACCESS_EMAIL is not on an approved access list');
      }

      return {
        email: config.devAccessEmail,
        role,
        subject: config.devAccessEmail,
        issuer: 'dev-override',
      };
    }

    const token = req.headers[ACCESS_JWT_HEADER];
    if (!token) {
      throw new Error('Missing Cloudflare Access JWT');
    }

    const parsed = parseJwt(token);
    const publicKey = await jwks.getKey(parsed.header.kid);
    const isValid = crypto.verify(
      'RSA-SHA256',
      Buffer.from(parsed.signingInput),
      publicKey,
      parsed.signature
    );

    if (!isValid) {
      throw new Error('Invalid Cloudflare Access JWT signature');
    }

    assertClaimChecks(parsed.payload, config);
accessAuth function · javascript · L227-L234 (8 LOC)
server/auth/accessAuth.js
  async function accessAuth(req, res, next) {
    try {
      req.user = await authenticateRequest(req);
      next();
    } catch (err) {
      next(err);
    }
  }
Methodology: Repobility · https://repobility.com/research/state-of-ai-code-2026/
isHtmlRequest function · javascript · L1-L3 (3 LOC)
server/auth/authorize.js
function isHtmlRequest(req) {
  return req.accepts(['html', 'json']) === 'html';
}
renderAuthErrorPage function · javascript · L5-L65 (61 LOC)
server/auth/authorize.js
function renderAuthErrorPage(statusCode, title, message) {
  return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>${title}</title>
  <style>
    body {
      margin: 0;
      min-height: 100vh;
      display: grid;
      place-items: center;
      background: #0a0e17;
      color: #e2e8f0;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    }
    main {
      width: min(560px, calc(100vw - 32px));
      padding: 32px;
      border: 1px solid rgba(255, 255, 255, 0.08);
      background: #151d2e;
      border-radius: 8px;
    }
    h1 {
      margin: 0 0 12px;
      color: #ff6600;
      font-size: 20px;
      letter-spacing: 0.04em;
      text-transform: uppercase;
    }
    p {
      margin: 0 0 16px;
      color: #cbd5e1;
      line-height: 1.6;
    }
    a {
      color: #ff6600;
      text-decoration: none;
    }
    a:hover {
      text-decoration: und
sendUnauthorized function · javascript · L67-L77 (11 LOC)
server/auth/authorize.js
function sendUnauthorized(res, req, statusCode, message) {
  if (isHtmlRequest(req)) {
    const title = statusCode === 401 ? 'Authentication Required' : 'Access Restricted';
    return res
      .status(statusCode)
      .type('html')
      .send(renderAuthErrorPage(statusCode, title, message));
  }

  return res.status(statusCode).json({ error: message });
}
requireAuth function · javascript · L79-L84 (6 LOC)
server/auth/authorize.js
function requireAuth(req, res, next) {
  if (req.user) {
    return next();
  }
  return sendUnauthorized(res, req, 401, 'A valid Cloudflare Access session is required.');
}
requireRole function · javascript · L86-L105 (20 LOC)
server/auth/authorize.js
function requireRole(role) {
  return (req, res, next) => {
    if (!req.user) {
      return sendUnauthorized(res, req, 401, 'A valid Cloudflare Access session is required.');
    }
    if (req.user.role === role) {
      return next();
    }

    console.warn(
      `[auth] forbidden role=${req.user.role} email=${req.user.email} path=${req.originalUrl}`
    );
    return sendUnauthorized(
      res,
      req,
      403,
      'Your account is signed in, but this area is restricted to the family tier.'
    );
  };
}
authErrorHandler function · javascript · L107-L110 (4 LOC)
server/auth/authorize.js
function authErrorHandler(err, req, res, next) { // eslint-disable-line no-unused-vars
  console.warn(`[auth] rejected path=${req.originalUrl} reason=${err.message}`);
  return sendUnauthorized(res, req, 401, 'Your Cloudflare Access session could not be verified.');
}
parseCsvList function · javascript · L1-L8 (8 LOC)
server/auth/config.js
function parseCsvList(value) {
  return new Set(
    String(value || '')
      .split(',')
      .map(item => item.trim().toLowerCase())
      .filter(Boolean)
  );
}
normalizeDomain function · javascript · L10-L14 (5 LOC)
server/auth/config.js
function normalizeDomain(value) {
  const trimmed = String(value || '').trim().replace(/\/+$/, '');
  if (!trimmed) return '';
  return trimmed.replace(/^https?:\/\//i, '').toLowerCase();
}
Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
getAccessConfig function · javascript · L16-L26 (11 LOC)
server/auth/config.js
function getAccessConfig(env = process.env) {
  return {
    audience: String(env.CLOUDFLARE_ACCESS_AUD || '').trim(),
    teamDomain: normalizeDomain(env.CLOUDFLARE_TEAM_DOMAIN),
    familyEmails: parseCsvList(env.FAMILY_EMAILS),
    generalEmails: parseCsvList(env.ALLOW_GENERAL_EMAILS),
    generalDomains: parseCsvList(env.ALLOW_GENERAL_DOMAINS),
    devAccessEmail: String(env.DEV_ACCESS_EMAIL || '').trim().toLowerCase(),
    nodeEnv: String(env.NODE_ENV || '').trim().toLowerCase(),
  };
}
hasGeneralAccessConfig function · javascript · L28-L30 (3 LOC)
server/auth/config.js
function hasGeneralAccessConfig(config) {
  return config.generalEmails.size > 0 || config.generalDomains.size > 0;
}
getRoleForEmail function · javascript · L32-L44 (13 LOC)
server/auth/config.js
function getRoleForEmail(email, config) {
  const normalizedEmail = String(email || '').trim().toLowerCase();
  if (!normalizedEmail) return null;
  if (config.familyEmails.has(normalizedEmail)) return 'family';
  if (config.generalEmails.has(normalizedEmail)) return 'general';

  const atIndex = normalizedEmail.lastIndexOf('@');
  if (atIndex === -1) return null;

  const domain = normalizedEmail.slice(atIndex + 1);
  if (config.generalDomains.has(domain)) return 'general';
  return null;
}
validateConfig function · javascript · L46-L57 (12 LOC)
server/auth/config.js
function validateConfig(config) {
  const problems = [];
  const isDevOverride = config.nodeEnv !== 'production' && !!config.devAccessEmail;

  if (!config.audience && !isDevOverride) problems.push('CLOUDFLARE_ACCESS_AUD is required');
  if (!config.teamDomain && !isDevOverride) problems.push('CLOUDFLARE_TEAM_DOMAIN is required');
  if (config.familyEmails.size === 0) problems.push('FAMILY_EMAILS must contain at least one email');
  if (!hasGeneralAccessConfig(config)) {
    problems.push('ALLOW_GENERAL_EMAILS or ALLOW_GENERAL_DOMAINS must be configured');
  }
  return problems;
}
sequentialMomentum function · javascript · L13-L48 (36 LOC)
server/calculator.js
function sequentialMomentum(company) {
  const hist = company.quarterlyHistory;
  if (!hist || hist.length < 3) return null;

  // Compute QoQ from revenue if not directly available
  let currentQoq, priorQoq;

  if (hist[0].revenueQoqPct !== null && hist[0].revenueQoqPct !== undefined) {
    currentQoq = hist[0].revenueQoqPct;
  } else if (hist[0].revenueMil && hist[1].revenueMil) {
    currentQoq = ((hist[0].revenueMil - hist[1].revenueMil) / hist[1].revenueMil) * 100;
  } else {
    return null;
  }

  if (hist[1].revenueQoqPct !== null && hist[1].revenueQoqPct !== undefined) {
    priorQoq = hist[1].revenueQoqPct;
  } else if (hist[1].revenueMil && hist[2].revenueMil) {
    priorQoq = ((hist[1].revenueMil - hist[2].revenueMil) / hist[2].revenueMil) * 100;
  } else {
    return null;
  }

  const delta = currentQoq - priorQoq;
  let trend;
  if (delta > 2) trend = 'accelerating';
  else if (delta < -2) trend = 'decelerating';
  else trend = 'stable';

  return {
    currentQoq: Math
growthAdjustedValuation function · javascript · L56-L61 (6 LOC)
server/calculator.js
function growthAdjustedValuation(company) {
  const pe = company.runRatePe || company.trailingPe || company.normalizedPe;
  const growth = company.revenueYoyPct;
  if (!pe || !growth || growth <= 0) return null;
  return Math.round((pe / growth) * 100) / 100;
}
operatingLeverage function · javascript · L69-L87 (19 LOC)
server/calculator.js
function operatingLeverage(company) {
  const currentEBITDA = company.ebitdaMil;
  const currentRevenue = company.revenueRecentMil;
  const ebitdaYoY = company.ebitdaYoyPct;
  const revenueYoY = company.revenueYoyPct;

  if (currentEBITDA == null || currentRevenue == null ||
      ebitdaYoY == null || revenueYoY == null ||
      revenueYoY === 0 || ebitdaYoY === -100) return null;

  // Back-calculate prior-year values from current + YoY growth
  const priorEBITDA = currentEBITDA / (1 + ebitdaYoY / 100);
  const priorRevenue = currentRevenue / (1 + revenueYoY / 100);
  const revenueDelta = currentRevenue - priorRevenue;
  if (revenueDelta === 0) return null;

  const ebitdaDelta = currentEBITDA - priorEBITDA;
  return Math.round((ebitdaDelta / revenueDelta) * 100) / 100;
}
distanceFrom52WeekHigh function · javascript · L93-L98 (6 LOC)
server/calculator.js
function distanceFrom52WeekHigh(company) {
  const price = company.price;
  const high = company.fiftyTwoWeekHigh;
  if (!price || !high) return null;
  return Math.round(((price - high) / high) * 10000) / 100;
}
Want this analysis on your repo? https://repobility.com/scan/
peCompression function · javascript · L105-L123 (19 LOC)
server/calculator.js
function peCompression(company) {
  // Use pre-computed if available
  if (company.peCompression) return company.peCompression;

  const t = company.trailingPe;
  const r = company.runRatePe;
  const f = company.forwardPe;

  if (t === null && r === null && f === null) return null;

  return {
    trailingPe: t,
    runRatePe: r,
    forwardPe: f,
    trailingToRunRate: (t !== null && r !== null) ? Math.round((t - r) * 100) / 100 : null,
    runRateToForward: (r !== null && f !== null) ? Math.round((r - f) * 100) / 100 : null,
    totalCompression: (t !== null && f !== null) ? Math.round((t - f) * 100) / 100 : null,
  };
}
computeAllMetrics function · javascript · L128-L137 (10 LOC)
server/calculator.js
function computeAllMetrics(company) {
  return {
    ticker: company.ticker,
    momentum: sequentialMomentum(company),
    gav: growthAdjustedValuation(company),
    operatingLeverage: operatingLeverage(company),
    distanceFromHigh: distanceFrom52WeekHigh(company),
    peCompression: peCompression(company),
  };
}
enrichCompanies function · javascript · L142-L147 (6 LOC)
server/calculator.js
function enrichCompanies(companies) {
  return companies.map(company => ({
    ...company,
    calculated: computeAllMetrics(company),
  }));
}
loadPortfolioConfig function · javascript · L34-L44 (11 LOC)
server/dataLoader.js
function loadPortfolioConfig() {
  try {
    const raw = fs.readFileSync(PORTFOLIO_PATH, 'utf-8');
    const config = JSON.parse(raw);
    portfolioHoldings = (config.holdings || []).map(t => t.toUpperCase());
    console.log(`[dataLoader] Portfolio: ${portfolioHoldings.length} holdings — ${portfolioHoldings.join(', ')}`);
  } catch (err) {
    console.warn('[dataLoader] Could not read portfolio.json, showing all companies:', err.message);
    portfolioHoldings = [];
  }
}
scanReportsDirectory function · javascript · L46-L59 (14 LOC)
server/dataLoader.js
function scanReportsDirectory() {
  if (!fs.existsSync(REPORTS_DIR)) {
    console.error(`[dataLoader] Reports directory not found: ${REPORTS_DIR}`);
    return [];
  }

  const entries = fs.readdirSync(REPORTS_DIR, { withFileTypes: true });
  const companyDirs = entries
    .filter(e => e.isDirectory() && e.name !== 'validation')
    .map(e => e.name);

  console.log(`[dataLoader] Found ${companyDirs.length} company directories in ${REPORTS_DIR}`);
  return companyDirs;
}
findJsonFile function · javascript · L61-L79 (19 LOC)
server/dataLoader.js
function findJsonFile(dirPath, ticker) {
  const files = fs.readdirSync(dirPath);
  const tickerLower = ticker.toLowerCase();

  // Prefer dashboard_metrics.json (unified format with evaluation data)
  const metricsFile = files.find(f =>
    f.toLowerCase() === `${tickerLower}_dashboard_metrics.json`
  );
  if (metricsFile) {
    console.log(`[dataLoader] ${ticker}: Using dashboard_metrics.json`);
    return path.join(dirPath, metricsFile);
  }

  // Fall back to company_data.json (legacy format)
  const jsonFile = files.find(f =>
    f.toLowerCase() === `${tickerLower}_company_data.json`
  );
  return jsonFile ? path.join(dirPath, jsonFile) : null;
}
findLatestMarkdown function · javascript · L81-L93 (13 LOC)
server/dataLoader.js
function findLatestMarkdown(dirPath, ticker) {
  const files = fs.readdirSync(dirPath);
  // Find all markdown files matching TICKER_YYYYMMDD.md pattern
  const mdFiles = files
    .filter(f => {
      const upper = f.toUpperCase();
      return upper.startsWith(ticker.toUpperCase() + '_') && upper.endsWith('.MD');
    })
    .sort()
    .reverse(); // newest first by filename date

  return mdFiles.length > 0 ? path.join(dirPath, mdFiles[0]) : null;
}
backfillQuarterEndDates function · javascript · L100-L145 (46 LOC)
server/dataLoader.js
function backfillQuarterEndDates(normalized, dirPath, ticker, primaryJsonPath) {
  const hist = normalized.quarterlyHistory;
  if (!hist || hist.length === 0) return;

  // Check if any entries already have calendarQuarter — if so, nothing to do
  if (hist.some(q => q.calendarQuarter)) return;

  // Check if any quarter labels suggest a fiscal year (contain "FY")
  const hasFiscalQuarters = hist.some(q => q.quarter && q.quarter.includes('FY'));
  if (!hasFiscalQuarters) return;

  // Try to find the company_data.json as a secondary source
  const tickerLower = ticker.toLowerCase();
  const companyDataPath = path.join(dirPath, `${tickerLower}_company_data.json`);
  if (companyDataPath === primaryJsonPath || !fs.existsSync(companyDataPath)) return;

  try {
    const raw = JSON.parse(fs.readFileSync(companyDataPath, 'utf-8'));
    const cdHist = raw.quantitative?.quarterly_history;
    if (!Array.isArray(cdHist)) return;

    // Build a map of quarter label → quarter_end date
    const e
Repobility — same analyzer, your code, free for public repos · /scan/
loadCompany function · javascript · L147-L199 (53 LOC)
server/dataLoader.js
function loadCompany(dirPath, ticker) {
  const result = {
    ticker: ticker.toUpperCase(),
    normalized: null,
    analysis: null,
    rawMarkdown: null,
    hasJson: false,
    hasMd: false,
    errors: [],
  };

  // Load JSON
  const jsonPath = findJsonFile(dirPath, ticker);
  if (jsonPath) {
    try {
      const raw = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
      result.normalized = normalizeCompany(raw);
      result.hasJson = true;

      // If dashboard_metrics was used (no quarter_end dates), backfill from company_data
      backfillQuarterEndDates(result.normalized, dirPath, ticker, jsonPath);
    } catch (err) {
      result.errors.push(`JSON parse error: ${err.message}`);
      console.warn(`[dataLoader] ${ticker}: JSON error — ${err.message}`);
    }
  }

  // Load Markdown
  const mdPath = findLatestMarkdown(dirPath, ticker);
  if (mdPath) {
    try {
      const mdText = fs.readFileSync(mdPath, 'utf-8');
      result.rawMarkdown = mdText;
      result.analysi
mergeMarkdownIntoNormalized function · javascript · L201-L264 (64 LOC)
server/dataLoader.js
function mergeMarkdownIntoNormalized(norm, analysis) {
  // Verdict & conviction: markdown fills gaps only (dashboard_metrics is primary)
  if (!norm.verdict && analysis.verdict) norm.verdict = analysis.verdict;
  if (!norm.convictionScore && analysis.convictionScore) norm.convictionScore = analysis.convictionScore;

  // Fill in P/E values from markdown if missing in JSON
  if (analysis.peValues) {
    if (!norm.trailingPe && analysis.peValues.trailingPe) norm.trailingPe = analysis.peValues.trailingPe;
    if (!norm.runRatePe && analysis.peValues.runRatePe) norm.runRatePe = analysis.peValues.runRatePe;
    if (!norm.forwardPe && analysis.peValues.forwardPe) norm.forwardPe = analysis.peValues.forwardPe;
    if (!norm.normalizedPe && analysis.peValues.normalizedPe) norm.normalizedPe = analysis.peValues.normalizedPe;
    if (!norm.priceToSales && analysis.peValues.priceToSales) norm.priceToSales = analysis.peValues.priceToSales;
  }

  // Fill financial metrics from markdown
  const fin 
buildFromMarkdownOnly function · javascript · L266-L300 (35 LOC)
server/dataLoader.js
function buildFromMarkdownOnly(analysis, ticker) {
  return {
    ticker: ticker.toUpperCase(),
    companyName: ticker.toUpperCase(),
    fetchDate: analysis.date,
    price: analysis.price,
    marketCapMil: null,
    trailingPe: analysis.peValues?.trailingPe || null,
    runRatePe: analysis.peValues?.runRatePe || null,
    forwardPe: analysis.peValues?.forwardPe || null,
    normalizedPe: analysis.peValues?.normalizedPe || null,
    priceToSales: analysis.peValues?.priceToSales || null,
    revenueRecentMil: null,
    revenueYoyPct: analysis.financials?.revenueYoyPct || null,
    revenueQoqPct: analysis.financials?.revenueQoqPct || null,
    grossMarginPct: analysis.financials?.grossMarginPct || null,
    ebitdaMarginPct: analysis.financials?.ebitdaMarginPct || null,
    quarterlyHistory: analysis.quarterlyHistory || [],
    verdict: analysis.verdict,
    convictionScore: analysis.convictionScore,
    confidenceLevel: null,
    keyStrengths: [],
    keyConcerns: [],
    saulSummary:
page 1 / 3next ›