Files
Mimante/Mimante/Controls/ExportControl.xaml.cs
T
Alby96andClaude Fable 5.1 8b7c39c9e3 Sostituisci le euristiche a soglia con regime e anticipo adattivi; un solo archivio per asta
Le strategie a numero fisso (calore, ritiro morbido, anti-bot, avversari aggressivi,
puntata probabilistica, velocità del prezzo, esaurimento, concorrenza) e le impostazioni
morte (cadenze di polling, finestra critica, database, log avanzati, suggerimenti
sull'anticipo, schede dettagliate) non ci sono più: rigiocate sui dossier non fermavano
una puntata sbagliata senza fermarne anche di giuste, e i loro numeri andavano tarati a
mano. Restano i paletti dell'utente (tetti, budget, fascia oraria) e il duello.

Al loro posto due componenti che imparano dalla sessione in corso, dentro i paletti:
- CompetitionRegime per asta (Calmo / Sfogo / Sondaggio) guidato dal valore atteso
  appreso: tre negativi di fila e si lascia sfogare gli altri, si rientra con una
  puntata di prova dopo abbastanza cicli buoni, e se viene coperta subito la pazienza
  raddoppia. È il "capire da soli quando tornare a puntare".
- LatencyModel per l'anticipo: margine + p99 della latenza misurata adesso, tenuto fra
  LeadMinMs e LeadMaxMs; una puntata tardiva alza il margine subito, venti in tempo lo
  abbassano piano. Un anticipo scritto a mano su un'asta vince sempre (BidLeadIsManual).

Archiviazione: l'archivio mensile aste-AAAA-MM.jsonl (95 MB) duplicava il riepilogo che
ogni dossier ha già in coda. AuctionDetailStore ora legge testa e coda dei dossier, con
cache: 6349 file in 6 s la prima volta. Pulsanti per svuotare le esportazioni e per
azzerare le statistiche voce per voce (lo storico passa dalla copia di sicurezza).

Scheda Apprendimento: sezione "Autonomia sul momento" con il modello di latenza e il
regime di ogni asta seguita. Ml/LEGGIMI.md descrive l'algoritmo per intero.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 00:11:49 +02:00

205 lines
8.4 KiB
C#

using System;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using AutoBidder.Utilities;
namespace AutoBidder.Controls
{
/// <summary>
/// Scheda Esporta: sceglie quali aste finiscono in un unico file e lo scrive.
///
/// <para>Come gli altri pannelli non conosce gli archivi: costruisce un
/// <see cref="AuctionExportFilter"/> e lascia il lavoro a
/// <see cref="AuctionExporter"/>. Qui dentro c'è solo la traduzione fra i campi
/// dell'interfaccia e i criteri — e il conteggio in tempo reale, che serve a non
/// scoprire dopo un minuto di scrittura che il filtro non selezionava nulla.</para>
/// </summary>
public partial class ExportControl : UserControl
{
private string? _lastExportPath;
public ExportControl()
{
InitializeComponent();
Loaded += (_, _) => RefreshPreview();
}
/// <summary>I criteri come sono impostati adesso.</summary>
public AuctionExportFilter BuildFilter() => new()
{
From = FromDatePicker.SelectedDate,
To = ToDatePicker.SelectedDate,
NameContains = string.IsNullOrWhiteSpace(NameFilterTextBox.Text) ? null : NameFilterTextBox.Text.Trim(),
IncludeWon = IncludeWonCheckBox.IsChecked == true,
IncludeLost = IncludeLostCheckBox.IsChecked == true,
IncludeClosed = IncludeClosedCheckBox.IsChecked == true,
OnlyComplete = OnlyCompleteCheckBox.IsChecked == true,
OnlyWithMyBids = OnlyWithMyBidsCheckBox.IsChecked == true,
OnlyWithDossier = OnlyWithDossierCheckBox.IsChecked == true,
MinFinalPrice = ParseNumber(MinPriceTextBox.Text),
MaxFinalPrice = ParseNumber(MaxPriceTextBox.Text),
MaxAuctions = (int)(ParseNumber(MaxAuctionsTextBox.Text) ?? 0),
IncludeEvents = IncludeEventsCheckBox.IsChecked == true
};
/// <summary>
/// Ricalcola quante aste corrispondono. Si fa a ogni modifica dei filtri: sapere
/// prima che il risultato è vuoto — o che sono duemila — evita di scoprirlo a file
/// scritto.
/// </summary>
public void RefreshPreview()
{
try
{
var selected = AuctionExporter.Select(BuildFilter());
MatchCountText.Text = selected.Count == 1 ? "1 asta" : $"{selected.Count} aste";
var complete = selected.Count(r => r.IsComplete);
CompleteCountText.Text = $"{complete} complete";
ExportButton.IsEnabled = selected.Count > 0;
}
catch (Exception ex)
{
MatchCountText.Text = "—";
CompleteCountText.Text = ex.Message;
}
}
/// <summary>Mostra l'esito dell'esportazione appena fatta.</summary>
public void ShowResult(AuctionExportResult result)
{
if (result.Success)
{
_lastExportPath = result.Path;
var size = result.Bytes >= 1024 * 1024
? $"{result.Bytes / 1024.0 / 1024.0:F1} MB"
: $"{Math.Max(1, result.Bytes / 1024)} kB";
ResultText.Text = $"{result.AuctionCount} aste ({result.WithEvents} con eventi), {size} — {result.Path}";
ResultText.SetResourceReference(ForegroundProperty, "Brush.Text");
OpenLastFileButton.Visibility = Visibility.Visible;
}
else
{
ResultText.Text = result.Message;
ResultText.SetResourceReference(ForegroundProperty, "Brush.Warning");
OpenLastFileButton.Visibility = Visibility.Collapsed;
}
}
/// <summary>Percorso dell'ultimo file scritto, per il pulsante "Apri il file".</summary>
public string? LastExportPath => _lastExportPath;
// ── Eventi dell'interfaccia ──────────────────────────────────────
private void PreviewButton_Click(object sender, RoutedEventArgs e) => RefreshPreview();
private void Filter_Changed(object sender, RoutedEventArgs e) => RefreshPreview();
private void Last7Days_Click(object sender, RoutedEventArgs e) => SetPeriod(7);
private void Last30Days_Click(object sender, RoutedEventArgs e) => SetPeriod(30);
private void SetPeriod(int days)
{
FromDatePicker.SelectedDate = DateTime.Today.AddDays(-days);
ToDatePicker.SelectedDate = DateTime.Today;
RefreshPreview();
}
private void ResetFiltersButton_Click(object sender, RoutedEventArgs e)
{
FromDatePicker.SelectedDate = null;
ToDatePicker.SelectedDate = null;
NameFilterTextBox.Text = "";
MinPriceTextBox.Text = "";
MaxPriceTextBox.Text = "";
MaxAuctionsTextBox.Text = "0";
IncludeWonCheckBox.IsChecked = true;
IncludeLostCheckBox.IsChecked = true;
IncludeClosedCheckBox.IsChecked = true;
OnlyCompleteCheckBox.IsChecked = false;
OnlyWithMyBidsCheckBox.IsChecked = false;
OnlyWithDossierCheckBox.IsChecked = false;
IncludeEventsCheckBox.IsChecked = true;
RefreshPreview();
}
private void ExportButton_Click(object sender, RoutedEventArgs e)
=> RaiseEvent(new RoutedEventArgs(ExportRequestedEvent, this));
private void OpenFolderButton_Click(object sender, RoutedEventArgs e)
=> RaiseEvent(new RoutedEventArgs(OpenExportFolderClickedEvent, this));
private void ClearExportsButton_Click(object sender, RoutedEventArgs e)
=> RaiseEvent(new RoutedEventArgs(ClearExportsClickedEvent, this));
private void OpenLastFileButton_Click(object sender, RoutedEventArgs e)
=> RaiseEvent(new RoutedEventArgs(OpenLastFileClickedEvent, this));
/// <summary>
/// Legge un numero accettando sia la virgola sia il punto: chi scrive "12,50" e chi
/// scrive "12.50" intende la stessa cosa, e rifiutarne uno dei due sarebbe pedanteria.
/// </summary>
private static double? ParseNumber(string? text)
{
if (string.IsNullOrWhiteSpace(text)) return null;
var clean = text.Trim().Replace(',', '.');
return double.TryParse(clean, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)
? value
: null;
}
// ── Routed events ────────────────────────────────────────────────
public static readonly RoutedEvent ExportRequestedEvent = EventManager.RegisterRoutedEvent(
"ExportRequested", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ExportControl));
public static readonly RoutedEvent OpenExportFolderClickedEvent = EventManager.RegisterRoutedEvent(
"OpenExportFolderClicked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ExportControl));
public static readonly RoutedEvent OpenLastFileClickedEvent = EventManager.RegisterRoutedEvent(
"OpenLastFileClicked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ExportControl));
public static readonly RoutedEvent ClearExportsClickedEvent = EventManager.RegisterRoutedEvent(
"ClearExportsClicked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ExportControl));
public event RoutedEventHandler ClearExportsClicked
{
add { AddHandler(ClearExportsClickedEvent, value); }
remove { RemoveHandler(ClearExportsClickedEvent, value); }
}
public event RoutedEventHandler ExportRequested
{
add { AddHandler(ExportRequestedEvent, value); }
remove { RemoveHandler(ExportRequestedEvent, value); }
}
public event RoutedEventHandler OpenExportFolderClicked
{
add { AddHandler(OpenExportFolderClickedEvent, value); }
remove { RemoveHandler(OpenExportFolderClickedEvent, value); }
}
public event RoutedEventHandler OpenLastFileClicked
{
add { AddHandler(OpenLastFileClickedEvent, value); }
remove { RemoveHandler(OpenLastFileClickedEvent, value); }
}
}
}