bugfixing

This commit is contained in:
MuslemRahimi 2025-02-22 22:06:55 +01:00
parent 22dd3cbdc2
commit 08798d15c3
3 changed files with 226 additions and 158 deletions

View File

@ -1,6 +1,6 @@
<script lang="ts">
import { stockTicker, displayCompanyName } from "$lib/store";
import { abbreviateNumber } from "$lib/utils";
import { abbreviateNumber, removeCompanyStrings } from "$lib/utils";
export let data;
let rawData = {};
@ -26,7 +26,7 @@
}
$: {
if ($stockTicker && typeof window !== "undefined") {
if ($stockTicker) {
rawData = data?.getEarningsSurprise;
epsRatio =
rawData?.epsPrior === 0
@ -44,83 +44,80 @@
}
</script>
{#if Object?.keys(rawData)?.length !== 0}
<div class="space-y-3 overflow-hidden">
<!--Start Content-->
<div class="w-auto lg:w-full p-1 flex flex-col m-auto">
<div class="flex flex-col items-center w-full mb-3">
<div class="flex flex-row justify-start mr-auto items-center">
<!--<img class="h-10 inline-block mr-2" src={copilotIcon} />-->
<div class="flex flex-row items-center">
<h3
class="mr-1 flex flex-row items-center text-white text-2xl font-bold"
>
Earnings Surprise
</h3>
<label
class="{latestInfoDate(rawData?.date)
? ''
: 'hidden'} text-black bg-[#fff] ml-2 font-semibold not-italic text-xs rounded px-2 py-0.5"
>New</label
>
</div>
<div class="space-y-3 overflow-hidden">
<!--Start Content-->
<div class="w-auto lg:w-full p-1 flex flex-col m-auto">
<div class="flex flex-col items-center w-full mb-3">
<div class="flex flex-row justify-start mr-auto items-center">
<!--<img class="h-10 inline-block mr-2" src={copilotIcon} />-->
<div class="flex flex-row items-center">
<h3
class="mr-1 flex flex-row items-center text-white text-2xl font-bold"
>
Earnings Surprise
</h3>
<label
class="{latestInfoDate(rawData?.date)
? ''
: 'hidden'} text-black bg-[#fff] ml-2 font-semibold not-italic text-xs rounded px-2 py-0.5"
>New</label
>
</div>
</div>
<div
class="text-white text-[1rem] {latestInfoDate(rawData?.date)
? 'bg-[#F9AB00] bg-opacity-[0.1] p-3 rounded-md'
: 'bg-default pl-1'} "
>
<div class="mt-1">
{$displayCompanyName} has released their quartely earnings on {new Date(
rawData?.date,
)?.toLocaleString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
daySuffix: "2-digit",
})}:
</div>
<li
class="ml-[20px] sm:ml-[30px]"
style="color: #fff; line-height: 22px; margin-top:10px; margin-bottom: 10px; list-style-type: disc;"
>
Revenue of <span class="font-semibold"
>{abbreviateNumber(rawData?.revenue, true)}</span
>
{rawData?.revenueSurprise > 0 ? "exceeds" : "misses"} estimates by {abbreviateNumber(
Math.abs(rawData?.revenueSurprise),
true,
)}, with
<span
class="font-semibold {revenueRatio > 0
? "before:content-['+'] text-[#00FC50]"
: 'text-[#FF2F1F]'}">{revenueRatio}%</span
>
YoY {revenueRatio < 0 ? "decline" : "growth"}.
</li>
<li
class="ml-[20px] sm:ml-[30px]"
style="color: #fff; line-height: 22px; margin-top: 0px; margin-bottom: 0px; list-style-type: disc;"
>
EPS of <span class="font-semibold">{rawData?.eps}</span>
{rawData?.epsSurprise > 0 ? "exceeds" : "misses"} estimates by {rawData?.epsSurprise?.toFixed(
2,
)}, with
<span
class="font-semibold {epsRatio === null
? 'text-white'
: epsRatio > 0
? 'text-[#00FC50]'
: 'text-[#FF2F1F]'}"
>
{epsRatio === null ? "n/a" : `${epsRatio}%`}
</span>
YoY {epsRatio === null ? "" : epsRatio < 0 ? "decline" : "growth"}.
</li>
</div>
</div>
<div
class="text-white text-[1rem] {latestInfoDate(rawData?.date)
? 'bg-[#F9AB00] bg-opacity-[0.1] p-3 rounded-md'
: 'bg-default pl-1'} "
>
<div class="mt-1">
{removeCompanyStrings($displayCompanyName)} has released their quartely earnings
on {new Date(rawData?.date)?.toLocaleString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
daySuffix: "2-digit",
})}:
</div>
<li
class="ml-[20px] sm:ml-[30px]"
style="color: #fff; line-height: 22px; margin-top:10px; margin-bottom: 10px; list-style-type: disc;"
>
Revenue of <span class=""
>{abbreviateNumber(rawData?.revenue, true)}</span
>
{rawData?.revenueSurprise > 0 ? "exceeds" : "misses"} estimates by {abbreviateNumber(
Math.abs(rawData?.revenueSurprise),
true,
)}, with
<span
class=" {revenueRatio > 0
? "before:content-['+'] text-[#00FC50]"
: 'text-[#FF2F1F]'}">{revenueRatio}%</span
>
YoY {revenueRatio < 0 ? "decline" : "growth"}.
</li>
<li
class="ml-[20px] sm:ml-[30px]"
style="color: #fff; line-height: 22px; margin-top: 0px; margin-bottom: 0px; list-style-type: disc;"
>
EPS of <span class="">{rawData?.eps}</span>
{rawData?.epsSurprise > 0 ? "exceeds" : "misses"} estimates by {rawData?.epsSurprise?.toFixed(
2,
)}, with
<span
class=" {epsRatio === null
? 'text-white'
: epsRatio > 0
? 'text-[#00FC50]'
: 'text-[#FF2F1F]'}"
>
{epsRatio === null ? "n/a" : `${epsRatio}%`}
</span>
YoY {epsRatio === null ? "" : epsRatio < 0 ? "decline" : "growth"}.
</li>
</div>
</div>
{/if}
</div>

View File

@ -1,5 +1,6 @@
const cleanString = (input) => {
const substringsToRemove = [
// Pre-compile regex pattern and substrings for cleaning
const REMOVE_PATTERNS = {
pattern: new RegExp(`\\b(${[
"Depositary",
"Inc.",
"Incorporated",
@ -11,73 +12,154 @@ const cleanString = (input) => {
"Oyj",
"Company",
"The",
"plc",
];
const pattern = new RegExp(`\\b(${substringsToRemove.join("|")})\\b|,`, "gi");
return input?.replace(pattern, "").trim();
"plc"
].join("|")})\\b|,`, "gi")
};
const fetchData = async (apiURL, apiKey, endpoint, ticker) => {
try {
const response = await fetch(`${apiURL}${endpoint}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": apiKey,
},
body: JSON.stringify({ ticker }),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
// Memoized string cleaning function
const cleanString = (() => {
const cache = new Map();
return (input) => {
if (!input) return '';
if (cache.has(input)) return cache.get(input);
const cleaned = input.replace(REMOVE_PATTERNS.pattern, '').trim();
cache.set(input, cleaned);
return cleaned;
};
})();
// Constants
const CACHE_DURATION = 5 * 60 * 1000;
const REQUEST_TIMEOUT = 5000;
const ENDPOINTS = Object.freeze([
"/etf-profile",
"/etf-holdings",
"/etf-sector-weighting",
"/stock-dividend",
"/stock-quote",
"/pre-post-quote",
"/wiim",
"/one-day-price",
"/stock-news"
]);
// LRU Cache implementation with automatic cleanup
class LRUCache {
constructor(maxSize = 100) {
this.cache = new Map();
this.maxSize = maxSize;
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() - item.timestamp >= CACHE_DURATION) {
this.cache.delete(key);
return null;
}
const data = await response.json();
return item.data;
}
set(key, data) {
if (this.cache.size >= this.maxSize) {
const oldestKey = this.cache.keys().next().value;
this.cache.delete(oldestKey);
}
this.cache.set(key, { data, timestamp: Date.now() });
}
}
const dataCache = new LRUCache();
// Optimized fetch function with AbortController and timeout
const fetchWithTimeout = async (url, options, timeout) => {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return await response.json();
} finally {
clearTimeout(timeoutId);
}
};
// Main data fetching function
const fetchData = async (apiURL, apiKey, endpoint, ticker) => {
const cacheKey = `${endpoint}-${ticker}`;
const cachedData = dataCache.get(cacheKey);
if (cachedData) return cachedData;
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": apiKey
},
body: JSON.stringify({ ticker, endpoints: ENDPOINTS })
};
try {
const data = await fetchWithTimeout(
`${apiURL}${endpoint}`,
options,
REQUEST_TIMEOUT
);
dataCache.set(cacheKey, data);
return data;
} catch (error) {
console.error(`Error fetching ${endpoint}:`, error);
if (error.name === 'AbortError') {
throw new Error(`Request timeout for ${endpoint}`);
}
return [];
}
};
// Optimized watchlist fetching with error boundary
const fetchWatchlist = async (pb, userId) => {
let output;
if (!userId) return [];
try {
output = await pb.collection("watchlist").getFullList({
filter: `user="${userId}"`,
return await pb.collection("watchlist").getFullList({
filter: `user="${userId}"`
});
} catch (e) {
//console.log(e)
output = [];
} catch {
return [];
}
return output;
};
// Main load function with parallel fetching
export const load = async ({ params, locals }) => {
const { apiURL, apiKey, pb, user } = locals;
const { tickerID } = params;
if (!tickerID) {
return getDefaultResponse(tickerID);
}
try {
const endpoints = [
"/etf-profile",
"/etf-holdings",
"/etf-sector-weighting",
"/stock-dividend",
"/stock-quote",
"/pre-post-quote",
"/wiim",
"/one-day-price",
"/stock-news",
];
const [bulkData, userWatchlist] = await Promise.all([
fetchData(apiURL, apiKey, "/bulk-data", tickerID),
fetchWatchlist(pb, user?.id)
]);
const promises = [
...endpoints.map((endpoint) =>
fetchData(apiURL, apiKey, endpoint, tickerID),
),
fetchWatchlist(pb, user?.id),
];
// Destructure with default empty arrays to prevent undefined errors
const {
'/etf-profile': getETFProfile = [],
'/etf-holdings': getETFHoldings = [],
'/etf-sector-weighting': getETFSectorWeighting = [],
'/stock-dividend': getStockDividend = [],
'/stock-quote': getStockQuote = [],
'/pre-post-quote': getPrePostQuote = [],
'/wiim': getWhyPriceMoved = [],
'/one-day-price': getOneDayPrice = [],
'/stock-news': getNews = []
} = bulkData;
const [
return {
getETFProfile,
getETFHoldings,
getETFSectorWeighting,
@ -87,38 +169,27 @@ export const load = async ({ params, locals }) => {
getWhyPriceMoved,
getOneDayPrice,
getNews,
getUserWatchlist,
] = await Promise.all(promises);
return {
getETFProfile: getETFProfile || [],
getETFHoldings: getETFHoldings || [],
getETFSectorWeighting: getETFSectorWeighting || [],
getStockDividend: getStockDividend || [],
getStockQuote: getStockQuote || [],
getPrePostQuote: getPrePostQuote || [],
getWhyPriceMoved: getWhyPriceMoved || [],
getOneDayPrice: getOneDayPrice || [],
getNews: getNews || [],
getUserWatchlist: getUserWatchlist || [],
getUserWatchlist: userWatchlist,
companyName: cleanString(getETFProfile?.at(0)?.name),
getParams: params.tickerID,
getParams: tickerID
};
} catch (error) {
console.error('Error in load function:', error);
return {
getETFProfile: [],
getETFHoldings: [],
getETFSectorWeighting: [],
getStockDividend: [],
getStockQuote: [],
getPrePostQuote: [],
getWhyPriceMoved: [],
getOneDayPrice: [],
getNews: [],
getUserWatchlist: [],
companyName: '',
getParams: params.tickerID,
};
return getDefaultResponse(tickerID);
}
};
};
// Helper function to generate default response
const getDefaultResponse = (tickerID) => ({
getETFProfile: [],
getETFHoldings: [],
getETFSectorWeighting: [],
getStockDividend: [],
getStockQuote: [],
getPrePostQuote: [],
getWhyPriceMoved: [],
getOneDayPrice: [],
getNews: [],
getUserWatchlist: [],
companyName: '',
getParams: tickerID
});

View File

@ -380,11 +380,11 @@
class="bg-default w-full max-w-screen sm:max-w-[1250px] min-h-screen overflow-hidden"
>
<!-- Page wrapper -->
<div class="mt-5 flex flex-col w-full relative w-full">
<div class="mt-5 flex flex-col w-full relative w-full sm:max-w-[1250px]">
<main class="grow w-full">
<section class="">
<div class="w-full">
<div class="sm:flex sm:justify-start w-full sm:max-w-[1250px]">
<div class="sm:flex sm:justify-start w-full">
<!--Start Mobile Navbar-->
<div class="fixed top-0 left-0 right-0 z-20 bg-default sm:hidden">
<div class="navbar w-full px-4 py-2">