diff --git a/Services/GoogleSheetService.cs b/Services/GoogleSheetService.cs
index 49463e3..2e8d05a 100644
--- a/Services/GoogleSheetService.cs
+++ b/Services/GoogleSheetService.cs
@@ -28,7 +28,6 @@ namespace Disco.Plugins.ServiceTracker.Services
}
string csvData = null;
-
if (File.Exists(_cachePath))
{
var cacheAge = DateTime.Now - File.GetLastWriteTime(_cachePath);
@@ -46,50 +45,25 @@ namespace Disco.Plugins.ServiceTracker.Services
url = "https://docs.google.com/spreadsheets/d/e/" + id + "/pub?output=csv&gid=" + _config.GId;
else
url = "https://docs.google.com/spreadsheets/d/" + id + "/export?format=csv&gid=" + _config.GId;
-
using (var client = new WebClient())
{
client.Encoding = Encoding.UTF8;
client.Headers.Add("User-Agent", "Mozilla/5.0 DiscoServiceTracker/1.0");
csvData = client.DownloadString(url);
}
-
if (csvData != null && csvData.TrimStart().StartsWith("
- /// Generates a deterministic hash from the row's key fields.
- /// Same timestamp + email + task always produces the same number.
+ /// Hash from RAW cell text - same spreadsheet cells always produce the same number.
+ /// Uses raw strings, never parsed values, so DateTime.Now never contaminates the hash.
///
- private int GenerateStableHash(DateTime timestamp, string email, string task)
+ private int GenerateStableHash(string rawTimestamp, string rawEmail, string rawTask, string rawIssue)
{
- var key = timestamp.ToString("yyyyMMddHHmmss") + "|"
- + (email ?? "").ToLower().Trim() + "|"
- + (task ?? "").ToLower().Trim();
+ var key = (rawTimestamp ?? "").Trim().ToLower() + "|"
+ + (rawEmail ?? "").Trim().ToLower() + "|"
+ + (rawTask ?? "").Trim().ToLower() + "|"
+ + (rawIssue ?? "").Trim().ToLower();
- // Use a simple but stable hash (DJB2-like)
uint hash = 5381;
for (int i = 0; i < key.Length; i++)
hash = ((hash << 5) + hash) + (uint)key[i];
-
- return (int)(hash % 800000) + 100000; // Range: 100000-899999
+ return (int)(hash % 800000) + 100000;
}
- ///
- /// Returns the hash as an ID, resolving collisions by incrementing.
- ///
private int StableIdFromHash(int baseId, HashSet used)
{
int id = baseId;
- while (used.Contains(id))
- id++;
+ while (used.Contains(id)) id++;
return id;
}
+ /// Returns raw untrimmed cell value for hashing. Never returns null - returns empty string.
+ private string RawGet(List fields, int index)
+ {
+ if (index < 0 || index >= fields.Count) return "";
+ return fields[index] ?? "";
+ }
+
public static string MapPriority(string raw)
{
if (string.IsNullOrEmpty(raw)) return "medium";
@@ -200,11 +169,6 @@ namespace Disco.Plugins.ServiceTracker.Services
if (lower.Contains("medium") || lower.Contains("3-5 day")) return "medium";
if (lower.Contains("low") || lower.Contains("1 week") || lower.Contains("week+")) return "low";
if (lower.Contains("scheduled") || lower.Contains("planned")) return "scheduled";
- // Emoji fallback
- if (raw.Contains("\U0001F534")) return "critical";
- if (raw.Contains("\U0001F7E0")) return "high";
- if (raw.Contains("\U0001F7E1")) return "medium";
- if (raw.Contains("\U0001F7E2")) return "low";
return "medium";
}
@@ -218,10 +182,10 @@ namespace Disco.Plugins.ServiceTracker.Services
private DateTime SafeGetDate(List fields, int index)
{
var val = SafeGet(fields, index);
- if (val == null) return DateTime.Now;
+ if (val == null) return DateTime.MinValue;
DateTime dt;
if (DateTime.TryParse(val, out dt)) return dt;
- return DateTime.Now;
+ return DateTime.MinValue;
}
private DateTime? SafeGetDateNullable(List fields, int index)