/// <summary> /// 取得文件的基本信息,文件名、大小和上传时间 /// </summary> /// <param name="sysNo">流水号</param> /// <returns></returns> public JsonResult GetFileInfo(string sysNo) { var fileName = sysNo + ".rar"; FileInfo info = new FileInfo(ConfigurationManager.AppSettings["AttachmentPath1"] + fileName); if (!info.Exists) { BillUtils ut = new BillUtils(); BillSv sv = (BillSv)ut.GetBillSvInstance(ut.GetBillEnType(sysNo)); info = new FileInfo(Path.Combine(sv.GetAttachmentPath(sysNo), fileName)); if (!info.Exists) { return(Json(new { success = false })); } } return(Json(new { success = true, am = new { file_name = fileName, file_size = info.Length / 1024 + "K", upload_time = info.CreationTime.ToString() } })); }
/// <summary> /// Report any bill that is new this week. /// A bill is new if it was initially reviewed during the past week. /// </summary> /// <param name="reports">Collected bill reports</param> /// <param name="past_week">The dates bounding the past calendar week</param> /// <param name="sw">StreamWriter which will be written to the report file</param> private void NewThisWeek(StreamWriter sw, BillReportCollection reports, DateRange past_week) { StartTable(sw, "New Of-Interest Bills This Week"); foreach (var report in reports) { string path = $"{Path.Combine(Config.Instance.HtmlFolder, BillUtils.EnsureNoLeadingZerosBill(report.Measure))}.html"; if (File.Exists(path)) { string contents = FileUtils.FileContents(path); if (BillUtils.IsNewThisWeek(contents, past_week)) { if (report.IsDead()) { continue; // Don't bother reporting dead bills } if (report.IsPositionNone()) { continue; // Don't bother reporting bills on which we have no position } if (report.IsChaptered()) { continue; // Don't bother reporting chaptered bills } ReportOneBill(sw, report); } } } EndTable(sw); }
public void TestNewOrChangePrefix(string right_answer, string week_start, string week_end, string report_file_path) { if (File.Exists(report_file_path)) { BaseController.EnsureGlobalData(); // Report constructor requires GlobalHistoryTable var range = new Scout2.WeeklyReport.WeeklyReport.DateRange(week_start, week_end); var report = new BillReport(report_file_path); var answer = BillUtils.NewOrChangePrefix(range, report); switch (right_answer) { case "New": Assert.True(answer.Contains("NEW"), $"TestNewOrChangePrefix: {answer} is not correct, should contain NEW."); break; case "Update": Assert.True(answer.Contains("UPDATED"), $"TestNewOrChangePrefix: {answer} is not correct, should contain UPDATED."); break; case "None": Assert.True(answer.Length == 0, $"TestNewOrChangePrefix: {answer} is not correct, should be of 0 length."); break; default: Assert.True(false, $"TestNewOrChangePrefix: {answer} is not a valid NewOrChangePrefix response."); break; } } else { Assert.True(false, $"TestNewOrChangePrefix: {report_file_path} does not exist."); } }
/// <summary> /// Report any bill that changed this week. /// </summary> /// <param name="reports">Collected bill reports</param> /// <param name="past_week">The dates bounding the past calendar week</param> /// <param name="sw">StreamWriter which will be written to the report file</param> private void ChangesThisWeek(StreamWriter sw, BillReportCollection reports, DateRange past_week) { StartTable(sw, "Changes This Week in Bills Of Interest"); foreach (var report in reports) { if (report.IsPositionNone()) { continue; // Don't bother reporting bills on which we have no position } if (report.IsDead()) { continue; // Don't bother reporting dead bills (e.g. Joint Rule 56) } string path = $"{Path.Combine(Config.Instance.HtmlFolder, BillUtils.EnsureNoLeadingZerosBill(report.Measure))}.html"; if (File.Exists(path)) { string contents = FileUtils.FileContents(path); if (BillUtils.IsNewThisWeek(contents, past_week)) { continue; // Don't report new bills. } var dt = DateFromLastAction(report); if (DateUtils.DateIsInPastWeek(dt, past_week)) { ReportOneBill(sw, report); } } } EndTable(sw); }
private void Predictions(StreamWriter sw, BillReportCollection reports, DateRange past_week) { StartPredictionTable(sw, "Predicted Committee Routing"); foreach (var report in reports) { if (report.IsPositionNone()) { continue; // Don't report bills on which we have no position } if (report.IsDead()) { continue; // Don't report dead bills (e.g. Joint Rule 56) } if (report.IsChaptered()) { continue; // Don't report chaptered bills } string path = $"{Path.Combine(Config.Instance.HtmlFolder, BillUtils.EnsureNoLeadingZerosBill(report.Measure))}.html"; string committees = IndividualReport.PreviousReport.Committees(path); string likelihood = IndividualReport.PreviousReport.Likelihood(path); if (committees.Length > 0 || likelihood.Length > 0) { ReportPrediction(sw, past_week, report, committees, likelihood); } } EndTable(sw); }
/// <summary> /// When the initial report on a bill is generated, it is in so-called "canonical" form, meaning that /// no position is taken, there is no meaningful summary, and so on. In essence, a blank form is generated. /// The user is then given the opportunity to edit the blank form, turning it into an actual report on the bill. /// Once that editing is complete, the database BillRow table is updated with the position given in the /// edited bill report. /// </summary> /// <param name="measure"></param> public static void GenerateCanonicalReport(string measure) { // Generate the canonical bill BillRow row = BillRow.Row(BillUtils.Ensure4DigitNumber(measure)); List <string> contents = BaseReportContents(row, string.Empty); string path = $"{Path.Combine(Config.Instance.HtmlFolder, BillUtils.EnsureNoLeadingZerosBill(measure))}.html"; WriteTextFile(contents, path); // Let the user edit the canonical bill, which has been written to disk. var process = Process.Start("notepad.exe", path); if (process != null) { process.WaitForExit(); } else { LogAndShow($"CreateNewReports.GenerateCanonicalReport: Failed to start Notepad for {path}."); } // Update the database position BillRow.UpdatePosition(BillUtils.Ensure4DigitNumber(measure), ""); GetPositionAndSummary(path, out List <string> summary, out List <string> position_list); string first_line = position_list.FirstOrDefault(); string position = first_line != null?Regex.Replace(first_line, ".*?:(.*)", "$1") : "None Specified"; BillRow.UpdatePosition(measure, position.Trim()); LogAndShow($"Completed {path}"); }
public BillRow(string bill_tbl_row, List <BillProfile> profiles) { var x = GlobalData.MostRecentEachBill; var fields = Regex.Split(bill_tbl_row, @"\t"); MeasureType = fields[(int)field_offsets.measure_type]; MeasureNum = fields[(int)field_offsets.measure_num]; var measure_num_4 = BillUtils.Ensure4DigitNumber(MeasureNum); var profile = GlobalData.MostRecentEachBill.Where (item => (item.Type == MeasureType) && (item.Number == measure_num_4)).ToList(); Bill = BillUtils.Ensure4DigitNumber($"{MeasureType}{MeasureNum}"); //Lob //NegativeScore //PositiveScore //Position //BillVersionID //Author //Title Location = fields[(int)field_offsets.current_location]; //Location2nd //MeasureState //CurrentHouse CurrentStatus = fields[(int)field_offsets.measure_state]; }
public void TestIsNewThisWeek(int new_week, string report_date, string week_start, string week_end) { string report_contents = $" <b>Summary</b>: (Reviewed {report_date})"; var range = new Scout2.WeeklyReport.WeeklyReport.DateRange(week_start, week_end); bool is_new_week = new_week != 0; bool test = is_new_week == BillUtils.IsNewThisWeek(report_contents, range); Assert.True(is_new_week == BillUtils.IsNewThisWeek(report_contents, range)); }
private void ReportPrediction(StreamWriter sw, DateRange past_week, BillReport report, string committees, string likelihood) { string prefix = BillUtils.NewOrChangePrefix(past_week, report); sw.WriteLine("<tr>"); sw.WriteLine($"<td>{prefix} {report.Measure} {report.Title} ({report.Author})</td>"); sw.WriteLine($"<td>{committees}</td> <td>{likelihood}</td>"); sw.WriteLine("</tr>"); }
private DateTime DateFromHistoryTable(string path) { string bill = Path.GetFileNameWithoutExtension(path); BillRow row = BillRow.Row(BillUtils.Ensure4DigitNumber(bill)); string name_ext = Path.GetFileName(row.Lob); // BillVersionTable bill_xml is unique BillVersionRow bv_row = GlobalData.VersionTable.Scalar(name_ext); List <BillHistoryRow> history = GlobalData.HistoryTable.RowSet(bv_row.BillID); DateTime.TryParse(history.First().ActionDate, out DateTime date_result); return(date_result); }
private void Create(string bill, string path) { try { BillRow row = BillRow.Row(BillUtils.Ensure4DigitNumber(bill)); List <string> contents = CreateIndividualReport.ReportContents(row, path); WriteTextFile(contents, path); var message = $"Regenerated {row.Bill} report."; BaseController.LogThis(message); } catch (Exception ex) { LogAndThrow($"IndividualReport.Create: {ex.Message}."); } }
private void HighestPriority(StreamWriter sw, BillReportCollection reports, DateRange past_week) { StartTable(sw, "Highest Priority Bills"); foreach (string bill in Config.Instance.HighestPriority) { var measure = BillUtils.EnsureDashAndNoLeadingZeros(bill); var report = (from item in reports where (item.Measure == measure) select item).FirstOrDefault(); if (report != null) { string prefix = BillUtils.NewOrChangePrefix(past_week, report); ReportOneBillWithPrefix(sw, report, prefix); } } EndTable(sw); }
public void TestExtractHouseNumber(int right_bool, string input, string right_house, string right_number) { bool desired = right_bool != 0 ? true : false; var result = BillUtils.ExtractHouseNumber(input, out string house, out string number); Assert.True(result == desired); if (right_house != null) { Assert.True(house == right_house); } if (right_number != null) { Assert.True(number == right_number); } }
public PositionFromReport(string a, string b) { Position = b; if (a.Contains("-")) { Measure = a; MeasureNoDash = Regex.Replace(Measure, "-", string.Empty); } else { MeasureNoDash = a; BillUtils.ExtractHouseNumber(MeasureNoDash, out string house, out string number); Measure = $"{house}-{number}"; } }
public void TestDateFromLastAction(string right_answer, string report_file_path) { if (File.Exists(report_file_path)) { BaseController.EnsureGlobalData(); // Report constructor requires GlobalHistoryTable var report = new BillReport(report_file_path); var correct = DateTime.Parse(right_answer); var answer = BillUtils.DateFromLastAction(report); Assert.True(correct.Year == answer.Year && correct.Month == answer.Month && correct.Day == answer.Day, $"TestNewOrChangePrefix: {answer.ToShortDateString()} is not correct, should be {correct.ToShortDateString()}."); } else { Assert.True(false, $"TestNewOrChangePrefix: {report_file_path} does not exist."); } }
/// <summary> /// Allow the user to update the current bill report. /// If the position is changed, the database BillRow table is updated with the new position. /// </summary> /// <param name="measure"></param> private void UpdateReport(string measure) { string path = $"{Path.Combine(Config.Instance.HtmlFolder, BillUtils.EnsureNoLeadingZerosBill(measure))}.html"; var process = Process.Start("notepad.exe", path); if (process != null) { process.WaitForExit(); } else { BaseController.LogAndShow($"CreateNewReports.GenerateCanonicalReport: Failed to start Notepad for {path}."); } // Update the database position BillRow.UpdatePosition(BillUtils.Ensure4DigitNumber(measure), ""); BaseController.LogAndShow($"Update for {path} is complete."); }
public void TestDateOfInitialReview(int is_valid, string right_answer, string report_contents) { DateTime.TryParse(right_answer, out DateTime correct); try { var result = BillUtils.DateOfInitialReview(report_contents); if (is_valid == 0) { Assert.True(false, $"Unexpected DateOfInitialReview success parsing {report_contents}"); } Assert.True(correct.Year == result.Year && correct.Month == result.Month && correct.Day == result.Day); } catch (Exception) { if (is_valid == 1) { Assert.True(false, $"Unexpected DateOfInitialReview failure parsing {report_contents}"); } } }
/// <summary> /// 开始下载文件 /// </summary> /// <param name="sysNo">流水号</param> /// <returns></returns> public FileStreamResult BeginDownloadFile(string sysNo) { string fileName = sysNo + ".rar"; string absoluFilePath = ConfigurationManager.AppSettings["AttachmentPath1"] + fileName; FileInfo info = new FileInfo(absoluFilePath); if (!info.Exists) { BillUtils ut = new BillUtils(); BillSv sv = (BillSv)ut.GetBillSvInstance(ut.GetBillEnType(sysNo)); absoluFilePath = Path.Combine(sv.GetAttachmentPath(sysNo), fileName); info = new FileInfo(absoluFilePath); if (!info.Exists) { return(null); } } Wlog("开始下载文件", sysNo); return(File(new FileStream(absoluFilePath, FileMode.Open), "application/octet-stream", Server.UrlEncode(fileName))); }
private void Modify_Monitor(StreamWriter sw, BillReportCollection reports, DateRange past_week) { StartTable(sw, "Modify/Monitor"); foreach (var report in reports) { if (report.IsDead()) { continue; // Don't bother reporting dead bills } if (report.IsChaptered()) { continue; // Chaptered bills are reported elsewhere } if (report.IsPositionModifyOrMonitor()) { string prefix = BillUtils.NewOrChangePrefix(past_week, report); ReportOneBillWithPrefix(sw, report, prefix); } } EndTable(sw); }
public void TestCheckManualUpdate(string right_answer, string measure, string testCommittees) { // Customize the list of manual updates var cache = Config.Instance.ManualCommitteeChanges; if (testCommittees is null) { Config.Instance.ManualCommitteeChanges = null; } else { Config.Instance.ManualCommitteeChanges = new List <string>() { testCommittees } }; // Answer whether this bill contains a manual update var answer = BillUtils.CheckManualUpdate(measure); switch (right_answer) { case "Manual": Assert.True(answer.Contains("MANUAL"), $"TestCheckManualUpdate: {answer} is not correct, should contain MANUAL."); break; case "": Assert.True(answer.Length == 0, $"TestCheckManualUpdate: {answer} is not correct, should be of 0 length."); break; default: Assert.True(false, $"TestCheckManualUpdate: {answer} is not a valid CheckManualUpdate response."); break; } // Restore the production list of manual updates. Config.Instance.ManualCommitteeChanges = cache; }
public BillReport(string file_path) { try { var lines = File.ReadLines(file_path).ToList(); // Find Author, Measure, Title var line = lines.Find(x => x.Contains("Title</b>:")); if (line == null) { throw new ApplicationException($"BillReport ctor: {file_path} contents has no title line."); } Measure = rx_measure.Match(line).ToString(); if (!Measure.Contains("B-")) { throw new ApplicationException($"BillReport ctor: {file_path} contents has no measure."); } Author = rx_author.Match(line).ToString(); if (!Author.Contains("(") || !Author.Contains(")")) { throw new ApplicationException($"BillReport ctor: {file_path} contents has no author."); } Author = Author.Trim(new[] { '(', ')' }); var index = line.IndexOf(')'); // Title follows closing author parenthese if (index < 0) { throw new ApplicationException($"BillReport ctor: {file_path} contents has no closing author parenthese."); } Title = line.Substring(index + 1).Trim(); // At least one space before title // Find Position line = lines.Find(x => x.Contains("Position</b>:")); if (line == null) { throw new ApplicationException($"BillReport ctor: {file_path} contents has no position."); } index = line.IndexOf(':'); // Position follows colon Position = line.Substring(index + 1).Trim(); // At least one space before position // Find summary statement line = lines.Find(x => x.Contains("ShortSummary</b>:")); if (line == null) { throw new ApplicationException($"BillReport ctor: {file_path} contents has no summary statement."); } index = line.IndexOf(':'); // Summary follows colon OneLiner = line.Substring(index + 1).Trim(); // At least one space before summary // Find Last Action. The correct source for Last Action is the history data imported from the // legislature site. That data is considered original, data emitted by this program is derivative. BillUtils.ExtractHouseNumber(Measure, out string house, out string number); var bill_id = $"{house}{number}"; BillHistoryRow mostRecent = GlobalData.HistoryTable.LatestFromHouseNumber(bill_id); LastAction = $"{DateUtils.Date(mostRecent.ActionDate)} {mostRecent.Action}"; LastAction = LastAction.Replace("''", "'"); // Governor''s becomes Governor's // Find WIC (Welfare and Institutions Code) and LPS (Lanterman-Petris-Short Act) var most_recent = GlobalData.MostRecentEachBill .Where(row => row.BillID == BillUtils.Ensure4DigitNumber(Measure)).FirstOrDefault(); if (most_recent != null) { if (File.Exists(most_recent.LobPath)) { var contents = FileUtils.FileContents(most_recent.LobPath); // Need text without CRLF var match_string = @"Section\s+\d+.*?Welfare\s+and\s+Institutions\s+Code"; MatchCollection matches = Regex.Matches(contents, match_string); WIC = matches.Count > 0 ? "Yes" : "No"; // Is WIC if WIC referenced LPS = "No"; // LPS defaults to "No" if (WIC == "Yes") { foreach (var match in matches) { var str = match.ToString(); var section_number = Regex.Match(str, @"\d+").ToString(); if (Int64.TryParse(section_number, out long numberResult)) { // If section is between 5000 and 6000, Lanterman-Petris_Short is referenced if (numberResult >= 5000 && numberResult < 6000) { LPS = "Yes"; } } } } } } else { //throw new ApplicationException($"BillReport.ctor({file_path}): {Measure} not in GlobalData.MostRecentEachBill"); } } catch (Exception ex) { BaseController.LogAndShow(ex.Message); throw; } }
public void TestEnsure4DigitNumberNoThrow(string input, string right_answer) { Assert.True(BillUtils.Ensure4DigitNumber(input) == right_answer); }
public void TestEnsureDashAndNoLeadingZeros(string input, string right_answer) { var result = BillUtils.EnsureDashAndNoLeadingZeros(input); Assert.True(result == right_answer); }
/// <summary> /// Build GlobalData.BillRows from scratch. /// The steps for this are /// 1. Import the BILL_TBL.dat data into GlobalData.BillRows. This fills 9 of the 15 BillRow fields. /// </summary> /// <param name="form1">The main (Form1) display form. Display messages and progress here.</param> /// <param name="path_bill_tbl">Fully-qualified path to BILL_TBL.dat</param> /// <param name="mostRecentEachBill">For each bill, the Bill_Identifier identifying the most recent version</param> private void BuildBillRowsTable(Form1 form1, string path_bill_tbl, List <BillProfile> profiles) { // Import BILL_TBL.dat, which is the legislative site's information on bills before the legislature. // Trim all non-bill items during the import -- we want only type AB and SB (Assembly and Senate Bills) var bill_table_wrapper = new Bill_Tbl_Wrapper(); bill_table_wrapper.ReadYourself(); // Import BILL_TBL.dat List <BillRow> rows_with_positions = BillRow.RowsetByQuery("Select * from Billrows Where Position <> ''"); List <BillRow> all_rows = BillRow.RowSet(); List <string> reports = BillUtils.HtmlFolderContents(); // Re-create GlobalData.BillRows, using data from bill_table_wrapper and elsewhere. GlobalData.BillRows.Clear(); List <string> SkipIf = new List <String>() { "Chaptered", "Died", "Enrolled", "Failed", "Failed Passage in Committee", "Vetoed" }; foreach (var item in bill_table_wrapper) { // Don't process bills that will not progress further in the legislature. //string result = SkipIf.FirstOrDefault(s => s == item.Current_status); //if (result != null) continue; // Use data from bill_table_wrapper. Some fields are left blank. var bill_row = new BillRow(); bill_row.MeasureType = item.Measure_type; // e.g. AB bill_row.MeasureNum = BillUtils.Ensure4DigitNumber(item.Measure_num); // e.g. 0010 bill_row.Bill = $"{bill_row.MeasureType}{BillUtils.EnsureNoLeadingZerosBill(bill_row.MeasureNum)}"; // e.g. AB10 //bill_row.Lob = item.; //bill_row.NegativeScore = item; //bill_row.PositiveScore = item; //bill_row.Position = item; // Hand Entered, e.g. Monitor bill_row.BillVersionID = VersionID(item); // e.g. 20190AB199INT //bill_row.Author = item; //bill_row.Title = item; bill_row.Location = item.Current_location; // e.g. CX08 bill_row.Location2nd = item.Current_secondary_loc; // e.g. Committee bill_row.MeasureState = item.Measure_state; // e.g. Amended Assembly bill_row.CurrentHouse = item.Current_house; // e.g. Assembly bill_row.CurrentStatus = item.Current_status; // e.g. In Committee Process // Obtain the author, title, lob file path, and positive/negative scores from the profile for this bill var four_digit_billid = BillUtils.Ensure4DigitNumber(bill_row.Bill); var profile = (from x in profiles where x.Identifier.BillID == four_digit_billid select x).First(); if (profile != null) { bill_row.Author = profile.Identifier.Author; bill_row.Title = profile.Identifier.Title; bill_row.Lob = profile.Identifier.LobPath; bill_row.NegativeScore = profile.NegScore; bill_row.PositiveScore = profile.PosScore; } else { throw new ApplicationException($"InitializeBillRows.BuildBillRowsTable: Bill {bill_row.Bill} is present in bill_table_wrapper, but not in GlobalData.Profiles."); } // Fill in the Position data -- the position we are taking on this bill. If we have a position, it is // in one of two places // 1. The database BillRows table (not all bills have a report), or var pos = (from x in rows_with_positions where x.Bill == bill_row.Bill select x).FirstOrDefault(); if (pos != null) { bill_row.Position = pos.Position; } // 2. If that table hasn't been updated, in the actual report // If the two are in conflict, the bill report wins. var short_id = BillUtils.EnsureNoLeadingZerosBill(bill_row.Bill); var report_file = (from x in reports where x.Contains($"{short_id}.html") select x).FirstOrDefault(); if (report_file != null) { var report = new BillReport(report_file); bill_row.Position = report.Position; } // Add this row to GlobalData.BillRows GlobalData.BillRows.Add(bill_row); } // Sort the table before returning it. Ordered by bill ID, e.g. AB0001, communicates well GlobalData.BillRows = GlobalData.BillRows.OrderBy(a => a.Bill).ToList(); }
public void TestEnsure4DigitNumberThrow(string input) { Assert.Throws <ApplicationException>(() => BillUtils.Ensure4DigitNumber(input)); }
/// <summary> /// Generate a new individual report or re-generate one that already exists. /// </summary> /// <param name="row">Bill description from the BillRows table</param> /// <param name="path">Path to the .lob file for the bill's current version</param> /// <returns></returns> protected static List <string> BaseReportContents(BillRow row, string path) { string name_ext = Path.GetFileName(row.Lob); // BillVersionTable bill_xml is unique BillVersionRow bv_row = GlobalData.VersionTable.Scalar(name_ext); List <BillHistoryRow> history = GlobalData.HistoryTable.RowSet(bv_row.BillID); var location_code = history.First().TernaryLocation; var location_code_row = GlobalData.LocationTable.Scalar(location_code); string appropriation = bv_row.Appropriation; string author = row.Author; string bill_id = row.Bill; string fiscal = bv_row.FiscalCommittee; string house = history.First().PrimaryLocation; string last_action = FindLastAction(row); string location = location_code_row == null?BillUtils.WhenNullLocationCode(history) : location_code_row.Description; string local_pgm = bv_row.LocalProgram; string number = row.MeasureNum.TrimStart('0'); string title = row.Title; string type_house = $"{bill_id.First()}B"; string vers_id = row.BillVersionID; string vote = bv_row.VoteRequired; // Position and Summary data come from the previous version of the bill report // If the passed path is null or empty, then this method was called when no previous report exists. // When regenerating a report, there is a previous report. var summary = new List <string>(); var position = new List <string>(); var shortsummary = string.Empty; var committees = string.Empty; var likelihood = string.Empty; if (CommonUtils.IsNullOrEmptyOrWhiteSpace(path)) { // do nothing } else { summary = PreviousReport.Summary(path); position = PreviousReport.Position(path); shortsummary = PreviousReport.ShortSummary(path); committees = PreviousReport.Committees(path); likelihood = PreviousReport.Likelihood(path); } // With all necessary data obtained, generate the report file. // Both initial creation (new bill) and re-generation are processed here. List <string> result = BeginIndividualReport(type_house, number, author, title); // Review result.AddRange(ReportReview(summary)); // Position result.AddRange(ReportPosition(position)); // Short Summary, Committees Prediction and Passage Likelihood result.AddRange(ReportSummaryPredictLikelihood(shortsummary, committees, likelihood)); // Status, Location, etc result.AddRange(ReportStatusLocationEtc(location, last_action, vote, appropriation, fiscal, local_pgm, history)); // Bill History result.AddRange(ReportHistory(history)); return(result); }
public void TestNoDash(string input, string right_answer) { Assert.True(BillUtils.NoDash(input) == right_answer); }