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

View File

@ -1,5 +1,6 @@
const cleanString = (input) => { // Pre-compile regex pattern and substrings for cleaning
const substringsToRemove = [ const REMOVE_PATTERNS = {
pattern: new RegExp(`\\b(${[
"Depositary", "Depositary",
"Inc.", "Inc.",
"Incorporated", "Incorporated",
@ -11,73 +12,154 @@ const cleanString = (input) => {
"Oyj", "Oyj",
"Company", "Company",
"The", "The",
"plc", "plc"
]; ].join("|")})\\b|,`, "gi")
const pattern = new RegExp(`\\b(${substringsToRemove.join("|")})\\b|,`, "gi");
return input?.replace(pattern, "").trim();
}; };
const fetchData = async (apiURL, apiKey, endpoint, ticker) => { // Memoized string cleaning function
try { const cleanString = (() => {
const response = await fetch(`${apiURL}${endpoint}`, { const cache = new Map();
method: "POST", return (input) => {
headers: { if (!input) return '';
"Content-Type": "application/json", if (cache.has(input)) return cache.get(input);
"X-API-KEY": apiKey, const cleaned = input.replace(REMOVE_PATTERNS.pattern, '').trim();
}, cache.set(input, cleaned);
body: JSON.stringify({ ticker }), return cleaned;
}); };
})();
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`); // 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;
} }
return item.data;
const data = await response.json(); }
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; return data;
} catch (error) { } catch (error) {
console.error(`Error fetching ${endpoint}:`, error); if (error.name === 'AbortError') {
throw new Error(`Request timeout for ${endpoint}`);
}
return []; return [];
} }
}; };
// Optimized watchlist fetching with error boundary
const fetchWatchlist = async (pb, userId) => { const fetchWatchlist = async (pb, userId) => {
let output; if (!userId) return [];
try { try {
output = await pb.collection("watchlist").getFullList({ return await pb.collection("watchlist").getFullList({
filter: `user="${userId}"`, filter: `user="${userId}"`
}); });
} catch (e) { } catch {
//console.log(e) return [];
output = [];
} }
return output;
}; };
// Main load function with parallel fetching
export const load = async ({ params, locals }) => { export const load = async ({ params, locals }) => {
const { apiURL, apiKey, pb, user } = locals; const { apiURL, apiKey, pb, user } = locals;
const { tickerID } = params; const { tickerID } = params;
if (!tickerID) {
return getDefaultResponse(tickerID);
}
try { try {
const endpoints = [ const [bulkData, userWatchlist] = await Promise.all([
"/etf-profile", fetchData(apiURL, apiKey, "/bulk-data", tickerID),
"/etf-holdings", fetchWatchlist(pb, user?.id)
"/etf-sector-weighting", ]);
"/stock-dividend",
"/stock-quote",
"/pre-post-quote",
"/wiim",
"/one-day-price",
"/stock-news",
];
const promises = [ // Destructure with default empty arrays to prevent undefined errors
...endpoints.map((endpoint) => const {
fetchData(apiURL, apiKey, endpoint, tickerID), '/etf-profile': getETFProfile = [],
), '/etf-holdings': getETFHoldings = [],
fetchWatchlist(pb, user?.id), '/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, getETFProfile,
getETFHoldings, getETFHoldings,
getETFSectorWeighting, getETFSectorWeighting,
@ -87,38 +169,27 @@ export const load = async ({ params, locals }) => {
getWhyPriceMoved, getWhyPriceMoved,
getOneDayPrice, getOneDayPrice,
getNews, getNews,
getUserWatchlist, getUserWatchlist: userWatchlist,
] = 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 || [],
companyName: cleanString(getETFProfile?.at(0)?.name), companyName: cleanString(getETFProfile?.at(0)?.name),
getParams: params.tickerID, getParams: tickerID
}; };
} catch (error) { } catch (error) {
console.error('Error in load function:', error); return getDefaultResponse(tickerID);
return {
getETFProfile: [],
getETFHoldings: [],
getETFSectorWeighting: [],
getStockDividend: [],
getStockQuote: [],
getPrePostQuote: [],
getWhyPriceMoved: [],
getOneDayPrice: [],
getNews: [],
getUserWatchlist: [],
companyName: '',
getParams: params.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" class="bg-default w-full max-w-screen sm:max-w-[1250px] min-h-screen overflow-hidden"
> >
<!-- Page wrapper --> <!-- 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"> <main class="grow w-full">
<section class=""> <section class="">
<div class="w-full"> <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--> <!--Start Mobile Navbar-->
<div class="fixed top-0 left-0 right-0 z-20 bg-default sm:hidden"> <div class="fixed top-0 left-0 right-0 z-20 bg-default sm:hidden">
<div class="navbar w-full px-4 py-2"> <div class="navbar w-full px-4 py-2">