From 3988a0a7f50c82866fb3157db22df80fe1c0fc7d Mon Sep 17 00:00:00 2001 From: Tallagrand Thierry Date: Sat, 10 Oct 2026 15:07:37 +0200 Subject: [PATCH] Revue glissante toutes les 15 min (alertes), recharge maximale jugee contre le possible --- HANDOFF.md | 18 +++++++++++- tests/revue-jour/run.mjs | 24 ++++++++++++++-- tools/historise-evcs.mjs | 52 +++++++++++++++++++++++++++++++++ tools/revue-jour.mjs | 62 +++++++++++++++++++++++++++++++++++----- 4 files changed, 146 insertions(+), 10 deletions(-) diff --git a/HANDOFF.md b/HANDOFF.md index a76f43c..3b916f3 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -2361,5 +2361,21 @@ batterie a 32 A et JEEP (branchee 13:01, 52 %) qui ne demarre pas. SOC 47 % a 12 - 14:05 : nouveau branchement (1,8 kW), etiquete VW par defaut et non confirme : probablement la JEEP (18 % a 13:16, depart 15:30) — a confirmer par le proprietaire. +### 10/10 ~15:30 — Revue glissante (accord du proprietaire) +- JEEP du 14:05 confirmee par le proprietaire (concierge 15:00). Pas d'identification automatique : garee a 13:00, + branchee a 14:05 (> 10 min apres la fin de trajet) et aucune publication Uconnect au demarrage de la charge (voiture + endormie ; la reveiller serait une commande, interdite). Le concierge a pose la question : comportement voulu. +- **Revue : recharge maximale jugee contre le POSSIBLE** (`rendementChargeMax`, tools/revue-jour.mjs) : possible = + somme minute par minute de min(7,3 kW, 90 % de l'abonnement - maison) x 0,92, sans le solaire (estimation basse) ; + gain < 50 % -> constat `charge_max_bridee` « a examiner » (sinon `charge_max_midi` info). Le 07/10 (31 -> 35 %, + +1,2 kWh pour ~13 kWh) devient « bridee ». Constat EN COURS `charge_max_bridee_en_cours` apres 60 min de fenetre + (`d.enCours`). Tests revue 17/17. +- **Revue glissante** (service, `revueGlissante`, toutes les 15 min a la minute 11, fenetre 3 h) : constats « a + examiner » nouveaux (`edf_heures_pleines` >= 15 min, `charge_max_bridee[_en_cours]`, `batterie_sous_objectif_*` ; + la voiture bloquee reste a l'arbitre) -> table `revue_alerte` (un episode qui dure prolonge sa ligne) et + `sensor.evcs_alerte_revue` (etat = id ; attributs code, texte, etat_systeme, a, dernieres). SPARK relaie. + Rejeu : 09/10 13:30 -> « recharge maximale BRIDEE : +1,8 kWh pour ~7,3 kWh permis » ; 08/10 00:30 -> EDF en HP ; + 10/10 15:30 -> rien. Service redemarre ~15:30. + --- -*Document mis a jour le : 10/10/2026 (~14:15) — conseiller relecteur de la journee (revue calculee + pistes) ; nuit : voiture qui profite d'EDF ferme pour la batterie, nuit qui prepare le soir les jours annonces faibles ; 08/10/2026 (~00:15) — jour faible : charge maximale en HC de midi (bilan 100 %, ACM plafond + solaire) ; 07/10/2026 (~22:45) — reprise de 16 h verifiee, EDF en HP a 18:03, bilan : reprise du soir et prevision recalee sur la matinee ; bilan HC de midi, reprise du soir apres coupure en pleine chauffe, mesure ECS sur 10 min ; recharge reseau HC : courant calcule (12 kVA) et consigne non tenue ; HC de midi : AUTO ne chauffe pas (BOOST), alerte v2, Jeep (contact, relecture, zone), recharge batterie bridee a 30 A par l'ACM ; jour faible = EDF les deux fenetres HC pour le chauffe-eau, alerte BOOST, Victron Mobile refait ; 1re decision jour faible en reel, mesure ECS reparee, douches 06:00/20:00, Grafana chauffe-eau, tableaux ECS ; chauffe-eau en HC de nuit les jours faibles (decision du bilan, workflow ECS sous controle du depot, VRM, V2.3 desactive) ; garde batterie reseau : voiture bridee a 6 A au lieu d'un arret ; garde-fou concierge 2 h ; allers-retours brefs de la borne retrouves dans l'historique HA (echange VW -> JEEP de 20 s), production 14,5 kWh contre 22 prevus, defaut garde batterie en mode reseau a trancher ; voiture a midi : la batterie n'est plus comptee comme du solaire (EDF automatique les jours de depart si le surplus ne couvre pas) ; nuit 05->06 : JEEP chargee sur EDF en complete (6,32 kWh), budget 0 du a la prevision, Windy/ECMWF contre VRM, a comparer ce soir ; concierge aligne sur la preuve fin de trajet ; a verifier : nuit 05->06 et mardi faible, comparaison VRM/Forecast.Solar ; JEEP identifiee aussi par sa fin de trajet, prevision de la nuit 05->06 ; identification JEEP reussie en reel, correction humaine qui prime (refus supprime), session du 04/10 coupee ; EVCS Master en quatre onglets ; Jeep lue par Uconnect et identifiee seule, capacite JEEP 10 kWh, concierge variante (a) ; releve par photo (SPARK), hass-uconnect en attente ; ecart semaine/week-end mesure (pas de profil week-end la nuit) ; nuit du budget : budget borne au plancher prudent, sans EDF = voiture bloquee meme EDF ferme (batterie seule) ; Energie : sauts corriges ; tableau Energie complete (Sud/Ouest, batterie par integration, borne ; aucun redemarrage HA) ; kilometrage des vehicules (km/kWh, capacite, releves de reference, agent cable 15/15) ; a verifier le 04/10 au matin : nuit du budget JEEP ; concierge : vehicule non confirme demande, heure de saisie ; Grafana vehicules en tableau ; Grafana vehicules : ZERO et Invite ajoutes, session 16:46 reetiquetee ZERO ; prevision SOC 10 h ~35 % ; concierge fiable : niveau note par le script, une ligne, dedoublonnage, simulation par drapeau ; agent : message incompris = question, bourse sur demande explicite (banc final 6/6) ; a verifier : nuit du samedi 03 au dimanche 04/10 (budget < besoin JEEP), cron orange-cloture lundi 05/10 apres 17:40, comparaison VRM / Forecast.Solar lundi 05/10* +*Document mis a jour le : 10/10/2026 (~15:35) — conseiller relecteur de la journee (revue calculee + pistes) ; nuit : voiture qui profite d'EDF ferme pour la batterie, nuit qui prepare le soir les jours annonces faibles ; 08/10/2026 (~00:15) — jour faible : charge maximale en HC de midi (bilan 100 %, ACM plafond + solaire) ; 07/10/2026 (~22:45) — reprise de 16 h verifiee, EDF en HP a 18:03, bilan : reprise du soir et prevision recalee sur la matinee ; bilan HC de midi, reprise du soir apres coupure en pleine chauffe, mesure ECS sur 10 min ; recharge reseau HC : courant calcule (12 kVA) et consigne non tenue ; HC de midi : AUTO ne chauffe pas (BOOST), alerte v2, Jeep (contact, relecture, zone), recharge batterie bridee a 30 A par l'ACM ; jour faible = EDF les deux fenetres HC pour le chauffe-eau, alerte BOOST, Victron Mobile refait ; 1re decision jour faible en reel, mesure ECS reparee, douches 06:00/20:00, Grafana chauffe-eau, tableaux ECS ; chauffe-eau en HC de nuit les jours faibles (decision du bilan, workflow ECS sous controle du depot, VRM, V2.3 desactive) ; garde batterie reseau : voiture bridee a 6 A au lieu d'un arret ; garde-fou concierge 2 h ; allers-retours brefs de la borne retrouves dans l'historique HA (echange VW -> JEEP de 20 s), production 14,5 kWh contre 22 prevus, defaut garde batterie en mode reseau a trancher ; voiture a midi : la batterie n'est plus comptee comme du solaire (EDF automatique les jours de depart si le surplus ne couvre pas) ; nuit 05->06 : JEEP chargee sur EDF en complete (6,32 kWh), budget 0 du a la prevision, Windy/ECMWF contre VRM, a comparer ce soir ; concierge aligne sur la preuve fin de trajet ; a verifier : nuit 05->06 et mardi faible, comparaison VRM/Forecast.Solar ; JEEP identifiee aussi par sa fin de trajet, prevision de la nuit 05->06 ; identification JEEP reussie en reel, correction humaine qui prime (refus supprime), session du 04/10 coupee ; EVCS Master en quatre onglets ; Jeep lue par Uconnect et identifiee seule, capacite JEEP 10 kWh, concierge variante (a) ; releve par photo (SPARK), hass-uconnect en attente ; ecart semaine/week-end mesure (pas de profil week-end la nuit) ; nuit du budget : budget borne au plancher prudent, sans EDF = voiture bloquee meme EDF ferme (batterie seule) ; Energie : sauts corriges ; tableau Energie complete (Sud/Ouest, batterie par integration, borne ; aucun redemarrage HA) ; kilometrage des vehicules (km/kWh, capacite, releves de reference, agent cable 15/15) ; a verifier le 04/10 au matin : nuit du budget JEEP ; concierge : vehicule non confirme demande, heure de saisie ; Grafana vehicules en tableau ; Grafana vehicules : ZERO et Invite ajoutes, session 16:46 reetiquetee ZERO ; prevision SOC 10 h ~35 % ; concierge fiable : niveau note par le script, une ligne, dedoublonnage, simulation par drapeau ; agent : message incompris = question, bourse sur demande explicite (banc final 6/6) ; a verifier : nuit du samedi 03 au dimanche 04/10 (budget < besoin JEEP), cron orange-cloture lundi 05/10 apres 17:40, comparaison VRM / Forecast.Solar lundi 05/10* diff --git a/tests/revue-jour/run.mjs b/tests/revue-jour/run.mjs index d7a40db..d390c5c 100644 --- a/tests/revue-jour/run.mjs +++ b/tests/revue-jour/run.mjs @@ -75,8 +75,9 @@ const sessionMidi = (d, requis) => { const c = constatsRevue(d).constats.find((x) => x.code === 'batterie_sous_objectif_midi'); ok('midi-sous-objectif-0710', !!c && /35 % pour 43,6 % vises, il manquait 2,6 kWh/.test(c.texte), JSON.stringify(c)); } { const d = base(); sessionMidi(d, 100); - const r = constatsRevue(d); const c = r.constats.find((x) => x.code === 'charge_max_midi'); - ok('midi-charge-max-info', !!c && c.gravite === 'info' && /31 % -> 35 % \(\+1,2 kWh\)/.test(c.texte) && !codes(r).includes('sous_objectif'), JSON.stringify(r.constats)); } + const r = constatsRevue(d); const c = r.constats.find((x) => /charge_max/.test(x.code)); + // 10/10 : 31 -> 35 % en 2 h (+1,2 kWh) quand l'abonnement en permettait ~13 kWh = le 07/10, recharge BRIDEE. + ok('midi-charge-max-bridee-0710', !!c && c.code === 'charge_max_bridee' && c.gravite === 'a_examiner' && /31 % -> 35 % \(\+1,2 kWh/.test(c.texte) && !codes(r).includes('sous_objectif'), JSON.stringify(r.constats)); } { const d = base(); sessionMidi(d, 43.6); d.acmA = [{ ts: T('07T12:00:00'), v: 30 }]; d.consigneW = [{ ts: T('07T12:30:00'), v: 5198 }]; @@ -85,5 +86,24 @@ const sessionMidi = (d, requis) => { ok('etat-joint-au-constat', !!c && /a 13:24/.test(c.etat) && /ACM 30 A/.test(c.etat) && /consigne reseau 5198 W/.test(c.etat) && /mode de nuit sans_edf/.test(c.etat) && /session HC engagee oui/.test(c.etat) && /budget voiture sans EDF inconnu/.test(c.etat), c && c.etat); } +// 10/10 : recharge maximale jugee contre le possible. Releve du 09/10 : maison ~2,2 kW, +// 49 -> 62 % (+3,9 kWh) sur 2 h ; possible ~ min(7,3 ; 10,8 - 2,2) x 0,92 x 2 = 13,4 kWh. +{ const d = base(); sessionMidi(d, 100); + d.soc = [{ ts: T('07T12:20:00'), v: 49 }, { ts: T('07T14:20:00'), v: 62 }]; + d.maisonW = [{ ts: T('07T12:00:00'), v: 2200 }]; + const c = constatsRevue(d).constats.find((x) => /charge_max/.test(x.code)); + ok('charge-max-bridee-a-examiner', c && c.code === 'charge_max_bridee' && c.gravite === 'a_examiner' && /13,4 kWh permis/.test(c.texte), c && c.texte); } +{ const d = base(); sessionMidi(d, 100); + d.soc = [{ ts: T('07T12:20:00'), v: 40 }, { ts: T('07T14:20:00'), v: 75 }]; + d.maisonW = [{ ts: T('07T12:00:00'), v: 2200 }]; + const c = constatsRevue(d).constats.find((x) => /charge_max/.test(x.code)); + ok('charge-max-normale-info', c && c.code === 'charge_max_midi' && c.gravite === 'info', c && c.texte); } +{ const d = base(); sessionMidi(d, 100); + d.soc = [{ ts: T('07T12:20:00'), v: 49 }, { ts: T('07T13:30:00'), v: 52 }]; + d.maisonW = [{ ts: T('07T12:00:00'), v: 2200 }]; + d.debut = T('07T11:30:00'); d.fin = T('07T13:30:00'); d.enCours = true; + const c = constatsRevue(d).constats.find((x) => x.code === 'charge_max_bridee_en_cours'); + ok('charge-max-bridee-en-cours', c && /depuis 12:24/.test(c.texte) && c.gravite === 'a_examiner', JSON.stringify(constatsRevue(d).constats.map((x) => x.code))); } + console.log(echecs ? `\n${echecs} ECHEC(S)` : '\nREVUE OK'); process.exit(echecs ? 1 : 0); diff --git a/tools/historise-evcs.mjs b/tools/historise-evcs.mjs index d01653a..f2f6334 100644 --- a/tools/historise-evcs.mjs +++ b/tools/historise-evcs.mjs @@ -1280,6 +1280,55 @@ async function publierRevueHistorique(client) { return 1; } +// --------------------------------------------------------------------- +// REVUE GLISSANTE (10/10/2026, proprietaire) : les memes constats que la revue +// du matin, toutes les 15 min sur les 3 dernieres heures. Un constat « a +// examiner » nouveau devient une ALERTE : sensor.evcs_alerte_revue (etat = id), +// relayee par SPARK sur WhatsApp, historisee dans revue_alerte. La voiture +// bloquee reste a l'arbitre (pas de doublon). Un episode qui dure (EDF en heures +// pleines) ne donne qu'une alerte : on prolonge la ligne existante. +const CODES_GLISSANTS = new Set(['edf_heures_pleines', 'charge_max_bridee_en_cours', 'charge_max_bridee', + 'batterie_sous_objectif_nuit', 'batterie_sous_objectif_midi']); +let revueGlissanteSchema = false; +async function revueGlissante(sql, client, st) { + if (!client) return 0; + if (!revueGlissanteSchema) { + await client.query(`CREATE TABLE IF NOT EXISTS revue_alerte ( + id text PRIMARY KEY, code text, debut timestamptz, fin timestamptz, texte text, etat text, envoyee timestamptz DEFAULT now())`); + revueGlissanteSchema = true; + } + const hc = (id) => (st.get(id)?.state || '').slice(0, 5); + const fenetres = [ + { nom: 'nuit', debut: hc('input_datetime.victron_evcs_hc_night_start'), fin: hc('input_datetime.victron_evcs_hc_night_stop') }, + { nom: 'midi', debut: hc('input_datetime.victron_evcs_hc_midday_start'), fin: hc('input_datetime.victron_evcs_hc_midday_stop') }, + ]; + const d = await donneesRevue(client, fenetres, Date.now(), 180); + d.enCours = true; + d.abonnementVa = Number(st.get('input_number.victron_grid_subscription_va')?.state) || 12000; + const { constats } = constatsRevue(d); + let n = 0; + for (const c of constats) { + if (c.gravite !== 'a_examiner' || !CODES_GLISSANTS.has(c.code)) continue; + if (c.code === 'edf_heures_pleines' && (c.chiffres?.minutes ?? 0) < 15) continue; + const { rows: [deja] } = await client.query( + `SELECT id FROM revue_alerte WHERE code = $1 AND fin >= $2 ORDER BY fin DESC LIMIT 1`, + [c.code, new Date(c.debut - 20 * 60000)]); + if (deja) { await client.query(`UPDATE revue_alerte SET fin = $2, texte = $3 WHERE id = $1`, [deja.id, new Date(c.fin), c.texte]); continue; } + const id = `${c.code}-${new Date(c.debut).toISOString().slice(0, 16)}`; + await client.query(`INSERT INTO revue_alerte (id, code, debut, fin, texte, etat) VALUES ($1,$2,$3,$4,$5,$6) ON CONFLICT DO NOTHING`, + [id, c.code, new Date(c.debut), new Date(c.fin), c.texte, c.etat || null]); + const { rows: hist } = await client.query(`SELECT id, code, texte, to_char(envoyee AT TIME ZONE 'Europe/Paris','DD/MM HH24:MI') a + FROM revue_alerte ORDER BY envoyee DESC LIMIT 10`); + await publierCapteur('sensor.evcs_alerte_revue', id, { + friendly_name: 'Alerte de la revue glissante', icon: 'mdi:alert-outline', + id, code: c.code, texte: c.texte, etat_systeme: c.etat || '', a: new Date().toISOString(), dernieres: hist, + }); + await journaliser(sql, 'revue', `Alerte revue glissante : ${c.code}`, { id, texte: c.texte }); + n++; + } + return n; +} + function decalageParis(jour) { const d = new Date(`${jour}T12:00:00Z`); const h = Number(new Intl.DateTimeFormat('en-GB', { timeZone: 'Europe/Paris', hour: '2-digit', hour12: false }).format(d)); @@ -2669,6 +2718,9 @@ async function cycle(client) { arbitrage: DRY ? 0 : await arbitrer(sql, client, st).catch((e) => { console.error('arbitrage :', e.message); return 0; }), + revueGlissante: (DRY || new Date().getMinutes() % 15 !== 11) ? 0 : await revueGlissante(sql, client, st).catch((e) => { + console.error('revue glissante :', e.message); return 0; + }), revue: DRY ? 0 : await revueDeLaJournee(sql, client, st).catch((e) => { console.error('revue de la journee :', e.message); return 0; }), diff --git a/tools/revue-jour.mjs b/tools/revue-jour.mjs index 5f49075..2293b71 100644 --- a/tools/revue-jour.mjs +++ b/tools/revue-jour.mjs @@ -22,6 +22,27 @@ const SEUIL_RESEAU_W = 100; // bruit de mesure du reseau (comme sensor. const SEUIL_VOITURE_MIN = 10; // voiture bloquee pendant une session HC const SEUIL_OBJECTIF_PTS = 2; // batterie sous son objectif en fin de HC +const CHARGEURS_MAX_W = 7300; // 2 MultiPlus, 140 A (HANDOFF 07/10) +const RENDEMENT_CHARGE = 0.92; +const PART_ABONNEMENT = 0.9; // marge sous l'abonnement, comme l'ACM + +// Recharge maximale de midi (jour faible) : gain reel contre ce que l'abonnement +// permettait, minute par minute (90 % de l'abonnement - maison, borne aux +// chargeurs, rendement), sans compter le solaire (estimation basse du possible). +export function rendementChargeMax(d, t0, t1) { + const s0 = aInstant(d.soc, t0), s1 = aInstant(d.soc, t1); + if (!Number.isFinite(s0) || !Number.isFinite(s1) || t1 <= t0) return null; + const abo = Number(d.abonnementVa) || 12000; + let possibleKwh = 0; + for (let t = t0; t < t1; t += MIN) { + const maison = d.maisonW ? aInstant(d.maisonW, t) : null; + const libre = Math.max(0, PART_ABONNEMENT * abo - (Number.isFinite(maison) ? maison : 0)); + possibleKwh += Math.min(CHARGEURS_MAX_W, libre) * RENDEMENT_CHARGE / 60000; + } + const gainKwh = (s1 - s0) * KWH_PAR_POINT; + return { s0, s1, gainKwh, possibleKwh, bridee: possibleKwh > 1 && gainKwh < 0.5 * possibleKwh }; +} + const ETATS_VOITURE_ABSENTE_OU_PLEINE = /not_connected|done|full|complete|charged/i; function minutesParis(ms) { @@ -178,13 +199,17 @@ export function constatsRevue(d) { let t0 = null; for (let u = t - 4 * 60 * MIN; u < t; u += MIN) if (minutesParis(u) === a) { t0 = u; break; } const soc0 = t0 === null ? null : aInstant(d.soc, t0); + // 10/10 : le gain se juge contre le POSSIBLE. Le 09/10, +3,9 kWh en 2 h pour + // ~8-10 kWh permis : la revue ne disait que « info ». + const r = t0 === null ? null : rendementChargeMax(d, t0, t); constats.push({ - code: 'charge_max_midi', - gravite: 'info', + code: r && r.bridee ? 'charge_max_bridee' : 'charge_max_midi', + gravite: r && r.bridee ? 'a_examiner' : 'info', debut: t, fin: t, texte: `Heures creuses de midi en charge maximale (jour faible) : batterie ${soc0 === null ? '?' : Math.round(soc0)} % -> ${Math.round(soc)} %` - + (soc0 === null ? '.' : ` (+${f1((soc - soc0) * KWH_PAR_POINT)} kWh).`), - chiffres: { socDebut: soc0, socFin: soc }, + + (soc0 === null ? '.' : ` (+${f1((soc - soc0) * KWH_PAR_POINT)} kWh`) + + (r ? ` pour environ ${f1(r.possibleKwh)} kWh permis par l'abonnement${r.bridee ? ', moins de la moitie : recharge BRIDEE' : ''}).` : (soc0 === null ? '' : ').')), + chiffres: { socDebut: soc0, socFin: soc, possibleKwh: r ? Math.round(r.possibleKwh * 10) / 10 : null }, etat: etatA(d, t - 60 * MIN), contexte: (x.raison || '').slice(0, 400), }); @@ -205,6 +230,28 @@ export function constatsRevue(d) { } } + // 4. EN COURS (revue glissante, 10/10) : recharge maximale de midi bridee, jugee + // apres au moins 60 min de fenetre (le constat de fin de fenetre arrive trop tard). + if (d.enCours) { + const midi = d.fenetres.find((f) => f.nom === 'midi'); + const a = midi ? hm(midi.debut) : null, b = midi ? hm(midi.fin) : null; + const m = minutesParis(d.fin); + const x = dec(d.fin); + const requis = aInstant(d.requis, d.fin); + if (a !== null && b !== null && m >= a + 60 && m < b && x && x.session_engagee === true && requis >= 99.5) { + const t0 = d.fin - (m - a) * MIN; + const r = rendementChargeMax(d, t0, d.fin); + if (r && r.bridee) { + constats.push({ + code: 'charge_max_bridee_en_cours', gravite: 'a_examiner', debut: t0, fin: d.fin, + texte: `Recharge maximale de midi BRIDEE : depuis ${hhmm(t0)}, batterie ${Math.round(r.s0)} % -> ${Math.round(r.s1)} % (+${f1(r.gainKwh)} kWh) pour environ ${f1(r.possibleKwh)} kWh permis par l'abonnement.`, + chiffres: { gainKwh: Math.round(r.gainKwh * 10) / 10, possibleKwh: Math.round(r.possibleKwh * 10) / 10 }, + etat: etatA(d, d.fin), contexte: (x.raison || '').slice(0, 400), + }); + } + } + } + constats.sort((a, b) => a.debut - b.debut); const texte = constats.length ? constats.map((c) => `- ${c.texte}`).join('\n') @@ -212,10 +259,11 @@ export function constatsRevue(d) { return { constats, texte }; } -export async function donneesRevue(client, fenetres, finMs) { - // 24 h qui se terminent a la fin de la fenetre HC de nuit (07:24 + 1 min). +export async function donneesRevue(client, fenetres, finMs, dureeMin = 24 * 60) { + // Par defaut 24 h qui se terminent a la fin de la fenetre HC de nuit (07:24 + + // 1 min) ; la revue glissante (10/10) regarde les 2 ou 3 dernieres heures. const fin = finMs; - const debut = fin - 24 * 60 * MIN; + const debut = fin - dureeMin * MIN; const q = async (sql, v) => (await client.query(sql, v)).rows; const d0 = new Date(debut), d1 = new Date(fin); const ms = (r) => new Date(r.ts).getTime();