lmi
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [lmi] Group premium quotes


From: Greg Chicares
Subject: Re: [lmi] Group premium quotes
Date: Sun, 21 Jun 2015 17:05:51 +0000
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Icedove/31.3.0

On 2015-06-19 23:30, Greg Chicares wrote:
> On 2015-06-19 14:20, Vadim Zeitlin wrote:
>> On Wed, 17 Jun 2015 15:24:52 +0000 Greg Chicares <address@hidden> wrote:

Should we use the existing class Ledger, with its somewhat "stringly"
interface, or create something new? Reuse should be the default answer;
the burden of proof lies with anything new, which necessarily makes lmi
bigger and more complex.

Up to now, I had thought that something new would be better.

- So few data are needed here that we may as well just get them directly.
Yet if the data are already available in class Ledger, or easily added
thereto without making it too heavy, then the burden of proof is unmet.

- If we grab the data directly from class Input, and perform only the few
specialized premium calculations that aren't needed elsewhere, then these
premium quotes are lightweight and fast. Yet adding new lightweight code
instead of reusing something heavy results in a heavier system. And it is
by no means crucial for premium quotes to be faster than illustrations.

- At first it seemed that new inputs would be required, which class Ledger
doesn't and shouldn't know about. However, we soon realized that new inputs
can and should be avoided. We are not trying to write a standalone premium
quote system with nifty bells and whistles, without time and cost limits;
we're trying to create a new report from an existing illustration system,
cheaply and quickly, without adding complexity that makes maintaining lmi
significantly harder or costlier.

- The subject gets changed when I bring this up...but it's a safe bet that
at least one distinct but similar report will be needed for inforce billing.
If we write new code (read input, do some calculations, pass some data) for
one report, we'll do it for other(s). If we discipline ourselves to create
this report from the existing class Ledger dataset, then other such reports
will be easier.

So far, the burden of proof hasn't been met. Let's reexamine the details.

>>  As for the "two pieces" part, for me this is more of an advantage than
>> anything else as it would make each of them simpler and, for the PDF one,
>> easier to test.
> 
> Very good point. The easiest way to regression-test the data that a
> PDF contains is to dump the data as flat text, and test that.

We already have regression tests for class Ledger.

>> The main drawback I see with this approach is that it
>> requires to decide in advance which data is passed from the GUI code to the
>> PDF generation function and that if we decide wrongly, we would need to
>> update the interface to allow passing more data.
> 
> I can almost see the dataset in its entirety.
> 
> The word "almost" is scary, though. We are seeking some clarifications.

class Ledger is plenary by design: it contains everything we need for
creating a wide variety of illustrations (with various '.xsl' files).

>>  This could be mitigated by using "generic" data structures such as passing
>> the entire product_data (with all of its keys) and a self-describing 2D
>> array for the premiums (i.e. an array containing information about its
>> columns) and if we foresee the need for the information in the generated
>> PDF to vary, then this would probably the best thing to do. But if we need
>> to generate just the kind of summaries discussed so far, it would, IMHO, be
>> better to avoid such broad stringly type interface and use a concrete
>> struct.
> 
> Agreed. BTW, lmi's 'class ledger' is just such a "stringly" interface.
> And I think that's a good design for its purpose: we can pass its data
> through a variety of different XSL-FO transformations. In this case,
> though, I think we'll want to dispense with that heavyweight "stringly"
> intermediate class.

Members of class Ledger and its HAS-A subordinates aren't quite "stringly".
However, Ledger::write(xml::element&) creates an inherently "stringly"
xml object. In particular, it formats every number. If anyone wants, say,
"$1234567" where this function provides "1,234,567", then I'm prepared
to explain that what we use for illustrations is good enough--as above:

| we're trying to create a new report from an existing illustration system,
| cheaply and quickly, without adding complexity that makes maintaining lmi
| significantly harder or costlier.

Whether to use class Ledger itself or its available xml equivalent is just
an implementation detail.

>>  BTW, a somewhat related question: will the totals (for each column) be
>> computed by lmi or PDF generation code? Computing the sum is not a
>> difficult operation, as maths go, so I guess the PDF code could be trusted
>> with it, or do you still see it as too risky?
> 
> It doesn't matter. Each premium in every column is already rounded to
> cents. Adding them together will give substantially the same result
> no matter where we do it.

BTW, if we run a full composite illustration, we get totals for free:
  Ledger& Ledger::PlusEq(Ledger const& a_Addend);
Hey, and if the numbers are so gigantic that they overflow the standard
printing width, they get scaled, too:
  void Ledger::AutoScale();
And when we handle the anticipated inforce billing report, I have to
suppose this:
  void Ledger::ZeroInforceAfterLapse();
fills some need that we would otherwise have to reimplement. Oh, and
all this code has received considerable testing.

>> GC>  - The PDF code could call back into lmi for each datum where it's 
>> needed.
>> GC>    That would probably work well for footnotes:
>> GC>      product_data const& p = [whatever];
>> GC>      std::string const marketing_name_footnote = 
>> p.datum("MarketingNameFootnote");
>> GC>      [then use that string in the PDF code]
>> GC>    But it might not work so well for premiums. We certainly want to keep 
>> the
>> GC>    premium-calculation details encapsulated, as the PDF code has no need 
>> to
>> GC>    see how those calculations are performed. Maybe we need to create a
>> GC>    quote_premiums object and pass that to the PDF code.
>> 
>>  The trouble is that I don't know where do these premiums live right now.
>> Are they already available in some existing data structure?
> 
> Not all of them are even calculated now. I've been looking into the
> pieces I need to use and thinking about how to tie this all together,
> and have come to the conclusion that I should just take your specimen
> PDF code and add my code to it. Then we can step back, take a look at
> the resulting...prototype specimen, and it'll be much clearer whether
> and where we should slice it.
> 
> On second though, let me sketch it out below...
                 ^t^
>>  If not, then I think we do need some object which would be part of the
>> bigger struct describing the entire group premium document to generate.
>> I.e. I see something like this, in pseudo-C++:
>> 
>>      struct premium_document_description
>>      {
>>          string
[...]
>>              ,company
>>              ,prepared_by
> 
> Input::CorporationName and Input::AgentName, respectively.

LedgerInvariant::CorpName and LedgerInvariant::ProducerName.

We improved the names in class Input, probably when we xml-ized it.
Regrettably, we didn't change the Ledger names; but we translate them
already:

void LedgerInvariant::Init(BasicValues* b)
{
...
    CorpName                = (*b->Input_)["CorporationName"].str();
    ProducerName            = (*b->Input_)["AgentName"].str();

and we're quite confident that the translation is solid, because we
rely on it for production.

If we use the xml dataset that Ledger::write(xml::element&) produces,
then these are simply <CorpName> and <ProducerName>.

> The
> complication here is that, conceptually, there's
>  - a top level, multiple_cell_document::case_parms_, which should
>    represent input fields that are (again, conceptually) exactly
>    the same for all participants; and
>  - a detail level, multiple_cell_document::cell_parms_, at which
>    every input field is actually present and modifiable (because
>    that makes the system maximally flexible and powerful).
> Here, the end users want the top-level inputs, but they don't
> enter top-level data, and a custom GUI is out of the question
> for this "premium quotes" task. I think the best solution is to
> read these values from first cell (participant), and then assert
> that they match in every other cell--assertion failure being an
> error that prevents any PDF output from being generated.

LedgerInvariant::PlusEq() does this:

    // TODO ?? Probably we should assert that these don't vary by life.
    CorpName                    = a_Addend.CorpName;
    MasterContractNumber        = a_Addend.MasterContractNumber;
    ProducerName                = a_Addend.ProducerName;

We can resolve that "TODO" in place if we want; or we can leave it
alone and decide that premium quotes call for stricter validation,
which is very easily done.

>>              ,summary_data
> 
> Input::Comments

In the Ledger xml this is <Comments>.

> Input::InsuredName, Input::IssueAge

<Insured1>, <Age>

> Input::ProjectedSalary, Input::SpecifiedAmount

<newcolumn><column name="Salary"><duration number="0" column_value="100,000"/>
<newcolumn><column name="SpecAmt"><duration number="0" 
column_value="1,000,000"/>

For the present purpose, we want only the first elements.

> Here's a complication that we must sidestep: these are input sequences,
> but we aren't going to "realize" them by calling RealizeProjectedSalary()
> or RealizeSpecifiedAmount(). In the one and only use case, these are
> (string representations of) numeric scalars. Any other use constitutes
> undefined behavior as defined in the language standard, with no diagnostic
> required. If you enter "100000, 3; 50000" then we just copy that string to
> output; or, if you like, we convert it to a numeric scalar, reformat it as
> a string, and no tears if an exception gets thrown for unconvertible input.
> 
> As an alternative, we might call Input::RealizeAllSequenceInput() and then
> construct a yare_input object and use that. I'll probably do that if it's
> handy for the premium calculations. In that case, I guess we'd just select
> the first element of each input sequence: yare_input::ProjectedSalary[0]
> for example; optionally, we could assert that all elements of that
> vector are equal.

If we use class Ledger, then all that stuff gets done exactly the same way
it's already done for illustrations, and, again:

| we're trying to create a new report from an existing illustration system,
| cheaply and quickly, without adding complexity that makes maintaining lmi
| significantly harder or costlier.

>>                  ,premium
>>                  ,premium_with_waiver
>>                  ,premium_with_adb
>>                  ,premium_with_waiver_and_adb
> 
> I will need to calculate these explicitly; I haven't yet worked out exactly
> how I want to do that.

Same way as LedgerInvariant::InitPrem, InitSevenPayPrem, and InitTgtPrem.

Adding four scalars to class Ledger doesn't make lmi appreciably more
ponderous. If the compliance department asked us to write these premiums
in an illustration header, we'd add them to class Ledger without a
second thought. The same goes for any footnote text.

>>          vector<participant> participants;

Now you have Ledger objects instead, and PrintRosterTabDelimited() shows
one way to get them. Because--stop me if I'm wrong, but--I think it's
clear now that we want to reuse class Ledger instead of writing custom
single-purpose code.

> This brings us back to the question whether, as we iterate through
> multiple_cell_document::cell_parms_, we want to
>  - populate such an intermediate structure, through which we'll later
>      iterate to write PDF rows; or
>  - write PDF rows as we process each cell_parms_ element.
> This is discussed in more detail below.
> 
>> And then
>> 
>>      void create_premium_summary_pdf
>>          (string const& file_name
>>          ,premium_document_description const& data
>>          );

I see two ways to process the data:

- The way the existing group roster uses it: one row at a time, by calling
  something like PrintRosterTabDelimited().

- All at once, which means calling something like PrintRosterTabDelimited()
  repeatedly and saving each row instead of printing it immediately.

I'm starting to see this as a generalized "roster" facility. The existing
group roster is one flavor; premium quotes are another; and the inforce
bill that I see on the horizon is a third.

- GUI: The existing "Print roster to spreadsheet" command could become
  "Print roster..." with a submenu, with its toolbar button perhaps
  becoming a combobox (which I suppose wx can put in a toolbar).

- Calling code: we could either expand enum mcenum_emission (which has
  thirteen enumerators now, and I guess we can portably have thirty-two
  total, being log2(1.0+ULONG_MAX), which should suffice for another
  decade or so; or add an argument:

double emit_ledger
    (fs::path const& filepath
    ,fs::path const& tsv_filepath
    ,Ledger const&   ledger
    ,mcenum_emission emission
+   ,mcenum_roster_t roster_type
    )
...
    if(emission & mce_emit_spreadsheet)
        {
+       if(enumerator_for_original_roster == roster_type) {
        LMI_ASSERT(!tsv_filepath.empty());
        PrintCellTabDelimited
            (ledger
            ,   tsv_filepath.string()
            +   configurable_settings::instance().spreadsheet_file_extension()
            );
+       } else if ... [a 'case' statement would seem better]
        }

>>  What do you think?

Right back atcha!




reply via email to

[Prev in Thread] Current Thread [Next in Thread]