Handoff notes between tasks¶
Everything one task leaves for another lives here, filed under the task that has to act on it.
Rules¶
- Deferring something ("left for T14", "T19 should decide", a known limitation someone will hit): add a note under the task that should act, in the same commit. If no task fits, put it under Unassigned and tell the user.
- Starting a task: read your task's section and Any task. Also run
git grep "TODO(T16)"with your ID. - Finishing a task: delete the notes your PR handled. Notes you decided not to handle stay, with one line saying why.
- A note is 1–3 lines: what, where in the code, what to do. Long explanations belong in the component's docs; the note links to them.
- Optional: mark the exact spot in code with
# TODO(T14): …. The note here is still required.
Any task¶
q,archetype,account_profile,pd_12manddefault_12mare hidden truth of the synthetic world. They must never reach features, the engine or the UI; only training labels (T10) and validation (T11) may read the outcome, and only the persona selection (T12) reads the archetype and the account profile. (from T01–T05, T12,apps/world/README.md)- New data in the world gets its own
company_rngstream; hash tests pin what T01–T05 generate. (from T01,apps/world/README.md)
T15 — scoring adapter¶
- T14 emits
source_unavailable_fns,source_unavailable_fsspandsource_unavailable_arbitrationflags. Apply a score penalty for each degraded source after the econ team defines penalty values; no such values exist in the current policy. (apps/api/stop_factors.py) - Not handled in T15: the values belong in
config/(shared-folder PR, econ team). Then apply them inScorer.score(apps/api/scoring.py) and show each penalty as its own row inScoreResult.contributions, so points still add up. (T15)
T25 — ИНН entry and consents¶
- Non-demo client accounts still lack a persistent user → company link. The built-in demo profile is restricted to the current T12 company catalog in
apps/api/main.py; add a real ownership binding before applying company-specific access rules to Telegram clients (company_card_v2,submit_v2,preapproval_v2). (from T19, T22, T29a) - The pre-approval batch assumes a standing 218-ФЗ consent given at account opening (
# MOCK:instanding_request,apps/api/preapproval.py); T25's application checkboxes are transient and do not create that consent register. (from T22)
T29a — stable demo companies and application history¶
- The curated T12 persona file can become stale after changing
WORLD_SEED,WORLD_N,WORLD_AS_OF, the generator, or policy. Regenerate it withpython -m apps.world.personas; until then the API exposes a fallback company from the current world (apps/api/demo_companies.py). (from T12)
T30 — staff dossier¶
- The dossier shows the BKI source call and its status. A separate record of our own bureau inquiry is still absent from the adapter payload, so the UI cannot display it as an inquiry event; Track 1 would need to expose it if the team wants that detail. (from T06)
T31 — staff actions UI¶
- No persona can demo the committee's two signatures: the
committeepersona (ИНН3664000050) reaches the committee route with no offers, because grade D–E companies get a zero limit and grades A–C reach the committee only above the 15M ₽ product maximum. The T31 screen supports the signature flow, but end-to-end demo still needs an application with a committee proposal. (from T12)
T32 — risk-manager page¶
- Challenger comparison remains optional and is deferred: the committed validation artifact is generated for the champion only; producing and maintaining a second report is outside T32's screen scope. (
apps/ml/validate.pycan validate v0 when needed.) (from T15) - Not handled: the web client maintains its API types manually, so T32 does not need a generated shared contract. (
ValidationReportremains inapps/ml/validation.py.) (from T11)
T36 — policy simulator¶
- Pass a changed
CreditPolicy(load_policy(path)ordefault_policy().model_copy(...),apps/api/credit_policy.py) intoquote/limit_breakdown; nothing in T16 reads global state besides the default policy. (from T16) - Routing thresholds are
RoutingPolicy(apps/api/routing.py): pass a changed one toroute_application/route_for, e.g. to see how the auto-decision share moves with the 5M ₽ bound. (from T18)
Unassigned¶
compose.vps.yamlpasses only an explicit list of variables, so the VPS stack gets none ofMOCK_SOURCES_*,SCORECARD_*,LOG_LEVELorSOURCE_*: there thesource_downpersona gets a normal bureau answer and INFO logs stay at the default. Addenv_fileor the variables there if the VPS demo needs them (T34 left the deploy setup alone). The nginx route (deploy/nginx/compose.nginx.yamlovercompose.yaml,VPS.md) reads the whole.envand doesn't have this gap. (from T34)- PSI on the live application flow (model monitoring) has no task. T11's PSI compares the holdout with the training sample, so it is ≈ 0 by construction. Applications are now stored (
loan_applications.decision, T19; card features aren't stored, onlyscore.contributions), sopsi(...)fromapps/ml/validation.pycan compare their grades with the training sample. (from T11, T19) - SPEC §7 includes a flag when the ОКВЭД does not match the application purpose, but defines no purpose-to-ОКВЭД mapping. Agree and configure that policy before implementing the flag in
apps/api/stop_factors.py. - The
Scorecardcontract has nofeature_set_version, so the scoring adapter reads it from a passport next to the card (scorecard_vN.training.json, also for the hand-setscorecard_v0). Proposed: a shared-folder PR addingfeature_set_versiontoScorecard; thenload_scorecard_model(apps/api/scoring.py) reads it from the card. (from T10, T15) - Offer policy is hard-coded in
apps/api/offers.pyas# SIMPLIFICATION:constants:MAX_OFFERS = 3,OFFER_VALID_DAYS = 10,REAPPLY_AFTER_DAYS = 90,CREDIT_LINE_UNUSED_FEE = 0.01,PURPOSE_PRODUCT. Proposed: a shared-folder PR adding anoffersblock toconfig/credit_policy.jsonandCreditPolicy(econ team owns the values), then read them from the policy. (from T17) decision_offered.jsonhas the "without the current loan" counterfactual at 890k withresulting_rate: null; the engine gives 900k at 20.61%. Optional shared-folder PR to bring the fixture andCONTRACTS.mdin line withgenerate_offers. (from T17)- Reapply date after an economic decline is the same 90 days for every reason. Per-reason dates (THIN: when history reaches the
FULLbound; leverage: when the loan closes) need policy values from the econ team. After a stop factordecide(apps/api/workflow.py) sets no reapply date at all, thoughdecision_declined.jsonshows one. (from T17, T19) - The SPEC §11 authority matrix is hard-coded in
apps/api/routing.pyas the# SIMPLIFICATION:RoutingPolicydefaults (auto up to 5M ₽FULL/ 1M ₽PARTIAL, underwriter up to 15M ₽, auto grades A–C, committee for D/E above 5M ₽). Proposed: a shared-folder PR adding aroutingblock toconfig/credit_policy.jsonandCreditPolicy(econ team owns the values; also confirm grade E = D), then buildRoutingPolicyfrom it. (from T18) Decision.policy_versionis one string, but a decision is built on three policies;decidewritescredit=<v>;data_quality=<v>;features=<v>(policy_versioninapps/api/workflow.py). Proposed: a shared-folder PR replacing it with separate fields (or a{credit, data_quality, features}object). (from T16, T19)- Staff reason codes are
# SIMPLIFICATION:constantsREASON_CODES/REJECT_REASON_CODESinapps/api/staff_actions.py. Proposed: a shared-folder PR moving them intoconfig/as a versioned directory (econ team and risk owners agree the list). (from T20) - Pre-approval values are
# SIMPLIFICATION:constants inapps/api/preapproval.py:VALID_DAYS = 30,BATCH_EVERY_SECONDS(a day). Proposed: move them with the T17 offer values into anoffersblock ofconfig/credit_policy.json(shared-folder PR, econ team owns the values).PreApprovalis not a contract either: if the UI wants generated types, propose moving it intoapps/api/contracts.py. (from T22) - Staff can't approve more than the computed limit:
Offerrequiresamount <= limit.limit, sochangerefuses it (422). If underwriters should be able to go above the limit with a reason code, that needs a contract change (e.g. an override field onOfferorLimitBreakdown). (from T20) - A failed pre-disbursement condition (today only a supplier invoice that doesn't match the loan) leaves the application in
conditionsfor good:TRANSITIONShas onlyconditions → disbursed, and no staff or back-office action exists (SPEC §12 calls back office optional). Decide on a path (staff fixes and re-checks, orconditions → declined/withdrawn), then add it inapps/api/disbursement.py. (from T21) - Signing has no deadline: documents can be signed after the offer's
valid_until, and there's nodocuments → expiredtransition. If the bank wants one, add it toTRANSITIONSand check it indisbursement.sign. (from T21) - The uploaded statement format (
Statementinapps/api/statements.py: bank, ИНН,MonthlyTurnovermonths) is a client → pipeline exchange but not a contract. Proposed: a shared-folder PR moving it intoapps/api/contracts.pywith a fixture intests/fixtures/. (from T23) - Statement policy is
# SIMPLIFICATION:constants inapps/api/statements.py:STALE_MONTHS = 3,FNS_EXCESS = 0.30,MAX_FILES = 5,MAX_FILE_BYTES. Proposed: a shared-folder PR adding them toconfig/data_quality.json(econ team owns the values). (from T23) - An application in
needs_datawaits for the client forever: SPEC §13 has no timeout for it, onlywithdrawn. If a data request should expire (e.g. after the 10 days an offer lasts), agree the term and add the transition. (from T23) - The
not_foundpersona (apps/world/demo/personas.json) can't be shown in the demo: a demo client may submit only ИНН from the catalog (available_demo_inns,apps/api/demo_companies.py), which holds only companies of the world, so the API answers 422. Let the catalog offer thenot_foundИНН too, or document it as a demo limitation.tests/test_golden_personas.pysubmits it as a non-demo client. (from T33) - v1 code is still in the repo though no v2 screen leads to it:
apps/api/engine.py,catalog.py,personas.py,storage.py(and theS3_*/AWS_*variables in.env.example), the/api/surveys,/api/applications,/api/documents,/api/staff/reviews,/api/catalog,/api/personasroutes inmain.py, the 43-question survey and v1 application screens inapps/web/src/main.tsx, thesurveys/applications/submission_keys/documents/jobstables (needs a migration),scripts/smoke.py,releases/alfa_hack-vps.zip(+.sha256), the committeddist/(Docker builds its own), "компоненты опроса" inapps/mini/README.md.config/rules.jsongoes in a shared-folder PR. The docs already describe v2 only. (from T35) - Need intake sends
declared_annual_revenue: null(apps/web/src/need-intake.tsx), so theinflaterpersona's revenue-mismatch flag can't be shown in the UI: there it gets an automatic 590 000 ₽ offer instead ofin_review. Ask for declared revenue on the need screen, or keep it as the documented demo limitation (README.md, «Что нельзя показать через интерфейс»). (from T35)