mirror of
https://github.com/theodore-song/polymarket-analyst.git
synced 2026-08-17 09:38:06 +00:00
Harden cloud state sync
This commit is contained in:
+15
-3
@@ -19,6 +19,10 @@ function agentStateFromItems(items) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function conflictResponse(res, error, current) {
|
||||||
|
return res.status(409).json({ ok: false, error, state: current });
|
||||||
|
}
|
||||||
|
|
||||||
export default async function handler(req, res) {
|
export default async function handler(req, res) {
|
||||||
res.setHeader("Cache-Control", "no-store");
|
res.setHeader("Cache-Control", "no-store");
|
||||||
try {
|
try {
|
||||||
@@ -38,11 +42,19 @@ export default async function handler(req, res) {
|
|||||||
const current = await readJsonBlob();
|
const current = await readJsonBlob();
|
||||||
const currentAgents = agentStateFromItems(current && current.items);
|
const currentAgents = agentStateFromItems(current && current.items);
|
||||||
const incomingAgents = agentStateFromItems(body.items);
|
const incomingAgents = agentStateFromItems(body.items);
|
||||||
if (!body.force && currentAgents && incomingAgents) {
|
if (!body.force && currentAgents) {
|
||||||
const sameHour = currentAgents.last_cycle_hour && currentAgents.last_cycle_hour === incomingAgents.last_cycle_hour;
|
const currentHour = currentAgents.last_cycle_hour || "";
|
||||||
|
const incomingHour = incomingAgents && incomingAgents.last_cycle_hour ? incomingAgents.last_cycle_hour : "";
|
||||||
|
if (currentHour && (!incomingAgents || !incomingHour)) {
|
||||||
|
return conflictResponse(res, "Cloud already has a cycle result; refusing unscheduled local state", current);
|
||||||
|
}
|
||||||
|
if (currentHour && incomingHour && incomingHour < currentHour) {
|
||||||
|
return conflictResponse(res, "Incoming state is older than the shared cloud result", current);
|
||||||
|
}
|
||||||
|
const sameHour = currentHour && currentHour === incomingHour;
|
||||||
const differentRun = currentAgents.last_run && incomingAgents.last_run && currentAgents.last_run !== incomingAgents.last_run;
|
const differentRun = currentAgents.last_run && incomingAgents.last_run && currentAgents.last_run !== incomingAgents.last_run;
|
||||||
if (sameHour && differentRun) {
|
if (sameHour && differentRun) {
|
||||||
return res.status(409).json({ ok: false, error: "This cycle hour already has a cloud result", state: current });
|
return conflictResponse(res, "This cycle hour already has a cloud result", current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const state = { version: 1, updated_at: new Date().toISOString(), items: body.items };
|
const state = { version: 1, updated_at: new Date().toISOString(), items: body.items };
|
||||||
|
|||||||
+38
-12
@@ -634,7 +634,8 @@ async function pushCloudState(force=false){
|
|||||||
const d=await r.json().catch(()=>null);
|
const d=await r.json().catch(()=>null);
|
||||||
if(d&&d.state&&d.state.items)applySyncItems(d.state.items);
|
if(d&&d.state&&d.state.items)applySyncItems(d.state.items);
|
||||||
else await loadAuthoritativeCloudState();
|
else await loadAuthoritativeCloudState();
|
||||||
toast("This hour already ran on another device. Loaded the shared result.");
|
toast("Loaded the newer shared cloud result.");
|
||||||
|
renderAll();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(!r.ok)throw new Error("cloud sync unavailable");
|
if(!r.ok)throw new Error("cloud sync unavailable");
|
||||||
@@ -644,19 +645,33 @@ async function pushCloudState(force=false){
|
|||||||
async function pullCloudState(){
|
async function pullCloudState(){
|
||||||
try{
|
try{
|
||||||
const r=await fetch("/api/state",{cache:"no-store"});
|
const r=await fetch("/api/state",{cache:"no-store"});
|
||||||
if(!r.ok)return false;
|
if(!r.ok)return {ok:false,state:null};
|
||||||
const d=await r.json();
|
const d=await r.json();
|
||||||
if(d&&d.state)return d.state;
|
return {ok:true,state:(d&&d.state)||null};
|
||||||
}catch(e){}
|
}catch(e){}
|
||||||
return null;
|
return {ok:false,state:null};
|
||||||
}
|
}
|
||||||
async function loadAuthoritativeCloudState(){
|
async function loadAuthoritativeCloudState(){
|
||||||
const cloudState=await pullCloudState();
|
const cloud=await pullCloudState();
|
||||||
|
if(!cloud.ok)return {ok:false,loaded:false};
|
||||||
|
const cloudState=cloud.state;
|
||||||
if(cloudState&&cloudState.items&&stateFromSyncItems(cloudState.items)){
|
if(cloudState&&cloudState.items&&stateFromSyncItems(cloudState.items)){
|
||||||
applySyncItems(cloudState.items);
|
applySyncItems(cloudState.items);
|
||||||
return true;
|
return {ok:true,loaded:true};
|
||||||
}
|
}
|
||||||
return false;
|
return {ok:true,loaded:false};
|
||||||
|
}
|
||||||
|
async function refreshFromCloudAndRender(silent=true){
|
||||||
|
const cloud=await loadAuthoritativeCloudState();
|
||||||
|
if(cloud.loaded){
|
||||||
|
renderAll();
|
||||||
|
setStatus("cloud synced",false);
|
||||||
|
if(!silent)toast("Shared cloud state refreshed.");
|
||||||
|
}else if(!cloud.ok){
|
||||||
|
setStatus("cloud sync unavailable",false);
|
||||||
|
if(!silent)toast("Cloud sync is unavailable. Results may be stale until it reconnects.");
|
||||||
|
}
|
||||||
|
return cloud;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Agent mechanics ---------- */
|
/* ---------- Agent mechanics ---------- */
|
||||||
@@ -1086,7 +1101,8 @@ async function backtestWeek(st){
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function runDailyCycle(){
|
async function runDailyCycle(){
|
||||||
await loadAuthoritativeCloudState();
|
const cloud=await loadAuthoritativeCloudState();
|
||||||
|
if(!cloud.ok)throw new Error("Cloud sync unavailable. Refusing to run a local-only cycle.");
|
||||||
let st=loadState();
|
let st=loadState();
|
||||||
const hour=currentCycleHour();
|
const hour=currentCycleHour();
|
||||||
if(st.seeded&&st.last_cycle_hour===hour){
|
if(st.seeded&&st.last_cycle_hour===hour){
|
||||||
@@ -1579,22 +1595,32 @@ $("resetBtn").addEventListener("click",()=>{
|
|||||||
if(!confirm("Reset all ten agents to their $10,000 starting balance?"))return;
|
if(!confirm("Reset all ten agents to their $10,000 starting balance?"))return;
|
||||||
saveState(defaultState());localStorage.removeItem(SUG_KEY);pushCloudState(true);toast("All agents reset.");renderAll();
|
saveState(defaultState());localStorage.removeItem(SUG_KEY);pushCloudState(true);toast("All agents reset.");renderAll();
|
||||||
});
|
});
|
||||||
|
window.addEventListener("focus",()=>refreshFromCloudAndRender(true));
|
||||||
|
document.addEventListener("visibilitychange",()=>{
|
||||||
|
if(!document.hidden)refreshFromCloudAndRender(true);
|
||||||
|
});
|
||||||
|
|
||||||
/* On load: first visit runs a 7-day backtest; afterwards the shared portfolio advances once per hour. */
|
/* On load: first visit runs a 7-day backtest; afterwards the shared portfolio advances once per hour. */
|
||||||
(async function init(){
|
(async function init(){
|
||||||
showTab(location.hash.slice(1)||"overview");
|
showTab(location.hash.slice(1)||"overview");
|
||||||
const cloudLoaded=await loadAuthoritativeCloudState();
|
const cloud=await loadAuthoritativeCloudState();
|
||||||
let st=loadState();
|
let st=loadState();
|
||||||
if(cloudLoaded){
|
if(cloud.loaded){
|
||||||
toast("Cloud state loaded for this device.");
|
toast("Cloud state loaded for this device.");
|
||||||
}else if(st.seeded){
|
}else if(cloud.ok&&st.seeded){
|
||||||
pushCloudState();
|
pushCloudState();
|
||||||
|
}else if(!cloud.ok){
|
||||||
|
setStatus("cloud sync unavailable",false);
|
||||||
}
|
}
|
||||||
renderAll();
|
renderAll();
|
||||||
const btn=$("runBtn");
|
const btn=$("runBtn");
|
||||||
|
if(!cloud.ok){
|
||||||
|
toast("Cloud sync is unavailable. Reconnect before running a cycle.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(!st.seeded){
|
if(!st.seeded){
|
||||||
btn.disabled=true;btn.textContent="Backtesting…";
|
btn.disabled=true;btn.textContent="Backtesting…";
|
||||||
try{await backtestWeek(st);st.date=todayStr();st.last_run=nowIso();st.last_cycle_hour=currentCycleHour();saveState(st);await pushCloudState();renderAll();
|
try{await backtestWeek(st);st.date=todayStr();st.last_run=nowIso();st.last_cycle_hour=currentCycleHour();saveState(st);if(!(await pushCloudState()))throw new Error("Cloud sync unavailable");renderAll();
|
||||||
toast("7-day backtest complete — live tracking begins today.");}
|
toast("7-day backtest complete — live tracking begins today.");}
|
||||||
catch(e){setStatus("backtest error — click Run",false);}
|
catch(e){setStatus("backtest error — click Run",false);}
|
||||||
btn.disabled=false;btn.textContent="Run cycle";
|
btn.disabled=false;btn.textContent="Run cycle";
|
||||||
|
|||||||
Reference in New Issue
Block a user