Refactor: Separate data sync from UI rendering, change colunm width
This commit is contained in:
@@ -4,6 +4,7 @@ from engine import DataEngine
|
||||
import concurrent.futures
|
||||
from flask_caching import Cache
|
||||
import csv, os, logging
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
app = Flask(__name__)
|
||||
cache = Cache(app, config={'CACHE_TYPE': 'SimpleCache'})
|
||||
@@ -11,52 +12,6 @@ cache = Cache(app, config={'CACHE_TYPE': 'SimpleCache'})
|
||||
import os
|
||||
import csv
|
||||
|
||||
def load_instruments_from_csv(file_path):
|
||||
instruments = []
|
||||
|
||||
# Standard static templates
|
||||
# For AGI, we use the ISIN-based Tearsheet URL
|
||||
TEMPLATES = {
|
||||
'jpm': "https://am.jpmorgan.com/FundsMarketingHandler/historicalData?cusip={cusip}&country=hk&role=per",
|
||||
'yahoo': "https://query1.finance.yahoo.com/v8/finance/chart/{cusip}?range=5y&interval=1d",
|
||||
'agi': "https://markets.ft.com/data/funds/tearsheet/historical?s={cusip}"
|
||||
}
|
||||
|
||||
try:
|
||||
abs_path = os.path.join(os.path.dirname(__file__), file_path)
|
||||
|
||||
if not os.path.exists(abs_path):
|
||||
print(f"Error: {file_path} not found.")
|
||||
return []
|
||||
|
||||
with open(abs_path, mode='r', encoding='utf-8-sig') as csvfile:
|
||||
reader = csv.DictReader(csvfile)
|
||||
# Standardize header names to lowercase
|
||||
reader.fieldnames = [name.strip().lower() for name in reader.fieldnames]
|
||||
|
||||
for row in reader:
|
||||
symbol = row.get('symbol', '').strip()
|
||||
cusip = row.get('cusip', '').strip()
|
||||
provider = row.get('provider', 'jpm').strip().lower()
|
||||
|
||||
if symbol and cusip:
|
||||
# Fetch correct template; default to JPM if provider is unknown
|
||||
template = TEMPLATES.get(provider, TEMPLATES['jpm'])
|
||||
url = template.format(cusip=cusip)
|
||||
|
||||
instruments.append({
|
||||
"symbol": symbol,
|
||||
"url": url,
|
||||
"provider": provider
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"CSV Loading Error: {e}")
|
||||
|
||||
return instruments
|
||||
|
||||
# Usage
|
||||
URL_CONFIG = load_instruments_from_csv('instruments.csv')
|
||||
|
||||
@cache.memoize(timeout=3600)
|
||||
def fetch_and_calculate(config):
|
||||
@@ -75,13 +30,38 @@ def index():
|
||||
|
||||
@app.route('/api/summary')
|
||||
def get_summary():
|
||||
engine_base = DataEngine()
|
||||
instruments = engine_base.load_instruments_from_csv('instruments.csv')
|
||||
|
||||
results = []
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
|
||||
futures = [executor.submit(fetch_and_calculate, cfg) for cfg in URL_CONFIG]
|
||||
for f in concurrent.futures.as_completed(futures):
|
||||
if f.result(): results.append(f.result())
|
||||
results.sort(key=lambda x: x['symbol'])
|
||||
for item in instruments:
|
||||
engine = DataEngine(
|
||||
symbol=item['symbol'],
|
||||
url=item['url'],
|
||||
provider=item['provider']
|
||||
)
|
||||
|
||||
metrics = engine.get_local_metrics()
|
||||
|
||||
if metrics and isinstance(metrics, dict):
|
||||
metrics['symbol'] = item['symbol']
|
||||
results.append(metrics)
|
||||
else:
|
||||
# 🔥 FIX: Include the 'error' key so JavaScript hits the Gatekeeper
|
||||
results.append({
|
||||
"symbol": item['symbol'],
|
||||
"last_close": None, # Ensure this is null, not the string "No Data"
|
||||
"error": True
|
||||
})
|
||||
|
||||
return jsonify(results)
|
||||
|
||||
@app.route('/api/sync', methods=['POST'])
|
||||
def run_sync():
|
||||
# ✅ THIS RUNS THE FULL fetch_data() WITH NETWORK ACCESS
|
||||
engine = DataEngine()
|
||||
report = engine.global_sync()
|
||||
return jsonify(report)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, host='0.0.0.0', port=5000)
|
||||
Reference in New Issue
Block a user