Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
36ffd0a
Crawler supports 1.4, 1.6, 1.7 and 3.2 (3.2 is broken)
TomerMe2 Jan 26, 2021
9693ceb
Crawler supports 1.8.4, 3.1 , 7.1
TomerMe2 Feb 2, 2021
7c39c8f
Crawler supports 3.2
TomerMe2 Apr 20, 2021
3119fc9
read missing PDFs script now supports new data
TomerMe2 Apr 27, 2021
a1d77d3
fixed bug when seeing 3.2 in plan's explanation
TomerMe2 Apr 28, 2021
08287d8
force 3.2 to be right after "טבלת שטחים"
TomerMe2 Apr 28, 2021
041a0a4
remove drawings from plans that got them
TomerMe2 May 3, 2021
22c4ea5
Better support in big PDFs
TomerMe2 May 6, 2021
dccbedb
fixed a bug where the future was never resolved in plans without מצב …
TomerMe2 Sep 14, 2021
4cc3fb5
added PDFs for tests
TomerMe2 Dec 6, 2021
8affc3e
added tests
TomerMe2 Dec 27, 2021
72685fa
Merge remote-tracking branch 'origin/master' into read_pdf_tbl_3
TomerMe2 Dec 27, 2021
38f7b9a
removed the geoms dummy
TomerMe2 Jan 3, 2022
d533ba4
add new tables to crawler.test.js
TomerMe2 Jan 10, 2022
f0df18c
added new tables to the test structs
TomerMe2 Jan 10, 2022
0670291
fix size of integer in test tables structs
TomerMe2 Jan 10, 2022
3faf413
add a tbl 3_2 where needed and fixed a column name
TomerMe2 Jan 10, 2022
50791e9
check if moving tests make them appear again
TomerMe2 Jan 10, 2022
159329e
re-enable a lot of tests
TomerMe2 Jan 10, 2022
b48bab8
fix 1.8.4 logic with empty 1.8.3
TomerMe2 Jan 15, 2022
6c303da
update crawler tests to look at tbl 1.8.4
TomerMe2 Jan 15, 2022
11c2b5d
update crawler tests to look at tbl 1.8.4
TomerMe2 Jan 15, 2022
a1d3ea4
update the row id in crawler test
TomerMe2 Jan 15, 2022
6819696
update the row id in crawler test
TomerMe2 Jan 15, 2022
df67298
removed TODO
TomerMe2 Jan 15, 2022
024eefa
changes according to PR
TomerMe2 Jan 24, 2022
4feb0bd
try more timeout on a failed test
TomerMe2 Jan 24, 2022
d965145
bad test into comment
TomerMe2 Feb 14, 2022
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
23,699 changes: 23,698 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

20 changes: 15 additions & 5 deletions server/api/lib/mavat/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,21 @@ const getByPlan = plan =>
jurisdiction: getJurisdictionString(cheerioPage),
files: planFiles,
planStatusList: getPlanStatusList(cheerioPage),
planExplanation: pageInstructions ? pageInstructions.planExplanation : undefined,
chartsOneEight: pageInstructions ? pageInstructions.chartsOneEight : undefined,
chartFour: pageInstructions ? pageInstructions.chartFour : undefined,
chartFive: pageInstructions ? pageInstructions.chartFive : undefined,
chartSix: pageInstructions ? pageInstructions.chartSix : undefined
kindOfPlan: pageInstructions?.textOneFour?.kindOfPlan,
isContainsDetailed: pageInstructions?.textOneFour?.isContainsDetailed,
laws: pageInstructions?.textOneFour?.laws,
permit: pageInstructions?.textOneFour?.permit,
unionAndDivision: pageInstructions?.textOneFour?.unionAndDivision,
planExplanation: pageInstructions?.planExplanation,
chartOneSix: pageInstructions?.chartOneSix,
chartOneSeven: pageInstructions?.chartOneSeven,
chartsOneEight: pageInstructions?.chartsOneEight,
chartsThreeOne: pageInstructions?.chartsThreeOne,
chartsThreeTwo: pageInstructions?.chartsThreeTwo,
chartFour: pageInstructions?.chartFour,
chartFive: pageInstructions?.chartFive,
chartSix: pageInstructions?.chartSix,
chartSevenOne: pageInstructions?.chartSevenOne
});
});

Expand Down
74 changes: 65 additions & 9 deletions server/api/lib/mavat/planInstructions/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
const HummusRecipe = require('hummus-recipe');
const hummus = require('hummus');

const fs = require('fs');
const util = require('util');
const path = require('path');
Expand All @@ -10,18 +13,71 @@ const unlink = util.promisify(fs.unlink);
const mkdir = util.promisify(fs.mkdir);

const processPlanInstructionsFile = async (fileDir) => {
const files = await readdir(fileDir);

const pdfs = files.filter(fileName => fileName.endsWith('.pdf'));
try {
const files = await readdir(fileDir);

if (pdfs.length > 0) {
// we just want the one file, at least for now
const filePath = path.join(fileDir, pdfs[0]);
const data = await extractPdfData(filePath);
return data;
}
const pdfs = files.filter(fileName => fileName.endsWith('.pdf'));

if (pdfs.length > 0) {
const pdfFileName = pdfs[0];
let filePath = path.join(fileDir, pdfFileName);
let fileSizeInMB = fs.statSync(filePath).size / (1024 * 1024);


// the pdf extension that we are using to read table 3.2 is very problematic in memory usage.
// we know that the end of plans can contain heavy drawings.
// if the file is larger than 3 MB, the plan contains these drawings.
// We remove those drawings by looking at the pages size.
// Pages that contains text have a unique size of around 595 on 842 (or filpped)
// If the page is not at that size, it contains a drawing
if (fileSizeInMB > 3) {

const instream = new hummus.PDFRStreamForFile(filePath);
const pdfReader = hummus.createReader(instream);
const pageCount = pdfReader.getPagesCount();

let lastFinePage = 0;
for (let i = 0; i < pageCount; i++) {
const boxForPage = pdfReader.parsePage(i).getMediaBox();

if ((Math.abs(boxForPage[2] - 595) < 3 && Math.abs(boxForPage[3] - 842) < 3) ||
(Math.abs(boxForPage[3] - 595) < 3 && Math.abs(boxForPage[2] - 842) < 3)) { // it can be flipped (table 5 page for example)
continue;
}

// if we got here, this is a page that is not fine
// it's not i - 1 because Hummus Recipe page index is starting at 1 (and this is starting at 0)
lastFinePage = i;
break;
}

return undefined;
instream.close();

if (lastFinePage !== 0) {

// everything but the last page
const pagesToAdd = [...Array(lastFinePage).keys()];

const strippedPdfPath = path.join(fileDir, 'strippedTemp.pdf');
const pdfDoc = new HummusRecipe('new', strippedPdfPath);
pdfDoc
.appendPage(filePath, pagesToAdd)
.endPDF();

filePath = strippedPdfPath;
}
}

const data = await extractPdfData(filePath);
return data;
}

}

catch(e) {
log.error('processPlanInstructionsFile error:\n' + e);
}
};

const clearOldPlanFiles = async (fileDir) => {
Expand Down
62 changes: 62 additions & 0 deletions server/api/lib/mavat/planInstructions/parser/chartOneSeven.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const { chartToArrayBuilder } = require('./chartToArrayBuilder');
const { getFromArr } = require('./parsersUtils');
const log = require('../../../log');

// this function look for the correct columns for the given headers, and returns a factory embedded with these findings
const rowAbstractFactoryChart1_7 = (firstPageOfTable, headersStartIndex) => {
// clean the headers
firstPageOfTable = firstPageOfTable.map(row => row.map(cell => cell.replace(/\n/g, ' ')

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

what happens if firstPageOfTable is undefined or not an array?

@TomerMe2 TomerMe2 Jan 24, 2022

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It's always an array. We can add a check beforehand, but we firstPageOfTable comes from the PDF extension and it's consistent.
Adding a check here will require adding the same check on at least 10 places.

.replace(/ {2}/g, ' ')
.trim()));

if (headersStartIndex === -1) {
// it's an error
log.error('didn\'t find headers for some char 1.6');
return () => {};
}

const header = firstPageOfTable[headersStartIndex];

const kindIdx = header.findIndex(title => title.includes('סוג המסמך'));
const containsIdx = header.findIndex(title => title.includes('תחולה'));
const scaleIdx = header.findIndex(title => title.includes('קנה מידה'));
const numberOfPagesIdx = header.findIndex(title => title.includes('מספר עמודים'));
const editDateIdx = header.findIndex(title => title.includes('תאריך עריכה'));
const editorIdx = header.findIndex(title => title.includes('עורך המסמך'));
const creationDateIdx = header.findIndex(title => title.includes('תאריך יצירה'));
const descriptionIdx = header.findIndex(title => title.includes('תיאור המסמך'));
const includedIdx = header.findIndex(title => title.includes('נכלל בהוראות התכנית'));



return (row) => {
return {
kind: getFromArr(row, kindIdx),
contains: getFromArr(row, containsIdx),
scale: getFromArr(row, scaleIdx),
number_of_pages: getFromArr(row, numberOfPagesIdx),
edit_date: getFromArr(row, editDateIdx),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

snake casing in purpose?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

yup, for the db. By using snake casing we don't need to change the map's keys when inserting into the db.

editor: getFromArr(row, editorIdx),
creation_date: getFromArr(row, creationDateIdx),
description: getFromArr(row, descriptionIdx),
included: getFromArr(row, includedIdx),
};
};
};

const extractChartOneSeven = (pageTables) => {
return chartToArrayBuilder({
pageTables,
rowAbstractFactory: rowAbstractFactoryChart1_7,
startOfChartPred: (cell) => cell.includes('מסמכי התכנית') && cell.includes('1.7'),
offsetOfRowWithDataInChart: 1,
chartDonePredicate: (row) => row.some(cell => cell.includes('כל מסמכי התכנית מהווים חלק בלתי נפרד ממנה')),
getHeaderRowIndex: (page, searchFrom) => page.slice(searchFrom).findIndex(row => row.some(cell => cell.includes('סוג המסמך'))) + searchFrom,
rowTrimmer: (row) => row.map((cell) => cell.replace(/\n/g, ' ').replace(/ {2}/g, ' ').trim()),
identifier: '1.7'
});
};

module.exports = {
extractChartOneSeven
};
55 changes: 55 additions & 0 deletions server/api/lib/mavat/planInstructions/parser/chartOneSix.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const { chartToArrayBuilder } = require('./chartToArrayBuilder');
const { getFromArr } = require('./parsersUtils');
const log = require('../../../log');

// this function look for the correct columns for the given headers, and returns a factory embedded with these findings
const rowAbstractFactoryChart1_6 = (firstPageOfTable, headersStartIndex) => {
// clean the headers
firstPageOfTable = firstPageOfTable.map(row => row.map(cell => cell.replace(/\n/g, ' ')
.replace(/ {2}/g, ' ')
.trim()));

if (headersStartIndex === -1) {
// it's an error
log.error('didn\'t find headers for some char 1.6');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

can you please add planId to the log so we can assign to a process for investigation?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Passing a planId here requires me to pass planId into more software layers (at least 2 more) and change at least 25 lines in at least 15 different files. It will be a bit complicated.
The crawler that runs in production doesn't log the planId if it fails. Can you check the logs to see if we got a lot of "didn't find headers for *" to see if it worth investing in?

return () => {};
}

const header = firstPageOfTable[headersStartIndex];

const planNumberIdx = header.findIndex(title => title.includes('מספר תכנית מאושרת'));
const kindIdx = header.findIndex(title => title.includes('סוג יחס'));
const commentIdx = header.findIndex(title => title.includes('הערה ליחס'));
const yalkootNumberIdx = header.findIndex(title => title.includes('מספר ילקוט פרסומים'));
const yalkootPageNumberIdx = header.findIndex(title => title.includes('עמוד בילקוט פרסומים'));
const dateIdx = header.findIndex(title => title.includes('תאריך'));


return (row) => {
return {
prev_plan_number: getFromArr(row, planNumberIdx),
kind: getFromArr(row, kindIdx),
comment: getFromArr(row, commentIdx),
yalkoot_number: getFromArr(row, yalkootNumberIdx),
yalkoot_page_number: getFromArr(row, yalkootPageNumberIdx),
date: getFromArr(row, dateIdx),
};
};
};

const extractChartOneSix = (pageTables) => {
return chartToArrayBuilder({
pageTables,
rowAbstractFactory: rowAbstractFactoryChart1_6,
startOfChartPred: (cell) => cell.includes('יחס בין התכנית לבין תכניות מאושרות קודמות') && cell.includes('1.6'),
offsetOfRowWithDataInChart: 1,
chartDonePredicate: (row) => row.some(cell => cell.includes('מסמכי התכנית') && cell.includes('1.7')),
getHeaderRowIndex: (page, searchFrom) => page.slice(searchFrom).findIndex(row => row.some(cell => cell.includes('סוג יחס'))) + searchFrom,
rowTrimmer: (row) => row.map((cell) => cell.replace(/\n/g, ' ').replace(/ {2}/g, ' ').trim()),
identifier: '1.6'
});
};

module.exports = {
extractChartOneSix
};
72 changes: 72 additions & 0 deletions server/api/lib/mavat/planInstructions/parser/chartSevenOne.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
const { chartToArrayBuilder } = require('./chartToArrayBuilder');
const { getFromArr } = require('./parsersUtils');
const log = require('../../../log');

// this function look for the correct columns for the given headers, and returns a factory embedded with these findings
const rowAbstractFactoryChart7_1 = (firstPageOfTable, headersStartIndex) => {
// clean the headers
firstPageOfTable = firstPageOfTable.map(row => row.map(cell => cell.replace(/\n/g, ' ')
.replace(/ {2}/g, ' ')
.trim()));

if (headersStartIndex === -1) {
// it's an error
log.error('didn\'t find headers for some chart 3.1_without_change');
return () => {};
}

const header = firstPageOfTable[headersStartIndex];

const phaseIndex = header.findIndex(title => title.includes('מספר שלב'));
const phaseDescription = header.findIndex(title => title.includes('תאור שלב'));
const conditioning = header.findIndex(title => title.includes('התנייה'));

return (row) => {
return {
phase: getFromArr(row, phaseIndex),
phase_description: getFromArr(row, phaseDescription),
conditioning: getFromArr(row, conditioning),
};
};
};


const extractChartSevenOne = (pageTables) => {
const rowsFromBuilder = chartToArrayBuilder({
pageTables,
rowAbstractFactory: rowAbstractFactoryChart7_1,
startOfChartPred: (cell) => cell.includes('7.1') && cell.includes('שלבי ביצוע'),
offsetOfRowWithDataInChart: 1,
chartDonePredicate: (row) => row.some(cell => cell.includes('7.2') && cell.includes('מימוש התכנית')),
getHeaderRowIndex: (page, searchFrom) => page.slice(searchFrom).findIndex(row => row.some(cell => cell.includes('מספר שלב')) &&
row.some(cell => cell.includes('תאור שלב')) &&
row.some(cell => cell.includes('התנייה'))) + searchFrom,
rowTrimmer: (row) => row.map((cell) => cell.replace(/\n/g, ' ').replace(/ {2}/g, ' ').trim()),
identifier: '7.1'
});

const newRows = [];

for (let i = 0; i < rowsFromBuilder.length; i++) {
const currRow = rowsFromBuilder[i];

if (currRow.phase === '' && newRows.length > 0) {
const lastFromNewRows = newRows[newRows.length - 1];

lastFromNewRows.phase_description += ' ' + currRow.phase_description;
lastFromNewRows.conditioning += ' ' + currRow.conditioning;
}

else {
newRows.push(currRow);
}

}

return newRows;

};

module.exports = {
extractChartSevenOne
};
Loading