How this platform got its shape
Insurance claims are not paid all at once. One opened in 2021 is still being paid out in 2024, so if you want to know how much of that year is still owed, there is nowhere to look it up. Somebody has to build the answer. That is the question written on the card above, and building answers like it is what everything here exists to do.
Six pieces sit underneath. One keeps the claims and reshapes them into tables people can actually ask questions of. One runs that reshaping on a schedule, unattended. One takes claims in as they happen instead of waiting for the nightly run. One writes down every server and database as a file, so the whole thing can be rebuilt from that file rather than from memory. One handles a continuous stream of incoming events, including the ones that turn up late. And one estimates what a policy ought to cost, given who is buying it.
I trained as an actuary, so I knew what these numbers were supposed to mean before I had to decide where they would live. That is most of what this page is about. And because a number nobody can reproduce is a number nobody can defend, a good part of what follows is simply proof of where each one came from. Every claim here points at a file you can open, and wherever I chose one tool over another I have tried to say what would make me choose differently.
- 1. How the Data Travels
- 2. The Six Projects
- 3. Where Four Jobs Meet
- 4. How a Question Becomes a Table
- 5. The Layering Rule
- 6. Actuarial Decisions That Are Really Modelling Decisions
- 7. What Governance Means Here
- 8. Google Cloud Choices, and the Case for the Other One
- 9. What This Deliberately Does Not Do
How the Data Travels
The Six Projects
Synthetic insurance data (policyholders, policies, claims, payments, coverages) loaded into BigQuery. Dataform manages 5 staging tables, 3 intermediate transforms, 6 dimension/fact tables, and 2 report views. 16 data quality assertions run on every deploy.
A Cloud Run container runs the full extract-load-transform cycle on a schedule. Cloud Run + Cloud Scheduler was chosen over Cloud Composer: same reliability at a fraction of the cost for a single-DAG workload.
Pub/Sub topic receives claim events in real time. A Cloud Run subscriber validates, deduplicates, and writes to BigQuery. Demonstrates event-driven architecture alongside the batch ELT pattern.
Every GCP resource behind P01 to P03 is defined in Terraform: reusable modules for BigQuery, GCS, Pub/Sub, Cloud Run and IAM, with dev and prod separated by variable-driven naming. CI checks formatting on every pull request and authenticates to GCP through Workload Identity Federation, so there are no service account keys to leak anywhere in the pipeline. Plans and applies are still run by hand — listed as an open gap under What This Deliberately Does Not Do rather than pretended otherwise.
A true streaming Apache Beam pipeline on Dataflow, where P03 runs Beam in batch. It windows on event time rather than arrival time, fires early results every 30 seconds and again when late events land, still accepts data up to an hour behind the watermark, and routes malformed events to a dead-letter topic with the reason attached.
A GLM (Generalized Linear Model) scores 144K policies with a predicted pure premium. Output lands in dev_pricing_ml.model_scoring and feeds the Pricing Adequacy page of this dashboard.
Where Four Jobs Meet
A claims table is where four jobs meet. In most insurers those jobs sit in different teams, and the handoffs between them are where the time and the disagreements go. Building all four sides is the point of this platform.
How a Question Becomes a Table
This is the actual path taken by the Loss Triangle page, step by step. It is the shape every other page in this dashboard follows.
-
1
Start from the actuarial question, not the data
“How much of the 2021 accident year is still unpaid?” That is a reserving question, and answering it needs cumulative paid amounts arranged by accident year against development year. Nothing about the source files suggests that shape; it comes from the question.
-
2
Decide the grain — this is the decision that matters
A claim is not paid once. It pays out repeatedly over years, which is the entire reason triangles exist. So the base fact is
fct_claim_payments, one row per payment, anddevelopment_yearis derived at that grain from accident date to payment date. Choosing claim grain here would not produce a slow query. It would produce a wrong reserve, quietly. -
3
Pin the source contract before transforming
stg_claim_paymentsdeclarespayment_idunique and four columns non-null. A single duplicated payment does not fail anything downstream — it inflates one cell of the triangle and every development factor derived from it. The assertion is cheaper than the investigation. -
4
Transform in layers, one job each
int_claim_payments_cumulativeaccumulates with a window function partitioned by accident year and ordered by development year.rpt_loss_triangleonly pivots that into columns. Splitting them means the accumulation logic exists once, and the pivot can change without anyone re-deriving the maths. -
5
Assert the thing that would be invisible if wrong
assert_payment_datesrequires that no payment precedes its accident date. A negative development year is not a curiosity: it shifts money into a cell that should not exist, and the triangle still renders perfectly. The assertions worth writing are the ones whose failure looks like a valid answer. -
6
Serve a table, not a query
The dashboard reads
rpt_loss_trianglewith aSELECT *and computes chain-ladder factors in the page. No business logic lives in the presentation layer, so the number on screen and the number in a notebook come from the same place.
The Layering Rule
Sixteen tables that Dataform builds, across five layers. The rule is that each layer is allowed to do one kind of work, so when a page needs a new column the question is which layer does this belong to rather than which query do I edit.
Declared, never created. Dataform reads these tables and is not allowed to write them, which keeps loading the data a separate and auditable step from transforming it. It is also the single most common way to break a first rebuild — run the workflow against an empty raw layer and it fails at the first staging model.
Rename, cast, trim. Strictly one-to-one with the source, no joins and no business logic, so a change in the source has exactly one place to land. This is where the uniqueness and non-null contracts are declared.
Joins and derivations that more than one mart needs: enriched claims, cumulative payments, policy exposure. Not exposed to any consumer, which is what makes them safe to restructure.
The model itself, and the only layer anyone should join against. fct_claims is partitioned by accident_date and clustered on coverage_type, claim_status — the two cuts every actuarial question starts from.
Shaped for one question and cheap to read, because a dashboard page that runs a six-join query is a dashboard page that eventually times out in front of somebody.
Actuarial Decisions That Are Really Modelling Decisions
These four look like schema details in a code review. Each of them is an actuarial judgement that became permanent the moment it was written into a table.
Both dates are in the table; only one is the partition key. Every reserving and pricing cut is by accident year, so partitioning there makes the common query prune to a fraction of the table for free. Report date is the operational view — useful for claims handling, wrong as the physical layout of an analytical fact.
It is computed at payment grain in the warehouse and never recomputed downstream. The alternative — every analyst deriving it in their own query — is exactly how two people produce two reserves from one database and spend a week finding out why.
fct_claims carries total_indemnity and total_expense as separate sums. Allocated expense is not loss. Rolling them into one column overstates severity, distorts the development pattern, and cannot be undone later without going back to payment grain.
The frequency model is a Poisson GLM with a log link and exposure entering as an offset, so a policy in force three months is not compared directly against one in force twelve. Passing exposure as an ordinary covariate lets the model estimate a coefficient for something the arithmetic already fixes at one.
What Governance Means Here
If somebody asks how a figure on this dashboard was produced — an auditor, a regulator, a colleague, or you in eight months — you have to be able to show them, step by step, and get the same number again. Everything below exists for that, and each part is defending against something specific.
- Each step names its own inputs — so the path from a figure on this screen back to the file it came from is something the tool draws for you, not something you reconstruct by reading SQL. In practice that is what
ref()buys. - The whole thing rebuilds from the original files — and the procedure is written down. That is not theoretical: on 14 August 2026 every dataset was empty, and it was rebuilt from the raw files in one afternoon.
- Every calculation is a file under version control — so a change to how a reserve is worked out carries a date, an author and a reason. The live branch only accepts changes through a reviewed pull request with passing checks.
- Checks run as part of building, not afterwards — so a broken assumption stops the tables that depend on it instead of turning up later in a report. Sixteen of them, on uniqueness, missing values, referential integrity, and dates that cannot be right.
- This is an actuarial obligation, not a preference — ASOP No. 23, Data Quality, requires the actuary to review data for appropriateness and to disclose its limitations rather than assume it is fit for the purpose. These checks are that review, written down once and re-run on every build.
- One account, six named permissions —
bigquery.dataEditor,bigquery.jobUser,storage.objectAdmin,pubsub.editor,run.invoker, andiam.serviceAccountTokenCreator. No Owner, no Editor, nothing granted because it was faster. - There are no passwords or keys to leak — the automation proves who it is through a trust relationship tied to this specific repository, so there is no credential file that can be copied, committed, or forgotten.
- Development and production are separate by construction —
dev_claims_*againstclaims_*, resolved from one variable in each model. Promoting is a variable change, not a second copy of the logic.
- New versions are tested before they take traffic — each one goes out with no visitors routed to it, gets checked on its own private address for real content rather than a 200, and only then takes over. If the check fails, the previous version never stopped serving.
- A daily check opens an issue, and closes it — it confirms the site answers, the warehouse is connected, and the old links still redirect. An open issue therefore means broken now, not broken once.
- A runaway query fails instead of billing —
max_bytes_billedcaps any single query at 10 GiB, and the tables are partitioned and clustered so the ordinary questions read a fraction of the data.
Saying so is part of the practice: the same standard that requires reviewing the data requires disclosing what it cannot support.
- Last December’s reserve cannot be reproduced exactly as it stood — policy records are overwritten when they change, so the history they had at the time is gone. For the questions on this dashboard that is fine. For an audit it would not be.
- The pricing model has no formal governance — no challenger model, no monitoring for drift as the book changes, no documented review. The fitted coefficients are inspectable, which is the floor, not the standard.
- The infrastructure description is not enforced automatically — a person notices if the written definition and the live project drift apart.
- There is no catalogue or glossary — what a column means lives in the model that builds it and on this page, which works for one author and stops working for four.
Each is stated in full under What This Deliberately Does Not Do.
Google Cloud Choices, and the Case for the Other One
A decision is only worth recording with the conditions that would reverse it. These are the four that shaped the platform.
Native to BigQuery, so there is no runner to host, no warehouse credentials to manage, and the dependency graph and assertions come with the product at no cost.
The other one wins when: the warehouse is not BigQuery, or the team already has dbt packages, macros and a mature test library that would have to be rewritten.
One scheduled pipeline does not need Airflow. Composer bills a floor of roughly $300 a month for an idle environment; a container that scales to zero costs approximately nothing and fails just as loudly.
The other one wins when: there are many interdependent DAGs, real backfills, per-task retries and a dependency graph a human needs to look at. That is the point where writing your own orchestrator starts costing more than renting one.
Both exist here on purpose. Per-message validation and insertion is stateless, so a subscriber that scales to zero is the right tool. Anything that aggregates across a stream is not, which is why P05 is a genuine Beam streaming job.
The other one wins when: the answer depends on event time rather than arrival time — windowing, watermarks, late-arriving claims, early results that get corrected. A subscriber cannot express that without rebuilding Beam badly.
Pure premium is modelled as a Tweedie GLM with variance power 1.5, which handles a target that is zero for most policies and continuous for the rest. The fitted coefficients matter as much as the predictions, because a rate has to be explainable to a regulator, not just accurate.
The other one wins when: the training set is too large to pull out of BigQuery, or the model family on offer is genuinely adequate and keeping everything in SQL is worth more than control over the fit.
Full control over the HTML, the CSS and the page weight, and no framework chrome announcing “prototype”. It is server-rendered, so every section is a real URL that search engines and analytics can see.
The other one wins when: the audience is internal and the value is entirely in the analysis. Streamlit gets a working data app in an afternoon; this took considerably longer, and that trade only pays when someone outside the team is looking.
Columnar storage, partition pruning, and storage separated from compute — so the claims table can grow without the query plan changing shape.
The other one wins when: the workload is transactional: many small writes, row-level updates, constraints enforced by the database. A claims system is Postgres. A claims warehouse is not.
What This Deliberately Does Not Do
- The data is synthetic. Volumes and distributions are realistic enough to exercise the patterns, and no real policyholder data was used. Treat the numbers on these pages as a demonstration of method, not as a book of business.
- No slowly changing dimensions.
dim_policyoverwrites on change, so a policy that switches coverage loses its history. Correct for the questions this platform answers today; wrong the moment anyone asks a retrospective one, and a Type 2 rebuild is the honest fix rather than a patch. - Terraform is not applied by CI.
terraform fmt -checkis the only automated gate; plans and applies are run by hand. Plan-on-pull-request is the intended next step and is not wired yet, which means drift between the configuration and the live project is still caught by a person rather than a pipeline. - One region, no disaster recovery. Everything runs in
us-central1. That is a deliberate cost decision for a portfolio, and it would be an unacceptable one for a production insurer. - Reserving stops at the triangle. Chain-ladder development factors are computed and shown. Bornhuetter-Ferguson, a tail factor, and any stochastic reserve with a confidence interval around it are not implemented.
Full decision records, cost estimates and post-mortems live in each project's README in the repository.
Cómo esta plataforma tomó su forma
Los siniestros no se pagan de una sola vez. Uno abierto en 2021 se sigue pagando en 2024, así que si quieres saber cuánto de ese año todavía se debe, no hay dónde consultarlo. Alguien tiene que construir la respuesta. Esa es la pregunta escrita en la tarjeta de arriba, y construir respuestas como esa es para lo que existe todo esto.
Debajo hay seis piezas. Una guarda los siniestros y los reacomoda en tablas a las que la gente pueda hacerles preguntas. Otra ejecuta ese reacomodo en un horario fijo, sin que nadie la vigile. Otra recibe los siniestros conforme ocurren, en lugar de esperar al proceso de la noche. Otra deja escrito en un archivo cada servidor y cada base de datos, para que todo se pueda reconstruir desde ahí y no desde la memoria de alguien. Otra procesa un flujo continuo de eventos, incluidos los que llegan tarde. Y la última estima cuánto debería costar una póliza según quién la está comprando.
Estudié actuaría, así que sabía qué debían significar estos números antes de tener que decidir dónde iban a vivir. De eso trata casi toda esta página. Y como un número que nadie puede reproducir es un número que nadie puede defender, buena parte de lo que sigue es simplemente la prueba de dónde salió cada uno. Cada afirmación apunta a un archivo que puedes abrir, y donde elegí una herramienta sobre otra intenté decir qué me haría elegir distinto.
- 1. Cómo Viajan los Datos
- 2. Los Seis Proyectos
- 3. Dónde se Encuentran Cuatro Oficios
- 4. Cómo una Pregunta se Vuelve una Tabla
- 5. La Regla de las Capas
- 6. Decisiones Actuariales que en Realidad son de Modelado
- 7. Qué Significa Gobierno de Datos Aquí
- 8. Decisiones de Google Cloud, y el Argumento a Favor de la Otra Opción
- 9. Lo Que Deliberadamente No Hace
Cómo Viajan los Datos
Los Seis Proyectos
Datos sintéticos de seguros (asegurados, pólizas, siniestros, pagos, coberturas) cargados en BigQuery. Dataform gestiona 5 tablas de staging, 3 transformaciones intermedias, 6 tablas dimensionales y de hechos, y 2 vistas de reportes. 16 validaciones de calidad corren en cada despliegue.
Un contenedor en Cloud Run ejecuta el ciclo completo de extracción, carga y transformación de forma programada. Se eligió Cloud Run + Cloud Scheduler en lugar de Cloud Composer: misma confiabilidad a una fracción del costo para una carga de trabajo de un solo DAG.
Un tópico de Pub/Sub recibe eventos de siniestros en tiempo real. Un suscriptor en Cloud Run valida, deduplica y escribe a BigQuery. Demuestra arquitectura orientada a eventos junto con el patrón batch del ELT.
Cada recurso de GCP detrás de P01 a P03 está definido en Terraform: módulos reutilizables para BigQuery, GCS, Pub/Sub, Cloud Run e IAM, con dev y prod separados por nombres derivados de variables. CI valida el formato en cada pull request y se autentica con GCP mediante Workload Identity Federation, así que no hay llaves de cuenta de servicio que se puedan filtrar en ningún punto del pipeline. Los planes y los applies todavía se corren a mano — declarado como pendiente abierto en Lo Que Deliberadamente No Hace, en vez de aparentar lo contrario.
Un pipeline de Apache Beam en streaming real sobre Dataflow, mientras que P03 corre Beam en modo batch. Ventanea por tiempo de evento y no por tiempo de llegada, emite resultados anticipados cada 30 segundos y otra vez cuando llegan eventos tardíos, acepta datos hasta una hora detrás del watermark, y manda los eventos malformados a un tópico de dead-letter con el motivo adjunto.
Un GLM (Modelo Lineal Generalizado) califica 144 mil pólizas con una prima pura estimada. La salida aterriza en dev_pricing_ml.model_scoring y alimenta la página de Adecuación de Precios de este dashboard.
Dónde se Encuentran Cuatro Oficios
Una tabla de siniestros es donde se encuentran cuatro oficios. En la mayoría de las aseguradoras esos oficios viven en equipos distintos, y los traspasos entre ellos son donde se va el tiempo y donde nacen los desacuerdos. Construir los cuatro lados es justamente el punto de esta plataforma.
Cómo una Pregunta se Vuelve una Tabla
Este es el camino que siguió de verdad la página del Triángulo de Pérdidas, paso a paso. Es la misma forma que sigue cada una de las demás páginas de este dashboard.
-
1
Empezar por la pregunta actuarial, no por los datos
«¿Cuánto del año de ocurrencia 2021 sigue sin pagarse?» Esa es una pregunta de reservas, y responderla exige montos pagados acumulados ordenados por año de ocurrencia contra año de desarrollo. Nada en los archivos fuente sugiere esa forma; la forma viene de la pregunta.
-
2
Definir el grano, que es la decisión que de verdad importa
Un siniestro no se paga una sola vez. Se paga varias veces a lo largo de los años, que es precisamente la razón de existir de los triángulos. Por eso el hecho base es
fct_claim_payments, un renglón por pago, ydevelopment_yearse deriva en ese grano, de la fecha de ocurrencia a la fecha de pago. Elegir grano de siniestro aquí no produciría una consulta lenta: produciría una reserva equivocada, en silencio. -
3
Fijar el contrato con la fuente antes de transformar
stg_claim_paymentsdeclarapayment_idúnico y cuatro columnas obligatorias. Un solo pago duplicado no rompe nada aguas abajo: infla una celda del triángulo y con ella todos los factores de desarrollo que salen de ahí. La validación cuesta menos que la investigación. -
4
Transformar por capas, una tarea cada una
int_claim_payments_cumulativeacumula con una función de ventana particionada por año de ocurrencia y ordenada por año de desarrollo.rpt_loss_trianglesolo lo pivotea a columnas. Separarlos significa que la lógica de acumulación existe una vez, y que el pivote puede cambiar sin que nadie vuelva a derivar las matemáticas. -
5
Validar aquello que sería invisible si estuviera mal
assert_payment_datesexige que ningún pago sea anterior a su fecha de ocurrencia. Un año de desarrollo negativo no es una curiosidad: mueve dinero a una celda que no debería existir, y el triángulo se sigue viendo perfecto. Las validaciones que vale la pena escribir son aquellas cuya falla parece una respuesta válida. -
6
Servir una tabla, no una consulta
El dashboard lee
rpt_loss_trianglecon unSELECT *y calcula los factores chain-ladder en la página. Ninguna lógica de negocio vive en la capa de presentación, así que el número en pantalla y el número en un notebook salen del mismo lugar.
La Regla de las Capas
Dieciséis tablas que Dataform construye, repartidas en cinco capas. La regla es que cada capa puede hacer un solo tipo de trabajo, de modo que cuando una página necesita una columna nueva la pregunta sea a qué capa pertenece esto y no qué consulta edito.
Se declaran, nunca se crean. Dataform lee estas tablas y no tiene permiso de escribirlas, lo que mantiene la carga de datos como un paso separado y auditable respecto de la transformación. También es la forma más común de romper una reconstrucción desde cero: si se corre el workflow contra una capa cruda vacía, falla en el primer modelo de staging.
Renombrar, castear, limpiar. Estrictamente uno a uno con la fuente, sin joins y sin lógica de negocio, para que un cambio en el origen tenga exactamente un lugar donde aterrizar. Aquí se declaran los contratos de unicidad y de campos obligatorios.
Joins y derivaciones que más de un mart necesita: siniestros enriquecidos, pagos acumulados, exposición de pólizas. No se exponen a ningún consumidor, y eso es justo lo que las hace seguras de reestructurar.
El modelo en sí, y la única capa contra la que alguien debería hacer joins. fct_claims está particionada por accident_date y agrupada por coverage_type, claim_status: los dos cortes con los que empieza cualquier pregunta actuarial.
Con la forma exacta de una pregunta y baratas de leer, porque una página que corre una consulta de seis joins es una página que tarde o temprano se cae enfrente de alguien.
Decisiones Actuariales que en Realidad son de Modelado
Las cuatro parecen detalles de esquema en una revisión de código. Cada una es un juicio actuarial que quedó permanente en el momento en que se escribió dentro de una tabla.
Ambas fechas están en la tabla; solo una es la llave de partición. Todo corte de reservas y de tarifa es por año de ocurrencia, así que particionar ahí hace que la consulta habitual lea una fracción de la tabla sin costo extra. La fecha de reporte es la vista operativa: útil para gestión de siniestros, equivocada como distribución física de un hecho analítico.
Se calcula en el grano de pago dentro del almacén y nunca se vuelve a calcular aguas abajo. La alternativa, que cada analista lo derive en su propia consulta, es exactamente la forma en que dos personas producen dos reservas distintas de una misma base y se llevan una semana averiguando por qué.
fct_claims lleva total_indemnity y total_expense como sumas distintas. El gasto asignado no es siniestro. Juntarlos en una sola columna sobreestima la severidad, distorsiona el patrón de desarrollo, y ya no se puede deshacer sin regresar al grano de pago.
El modelo de frecuencia es un GLM Poisson con liga logarítmica y la exposición entrando como offset, para que una póliza vigente tres meses no se compare directo contra una vigente doce. Pasar la exposición como covariable normal deja que el modelo estime un coeficiente para algo que la aritmética ya fija en uno.
Qué Significa Gobierno de Datos Aquí
Si alguien pregunta cómo se produjo una cifra de este dashboard — un auditor, un regulador, un colega, o tú mismo en ocho meses — tienes que poder mostrárselo paso por paso y llegar otra vez al mismo número. Todo lo de abajo existe para eso, y cada parte protege contra algo concreto.
- Cada paso declara sus propios insumos — así que el camino desde una cifra en esta pantalla hasta el archivo del que salió es algo que la herramienta dibuja sola, no algo que reconstruyes leyendo SQL. En la práctica, eso es lo que compra
ref(). - Todo se reconstruye desde los archivos originales — y el procedimiento está escrito. No es teoría: el 14 de agosto de 2026 todos los conjuntos de datos aparecieron vacíos y se reconstruyeron desde los archivos crudos en una tarde.
- Cada cálculo es un archivo versionado — así que un cambio en cómo se calcula una reserva trae fecha, autor y motivo. La rama en vivo solo acepta cambios por pull request revisado y con las validaciones en verde.
- Las revisiones corren al construir, no después — así que un supuesto roto detiene a las tablas que dependen de él en vez de aparecer más tarde en un reporte. Son dieciséis: unicidad, campos vacíos, integridad referencial y fechas que no pueden ser correctas.
- Esto es una obligación actuarial, no una preferencia — El ASOP No. 23, Data Quality, exige que el actuario revise la información y declare sus limitaciones, en vez de suponer que sirve para el propósito. Estas revisiones son exactamente eso, escritas una vez y ejecutadas en cada construcción.
- Una cuenta, seis permisos con nombre —
bigquery.dataEditor,bigquery.jobUser,storage.objectAdmin,pubsub.editor,run.invokeryiam.serviceAccountTokenCreator. Nada de Owner, nada de Editor, nada otorgado porque era más rápido. - No hay contraseñas ni llaves que se puedan filtrar — la automatización demuestra quién es mediante una relación de confianza atada a este repositorio, así que no existe un archivo de credenciales que se pueda copiar, subir por error u olvidar.
- Desarrollo y producción están separados por construcción —
dev_claims_*contraclaims_*, resuelto desde una variable en cada modelo. Promover es cambiar esa variable, no mantener una segunda copia de la lógica.
- Las versiones nuevas se prueban antes de recibir tráfico — cada una sale sin visitantes dirigidos a ella, se revisa en su propia dirección privada buscando contenido real y no un 200, y apenas entonces toma el relevo. Si la revisión falla, la versión anterior nunca dejó de servir.
- Una revisión diaria abre un issue, y lo cierra — confirma que el sitio responde, que el almacén está conectado y que los enlaces viejos siguen redirigiendo. Un issue abierto significa entonces roto ahora, no roto alguna vez.
- Una consulta desbocada falla en vez de facturar —
max_bytes_billedlimita cualquier consulta a 10 GiB, y las tablas están particionadas y agrupadas para que las preguntas habituales lean una fracción de los datos.
Decirlo es parte de la práctica: el mismo estándar que exige revisar la información exige declarar lo que no puede sostener.
- La reserva de diciembre pasado no se puede reproducir tal como estaba — los registros de pólizas se sobrescriben al cambiar, así que la historia que tenían en ese momento ya no existe. Para las preguntas de este dashboard alcanza. Para una auditoría no.
- El modelo de pricing no tiene gobierno formal — no hay modelo retador, no hay monitoreo de deriva conforme cambia la cartera, no hay revisión documentada. Los coeficientes se pueden inspeccionar, que es el piso y no el estándar.
- La descripción de la infraestructura no se aplica sola — si la definición escrita y el proyecto vivo se separan, lo nota una persona.
- No hay catálogo ni glosario — qué significa cada columna vive en el modelo que la construye y en esta página, lo que funciona con un autor y deja de funcionar con cuatro.
Cada uno se explica completo en Lo Que Deliberadamente No Hace.
Decisiones de Google Cloud, y el Argumento a Favor de la Otra Opción
Una decisión solo vale la pena documentarla junto con las condiciones que la revertirían. Estas son las cuatro que le dieron forma a la plataforma.
Nativo de BigQuery: no hay runner que hospedar, no hay credenciales de almacén que administrar, y el grafo de dependencias y las validaciones vienen incluidos sin costo.
La otra opción gana cuando: el almacén no es BigQuery, o el equipo ya tiene paquetes, macros y una librería madura de pruebas en dbt que habría que reescribir.
Un solo pipeline programado no necesita Airflow. Composer cobra un piso de unos 300 dólares al mes por un ambiente ocioso; un contenedor que escala a cero cuesta prácticamente nada y falla igual de fuerte.
La otra opción gana cuando: hay muchos DAGs interdependientes, backfills de verdad, reintentos por tarea y un grafo de dependencias que un humano necesita mirar. Ese es el punto donde escribir tu propio orquestador empieza a costar más que rentar uno.
Los dos existen aquí a propósito. Validar e insertar mensaje por mensaje no guarda estado, así que un suscriptor que escala a cero es la herramienta correcta. Cualquier cosa que agregue a lo largo del flujo no lo es, y por eso P05 es un job de streaming real en Beam.
La otra opción gana cuando: la respuesta depende del tiempo del evento y no del de llegada: ventanas, watermarks, siniestros que llegan tarde, resultados anticipados que después se corrigen. Un suscriptor no puede expresar eso sin reconstruir Beam a medias.
La prima pura se modela con un GLM Tweedie de potencia de varianza 1.5, que maneja un objetivo que vale cero para la mayoría de las pólizas y es continuo para el resto. Los coeficientes ajustados importan tanto como las predicciones, porque una tarifa tiene que poder explicarse ante un regulador, no solo ser precisa.
La otra opción gana cuando: el conjunto de entrenamiento es demasiado grande para sacarlo de BigQuery, o la familia de modelos disponible alcanza de sobra y quedarse en SQL vale más que el control sobre el ajuste.
Control total sobre el HTML, el CSS y el peso de la página, sin la estética de framework que grita «prototipo». Se renderiza en el servidor, así que cada sección es una URL real que buscadores y analítica pueden ver.
La otra opción gana cuando: la audiencia es interna y todo el valor está en el análisis. Streamlit te da una app de datos funcionando en una tarde; esto tomó bastante más, y ese intercambio solo se paga cuando alguien de fuera del equipo está mirando.
Almacenamiento columnar, poda de particiones y cómputo separado del almacenamiento, para que la tabla de siniestros pueda crecer sin que cambie la forma del plan de ejecución.
La otra opción gana cuando: la carga es transaccional: muchas escrituras pequeñas, actualizaciones a nivel de renglón, restricciones que impone la base. Un sistema de siniestros es Postgres. Un almacén de siniestros no.
Lo Que Deliberadamente No Hace
- Los datos son sintéticos. Los volúmenes y las distribuciones son lo bastante realistas para ejercitar los patrones, y no se usó información de ningún asegurado real. Los números de estas páginas son una demostración de método, no una cartera.
- No hay dimensiones de cambio lento.
dim_policysobrescribe al cambiar, así que una póliza que cambia de cobertura pierde su historia. Es correcto para las preguntas que esta plataforma responde hoy y deja de serlo en cuanto alguien haga una pregunta retrospectiva; la solución honesta es reconstruirla como Tipo 2, no parcharla. - Terraform no lo aplica CI.
terraform fmt -checkes la única compuerta automática; los planes y los applies se corren a mano. El plan en cada pull request es el siguiente paso previsto y todavía no está conectado, lo que significa que la deriva entre la configuración y el proyecto vivo la detecta una persona y no un pipeline. - Una sola región, sin plan de recuperación. Todo corre en
us-central1. Es una decisión deliberada de costo para un portafolio, y sería inaceptable para una aseguradora en producción. - Las reservas se detienen en el triángulo. Se calculan y se muestran los factores de desarrollo chain-ladder. Bornhuetter-Ferguson, un factor de cola, y cualquier reserva estocástica con un intervalo de confianza alrededor no están implementados.
Los registros de decisión completos, las estimaciones de costo y los post-mortems viven en el README de cada proyecto dentro del repositorio.