Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions finances/calculate_file_names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export async function calculateFileNames(
): Promise<Map<string, string> | CalculateFileNamesError> {
const infoByFileName = new Map<
string,
Array<{ filePath: string; hash: string }>
Array<{ filePath: string; pdf: string }>
>();
const fileNameByFilePath = new Map<string, string>();

Expand All @@ -37,16 +37,16 @@ export async function calculateFileNames(
continue;
}

const readPdfResult = await readPdf(filePath);
if (isReadPdfError(readPdfResult)) {
const pdf = await readPdf(filePath);
if (isReadPdfError(pdf)) {
return {
type: "CalculateFileNamesError",
message: readPdfResult.message,
message: pdf.message,
filePath,
};
}

const document = identify(readPdfResult);
const document = identify(pdf);
if (typeof document === "undefined") {
return {
type: "CalculateFileNamesError",
Expand All @@ -62,7 +62,7 @@ export async function calculateFileNames(
};
}

const parseResult = document.parse(readPdfResult);
const parseResult = document.parse(pdf);
if (isDocumentParseError(parseResult)) {
return {
type: "CalculateFileNamesError",
Expand All @@ -75,7 +75,7 @@ export async function calculateFileNames(
const fileName = document.calculateFileName(parseResult as unknown as any);
fileNameByFilePath.set(filePath, fileName);

const newFileNameInfo = { filePath, hash: readPdfResult.hash };
const newFileNameInfo = { filePath, pdf };
const info = infoByFileName.get(fileName);
if (info) {
info.push(newFileNameInfo);
Expand All @@ -85,22 +85,22 @@ export async function calculateFileNames(
}

for (const [fileName, infoList] of infoByFileName.entries()) {
const hashes = new Set<string>();
const pdfs = new Set<string>();
for (const info of infoList) {
hashes.add(info.hash);
pdfs.add(info.pdf);
}

if (hashes.size < 2) {
if (pdfs.size < 2) {
continue;
}

const numberByHash = new Map<string, number>();
for (const hash of hashes) {
numberByHash.set(hash, numberByHash.size + 1);
const numberByPdf = new Map<string, number>();
for (const pdf of pdfs) {
numberByPdf.set(pdf, numberByPdf.size + 1);
}

const fileNameByHash = new Map<string, string>();
for (const [hash, fileNumber] of numberByHash) {
const fileNameByPdf = new Map<string, string>();
for (const [pdf, fileNumber] of numberByPdf) {
const lastPeriodIndex = fileName.lastIndexOf(".");
let numberedFileName: string;
if (lastPeriodIndex < 0) {
Expand All @@ -111,11 +111,11 @@ export async function calculateFileNames(
` ${fileNumber}` +
fileName.substring(lastPeriodIndex);
}
fileNameByHash.set(hash, numberedFileName);
fileNameByPdf.set(pdf, numberedFileName);
}

for (const info of infoList) {
const newFileName = fileNameByHash.get(info.hash);
const newFileName = fileNameByPdf.get(info.pdf);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error message thrown on the subsequent lines (lines 122-123) still references fileNameByHash.get(info.hash). Since fileNameByHash and info.hash have been replaced by fileNameByPdf and info.pdf, please update the error message string to avoid confusion.

if (!newFileName) {
throw new Error(
`internal error kwteqzzx79: ` +
Expand Down
10 changes: 5 additions & 5 deletions finances/commands/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ export async function parseCommand(
return;
}

const readPdfResult = await readPdf(filePath);
if (isReadPdfError(readPdfResult)) {
console.error(`ERROR: ${readPdfResult.message}: ${filePath}`);
const pdf = await readPdf(filePath);
if (isReadPdfError(pdf)) {
console.error(`ERROR: ${pdf.message}: ${filePath}`);
process.exit(1);
}

const document = identify(readPdfResult);
const document = identify(pdf);
if (typeof document === "undefined") {
console.error(`ERROR: unable to identify pdf contents: ${filePath}`);
process.exit(1);
Expand All @@ -34,7 +34,7 @@ export async function parseCommand(
process.exit(1);
}

const parseResult = document.parse(readPdfResult);
const parseResult = document.parse(pdf);
if (isDocumentParseError(parseResult)) {
const { message } = parseResult;
console.error(`ERROR: ${message}: ${filePath}`);
Expand Down
8 changes: 4 additions & 4 deletions finances/commands/print.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ export async function printCommand(
return;
}

const readPdfResult = await readPdf(filePath);
if (isReadPdfError(readPdfResult)) {
console.error(`ERROR: ${readPdfResult.message}: ${filePath}`);
const pdf = await readPdf(filePath);
if (isReadPdfError(pdf)) {
console.error(`ERROR: ${pdf.message}: ${filePath}`);
process.exit(1);
}

if (options?.v) {
console.log(filePath);
}
console.log(readPdfResult.text);
console.log(pdf);
}
9 changes: 2 additions & 7 deletions finances/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@ export interface DocumentParseError {
message: string;
}

export interface DocumentSource {
text: string;
lines: readonly string[];
}

export function isDocumentParseError(e: unknown): e is DocumentParseError {
return (
e !== null &&
Expand All @@ -29,9 +24,9 @@ export interface Document<
> {
readonly type: TypeName;

identify(source: Readonly<DocumentSource>): boolean;
identify(pdf: string): boolean;

parse(source: Readonly<DocumentSource>): ParsedDocumentT | DocumentParseError;
parse(pdf: string): ParsedDocumentT | DocumentParseError;

calculateFileName(pdf: Readonly<ParsedDocumentT>): string;
}
53 changes: 24 additions & 29 deletions finances/document_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,41 @@ interface StringFromLinesOptions {
resultPrefix: string;
}

export function stringFromLines(
lines: readonly string[],
export function stringFromPdf(
pdf: string,
regex: RegExp,
options?: Partial<StringFromLinesOptions>,
): string | DocumentParseError {
for (const line of lines) {
const trimmedLine = line.trim();
const match = trimmedLine.match(regex);
if (!match) {
continue;
}

const matchingString = match[1];
if (typeof matchingString === "undefined") {
throw new Error(
`internal error stdp7xw6rb: regex should have matched line; ` +
`regex=${regex.source}, line=${trimmedLine}`,
);
}
const match = pdf.match(regex);
if (!match) {
return {
type: "DocumentParseError",
message: `regular expression was not matched: ${regex.source}`,
};
}

const resultPrefix = options?.resultPrefix;
if (typeof resultPrefix === "undefined") {
return matchingString;
} else {
return resultPrefix + matchingString;
}
const matchingString = match[1];
if (typeof matchingString === "undefined") {
throw new Error(
`internal error stdp7xw6rb: regex should have had a matching group: ` +
regex.source,
);
}

return {
type: "DocumentParseError",
message: `line not found matching regex: ${regex.source}`,
};
const resultPrefix = options?.resultPrefix;
if (typeof resultPrefix !== "undefined") {
return resultPrefix + matchingString;
} else {
return matchingString;
}
}

export function yyyymmddDateFromLines(
lines: readonly string[],
export function yyyymmddDateFromPdf(
pdf: string,
regex: RegExp,
dateFormat: string,
): string | DocumentParseError {
const dateStr = stringFromLines(lines, regex);
const dateStr = stringFromPdf(pdf, regex);
if (isDocumentParseError(dateStr)) {
return dateStr;
}
Expand Down
29 changes: 12 additions & 17 deletions finances/documents/kitchener_utilities.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import type {
Document,
DocumentSource,
DocumentParseError,
} from "../document.ts";
import type { Document, DocumentParseError } from "../document.ts";
import { isDocumentParseError } from "../document.ts";
import { stringFromLines, yyyymmddDateFromLines } from "../document_utils.ts";
import { stringFromPdf, yyyymmddDateFromPdf } from "../document_utils.ts";

export interface ParsedKitchenerUtilitiesBill {
type: "KitchenerUtilitiesBill";
Expand All @@ -18,30 +14,29 @@ class KitchenerUtilitiesBill implements Document<
> {
readonly type = "KitchenerUtilitiesBill" as const;

identify(source: Readonly<DocumentSource>): boolean {
return source.lines.some((line) => line.includes("UTILITIES@KITCHENER.CA"));
identify(pdf: string): boolean {
const regex = /\s519-741-2626.*utilities@kitchener.ca$/im;
return regex.test(pdf);
}

calculateFileName(pdf: Readonly<ParsedKitchenerUtilitiesBill>): string {
const { statementDate, amountDue } = pdf;
return `${statementDate} Kitchener Utilities Bill ${amountDue}.pdf`;
}

parse(
source: Readonly<DocumentSource>,
): ParsedKitchenerUtilitiesBill | DocumentParseError {
const statementDate = yyyymmddDateFromLines(
source.lines,
/^Statement Date:\s*(\w+\s+\d+\s+\d+)\s+/i,
parse(pdf: string): ParsedKitchenerUtilitiesBill | DocumentParseError {
const statementDate = yyyymmddDateFromPdf(
pdf,
/^Statement Date:\s*(\w+\s+\d+\s+\d+)\s/im,
"MMM D YYYY",
);
if (isDocumentParseError(statementDate)) {
return statementDate;
}

const amountDue = stringFromLines(
source.lines,
/^Pre-authorized Withdrawal:\s*(\d+\.\d+)$/i,
const amountDue = stringFromPdf(
pdf,
/^Pre-authorized Withdrawal:\s*(\d+\.\d+)$/im,
{ resultPrefix: "$" },
);
if (isDocumentParseError(amountDue)) {
Expand Down
50 changes: 20 additions & 30 deletions finances/documents/morgan_stanley.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import type {
Document,
DocumentSource,
DocumentParseError,
} from "../document.ts";
import type { Document, DocumentParseError } from "../document.ts";
import { isDocumentParseError } from "../document.ts";
import { stringFromLines, yyyymmddDateFromLines } from "../document_utils.ts";
import { stringFromPdf, yyyymmddDateFromPdf } from "../document_utils.ts";

export interface ParsedMorganStanleyRelease {
type: "MorganStanleyRelease";
Expand All @@ -22,10 +18,9 @@ class MorganStanleyRelease implements Document<
> {
readonly type = "MorganStanleyRelease" as const;

identify(source: Readonly<DocumentSource>): boolean {
return source.lines.some(
(line) => line.toLowerCase() === "release confirmation",
);
identify(pdf: string): boolean {
const regex = /^release confirmation$/im;
return regex.test(pdf);
}

calculateFileName(pdf: Readonly<ParsedMorganStanleyRelease>): string {
Expand All @@ -45,50 +40,45 @@ class MorganStanleyRelease implements Document<
);
}

parse(
source: Readonly<DocumentSource>,
): ParsedMorganStanleyRelease | DocumentParseError {
const awardId = stringFromLines(source.lines, /^Award ID:\s+([\w\d]+)$/i);
parse(pdf: string): ParsedMorganStanleyRelease | DocumentParseError {
const awardId = stringFromPdf(pdf, /^Award ID:\s+([\w\d]+)$/im);
if (isDocumentParseError(awardId)) {
return awardId;
}

const settlementDate = yyyymmddDateFromLines(
source.lines,
/^Settlement Date:\s*(\d+-\w+-\d+)$/i,
const settlementDate = yyyymmddDateFromPdf(
pdf,
/^Settlement Date:\s*(\d+-\w+-\d+)$/im,
"DD-MMM-YYYY",
);
if (isDocumentParseError(settlementDate)) {
return settlementDate;
}

const vestedValue = stringFromLines(
source.lines,
/^Total Gain.*:\s*(\$[\d,.]+)$/i,
);
const vestedValue = stringFromPdf(pdf, /^Total Gain.*:\s*(\$[\d,.]+)$/im);
if (isDocumentParseError(vestedValue)) {
return vestedValue;
}

const saleAmount = stringFromLines(
source.lines,
/^Sale PricexQuantity Sold:\s*\((\$[\d,.]+)\)$/i,
const saleAmount = stringFromPdf(
pdf,
/^Sale PricexQuantity Sold:\s*\((\$[\d,.]+)\)$/im,
);
if (isDocumentParseError(saleAmount)) {
return saleAmount;
}

const sharesSold = stringFromLines(
source.lines,
/^Quantity Sold:\s*\((\d+\.\d{3})0*\)$/i,
const sharesSold = stringFromPdf(
pdf,
/^Quantity Sold:\s*\((\d+\.\d{3})0*\)$/im,
);
if (isDocumentParseError(sharesSold)) {
return sharesSold;
}

const salePrice = stringFromLines(
source.lines,
/shares at (\$\d+\.\d{4})0* per share/i,
const salePrice = stringFromPdf(
pdf,
/\sshares at (\$\d+\.\d{4})0* per share\s/im,
);
if (isDocumentParseError(salePrice)) {
return salePrice;
Expand Down
Loading
Loading