Sei modifiche chieste in questa sessione, di cui una pesa piu' di tutte le altre. CompletedAuctionsStore rileggeva e rideserializzava l'intero file a ogni chiamata: misurati 72 ms su 5876 record, 3,9 MB. Append pagava quel prezzo piu' la riserializzazione e la riscrittura completa per cambiare un record solo, 120 ms in tutto, e la chiamata arriva da un gestore di eventi di MainWindow, quindi dal thread dell'interfaccia, che restava fermo a ogni asta conclusa. Il recupero delle puntate dei vincitori lo faceva in un ciclo: 4675 aste erano 7,2 minuti di solo rimescolamento JSON e 17,8 GB scritti sul disco. Adesso c'e' una copia in memoria invalidata sulla data di modifica del file, cosi' resta giusta anche se il file viene sostituito da fuori (ripristino di un backup), e un AppendMany che applica molti record con un salvataggio solo, con un indice per id perche' il caso per cui esiste non torni quadratico. Il recupero salva a blocchi di cinquanta e deposita comunque quello che ha in mano quando esce, anche per annullamento: le richieste gia' fatte sono state pagate al server. Misurato sullo stesso file: 95 volte piu' veloce, 7,2 minuti diventano 4,5 secondi, 17,8 GB diventano 0,36 GB. Le altre cinque: - Il setup si chiama AutoBidder_<versione>.exe. - I pulsanti di stop sono rossi, sia quello globale sia quelli per asta nella griglia: erano gli unici tre senza colore mentre avvia e osserva ce l'avevano. - La pulizia dello storico sa togliere le aste seguite solo in parte. Non serve nessun campo nuovo: il prezzo finale in centesimi e' il totale delle puntate dell'asta, quindi il rapporto con i reset osservati dice quanta asta si e' vista, e la regola vale anche sullo storico gia' raccolto. Verificata sui dati veri: su 5876 aste nessuna supera copertura 1,02, che e' quanto ci si aspetta se l'identita' e' giusta. - La griglia ricicla i contenitori di riga invece di ricrearli. - Il costo puntata gia' esisteva col predefinito giusto: al suggerimento e' stato aggiunto perche' 0,20 e' il numero da tenere. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
96 lines
4.5 KiB
C#
96 lines
4.5 KiB
C#
using System;
|
|
using AutoBidder.Models;
|
|
|
|
namespace AutoBidder.Utilities
|
|
{
|
|
/// <summary>
|
|
/// Decide se il conteggio delle puntate del vincitore di un'asta è credibile.
|
|
///
|
|
/// <para>Serve perché una statistica sbagliata è peggio di una statistica assente: un
|
|
/// numero mancante si vede e si ignora, un numero storto entra nelle medie e sposta i
|
|
/// limiti di prezzo senza che nessuno se ne accorga. Meglio scartare un'asta dubbia che
|
|
/// inquinare il prodotto.</para>
|
|
///
|
|
/// <para><b>La verifica.</b> Su Bidoo ogni puntata alza il prezzo di un centesimo, quindi
|
|
/// il prezzo finale in centesimi <i>è</i> il numero totale di puntate dell'asta. Il
|
|
/// vincitore non può averne fatte di più di quante ne conti l'asta intera, e non può
|
|
/// averne fatte zero (l'ultima puntata è per definizione la sua). Due condizioni
|
|
/// semplici, ma prendono tutti i casi in cui il dato arriva monco.</para>
|
|
/// </summary>
|
|
public static class AuctionIntegrity
|
|
{
|
|
/// <summary>Esito del controllo, col motivo dello scarto quando non passa.</summary>
|
|
public readonly record struct Check(bool IsTrustworthy, string? Reason)
|
|
{
|
|
public static Check Ok() => new(true, null);
|
|
public static Check No(string reason) => new(false, reason);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Il conteggio delle puntate del vincitore è utilizzabile per le statistiche?
|
|
/// </summary>
|
|
public static Check CheckWinnerBids(double finalPrice, int? winnerBids)
|
|
{
|
|
if (winnerBids is null)
|
|
return Check.No("puntate del vincitore non registrate");
|
|
|
|
if (winnerBids < 1)
|
|
return Check.No("puntate del vincitore a zero: l'ultima puntata è per forza sua");
|
|
|
|
if (finalPrice <= 0)
|
|
return Check.No("prezzo finale mancante: non c'è nulla con cui confrontare");
|
|
|
|
// Il prezzo in centesimi è il totale delle puntate dell'asta. Si concede un
|
|
// centesimo di tolleranza sull'arrotondamento del prezzo.
|
|
var totalBids = (int)Math.Round(finalPrice * 100) + 1;
|
|
|
|
if (winnerBids > totalBids)
|
|
return Check.No($"puntate del vincitore ({winnerBids}) oltre il totale dell'asta ({totalBids})");
|
|
|
|
return Check.Ok();
|
|
}
|
|
|
|
/// <summary>Comodità: il controllo su un'asta conclusa già registrata.</summary>
|
|
public static Check CheckWinnerBids(CompletedAuctionRecord record) =>
|
|
record == null
|
|
? Check.No("nessun record")
|
|
: CheckWinnerBids(record.FinalPrice, record.WinnerBidsUsed);
|
|
|
|
/// <summary>
|
|
/// Puntate del vincitore da usare nelle statistiche, oppure <c>null</c> se il dato
|
|
/// non supera il controllo. È il punto unico da cui devono passare tutti gli
|
|
/// aggregati: così una regola sola decide cosa entra nelle medie.
|
|
/// </summary>
|
|
public static int? TrustedWinnerBids(CompletedAuctionRecord record) =>
|
|
CheckWinnerBids(record).IsTrustworthy ? record.WinnerBidsUsed : null;
|
|
|
|
/// <summary>
|
|
/// Quanta parte dell'asta è stata davvero osservata, da 0 a 1. <c>null</c> se non
|
|
/// si può dire (prezzo finale mancante).
|
|
///
|
|
/// <para>Sfrutta la stessa identità del controllo qui sopra: il prezzo finale in
|
|
/// centesimi è il numero totale di puntate dell'asta. Confrontarlo con i reset
|
|
/// visti dice, senza bisogno di registrare nient'altro, se l'asta è stata seguita
|
|
/// dall'inizio o presa per strada. Un'asta agganciata a metà ha numeri veri ma
|
|
/// parziali — è il caso peggiore, perché sembra buona.</para>
|
|
///
|
|
/// <para>Il valore è tagliato a 1: sui dati reali (5876 aste) nessun record supera
|
|
/// 1,02, e il poco che sfora è arrotondamento del prezzo, non un'asta osservata
|
|
/// più del possibile.</para>
|
|
/// </summary>
|
|
public static double? ObservedCoverage(double finalPrice, int totalResets)
|
|
{
|
|
if (finalPrice <= 0) return null;
|
|
|
|
var totalBids = Math.Round(finalPrice * 100);
|
|
if (totalBids <= 0) return null;
|
|
|
|
return Math.Min(1.0, Math.Max(0, totalResets) / totalBids);
|
|
}
|
|
|
|
/// <summary>Comodità: la copertura di un'asta già registrata.</summary>
|
|
public static double? ObservedCoverage(CompletedAuctionRecord record) =>
|
|
record == null ? null : ObservedCoverage(record.FinalPrice, record.TotalResets);
|
|
}
|
|
}
|