Name Trade Republic uploads by statement period
Trade Republic statements all download as document-N.pdf, which says
nothing about what they hold. traderepublic now implements parser.Namer:
it reads the period from the address block at the top of the first page
("DATE 01 May 2025 - 31 Jul 2026") and names the statement
traderepublic_2025_05_01_2026_07_31, lowercase like the NLB names, so the
folder sorts by start date and two downloads of one statement meet under
one name. The transaction table's own DATE heading never matches, since
no date range follows it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,12 @@ const trAmountSlack = 5
|
||||
// trDateLayout is the date Trade Republic prints, e.g. "05 Jan 2026".
|
||||
const trDateLayout = "02 Jan 2006"
|
||||
|
||||
// trPeriod is the statement period in the address block at the top of the
|
||||
// first page, e.g. "DATE 01 May 2025 - 31 Jul 2026". It names the
|
||||
// statement: Trade Republic's downloads are all called document-N.pdf. The
|
||||
// transaction table's own DATE heading never matches, since no dates follow it.
|
||||
var trPeriod = regexp.MustCompile(`\bDATE\s+(\d{2} [A-Z][a-z]{2} \d{4})\s*[-\x{2013}\x{2212}]\s*(\d{2} [A-Z][a-z]{2} \d{4})`)
|
||||
|
||||
func (p *tradeRepublicParser) Parse(path string, acc *config.Account) ([]RawTxn, error) {
|
||||
text, err := pdfToText(path)
|
||||
if err != nil {
|
||||
@@ -54,6 +60,33 @@ func (p *tradeRepublicParser) Parse(path string, acc *config.Account) ([]RawTxn,
|
||||
return parseTradeRepublicText(text, p.digits)
|
||||
}
|
||||
|
||||
// StatementName names a statement after its period,
|
||||
// traderepublic_YYYY_MM_DD_YYYY_MM_DD, so the folder sorts by when it starts
|
||||
// and two downloads of one statement collide.
|
||||
func (p *tradeRepublicParser) StatementName(path string) (string, error) {
|
||||
text, err := pdfToText(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tradeRepublicStatementName(text), nil
|
||||
}
|
||||
|
||||
func tradeRepublicStatementName(text string) string {
|
||||
m := trPeriod.FindStringSubmatch(text)
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
from, err := time.Parse(trDateLayout, m[1])
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
to, err := time.Parse(trDateLayout, m[2])
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return "traderepublic_" + from.Format("2006_01_02") + "_" + to.Format("2006_01_02")
|
||||
}
|
||||
|
||||
// parseTradeRepublicText holds the whole parser, separated from PDF extraction
|
||||
// so it can be tested against captured pdftotext output.
|
||||
func parseTradeRepublicText(text string, digits int) ([]RawTxn, error) {
|
||||
|
||||
Reference in New Issue
Block a user