bid-deadline-check
Read a government solicitation (RFP, RFQ, IFB; PDF or text) and get back:
- Deadlines with date, time and timezone exactly as written: questions due, proposal due, revised/final proposals, registration, site visit, intent to bid, orals. Each date is bound only to the time and zone written with it, several deadlines on one line are all kept, and anything unresolved (no time, no zone, "EOD", "midnight", an impossible date, a date described as withdrawn, cancelled, superseded or no longer in effect, a date taken from a "received after" clause) is flagged
- An outcome, READ or NEEDS REVIEW. READ is allow-list based: every sentence must be in a form this tool recognizes. Each sentence that is not, and each sentence about a deadline that could not be resolved (a change with no new date read, a cancellation, TBD, a date form it does not read), is listed with its exact text and a reason, and the deadlines it may change are marked not actionable as stated
- Page limits per volume
- Format rules: font, margins, paper size, spacing, file format
- Forms and clauses: SF-1449, SF-33, SF-30, FAR 52.xxx, DFARS 252.xxx
- Sections and volumes referenced (L, M, Volume I/II/III)
- "Shall / must" sentences aimed at the offeror, found by keyword rules, including sentences wrapped across lines. Up to 1000 characters they are kept whole; longer ones, and sentences interrupted by a list-item or heading line, are shown as flagged excerpts with their source lines
- A compliance checklist (text, JSON, CSV or Markdown)
- Warnings for what is missing or needs a human check
It is deterministic: regex and rules only. No LLM, no network, nothing leaves your machine.
Quickstart
pip install "bid-deadline-check[pdf]"
bid-deadline-check solicitation.pdf
bid-deadline-check solicitation.pdf -f csv > compliance-matrix.csv
bid-deadline-check solicitation.txt -f md
bid-deadline-check solicitation.txt -f json
Abridged example output (from tests/fixtures/rfp_sample.txt, a synthetic RFP; most sentences of a real solicitation
are outside the recognized forms, so NEEDS REVIEW is the normal result). Produced by the 0.2.0 build:
OUTCOME: NEEDS REVIEW (11 sentence(s) to review, 14 warning(s))
NEEDS REVIEW (read these in the source)
? [not_recognized] line 1: SOLICITATION NO. EXAMPLE-26-R-0001 (SYNTHETIC SAMPLE) (not recognized: not a supported sentence form: ...)
...
? [not_recognized] line 6: L.1 Questions regarding this solicitation are due no later than October 20, 2026 at 2:00 PM Eastern Time. (not recognized: not a supported deadline form: Questions regarding this solicitation are due no later than <D> at <T> <Z>.)
? [not_recognized] line 8: L.3 A pre-proposal conference (site visit) will be held 10/27/2026 at 10:00 AM local time; registration is due by 10/23/2026. (not recognized: a deadline that is not complete)
...
? [not_recognized] line 21: The Government will evaluate proposals in accordance with Section M. (not recognized: not a supported sentence form: ...)
DEADLINES
[questions_due] October 20, 2026 2:00 PM Eastern Time (line 6) NOT ACTIONABLE AS STATED: needs review
[proposal_due] November 5, 2026 11:00 AM EDT (line 7) NOT ACTIONABLE AS STATED: needs review
[site_visit] 10/27/2026 10:00 AM local time (line 8) CHECK: zone_unanchored NOT ACTIONABLE AS STATED: needs review
[registration] 10/23/2026 (no time) (no timezone) (line 8) CHECK: no_time NOT ACTIONABLE AS STATED: needs review
PAGE LIMITS
Volume I Technical: 20 pages (line 13)
Volume II Past Performance: 10 pages (line 14)
...
What --strict means
Every run reports an outcome: READ or NEEDS REVIEW (outcome in JSON, the first line of text output).
--strict exits 2 unless the outcome is READ, and 1 when the input cannot be read.
Since 0.2.0, READ is allow-list based: every sentence of the input must be positively recognized (next
section), and in addition none of the checks below may fire. An unrecognized sentence is a review item with reason
not_recognized, its exact text and a detail saying what is not recognized (for most sentences, the sentence as it
was compared with the templates; otherwise the character, marker or deadline problem found), and it
holds every deadline, because the tool cannot tell which deadline it changes. The cost is deliberate: almost any
real solicitation, and most ordinary prose, needs review. READ is meant for short, plainly written deadline notices in
the supported forms; on anything else, NEEDS REVIEW is the expected result and says only that a person must read it.
Recognized sentences (the allow-list)
A sentence is recognized only if one of these holds:
-
it is, whole, an unaltered standard clause (below), the SF-30 Block 11 line checked "[X] is not extended", or one of the fixed rule sentences "Late (Oral, Verbal, Faxed, Facsimile, Telephonic, Telephone) proposals (offers, bids, quotes, quotations) will (shall) not be accepted (considered).", "No proposals will be accepted by fax (facsimile, telephone).", "The Government reserves the right to cancel this solicitation." and "The Government may cancel this solicitation at any time.";
-
it is a late-receipt rule, whole, whose time equals that of a complete proposal deadline in the same statement, and whose zone, when it states one, equals that deadline's zone ("Proposals due November 5, 2026 at 5:00 PM ET; proposals received after 5:00 PM ET will be rejected."; on a line of its own it needs review);
-
it holds exactly one complete deadline (date, time and zone, nothing flagged) and, with that date, time and zone in place, is one of these forms, with a label that names that deadline's own kind: " ." or ": " (also with "|" or "-" after the label), " are (is, must be, shall be) due (submitted, received, delivered) [electronically | via email] [to
] [no later than | not later than | NLT | on or before | by | on] .", "Offerors (quoters, vendors, bidders, respondents) must (shall) submit [their | all] ... .", a table row "| | | |", a capitals label above a date line, and, for the proposal deadline, "[This] RFQ (RFP, IFB, solicitation) closes [on] ." For the proposal deadline is proposal(s), offer(s), quote(s), quotation(s) or bid(s), optionally followed by "due date", "due", "deadline", "closing date" or "submission deadline", with an optional "the"; for questions, question(s), inquiry or inquiries; for the other kinds, their own names (site visit, pre-proposal conference, oral presentations, registration, notice of intent, final proposal revisions, BAFO, FPR, revised proposals). "Responses", "submissions", "applications" and a bare "Closing date:" or "Due date:" are not labels: they also name answers to questions, questionnaire returns and other events. Here is the date with its time and zone in the usual orders (", at, @, |, -, /" between them; "hrs", "hours", "sharp" or "prevailing" before the zone; the zone repeated in parentheses; an ISO 8601 date-time). Nothing else may remain in the sentence: no second date or time, no other zone, no "or", no lot. A deadline sentence is not subject to the lower-case and joining-word rule below (its form is fixed whole, and no form begins with a joining word); -
it is a zone statement "All times (in this solicitation) are ." (r6 compares it with every deadline's zone);
-
it has no date, time, zone or date-like value, does not begin in lower case or with a joining or condition word ("or", "and", "but", "if", "unless", ...), and, with a leading list marker set aside, matches one of these templates whole (case aside;
core.PLAIN_FORMSandcore.LIST_FORMS):- page limits: " shall (must) not exceed pages.", " is (are) limited to pages.", " page limit: pages", where the subject is a proposal, volume, response, narrative or approach (optionally technical, price, cost, management, past performance or staffing) or "Volume I" to "Volume X" (or 1 to 10) with an optional title, and is a number of 1 to 3 digits, or one of the words one to twelve, fifteen, twenty, twenty-five, thirty, forty and fifty, with an optional "(1 to 3 digits)";
- submission: " shall (must) be submitted electronically (by email, via email) [to | to the contracting officer [at ]].", "... be submitted in PDF format (as a single PDF file) [by email].", "... be submitted through .", "Submit [by email] to .", "The technical (price, cost, management, past performance) proposal (volume) shall be submitted in (as) Volume .", "All proposals shall (must) be received by the [proposal] deadline (due date, closing date).";
- questions: "Questions shall (must) be submitted by email (via email, in writing) [to | to the contracting officer [at ]].", "Questions shall (must) be directed to the contracting officer [at ].", "The offeror shall direct all questions to the point of contact.";
- format: "Pages shall be 8.5 x 11 inches [with 1-inch margins].", "Text shall use [12-point] Times New Roman (Arial, Calibri) font[, single-spaced].", and "The offeror shall include its UEI and CAGE code on the cover page.";
- headings and the volume-list opener, only as a whole statement (a line of their own, never beside another sentence): "The proposal (offer, quote) shall consist of the following volumes:", "Section A" to "Section M" (optionally "- Instructions to Offerors" or "- Evaluation Factors [for Award]"), "Instructions to Offerors", "Evaluation Factors", "Table of Contents", "Introduction", "Scope", "Submission Instructions", "Proposal Format". Headings that limit what follows to one part of the proposal ("Price", "Past Performance", "Technical Approach", "Volume II", "Volume II - Price") are not templates: a document that has one needs review.
Here is proposals, offers, quotes, quotations or bids (with an optional "all" or "the"), and
an email address or a bare host name (an optional "https://" or "www.", a name ending in .gov, .mil, .com, .org, .net or .us, and an optional "/"), with no digit, no path or query, and none of these stems anywhere inside it: time words (due, date, late, early, after, before, until, next, last, week, day, month, year, hour, time, now, ...), status and change words (cancel, void, extend, amend, amdt, postpon, clos, open, new, revis, chang, hold, pause, tbd, not, no, only, except, if, ...), event words (oral, present, visit, conferen, meet, award, notice, brief, ...) and month abbreviations (`core.ADDRESS_STEMS` has the full list). A volume or section and its title may be separated by "-", ":", an en dash or an em dash. -
it is punctuation only and a table or separator rule ("---", "|---|---|"); other punctuation-only sentences ("( )", "* * *") need review.
There is no open vocabulary: the two code reviews of an earlier vocabulary-based version showed that ordinary words
could still move or qualify a submission ("Proposals shall be submitted with the questions.", "Proposals shall be
submitted at the presentation."), so the owner chose this closed list. A template with any word changed or added needs
review. Also unrecognized: a mark of omitted or struck text ("...", an ellipsis character, "[...]", "* * *") or a
literal template slot ("", "", "", "") anywhere in a sentence; and, checked on each raw line before any
whitespace is collapsed or zero-width character removed, any non-ASCII character other than U+2018, U+2019, U+201C,
U+201D (typographic quotes), U+2013, U+2014 (en and em dash), U+2022, U+25CF, U+25AA (bullets), U+2026 (ellipsis,
itself an omission mark), U+00A7 (section sign), U+2713, U+2714 and U+2612 (check marks), which covers other scripts,
full-width and look-alike letters, Unicode spaces (U+00A0, U+1680, U+3000, ...), zero-width characters (U+200B to
U+200D, U+2060, a U+FEFF byte-order mark) and invisible format characters; an ASCII control character other than tab
(U+0000 to U+001F, including U+001F); a line separator other than a newline or carriage return
(\x0b, \x0c, \x1c to \x1e, U+0085, U+2028, U+2029: the line it ends is reviewed); a 12-hour clock time without AM or PM in a deadline ("5:00 ET"); and a sentence
at the edge of a statement cut by a size bound or by an interrupting list or heading line, unless it is a heading or
the volume-list opener standing as a whole statement and the cut is not a size bound. After an unaltered standard clause that ends "; or", that "or" alone is recognized.
The checks that still give their own reasons:
- a proposal due date and a questions-due date were found, neither negated or withdrawn, and page limits were found;
- every extracted deadline has a valid calendar date in 1900 to 2100 whose weekday, if written, matches; a clock time (or "noon"); and one valid zone (an IANA name the zone database knows, or a UTC offset from UTC-12:00 to UTC+14:00), all bound to the same clause, with no "EOD", "midnight" or close-of-business marker, no two-digit year and no second, different zone written for the same time ("3:00 PM PST / 3:00 PM EST", "4:00 PM MT, 4:00 PM PT"), and no other clock time in the sentence, also before the date ("By 3:00 PM ET, proposals are due ... at 5:00 PM ET"; a time named for a briefing or other event just before it, or across a ";", does not count);
- no deadline is flagged as withdrawn, superseded, paused or on hold, as replacing an earlier date ("New date:"), as quoted from or labeled as an earlier text ("the RFP stated:", "Amendment 0001 read:", "former deadline", "Original Proposal Due", "Proposals were due", "Was:"), as possibly an issue or announcement date, as of uncertain kind (a date that names a deadline or a submission but no kind is read as a proposal deadline of uncertain kind, so it conflicts with an old cutoff), as sharing its sentence with another clock time or an unread date, as taken from a "received after" clause, or as of uncertain kind;
- no kind of deadline has two different values, and none is withdrawn or quoted from an earlier text in one place and written as current in another;
- no sentence about a deadline was left unresolved (the review items below), and no unsupported date form was found;
- every deadline is actionable; and
- the input decoded without replacement characters, and extraction did not stop at its per-document limits.
NEEDS REVIEW lists each unresolved candidate in needs_review with its first and last line, a reason code and its
exact source text (up to 600 characters). Each deadline carries status: actionable, or needs_review with the
reasons in review. A deadline is held (not actionable as stated) when it has any of the flags above, when another
value of its kind conflicts with it or is withdrawn, or when an unresolved item names its kind, names no kind at all,
or uses a generic deadline word ("the deadline", "the closing date"), so a
recognized old cutoff is never presented as actionable while a change to it is unresolved. The text output prints
these as "NOT ACTIONABLE AS STATED"; the checklist (md, csv) lists the review items first.
| Reason code | A sentence that names a deadline and ... |
|---|---|
solicitation_cancelled_or_draft |
(any sentence) cancels, withdraws, suspends, closes or puts on hold the whole solicitation or acquisition ("RFP cancelled.", "The agency withdrew the RFP.", "NOTICE OF CANCELLATION"), or marks it as a draft, notional or not yet requested; every deadline is held. "whether", "if" or "in the event" just before it, or a reserved right ("the right to cancel this solicitation"), does not count |
schedule_changed |
(any sentence) changes the schedule, the timeline or all dates at once ("The schedule is extended by two weeks", "all subsequent dates are postponed", "The timeline slips two weeks"); every deadline is held |
change_not_read |
changes it (a change verb, negated or not, "shortened", "waived", an imperative such as "Alter the closing time" or "Disregard the proposal due date", "reopens", "is now", "one week later than stated", a checked SF-30 "[X] is extended") with no replacement date read; also any sentence that corrects an earlier text ("should read", "is corrected to", "typographical error") and gives a date or time, a "New date:" label or "extends it to ", a change whose subject is only a pronoun ("It is extended one week"), a sentence that begins with the change ("extended by one week." after a blank line, "(Postponed.)"), and a stated interruption or emergency that triggers the FAR emergency extension |
deadline_cancelled_or_suspended |
cancels, withdraws, rescinds, revokes, suspends or postpones it |
deadline_to_be_determined |
says it is TBD, T.B.D., to be determined, pending, N/A or will be set later (also in a table row, "Revised Proposal Due | TBD"); also any sentence saying the schedule, timeline or dates are TBD or TBA |
deadline_negated |
says it is no longer due, no longer applies, or that proposals are no longer (or not) accepted or required, or that no proposals will be accepted. Not counted: a qualifier right after it ("after", "later than", "past", "beyond", "before", "by", "through", "via", "unless", "if", "in paper", "electronically"), which over-matches ("by any means" is missed), or an adjective right before the noun that names only some proposals ("Late", "Oral", "Faxed", "Unsigned", "Incomplete"; not after "and" or "or") |
time_without_date |
gives a clock time but no date that was read (a restatement of a time already read in the same statement is not one) |
date_not_read_as_deadline |
contains a date that was not read as a deadline and is not an issue date |
unread_date_value |
contains a date-like value this tool does not read ("2026.11.5", "20261112", "2026 11 12", "Friday", "tomorrow", "next week", "Thanksgiving", "end of month") |
zone_statement |
sets, changes or redefines the zone for all times ("All times are Central Time, not Eastern Time", "Times in this RFP are Central Time", "deadlines are in Central Time", "All times are changed from Eastern Time to Central Time", "Time zone for all deadlines: Mountain", ""ET" in this RFP means Central Time") in a way that conflicts with a deadline's zone or rules one out. A deadline gets one conflict code however many statements there are, and at most 50 statements are listed one by one |
unsupported_date_form |
(any line) holds one of the screened malformed forms listed below |
not_recognized |
(any sentence, since 0.2.0rc7) is not one of the recognized forms above; detail says what is not recognized (usually the sentence as compared with the templates); every deadline is held, and each deadline carries one not_recognized:N code after its other reasons |
What names a deadline: the deadline words "due", "deadline", "closing", "close date", "cut-off", "no later than", "not later than", "NLT", "on or before"; "receipt of offers" (proposals, bids, quotes, responses); "time" or "date" "for receipt"; "submission", "proposal", "offer" or "response" followed by date, time, deadline, window or period; "period" or "window for submission"; "submit ... proposals" (offers, bids, quotes, responses); and "proposals" (offers, bids, quotes, responses) followed within three words by a form of accept, receive, submit, deliver or "due". A sentence without any of these is not reviewed.
Every sentence is also checked against the allow-list above, whether or not it uses any of these words.
READ does not mean that every deadline or requirement in the document was found, that the extracted values are what the agency intended, that the input is the whole solicitation with every amendment, or that a submission would be timely. With the allow-list, READ is defensible only for input written entirely in the supported forms; the remaining gaps are listed under "Known misses". Read the solicitation and every amendment yourself.
Supported scope
- Dates read: a month name first ("November 12, 2026", "Nov. 12, 2026", "November 12th, 2026", "November 12 2026"),
day first ("12 November 2026", "12th Nov. 2026", "12 November, 2026"),
11/12/2026(month first),2026-11-12, each with an optional weekday ("Thursday, November 12, 2026");11/12/26is read and flagged as a two-digit year. - Times read: "5:00 PM", "5 p.m.", "17:00", "17:00:30", "1700 hours", "noon"; markers such as "close of business" are kept and flagged. Zones read: ET/EST/EDT and the other US abbreviations, "Eastern Time" and the other US names, UTC/GMT with or without an offset, ISO 8601 offsets, "Z", "Zulu", IANA names, and "local time" (flagged).
- screened, and reported as unsupported date forms:
111/05/2026,5November 2026,November 12,2026,12-NOV-2026,11-12-2026,Nov-12-2026,03 NOV 26,November 12, 26(not when the two-digit year is followed by a comma, a letter, a digit,_, a dot and a digit, or "and", "or", "through", "thru", "to", "&", "-" or an en dash, as in "November 3, 10 and 17", "November 12, 26, 5:00 PM" or "November 12, 26 - 5:00 PM"),November, 12, 2026andNovember,12,2026, an ISO date with exactly one component of the wrong length (2026-11-005,2026-111-05,20260-11-05; not2026-111-005), a year-first date with.whose month (01 to 12) and day (01 to 31) have two digits each (2026.11.12; not2026.13.05), a year-first date with/whose day has two to four digits, whatever its values (2026/11/12,2026/13/05and3026/11/12, through the111/05/2026form), a year-first date with a one-digit month or day (2026-11-5,2026/11/5,2026.1.15) only on a line that itself contains "due", "deadline", "no later than", "not later than", "NLT", "closing", "closes", "close", "closed", "on or before" or "cut-off" (so not "received by 2026-11-5", and not when that word ends the previous line),12.11.2026(not directly after a letter, digit,_or dot), a date followed by more slash parts (11/12/2026/2027), or a letter typed for a digit as in2O26orNovember l2, 2026. Only these forms are screened, and in the ISO forms, the dotted forms and the one-digit form the year must look like 19xx or 20xx; other forms, such as12.11.26, are not, and one of them beside a valid deadline disappears. No deadline's date or zone runs into the text after it (November 5, 2026xandNovember 5, 2026_draftare read as November 5 and flagged;17:00:00ZdraftandUTC+05:30junkare read asZandUTC+05:30and flagged); - Standard clause text that names a deadline and a time without changing it is not reviewed when it matches the published text exactly (case and spacing aside, including across a line wrap): FAR 52.212-1(f)(1) and 52.215-1(c)(3)(i) (default time for receipt), 52.212-1(f)(2)(i)(A) and 52.215-1(c)(3)(ii)(A)(1) (electronic commerce receipt), 52.212-1(f)(4) and 52.215-1(c)(3)(iv) (emergency extension), as published on acquisition.gov (52.212-1 SEP 2023, 52.215-1 NOV 2021). Any other wording, an altered time, an added change or a negation is reviewed like any other sentence.
- Not in scope: converting zones (two notations of one moment, "17:00 UTC-4 (ET)", need review), holidays and business days, deadlines stated relative to another event ("30 days after award"), text that is not English, scanned PDFs without a text layer, and tables whose cells are split across lines in an order that separates a label from its date.
A change verb counts even when it is negated ("The proposal due date is not changed." needs review): an exemption for negated changes was tried in this release and removed, because it could not be made safe when the real change is in the next sentence ("... will not be extended. Offerors have an additional week."). Hard, paper, printed and physical copies name a proposal submission, so a separate hard-copy deadline conflicts with the electronic one.
Known false failures (NEEDS REVIEW although nothing is wrong). Since 0.2.0rc7, every sentence outside the supported
forms, which is most ordinary prose: numbers written as words ("three volumes"), names ("Point of Contact: Jane Doe"),
counts ("5 copies"), "As amended, proposals are due ...", "As previously announced, ...", periods of performance,
award dates, office hours, version numbers, a label on one line with its date on the next sentence, and any plain
sentence that is not one of the templates. 76 earlier test texts that read cleanly before now need review; each is listed, with the exact
item it now produces, in tests/r7_false_failures.json. Also, from earlier releases: the same time restated in another zone or notation
("5:00 PM ET (2:00 PM PT)", "17:00 UTC-4 (ET)"); office hours in a deadline's sentence; numbers written like a
screened date form (Version 1.2.2026, "In May, 12 2026 vehicles were inspected", 2026-111-05, JUN-12-1234, and
on a deadline line RFP 2026-1-05); a validity or performance-period change in a sentence that names a deadline; a negated change ("The proposal due date
is not changed.", "will not be extended for traffic delays");
hedged, conditional or hypothetical sentences that name a deadline and use change wording ("Requests for extension
of the proposal due date will not be granted", "Proposals may be modified or withdrawn at any time before the closing
date", "The County may extend the deadline by addendum"); a zone written as "Eastern" alone, "Arizona time" or
"Atlantic Standard Time" (no zone is read); a deadline with no zone in a document whose zone statement sets one (the
statement is not applied); a four-digit time without "hours" ("by 1600 CST"); dates written in words or as
14-JAN-2027; a notice of intent to bid or a second round of questions read as a second deadline of the same kind; a standard clause in any wording other than the published one; and an announcement verb in a deadline's
clause ("Questions are due as communicated..." stays READ only because "as" marks a reference).
Known misses (READ although a careful reader would not act). The 41 wordings that 0.2.0rc6 recorded as read cleanly ("The date above is no longer valid.", "Amendment 0002 grants a two-week extension.", "This RFP is no longer active.", "Time Zone: Central", ...) are now caught and are ordinary tests. What the allow-list cannot see: a deadline date that has already passed (the tool does not know today's date); an amendment that is not in the input; a supported-form deadline that is itself wrong; and a change written entirely in the recognized forms above. The forms were reviewed one by one and attacked in code review; every input found is now a test, and further ones may exist. Measured once, on an independent held-out set of 200 synthetic excerpts sealed before any run (0.2.0, 2026-10-22): none of the 106 excerpts labeled unsafe read cleanly and none left a proposal deadline actionable; all 94 clean excerpts needed review, each for a sentence outside the templates. That is one evaluation on synthetic text, not a guarantee, and it is the expected shape of this design: READ is rare and NEEDS REVIEW is the normal result.
Library
from bid_deadline_check import analyze, read_document
report = analyze(read_document("solicitation.pdf"))
for d in report.deadlines:
print(d.kind, d.date, d.time, d.timezone, d.line, d.status, d.ambiguities)
for n in report.needs_review: # unresolved deadline sentences: line, end_line, reason, text
print(n["reason"], n["line"], n["text"])
print(report.outcome) # "read" or "needs_review"
rows = report.checklist() # list of dicts: id, category, item, line, done
Report.outcome is read or needs_review (see "What --strict means"); Report.needs_review lists the
unresolved sentences. Deadline.status is actionable or needs_review, and Deadline.review gives the reasons:
its own ambiguity codes, conflicting_<kind>, line_<n>:<reason> for a review item that may change it, and
withdrawn_<kind> when the same kind is withdrawn elsewhere, and unlisted_review_items when more than 50 items
were found.
Deadline has kind, date, time, timezone, line, text (the clause around the date),
end_line and ambiguities (codes: no_time, no_timezone, time_marker:<text>, invalid_date,
invalid_time, quoted_or_former, implausible_year (outside 1900 to 2100), weekday_mismatch, two_digit_year, negated_or_withdrawn, replaces_earlier_date (the new date of an
"extended/changed/moved from ... to ..." or "... it is now " amendment), issue_date_possible (an issue,
amendment or announcement word, such as "was publicized" or "was delivered", sits between the deadline word and the
date), date_glued_to_text (letters follow the date with no space, as in 2026at), timezone_glued_to_text
(letters or digits follow the zone with no space, as in 17:00:00Zdraft or UTC+05:30junk), other_time:<text> (another clock time in the same
sentence, at most five), unread_date:<text> (another date in a sentence with change wording that was not read as
a deadline, at most five), after_clause, kind_uncertain, timezone_unclear (a zone that may be cut
short or is in an unsupported form, such as UTC+05:30:15), zone_invalid, zone_unverified, zone_unanchored,
zone_conflict:<zone>, statement_split_at_limit, and
context_truncated, which only says the text excerpt was shortened and raises no warning). Each requirement is {"text", "line", "end_line", "truncated"}.
Scope and limits
- It finds what is written; it does not interpret it. This is a checklist aid, not legal or contracting advice.
- Dates and times are reported as written. There is no timezone conversion, no daylight-saving
validation (a winter date marked "EDT" passes), no holiday or next-business-day logic, and no
judgement of whether a submission is on time. Numeric dates such as
10/05/2026are not re-ordered; two-digit years are flagged. - Signed UTC/GMT offsets (
UTC-04:00), ISO offsets (2026-11-05T17:00:00-04:00) and IANA names (America/New_York) are kept exactly. IANA names are checked against the zone database Python finds (zone_invalid;zone_unverifiedwhen there is none, which is usual on Windows without thetzdatapackage); offsets outside UTC-12:00 to UTC+14:00 or with minutes other than 00, 15, 30 or 45 arezone_invalid. An offset followed by a fraction (UTC+05:30.5,GMT+5.5,-04:00.5) is not shortened: it is flaggedtimezone_unclear. A second, different zone written for the same time (5:00 PM ET or CT,5:00 PM EST (Eastern Daylight Time)) is flaggedzone_conflict, and "local time" iszone_unanchored. Zones are compared by name, not converted, so two notations of one moment also need review. - Rules are keyword based and match whole words, so "registered", "conference call", "questionnaire" and "offeror" do not name a kind of deadline. When a clause names both a submission ("submit proposals") and something else ("industry day", "registration"), the submission wins. A date is bound to what is written with it in its own clause, or to a label on the line just above it. Obligations phrased without "shall", "must" or "is/are required to", or not naming an offeror, contractor, quoter, vendor, bidder, proposal, response or submission, are not listed. Wrapped lines are joined into one statement for at most 25 lines or 6000 characters; text cut at that bound, or interrupted by a list item or heading, is flagged.
- Scanned PDFs without a text layer return no text. Run OCR first.
- Limits: files and stdin over 50 MB are refused. At most 2000 deadlines and 10000 requirements are extracted per document; a warning says where extraction stopped. At most 50 unresolved sentences are listed, each with up to 600 characters of its source text; a warning counts the rest. Measured on macOS (arm64) for this candidate on inputs up to 1 MB, analysis grows linearly and takes up to about 6 s per MB of text, with up to about 180 MB peak memory at 1 MB (the slowest and largest shapes found in review: a dense run of numbers; 2000 deadlines followed by zone statements took 2.2 s and 58 to 75 MB). Larger inputs were not measured for this candidate; the previous candidate needed 456 MB and 11 s for 20 MB of dense dated text and 321 MB and 37 s for 20 MB of one-word lines, so a 50 MB input should be expected to need more than a gigabyte and minutes.
- PDFs over 1000 pages or over 5 million extracted characters are refused (checked between pages). By
default PDFs are parsed in a separate Python process that is stopped after 65 s of wall-clock time
and limited to 61 s of CPU time (or the environment's hard CPU limit, if lower); where the operating
system allows it (normally Linux) its address space is also limited to 1 GiB. macOS does not allow
that memory limit (measured), and Windows has no CPU limit, so there only the wall-clock stop
applies. The worker never imports from the current directory. A worker that crashes is reported as a
failure, not as a limit.
read_document(path, isolate=False)parses in-process with between-page checks only, which do not bound a single slow page. Requires pypdf 6.21.1 or later. - CSV output prefixes cells that start with
=,+,-,@, tab or CR with'. This is a common mitigation for formula injection; it is not tested against any particular spreadsheet application. - Output contains solicitation text. Treat it as untrusted data if another program or an AI assistant reads it.
- Out of scope: scoring, bid/no-bid, writing proposal content.
Development
The pull-request workflow is configured for Python 3.10, 3.12 and 3.14 on Linux, plus Python 3.14 on macOS; configured jobs are not executed by this change. Python 3.10 reached end of life on 2026-10-01 and remains the declared minimum.
python -m venv .venv && . .venv/bin/activate
pip install --require-hashes -r requirements/build.txt -r requirements/test.txt
pip install --no-deps --no-build-isolation -e .
pytest
Fixtures are synthetic. Please don't add real solicitations that carry controlled or proprietary markings.
License
MIT © IT Custom Solution
Metadata
Release files for bid-deadline-check 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| bid_deadline_check-0.2.0.tar.gz | 126.4 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bid_deadline_check-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 187.2 kB
Release files / bid_deadline_check-0.2.0.tar.gz
| Download URL | bid_deadline_check-0.2.0.tar.gz |
|---|---|
| Size | 126.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
37104e58c4f6f45bbfcb5411ca3007f8c3c215050a7dcec62ddaa094a65e9b2d
|
|
BLAKE2b-256 checksum How to use checksums |
6fdca5a41ce97f351d451bc71783536872fb44528363f9b124fd715751f2c675
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.12
|
Release files / bid_deadline_check-0.2.0-py3-none-any.whl
| Download URL | bid_deadline_check-0.2.0-py3-none-any.whl |
|---|---|
| Size | 60.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bb0164a672e7d9814206d7ab023ec8f3aabb433d61ab322ca1b74df462dd6778
|
|
BLAKE2b-256 checksum How to use checksums |
d68c4718de89de05793c5fa0255925a8565e3efdcbaa8cd07413f3bcb375032c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.12
|
Security for bid-deadline-check 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.